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