|
|
@@ -0,0 +1,394 @@
|
|
|
+<template>
|
|
|
+ <view class="page-container">
|
|
|
+ <!-- 标题栏 -->
|
|
|
+ <view class="header">
|
|
|
+ <text class="title">📋 我的测评方案</text>
|
|
|
+ <text class="subtitle">根据报告自动生成 · 14 天个性化成长计划</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <view class="loading" v-if="loading">加载中...</view>
|
|
|
+
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <view class="empty" v-else-if="plans.length === 0">
|
|
|
+ <view class="empty-icon">📊</view>
|
|
|
+ <text class="empty-title">暂无测评方案</text>
|
|
|
+ <text class="empty-desc">用户上传测评报告并确认入库后,系统将自动生成个性化成长方案</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 方案列表 -->
|
|
|
+ <view class="plan-list" v-else>
|
|
|
+ <view
|
|
|
+ class="plan-card"
|
|
|
+ v-for="plan in plans"
|
|
|
+ :key="plan.id"
|
|
|
+ @click="goToDetail(plan.id)"
|
|
|
+ >
|
|
|
+ <view class="plan-header">
|
|
|
+ <text class="plan-status-badge" :class="'status-' + plan.status">{{ getStatusText(plan.status) }}</text>
|
|
|
+ <text class="plan-date">{{ formatDateTime(plan.createdAt) }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="plan-body">
|
|
|
+ <text class="plan-name">{{ plan.name }}</text>
|
|
|
+ <view class="plan-progress">
|
|
|
+ <view class="progress-info">
|
|
|
+ <text class="progress-text">{{ plan.completedDays }}/{{ plan.totalDays }} 天已完成</text>
|
|
|
+ </view>
|
|
|
+ <view class="progress-bar">
|
|
|
+ <view
|
|
|
+ class="progress-fill"
|
|
|
+ :style="'width: ' + (plan.completedDays / plan.totalDays * 100) + '%'"
|
|
|
+ ></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="plan-meta">
|
|
|
+ <text class="meta-item">
|
|
|
+ <text class="meta-label">周期:</text>
|
|
|
+ <text class="meta-value">{{ plan.totalDays }}天</text>
|
|
|
+ </text>
|
|
|
+ <text class="meta-item">
|
|
|
+ <text class="meta-label">孩子:</text>
|
|
|
+ <text class="meta-value">{{ plan.childName || '未知' }}</text>
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="plan-footer">
|
|
|
+ <text class="detail-text">查看详情 ›</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 底部占位 -->
|
|
|
+ <view class="spacer"></view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getMyAssessmentPlans } from '../../utils/api.js'
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ plans: [],
|
|
|
+ familyId: null,
|
|
|
+ childId: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad(options) {
|
|
|
+ // 获取家庭 ID 和孩子 ID
|
|
|
+ this.familyId = uni.getStorageSync('familyId') ? parseInt(uni.getStorageSync('familyId')) : null
|
|
|
+ this.childId = options.childId ? parseInt(options.childId) : null
|
|
|
+
|
|
|
+ if (!this.familyId) {
|
|
|
+ uni.showToast({ title: '未找到家庭信息', icon: 'none' })
|
|
|
+ setTimeout(() => uni.navigateBack(), 1500)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loadData()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ loadData() {
|
|
|
+ var self = this
|
|
|
+ self.loading = true
|
|
|
+
|
|
|
+ getMyAssessmentPlans(self.familyId, self.childId).then(function(res) {
|
|
|
+ self.loading = false
|
|
|
+
|
|
|
+ if (res.code === 200 && Array.isArray(res.data)) {
|
|
|
+ self.plans = res.data.map(function(plan) {
|
|
|
+ return {
|
|
|
+ ...plan,
|
|
|
+ childName: plan.childName || self.getChildNameFromRemark(plan.remark)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 如果没有传 childId 且有多条记录,按孩子分组显示
|
|
|
+ if (!self.childId && self.hasMultipleChildren()) {
|
|
|
+ console.log('检测到多个孩子,已自动按孩子分组显示')
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ uni.showToast({
|
|
|
+ title: res.message || '加载失败',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }).catch(function(e) {
|
|
|
+ self.loading = false
|
|
|
+ console.error('加载方案失败', e)
|
|
|
+ uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 从 remark 中解析孩子姓名(如果未包含在数据中)
|
|
|
+ getChildNameFromRemark(remark) {
|
|
|
+ if (!remark) return ''
|
|
|
+ try {
|
|
|
+ // 尝试从备注中解析,格式如:"【测评方案自动生成】\n..."
|
|
|
+ var lines = remark.split('\n')
|
|
|
+ for (var i = 0; i < lines.length; i++) {
|
|
|
+ if (lines[i].includes('孩子:') || lines[i].includes('child_id:')) {
|
|
|
+ return lines[i].split(':')[1]?.trim() || ''
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析备注失败', e)
|
|
|
+ }
|
|
|
+ return ''
|
|
|
+ },
|
|
|
+
|
|
|
+ // 检查是否有多个孩子的方案
|
|
|
+ hasMultipleChildren() {
|
|
|
+ if (!this.plans || this.plans.length === 0) return false
|
|
|
+ var children = new Set()
|
|
|
+ for (var i = 0; i < this.plans.length; i++) {
|
|
|
+ if (this.plans[i].childId) {
|
|
|
+ children.add(this.plans[i].childId)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return children.size > 1
|
|
|
+ },
|
|
|
+
|
|
|
+ goToDetail(planId) {
|
|
|
+ // TODO: 实现详情页面(待开发)
|
|
|
+ // 暂时跳转到成长计划页面
|
|
|
+ uni.navigateTo({
|
|
|
+ url: '/pages/health/health-plan-summary?planId=' + planId
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ getStatusText(status) {
|
|
|
+ var map = {
|
|
|
+ 'generated': '已生成',
|
|
|
+ 'active': '进行中',
|
|
|
+ 'completed': '已完成',
|
|
|
+ 'expired': '已过期',
|
|
|
+ 'draft': '草稿'
|
|
|
+ }
|
|
|
+ return map[status] || status
|
|
|
+ },
|
|
|
+
|
|
|
+ formatDateTime(dateStr) {
|
|
|
+ if (!dateStr) return ''
|
|
|
+ var d = this.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')
|
|
|
+ var h = ('' + d.getHours()).padStart(2, '0')
|
|
|
+ var min = ('' + d.getMinutes()).padStart(2, '0')
|
|
|
+
|
|
|
+ return y + '-' + m + '-' + day + ' ' + h + ':' + min
|
|
|
+ },
|
|
|
+
|
|
|
+ parseDate(dateStr) {
|
|
|
+ // 兼容 ISO 8601 格式(2024-08-26T14:30:00)和本地格式(2024-08-26 14:30:00)
|
|
|
+ var normalized = dateStr.replace(' ', 'T')
|
|
|
+ var d = new Date(normalized)
|
|
|
+ return isNaN(d.getTime()) ? null : d
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.page-container {
|
|
|
+ min-height: 100vh;
|
|
|
+ background: #F5F7FA;
|
|
|
+ padding-bottom: 60rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 标题栏 ===== */
|
|
|
+.header {
|
|
|
+ background: linear-gradient(135deg, #5B9BD5 0%, #3A7CC4 100%);
|
|
|
+ padding: 60rpx 30rpx 40rpx;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.title {
|
|
|
+ font-size: 48rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.subtitle {
|
|
|
+ font-size: 24rpx;
|
|
|
+ opacity: 0.9;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 加载状态 ===== */
|
|
|
+.loading {
|
|
|
+ text-align: center;
|
|
|
+ padding: 120rpx 0;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #94A3B8;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 空状态 ===== */
|
|
|
+.empty {
|
|
|
+ text-align: center;
|
|
|
+ padding: 160rpx 60rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-icon {
|
|
|
+ font-size: 120rpx;
|
|
|
+ margin-bottom: 32rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-title {
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-desc {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #94A3B8;
|
|
|
+ line-height: 1.6;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 方案列表 ===== */
|
|
|
+.plan-list {
|
|
|
+ padding: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.plan-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 32rpx;
|
|
|
+ margin-bottom: 28rpx;
|
|
|
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+.plan-header {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.plan-status-badge {
|
|
|
+ font-size: 22rpx;
|
|
|
+ padding: 6rpx 20rpx;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.status-generated {
|
|
|
+ background: #E3F2FD;
|
|
|
+ color: #1976D2;
|
|
|
+}
|
|
|
+
|
|
|
+.status-active {
|
|
|
+ background: #FFF3E0;
|
|
|
+ color: #F57C00;
|
|
|
+}
|
|
|
+
|
|
|
+.status-completed {
|
|
|
+ background: #E8F5E9;
|
|
|
+ color: #388E3C;
|
|
|
+}
|
|
|
+
|
|
|
+.status-expired {
|
|
|
+ background: #FFEBEE;
|
|
|
+ color: #C62828;
|
|
|
+}
|
|
|
+
|
|
|
+.plan-date {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #94A3B8;
|
|
|
+}
|
|
|
+
|
|
|
+.plan-body {
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.plan-name {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.plan-progress {
|
|
|
+ margin-top: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #64748B;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-bar {
|
|
|
+ height: 12rpx;
|
|
|
+ background: #E2E8F0;
|
|
|
+ border-radius: 6rpx;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-fill {
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(90deg, #5B9BD5 0%, #3A7CC4 100%);
|
|
|
+ transition: width 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.plan-meta {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ gap: 40rpx;
|
|
|
+ margin-top: 24rpx;
|
|
|
+ padding-top: 20rpx;
|
|
|
+ border-top: 2rpx solid #F0F0F0;
|
|
|
+}
|
|
|
+
|
|
|
+.meta-item {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #64748B;
|
|
|
+}
|
|
|
+
|
|
|
+.meta-label {
|
|
|
+ color: #94A3B8;
|
|
|
+ margin-right: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.meta-value {
|
|
|
+ color: #333;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.plan-footer {
|
|
|
+ text-align: right;
|
|
|
+ padding-top: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-text {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #5B9BD5;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 底部占位 ===== */
|
|
|
+.spacer {
|
|
|
+ height: 60rpx;
|
|
|
+}
|
|
|
+</style>
|