|
|
@@ -51,6 +51,61 @@
|
|
|
<!-- ⓪ 新手成长礼包进度条(新用户引导,全部领取后自动隐藏) -->
|
|
|
<OnboardingProgressBar ref="onboardingProgress" />
|
|
|
|
|
|
+ <!-- ⓪.b 问候条 — 家庭成员 + 按时间问候语(设计稿 §2 视觉焦点) -->
|
|
|
+ <view class="greet-bar">
|
|
|
+ <view class="greet-avatars">
|
|
|
+ <image
|
|
|
+ v-for="m in greetAvatarMembers"
|
|
|
+ :key="m._key"
|
|
|
+ class="greet-avatar"
|
|
|
+ :src="m.avatar || '/static/default-avatar.png'"
|
|
|
+ mode="aspectFill" />
|
|
|
+ <view v-if="greetExtraCount > 0" class="greet-avatar greet-avatar-more">
|
|
|
+ <text class="greet-avatar-more-text">+{{ greetExtraCount }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="greet-main">
|
|
|
+ <text class="greet-title">{{ greetTitle }}</text>
|
|
|
+ <text class="greet-line">{{ greetLine }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="greet-bell" @click="goNotifications">
|
|
|
+ <image class="greet-bell-icon" src="/static/icons/bell.svg" mode="aspectFit" />
|
|
|
+ <text v-if="unreadNoticeCount > 0" class="greet-bell-badge">{{ unreadNoticeCount }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- ⓪.c 五维能量状态带 — 健康指数 + 五行状态行(线性序 身→智→富→行→心,设计稿 §3) -->
|
|
|
+ <view class="dim-band" :class="{ 'dim-band--locked': sandboxLocked }">
|
|
|
+ <view class="dim-band-head">
|
|
|
+ <text class="dim-band-kicker">浠艾福能量沙盘</text>
|
|
|
+ <text class="dim-band-index">
|
|
|
+ <text class="dim-band-index-num">{{ dimBandHealthIndex }}</text>
|
|
|
+ <text class="dim-band-index-label">健康指数</text>
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <view v-if="!sandboxLocked && dimBandRows.length > 0" class="dim-band-rows">
|
|
|
+ <view
|
|
|
+ v-for="row in dimBandRows"
|
|
|
+ :key="row._key"
|
|
|
+ class="dim-row"
|
|
|
+ :class="'dim-row--' + row.code">
|
|
|
+ <image class="dim-row-icon" :src="row.icon" mode="aspectFit" />
|
|
|
+ <view class="dim-row-main">
|
|
|
+ <view class="dim-row-line">
|
|
|
+ <text class="dim-row-name">{{ row.name }}</text>
|
|
|
+ <text v-if="row.value !== null" class="dim-row-value">{{ row.value }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="dim-row-detail">{{ row.detail }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="dim-row-chip" :class="'chip-' + row.statusCss">{{ row.statusText }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else class="dim-band-empty" @click="goSelfTest">
|
|
|
+ <text class="dim-band-empty-title">{{ sandboxLocked ? '尚未完成健康自检' : '正在获取能量数据…' }}</text>
|
|
|
+ <text class="dim-band-empty-sub">{{ sandboxLocked ? '完成 4 步问卷,解锁全家能量报告' : '请稍候刷新' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
<!-- ① 了解区 — 自测大卡(首屏,引导"从了解开始") -->
|
|
|
<view class="self-test-card" @click="goSelfTest">
|
|
|
<view class="self-test-head">
|
|
|
@@ -698,6 +753,85 @@ title: '添加家庭成员',
|
|
|
color: 'gray'
|
|
|
}
|
|
|
},
|
|
|
+ // ===== ⓪.b 问候条数据(设计稿 §2)=====
|
|
|
+ // 头像组:最多展示 4 个成员头像,超出显示 +N
|
|
|
+ greetAvatarMembers: function() {
|
|
|
+ var arr = this.familyMembersVisible || []
|
|
|
+ var out = []
|
|
|
+ for (var i = 0; i < arr.length && i < 4; i++) {
|
|
|
+ var m = arr[i] || {}
|
|
|
+ out.push({
|
|
|
+ _key: 'ga-' + i + '-' + (m.id || m.memberId || 'x'),
|
|
|
+ avatar: m.avatar || m.headImg || m.headImage || ''
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return out
|
|
|
+ },
|
|
|
+ greetExtraCount: function() {
|
|
|
+ var arr = this.familyMembersVisible || []
|
|
|
+ return arr.length > 4 ? (arr.length - 4) : 0
|
|
|
+ },
|
|
|
+ // 问候语:按当前小时分时段(凌晨/早上/中午/下午/晚上)
|
|
|
+ greetTitle: function() {
|
|
|
+ var h = new Date().getHours()
|
|
|
+ if (h >= 0 && h < 6) return '夜深了'
|
|
|
+ if (h >= 6 && h < 9) return '早上好'
|
|
|
+ if (h >= 9 && h < 12) return '上午好'
|
|
|
+ if (h >= 12 && h < 14) return '中午好'
|
|
|
+ if (h >= 14 && h < 18) return '下午好'
|
|
|
+ return '晚上好'
|
|
|
+ },
|
|
|
+ greetLine: function() {
|
|
|
+ var count = (this.familyMembersVisible || []).length
|
|
|
+ var me = null
|
|
|
+ var arr = this.familyMembersVisible || []
|
|
|
+ for (var i = 0; i < arr.length; i++) {
|
|
|
+ if (arr[i] && arr[i].isSelf) { me = arr[i]; break }
|
|
|
+ }
|
|
|
+ var name = me ? (me.nickname || '家人') : '家人'
|
|
|
+ if (count === 0) return '欢迎回家,开启今天的健康管理'
|
|
|
+ return name + ',今天家里有 ' + count + ' 位成员一起守护健康'
|
|
|
+ },
|
|
|
+ unreadNoticeCount: function() {
|
|
|
+ var n = 0
|
|
|
+ var arr = this.notices || []
|
|
|
+ for (var i = 0; i < arr.length; i++) {
|
|
|
+ if (arr[i] && arr[i].unread) n++
|
|
|
+ }
|
|
|
+ return n
|
|
|
+ },
|
|
|
+ // ===== ⓪.c 五维能量状态带(设计稿 §3,线性序 身→智→富→行→心)=====
|
|
|
+ dimBandHealthIndex: function() {
|
|
|
+ if (this.sandboxLocked) return '--'
|
|
|
+ var v = this.totalHealthIndex || 0
|
|
|
+ return v > 0 ? String(v) : '--'
|
|
|
+ },
|
|
|
+ dimBandRows: function() {
|
|
|
+ if (this.sandboxLocked) return []
|
|
|
+ // 线性顺序(与沙盘星图环形序不同,B1 明确 身→智→富→行→心)
|
|
|
+ var order = ['body', 'wisdom', 'wealth', 'action', 'mind']
|
|
|
+ var scores = this.familySandboxScores || {}
|
|
|
+ var members = this.familySandboxMembers || []
|
|
|
+ var self = this
|
|
|
+ var rows = []
|
|
|
+ for (var i = 0; i < order.length; i++) {
|
|
|
+ var code = order[i]
|
|
|
+ var meta = self._dimMeta(code)
|
|
|
+ var val = scores[code]
|
|
|
+ var hasVal = (typeof val === 'number' && val > 0)
|
|
|
+ rows.push({
|
|
|
+ _key: 'db-' + code,
|
|
|
+ code: code,
|
|
|
+ name: meta.name,
|
|
|
+ icon: meta.icon,
|
|
|
+ value: hasVal ? val : null,
|
|
|
+ detail: self._dimDetail(code, val, members),
|
|
|
+ statusCss: hasVal ? self._dimStatusCss(val) : 'muted',
|
|
|
+ statusText: hasVal ? self._dimStatusText(val) : '未记录'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return rows
|
|
|
+ },
|
|
|
// ===== 首页动态提醒列表(含具体值)=====
|
|
|
// 顺序:今日能量/积分/CF 变更(带数值)→ 家庭动态(通知/公告/任务完成)
|
|
|
feedItems: function() {
|
|
|
@@ -1719,6 +1853,50 @@ title: '添加家庭成员',
|
|
|
uni.navigateTo({ url: '/pages/health/health-survey?modules=basic,status,diet,disease,bowel,sleep,med,allergy' })
|
|
|
}
|
|
|
},
|
|
|
+ // ===== ⓪.b 问候条 — 通知入口 =====
|
|
|
+ goNotifications: function() {
|
|
|
+ uni.navigateTo({ url: '/pages/teacher/messages' })
|
|
|
+ },
|
|
|
+ // ===== ⓪.c 五维能量状态带辅助方法 =====
|
|
|
+ // 维度元信息(图标为静态 SVG 资源;小程序不支持模板内联 svg)
|
|
|
+ _dimMeta: function(code) {
|
|
|
+ var map = {
|
|
|
+ body: { name: '身', icon: '/static/icons/dim-body.svg' },
|
|
|
+ wisdom: { name: '智', icon: '/static/icons/dim-wisdom.svg' },
|
|
|
+ wealth: { name: '富', icon: '/static/icons/dim-wealth.svg' },
|
|
|
+ action: { name: '行', icon: '/static/icons/dim-action.svg' },
|
|
|
+ mind: { name: '心', icon: '/static/icons/dim-mind.svg' }
|
|
|
+ }
|
|
|
+ return map[code] || { name: '', icon: '' }
|
|
|
+ },
|
|
|
+ // 详情文案:优先取该维度贡献最高的成员昵称,落到一句可读状态
|
|
|
+ _dimDetail: function(code, val, members) {
|
|
|
+ var list = members || []
|
|
|
+ var pick = null
|
|
|
+ for (var i = 0; i < list.length; i++) {
|
|
|
+ var m = list[i] || {}
|
|
|
+ var v = m[code + 'Score']
|
|
|
+ if (typeof v === 'number' && v > 0) {
|
|
|
+ if (!pick || v > pick.v) pick = { v: v, name: m.nickname || m.name || '成员' }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!pick) return '暂无数据'
|
|
|
+ var level = this._dimStatusText(val)
|
|
|
+ return pick.name + ' · ' + level
|
|
|
+ },
|
|
|
+ // 状态标签:平稳 / 略偏 / 需关注(阈值与沙盘读数口径一致)
|
|
|
+ _dimStatusText: function(val) {
|
|
|
+ if (typeof val !== 'number' || val <= 0) return '未记录'
|
|
|
+ if (val >= 80) return '平稳'
|
|
|
+ if (val >= 60) return '略偏'
|
|
|
+ return '需关注'
|
|
|
+ },
|
|
|
+ _dimStatusCss: function(val) {
|
|
|
+ if (typeof val !== 'number' || val <= 0) return 'muted'
|
|
|
+ if (val >= 80) return 'ok'
|
|
|
+ if (val >= 60) return 'warn'
|
|
|
+ return 'danger'
|
|
|
+ },
|
|
|
// ===== Next Best Action 引导条点击 =====
|
|
|
onGuideTap: function(action) {
|
|
|
if (!action) return
|
|
|
@@ -2834,6 +3012,207 @@ $c-warm-tint: #FFF3E7;
|
|
|
padding-bottom: 120rpx;
|
|
|
}
|
|
|
|
|
|
+/* ===== ⓪.b 问候条(设计稿 §2) ===== */
|
|
|
+.greet-bar {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20rpx $uni-space-lg;
|
|
|
+ background: $warm-base;
|
|
|
+}
|
|
|
+.greet-avatars {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ margin-right: 16rpx;
|
|
|
+}
|
|
|
+.greet-avatar {
|
|
|
+ width: 72rpx;
|
|
|
+ height: 72rpx;
|
|
|
+ border-radius: $radius-full;
|
|
|
+ border: 3rpx solid $warm-base;
|
|
|
+ margin-left: -16rpx;
|
|
|
+ background: $warm-base-alt;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.greet-avatar:first-child { margin-left: 0; }
|
|
|
+.greet-avatar-more {
|
|
|
+ background: $brand-primary-soft;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+.greet-avatar-more-text {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: $brand-primary-deep;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.greet-main { flex: 1; min-width: 0; }
|
|
|
+.greet-title {
|
|
|
+ display: block;
|
|
|
+ font-size: calc(38rpx * var(--font-scale, 1));
|
|
|
+ font-weight: bold;
|
|
|
+ color: $ink-main;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+.greet-line {
|
|
|
+ display: block;
|
|
|
+ font-size: calc(24rpx * var(--font-scale, 1));
|
|
|
+ color: $ink-subtle;
|
|
|
+ margin-top: 6rpx;
|
|
|
+}
|
|
|
+.greet-bell {
|
|
|
+ position: relative;
|
|
|
+ width: 88rpx;
|
|
|
+ height: 88rpx;
|
|
|
+ min-width: 88rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+.greet-bell-icon {
|
|
|
+ width: 48rpx;
|
|
|
+ height: 48rpx;
|
|
|
+}
|
|
|
+.greet-bell-badge {
|
|
|
+ position: absolute;
|
|
|
+ top: 4rpx;
|
|
|
+ right: 8rpx;
|
|
|
+ min-width: 32rpx;
|
|
|
+ height: 32rpx;
|
|
|
+ padding: 0 6rpx;
|
|
|
+ border-radius: $radius-full;
|
|
|
+ background: $brand-primary;
|
|
|
+ color: $ink-on-dark;
|
|
|
+ font-size: 20rpx;
|
|
|
+ line-height: 32rpx;
|
|
|
+ text-align: center;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== ⓪.c 五维能量状态带(设计稿 §3,线性序 身→智→富→行→心) ===== */
|
|
|
+.dim-band {
|
|
|
+ margin: 0 $uni-space-lg 16rpx;
|
|
|
+ background: $app-surface;
|
|
|
+ border-radius: $radius-lg;
|
|
|
+ padding: 24rpx 28rpx;
|
|
|
+ border: 1rpx solid rgba($brand-primary, 0.1);
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
|
|
|
+}
|
|
|
+.dim-band--locked { opacity: 0.9; }
|
|
|
+.dim-band-head {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+.dim-band-kicker {
|
|
|
+ font-size: calc(26rpx * var(--font-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ color: $brand-primary-deep;
|
|
|
+}
|
|
|
+.dim-band-index { display: flex; flex-direction: row; align-items: baseline; }
|
|
|
+.dim-band-index-num {
|
|
|
+ font-size: calc(48rpx * var(--font-scale, 1));
|
|
|
+ font-weight: bold;
|
|
|
+ color: $c-brand-ink;
|
|
|
+ margin-right: 8rpx;
|
|
|
+}
|
|
|
+.dim-band-index-label {
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
+ color: $ink-subtle;
|
|
|
+}
|
|
|
+.dim-band-rows { display: flex; flex-direction: column; }
|
|
|
+.dim-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ min-height: 88rpx;
|
|
|
+ padding: 12rpx 0;
|
|
|
+}
|
|
|
+.dim-row-icon {
|
|
|
+ width: 44rpx;
|
|
|
+ height: 44rpx;
|
|
|
+ margin-right: 16rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.dim-row-main { flex: 1; min-width: 0; }
|
|
|
+.dim-row-line { display: flex; flex-direction: row; align-items: baseline; }
|
|
|
+.dim-row-name {
|
|
|
+ font-size: calc(28rpx * var(--font-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ color: $ink-main;
|
|
|
+ margin-right: 12rpx;
|
|
|
+}
|
|
|
+.dim-row-value {
|
|
|
+ font-size: calc(28rpx * var(--font-scale, 1));
|
|
|
+ font-weight: bold;
|
|
|
+ color: $c-brand-ink;
|
|
|
+}
|
|
|
+.dim-row-detail {
|
|
|
+ display: block;
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
+ color: $ink-subtle;
|
|
|
+ margin-top: 4rpx;
|
|
|
+}
|
|
|
+.dim-row-chip {
|
|
|
+ flex-shrink: 0;
|
|
|
+ min-width: 88rpx;
|
|
|
+ text-align: center;
|
|
|
+ padding: 8rpx 16rpx;
|
|
|
+ border-radius: $radius-full;
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.chip-ok { background: $status-ok-tint; color: $status-ok-bg; }
|
|
|
+.chip-warn { background: $status-warn-tint; color: $status-warn-bg; }
|
|
|
+.chip-danger { background: $status-danger-tint; color: $status-danger-bg; }
|
|
|
+.chip-muted { background: $status-neutral-tint; color: $status-neutral-bg; }
|
|
|
+.dim-band-empty {
|
|
|
+ padding: 32rpx 0;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+.dim-band-empty-title {
|
|
|
+ display: block;
|
|
|
+ font-size: calc(28rpx * var(--font-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ color: $ink-main;
|
|
|
+}
|
|
|
+.dim-band-empty-sub {
|
|
|
+ display: block;
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
+ color: $ink-subtle;
|
|
|
+ margin-top: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 按钮八态(设计稿 §4:默认/悬停/焦点/按下/加载中/出错/成功白字;禁用=暖灰底+深字) ===== */
|
|
|
+.btn {
|
|
|
+ min-height: 88rpx;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border-radius: $radius-md;
|
|
|
+ font-size: calc(28rpx * var(--font-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ border: none;
|
|
|
+ transition: background-color $duration-base ease-out;
|
|
|
+}
|
|
|
+.btn--default { background: $btn-default-bg; color: $btn-default-ink; }
|
|
|
+.btn--hover { background: $btn-hover-bg; color: $btn-hover-ink; }
|
|
|
+.btn--focus { background: $btn-focus-bg; color: $btn-focus-ink; }
|
|
|
+.btn--press { background: $btn-press-bg; color: $btn-press-ink; }
|
|
|
+.btn--loading { background: $btn-loading-bg; color: $btn-loading-ink; }
|
|
|
+.btn--error { background: $btn-error-bg; color: $btn-error-ink; }
|
|
|
+.btn--success { background: $btn-success-bg; color: $btn-success-ink; }
|
|
|
+.btn--disabled { background: $btn-disabled-bg; color: $btn-disabled-ink; opacity: 1; }
|
|
|
+/* 触控目标 ≥44px(88rpx 基准),禁用态不降透明度 */
|
|
|
+.btn--disabled, .btn--disabled:active { opacity: 1; }
|
|
|
+/* 降低动效偏好:按钮/状态带/问候条过渡全局降级 */
|
|
|
+@media (prefers-reduced-motion: reduce) {
|
|
|
+ .btn, .dim-row, .greet-bar { transition-duration: 0.01ms !important; }
|
|
|
+}
|
|
|
+
|
|
|
/* ===== ① 了解区 — 自测大卡(引导"从了解开始") ===== */
|
|
|
.self-test-card {
|
|
|
margin: 20rpx $uni-space-lg 16rpx;
|