feat: H5首页对齐原型(暗色监控卡/圆形图标/服务排期) + demo演示数据
This commit is contained in:
@@ -0,0 +1,254 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class DemoClientSeeder extends Seeder
|
||||||
|
{
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$store = DB::table('stores')->first();
|
||||||
|
if (!$store) {
|
||||||
|
$this->command->warn('No store found. Run InitSeeder first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$storeId = $store->id;
|
||||||
|
|
||||||
|
// ── 1. Demo 客户 ──────────────────────────────────────────────────────
|
||||||
|
$customerId = DB::table('customers')->insertGetId([
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'name' => '李婉婷',
|
||||||
|
'phone' => '13800138001',
|
||||||
|
'password' => Hash::make('123456'),
|
||||||
|
'wechat' => 'liwanting88',
|
||||||
|
'expected_date' => now()->subDays(14)->toDateString(),
|
||||||
|
'actual_date' => now()->subDays(14)->toDateString(),
|
||||||
|
'baby_count' => 1,
|
||||||
|
'status' => 3, // 在住
|
||||||
|
'remark' => '顺产,宝宝健康,无过敏史',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ── 2. 护理档案 ───────────────────────────────────────────────────────
|
||||||
|
$profileId = DB::table('care_profiles')->insertGetId([
|
||||||
|
'customer_id' => $customerId,
|
||||||
|
'type' => 1,
|
||||||
|
'birth_method' => 1, // 顺产
|
||||||
|
'risk_level' => 0,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ── 3. 护理记录(近7天)─────────────────────────────────────────────
|
||||||
|
$careTypes = [
|
||||||
|
[1, '产后按摩,疏通经络,妈妈表示很舒适'],
|
||||||
|
[1, '乳腺疏通护理,双侧通畅'],
|
||||||
|
[2, '宝宝抚触操,脐带护理正常'],
|
||||||
|
[1, '产后盆底肌恢复训练,第3次'],
|
||||||
|
[2, '宝宝沐浴、脐带护理、黄疸监测'],
|
||||||
|
[1, '子宫复旧按摩,复旧良好'],
|
||||||
|
[2, '宝宝生长评估,体重增长正常'],
|
||||||
|
];
|
||||||
|
foreach ($careTypes as $i => [$type, $remark]) {
|
||||||
|
DB::table('care_records')->insert([
|
||||||
|
'care_profile_id' => $profileId,
|
||||||
|
'type' => $type,
|
||||||
|
'remark' => $remark,
|
||||||
|
'nurse_id' => 1,
|
||||||
|
'recorded_at' => now()->subDays($i)->setHour(10)->setMinute(0),
|
||||||
|
'created_at' => now()->subDays($i),
|
||||||
|
'updated_at' => now()->subDays($i),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 4. 健康指标(近10条)──────────────────────────────────────────────
|
||||||
|
$metricData = [
|
||||||
|
[1, 36.5, '℃'],
|
||||||
|
[2, 58.2, 'kg'],
|
||||||
|
[1, 36.7, '℃'],
|
||||||
|
[4, 8.2, 'mg/dL'],
|
||||||
|
[5, 3.65, 'kg'],
|
||||||
|
[6, 36.8, '℃'],
|
||||||
|
[1, 36.4, '℃'],
|
||||||
|
[2, 57.9, 'kg'],
|
||||||
|
[3, '110/70', 'mmHg'],
|
||||||
|
[5, 3.72, 'kg'],
|
||||||
|
];
|
||||||
|
foreach ($metricData as $i => [$type, $value, $unit]) {
|
||||||
|
DB::table('health_metrics')->insert([
|
||||||
|
'care_profile_id' => $profileId,
|
||||||
|
'metric_type' => $type,
|
||||||
|
'value' => $value,
|
||||||
|
'unit' => $unit,
|
||||||
|
'measured_at' => now()->subDays((int)($i / 2))->setHour(8 + $i % 3),
|
||||||
|
'created_at' => now()->subDays((int)($i / 2)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 5. 今日 & 近7天膳食计划 ───────────────────────────────────────────
|
||||||
|
$mealsByDay = [
|
||||||
|
// 今天
|
||||||
|
[0, 1, [['name'=>'小米红枣粥'],['name'=>'鸡蛋羹'],['name'=>'红糖姜水']]],
|
||||||
|
[0, 2, [['name'=>'清蒸鲈鱼'],['name'=>'猪肝菠菜汤'],['name'=>'米饭']]],
|
||||||
|
[0, 3, [['name'=>'枸杞红枣炖乌鸡'],['name'=>'核桃露']]],
|
||||||
|
[0, 4, [['name'=>'番茄蛋花汤'],['name'=>'清炒时蔬'],['name'=>'糙米饭']]],
|
||||||
|
// 昨天
|
||||||
|
[1, 1, [['name'=>'燕麦南瓜粥'],['name'=>'水煮蛋']]],
|
||||||
|
[1, 2, [['name'=>'鲫鱼豆腐汤'],['name'=>'香菇蒸鸡'],['name'=>'米饭']]],
|
||||||
|
[1, 4, [['name'=>'花生猪脚汤'],['name'=>'蒜蓉青菜']]],
|
||||||
|
[1, 5, [['name'=>'红豆薏米水']]],
|
||||||
|
// 前天
|
||||||
|
[2, 1, [['name'=>'黑米红豆粥'],['name'=>'荷包蛋']]],
|
||||||
|
[2, 2, [['name'=>'冬瓜排骨汤'],['name'=>'清炒豆芽'],['name'=>'米饭']]],
|
||||||
|
[2, 4, [['name'=>'参鸡汤'],['name'=>'蒸南瓜']]],
|
||||||
|
// 3天前
|
||||||
|
[3, 1, [['name'=>'紫米粥'],['name'=>'银耳莲子羹']]],
|
||||||
|
[3, 2, [['name'=>'红枣枸杞炖排骨'],['name'=>'米饭']]],
|
||||||
|
[3, 3, [['name'=>'核桃芝麻糊']]],
|
||||||
|
[3, 4, [['name'=>'番茄豆腐汤'],['name'=>'清蒸时蔬']]],
|
||||||
|
];
|
||||||
|
foreach ($mealsByDay as [$daysAgo, $mealType, $dishes]) {
|
||||||
|
DB::table('daily_meal_plans')->insert([
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'customer_id' => $customerId,
|
||||||
|
'plan_date' => now()->subDays($daysAgo)->toDateString(),
|
||||||
|
'meal_type' => $mealType,
|
||||||
|
'dishes' => json_encode($dishes),
|
||||||
|
'status' => $daysAgo > 0 ? 2 : 1, // 历史=已送达, 今天=已备餐
|
||||||
|
'created_at' => now()->subDays($daysAgo),
|
||||||
|
'updated_at' => now()->subDays($daysAgo),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 给昨天2条膳食添加评价
|
||||||
|
$planIds = DB::table('daily_meal_plans')
|
||||||
|
->where('customer_id', $customerId)
|
||||||
|
->where('plan_date', now()->subDay()->toDateString())
|
||||||
|
->pluck('id');
|
||||||
|
foreach ($planIds->take(2) as $planId) {
|
||||||
|
DB::table('meal_reviews')->insert([
|
||||||
|
'daily_meal_plan_id' => $planId,
|
||||||
|
'customer_id' => $customerId,
|
||||||
|
'score' => rand(4, 5),
|
||||||
|
'content' => '味道很好,营养均衡,非常感谢厨师团队!',
|
||||||
|
'created_at' => now()->subDay(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 6. 月嫂 + 派单 ────────────────────────────────────────────────────
|
||||||
|
$nannyId = DB::table('nannies')->insertGetId([
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'name' => '王秀珍',
|
||||||
|
'phone' => '13900139001',
|
||||||
|
'level' => 3, // 高级
|
||||||
|
'experience_years' => 8,
|
||||||
|
'skills' => json_encode(['产后护理', '宝宝沐浴', '乳腺疏通', '产后恢复']),
|
||||||
|
'introduction' => '从业8年,持高级育婴师证书,服务过200余位妈妈,口碑优秀',
|
||||||
|
'status' => 2, // 服务中
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('nanny_orders')->insert([
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'customer_id' => $customerId,
|
||||||
|
'nanny_id' => $nannyId,
|
||||||
|
'order_no' => 'NO' . date('YmdHis'),
|
||||||
|
'service_type'=> 1,
|
||||||
|
'start_date' => now()->subDays(14)->toDateString(),
|
||||||
|
'end_date' => now()->addDays(16)->toDateString(),
|
||||||
|
'days' => 30,
|
||||||
|
'price' => 12000.00,
|
||||||
|
'status' => 2, // 已签约/服务中
|
||||||
|
'created_at' => now()->subDays(14),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ── 7. 服务订单 ───────────────────────────────────────────────────────
|
||||||
|
$serviceOrders = [
|
||||||
|
['产后修复套餐', 2, 3980.00],
|
||||||
|
['宝宝游泳抚触课程', 2, 1200.00],
|
||||||
|
['气血能量房体验', 2, 680.00],
|
||||||
|
];
|
||||||
|
foreach ($serviceOrders as [$name, $status, $amount]) {
|
||||||
|
DB::table('service_orders')->insert([
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'customer_id' => $customerId,
|
||||||
|
'order_no' => 'SO' . date('YmdHis') . rand(100, 999),
|
||||||
|
'type' => 1,
|
||||||
|
'items' => json_encode([['name' => $name, 'qty' => 1, 'price' => $amount]]),
|
||||||
|
'total_amount' => $amount,
|
||||||
|
'actual_amount' => $amount,
|
||||||
|
'status' => $status,
|
||||||
|
'pay_method' => 1,
|
||||||
|
'paid_at' => now()->subDays(14),
|
||||||
|
'created_at' => now()->subDays(14),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 8. 客户账户 + 流水 ────────────────────────────────────────────────
|
||||||
|
$accountId = DB::table('customer_accounts')->insertGetId([
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'customer_id' => $customerId,
|
||||||
|
'cash_balance' => 3200.00,
|
||||||
|
'card_balance' => 8800.00,
|
||||||
|
'points' => 1580,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$txs = [
|
||||||
|
[1, 20000.00, 20000.00, '签约充值'],
|
||||||
|
[2, 3980.00, 16020.00, '产后修复套餐'],
|
||||||
|
[2, 1200.00, 14820.00, '宝宝游泳课程'],
|
||||||
|
[3, 500.00, 15320.00, '退款:项目未使用'],
|
||||||
|
[2, 680.00, 14640.00, '气血能量房体验'],
|
||||||
|
[2, 2640.00, 12000.00, '月嫂服务首期'],
|
||||||
|
];
|
||||||
|
foreach ($txs as $i => [$type, $amount, $balanceAfter, $desc]) {
|
||||||
|
DB::table('account_transactions')->insert([
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'customer_account_id' => $accountId,
|
||||||
|
'customer_id' => $customerId,
|
||||||
|
'type' => $type,
|
||||||
|
'amount' => $amount,
|
||||||
|
'balance_after' => $balanceAfter,
|
||||||
|
'description' => $desc,
|
||||||
|
'created_at' => now()->subDays(14 - $i * 2),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 9. 投诉反馈 ───────────────────────────────────────────────────────
|
||||||
|
DB::table('complaints')->insert([
|
||||||
|
[
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'customer_id' => $customerId,
|
||||||
|
'type' => 1,
|
||||||
|
'content' => '昨晚空调温度偏低,宝宝有点凉,希望能保持在26度左右',
|
||||||
|
'status' => 2,
|
||||||
|
'handle_result' => '已通知客房调整空调,确保室温维持在25-26℃,感谢您的反馈!',
|
||||||
|
'handle_at' => now()->subDays(1),
|
||||||
|
'created_at' => now()->subDays(2),
|
||||||
|
'updated_at' => now()->subDays(1),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'customer_id' => $customerId,
|
||||||
|
'type' => 2,
|
||||||
|
'content' => '今日午餐的汤偏咸,建议减少盐量,产后饮食应清淡',
|
||||||
|
'status' => 1,
|
||||||
|
'created_at' => now()->subHours(5),
|
||||||
|
'updated_at' => now()->subHours(5),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->command->info("✅ Demo 客户创建成功!");
|
||||||
|
$this->command->info(" 手机号:13800138001 密码:123456");
|
||||||
|
$this->command->info(" 姓名:李婉婷 状态:在住第14天");
|
||||||
|
}
|
||||||
|
}
|
||||||
+162
-129
@@ -16,56 +16,75 @@ onMounted(async () => {
|
|||||||
} catch { } finally { loading.value = false }
|
} catch { } finally { loading.value = false }
|
||||||
})
|
})
|
||||||
|
|
||||||
const customer = computed(() => homeData.value?.customer || store.info)
|
const customer = computed(() => homeData.value?.customer || store.info)
|
||||||
const todayMeals = computed(() => homeData.value?.today_meals || [])
|
const todayMeals = computed(() => homeData.value?.today_meals || [])
|
||||||
const latestRecords = computed(() => homeData.value?.care_records || [])
|
const latestRecords = computed(() => homeData.value?.care_records || [])
|
||||||
const nannyOrder = computed(() => homeData.value?.nanny_order)
|
const nannyOrder = computed(() => homeData.value?.nanny_order)
|
||||||
|
|
||||||
const mealTypeLabel = { 1: '早餐', 2: '午餐', 3: '下午茶', 4: '晚餐', 5: '宵夜' }
|
// 膳食时间映射
|
||||||
|
const mealTime = { 1: '07:30', 2: '12:00', 3: '15:00', 4: '18:00', 5: '21:00' }
|
||||||
|
const mealLabel = { 1: '早餐', 2: '午餐', 3: '下午茶', 4: '晚餐', 5: '宵夜' }
|
||||||
|
|
||||||
|
// 入住天数
|
||||||
|
const checkinDays = computed(() => {
|
||||||
|
const d = customer.value?.actual_date
|
||||||
|
if (!d) return null
|
||||||
|
const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000)
|
||||||
|
return diff > 0 ? diff : null
|
||||||
|
})
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{ label: '月子餐', bg: '#fff0f4', color: '#f43f5e', icon: 'food-o', path: '/meal' },
|
{ label: '月子餐', bg: '#fff0f4', color: '#f43f5e', icon: '🍱', path: '/meal' },
|
||||||
{ label: '服务包', bg: '#eff6ff', color: '#3b82f6', icon: 'balance-list-o', path: '/service/orders' },
|
{ label: '服务包', bg: '#eff6ff', color: '#3b82f6', icon: '🛏', path: '/service/orders' },
|
||||||
{ label: '护理记录', bg: '#fffbeb', color: '#f59e0b', icon: 'orders-o', path: '/care/records' },
|
{ label: '护理记录', bg: '#fffbeb', color: '#f59e0b', icon: '🩺', path: '/care/records' },
|
||||||
{ label: '我的月嫂', bg: '#f5f3ff', color: '#8b5cf6', icon: 'user-circle-o', path: '/nanny' },
|
{ label: '我的月嫂', bg: '#f5f3ff', color: '#8b5cf6', icon: '👩', path: '/nanny' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const today = new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'numeric' })
|
const today = new Date().toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<!-- 顶部问候区 -->
|
<!-- ── 顶部问候 ── -->
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div>
|
<div>
|
||||||
<p class="greet-sub">欢迎回来,</p>
|
<p class="greet-sub">欢迎回来,</p>
|
||||||
<h2 class="greet-name">
|
<h2 class="greet-name">
|
||||||
{{ customer?.name || '亲爱的' }}妈妈
|
{{ customer?.name || '亲爱的' }}妈妈
|
||||||
<van-icon name="shield-o" color="#f43f5e" size="17" style="vertical-align:middle;margin-left:3px" />
|
<van-icon name="shield-o" color="#f43f5e" size="16" style="vertical-align:middle;margin-left:2px" />
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar">
|
<div class="avatar-wrap">
|
||||||
<van-icon name="user-circle-o" size="34" color="#f43f5e" />
|
<van-icon name="contact" size="32" color="#f43f5e" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 滚动主体 -->
|
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
|
||||||
<!-- 安心守护横幅(原型:宝宝监控卡) -->
|
<!-- ── 宝宝监控卡(暗色摄像风格) ── -->
|
||||||
<div class="hero-card">
|
<div class="cam-card">
|
||||||
<span class="live-badge">● 在住中</span>
|
<!-- 暗色网格背景 -->
|
||||||
<div class="hero-center">
|
<div class="cam-grid" />
|
||||||
<div class="hero-icon-wrap">
|
<!-- LIVE badge -->
|
||||||
<van-icon name="home-o" size="30" color="white" />
|
<div class="cam-topbar">
|
||||||
</div>
|
<span class="live-dot">● LIVE</span>
|
||||||
<p class="hero-name">{{ customer?.name ? customer.name + ' 妈妈' : '宫中有喜' }}</p>
|
<span class="cam-label">Cam 02 (室内)</span>
|
||||||
<p class="hero-desc">贴心守护,安心坐月子 🌸</p>
|
</div>
|
||||||
|
<!-- 播放按钮 -->
|
||||||
|
<div class="play-wrap">
|
||||||
|
<div class="play-btn">
|
||||||
|
<svg width="22" height="24" viewBox="0 0 22 24" fill="none">
|
||||||
|
<path d="M2 2L20 12L2 22V2Z" fill="white" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 底部文字 -->
|
||||||
|
<div class="cam-bottom">
|
||||||
|
<span class="cam-title">宝宝安防监控 · 实时画面</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="hero-badge-br">月子会所 · 专属守护</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 4格金刚位 -->
|
<!-- ── 4 格功能入口(圆形图标) ── -->
|
||||||
<div class="features-row">
|
<div class="features-row">
|
||||||
<div
|
<div
|
||||||
v-for="f in features"
|
v-for="f in features"
|
||||||
@@ -73,36 +92,37 @@ const today = new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'nume
|
|||||||
class="feat-item"
|
class="feat-item"
|
||||||
@click="router.push(f.path)"
|
@click="router.push(f.path)"
|
||||||
>
|
>
|
||||||
<div class="feat-icon" :style="{ background: f.bg }">
|
<div class="feat-circle" :style="{ background: f.bg }">
|
||||||
<van-icon :name="f.icon" :color="f.color" size="26" />
|
<span class="feat-emoji">{{ f.icon }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="feat-label">{{ f.label }}</span>
|
<span class="feat-label">{{ f.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 今日服务排期(teal 渐变卡) -->
|
<!-- ── 今日服务排期(teal 渐变卡) ── -->
|
||||||
<div class="schedule-card">
|
<div class="schedule-card">
|
||||||
<div class="sch-bg-icon">🍽</div>
|
|
||||||
<div class="sch-head">
|
<div class="sch-head">
|
||||||
<div>
|
<div>
|
||||||
<h5 class="sch-title">今日用膳</h5>
|
<h5 class="sch-title">今日服务排期</h5>
|
||||||
<p class="sch-date">{{ today }} · 共 {{ todayMeals.length }} 餐</p>
|
<p class="sch-date">{{ today }}(共 {{ todayMeals.length }} 项)</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="sch-more-btn" @click="router.push('/meal')">查看全部</span>
|
<span class="sch-day-badge" v-if="checkinDays">入住第{{ checkinDays }}天</span>
|
||||||
|
<span class="sch-more-btn" v-else @click="router.push('/meal')">查看全部</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sch-list" v-if="todayMeals.length">
|
<div class="sch-list" v-if="todayMeals.length">
|
||||||
<div v-for="m in todayMeals.slice(0, 3)" :key="m.id" class="sch-item">
|
<div v-for="m in todayMeals.slice(0, 3)" :key="m.id" class="sch-item">
|
||||||
<span class="sch-time">{{ mealTypeLabel[m.meal_type] || '用餐' }}</span>
|
<span class="sch-time">{{ mealTime[m.meal_type] || '--:--' }}</span>
|
||||||
<div class="sch-sep" />
|
<div class="sch-sep" />
|
||||||
<span class="sch-content">
|
<span class="sch-content">
|
||||||
{{ m.dishes?.length ? m.dishes.map(d => d.name).join('、') : '待排餐' }}
|
{{ mealLabel[m.meal_type] || '用餐' }}
|
||||||
|
<span v-if="m.dishes?.length">:{{ m.dishes.map(d => d.name || d).join('、') }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="sch-empty" v-else>今日暂无用餐安排</p>
|
<p class="sch-empty" v-else>今日暂无服务安排</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 最近监测数据(2列) -->
|
<!-- ── 最近监测数据 ── -->
|
||||||
<div class="data-section">
|
<div class="data-section">
|
||||||
<h5 class="sec-title">最近监测数据</h5>
|
<h5 class="sec-title">最近监测数据</h5>
|
||||||
<div class="data-grid">
|
<div class="data-grid">
|
||||||
@@ -114,12 +134,12 @@ const today = new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'nume
|
|||||||
<div class="data-card" @click="router.push('/nanny')">
|
<div class="data-card" @click="router.push('/nanny')">
|
||||||
<p class="data-label">我的月嫂</p>
|
<p class="data-label">我的月嫂</p>
|
||||||
<p class="data-val">{{ nannyOrder?.nanny?.name || '待分配' }}</p>
|
<p class="data-val">{{ nannyOrder?.nanny?.name || '待分配' }}</p>
|
||||||
<p class="data-ext blue">{{ nannyOrder?.nanny?.level || '---' }}</p>
|
<p class="data-ext blue">{{ nannyOrder?.nanny?.level ? '高级月嫂' : '---' }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 护理动态 -->
|
<!-- ── 护理动态 ── -->
|
||||||
<div v-if="latestRecords.length" class="records-section">
|
<div v-if="latestRecords.length" class="records-section">
|
||||||
<div class="rec-head">
|
<div class="rec-head">
|
||||||
<h5 class="sec-title">护理动态</h5>
|
<h5 class="sec-title">护理动态</h5>
|
||||||
@@ -141,138 +161,151 @@ const today = new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'nume
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.page {
|
.page { background: #f5f5f7; min-height: 100vh; padding-bottom: 90px; }
|
||||||
background: #fff8fc;
|
|
||||||
min-height: 100vh;
|
|
||||||
padding-bottom: 90px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Header ─────────────────────────────── */
|
/* ── Header ── */
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex; justify-content: space-between; align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 52px 22px 18px;
|
padding: 52px 22px 18px;
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
.greet-sub { font-size: 12px; color: #9ca3af; margin: 0 0 3px; }
|
.greet-sub { font-size: 13px; color: #9ca3af; margin: 0 0 2px; }
|
||||||
.greet-name { font-size: 20px; font-weight: 700; color: #111827; margin: 0; }
|
.greet-name { font-size: 22px; font-weight: 800; color: #111827; margin: 0; letter-spacing: -0.3px; }
|
||||||
.avatar {
|
.avatar-wrap {
|
||||||
width: 50px; height: 50px;
|
width: 52px; height: 52px;
|
||||||
background: #fff0f4;
|
background: #fff0f4;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2px solid #fecdd3;
|
border: 2px solid #fecdd3;
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Body ───────────────────────────────── */
|
/* ── Body ── */
|
||||||
.body { padding: 16px; }
|
.body { padding: 14px 16px; }
|
||||||
|
|
||||||
/* ── Hero Card ──────────────────────────── */
|
/* ── Camera Card ── */
|
||||||
.hero-card {
|
.cam-card {
|
||||||
background: linear-gradient(135deg, #0d9488, #14b8a6);
|
border-radius: 24px;
|
||||||
border-radius: 28px;
|
height: 200px;
|
||||||
height: 178px;
|
|
||||||
margin-bottom: 22px;
|
margin-bottom: 22px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 8px 28px rgba(20, 184, 166, 0.38);
|
background: #0d1117;
|
||||||
display: flex;
|
box-shadow: 0 10px 32px rgba(0,0,0,0.35);
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
.live-badge {
|
.cam-grid {
|
||||||
position: absolute;
|
position: absolute; inset: 0;
|
||||||
top: 14px; left: 16px;
|
background-image:
|
||||||
background: rgba(255,255,255,0.22);
|
linear-gradient(rgba(255,255,255,0.04) 1px, transparent 1px),
|
||||||
color: white; font-size: 11px;
|
linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px);
|
||||||
|
background-size: 40px 40px;
|
||||||
|
}
|
||||||
|
/* 暗色影调 */
|
||||||
|
.cam-card::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute; inset: 0;
|
||||||
|
background: radial-gradient(ellipse at 50% 50%, rgba(20,184,166,0.12) 0%, rgba(0,0,0,0.55) 100%);
|
||||||
|
}
|
||||||
|
.cam-topbar {
|
||||||
|
position: absolute; top: 14px; left: 14px;
|
||||||
|
display: flex; align-items: center; gap: 8px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.live-dot {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white; font-size: 11px; font-weight: 700;
|
||||||
padding: 3px 10px; border-radius: 20px;
|
padding: 3px 10px; border-radius: 20px;
|
||||||
animation: pulse 2s infinite;
|
letter-spacing: 0.5px;
|
||||||
|
animation: blink 1.8s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
|
@keyframes blink { 0%,100%{ opacity:1 } 50%{ opacity:0.55 } }
|
||||||
.hero-badge-br {
|
.cam-label { color: rgba(255,255,255,0.75); font-size: 12px; }
|
||||||
position: absolute;
|
.play-wrap {
|
||||||
bottom: 12px; right: 14px;
|
position: absolute; inset: 0;
|
||||||
background: rgba(0,0,0,0.16);
|
display: flex; align-items: center; justify-content: center;
|
||||||
color: white; font-size: 10px;
|
z-index: 2;
|
||||||
padding: 3px 10px; border-radius: 20px;
|
|
||||||
}
|
}
|
||||||
.hero-center { display: flex; flex-direction: column; align-items: center; gap: 6px; }
|
.play-btn {
|
||||||
.hero-icon-wrap {
|
width: 64px; height: 64px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255,255,255,0.18);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 2px solid rgba(255,255,255,0.35);
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
.cam-bottom {
|
||||||
|
position: absolute; bottom: 16px; left: 0; right: 0;
|
||||||
|
text-align: center; z-index: 2;
|
||||||
|
}
|
||||||
|
.cam-title { color: rgba(255,255,255,0.88); font-size: 14px; font-weight: 600; letter-spacing: 0.5px; }
|
||||||
|
|
||||||
|
/* ── Features (圆形) ── */
|
||||||
|
.features-row {
|
||||||
|
display: grid; grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 22px;
|
||||||
|
padding: 18px 8px 14px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
.feat-item { display: flex; flex-direction: column; align-items: center; gap: 8px; cursor: pointer; }
|
||||||
|
.feat-circle {
|
||||||
width: 58px; height: 58px;
|
width: 58px; height: 58px;
|
||||||
background: rgba(255,255,255,0.2);
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
margin-bottom: 4px;
|
box-shadow: 0 3px 10px rgba(0,0,0,0.08);
|
||||||
}
|
}
|
||||||
.hero-name { color: white; font-size: 16px; font-weight: 700; margin: 0; }
|
.feat-emoji { font-size: 26px; line-height: 1; }
|
||||||
.hero-desc { color: rgba(255,255,255,0.82); font-size: 12px; margin: 0; }
|
.feat-label { font-size: 12px; font-weight: 600; color: #374151; text-align: center; }
|
||||||
|
|
||||||
/* ── Features 4-grid ───────────────────── */
|
/* ── Schedule Card ── */
|
||||||
.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 {
|
.schedule-card {
|
||||||
background: linear-gradient(135deg, #0d9488, #0891b2);
|
background: linear-gradient(135deg, #0d9488, #0891b2);
|
||||||
border-radius: 28px;
|
border-radius: 24px;
|
||||||
padding: 20px 18px;
|
padding: 20px 18px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
color: white;
|
color: white;
|
||||||
position: relative;
|
box-shadow: 0 6px 24px rgba(13,148,136,0.38);
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 6px 22px rgba(13, 148, 136, 0.35);
|
|
||||||
}
|
}
|
||||||
.sch-bg-icon {
|
.sch-head { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; }
|
||||||
position: absolute; right: -8px; top: -8px;
|
.sch-title { font-size: 18px; font-weight: 800; margin: 0 0 4px; letter-spacing: -0.2px; }
|
||||||
font-size: 80px; opacity: 0.12;
|
.sch-date { font-size: 12px; color: rgba(255,255,255,0.72); margin: 0; }
|
||||||
|
.sch-day-badge {
|
||||||
|
background: rgba(255,255,255,0.22);
|
||||||
|
padding: 5px 14px; border-radius: 20px;
|
||||||
|
font-size: 12px; font-weight: 600; white-space: nowrap; flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.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 {
|
.sch-more-btn {
|
||||||
background: rgba(255,255,255,0.22);
|
background: rgba(255,255,255,0.22);
|
||||||
padding: 4px 12px; border-radius: 20px;
|
padding: 5px 12px; border-radius: 20px;
|
||||||
font-size: 11px; cursor: pointer; white-space: nowrap;
|
font-size: 11px; cursor: pointer; white-space: nowrap; flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.sch-list { display: flex; flex-direction: column; gap: 8px; }
|
.sch-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
.sch-item {
|
.sch-item {
|
||||||
background: rgba(255,255,255,0.14);
|
background: rgba(255,255,255,0.15);
|
||||||
border-radius: 14px; padding: 9px 14px;
|
border-radius: 14px; padding: 11px 14px;
|
||||||
display: flex; align-items: center; gap: 10px;
|
display: flex; align-items: center; gap: 12px;
|
||||||
}
|
}
|
||||||
.sch-time { font-size: 12px; font-weight: 600; white-space: nowrap; }
|
.sch-time { font-size: 13px; font-weight: 700; white-space: nowrap; min-width: 42px; }
|
||||||
.sch-sep { width: 1px; height: 18px; background: rgba(255,255,255,0.25); }
|
.sch-sep { width: 1px; height: 20px; background: rgba(255,255,255,0.3); flex-shrink: 0; }
|
||||||
.sch-content { font-size: 12px; font-weight: 500; }
|
.sch-content { font-size: 13px; font-weight: 500; }
|
||||||
.sch-empty { text-align: center; font-size: 13px; color: rgba(255,255,255,0.65); margin: 0; }
|
.sch-empty { text-align: center; font-size: 13px; color: rgba(255,255,255,0.6); margin: 4px 0 0; }
|
||||||
|
|
||||||
/* ── Data Grid ─────────────────────────── */
|
/* ── Data Grid ── */
|
||||||
.data-section { margin-bottom: 20px; }
|
.data-section { margin-bottom: 20px; }
|
||||||
.sec-title { font-size: 15px; font-weight: 700; color: #1f2937; margin: 0 0 12px; }
|
.sec-title { font-size: 16px; font-weight: 700; color: #1f2937; margin: 0 0 12px; }
|
||||||
.data-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
.data-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||||
.data-card {
|
.data-card {
|
||||||
background: white; border-radius: 22px; padding: 16px;
|
background: white; border-radius: 20px; padding: 16px;
|
||||||
box-shadow: 0 2px 14px rgba(0,0,0,0.05); cursor: pointer;
|
box-shadow: 0 2px 12px rgba(0,0,0,0.05); cursor: pointer;
|
||||||
}
|
}
|
||||||
.data-label { font-size: 11px; color: #9ca3af; margin: 0 0 6px; }
|
.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-val { font-size: 17px; font-weight: 700; color: #1f2937; margin: 0 0 3px; }
|
||||||
.data-ext { font-size: 12px; color: #10b981; margin: 0; }
|
.data-ext { font-size: 12px; color: #10b981; margin: 0; }
|
||||||
.data-ext.blue { color: #6366f1; }
|
.data-ext.blue { color: #6366f1; }
|
||||||
|
|
||||||
/* ── Records ───────────────────────────── */
|
/* ── Records ── */
|
||||||
.records-section { }
|
.records-section { }
|
||||||
.rec-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
.rec-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||||
.rec-more { font-size: 13px; color: #f43f5e; cursor: pointer; }
|
.rec-more { font-size: 13px; color: #f43f5e; cursor: pointer; }
|
||||||
@@ -282,7 +315,7 @@ const today = new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'nume
|
|||||||
display: flex; align-items: center; gap: 12px;
|
display: flex; align-items: center; gap: 12px;
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.04);
|
box-shadow: 0 2px 10px rgba(0,0,0,0.04);
|
||||||
}
|
}
|
||||||
.rec-emoji { font-size: 22px; }
|
.rec-emoji { font-size: 22px; }
|
||||||
.rec-nurse { font-size: 14px; font-weight: 600; color: #1f2937; margin: 0 0 2px; }
|
.rec-nurse { font-size: 14px; font-weight: 600; color: #1f2937; margin: 0 0 2px; }
|
||||||
.rec-time { font-size: 12px; color: #9ca3af; margin: 0; }
|
.rec-time { font-size: 12px; color: #9ca3af; margin: 0; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user