|
|
@@ -0,0 +1,223 @@
|
|
|
+<template>
|
|
|
+ <view class="page">
|
|
|
+ <!-- 目标成员(代管时显示,仅未登录成员可选) -->
|
|
|
+ <view class="field" v-if="!isSelfOnly">
|
|
|
+ <text class="field-label">目标成员</text>
|
|
|
+ <view class="member-grid">
|
|
|
+ <view
|
|
|
+ v-for="m in members"
|
|
|
+ :key="m.id"
|
|
|
+ class="member-item"
|
|
|
+ :class="{ active: form.memberId === m.id }"
|
|
|
+ @click="selectMember(m)"
|
|
|
+ >
|
|
|
+ <text>{{ m.nickname }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 阶段主题 -->
|
|
|
+ <view class="field">
|
|
|
+ <text class="field-label">阶段主题(可选)</text>
|
|
|
+ <input class="field-input" v-model="form.title" placeholder="如:暑期习惯养成" maxlength="50" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 周期选择 -->
|
|
|
+ <view class="field">
|
|
|
+ <text class="field-label">阶段周期 <text class="required">*</text></text>
|
|
|
+ <view class="radio-group">
|
|
|
+ <view class="radio-btn" :class="{ active: form.durationWeeks === 2 }" @click="form.durationWeeks = 2">2周</view>
|
|
|
+ <view class="radio-btn" :class="{ active: form.durationWeeks === 4 }" @click="form.durationWeeks = 4">4周</view>
|
|
|
+ <view class="radio-btn" :class="{ active: form.durationWeeks === 8 }" @click="form.durationWeeks = 8">8周</view>
|
|
|
+ <view class="radio-btn" :class="{ active: form.durationWeeks === 12 }" @click="form.durationWeeks = 12">12周</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 目标列表(1-3条) -->
|
|
|
+ <view class="field">
|
|
|
+ <text class="field-label">阶段目标(1-3 个,不同领域)</text>
|
|
|
+ <view v-for="(goal, idx) in form.goals" :key="getGoalKey(goal, idx)" class="goal-editor">
|
|
|
+ <view class="goal-domain-row">
|
|
|
+ <text class="goal-label">目标 {{ idx + 1 }} 领域</text>
|
|
|
+ <text class="goal-remove" @click="removeGoal(idx)">✕</text>
|
|
|
+ </view>
|
|
|
+ <view class="radio-group">
|
|
|
+ <view
|
|
|
+ v-for="d in domains"
|
|
|
+ :key="d.value"
|
|
|
+ class="radio-btn small"
|
|
|
+ :class="{ active: goal.domain === d.value }"
|
|
|
+ @click="goal.domain = d.value"
|
|
|
+ >{{ d.label }}</view>
|
|
|
+ </view>
|
|
|
+ <input class="field-input" v-model="goal.content" placeholder="量化目标,如:体重降到 172 以下" maxlength="100" />
|
|
|
+ </view>
|
|
|
+ <view class="add-goal" v-if="form.goals.length < 3" @click="addGoal">+ 添加目标</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <button class="btn-submit" @click="submit">创建阶段</button>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { createDoaStage, getFamilyMemberList } from '../../utils/api.js'
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ members: [],
|
|
|
+ selfMemberId: 0,
|
|
|
+ targetMemberId: 0,
|
|
|
+ hasLocalMembers: false,
|
|
|
+ domains: [
|
|
|
+ { value: 'health', label: '健康' },
|
|
|
+ { value: 'wealth', label: '事业' },
|
|
|
+ { value: 'family', label: '家庭关系' },
|
|
|
+ { value: 'social', label: '社会关系' },
|
|
|
+ { value: 'help', label: '帮助他人' }
|
|
|
+ ],
|
|
|
+ form: {
|
|
|
+ memberId: 0,
|
|
|
+ title: '',
|
|
|
+ durationWeeks: 4,
|
|
|
+ goals: [{ domain: 'health', content: '' }]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ isSelfOnly() {
|
|
|
+ // 只为自己创建时(未传入 memberId 且无可代管成员)
|
|
|
+ return this.targetMemberId === 0 && !this.hasLocalMembers
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad(options) {
|
|
|
+ this.selfMemberId = uni.getStorageSync('currentMemberId') || 0
|
|
|
+ if (options.memberId) {
|
|
|
+ this.targetMemberId = parseInt(options.memberId, 10)
|
|
|
+ this.form.memberId = parseInt(options.memberId, 10)
|
|
|
+ }
|
|
|
+ this.loadMembers()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ getGoalKey(goal, idx) {
|
|
|
+ return goal.domain + '_' + idx
|
|
|
+ },
|
|
|
+ async loadMembers() {
|
|
|
+ try {
|
|
|
+ var res = await getFamilyMemberList({ visibleOnly: true })
|
|
|
+ var list = res.data || []
|
|
|
+ this.members = list
|
|
|
+ this.hasLocalMembers = list.some(function(m) { return !m.userId })
|
|
|
+ // 若从总览"为他创建"进入且目标成员未登录 → 直接锁定
|
|
|
+ if (this.targetMemberId > 0) {
|
|
|
+ this.form.memberId = this.targetMemberId
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ this.members = []
|
|
|
+ this.hasLocalMembers = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ selectMember(m) {
|
|
|
+ this.form.memberId = m.id
|
|
|
+ },
|
|
|
+ addGoal() {
|
|
|
+ if (this.form.goals.length >= 3) return
|
|
|
+ this.form.goals.push({ domain: 'health', content: '' })
|
|
|
+ },
|
|
|
+ removeGoal(idx) {
|
|
|
+ if (this.form.goals.length <= 1) return
|
|
|
+ this.form.goals.splice(idx, 1)
|
|
|
+ },
|
|
|
+ async submit() {
|
|
|
+ // 目标成员校验
|
|
|
+ var memberId = this.form.memberId || this.selfMemberId
|
|
|
+ // 目标校验
|
|
|
+ var validGoals = []
|
|
|
+ var usedDomains = {}
|
|
|
+ var hasError = false
|
|
|
+ for (var i = 0; i < this.form.goals.length; i++) {
|
|
|
+ var g = this.form.goals[i]
|
|
|
+ if (!g.content.trim()) {
|
|
|
+ uni.showToast({ title: '请填写目标 ' + (i + 1) + ' 的内容', icon: 'none' })
|
|
|
+ hasError = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if (usedDomains[g.domain]) {
|
|
|
+ uni.showToast({ title: '目标领域不能重复', icon: 'none' })
|
|
|
+ hasError = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ usedDomains[g.domain] = true
|
|
|
+ validGoals.push({ domain: g.domain, content: g.content.trim() })
|
|
|
+ }
|
|
|
+ if (hasError) return
|
|
|
+ if (validGoals.length === 0) {
|
|
|
+ uni.showToast({ title: '请至少填写1个目标', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ var data = {
|
|
|
+ title: this.form.title.trim(),
|
|
|
+ durationWeeks: this.form.durationWeeks,
|
|
|
+ goals: validGoals
|
|
|
+ }
|
|
|
+ if (memberId && memberId !== this.selfMemberId) {
|
|
|
+ data.memberId = memberId
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ var res = await createDoaStage(data)
|
|
|
+ if (res.code === 200) {
|
|
|
+ uni.showToast({ title: '创建成功', icon: 'success' })
|
|
|
+ setTimeout(function() {
|
|
|
+ uni.navigateBack()
|
|
|
+ }, 600)
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '创建失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: '创建失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.page { min-height: 100vh; background: #F5F7FA; padding: 24rpx; }
|
|
|
+.field { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
|
|
|
+.field-label { font-size: 26rpx; color: #475569; font-weight: 600; margin-bottom: 12rpx; display: block; }
|
|
|
+.required { color: #EF4444; }
|
|
|
+.field-input {
|
|
|
+ background: #F8FAFC; border: 1rpx solid #E2E8F0; border-radius: 12rpx;
|
|
|
+ padding: 16rpx 18rpx; font-size: 28rpx; color: #1E293B; margin-top: 12rpx;
|
|
|
+}
|
|
|
+.member-grid { display: flex; flex-wrap: wrap; gap: 12rpx; }
|
|
|
+.member-item {
|
|
|
+ padding: 10rpx 22rpx; border-radius: 24rpx; font-size: 24rpx;
|
|
|
+ color: #475569; background: #F1F5F9; border: 2rpx solid transparent;
|
|
|
+}
|
|
|
+.member-item.active { background: #DCFCE7; color: #16A34A; border-color: #10B981; }
|
|
|
+.radio-group { display: flex; gap: 12rpx; margin-top: 12rpx; flex-wrap: wrap; }
|
|
|
+.radio-btn {
|
|
|
+ flex: 1; text-align: center; padding: 12rpx 0; border-radius: 12rpx;
|
|
|
+ font-size: 26rpx; color: #475569; background: #F1F5F9;
|
|
|
+}
|
|
|
+.radio-btn.small { flex: none; padding: 8rpx 18rpx; font-size: 22rpx; }
|
|
|
+.radio-btn.active { background: #10B981; color: #fff; font-weight: 600; }
|
|
|
+.goal-editor { background: #F8FAFC; border-radius: 12rpx; padding: 16rpx; margin-top: 12rpx; }
|
|
|
+.goal-domain-row { display: flex; justify-content: space-between; align-items: center; }
|
|
|
+.goal-label { font-size: 24rpx; color: #475569; font-weight: 600; }
|
|
|
+.goal-remove { font-size: 28rpx; color: #EF4444; padding: 4rpx 12rpx; }
|
|
|
+.add-goal {
|
|
|
+ margin-top: 16rpx; text-align: center; padding: 16rpx 0;
|
|
|
+ border: 2rpx dashed #10B981; border-radius: 12rpx;
|
|
|
+ color: #10B981; font-size: 26rpx; font-weight: 600;
|
|
|
+}
|
|
|
+.btn-submit {
|
|
|
+ background: linear-gradient(135deg, #10B981, #34D399);
|
|
|
+ color: #fff; font-size: 30rpx; font-weight: 600;
|
|
|
+ border-radius: 42rpx; border: none; height: 84rpx; line-height: 84rpx;
|
|
|
+ margin-top: 8rpx;
|
|
|
+}
|
|
|
+</style>
|