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