Răsfoiți Sursa

feat(frontend): 新增方案详情页,支持编辑/通过/驳回与部分成功提示

iwt 2 zile în urmă
părinte
comite
fb58a9a8e9
1 a modificat fișierele cu 148 adăugiri și 0 ștergeri
  1. 148 0
      cfc-frontend/pages/teacher/plan-detail.vue

+ 148 - 0
cfc-frontend/pages/teacher/plan-detail.vue

@@ -0,0 +1,148 @@
+<template>
+  <view class="page">
+    <view class="card">
+      <text class="title">{{ planTitle }}</text>
+      <text class="meta">创建于 {{ formatTime(plan.createdAt) }}</text>
+      <text class="meta" v-if="plan.teacherId">审核规划师:{{ plan.teacherId }}</text>
+      <text class="meta" v-else>未分配规划师</text>
+    </view>
+
+    <view class="card">
+      <text class="label">方案正文</text>
+      <textarea
+        class="editor"
+        v-model="planContent"
+        :disabled="!editing"
+        placeholder="方案正文"
+      />
+    </view>
+
+    <view class="actions">
+      <view class="btn ghost" @click="toggleEdit">{{ editing ? '取消编辑' : '编辑' }}</view>
+      <view class="btn ghost" v-if="editing" @click="save">保存</view>
+      <view class="btn ok" @click="confirmThen('approve')">通过</view>
+      <view class="btn no" @click="confirmThen('reject')">驳回</view>
+    </view>
+  </view>
+</template>
+
+<script>
+import {
+  updateGuidePlanContent,
+  approveGuidePlan,
+  rejectGuidePlan,
+  getGuidePendingPlans
+} from '@/utils/api.js'
+
+export default {
+  data() {
+    return {
+      familyId: null,
+      planId: null,
+      plan: {},
+      planContent: '',
+      editing: false
+    }
+  },
+  computed: {
+    planTitle() {
+      // HealthPlan 实体无 title 字段,只能依次回退到 goal / memberName
+      return this.plan.goal || this.plan.memberName || '健康方案'
+    }
+  },
+  onLoad(options) {
+    this.familyId = options.familyId
+    this.planId = options.planId
+    this.fetchPlan()
+  },
+  methods: {
+    async fetchPlan() {
+      try {
+        const res = await getGuidePendingPlans(this.familyId)
+        if (res && res.code === 200) {
+          const list = res.data || []
+          for (let i = 0; i < list.length; i++) {
+            if (String(list[i].id) === String(this.planId)) {
+              this.plan = list[i]
+              this.planContent = list[i].planContent || ''
+              return
+            }
+          }
+          uni.showToast({ title: '方案不存在或已被审核', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: '加载失败', icon: 'none' })
+      }
+    },
+    toggleEdit() {
+      this.editing = !this.editing
+    },
+    async save() {
+      try {
+        const res = await updateGuidePlanContent(this.familyId, this.planId, this.planContent)
+        if (res && res.code === 200) {
+          uni.showToast({ title: '已保存', icon: 'none' })
+          this.editing = false
+        } else {
+          uni.showToast({ title: (res && res.message) || '保存失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: '网络异常', icon: 'none' })
+      }
+    },
+    confirmThen(action) {
+      uni.showModal({
+        title: action === 'approve' ? '确认通过?' : '确认驳回?',
+        content: action === 'approve' ? '通过后将自动生成每日任务' : '驳回后家长需重新提交',
+        success: (r) => {
+          if (!r.confirm) return
+          this.doAction(action, '')
+        }
+      })
+    },
+    async doAction(action, comment) {
+      try {
+        const fn = action === 'approve' ? approveGuidePlan : rejectGuidePlan
+        const res = await fn(this.familyId, this.planId, comment)
+        if (res && res.code === 200) {
+          const d = res.data || {}
+          // taskGenerated=false 是「已发布但未生成任务」,属部分成功,必须与失败区分开提示
+          if (action === 'approve' && d.taskGenerated === false) {
+            uni.showModal({
+              title: '部分成功',
+              content: d.message || '方案已发布,但未生成任何任务,请检查方案内容',
+              showCancel: false
+            })
+          } else {
+            uni.showToast({ title: '操作成功', icon: 'none' })
+          }
+          setTimeout(() => uni.navigateBack(), 1200)
+        } else {
+          uni.showToast({ title: (res && res.message) || '操作失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: '网络异常', icon: 'none' })
+      }
+    },
+    formatTime(t) {
+      if (!t) return '-'
+      // 项目统一时间格式:取前19位并把 ISO 的 T 换成空格,避免 toLocaleString 的浏览器差异
+      return String(t).substring(0, 19).replace('T', ' ')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page { padding: 24rpx; background: #F5FAFE; min-height: 100vh; }
+.card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 20rpx; }
+.title { font-size: 32rpx; font-weight: 600; color: #1F2D3D; display: block; margin-bottom: 12rpx; }
+.meta { font-size: 24rpx; color: #8A94A6; display: block; margin-bottom: 6rpx; }
+.label { font-size: 26rpx; color: #5A6478; display: block; margin-bottom: 12rpx; }
+.editor { width: 100%; min-height: 400rpx; font-size: 28rpx; color: #1F2D3D; padding: 16rpx; background: #F7F9FC; border-radius: 12rpx; }
+.actions { display: flex; flex-direction: row; flex-wrap: wrap; }
+.btn { flex: 1; text-align: center; padding: 20rpx 0; border-radius: 12rpx; font-size: 28rpx; margin-right: 16rpx; margin-bottom: 16rpx; min-width: 40%; }
+.btn.ghost { background: #fff; color: #4A9BD7; border: 1rpx solid #4A9BD7; }
+.btn.ok { background: #4A9BD7; color: #fff; }
+.btn.no { background: #F2F4F7; color: #5A6478; margin-right: 0; }
+</style>