初始化
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view>
|
||||
<view class="card head">
|
||||
<image v-if="p.cover" class="cover" :src="p.cover" mode="aspectFill" />
|
||||
<view class="name">{{ p.name }}</view>
|
||||
<view class="row between meta">
|
||||
<view class="muted">服务时长:{{ p.durationMin }} 分钟</view>
|
||||
<view class="price">
|
||||
<text class="yen">¥</text>
|
||||
<text class="num">{{ p.price }}</text>
|
||||
<text class="ori muted" v-if="p.originPrice">¥{{ p.originPrice }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="row between meta2">
|
||||
<view class="tag2">{{ categoryName }}</view>
|
||||
<view class="muted">评分 {{ rating }} / 5.0</view>
|
||||
</view>
|
||||
<view class="tags row">
|
||||
<view class="tag">{{ p.fitFor }}</view>
|
||||
<view class="tag warn">禁忌:{{ p.taboo }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card block">
|
||||
<view class="b-t">服务介绍</view>
|
||||
<view class="b-v muted">{{ p.desc }}</view>
|
||||
</view>
|
||||
|
||||
<view class="card block">
|
||||
<view class="b-t">功效说明</view>
|
||||
<view class="b-v muted" v-for="x in effects" :key="x">· {{ x }}</view>
|
||||
</view>
|
||||
|
||||
<view class="card block">
|
||||
<view class="b-t">服务流程</view>
|
||||
<view class="b-v muted" v-for="x in steps" :key="x">· {{ x }}</view>
|
||||
</view>
|
||||
|
||||
<view class="card block">
|
||||
<view class="b-t">适合人群</view>
|
||||
<view class="b-v muted">{{ p.fitFor }}</view>
|
||||
</view>
|
||||
|
||||
<view class="card block">
|
||||
<view class="b-t">禁忌说明</view>
|
||||
<view class="b-v muted">{{ p.taboo }}</view>
|
||||
</view>
|
||||
|
||||
<view class="card block">
|
||||
<view class="b-t">用户评价</view>
|
||||
<view class="review">
|
||||
<view class="r1 row between">
|
||||
<view class="rn">顾客 A</view>
|
||||
<view class="muted">5.0</view>
|
||||
</view>
|
||||
<view class="muted rt">做完肤感很通透,过程舒服,推荐。</view>
|
||||
</view>
|
||||
<view class="review">
|
||||
<view class="r1 row between">
|
||||
<view class="rn">顾客 B</view>
|
||||
<view class="muted">4.8</view>
|
||||
</view>
|
||||
<view class="muted rt">店里环境很干净,技师很专业。</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="space"></view>
|
||||
|
||||
<view class="fixbar">
|
||||
<view class="bar card">
|
||||
<view class="btn btn-ghost a" @tap="book">立即预约</view>
|
||||
<view class="btn btn-primary b" @tap="buy">立即购买</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<AiFloat />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiFloat from '@/components/AiFloat.vue'
|
||||
import { categories, projects } from '@/common/mockData'
|
||||
|
||||
const fallbackCategories = [
|
||||
{ id: 'c1', name: '体验爆款' },
|
||||
{ id: 'c2', name: '面部护理' },
|
||||
{ id: 'c3', name: '身体养生' },
|
||||
{ id: 'c4', name: '美甲美睫' },
|
||||
{ id: 'c5', name: '特惠套餐' }
|
||||
]
|
||||
|
||||
const fallbackProjects = [
|
||||
{
|
||||
id: 'p1',
|
||||
categoryId: 'c1',
|
||||
name: '水氧净透体验',
|
||||
price: 99,
|
||||
originPrice: 199,
|
||||
durationMin: 60,
|
||||
fitFor: '初次体验、暗沉、出油',
|
||||
taboo: '近期激光/微针术后 7 天内不建议',
|
||||
cover: '',
|
||||
desc: '轻盈水氧 + 净透护理,适合快速提升肤感与通透度。'
|
||||
},
|
||||
{
|
||||
id: 'p2',
|
||||
categoryId: 'c2',
|
||||
name: '深层清洁黑头管理',
|
||||
price: 168,
|
||||
originPrice: 268,
|
||||
durationMin: 75,
|
||||
fitFor: 'T 区油脂旺盛、黑头粉刺',
|
||||
taboo: '炎症爆痘期需评估后进行',
|
||||
cover: '',
|
||||
desc: '清洁、舒缓、收敛三步走,减少反复出油与毛孔困扰。'
|
||||
},
|
||||
{
|
||||
id: 'p3',
|
||||
categoryId: 'c2',
|
||||
name: '补水修护屏障护理',
|
||||
price: 238,
|
||||
originPrice: 368,
|
||||
durationMin: 80,
|
||||
fitFor: '敏感泛红、干燥紧绷',
|
||||
taboo: '过敏急性期请先咨询',
|
||||
cover: '',
|
||||
desc: '修护屏障与舒缓敏感,适合换季与长期干燥人群。'
|
||||
},
|
||||
{
|
||||
id: 'p4',
|
||||
categoryId: 'c3',
|
||||
name: '肩颈舒缓筋膜放松',
|
||||
price: 188,
|
||||
originPrice: 288,
|
||||
durationMin: 60,
|
||||
fitFor: '久坐办公、肩颈僵硬',
|
||||
taboo: '急性损伤与发热期不建议',
|
||||
cover: '',
|
||||
desc: '深度放松肌群与筋膜,改善紧绷与酸胀。'
|
||||
},
|
||||
{
|
||||
id: 'p5',
|
||||
categoryId: 'c5',
|
||||
name: '皮肤管理次卡 5 次',
|
||||
price: 899,
|
||||
originPrice: 1199,
|
||||
durationMin: 60,
|
||||
fitFor: '长期管理、复购人群',
|
||||
taboo: '具体项目以到店评估为准',
|
||||
cover: '',
|
||||
desc: '灵活使用,随时预约,到店核销自动扣次。'
|
||||
},
|
||||
{
|
||||
id: 'p6',
|
||||
categoryId: 'c4',
|
||||
name: '轻奢美甲 · 单色',
|
||||
price: 168,
|
||||
originPrice: 238,
|
||||
durationMin: 75,
|
||||
fitFor: '通勤、日常、显白',
|
||||
taboo: '甲面破损/感染需先处理',
|
||||
cover: '',
|
||||
desc: '干净利落的通勤单色,显白耐看,可按肤色搭配色卡。'
|
||||
},
|
||||
{
|
||||
id: 'p7',
|
||||
categoryId: 'c4',
|
||||
name: '自然单根美睫 · 清透款',
|
||||
price: 198,
|
||||
originPrice: 298,
|
||||
durationMin: 90,
|
||||
fitFor: '自然放大双眼、日常耐看',
|
||||
taboo: '眼部炎症/过敏期不建议',
|
||||
cover: '',
|
||||
desc: '清透自然的单根嫁接,整体更轻盈,适合新手与通勤。'
|
||||
},
|
||||
{
|
||||
id: 'p8',
|
||||
categoryId: 'c3',
|
||||
name: '全身精油舒压 · 90 分钟',
|
||||
price: 298,
|
||||
originPrice: 398,
|
||||
durationMin: 90,
|
||||
fitFor: '压力大、睡眠欠佳、疲劳',
|
||||
taboo: '孕期/发热/急性炎症期不建议',
|
||||
cover: '',
|
||||
desc: '精油舒压与深度放松结合,帮助缓解疲劳与紧绷。'
|
||||
},
|
||||
{
|
||||
id: 'p9',
|
||||
categoryId: 'c5',
|
||||
name: '新客体验套餐 · 3 次',
|
||||
price: 299,
|
||||
originPrice: 499,
|
||||
durationMin: 60,
|
||||
fitFor: '初次体验、想要快速改善肤感',
|
||||
taboo: '具体项目以到店评估为准',
|
||||
cover: '',
|
||||
desc: '高性价比新客套餐,适合建立基础皮肤管理节奏。'
|
||||
}
|
||||
]
|
||||
|
||||
const runtimeProjects = Array.isArray(projects) && projects.length ? projects : fallbackProjects
|
||||
const runtimeCategories = Array.isArray(categories) && categories.length ? categories : fallbackCategories
|
||||
|
||||
const detailPreset = {
|
||||
p1: {
|
||||
rating: 5.0,
|
||||
effects: ['净透提亮肤感', '补水保湿', '舒缓修护'],
|
||||
steps: ['洁面清洁', '水氧净透护理', '精华导入', '舒缓收尾与防护']
|
||||
},
|
||||
p2: {
|
||||
rating: 4.8,
|
||||
effects: ['减少黑头粉刺困扰', '清理油脂与角质', '舒缓收敛毛孔观感'],
|
||||
steps: ['皮肤评估与卸妆', '深层清洁与导出', '舒缓镇定', '收尾修护']
|
||||
},
|
||||
p3: {
|
||||
rating: 4.9,
|
||||
effects: ['补水修护屏障', '舒缓敏感泛红', '改善干燥紧绷'],
|
||||
steps: ['温和清洁', '舒缓修护导入', '补水面膜', '收尾锁水防护']
|
||||
},
|
||||
p4: {
|
||||
rating: 4.8,
|
||||
effects: ['放松肩颈肌群', '改善紧绷酸胀', '提升舒适度与精神状态'],
|
||||
steps: ['热敷放松', '筋膜松解', '肩颈重点放松', '收尾舒缓']
|
||||
},
|
||||
p5: {
|
||||
rating: 4.9,
|
||||
effects: ['长期皮肤管理更划算', '支持随时预约使用', '到店核销自动扣次'],
|
||||
steps: ['购买次卡', '在线预约', '到店出示核销码', '门店核销扣次']
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { AiFloat },
|
||||
data() {
|
||||
return {
|
||||
id: '',
|
||||
project: runtimeProjects[0] || null,
|
||||
rating: 4.9,
|
||||
effects: ['效果以到店评估为准', '体验舒适、过程规范', '可按肤质做个性化调整'],
|
||||
steps: ['到店评估', '护理服务', '舒缓收尾', '给出居家建议'],
|
||||
categoryName: runtimeCategories[0]?.name || '项目'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
p() {
|
||||
return (
|
||||
this.project ||
|
||||
runtimeProjects[0] ||
|
||||
fallbackProjects[0] || {
|
||||
id: 'p1',
|
||||
categoryId: 'c1',
|
||||
name: '水氧净透体验',
|
||||
price: 99,
|
||||
originPrice: 199,
|
||||
durationMin: 60,
|
||||
fitFor: '初次体验、暗沉、出油',
|
||||
taboo: '近期激光/微针术后 7 天内不建议',
|
||||
cover: '',
|
||||
desc: '轻盈水氧 + 净透护理,适合快速提升肤感与通透度。'
|
||||
}
|
||||
)
|
||||
},
|
||||
displayProject() {
|
||||
return this.project || runtimeProjects[0] || null
|
||||
}
|
||||
},
|
||||
onLoad(query) {
|
||||
this.id = query.id || query.projectId || ''
|
||||
if (!this.id && runtimeProjects.length) this.id = runtimeProjects[0].id
|
||||
this.project = runtimeProjects.find((x) => x.id === this.id) || runtimeProjects[0] || null
|
||||
const cat = this.p ? runtimeCategories.find((c) => c.id === this.p.categoryId) : null
|
||||
this.categoryName = cat ? cat.name : '项目'
|
||||
const p = detailPreset[this.id] || null
|
||||
this.rating = p ? p.rating : 4.9
|
||||
this.effects = p ? p.effects : ['效果以到店评估为准', '体验舒适、过程规范', '可按肤质做个性化调整']
|
||||
this.steps = p ? p.steps : ['到店评估', '护理服务', '舒缓收尾', '给出居家建议']
|
||||
},
|
||||
methods: {
|
||||
book() {
|
||||
uni.navigateTo({ url: `/pages/booking/create?projectId=${this.p.id}` })
|
||||
},
|
||||
buy() {
|
||||
uni.navigateTo({ url: `/pages/order/confirm?type=coupon&projectId=${this.p.id}` })
|
||||
},
|
||||
goList() {
|
||||
uni.switchTab({ url: '/pages/projects/list' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.head {
|
||||
padding: 26rpx;
|
||||
}
|
||||
.cover {
|
||||
width: 100%;
|
||||
height: 320rpx;
|
||||
border-radius: 18rpx;
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
.name {
|
||||
font-size: 42rpx;
|
||||
font-weight: 950;
|
||||
}
|
||||
.meta {
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
.meta2 {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
.price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
.yen {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.num {
|
||||
font-size: 46rpx;
|
||||
font-weight: 950;
|
||||
}
|
||||
.ori {
|
||||
margin-left: 10rpx;
|
||||
font-size: 24rpx;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.tag2 {
|
||||
padding: 10rpx 14rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 24rpx;
|
||||
background: rgba(17, 24, 39, 0.06);
|
||||
font-weight: 900;
|
||||
}
|
||||
.tags {
|
||||
margin-top: 18rpx;
|
||||
gap: 12rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.tag {
|
||||
padding: 10rpx 14rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 24rpx;
|
||||
background: rgba(17, 24, 39, 0.06);
|
||||
color: #111827;
|
||||
}
|
||||
.warn {
|
||||
background: rgba(245, 158, 11, 0.14);
|
||||
color: #b45309;
|
||||
}
|
||||
.block {
|
||||
margin-top: 18rpx;
|
||||
padding: 22rpx;
|
||||
}
|
||||
.b-t {
|
||||
font-weight: 950;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.b-v {
|
||||
margin-top: 12rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.review {
|
||||
margin-top: 16rpx;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid rgba(17, 24, 39, 0.08);
|
||||
}
|
||||
.review:first-of-type {
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
.r1 {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.rn {
|
||||
font-size: 28rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
.rt {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.space {
|
||||
height: 150rpx;
|
||||
}
|
||||
.empty {
|
||||
margin-top: 18rpx;
|
||||
padding: 34rpx 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.en {
|
||||
font-size: 36rpx;
|
||||
font-weight: 950;
|
||||
}
|
||||
.et {
|
||||
margin-top: 12rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.eb {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.fixbar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 18rpx 24rpx 24rpx;
|
||||
background: linear-gradient(180deg, rgba(246, 247, 251, 0) 0%, rgba(246, 247, 251, 1) 46%);
|
||||
}
|
||||
.bar {
|
||||
padding: 18rpx;
|
||||
display: flex;
|
||||
gap: 14rpx;
|
||||
}
|
||||
.a {
|
||||
flex: 1;
|
||||
height: 82rpx;
|
||||
}
|
||||
.b {
|
||||
flex: 1;
|
||||
height: 82rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="card search">
|
||||
<view class="row between">
|
||||
<input class="ipt" v-model="kw" placeholder="搜索项目:补水/清洁/肩颈…" confirm-type="search" />
|
||||
<view class="sbtn" @tap="kw = ''">清空</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card filters">
|
||||
<scroll-view class="sc" scroll-x>
|
||||
<view class="row">
|
||||
<view
|
||||
v-for="c in allCategories"
|
||||
:key="c.id"
|
||||
class="chip"
|
||||
:class="{ on: c.id === activeCategoryId }"
|
||||
@tap="setCategory(c.id)"
|
||||
>
|
||||
{{ c.name }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view class="list">
|
||||
<ProjectCard v-for="p in filteredProjects" :key="p.id" :project="p" class="mb" />
|
||||
</view>
|
||||
|
||||
<AiFloat />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiFloat from '@/components/AiFloat.vue'
|
||||
import ProjectCard from '@/components/ProjectCard.vue'
|
||||
import { categories, projects } from '@/common/mockData'
|
||||
|
||||
export default {
|
||||
components: { ProjectCard, AiFloat },
|
||||
data() {
|
||||
return {
|
||||
allCategories: [{ id: 'all', name: '全部' }, ...categories],
|
||||
activeCategoryId: 'all',
|
||||
kw: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredProjects() {
|
||||
const kw = (this.kw || '').trim()
|
||||
const list = this.activeCategoryId === 'all' ? projects : projects.filter((p) => p.categoryId === this.activeCategoryId)
|
||||
if (!kw) return list
|
||||
return list.filter((p) => `${p.name}${p.fitFor}${p.desc}`.includes(kw))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setCategory(id) {
|
||||
this.activeCategoryId = id
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search {
|
||||
padding: 16rpx;
|
||||
}
|
||||
.ipt {
|
||||
flex: 1;
|
||||
height: 76rpx;
|
||||
padding: 0 16rpx;
|
||||
border-radius: 18rpx;
|
||||
background: rgba(17, 24, 39, 0.05);
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.sbtn {
|
||||
margin-left: 12rpx;
|
||||
padding: 18rpx 14rpx;
|
||||
border-radius: 18rpx;
|
||||
background: rgba(17, 24, 39, 0.06);
|
||||
font-size: 26rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
.filters {
|
||||
margin-top: 16rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
.sc {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chip {
|
||||
padding: 14rpx 18rpx;
|
||||
margin-right: 12rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 26rpx;
|
||||
background: rgba(17, 24, 39, 0.06);
|
||||
color: #111827;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.on {
|
||||
background: rgba(59, 130, 246, 0.14);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
.list {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
.mb {
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user