|
@@ -0,0 +1,170 @@
|
|
|
|
|
+<!--
|
|
|
|
|
+ 健康计划摘要组件(Step 3 签名前确认,Vue 2 Options API / uni-app 小程序兼容)
|
|
|
|
|
+ Props:
|
|
|
|
|
+ planName string 计划名
|
|
|
|
|
+ cycleText string 周期说明(如「8 周」/「2026-08-15 ~ 2026-10-15」)
|
|
|
|
|
+ planItems array 项目列表(元素可为字符串或 { name } 对象)
|
|
|
|
|
+ participants array 参与人列表(元素可为字符串或 { name } 对象)
|
|
|
|
|
+ Emits:
|
|
|
|
|
+ confirm 点击「确认」按钮时触发(由父页面承接签名/生成方案等后续动作)
|
|
|
|
|
+ 用法:
|
|
|
|
|
+ <health-plan-summary
|
|
|
|
|
+ :plan-name="planName"
|
|
|
|
|
+ :cycle-text="cycleText"
|
|
|
|
|
+ :plan-items="planItems"
|
|
|
|
|
+ :participants="participants"
|
|
|
|
|
+ @confirm="onConfirm"
|
|
|
|
|
+ />
|
|
|
|
|
+ 约定:
|
|
|
|
|
+ - 纯展示组件,数据一律经 props 传入,不发起任何 API 请求(父组件通过 props 传递数据)
|
|
|
|
|
+ - 遵守小程序约束:不用可选链 ?.、不用 CSS Grid、:key 用方法调用、日期解析统一走 parseDate
|
|
|
|
|
+-->
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="health-plan-summary">
|
|
|
|
|
+ <view class="summary-card">
|
|
|
|
|
+ <view class="summary-row">
|
|
|
|
|
+ <text class="summary-label">计划名</text>
|
|
|
|
|
+ <text class="summary-value">{{ planName }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="summary-row">
|
|
|
|
|
+ <text class="summary-label">周期</text>
|
|
|
|
|
+ <text class="summary-value">{{ cycleText }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="summary-block">
|
|
|
|
|
+ <text class="summary-label">项目</text>
|
|
|
|
|
+ <view class="summary-tags">
|
|
|
|
|
+ <view v-for="(item, index) in planItems" :key="getItemKey(item, index)" class="summary-tag">
|
|
|
|
|
+ <text class="summary-tag-text">{{ item && item.name ? item.name : item }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text v-if="!planItems || planItems.length === 0" class="summary-empty">暂无项目</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="summary-block">
|
|
|
|
|
+ <text class="summary-label">参与人</text>
|
|
|
|
|
+ <view class="summary-tags">
|
|
|
|
|
+ <view v-for="(participant, index) in participants" :key="getParticipantKey(participant, index)" class="summary-tag">
|
|
|
|
|
+ <text class="summary-tag-text">{{ participant && participant.name ? participant.name : participant }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text v-if="!participants || participants.length === 0" class="summary-empty">暂无参与人</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <button class="btn-confirm" @click="onConfirm">确认</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'HealthPlanSummary',
|
|
|
|
|
+ props: {
|
|
|
|
|
+ planName: {
|
|
|
|
|
+ type: String,
|
|
|
|
|
+ default: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ cycleText: {
|
|
|
|
|
+ type: String,
|
|
|
|
|
+ default: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ planItems: {
|
|
|
|
|
+ type: Array,
|
|
|
|
|
+ default: function() {
|
|
|
|
|
+ return []
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ participants: {
|
|
|
|
|
+ type: Array,
|
|
|
|
|
+ default: function() {
|
|
|
|
|
+ return []
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ // 小程序模板不支持带运算符的 :key 绑定 → 统一用方法返回稳定键
|
|
|
|
|
+ getItemKey: function(item, index) {
|
|
|
|
|
+ if (item && item.id != null) return String(item.id)
|
|
|
|
|
+ return String(index)
|
|
|
|
|
+ },
|
|
|
|
|
+ getParticipantKey: function(participant, index) {
|
|
|
|
|
+ if (participant && participant.memberId != null) return String(participant.memberId)
|
|
|
|
|
+ if (participant && participant.id != null) return String(participant.id)
|
|
|
|
|
+ return String(index)
|
|
|
|
|
+ },
|
|
|
|
|
+ // 确认回调:仅通知父页面,具体后续(签名/生成方案)由父页面决定
|
|
|
|
|
+ onConfirm: function() {
|
|
|
|
|
+ this.$emit('confirm')
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.health-plan-summary {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.summary-card {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ background: $app-surface;
|
|
|
|
|
+ border: 2rpx solid $facade-fill-2;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.summary-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ padding: 12rpx 0;
|
|
|
|
|
+}
|
|
|
|
|
+.summary-block {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ padding: 12rpx 0;
|
|
|
|
|
+}
|
|
|
|
|
+.summary-label {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ width: 120rpx;
|
|
|
|
|
+ font-size: calc(13px * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-muted;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+}
|
|
|
|
|
+.summary-value {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ font-size: calc(14px * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-strong;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+}
|
|
|
|
|
+.summary-tags {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ margin-top: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.summary-tag {
|
|
|
|
|
+ margin: 0 12rpx 12rpx 0;
|
|
|
|
|
+ padding: 8rpx 20rpx;
|
|
|
|
|
+ background: $facade-fill-2;
|
|
|
|
|
+ border-radius: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.summary-tag-text {
|
|
|
|
|
+ font-size: calc(12px * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-body;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+}
|
|
|
|
|
+.summary-empty {
|
|
|
|
|
+ font-size: calc(12px * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-ghost;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+}
|
|
|
|
|
+.btn-confirm {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 88rpx;
|
|
|
|
|
+ line-height: 88rpx;
|
|
|
|
|
+ margin-top: 24rpx;
|
|
|
|
|
+ font-size: calc(15px * var(--font-scale, 1));
|
|
|
|
|
+ color: $app-surface;
|
|
|
|
|
+ background: linear-gradient(135deg, $accent-indigo 0%, $accent-indigo-deep 100%);
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ border-radius: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.btn-confirm::after {
|
|
|
|
|
+ border: none;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|