342 lines
8.3 KiB
Vue
342 lines
8.3 KiB
Vue
<template>
|
||
<view class="container">
|
||
<view class="card hero" @tap="goStore">
|
||
<view class="row between">
|
||
<view>
|
||
<view class="trow">
|
||
<image v-if="store.logo" class="logo" :src="store.logo" mode="aspectFill" />
|
||
<view class="title">{{ store.name }}</view>
|
||
</view>
|
||
<view class="sub muted">{{ store.openHours }} · {{ store.address }}</view>
|
||
</view>
|
||
<view class="pill" @tap.stop="callStore">
|
||
<text class="p-t">电话</text>
|
||
</view>
|
||
</view>
|
||
<view class="locrow row between">
|
||
<view class="loc muted">{{ locText }} · {{ distText }}</view>
|
||
<view class="nav" @tap.stop="navToStore">导航</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card banner">
|
||
<swiper class="sw" circular autoplay interval="3500" duration="500">
|
||
<swiper-item>
|
||
<view class="b b1">
|
||
<view class="b-t">新客体验</view>
|
||
<view class="b-s">水氧净透 ¥99</view>
|
||
</view>
|
||
</swiper-item>
|
||
<swiper-item>
|
||
<view class="b b2">
|
||
<view class="b-t">皮肤管理</view>
|
||
<view class="b-s">补水修护 ¥238</view>
|
||
</view>
|
||
</swiper-item>
|
||
<swiper-item>
|
||
<view class="b b3">
|
||
<view class="b-t">次卡特惠</view>
|
||
<view class="b-s">5 次 ¥899</view>
|
||
</view>
|
||
</swiper-item>
|
||
</swiper>
|
||
</view>
|
||
|
||
<view class="grid4">
|
||
<view class="card q" @tap="goBooking">
|
||
<view class="q-t">立即预约</view>
|
||
<view class="q-s muted">选时间/技师</view>
|
||
</view>
|
||
<view class="card q" @tap="goProjects">
|
||
<view class="q-t">全部项目</view>
|
||
<view class="q-s muted">价格/时长</view>
|
||
</view>
|
||
<view class="card q" @tap="goCoupons">
|
||
<view class="q-t">我的次卡</view>
|
||
<view class="q-s muted">剩余次数</view>
|
||
</view>
|
||
<view class="card q" @tap="goMember">
|
||
<view class="q-t">会员中心</view>
|
||
<view class="q-s muted">积分/储值</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section row between">
|
||
<view class="h">人气推荐项目</view>
|
||
<view class="more" @tap="goProjects">全部</view>
|
||
</view>
|
||
|
||
<view class="grid2">
|
||
<ProjectGridCard v-for="p in hotProjects" :key="p.id" :project="p" />
|
||
</view>
|
||
|
||
<view class="card store">
|
||
<view class="row between">
|
||
<view class="sh">门店信息</view>
|
||
<view class="more" @tap="goStore">查看</view>
|
||
</view>
|
||
<view class="muted sline">营业时间:{{ store.openHours }}|休息:{{ store.restDay }}</view>
|
||
<view class="muted sline">地址:{{ store.address }}</view>
|
||
<view class="muted sline">电话:{{ store.phone }}</view>
|
||
<view class="row between sline">
|
||
<view class="muted">技师团队:点击查看</view>
|
||
<view class="rt">
|
||
<view class="star">★ {{ teamRating }}</view>
|
||
<view class="like" @tap.stop="likeTeam">赞 {{ likeCount }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<AiFloat />
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import AiFloat from '@/components/AiFloat.vue'
|
||
import ProjectGridCard from '@/components/ProjectGridCard.vue'
|
||
import { storeProfile, projects } from '@/common/mockData'
|
||
|
||
export default {
|
||
components: { AiFloat, ProjectGridCard },
|
||
data() {
|
||
return {
|
||
store: storeProfile,
|
||
hotProjects: projects.slice(0, 4),
|
||
locText: '定位:获取中…',
|
||
distText: '距离:计算中…',
|
||
teamRating: 4.9,
|
||
likeCount: 128
|
||
}
|
||
},
|
||
onShow() {
|
||
this.locText = '定位:获取中…'
|
||
this.distText = '距离:计算中…'
|
||
uni.getLocation({
|
||
type: 'gcj02',
|
||
success: (res) => {
|
||
this.locText = '定位:上海市 静安区(示例)'
|
||
const d = this.calcDistanceMeter(res.latitude, res.longitude, this.store.latitude, this.store.longitude)
|
||
this.distText = `距离:${this.formatDistance(d)}`
|
||
},
|
||
fail: () => {
|
||
this.locText = '定位:未开启(可在“我的”页授权定位)'
|
||
this.distText = '距离:--'
|
||
}
|
||
})
|
||
},
|
||
methods: {
|
||
goProjects() {
|
||
uni.switchTab({ url: '/pages/projects/list' })
|
||
},
|
||
goBooking() {
|
||
const p = Array.isArray(projects) && projects.length ? projects[0] : null
|
||
if (!p) {
|
||
this.goProjects()
|
||
return
|
||
}
|
||
uni.navigateTo({ url: `/pages/booking/create?projectId=${p.id}` })
|
||
},
|
||
goCoupons() {
|
||
uni.navigateTo({ url: '/pages/coupons/list' })
|
||
},
|
||
goMember() {
|
||
uni.switchTab({ url: '/pages/member/index' })
|
||
},
|
||
goStore() {
|
||
uni.navigateTo({ url: '/pages/store/detail' })
|
||
},
|
||
callStore() {
|
||
uni.makePhoneCall({ phoneNumber: this.store.phone })
|
||
},
|
||
navToStore() {
|
||
const lat = this.store.latitude
|
||
const lng = this.store.longitude
|
||
if (typeof lat !== 'number' || typeof lng !== 'number') {
|
||
uni.showToast({ title: '门店坐标未配置', icon: 'none' })
|
||
return
|
||
}
|
||
uni.openLocation({
|
||
latitude: lat,
|
||
longitude: lng,
|
||
name: this.store.name,
|
||
address: this.store.address
|
||
})
|
||
},
|
||
calcDistanceMeter(lat1, lng1, lat2, lng2) {
|
||
if ([lat1, lng1, lat2, lng2].some((x) => typeof x !== 'number')) return NaN
|
||
const toRad = (v) => (v * Math.PI) / 180
|
||
const R = 6371000
|
||
const dLat = toRad(lat2 - lat1)
|
||
const dLng = toRad(lng2 - lng1)
|
||
const a =
|
||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2)
|
||
return 2 * R * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||
},
|
||
formatDistance(m) {
|
||
if (!Number.isFinite(m)) return '--'
|
||
if (m < 1000) return `${Math.max(1, Math.round(m))}m`
|
||
const km = m / 1000
|
||
if (km < 10) return `${km.toFixed(1)}km`
|
||
return `${Math.round(km)}km`
|
||
},
|
||
likeTeam() {
|
||
this.likeCount += 1
|
||
uni.showToast({ title: '已点赞', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.hero {
|
||
padding: 26rpx;
|
||
}
|
||
.trow {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14rpx;
|
||
}
|
||
.logo {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
border-radius: 16rpx;
|
||
}
|
||
.title {
|
||
font-size: 40rpx;
|
||
font-weight: 950;
|
||
}
|
||
.sub {
|
||
margin-top: 8rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
.loc {
|
||
font-size: 24rpx;
|
||
}
|
||
.locrow {
|
||
margin-top: 14rpx;
|
||
}
|
||
.nav {
|
||
padding: 12rpx 16rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba(59, 130, 246, 0.14);
|
||
color: #1d4ed8;
|
||
font-size: 24rpx;
|
||
font-weight: 900;
|
||
}
|
||
.pill {
|
||
padding: 14rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba(17, 24, 39, 0.06);
|
||
}
|
||
.p-t {
|
||
font-weight: 700;
|
||
font-size: 26rpx;
|
||
}
|
||
.banner {
|
||
margin-top: 18rpx;
|
||
padding: 0;
|
||
overflow: hidden;
|
||
}
|
||
.sw {
|
||
height: 220rpx;
|
||
}
|
||
.b {
|
||
height: 220rpx;
|
||
padding: 26rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-end;
|
||
}
|
||
.b1 {
|
||
background: linear-gradient(135deg, rgba(17, 24, 39, 1) 0%, rgba(59, 130, 246, 1) 100%);
|
||
}
|
||
.b2 {
|
||
background: linear-gradient(135deg, rgba(3, 105, 161, 1) 0%, rgba(16, 185, 129, 1) 100%);
|
||
}
|
||
.b3 {
|
||
background: linear-gradient(135deg, rgba(124, 58, 237, 1) 0%, rgba(59, 130, 246, 1) 100%);
|
||
}
|
||
.b-t {
|
||
color: #fff;
|
||
font-weight: 950;
|
||
font-size: 42rpx;
|
||
}
|
||
.b-s {
|
||
margin-top: 10rpx;
|
||
color: rgba(255, 255, 255, 0.84);
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
}
|
||
.grid4 {
|
||
margin-top: 18rpx;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 18rpx;
|
||
}
|
||
.q {
|
||
padding: 18rpx;
|
||
}
|
||
.q-t {
|
||
font-weight: 950;
|
||
font-size: 30rpx;
|
||
}
|
||
.q-s {
|
||
margin-top: 8rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
.section {
|
||
margin-top: 28rpx;
|
||
padding: 6rpx 2rpx;
|
||
}
|
||
.h {
|
||
font-size: 32rpx;
|
||
font-weight: 950;
|
||
}
|
||
.more {
|
||
color: #3b82f6;
|
||
font-weight: 700;
|
||
font-size: 28rpx;
|
||
}
|
||
.grid2 {
|
||
margin-top: 14rpx;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 18rpx;
|
||
}
|
||
.store {
|
||
margin-top: 18rpx;
|
||
padding: 22rpx;
|
||
}
|
||
.rt {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14rpx;
|
||
}
|
||
.star {
|
||
padding: 10rpx 14rpx;
|
||
border-radius: 999rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 900;
|
||
background: rgba(245, 158, 11, 0.14);
|
||
color: #b45309;
|
||
}
|
||
.like {
|
||
padding: 10rpx 14rpx;
|
||
border-radius: 999rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 900;
|
||
background: rgba(17, 24, 39, 0.06);
|
||
color: rgba(17, 24, 39, 0.86);
|
||
}
|
||
.sh {
|
||
font-size: 32rpx;
|
||
font-weight: 950;
|
||
}
|
||
.sline {
|
||
margin-top: 12rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
</style>
|
||
|