Files
hms/apps/miniprogram-uniapp/src/pages/index/index.vue
iven 2c567bd772 fix(mp): T40 UI 审查全量修复 + 设计体系一致性优化
Phase 0 基础设施:
- statusTag.ts: getStatusInlineStyle() 移除内联 borderRadius/padding/fontSize,仅返回 {background, color}
- 新增 SEVERITY_COLORS + getSeverityStyle() + getSeverityLabel() 统一告警严重程度样式
- variables.scss: 新增 9 个语义颜色别名 ($success/$danger/$warning/$info 等)
- mixins.scss: 新增 status-inline mixin 统一状态标签样式
- 7 个消费者页面添加 @include status-inline CSS 补偿

Phase 1 HIGH 修复 (4 页面):
- P46 随访管理: 移除 getTypeStyle() 硬编码 fontSize,替换文字 Loading 为组件
- P45 咨询详情医护: 添加 Loading/ErrorState 三态模板 + error ref
- P02 健康数据: 添加 loading ref + Loading 组件 + 错误 toast 提示
- P48 告警中心: 替换本地 SEVERITY_COLORS/SEVERITY_LABELS 为 statusTag.ts 导出

Phase 2 全局一致性:
- 2.1 触控补全: 17 页面为可点击元素添加 min-height: $touch-min
- 2.2 字号替换: 19 文件 31 处硬编码 px → Design Token CSS 变量
- 2.3 颜色替换: 18 文件 ~50 处硬编码十六进制 → SCSS 语义变量
- 2.4 elder-mode.scss: 新增 9 个选择器到触控放大清单

Phase 3 LOW 修复:
- 3.1 统一 Loading: 21 页面旧式文字加载 → <Loading> 组件
- 3.2 useElderClass: 8 页面补全长者模式 class 绑定
- 3.3 零散修复: 按钮 44px→48px,诊断记录添加 scroll-view 无限加载

同时新增 UniApp (Vue 3 + Vite) 小程序完整代码库 (146 文件)
2026-05-15 11:22:51 +08:00

318 lines
8.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<scroll-view scroll-y class="home-scroll" @scrolltolower="onLoadMore">
<view :class="['home-page', elderClass]">
<!-- 已登录模式 -->
<template v-if="authStore.user">
<!-- 用户问候 -->
<view class="greeting-section">
<text class="greeting-text">{{ greeting }}{{ authStore.user.display_name || authStore.user.username }}</text>
<text class="greeting-date">{{ today }}</text>
</view>
<!-- 快捷功能 -->
<view class="quick-actions">
<view class="action-item" @tap="navigateTo('/pages-sub/appointment/create/index')">
<text class="action-icon">📅</text>
<text class="action-label">预约</text>
</view>
<view class="action-item" @tap="navigateTo('/pages-sub/consultation/index')">
<text class="action-icon">💬</text>
<text class="action-label">咨询</text>
</view>
<view class="action-item" @tap="navigateTo('/pages-sub/pkg-health/trend/index')">
<text class="action-icon">📊</text>
<text class="action-label">趋势</text>
</view>
<view class="action-item" @tap="navigateTo('/pages-sub/article/index')">
<text class="action-icon">📰</text>
<text class="action-label">文章</text>
</view>
</view>
<!-- 健康概览卡片 -->
<view class="health-summary card">
<text class="section-title">健康概览</text>
<view v-if="healthSummary" class="summary-grid">
<view class="summary-item">
<text class="summary-value">{{ healthSummary.heart_rate || '--' }}</text>
<text class="summary-label">心率</text>
</view>
<view class="summary-item">
<text class="summary-value">{{ healthSummary.blood_pressure || '--' }}</text>
<text class="summary-label">血压</text>
</view>
<view class="summary-item">
<text class="summary-value">{{ healthSummary.blood_sugar || '--' }}</text>
<text class="summary-label">血糖</text>
</view>
</view>
<Loading v-else-if="summaryLoading" text="加载中..." />
<EmptyState v-else icon="📋" title="暂无健康数据" action-text="录入数据" @action="switchTab('/pages/health/index')" />
</view>
<!-- 最近文章 -->
<view class="articles-section card">
<text class="section-title">健康文章</text>
<Loading v-if="articlesLoading" text="加载中..." />
<template v-else-if="articles.length > 0">
<view v-for="article in articles" :key="article.id" class="article-entry" @tap="goArticle(article.id)">
<text class="article-title">{{ article.title }}</text>
<text class="article-date">{{ formatDate(article.created_at) }}</text>
</view>
</template>
<EmptyState v-else icon="📰" title="暂无文章" />
</view>
</template>
<!-- 访客模式 -->
<template v-else>
<view class="guest-page">
<text class="guest-hero-icon">🏥</text>
<text class="guest-hero-title">健康管理</text>
<text class="guest-hero-desc">您的专属健康管家</text>
<view class="guest-login-btn" @tap="navigateTo('/pages/login/index')">
登录 / 注册
</view>
<text class="guest-browse">浏览健康资讯</text>
<!-- 访客也展示文章 -->
<view class="articles-section card">
<text class="section-title">健康文章</text>
<Loading v-if="articlesLoading" text="加载中..." />
<template v-else-if="articles.length > 0">
<view v-for="article in articles" :key="article.id" class="article-entry" @tap="goArticle(article.id)">
<text class="article-title">{{ article.title }}</text>
<text class="article-date">{{ formatDate(article.created_at) }}</text>
</view>
</template>
</view>
</view>
</template>
</view>
</scroll-view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { useAuthStore } from '@/stores/auth'
import { useElderClass } from '@/composables/useElderClass'
import { getArticles } from '@/services/article'
import { getTodaySummary } from '@/services/health'
import { formatDate } from '@/utils/date'
import Loading from '@/components/Loading.vue'
import EmptyState from '@/components/EmptyState.vue'
const authStore = useAuthStore()
const { elderClass } = useElderClass()
const articlesLoading = ref(false)
const summaryLoading = ref(false)
const articles = ref<any[]>([])
const healthSummary = ref<any>(null)
const greeting = computed(() => {
const h = new Date().getHours()
if (h < 6) return '夜深了'
if (h < 12) return '早上好'
if (h < 14) return '中午好'
if (h < 18) return '下午好'
return '晚上好'
})
const today = computed(() => formatDate(new Date(), 'YYYY年MM月DD日'))
function navigateTo(url: string) {
uni.navigateTo({ url })
}
function switchTab(url: string) {
uni.switchTab({ url })
}
function goArticle(id: string) {
uni.navigateTo({ url: `/pages-sub/article/detail/index?id=${id}` })
}
function onLoadMore() {
// 分页加载预留
}
async function fetchArticles() {
articlesLoading.value = true
try {
const res = await getArticles({ limit: 5 })
articles.value = res || []
} catch {
articles.value = []
}
articlesLoading.value = false
}
async function fetchHealthSummary() {
if (!authStore.user) return
summaryLoading.value = true
try {
healthSummary.value = await getTodaySummary()
} catch {
healthSummary.value = null
}
summaryLoading.value = false
}
onMounted(() => {
fetchArticles()
fetchHealthSummary()
})
onShow(() => {
authStore.restore()
})
</script>
<style lang="scss" scoped>
.home-scroll {
height: 100vh;
background: $bg;
}
.home-page {
padding: 28px 24px 120px;
}
.greeting-section {
margin-bottom: 28px;
}
.greeting-text {
display: block;
font-size: var(--tk-font-h1);
font-weight: bold;
color: $tx;
}
.greeting-date {
display: block;
font-size: var(--tk-font-body-sm);
color: $tx3;
margin-top: 4px;
}
.quick-actions {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 28px;
}
.action-item {
@include flex-center;
flex-direction: column;
background: $card;
border-radius: $r-sm;
padding: 20px 0;
box-shadow: $shadow-sm;
}
.action-icon {
font-size: var(--tk-font-hero);
margin-bottom: 8px;
}
.action-label {
font-size: var(--tk-font-body-sm);
color: $tx2;
}
.card {
@include card;
}
.section-title {
@include section-title;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.summary-item {
@include flex-center;
flex-direction: column;
}
.summary-value {
font-size: var(--tk-font-num);
font-weight: bold;
color: $pri;
@include serif-number;
}
.summary-label {
font-size: var(--tk-font-body-sm);
color: $tx3;
margin-top: 4px;
}
.article-entry {
padding: 16px 0;
min-height: $touch-min;
border-bottom: 1px solid $bd-l;
&:last-child { border-bottom: none; }
}
.article-title {
display: block;
font-size: var(--tk-font-body);
color: $tx;
font-weight: 500;
}
.article-date {
display: block;
font-size: var(--tk-font-cap);
color: $tx3;
margin-top: 4px;
}
// 访客模式
.guest-page {
@include flex-center;
flex-direction: column;
padding-top: 80px;
}
.guest-hero-icon {
font-size: var(--tk-font-display);
margin-bottom: 16px;
}
.guest-hero-title {
font-size: var(--tk-font-h1);
font-weight: bold;
color: $tx;
margin-bottom: 8px;
}
.guest-hero-desc {
font-size: var(--tk-font-body);
color: $tx3;
margin-bottom: 40px;
}
.guest-login-btn {
@include btn-primary;
width: 280px;
margin-bottom: 16px;
}
.guest-browse {
font-size: var(--tk-font-body-sm);
color: $pri;
margin-bottom: 40px;
}
</style>