feat: H5微客宝UI全面升级 - 对齐原型图设计
This commit is contained in:
+117
-64
@@ -10,100 +10,153 @@ const loading = ref(true)
|
||||
|
||||
const metricTypeLabel = { 1: '体温', 2: '体重', 3: '血压', 4: '黄疸', 5: '宝宝体重', 6: '宝宝体温' }
|
||||
const metricUnit = { 1: '℃', 2: 'kg', 3: 'mmHg', 4: 'mg/dL', 5: 'kg', 6: '℃' }
|
||||
const metricColor = { 1: '#f43f5e', 2: '#3b82f6', 3: '#8b5cf6', 4: '#f59e0b', 5: '#10b981', 6: '#ec4899' }
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const [pRes, mRes] = await Promise.all([
|
||||
careApi.profile(),
|
||||
careApi.metrics({ limit: 10 }),
|
||||
])
|
||||
const [pRes, mRes] = await Promise.all([careApi.profile(), careApi.metrics({ limit: 10 })])
|
||||
profile.value = pRes.data?.data
|
||||
metrics.value = mRes.data?.data || []
|
||||
} catch { } finally {
|
||||
loading.value = false }
|
||||
} catch { } finally { loading.value = false }
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<!-- 头部 -->
|
||||
<div class="top-bar">
|
||||
<div class="top-header">
|
||||
<h2 class="page-title">共伴成长</h2>
|
||||
<p class="page-sub">母婴护理记录中心</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 护理档案卡 -->
|
||||
<section class="card" v-if="profile">
|
||||
<div class="card-title">📋 护理档案</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item" v-if="profile.admission_date">
|
||||
<span class="info-label">入住日期</span>
|
||||
<span class="info-val">{{ profile.admission_date }}</span>
|
||||
<div class="body">
|
||||
<!-- 护理档案 -->
|
||||
<div class="profile-card" v-if="profile">
|
||||
<div class="profile-top">
|
||||
<div class="profile-avatar">
|
||||
<van-icon name="award-o" size="24" color="#3b82f6" />
|
||||
</div>
|
||||
<div class="info-item" v-if="profile.expected_discharge">
|
||||
<span class="info-label">预计离院</span>
|
||||
<span class="info-val">{{ profile.expected_discharge }}</span>
|
||||
</div>
|
||||
<div class="info-item" v-if="profile.delivery_type">
|
||||
<span class="info-label">分娩方式</span>
|
||||
<span class="info-val">{{ profile.delivery_type === 1 ? '顺产' : '剖腹产' }}</span>
|
||||
<div>
|
||||
<p class="profile-name">在院档案</p>
|
||||
<p class="profile-status">入住中</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="info-cell" v-if="profile.admission_date">
|
||||
<span class="info-val">{{ profile.admission_date }}</span>
|
||||
<span class="info-label">入住日期</span>
|
||||
</div>
|
||||
<div class="info-cell" v-if="profile.expected_discharge">
|
||||
<span class="info-val">{{ profile.expected_discharge }}</span>
|
||||
<span class="info-label">预计离院</span>
|
||||
</div>
|
||||
<div class="info-cell" v-if="profile.delivery_type">
|
||||
<span class="info-val">{{ profile.delivery_type === 1 ? '顺产' : '剖腹产' }}</span>
|
||||
<span class="info-label">分娩方式</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-empty v-else-if="!loading" description="暂无护理档案" image="search" />
|
||||
|
||||
<!-- 快捷入口 -->
|
||||
<section class="card">
|
||||
<div class="quick-entries">
|
||||
<div class="entry-btn" @click="router.push('/care/records')">
|
||||
<van-icon name="orders-o" color="#e8718d" size="24" />
|
||||
<span>护理记录</span>
|
||||
</div>
|
||||
<div class="entry-btn" @click="router.push('/care/metrics')">
|
||||
<van-icon name="chart-trending-o" color="#5b8ff9" size="24" />
|
||||
<span>健康指标</span>
|
||||
</div>
|
||||
<div class="quick-row">
|
||||
<div class="quick-card rose" @click="router.push('/care/records')">
|
||||
<van-icon name="orders-o" size="28" color="#f43f5e" />
|
||||
<span>护理记录</span>
|
||||
</div>
|
||||
</section>
|
||||
<div class="quick-card blue" @click="router.push('/care/metrics')">
|
||||
<van-icon name="chart-trending-o" size="28" color="#3b82f6" />
|
||||
<span>健康指标</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 最新健康数据 -->
|
||||
<section class="card" v-if="metrics.length">
|
||||
<div class="card-header">
|
||||
<span class="card-title">📊 健康数据</span>
|
||||
<span class="more" @click="router.push('/care/metrics')">全部 ›</span>
|
||||
<div v-if="metrics.length" class="metrics-section">
|
||||
<div class="sec-head">
|
||||
<h5 class="sec-title">最新健康数据</h5>
|
||||
<span class="sec-more" @click="router.push('/care/metrics')">全部 ›</span>
|
||||
</div>
|
||||
<div class="metric-item" v-for="m in metrics" :key="m.id">
|
||||
<div class="metric-left">
|
||||
<span class="metric-type">{{ metricTypeLabel[m.metric_type] || '指标' }}</span>
|
||||
<span class="metric-time">{{ m.measured_at?.slice(0, 16) }}</span>
|
||||
<div class="metric-list">
|
||||
<div v-for="m in metrics" :key="m.id" class="metric-row">
|
||||
<div class="m-dot" :style="{ background: metricColor[m.metric_type] || '#ccc' }" />
|
||||
<div class="m-info">
|
||||
<span class="m-type">{{ metricTypeLabel[m.metric_type] || '指标' }}</span>
|
||||
<span class="m-time">{{ m.measured_at?.slice(0, 16) }}</span>
|
||||
</div>
|
||||
<span class="m-val" :style="{ color: metricColor[m.metric_type] || '#333' }">
|
||||
{{ m.value }} {{ metricUnit[m.metric_type] || '' }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="metric-val">{{ m.value }} {{ metricUnit[m.metric_type] || '' }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page { background: #f0f4ff; min-height: 100vh; padding-bottom: 60px; }
|
||||
.top-bar { background: linear-gradient(135deg, #5b8ff9, #7ec0ff); padding: 50px 20px 24px; border-radius: 0 0 24px 24px; }
|
||||
.page-title { font-size: 20px; font-weight: 700; color: white; margin: 0; }
|
||||
.content { padding: 16px; }
|
||||
.card { background: white; border-radius: 16px; padding: 16px; margin-bottom: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.04); }
|
||||
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
||||
.card-title { font-size: 15px; font-weight: 600; color: #1d1d1f; margin-bottom: 12px; }
|
||||
.more { font-size: 13px; color: #5b8ff9; }
|
||||
.page { background: #f0f6ff; min-height: 100vh; padding-bottom: 90px; }
|
||||
|
||||
/* Header */
|
||||
.top-header {
|
||||
background: linear-gradient(135deg, #3b82f6, #6366f1);
|
||||
padding: 52px 22px 28px;
|
||||
border-radius: 0 0 28px 28px;
|
||||
}
|
||||
.page-title { font-size: 22px; font-weight: 700; color: white; margin: 0 0 4px; }
|
||||
.page-sub { font-size: 13px; color: rgba(255,255,255,0.75); margin: 0; }
|
||||
|
||||
.body { padding: 16px; }
|
||||
|
||||
/* Profile card */
|
||||
.profile-card {
|
||||
background: white; border-radius: 22px; padding: 18px;
|
||||
margin-bottom: 14px;
|
||||
box-shadow: 0 2px 14px rgba(0,0,0,0.06);
|
||||
}
|
||||
.profile-top { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
|
||||
.profile-avatar {
|
||||
width: 44px; height: 44px;
|
||||
background: #eff6ff; border-radius: 14px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.profile-name { font-size: 15px; font-weight: 700; color: #1f2937; margin: 0 0 3px; }
|
||||
.profile-status { font-size: 12px; color: #10b981; margin: 0; }
|
||||
.info-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
|
||||
.info-item { text-align: center; }
|
||||
.info-label { display: block; font-size: 11px; color: #999; }
|
||||
.info-val { display: block; font-size: 14px; font-weight: 600; color: #333; margin-top: 4px; }
|
||||
.quick-entries { display: flex; gap: 12px; }
|
||||
.entry-btn { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; background: #f8f8f8; border-radius: 12px; padding: 16px; cursor: pointer; font-size: 13px; color: #666; }
|
||||
.metric-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #f5f5f5; }
|
||||
.metric-item:last-child { border: none; }
|
||||
.metric-left { display: flex; flex-direction: column; gap: 2px; }
|
||||
.metric-type { font-size: 13px; font-weight: 600; color: #333; }
|
||||
.metric-time { font-size: 11px; color: #999; }
|
||||
.metric-val { font-size: 15px; font-weight: 700; color: #5b8ff9; }
|
||||
.info-cell {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
background: #f8faff; border-radius: 14px; padding: 10px 8px;
|
||||
}
|
||||
.info-val { font-size: 13px; font-weight: 700; color: #1f2937; margin-bottom: 3px; }
|
||||
.info-label { font-size: 11px; color: #9ca3af; }
|
||||
|
||||
/* Quick entries */
|
||||
.quick-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
|
||||
.quick-card {
|
||||
border-radius: 20px; padding: 20px 16px;
|
||||
display: flex; flex-direction: column; align-items: center; gap: 8px;
|
||||
cursor: pointer; font-size: 13px; font-weight: 600; color: #374151;
|
||||
}
|
||||
.quick-card.rose { background: #fff0f4; }
|
||||
.quick-card.blue { background: #eff6ff; }
|
||||
|
||||
/* Metrics */
|
||||
.metrics-section { }
|
||||
.sec-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||
.sec-title { font-size: 15px; font-weight: 700; color: #1f2937; margin: 0; }
|
||||
.sec-more { font-size: 13px; color: #3b82f6; cursor: pointer; }
|
||||
.metric-list {
|
||||
background: white; border-radius: 20px; overflow: hidden;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
.metric-row {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f6ff;
|
||||
}
|
||||
.metric-row:last-child { border: none; }
|
||||
.m-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
||||
.m-info { flex: 1; }
|
||||
.m-type { display: block; font-size: 14px; font-weight: 600; color: #1f2937; }
|
||||
.m-time { display: block; font-size: 11px; color: #9ca3af; margin-top: 2px; }
|
||||
.m-val { font-size: 16px; font-weight: 700; }
|
||||
</style>
|
||||
|
||||
+238
-100
@@ -13,9 +13,7 @@ onMounted(async () => {
|
||||
try {
|
||||
const res = await homeApi.index()
|
||||
homeData.value = res.data?.data
|
||||
} catch { } finally {
|
||||
loading.value = false
|
||||
}
|
||||
} catch { } finally { loading.value = false }
|
||||
})
|
||||
|
||||
const customer = computed(() => homeData.value?.customer || store.info)
|
||||
@@ -26,125 +24,265 @@ const nannyOrder = computed(() => homeData.value?.nanny_order)
|
||||
const mealTypeLabel = { 1: '早餐', 2: '上午茶', 3: '午餐', 4: '下午茶', 5: '晚餐', 6: '宵夜' }
|
||||
|
||||
const features = [
|
||||
{ icon: 'records', label: '护理记录', color: '#ff8fa3', path: '/care/records' },
|
||||
{ icon: 'chart-trending-o', label: '健康指标', color: '#7ec8e3', path: '/care/metrics' },
|
||||
{ icon: 'balance-list-o', label: '我的服务', color: '#a8d8a8', path: '/service/orders' },
|
||||
{ icon: 'user-o', label: '我的月嫂', color: '#c9b8f0', path: '/nanny' },
|
||||
{ icon: 'gold-coin-o', label: '我的账户', color: '#ffd580', path: '/account' },
|
||||
{ icon: 'chat-o', label: '投诉反馈', color: '#ffb085', path: '/complaints' },
|
||||
{ label: '月子餐', bg: '#fff0f4', color: '#f43f5e', icon: 'food-o', path: '/meal' },
|
||||
{ label: '服务包', bg: '#eff6ff', color: '#3b82f6', icon: 'balance-list-o', path: '/service/orders' },
|
||||
{ label: '护理记录', bg: '#fffbeb', color: '#f59e0b', icon: 'orders-o', path: '/care/records' },
|
||||
{ label: '我的月嫂', bg: '#f5f3ff', color: '#8b5cf6', icon: 'user-circle-o', path: '/nanny' },
|
||||
]
|
||||
|
||||
const today = new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'numeric' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<!-- 顶部横幅 -->
|
||||
<div class="top-banner">
|
||||
<div class="banner-content">
|
||||
<div>
|
||||
<p class="greeting">您好,{{ customer?.name || '亲爱的客户' }} 👋</p>
|
||||
<p class="sub-text" v-if="customer?.expected_date">
|
||||
预产期:{{ customer.expected_date }}
|
||||
</p>
|
||||
<p class="sub-text" v-else-if="customer?.actual_date">
|
||||
宝宝出生:{{ customer.actual_date }}
|
||||
</p>
|
||||
</div>
|
||||
<van-icon name="home-o" size="40" color="rgba(255,255,255,0.6)" />
|
||||
<!-- 顶部问候区 -->
|
||||
<div class="header">
|
||||
<div>
|
||||
<p class="greet-sub">欢迎回来,</p>
|
||||
<h2 class="greet-name">
|
||||
{{ customer?.name || '亲爱的' }}妈妈
|
||||
<van-icon name="shield-o" color="#f43f5e" size="17" style="vertical-align:middle;margin-left:3px" />
|
||||
</h2>
|
||||
</div>
|
||||
<div class="avatar">
|
||||
<van-icon name="user-circle-o" size="34" color="#f43f5e" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 今日用膳 -->
|
||||
<section class="card" v-if="todayMeals.length">
|
||||
<div class="card-header">
|
||||
<span class="card-title">🍱 今日用膳</span>
|
||||
<span class="more" @click="router.push('/meal')">查看全部 ›</span>
|
||||
</div>
|
||||
<div class="meal-row" v-for="m in todayMeals" :key="m.id">
|
||||
<span class="meal-type">{{ mealTypeLabel[m.meal_type] || '用餐' }}</span>
|
||||
<span class="meal-dishes" v-if="m.dishes && m.dishes.length">
|
||||
{{ m.dishes.map(d => d.name).join(' · ') }}
|
||||
</span>
|
||||
<span class="meal-dishes" v-else>待排餐</span>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 滚动主体 -->
|
||||
<div class="body">
|
||||
|
||||
<!-- 最新护理 -->
|
||||
<section class="card" v-if="latestRecords.length">
|
||||
<div class="card-header">
|
||||
<span class="card-title">💆 护理记录</span>
|
||||
<span class="more" @click="router.push('/care/records')">更多 ›</span>
|
||||
<!-- 安心守护横幅(原型:宝宝监控卡) -->
|
||||
<div class="hero-card">
|
||||
<span class="live-badge">● 在住中</span>
|
||||
<div class="hero-center">
|
||||
<div class="hero-icon-wrap">
|
||||
<van-icon name="home-o" size="30" color="white" />
|
||||
</div>
|
||||
<p class="hero-name">{{ customer?.name ? customer.name + ' 妈妈' : '宫中有喜' }}</p>
|
||||
<p class="hero-desc">贴心守护,安心坐月子 🌸</p>
|
||||
</div>
|
||||
<div class="record-item" v-for="r in latestRecords" :key="r.id">
|
||||
<span class="record-nurse">{{ r.nurse?.name || '护理师' }}</span>
|
||||
<span class="record-time">{{ r.recorded_at?.slice(0, 10) }}</span>
|
||||
</div>
|
||||
</section>
|
||||
<span class="hero-badge-br">月子会所 · 专属守护</span>
|
||||
</div>
|
||||
|
||||
<!-- 月嫂信息 -->
|
||||
<section class="card" v-if="nannyOrder">
|
||||
<div class="card-header">
|
||||
<span class="card-title">👩⚕️ 我的月嫂</span>
|
||||
<span class="more" @click="router.push('/nanny')">详情 ›</span>
|
||||
<!-- 4格金刚位 -->
|
||||
<div class="features-row">
|
||||
<div
|
||||
v-for="f in features"
|
||||
:key="f.path"
|
||||
class="feat-item"
|
||||
@click="router.push(f.path)"
|
||||
>
|
||||
<div class="feat-icon" :style="{ background: f.bg }">
|
||||
<van-icon :name="f.icon" :color="f.color" size="26" />
|
||||
</div>
|
||||
<span class="feat-label">{{ f.label }}</span>
|
||||
</div>
|
||||
<div class="nanny-row">
|
||||
<van-icon name="user-circle-o" size="36" color="#e8718d" />
|
||||
<div class="nanny-info">
|
||||
<p class="nanny-name">{{ nannyOrder.nanny?.name }}</p>
|
||||
<p class="nanny-sub">{{ nannyOrder.nanny?.level || '专业月嫂' }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 今日服务排期(teal 渐变卡) -->
|
||||
<div class="schedule-card">
|
||||
<div class="sch-bg-icon">🍽</div>
|
||||
<div class="sch-head">
|
||||
<div>
|
||||
<h5 class="sch-title">今日用膳</h5>
|
||||
<p class="sch-date">{{ today }} · 共 {{ todayMeals.length }} 餐</p>
|
||||
</div>
|
||||
<span class="sch-more-btn" @click="router.push('/meal')">查看全部</span>
|
||||
</div>
|
||||
<div class="sch-list" v-if="todayMeals.length">
|
||||
<div v-for="m in todayMeals.slice(0, 3)" :key="m.id" class="sch-item">
|
||||
<span class="sch-time">{{ mealTypeLabel[m.meal_type] || '用餐' }}</span>
|
||||
<div class="sch-sep" />
|
||||
<span class="sch-content">
|
||||
{{ m.dishes?.length ? m.dishes.map(d => d.name).join('、') : '待排餐' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<p class="sch-empty" v-else>今日暂无用餐安排</p>
|
||||
</div>
|
||||
|
||||
<!-- 功能入口九宫格 -->
|
||||
<section class="card">
|
||||
<div class="card-title" style="margin-bottom:12px">快捷功能</div>
|
||||
<div class="features-grid">
|
||||
<div
|
||||
v-for="f in features"
|
||||
:key="f.path"
|
||||
class="feature-item"
|
||||
@click="router.push(f.path)"
|
||||
>
|
||||
<div class="feature-icon" :style="{ background: f.color + '22' }">
|
||||
<van-icon :name="f.icon" :color="f.color" size="22" />
|
||||
<!-- 最近监测数据(2列) -->
|
||||
<div class="data-section">
|
||||
<h5 class="sec-title">最近监测数据</h5>
|
||||
<div class="data-grid">
|
||||
<div class="data-card" @click="router.push('/care/records')">
|
||||
<p class="data-label">最近护理</p>
|
||||
<p class="data-val">{{ latestRecords.length ? (latestRecords[0]?.nurse?.name || '已护理') : '暂无记录' }}</p>
|
||||
<p class="data-ext">{{ latestRecords.length ? latestRecords[0]?.recorded_at?.slice(5, 10) : '--' }}</p>
|
||||
</div>
|
||||
<div class="data-card" @click="router.push('/nanny')">
|
||||
<p class="data-label">我的月嫂</p>
|
||||
<p class="data-val">{{ nannyOrder?.nanny?.name || '待分配' }}</p>
|
||||
<p class="data-ext blue">{{ nannyOrder?.nanny?.level || '---' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 护理动态 -->
|
||||
<div v-if="latestRecords.length" class="records-section">
|
||||
<div class="rec-head">
|
||||
<h5 class="sec-title">护理动态</h5>
|
||||
<span class="rec-more" @click="router.push('/care/records')">全部 ›</span>
|
||||
</div>
|
||||
<div class="rec-list">
|
||||
<div v-for="r in latestRecords" :key="r.id" class="rec-item">
|
||||
<div class="rec-emoji">💆</div>
|
||||
<div class="rec-info">
|
||||
<p class="rec-nurse">{{ r.nurse?.name || '护理师' }}</p>
|
||||
<p class="rec-time">{{ r.recorded_at?.slice(0, 16) || '' }}</p>
|
||||
</div>
|
||||
<span class="feature-label">{{ f.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page { background: #f8f0f5; min-height: 100vh; padding-bottom: 60px; }
|
||||
.top-banner {
|
||||
background: linear-gradient(135deg, #f5a0b0 0%, #e8718d 100%);
|
||||
padding: 50px 20px 30px;
|
||||
border-radius: 0 0 24px 24px;
|
||||
.page {
|
||||
background: #fff8fc;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
.banner-content { display: flex; justify-content: space-between; align-items: flex-start; color: white; }
|
||||
.greeting { font-size: 20px; font-weight: 700; margin: 0 0 4px; }
|
||||
.sub-text { font-size: 13px; opacity: 0.85; margin: 0; }
|
||||
.content { padding: 16px; }
|
||||
.card { background: white; border-radius: 16px; padding: 16px; margin-bottom: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.04); }
|
||||
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
||||
.card-title { font-size: 15px; font-weight: 600; color: #1d1d1f; }
|
||||
.more { font-size: 13px; color: #e8718d; }
|
||||
.meal-row { display: flex; gap: 8px; padding: 6px 0; border-bottom: 1px solid #f5f5f5; }
|
||||
.meal-row:last-child { border: none; }
|
||||
.meal-type { font-size: 12px; color: white; background: #e8718d; padding: 2px 8px; border-radius: 10px; white-space: nowrap; }
|
||||
.meal-dishes { font-size: 13px; color: #666; }
|
||||
.record-item { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #f5f5f5; font-size: 13px; }
|
||||
.record-item:last-child { border: none; }
|
||||
.record-nurse { color: #333; }
|
||||
.record-time { color: #999; }
|
||||
.nanny-row { display: flex; align-items: center; gap: 12px; }
|
||||
.nanny-name { font-size: 15px; font-weight: 600; margin: 0 0 2px; }
|
||||
.nanny-sub { font-size: 12px; color: #999; margin: 0; }
|
||||
.features-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
||||
.feature-item { display: flex; flex-direction: column; align-items: center; gap: 6px; cursor: pointer; }
|
||||
.feature-icon { width: 50px; height: 50px; border-radius: 14px; display: flex; align-items: center; justify-content: center; }
|
||||
.feature-label { font-size: 12px; color: #666; text-align: center; }
|
||||
|
||||
/* ── Header ─────────────────────────────── */
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 52px 22px 18px;
|
||||
background: white;
|
||||
}
|
||||
.greet-sub { font-size: 12px; color: #9ca3af; margin: 0 0 3px; }
|
||||
.greet-name { font-size: 20px; font-weight: 700; color: #111827; margin: 0; }
|
||||
.avatar {
|
||||
width: 50px; height: 50px;
|
||||
background: #fff0f4;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #fecdd3;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
/* ── Body ───────────────────────────────── */
|
||||
.body { padding: 16px; }
|
||||
|
||||
/* ── Hero Card ──────────────────────────── */
|
||||
.hero-card {
|
||||
background: linear-gradient(135deg, #0d9488, #14b8a6);
|
||||
border-radius: 28px;
|
||||
height: 178px;
|
||||
margin-bottom: 22px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 28px rgba(20, 184, 166, 0.38);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.live-badge {
|
||||
position: absolute;
|
||||
top: 14px; left: 16px;
|
||||
background: rgba(255,255,255,0.22);
|
||||
color: white; font-size: 11px;
|
||||
padding: 3px 10px; border-radius: 20px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
|
||||
.hero-badge-br {
|
||||
position: absolute;
|
||||
bottom: 12px; right: 14px;
|
||||
background: rgba(0,0,0,0.16);
|
||||
color: white; font-size: 10px;
|
||||
padding: 3px 10px; border-radius: 20px;
|
||||
}
|
||||
.hero-center { display: flex; flex-direction: column; align-items: center; gap: 6px; }
|
||||
.hero-icon-wrap {
|
||||
width: 58px; height: 58px;
|
||||
background: rgba(255,255,255,0.2);
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.hero-name { color: white; font-size: 16px; font-weight: 700; margin: 0; }
|
||||
.hero-desc { color: rgba(255,255,255,0.82); font-size: 12px; margin: 0; }
|
||||
|
||||
/* ── Features 4-grid ───────────────────── */
|
||||
.features-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
.feat-item { display: flex; flex-direction: column; align-items: center; gap: 7px; cursor: pointer; }
|
||||
.feat-icon {
|
||||
width: 52px; height: 52px;
|
||||
border-radius: 18px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
||||
}
|
||||
.feat-label { font-size: 11px; font-weight: 600; color: #374151; text-align: center; }
|
||||
|
||||
/* ── Schedule Card (Teal) ──────────────── */
|
||||
.schedule-card {
|
||||
background: linear-gradient(135deg, #0d9488, #0891b2);
|
||||
border-radius: 28px;
|
||||
padding: 20px 18px;
|
||||
margin-bottom: 20px;
|
||||
color: white;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6px 22px rgba(13, 148, 136, 0.35);
|
||||
}
|
||||
.sch-bg-icon {
|
||||
position: absolute; right: -8px; top: -8px;
|
||||
font-size: 80px; opacity: 0.12;
|
||||
}
|
||||
.sch-head { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 14px; }
|
||||
.sch-title { font-size: 16px; font-weight: 700; margin: 0 0 3px; }
|
||||
.sch-date { font-size: 11px; color: rgba(255,255,255,0.72); margin: 0; }
|
||||
.sch-more-btn {
|
||||
background: rgba(255,255,255,0.22);
|
||||
padding: 4px 12px; border-radius: 20px;
|
||||
font-size: 11px; cursor: pointer; white-space: nowrap;
|
||||
}
|
||||
.sch-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.sch-item {
|
||||
background: rgba(255,255,255,0.14);
|
||||
border-radius: 14px; padding: 9px 14px;
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
}
|
||||
.sch-time { font-size: 12px; font-weight: 600; white-space: nowrap; }
|
||||
.sch-sep { width: 1px; height: 18px; background: rgba(255,255,255,0.25); }
|
||||
.sch-content { font-size: 12px; font-weight: 500; }
|
||||
.sch-empty { text-align: center; font-size: 13px; color: rgba(255,255,255,0.65); margin: 0; }
|
||||
|
||||
/* ── Data Grid ─────────────────────────── */
|
||||
.data-section { margin-bottom: 20px; }
|
||||
.sec-title { font-size: 15px; font-weight: 700; color: #1f2937; margin: 0 0 12px; }
|
||||
.data-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.data-card {
|
||||
background: white; border-radius: 22px; padding: 16px;
|
||||
box-shadow: 0 2px 14px rgba(0,0,0,0.05); cursor: pointer;
|
||||
}
|
||||
.data-label { font-size: 11px; color: #9ca3af; margin: 0 0 6px; }
|
||||
.data-val { font-size: 17px; font-weight: 700; color: #1f2937; margin: 0 0 3px; }
|
||||
.data-ext { font-size: 12px; color: #10b981; margin: 0; }
|
||||
.data-ext.blue { color: #6366f1; }
|
||||
|
||||
/* ── Records ───────────────────────────── */
|
||||
.records-section { }
|
||||
.rec-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||
.rec-more { font-size: 13px; color: #f43f5e; cursor: pointer; }
|
||||
.rec-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.rec-item {
|
||||
background: white; border-radius: 18px; padding: 12px 16px;
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.04);
|
||||
}
|
||||
.rec-emoji { font-size: 22px; }
|
||||
.rec-nurse { font-size: 14px; font-weight: 600; color: #1f2937; margin: 0 0 2px; }
|
||||
.rec-time { font-size: 12px; color: #9ca3af; margin: 0; }
|
||||
</style>
|
||||
|
||||
@@ -1,37 +1,113 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { showToast } from 'vant'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const tabMap = { home: 'home', grow: 'grow', meal: 'meal', my: 'my' }
|
||||
const active = ref(route.name?.toLowerCase() || 'home')
|
||||
const active = ref('home')
|
||||
|
||||
watch(() => route.name, (n) => {
|
||||
if (n && tabMap[n.toLowerCase()]) active.value = n.toLowerCase()
|
||||
})
|
||||
if (n) {
|
||||
const nm = n.toLowerCase()
|
||||
if (['home', 'meal', 'grow', 'my'].includes(nm)) active.value = nm
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
function onChange(val) {
|
||||
router.push({ name: val.charAt(0).toUpperCase() + val.slice(1) })
|
||||
function go(name) {
|
||||
router.push({ name: name.charAt(0).toUpperCase() + name.slice(1) })
|
||||
}
|
||||
|
||||
function onCall() {
|
||||
showToast({ message: '如需帮助请联系前台工作人员 📞', position: 'bottom', duration: 2000 })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layout-wrap">
|
||||
<div class="layout">
|
||||
<router-view />
|
||||
<van-tabbar v-model="active" @change="onChange" active-color="#e8718d" fixed safe-area-inset-bottom>
|
||||
<van-tabbar-item name="home" icon="home-o">首页</van-tabbar-item>
|
||||
<van-tabbar-item name="grow" icon="friends-o">共伴成长</van-tabbar-item>
|
||||
<van-tabbar-item name="meal" icon="restaurant-o">用膳</van-tabbar-item>
|
||||
<van-tabbar-item name="my" icon="contact-o">我的</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
<div class="tabbar">
|
||||
<div class="tab-item" :class="{ active: active === 'home' }" @click="go('home')">
|
||||
<van-icon :name="active === 'home' ? 'home' : 'home-o'" size="23" />
|
||||
<span>首页</span>
|
||||
</div>
|
||||
<div class="tab-item" :class="{ active: active === 'meal' }" @click="go('meal')">
|
||||
<van-icon name="food-o" size="23" />
|
||||
<span>用膳</span>
|
||||
</div>
|
||||
<!-- 中央呼叫按钮 -->
|
||||
<div class="tab-center">
|
||||
<div class="center-btn" @click="onCall">
|
||||
<van-icon name="phone-o" size="25" color="white" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-item" :class="{ active: active === 'grow' }" @click="go('grow')">
|
||||
<van-icon name="flower-o" size="23" />
|
||||
<span>成长</span>
|
||||
</div>
|
||||
<div class="tab-item" :class="{ active: active === 'my' }" @click="go('my')">
|
||||
<van-icon :name="active === 'my' ? 'contact' : 'contact-o'" size="23" />
|
||||
<span>我的</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.layout-wrap {
|
||||
.layout {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 50px;
|
||||
padding-bottom: 72px;
|
||||
}
|
||||
.tabbar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 68px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
z-index: 999;
|
||||
}
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
cursor: pointer;
|
||||
color: #9ca3af;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
padding: 6px 0 0;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.tab-item.active { color: #f43f5e; }
|
||||
.tab-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
.center-btn {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
background: linear-gradient(145deg, #f87171, #f43f5e);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 6px 20px rgba(244, 63, 94, 0.45);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
bottom: 8px;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
.center-btn:active { transform: scale(0.93); }
|
||||
</style>
|
||||
|
||||
@@ -11,7 +11,8 @@ const reviewForm = ref({ rating: 5, content: '' })
|
||||
const submitting = ref(false)
|
||||
|
||||
const mealTypeLabel = { 1: '早餐', 2: '上午茶', 3: '午餐', 4: '下午茶', 5: '晚餐', 6: '宵夜' }
|
||||
const mealTypeColor = { 1: '#ff8c69', 2: '#ffd166', 3: '#06d6a0', 4: '#a8dadc', 5: '#457b9d', 6: '#6d6875' }
|
||||
const mealTypeBg = { 1: '#fff0f4', 2: '#fffbeb', 3: '#f0fdf4', 4: '#f0f9ff', 5: '#eff6ff', 6: '#faf5ff' }
|
||||
const mealTypeColor = { 1: '#f43f5e', 2: '#f59e0b', 3: '#10b981', 4: '#0ea5e9', 5: '#3b82f6', 6: '#8b5cf6' }
|
||||
|
||||
onMounted(fetchList)
|
||||
|
||||
@@ -20,8 +21,7 @@ async function fetchList() {
|
||||
try {
|
||||
const res = await mealApi.list({ days: 7 })
|
||||
plans.value = res.data?.data?.list || []
|
||||
} catch { } finally {
|
||||
loading.value = false }
|
||||
} catch { } finally { loading.value = false }
|
||||
}
|
||||
|
||||
function openReview(plan) {
|
||||
@@ -38,13 +38,10 @@ async function submitReview() {
|
||||
showToast({ type: 'success', message: '评价成功' })
|
||||
reviewVisible.value = false
|
||||
fetchList()
|
||||
} catch {
|
||||
showToast('评价失败,请重试')
|
||||
} finally {
|
||||
submitting.value = false }
|
||||
} catch { showToast('评价失败,请重试') }
|
||||
finally { submitting.value = false }
|
||||
}
|
||||
|
||||
// 按日期分组
|
||||
const grouped = computed(() => {
|
||||
const map = {}
|
||||
plans.value.forEach(p => {
|
||||
@@ -58,62 +55,68 @@ const grouped = computed(() => {
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="top-bar">
|
||||
<div class="top-header">
|
||||
<h2 class="page-title">我的用膳</h2>
|
||||
<p class="sub-text">最近 7 天膳食安排</p>
|
||||
<p class="page-sub">最近 7 天膳食安排</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<van-loading v-if="loading" vertical color="#e8718d" class="center-load">加载中...</van-loading>
|
||||
<div class="body">
|
||||
<van-loading v-if="loading" vertical color="#f43f5e" class="center-loader">加载中...</van-loading>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="grouped.length === 0">
|
||||
<van-empty description="暂无膳食记录" />
|
||||
</div>
|
||||
<van-empty v-if="!grouped.length" description="暂无膳食记录" />
|
||||
|
||||
<div v-for="[date, dayPlans] in grouped" :key="date" class="day-group">
|
||||
<div class="day-header">{{ date }}</div>
|
||||
<div class="day-label">{{ date }}</div>
|
||||
|
||||
<div v-for="plan in dayPlans" :key="plan.id" class="meal-card">
|
||||
<div class="meal-header">
|
||||
<span class="meal-type-tag" :style="{ background: mealTypeColor[plan.meal_type] || '#ccc' }">
|
||||
<div class="meal-top">
|
||||
<div
|
||||
class="meal-type-chip"
|
||||
:style="{ background: mealTypeBg[plan.meal_type], color: mealTypeColor[plan.meal_type] }"
|
||||
>
|
||||
{{ mealTypeLabel[plan.meal_type] || '用餐' }}
|
||||
</span>
|
||||
<span v-if="plan.review" class="reviewed-badge">⭐ 已评价</span>
|
||||
</div>
|
||||
<span v-if="plan.review" class="reviewed-tag">⭐ 已评价</span>
|
||||
<span
|
||||
v-else-if="plan.status === 2"
|
||||
class="review-btn"
|
||||
@click="openReview(plan)"
|
||||
>评价</span>
|
||||
>去评价</span>
|
||||
</div>
|
||||
|
||||
<div class="dishes-list" v-if="plan.dishes?.length">
|
||||
<div v-for="(dish, i) in plan.dishes" :key="i" class="dish-item">
|
||||
<span class="dish-name">{{ dish.name || dish }}</span>
|
||||
</div>
|
||||
<div class="dishes" v-if="plan.dishes?.length">
|
||||
<span
|
||||
v-for="(dish, i) in plan.dishes"
|
||||
:key="i"
|
||||
class="dish-tag"
|
||||
>{{ dish.name || dish }}</span>
|
||||
</div>
|
||||
<p v-else class="no-dish">待排餐</p>
|
||||
<p class="no-dish" v-else>待排餐</p>
|
||||
|
||||
<p v-if="plan.special_note" class="special-note">备注:{{ plan.special_note }}</p>
|
||||
<p v-if="plan.special_note" class="note-text">📝 {{ plan.special_note }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 评价弹窗 -->
|
||||
<van-popup v-model:show="reviewVisible" round position="bottom" :style="{ padding: '24px 16px 40px' }">
|
||||
<van-popup v-model:show="reviewVisible" round position="bottom" :style="{ padding: '28px 20px 44px' }">
|
||||
<h3 class="popup-title">膳食评价</h3>
|
||||
<van-rate v-model="reviewForm.rating" :size="28" color="#e8718d" void-icon="star" void-color="#eee" />
|
||||
<div class="rate-wrap">
|
||||
<van-rate v-model="reviewForm.rating" :size="32" color="#f43f5e" void-icon="star" void-color="#eee" />
|
||||
</div>
|
||||
<van-field
|
||||
v-model="reviewForm.content"
|
||||
type="textarea"
|
||||
placeholder="分享您对这餐的感受..."
|
||||
:rows="3"
|
||||
style="margin-top:16px; background:#f8f8f8; border-radius:12px;"
|
||||
style="margin-top:14px; background:#f8f8f8; border-radius:16px;"
|
||||
/>
|
||||
<van-button
|
||||
block round type="primary"
|
||||
color="linear-gradient(135deg, #f5a0b0, #e8718d)"
|
||||
style="margin-top:16px"
|
||||
color="linear-gradient(135deg, #f87171, #f43f5e)"
|
||||
style="margin-top:18px"
|
||||
:loading="submitting"
|
||||
@click="submitReview"
|
||||
>提交评价</van-button>
|
||||
@@ -122,22 +125,48 @@ const grouped = computed(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page { background: #fff8f5; min-height: 100vh; padding-bottom: 60px; }
|
||||
.top-bar { background: linear-gradient(135deg, #ff9a6c, #ff6b9d); padding: 50px 20px 24px; border-radius: 0 0 24px 24px; }
|
||||
.page-title { font-size: 20px; font-weight: 700; color: white; margin: 0 0 4px; }
|
||||
.sub-text { font-size: 13px; color: rgba(255,255,255,0.8); margin: 0; }
|
||||
.content { padding: 16px; }
|
||||
.center-load { display: flex; justify-content: center; padding: 60px 0; }
|
||||
.day-group { margin-bottom: 16px; }
|
||||
.day-header { font-size: 13px; font-weight: 600; color: #999; margin-bottom: 8px; }
|
||||
.meal-card { background: white; border-radius: 16px; padding: 14px; margin-bottom: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.04); }
|
||||
.meal-header { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
||||
.meal-type-tag { color: white; font-size: 11px; font-weight: 600; padding: 3px 10px; border-radius: 20px; }
|
||||
.reviewed-badge { font-size: 11px; color: #f59e0b; }
|
||||
.review-btn { font-size: 12px; color: #e8718d; border: 1px solid #e8718d; padding: 2px 8px; border-radius: 10px; cursor: pointer; margin-left: auto; }
|
||||
.dishes-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.dish-item { background: #fff0f4; color: #e8718d; font-size: 12px; padding: 3px 10px; border-radius: 10px; }
|
||||
.no-dish { font-size: 13px; color: #ccc; margin: 4px 0 0; }
|
||||
.special-note { font-size: 12px; color: #999; margin: 8px 0 0; }
|
||||
.popup-title { font-size: 16px; font-weight: 600; text-align: center; margin: 0 0 16px; }
|
||||
.page { background: #fff8fc; min-height: 100vh; padding-bottom: 90px; }
|
||||
|
||||
.top-header {
|
||||
background: linear-gradient(135deg, #f87171, #f43f5e);
|
||||
padding: 52px 22px 28px;
|
||||
border-radius: 0 0 28px 28px;
|
||||
}
|
||||
.page-title { font-size: 22px; font-weight: 700; color: white; margin: 0 0 4px; }
|
||||
.page-sub { font-size: 13px; color: rgba(255,255,255,0.78); margin: 0; }
|
||||
|
||||
.body { padding: 16px; }
|
||||
.center-loader { display: flex; justify-content: center; padding: 60px 0; }
|
||||
|
||||
.day-group { margin-bottom: 18px; }
|
||||
.day-label { font-size: 13px; font-weight: 600; color: #9ca3af; margin-bottom: 10px; }
|
||||
|
||||
.meal-card {
|
||||
background: white; border-radius: 22px; padding: 16px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 2px 14px rgba(0,0,0,0.05);
|
||||
}
|
||||
.meal-top { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
|
||||
.meal-type-chip {
|
||||
font-size: 12px; font-weight: 700;
|
||||
padding: 4px 12px; border-radius: 20px;
|
||||
}
|
||||
.reviewed-tag { font-size: 12px; color: #f59e0b; margin-left: auto; }
|
||||
.review-btn {
|
||||
font-size: 12px; color: #f43f5e;
|
||||
border: 1px solid #f43f5e;
|
||||
padding: 3px 10px; border-radius: 12px;
|
||||
cursor: pointer; margin-left: auto;
|
||||
}
|
||||
|
||||
.dishes { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.dish-tag {
|
||||
background: #fff0f4; color: #f43f5e;
|
||||
font-size: 12px; padding: 4px 12px; border-radius: 12px;
|
||||
}
|
||||
.no-dish { font-size: 13px; color: #d1d5db; margin: 0; }
|
||||
.note-text { font-size: 12px; color: #9ca3af; margin: 10px 0 0; }
|
||||
|
||||
.popup-title { font-size: 17px; font-weight: 700; text-align: center; margin: 0 0 20px; color: #1f2937; }
|
||||
.rate-wrap { display: flex; justify-content: center; }
|
||||
</style>
|
||||
|
||||
@@ -10,10 +10,10 @@ const store = useCustomerStore()
|
||||
const customer = computed(() => store.info)
|
||||
|
||||
const menus = [
|
||||
{ icon: 'balance-list-o', label: '我的服务', color: '#5b8ff9', path: '/service/orders' },
|
||||
{ icon: 'gold-coin-o', label: '账户余额', color: '#f59e0b', path: '/account' },
|
||||
{ icon: 'user-o', label: '我的月嫂', color: '#c084fc', path: '/nanny' },
|
||||
{ icon: 'chat-o', label: '投诉反馈', color: '#f87171', path: '/complaints' },
|
||||
{ icon: 'balance-list-o', label: '我的服务', desc: '服务订单与执行记录', color: '#3b82f6', bg: '#eff6ff', path: '/service/orders' },
|
||||
{ icon: 'gold-coin-o', label: '账户余额', desc: '充值与消费流水', color: '#f59e0b', bg: '#fffbeb', path: '/account' },
|
||||
{ icon: 'user-circle-o', label: '我的月嫂', desc: '月嫂信息与排班', color: '#8b5cf6', bg: '#f5f3ff', path: '/nanny' },
|
||||
{ icon: 'chat-o', label: '投诉反馈', desc: '问题与建议反馈', color: '#f43f5e', bg: '#fff0f4', path: '/complaints' },
|
||||
]
|
||||
|
||||
async function doLogout() {
|
||||
@@ -28,63 +28,65 @@ async function doLogout() {
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<!-- 个人信息头 -->
|
||||
<!-- 个人信息头部 -->
|
||||
<div class="profile-header">
|
||||
<div class="avatar-wrap">
|
||||
<van-icon name="user-circle-o" size="64" color="white" />
|
||||
<div class="avatar-circle">
|
||||
<van-icon name="user-circle-o" size="44" color="white" />
|
||||
</div>
|
||||
<div class="profile-info">
|
||||
<p class="p-name">{{ customer?.name || '- -' }}</p>
|
||||
<p class="p-phone">{{ customer?.phone }}</p>
|
||||
</div>
|
||||
<div class="header-badge">
|
||||
<van-icon name="shield-o" size="16" color="rgba(255,255,255,0.8)" />
|
||||
<span>会员</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 账户状态卡 -->
|
||||
<div class="body">
|
||||
<!-- 状态统计卡 -->
|
||||
<div class="stat-card" v-if="customer">
|
||||
<div class="stat-item">
|
||||
<span class="stat-val">{{ customer.baby_count || 1 }}</span>
|
||||
<span class="stat-label">宝宝数</span>
|
||||
</div>
|
||||
<div class="stat-divider" />
|
||||
<div class="stat-line" />
|
||||
<div class="stat-item">
|
||||
<span class="stat-val">{{ customer.actual_date ? '在住' : '待入住' }}</span>
|
||||
<span class="stat-label">状态</span>
|
||||
</div>
|
||||
<div class="stat-divider" />
|
||||
<div class="stat-line" />
|
||||
<div class="stat-item">
|
||||
<span class="stat-val">{{ customer.contracts?.length || 0 }}</span>
|
||||
<span class="stat-label">合同数</span>
|
||||
<span class="stat-val">{{ customer.baby_count ? customer.baby_count + '宝' : '--' }}</span>
|
||||
<span class="stat-label">宝宝信息</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能列表 -->
|
||||
<van-cell-group inset class="menu-group">
|
||||
<van-cell
|
||||
<!-- 功能菜单 -->
|
||||
<div class="menu-list">
|
||||
<div
|
||||
v-for="m in menus"
|
||||
:key="m.path"
|
||||
:title="m.label"
|
||||
is-link
|
||||
clickable
|
||||
class="menu-item"
|
||||
@click="router.push(m.path)"
|
||||
>
|
||||
<template #icon>
|
||||
<div class="menu-icon-wrap" :style="{ background: m.color + '22' }">
|
||||
<van-icon :name="m.icon" :color="m.color" size="18" />
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
<div class="menu-icon" :style="{ background: m.bg }">
|
||||
<van-icon :name="m.icon" :color="m.color" size="22" />
|
||||
</div>
|
||||
<div class="menu-text">
|
||||
<p class="menu-label">{{ m.label }}</p>
|
||||
<p class="menu-desc">{{ m.desc }}</p>
|
||||
</div>
|
||||
<van-icon name="arrow" color="#d1d5db" size="16" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 退出 -->
|
||||
<!-- 退出按钮 -->
|
||||
<div class="logout-wrap">
|
||||
<van-button
|
||||
round
|
||||
block
|
||||
plain
|
||||
type="danger"
|
||||
round block plain
|
||||
style="color:#f43f5e; border-color:#fecdd3; background:#fff8fc;"
|
||||
@click="doLogout"
|
||||
style="color: #e8718d; border-color: #e8718d;"
|
||||
>退出登录</van-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,22 +94,72 @@ async function doLogout() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page { background: #f8f0f5; min-height: 100vh; padding-bottom: 60px; }
|
||||
.page { background: #fff8fc; min-height: 100vh; padding-bottom: 90px; }
|
||||
|
||||
/* Profile Header */
|
||||
.profile-header {
|
||||
background: linear-gradient(135deg, #f5a0b0 0%, #e8718d 100%);
|
||||
padding: 50px 20px 30px;
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
background: linear-gradient(135deg, #f87171, #f43f5e);
|
||||
padding: 52px 22px 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
position: relative;
|
||||
}
|
||||
.avatar-wrap { width: 72px; height: 72px; background: rgba(255,255,255,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; }
|
||||
.p-name { font-size: 18px; font-weight: 700; color: white; margin: 0 0 4px; }
|
||||
.avatar-circle {
|
||||
width: 72px; height: 72px;
|
||||
background: rgba(255,255,255,0.22);
|
||||
border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border: 2px solid rgba(255,255,255,0.4);
|
||||
}
|
||||
.profile-info { flex: 1; }
|
||||
.p-name { font-size: 20px; font-weight: 700; color: white; margin: 0 0 4px; }
|
||||
.p-phone { font-size: 13px; color: rgba(255,255,255,0.8); margin: 0; }
|
||||
.content { padding: 16px; }
|
||||
.stat-card { background: white; border-radius: 16px; padding: 16px; display: flex; justify-content: space-around; margin-bottom: 16px; box-shadow: 0 2px 12px rgba(0,0,0,0.04); }
|
||||
.header-badge {
|
||||
display: flex; align-items: center; gap: 4px;
|
||||
background: rgba(255,255,255,0.18);
|
||||
padding: 5px 12px; border-radius: 20px;
|
||||
font-size: 12px; color: rgba(255,255,255,0.9);
|
||||
}
|
||||
|
||||
/* Body */
|
||||
.body { padding: 16px; }
|
||||
|
||||
/* Stats */
|
||||
.stat-card {
|
||||
background: white; border-radius: 22px; padding: 18px 12px;
|
||||
display: flex; justify-content: space-around; align-items: center;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 14px rgba(0,0,0,0.06);
|
||||
}
|
||||
.stat-item { display: flex; flex-direction: column; align-items: center; gap: 4px; }
|
||||
.stat-val { font-size: 18px; font-weight: 700; color: #e8718d; }
|
||||
.stat-label { font-size: 12px; color: #999; }
|
||||
.stat-divider { width: 1px; background: #f0f0f0; }
|
||||
.menu-group { margin-bottom: 16px; }
|
||||
.menu-icon-wrap { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; margin-right: 10px; }
|
||||
.logout-wrap { margin-top: 24px; padding: 0 8px; }
|
||||
.stat-val { font-size: 20px; font-weight: 700; color: #f43f5e; }
|
||||
.stat-label { font-size: 12px; color: #9ca3af; }
|
||||
.stat-line { width: 1px; height: 36px; background: #f5f5f5; }
|
||||
|
||||
/* Menu */
|
||||
.menu-list {
|
||||
background: white; border-radius: 22px; overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 14px rgba(0,0,0,0.06);
|
||||
}
|
||||
.menu-item {
|
||||
display: flex; align-items: center; gap: 14px;
|
||||
padding: 16px 16px;
|
||||
border-bottom: 1px solid #fafafa;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.menu-item:last-child { border: none; }
|
||||
.menu-item:active { background: #fafafa; }
|
||||
.menu-icon {
|
||||
width: 44px; height: 44px; border-radius: 14px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.menu-text { flex: 1; }
|
||||
.menu-label { font-size: 15px; font-weight: 600; color: #1f2937; margin: 0 0 3px; }
|
||||
.menu-desc { font-size: 12px; color: #9ca3af; margin: 0; }
|
||||
|
||||
.logout-wrap { margin-top: 8px; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user