|
@@ -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>
|