فهرست منبع

fix: 修复 assessment-plan 分包页面缺失导致编译崩溃,补充 list/detail 页面并修正接口路径

liaoxg 2 ماه پیش
والد
کامیت
c8ece43ebb
3فایلهای تغییر یافته به همراه520 افزوده شده و 1 حذف شده
  1. 276 0
      cfc-frontend/pages/assessment-plan/detail.vue
  2. 243 0
      cfc-frontend/pages/assessment-plan/list.vue
  3. 1 1
      cfc-frontend/utils/api.js

+ 276 - 0
cfc-frontend/pages/assessment-plan/detail.vue

@@ -0,0 +1,276 @@
+<template>
+  <view class="detail-container">
+    <!-- 加载状态 -->
+    <view class="loading-wrap" v-if="loading">
+      <text class="loading-text">加载中...</text>
+    </view>
+
+    <!-- 方案详情 -->
+    <view class="detail-card" v-else-if="plan">
+      <view class="detail-header">
+        <text class="detail-title">{{ plan.name || '测评方案' }}</text>
+        <text class="plan-status" :class="'status-' + (plan.status || 'unknown')">{{ statusLabel(plan.status) }}</text>
+      </view>
+
+      <view class="detail-meta">
+        <view class="meta-row">
+          <text class="meta-label">方案周期</text>
+          <text class="meta-value">{{ formatDate(plan.startDate) }} ~ {{ formatDate(plan.endDate) }}</text>
+        </view>
+        <view class="meta-row" v-if="plan.totalDays">
+          <text class="meta-label">完成进度</text>
+          <text class="meta-value">{{ plan.completedDays || 0 }} / {{ plan.totalDays }} 天</text>
+        </view>
+        <view class="meta-row" v-if="plan.remark">
+          <text class="meta-label">方案说明</text>
+          <text class="meta-value">{{ plan.remark }}</text>
+        </view>
+        <view class="meta-row" v-if="plan.reviewComment">
+          <text class="meta-label">审核备注</text>
+          <text class="meta-value">{{ plan.reviewComment }}</text>
+        </view>
+        <view class="meta-row" v-if="plan.source">
+          <text class="meta-label">方案来源</text>
+          <text class="meta-value">{{ sourceLabel(plan.source) }}</text>
+        </view>
+        <view class="meta-row" v-if="plan.createdAt">
+          <text class="meta-label">创建时间</text>
+          <text class="meta-value">{{ formatDateTime(plan.createdAt) }}</text>
+        </view>
+      </view>
+
+      <view class="progress-block" v-if="plan.totalDays">
+        <view class="progress-track">
+          <view class="progress-fill" :style="'width:' + progressPercent(plan) + '%'"></view>
+        </view>
+        <text class="progress-text">{{ progressPercent(plan) }}%</text>
+      </view>
+    </view>
+
+    <!-- 空状态 -->
+    <view class="empty-wrap" v-else>
+      <text class="empty-icon">📋</text>
+      <text class="empty-title">方案不存在</text>
+      <text class="empty-desc">该测评方案可能已被删除,请返回列表刷新</text>
+    </view>
+
+    <!-- 底部占位 -->
+    <view class="bottom-spacer"></view>
+  </view>
+</template>
+
+<script>
+import { getMyAssessmentPlans } from '../../utils/api.js'
+import { parseDate } from '../../utils/format.js'
+
+export default {
+  data() {
+    return {
+      plan: null,
+      loading: false,
+      planId: null,
+      familyId: null,
+      childId: null
+    }
+  },
+  onLoad(options) {
+    this.planId = options.planId ? parseInt(options.planId) : null
+    this.familyId = options.familyId ? parseInt(options.familyId) : null
+    this.childId = options.childId ? parseInt(options.childId) : null
+    this.loadDetail()
+  },
+  methods: {
+    loadDetail() {
+      var self = this
+      var familyId = this.familyId || uni.getStorageSync('familyId')
+      if (!familyId || !this.planId) {
+        this.loading = false
+        return
+      }
+      self.loading = true
+      getMyAssessmentPlans(familyId, this.childId).then(function(res) {
+        var list = (res && res.code === 200 && res.data) ? res.data : []
+        var found = null
+        for (var i = 0; i < list.length; i++) {
+          if (list[i] && list[i].id === self.planId) {
+            found = list[i]
+            break
+          }
+        }
+        self.plan = found
+      }).catch(function(e) {
+        console.log('获取方案详情失败', e)
+        uni.showToast({ title: '加载失败', icon: 'none' })
+      }).finally(function() {
+        self.loading = false
+      })
+    },
+    statusLabel(status) {
+      var map = { active: '进行中', completed: '已完成', cancelled: '已取消', generated: '待审核', reviewed: '已通过', rejected: '已驳回' }
+      return map[status] || (status || '未知')
+    },
+    sourceLabel(source) {
+      var map = { manual: '手动创建', assessment: '测评生成', template: '模板创建' }
+      return map[source] || (source || '--')
+    },
+    progressPercent(item) {
+      if (!item.totalDays) return 0
+      var completed = item.completedDays || 0
+      var p = Math.round(completed / item.totalDays * 100)
+      return p > 100 ? 100 : p
+    },
+    formatDate(dateStr) {
+      if (!dateStr) return '--'
+      var d = parseDate(dateStr)
+      if (!d) return dateStr
+      var y = d.getFullYear()
+      var m = ('' + (d.getMonth() + 1)).padStart(2, '0')
+      var day = ('' + d.getDate()).padStart(2, '0')
+      return y + '-' + m + '-' + day
+    },
+    formatDateTime(dateStr) {
+      if (!dateStr) return '--'
+      var d = parseDate(dateStr)
+      if (!d) return dateStr
+      var hh = ('' + d.getHours()).padStart(2, '0')
+      var mm = ('' + d.getMinutes()).padStart(2, '0')
+      var ss = ('' + d.getSeconds()).padStart(2, '0')
+      return this.formatDate(dateStr) + ' ' + hh + ':' + mm + ':' + ss
+    }
+  }
+}
+</script>
+
+<style scoped>
+.detail-container {
+  min-height: 100vh;
+  background: #F5F7FA;
+}
+
+/* ===== 加载状态 ===== */
+.loading-wrap {
+  display: flex;
+  justify-content: center;
+  padding: 80rpx 0;
+}
+.loading-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
+/* ===== 详情卡片 ===== */
+.detail-card {
+  margin: 30rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 32rpx 28rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+}
+.detail-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 24rpx;
+}
+.detail-title {
+  font-size: 34rpx;
+  font-weight: 600;
+  color: #333;
+  flex: 1;
+  min-width: 0;
+}
+.plan-status {
+  font-size: 22rpx;
+  padding: 4rpx 16rpx;
+  border-radius: 20rpx;
+  margin-left: 12rpx;
+}
+.status-active { background: #E3F2FD; color: #1565C0; }
+.status-completed { background: #E8F5E9; color: #2E7D32; }
+.status-cancelled { background: #ECEFF1; color: #78909C; }
+.status-generated { background: #FFF3E0; color: #E65100; }
+.status-reviewed { background: #E8F5E9; color: #2E7D32; }
+.status-rejected { background: #FDE8E8; color: #C62828; }
+.status-unknown { background: #ECEFF1; color: #78909C; }
+
+/* ===== 元信息 ===== */
+.detail-meta {
+  border-top: 1rpx solid #F1F5F9;
+  padding-top: 8rpx;
+}
+.meta-row {
+  display: flex;
+  flex-direction: row;
+  align-items: flex-start;
+  padding: 14rpx 0;
+}
+.meta-label {
+  width: 160rpx;
+  font-size: 26rpx;
+  color: #999;
+  flex-shrink: 0;
+  line-height: 1.5;
+}
+.meta-value {
+  flex: 1;
+  font-size: 26rpx;
+  color: #333;
+  line-height: 1.5;
+  word-break: break-all;
+}
+
+/* ===== 进度 ===== */
+.progress-block {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-top: 24rpx;
+}
+.progress-track {
+  flex: 1;
+  height: 16rpx;
+  background: #EEF2F7;
+  border-radius: 8rpx;
+  overflow: hidden;
+  margin-right: 16rpx;
+}
+.progress-fill {
+  height: 100%;
+  background: #5B9BD5;
+  border-radius: 8rpx;
+}
+.progress-text {
+  font-size: 24rpx;
+  color: #999;
+}
+
+/* ===== 空状态 ===== */
+.empty-wrap {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 120rpx 60rpx;
+}
+.empty-icon {
+  font-size: 80rpx;
+  margin-bottom: 24rpx;
+}
+.empty-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 12rpx;
+}
+.empty-desc {
+  font-size: 26rpx;
+  color: #999;
+  text-align: center;
+  line-height: 1.5;
+}
+
+/* ===== 底部 ===== */
+.bottom-spacer {
+  height: 60rpx;
+}
+</style>

+ 243 - 0
cfc-frontend/pages/assessment-plan/list.vue

@@ -0,0 +1,243 @@
+<template>
+  <view class="list-container">
+    <!-- 加载状态 -->
+    <view class="loading-wrap" v-if="loading">
+      <text class="loading-text">加载中...</text>
+    </view>
+
+    <!-- 方案列表 -->
+    <view class="plan-list" v-else-if="plans.length > 0">
+      <view
+        class="plan-card"
+        v-for="item in plans"
+        :key="item.id"
+        @click="goToDetail(item.id)"
+      >
+        <view class="plan-card-top">
+          <text class="plan-name">{{ item.name || '测评方案' }}</text>
+          <text class="plan-status" :class="'status-' + (item.status || 'unknown')">{{ statusLabel(item.status) }}</text>
+        </view>
+        <view class="plan-meta" v-if="item.startDate || item.endDate">
+          <text class="plan-meta-item">周期:{{ formatDate(item.startDate) }} ~ {{ formatDate(item.endDate) }}</text>
+        </view>
+        <view class="plan-progress" v-if="item.totalDays">
+          <view class="progress-track">
+            <view class="progress-fill" :style="'width:' + progressPercent(item) + '%'"></view>
+          </view>
+          <text class="progress-text">{{ item.completedDays || 0 }} / {{ item.totalDays }} 天</text>
+        </view>
+        <view class="plan-arrow">›</view>
+      </view>
+    </view>
+
+    <!-- 空状态 -->
+    <view class="empty-wrap" v-else>
+      <text class="empty-icon">📋</text>
+      <text class="empty-title">暂无测评方案</text>
+      <text class="empty-desc">上传健康报告后,系统将自动为您生成个性化测评方案</text>
+    </view>
+
+    <!-- 底部占位 -->
+    <view class="bottom-spacer"></view>
+  </view>
+</template>
+
+<script>
+import { getMyAssessmentPlans } from '../../utils/api.js'
+import { parseDate } from '../../utils/format.js'
+
+export default {
+  data() {
+    return {
+      plans: [],
+      loading: false,
+      familyId: null,
+      childId: null
+    }
+  },
+  onLoad(options) {
+    this.familyId = options.familyId ? parseInt(options.familyId) : null
+    this.childId = options.childId ? parseInt(options.childId) : null
+    this.loadData()
+  },
+  methods: {
+    loadData() {
+      var self = this
+      var familyId = this.familyId || uni.getStorageSync('familyId')
+      if (!familyId) {
+        uni.showToast({ title: '请先选择家庭', icon: 'none' })
+        return
+      }
+      self.loading = true
+      getMyAssessmentPlans(familyId, this.childId).then(function(res) {
+        if (res.code === 200) {
+          self.plans = res.data || []
+        } else {
+          uni.showToast({ title: res.message || '加载失败', icon: 'none' })
+        }
+      }).catch(function(e) {
+        console.log('获取测评方案失败', e)
+        uni.showToast({ title: '加载失败', icon: 'none' })
+      }).finally(function() {
+        self.loading = false
+      })
+    },
+    goToDetail(planId) {
+      uni.navigateTo({ url: '/pages/assessment-plan/detail?planId=' + planId })
+    },
+    statusLabel(status) {
+      var map = { active: '进行中', completed: '已完成', cancelled: '已取消', generated: '待审核', reviewed: '已通过', rejected: '已驳回' }
+      return map[status] || (status || '未知')
+    },
+    progressPercent(item) {
+      if (!item.totalDays) return 0
+      var completed = item.completedDays || 0
+      var p = Math.round(completed / item.totalDays * 100)
+      return p > 100 ? 100 : p
+    },
+    formatDate(dateStr) {
+      if (!dateStr) return '--'
+      var d = parseDate(dateStr)
+      if (!d) return dateStr
+      var y = d.getFullYear()
+      var m = ('' + (d.getMonth() + 1)).padStart(2, '0')
+      var day = ('' + d.getDate()).padStart(2, '0')
+      return y + '-' + m + '-' + day
+    }
+  }
+}
+</script>
+
+<style scoped>
+.list-container {
+  min-height: 100vh;
+  background: #F5F7FA;
+}
+
+/* ===== 加载状态 ===== */
+.loading-wrap {
+  display: flex;
+  justify-content: center;
+  padding: 80rpx 0;
+}
+.loading-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
+/* ===== 方案列表 ===== */
+.plan-list {
+  margin: 20rpx 30rpx;
+}
+.plan-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 28rpx 24rpx;
+  margin-bottom: 16rpx;
+  position: relative;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+}
+.plan-card:active {
+  opacity: 0.8;
+}
+.plan-card-top {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+}
+.plan-name {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #333;
+  flex: 1;
+  min-width: 0;
+}
+.plan-status {
+  font-size: 22rpx;
+  padding: 4rpx 16rpx;
+  border-radius: 20rpx;
+  margin-left: 12rpx;
+}
+.status-active { background: #E3F2FD; color: #1565C0; }
+.status-completed { background: #E8F5E9; color: #2E7D32; }
+.status-cancelled { background: #ECEFF1; color: #78909C; }
+.status-generated { background: #FFF3E0; color: #E65100; }
+.status-reviewed { background: #E8F5E9; color: #2E7D32; }
+.status-rejected { background: #FDE8E8; color: #C62828; }
+.status-unknown { background: #ECEFF1; color: #78909C; }
+
+/* ===== 维度 ===== */
+.plan-meta {
+  margin-top: 12rpx;
+}
+.plan-meta-item {
+  font-size: 24rpx;
+  color: #888;
+}
+
+/* ===== 进度 ===== */
+.plan-progress {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-top: 16rpx;
+}
+.progress-track {
+  flex: 1;
+  height: 12rpx;
+  background: #EEF2F7;
+  border-radius: 6rpx;
+  overflow: hidden;
+  margin-right: 16rpx;
+}
+.progress-fill {
+  height: 100%;
+  background: #5B9BD5;
+  border-radius: 6rpx;
+}
+.progress-text {
+  font-size: 22rpx;
+  color: #999;
+}
+
+/* ===== 箭头 ===== */
+.plan-arrow {
+  position: absolute;
+  right: 24rpx;
+  top: 50%;
+  transform: translateY(-50%);
+  font-size: 36rpx;
+  color: #CCC;
+}
+
+/* ===== 空状态 ===== */
+.empty-wrap {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 120rpx 60rpx;
+}
+.empty-icon {
+  font-size: 80rpx;
+  margin-bottom: 24rpx;
+}
+.empty-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 12rpx;
+}
+.empty-desc {
+  font-size: 26rpx;
+  color: #999;
+  text-align: center;
+  margin-bottom: 40rpx;
+  line-height: 1.5;
+}
+
+/* ===== 底部 ===== */
+.bottom-spacer {
+  height: 60rpx;
+}
+</style>

+ 1 - 1
cfc-frontend/utils/api.js

@@ -2262,7 +2262,7 @@ export const parseReportDraft = (draftId) => request('/api/health/report/parse',
 export const confirmAssessmentReportConfirmInbox = (resultId, overallScore) => 
   request('/api/dan-assessment/result/' + resultId + '/confirm-inbox', 'POST', { overallScore })
 export const getMyAssessmentPlans = (familyId, childId) => 
-  request('/api/dan-assessment-result/plans/list', 'POST', { familyId, childId })
+  request('/api/dan-assessment/result/plans/list', 'POST', { familyId, childId })
 // 鎸囨爣瀹氫箟
 export const getIndicatorDefinitions = (data) => request('/api/admin/indicator/list', 'POST', data)