|
|
@@ -0,0 +1,213 @@
|
|
|
+<template>
|
|
|
+ <view class="page">
|
|
|
+ <view v-if="loading" class="loading-box">加载中...</view>
|
|
|
+
|
|
|
+ <view v-else-if="stage" class="content">
|
|
|
+ <!-- 阶段头部 -->
|
|
|
+ <view class="stage-header">
|
|
|
+ <text class="stage-title">{{ stage.title || '阶段目标' }}</text>
|
|
|
+ <text class="stage-dates">{{ formatDate(stage.startDate) }} ~ {{ formatDate(stage.endDate) }}</text>
|
|
|
+ <view class="progress-row">
|
|
|
+ <text class="progress-text">复盘进度 {{ stage.reviewedWeeks || 0 }}/{{ stage.totalWeeks || 0 }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 目标列表 -->
|
|
|
+ <view class="section">
|
|
|
+ <text class="section-title">🎯 阶段目标</text>
|
|
|
+ <view v-for="g in stage.goals" :key="g.id" class="goal-item">
|
|
|
+ <text class="goal-tag" :class="'domain-' + g.domain">{{ domainName(g.domain) }}</text>
|
|
|
+ <text class="goal-content">{{ g.content }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 周时间线 -->
|
|
|
+ <view class="section">
|
|
|
+ <text class="section-title">📅 每周计划与复盘</text>
|
|
|
+ <view v-for="w in stage.weeks" :key="getWeekKey(w)" class="week-card">
|
|
|
+ <view class="week-header" @click="toggleWeek(w.id)">
|
|
|
+ <text class="week-no">第 {{ w.weekNo }} 周</text>
|
|
|
+ <text class="week-dates">{{ formatDate(w.weekStart) }} ~ {{ formatDate(w.weekEnd) }}</text>
|
|
|
+ <text class="week-status" :class="'ws-' + w.status">{{ weekStatusText(w.status) }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-if="expandedWeekId === w.id" class="week-body">
|
|
|
+ <!-- 计划 -->
|
|
|
+ <view class="week-block">
|
|
|
+ <text class="block-title">📋 本周计划</text>
|
|
|
+ <text class="block-text">{{ w.plan || '(未填写)' }}</text>
|
|
|
+ </view>
|
|
|
+ <!-- 复盘 -->
|
|
|
+ <view class="week-block" v-if="w.status === 'reviewed'">
|
|
|
+ <text class="block-title">✅ 复盘总结</text>
|
|
|
+ <view class="review-item">
|
|
|
+ <text class="review-label">达成:</text>
|
|
|
+ <text class="review-text">{{ w.achieved || '—' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="review-item">
|
|
|
+ <text class="review-label">体验:</text>
|
|
|
+ <text class="review-text">{{ w.experience || '—' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="review-item">
|
|
|
+ <text class="review-label">行得通:</text>
|
|
|
+ <text class="review-text worked">{{ w.worked || '—' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="review-item">
|
|
|
+ <text class="review-label">要提升:</text>
|
|
|
+ <text class="review-text improve">{{ w.improve || '—' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="review-item">
|
|
|
+ <text class="review-label">下周挑战:</text>
|
|
|
+ <text class="review-text">{{ w.nextChallenge || '—' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 编辑按钮(本人或代管) -->
|
|
|
+ <view v-if="canEdit" class="week-actions">
|
|
|
+ <button class="btn-edit" @click="editWeek(w)">编辑{{ w.status === 'reviewed' ? '' : '计划' }}</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 取消阶段 -->
|
|
|
+ <button v-if="canEdit && stage.status === 'active'" class="btn-cancel" @click="cancelStage">取消阶段</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getDoaStageDetail, cancelDoaStage } from '../../utils/api.js'
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: true,
|
|
|
+ stage: null,
|
|
|
+ stageId: 0,
|
|
|
+ selfMemberId: 0,
|
|
|
+ expandedWeekId: 0,
|
|
|
+ canEdit: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad(options) {
|
|
|
+ this.stageId = parseInt(options.stageId || '0', 10)
|
|
|
+ this.selfMemberId = uni.getStorageSync('currentMemberId') || 0
|
|
|
+ this.loadDetail()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ getWeekKey(w) {
|
|
|
+ return w.id
|
|
|
+ },
|
|
|
+ async loadDetail() {
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ var res = await getDoaStageDetail(this.stageId)
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ this.stage = res.data
|
|
|
+ // 编辑权限:自己 或 未登录成员(memberNickname 存在且非本人 → 后端已校验,前端保守判断)
|
|
|
+ this.canEdit = this.stage.memberId === this.selfMemberId || !this.stage.memberUserId
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '加载失败', icon: 'none' })
|
|
|
+ setTimeout(function() { uni.navigateBack() }, 1000)
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: '加载失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ domainName(d) {
|
|
|
+ var map = { health: '健康', wealth: '事业', family: '家庭关系', social: '社会关系', help: '帮助他人' }
|
|
|
+ return map[d] || d
|
|
|
+ },
|
|
|
+ formatDate(dateStr) {
|
|
|
+ if (!dateStr) return ''
|
|
|
+ return String(dateStr).slice(0, 10)
|
|
|
+ },
|
|
|
+ weekStatusText(s) {
|
|
|
+ if (s === 'reviewed') return '已复盘'
|
|
|
+ if (s === 'planned') return '已计划'
|
|
|
+ return '待计划'
|
|
|
+ },
|
|
|
+ toggleWeek(id) {
|
|
|
+ this.expandedWeekId = this.expandedWeekId === id ? 0 : id
|
|
|
+ },
|
|
|
+ editWeek(w) {
|
|
|
+ uni.navigateTo({ url: '/pages/health/doa-week-edit?weekId=' + w.id + '&stageId=' + this.stageId })
|
|
|
+ },
|
|
|
+ cancelStage() {
|
|
|
+ var self = this
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认取消',
|
|
|
+ content: '确定要取消这个阶段吗?周记录将保留但不可再编辑。',
|
|
|
+ success: function(res) {
|
|
|
+ if (res.confirm) {
|
|
|
+ cancelDoaStage(self.stageId).then(function(r) {
|
|
|
+ if (r.code === 200) {
|
|
|
+ uni.showToast({ title: '已取消', icon: 'success' })
|
|
|
+ setTimeout(function() { uni.navigateBack() }, 600)
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: r.message || '取消失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.page { min-height: 100vh; background: #F5F7FA; padding: 24rpx; }
|
|
|
+.loading-box { text-align: center; padding: 100rpx 0; color: #94A3B8; font-size: 28rpx; }
|
|
|
+.stage-header {
|
|
|
+ background: linear-gradient(135deg, #10B981, #34D399);
|
|
|
+ border-radius: 20rpx; padding: 32rpx; margin-bottom: 20rpx; color: #fff;
|
|
|
+}
|
|
|
+.stage-title { font-size: 34rpx; font-weight: 700; display: block; }
|
|
|
+.stage-dates { font-size: 24rpx; opacity: 0.9; margin-top: 8rpx; display: block; }
|
|
|
+.progress-row { margin-top: 16rpx; }
|
|
|
+.progress-text { font-size: 24rpx; font-weight: 600; }
|
|
|
+.section { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 20rpx; }
|
|
|
+.section-title { font-size: 28rpx; font-weight: 700; color: #1E293B; display: block; margin-bottom: 16rpx; }
|
|
|
+.goal-item { display: flex; align-items: center; margin-bottom: 12rpx; }
|
|
|
+.goal-tag {
|
|
|
+ font-size: 20rpx; padding: 6rpx 14rpx; border-radius: 8rpx; margin-right: 12rpx;
|
|
|
+ border: 2rpx solid #E2E8F0; color: #475569; flex-shrink: 0;
|
|
|
+}
|
|
|
+.domain-health { border-color: #FF8C42 !important; color: #C2410C !important; }
|
|
|
+.domain-wealth { border-color: #F59E0B !important; color: #B45309 !important; }
|
|
|
+.domain-family { border-color: #10B981 !important; color: #047857 !important; }
|
|
|
+.domain-social { border-color: #6366F1 !important; color: #4338CA !important; }
|
|
|
+.domain-help { border-color: #FF6B9D !important; color: #BE185D !important; }
|
|
|
+.goal-content { font-size: 26rpx; color: #334155; flex: 1; }
|
|
|
+.week-card { background: #F8FAFC; border-radius: 12rpx; padding: 20rpx; margin-bottom: 12rpx; }
|
|
|
+.week-header { display: flex; align-items: center; }
|
|
|
+.week-no { font-size: 26rpx; font-weight: 700; color: #1E293B; flex: 1; }
|
|
|
+.week-dates { font-size: 20rpx; color: #94A3B8; margin-right: 12rpx; }
|
|
|
+.week-status { font-size: 20rpx; padding: 4rpx 12rpx; border-radius: 20rpx; }
|
|
|
+.ws-pending { background: #F1F5F9; color: #64748B; }
|
|
|
+.ws-planned { background: #DBEAFE; color: #1D4ED8; }
|
|
|
+.ws-reviewed { background: #DCFCE7; color: #16A34A; }
|
|
|
+.week-body { margin-top: 16rpx; border-top: 2rpx solid #E2E8F0; padding-top: 16rpx; }
|
|
|
+.week-block { margin-bottom: 16rpx; }
|
|
|
+.block-title { font-size: 24rpx; font-weight: 700; color: #475569; display: block; margin-bottom: 8rpx; }
|
|
|
+.block-text { font-size: 24rpx; color: #334155; white-space: pre-wrap; display: block; }
|
|
|
+.review-item { margin-bottom: 8rpx; }
|
|
|
+.review-label { font-size: 22rpx; color: #64748B; }
|
|
|
+.review-text { font-size: 22rpx; color: #334155; }
|
|
|
+.review-text.worked { color: #16A34A; }
|
|
|
+.review-text.improve { color: #DC2626; }
|
|
|
+.week-actions { margin-top: 12rpx; }
|
|
|
+.btn-edit {
|
|
|
+ background: #10B981; color: #fff; font-size: 24rpx; font-weight: 600;
|
|
|
+ padding: 12rpx 32rpx; border-radius: 32rpx; border: none; width: auto;
|
|
|
+}
|
|
|
+.btn-cancel {
|
|
|
+ background: #fff; color: #EF4444; font-size: 26rpx; font-weight: 600;
|
|
|
+ border: 2rpx solid #FCA5A5; border-radius: 42rpx; height: 80rpx; line-height: 80rpx;
|
|
|
+ margin-top: 8rpx;
|
|
|
+}
|
|
|
+</style>
|