| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767 |
- <template>
- <view class="plan-page">
- <!-- 步骤指示器 -->
- <view class="steps-bar" v-if="!viewMode">
- <view class="step" v-for="(s, i) in steps" :key="i"
- :class="{ active: step === i + 1, done: step > i + 1 }">
- <text class="step-num">{{ i + 1 }}</text>
- <text class="step-label">{{ s }}</text>
- </view>
- </view>
- <!-- Step 1: 选人 + 目标 -->
- <view class="section" v-if="step === 1 && !viewMode">
- <view class="section-title-row">
- <text class="section-title">选择服务对象</text>
- <text class="selected-count-badge" v-if="selectedMemberIds.length > 0">{{ selectedMemberIds.length }} 人已选</text>
- </view>
- <view class="hint-text">选择方案针对的家庭成员</view>
- <member-chip-picker
- ref="memberPicker"
- :selected-ids="selectedMemberIds"
- @change="onPickerChange"
- @loaded="onPickerLoaded"
- @add-member="onAddMember"
- />
- <view class="section-title" style="margin-top:20rpx">健康目标</view>
- <view class="hint-text">描述您希望改善的方向,AI将为您定制方案</view>
- <textarea class="goal-textarea" v-model="userGoal" placeholder="例如:孩子最近睡眠不好,希望改善睡眠质量,同时提升免疫力..."
- :maxlength="300" auto-height :disabled="loading" @input="onGoalInput"></textarea>
- <view class="goal-footer">
- <text class="goal-count">{{ goalLen }}/300</text>
- <button class="btn-next" :disabled="!canGenerate" @click="goToStep2">{{ loading ? '加载中...' : '下一步' }}</button>
- </view>
- </view>
- <!-- Step 2: 健康问卷 (嵌入式) -->
- <view class="section" v-if="step === 2 && !viewMode">
- <view class="section-title">📋 健康问卷</view>
- <view class="hint-text">填写健康现状,方案将参考这些信息。可跳过,但填写后方案更精准。</view>
- <!-- 多成员分别填写:成员切换 tab -->
- <view class="member-tabs" v-if="selectedMembers.length > 1">
- <view v-for="m in selectedMembers" :key="m.memberId"
- class="member-tab" :class="{ active: m.memberId === activeFormMemberId }"
- @click="switchMember(m.memberId)">
- <text>{{ m.name }}</text>
- </view>
- </view>
- <view class="form-member-hint" v-if="selectedMembers.length > 1">
- <text class="form-member-label">正在为「{{ currentMemberName }}」填写</text>
- <text class="form-member-save">每个成员独立保存,切换后自动保留</text>
- </view>
- <!-- 基础体征 -->
- <view class="form-section">
- <view class="form-title">基础体征</view>
- <view class="form-row">
- <text class="form-label">身高 (cm)</text>
- <input class="form-input" type="digit" v-model="form.heightCm" placeholder="如 170" />
- </view>
- <view class="form-row">
- <text class="form-label">体重 (kg)</text>
- <input class="form-input" type="digit" v-model="form.weightKg" placeholder="如 60" />
- </view>
- <view class="form-row">
- <text class="form-label">血压</text>
- <input class="form-input" type="text" v-model="form.bloodPressure" placeholder="如 120/80" />
- </view>
- <view class="form-row">
- <text class="form-label">空腹血糖 (mmol/L)</text>
- <input class="form-input" type="digit" v-model="form.bloodGlucose" placeholder="如 5.6" />
- </view>
- </view>
- <!-- 血脂 -->
- <view class="form-section">
- <view class="form-title">血脂</view>
- <view class="form-row">
- <text class="form-label">总胆固醇 (mmol/L)</text>
- <input class="form-input" type="digit" v-model="lipidCholesterol" placeholder="如 4.5" />
- </view>
- </view>
- <!-- 疾病史 -->
- <view class="form-section">
- <view class="form-title">疾病史</view>
- <view v-for="(group, gi) in diseaseGroups" :key="group.key" class="disease-group">
- <view class="group-head" @click="toggleGroup(group.key)">
- <text class="group-title">{{ group.title }}</text>
- <text class="group-arrow">{{ expandedGroups.indexOf(group.key) >= 0 ? '收起' : '展开' }}</text>
- </view>
- <view v-if="expandedGroups.indexOf(group.key) >= 0" class="tag-wrap">
- <view
- v-for="(item, ii) in group.items"
- :key="item.name"
- class="tag-item"
- :class="{ 'tag-active': selectedNames.indexOf(item.name) >= 0 }"
- @click="toggleDisease(item.name)">
- <text>{{ item.name }}</text>
- <text v-if="item.risk" class="risk-badge" @click.stop="showRiskTip(item.name)">★</text>
- </view>
- </view>
- </view>
- <view class="custom-row">
- <input class="custom-input" type="text" v-model="diseaseInput" placeholder="输入其他疾病" />
- <text class="add-link" @click="addCustomDisease">添加</text>
- </view>
- <view v-if="selectedDiseases.length > 0" class="selected-area">
- <view class="selected-title">已选疾病({{ selectedDiseases.length }})</view>
- <view v-for="(item, si) in selectedDiseases" :key="getSelKey(si)" class="sel-row">
- <text class="sel-name">{{ item.name }}</text>
- <picker mode="date" fields="month" :value="item.diagnosedAt || currentMonth" :end="monthEnd" @change="onDiagChange(si, $event)">
- <view class="sel-diag">{{ item.diagnosedAt ? item.diagnosedAt : '设确诊时间' }}</view>
- </picker>
- <text class="sel-del" @click="removeSelected(si)">×</text>
- </view>
- </view>
- </view>
- <!-- 在服药物/治疗 -->
- <view class="form-section">
- <view class="form-title">在服药物/治疗</view>
- <view v-for="(med, idx) in medRows" :key="idx" class="med-row">
- <view class="med-line">
- <input class="med-name" type="text" v-model="med.name" placeholder="药物品牌,如 立普妥" />
- <text class="med-del" @click="removeMed(idx)">删除</text>
- </view>
- <view class="med-line">
- <picker mode="date" fields="month" :value="med.startDate || currentMonth" :end="monthEnd" @change="onMedStartChange(idx, $event)">
- <view class="med-picker">{{ med.startDate ? med.startDate : '选择开始日期' }}</view>
- </picker>
- <text class="med-forget" @click="onForgetClick(idx)">记不清</text>
- </view>
- <view class="med-line" v-if="med.durationText">
- <text class="med-duration">已服时长:{{ med.durationText }}</text>
- </view>
- <view class="med-line">
- <input class="med-time" type="text" v-model="med.time" placeholder="服药时间,如 早饭后一次" />
- </view>
- </view>
- <text class="add-link" @click="addMed">+ 添加药物/治疗</text>
- </view>
- <!-- 其他补充 -->
- <view class="form-section">
- <view class="form-title">其他补充</view>
- <textarea class="notes-area" v-model="form.notes" placeholder="其他想补充的健康信息(选填)" :maxlength="500" />
- </view>
- <!-- 底部按钮 -->
- <view class="btn-pair">
- <button class="btn-skip" @click="skipToGenerate">跳过</button>
- <button class="btn-generate" :disabled="loading" @click="saveAndGenerate">{{ loading ? '生成中...' : '保存并生成' }}</button>
- </view>
- </view>
- <!-- Step 3: 生成方案 -->
- <view class="section" v-if="step === 3 && !viewMode">
- <view class="section-title">📊 方案生成</view>
- <!-- 加载中 -->
- <view v-if="loading" class="loading-state">
- <text class="loading-icon">⏳</text>
- <text class="loading-text">AI 正在为您生成个性化健康方案...</text>
- <text class="loading-hint">预计需要 10-30 秒,请耐心等待</text>
- </view>
- <!-- 方案预览 -->
- <view v-else>
- <view class="overview-card" v-if="planData.overview">
- <text class="overview-text">{{ planData.overview }}</text>
- </view>
- <view v-else class="empty-state"><text class="empty-text">方案生成失败,请重试</text></view>
- <view class="section-title" style="margin-top:20rpx">方案结构</view>
- <view class="section-preview-list">
- <view v-for="(sec, i) in sectionKeys" :key="i" class="section-preview-item">
- <view class="sec-icon">{{ secIcons[i] }}</view>
- <view class="sec-info">
- <text class="sec-title">{{ secTitles[i] }}</text>
- <text class="sec-status" :class="{ done: sections[sec].confirmed }">{{ sections[sec].confirmed ? '已确认' : '待确认' }}</text>
- </view>
- </view>
- </view>
- <!-- 两个并排按钮:修改确认 / 选择规划师 -->
- <view class="btn-pair">
- <button class="btn-confirm" :disabled="loading" @click="startEdit">{{ loading ? '处理中...' : '修改确认' }}</button>
- <button class="btn-teacher" @click="goToTeacherService">选择规划师</button>
- </view>
- <view class="back-link" @click="step = 2">← 返回修改问卷</view>
- </view>
- </view>
- <!-- Step 3 编辑模式:各 section 编辑 -->
- <view class="section" v-if="editing && !viewMode">
- <view class="section-title-row">
- <text class="section-title">{{ currentSectionTitle }}</text>
- <text class="section-badge">{{ currentSectionKey }}</text>
- </view>
- <view class="hint-text">{{ currentSectionHint }}</view>
- <textarea class="edit-textarea" v-model="sections[currentSectionKey].content"
- :disabled="loading" auto-height></textarea>
- <view class="feedback-row">
- <input class="feedback-input" v-model="sectionFeedback" placeholder="如有修改意见请填写(可选)..." />
- </view>
- <view class="section-actions">
- <button class="btn-edit" @click="regenerateCurrentSection" :disabled="loading">🔄 重新生成</button>
- <button class="btn-confirm" @click="confirmCurrentSection" :disabled="loading">保存</button>
- </view>
- <view class="back-link" @click="exitEditMode">← 返回方案预览</view>
- </view>
- <!-- 最终确认保存 (所有 section 确认后) -->
- <view class="section" v-if="step === 3 && allConfirmed && !editing && !loading && !viewMode">
- <view class="section-title">📋 确认方案</view>
- <view class="hint-text">所有板块已确认,点击保存生成最终方案</view>
- <view class="final-review">
- <view v-for="(sec, i) in sectionKeys" :key="i" class="review-item">
- <text class="review-title">{{ secTitles[i] }}</text>
- <text class="review-preview">{{ sections[sec].content.substring(0, 80) }}{{ sections[sec].content.length > 80 ? '...' : '' }}</text>
- </view>
- </view>
- <view class="btn-pair">
- <button class="btn-confirm full-btn" :disabled="loading" @click="submitPlan">{{ loading ? '保存中...' : '确认保存' }}</button>
- <button class="btn-teacher" @click="goToTeacherService">选择规划师</button>
- </view>
- <view class="back-link" @click="startEdit">← 继续修改</view>
- </view>
- <!-- 查看模式 -->
- <view class="section plan-view-section" v-if="viewMode && planDetail">
- <view class="section-header">
- <text class="section-title">📊 健康方案</text>
- </view>
- <view class="plan-meta">
- <text class="plan-meta-item">目标:{{ planDetail.goal || '—' }}</text>
- <text class="plan-meta-item" v-if="planDetail.memberName">成员:{{ planDetail.memberName }}</text>
- <text class="plan-meta-item" v-if="planDetail.createdAt">制定于 {{ formatTime(planDetail.createdAt) }}</text>
- <text class="plan-meta-item" v-if="planDetail.status === 'confirmed' || planDetail.status === 'published'">状态:已发布</text>
- <text class="plan-meta-item" v-if="planDetail.status === 'draft'">状态:待确认</text>
- <text class="plan-meta-item" v-if="planDetail.status === 'pending_review'">状态:待规划师审核</text>
- <text class="plan-meta-item" v-if="planDetail.status === 'rejected'">状态:已驳回</text>
- <text class="plan-meta-item" v-if="planDetail.reviewComment && planDetail.status !== 'draft'">审核备注:{{ planDetail.reviewComment }}</text>
- </view>
- <view v-if="planDetail.planContent" class="plan-preview">
- <text class="plan-preview-text">{{ planDetail.planContent }}</text>
- </view>
- <!-- 待确认:点「确定」分解任务(任务在首页/任务中心查看,不在方案下方展示);「申请规划师」预留 -->
- <view class="btn-pair plan-confirm-actions" v-if="needsTaskConfirm">
- <button class="btn-confirm" :disabled="confirmingTasks" @click="confirmPlanTasks">{{ confirmingTasks ? '处理中...' : '确定' }}</button>
- <button class="btn-teacher" @click="applyForTeacher">申请规划师</button>
- </view>
- </view>
- <!-- 成功遮罩 -->
- <view v-if="taskSubmitted" class="success-overlay">
- <view class="success-card">
- <text class="success-icon">✓</text>
- <text class="success-title">{{ planDetail && (planDetail.status === 'published' || planDetail.status === 'confirmed') ? '方案已发布' : '方案已提交审核' }}</text>
- <text class="success-sub">已为您创建日常任务</text>
- <button class="btn-done" @click="goBack">完成</button>
- </view>
- </view>
- </view>
- </template>
- <script>
- import { generateHealthPlan, regenerateSection, confirmPlan, saveHealthPlan, getHealthPlanDetail, approvePlan } from '../../utils/api'
- import { getHealthStatus, saveHealthStatus } from '../../utils/api'
- import MemberChipPicker from '../../components/MemberChipPicker.vue'
- // v2: 预置疾病清单(菌群报告病症风险 ★ + 互联网统计常见病 两源合并去重,8 组 54 项)
- var DISEASE_GROUPS = [
- { key: 'cardio', title: '心血管代谢', items: [
- { name: '高血压', risk: false },
- { name: '糖尿病', risk: false },
- { name: '血脂异常', risk: false },
- { name: '冠心病', risk: false },
- { name: '脑卒中', risk: false },
- { name: '心力衰竭', risk: false },
- { name: '心房颤动', risk: false },
- { name: '慢阻肺', risk: false },
- { name: '心脑血管疾病', risk: true },
- { name: 'II型糖尿病', risk: true },
- { name: '肥胖', risk: true },
- { name: '超重', risk: false },
- { name: '高尿酸血症', risk: false },
- { name: '骨质疏松', risk: false }
- ]},
- { key: 'digest', title: '消化系统', items: [
- { name: '幽门螺杆菌感染', risk: false },
- { name: '脂肪肝', risk: false },
- { name: '慢性胃炎', risk: false },
- { name: '胃食管反流', risk: false },
- { name: '肠易激综合征', risk: true },
- { name: '炎症性肠炎', risk: true },
- { name: '慢性便秘', risk: true },
- { name: '感染性腹泻', risk: true },
- { name: '胆结石', risk: false },
- { name: '炎症性肠病', risk: false },
- { name: '肝病', risk: true },
- { name: '胃病', risk: true },
- { name: '胆病', risk: true },
- { name: '肾病', risk: true },
- { name: '腹胀', risk: true }
- ]},
- { key: 'immune', title: '过敏免疫', items: [
- { name: '过敏性鼻炎', risk: true },
- { name: '哮喘', risk: false },
- { name: '湿疹', risk: false },
- { name: '食物过敏', risk: false },
- { name: '自体免疫病', risk: true }
- ]},
- { key: 'neuro', title: '精神神经', items: [
- { name: '抑郁症', risk: true },
- { name: '失眠', risk: true },
- { name: '焦虑障碍', risk: false },
- { name: '偏头痛', risk: false },
- { name: '自闭症', risk: true }
- ]},
- { key: 'resp', title: '呼吸系统', items: [
- { name: '肺部感染或疾病', risk: true },
- { name: '肺部疾病', risk: true }
- ]},
- { key: 'tumor', title: '肿瘤', items: [
- { name: '结直肠癌', risk: true },
- { name: '肺癌', risk: false },
- { name: '甲状腺癌', risk: false },
- { name: '肝癌', risk: false },
- { name: '胃癌', risk: false },
- { name: '乳腺癌', risk: false },
- { name: '食管癌', risk: false }
- ]},
- { key: 'child', title: '儿童青少年', items: [
- { name: '近视', risk: false },
- { name: '龋齿', risk: false },
- { name: '注意缺陷多动障碍', risk: false },
- { name: '甲状腺疾病', risk: true }
- ]},
- { key: 'other', title: '其他', items: [
- { name: '缺铁性贫血', risk: false },
- { name: '贫血', risk: false }
- ]}
- ]
- export default {
- components: { MemberChipPicker },
- data() {
- return {
- // 步骤控制
- step: 1,
- // 成员选择(字段名对齐 report-list,避免 executors 在部分基础库 setData 异常)
- selectedMemberIds: [],
- pickerMembers: [],
- needReloadMembers: false,
- // 目标
- userGoal: '',
- goalLen: 0,
- // 健康问卷表单
- form: {
- heightCm: '',
- weightKg: '',
- bloodPressure: '',
- bloodGlucose: '',
- notes: ''
- },
- lipidCholesterol: '',
- // 疾病清单很大,不放进首屏 data,避免微信 setData 超限导致成员列表无法渲染
- expandedGroups: [],
- selectedDiseases: [],
- diseaseInput: '',
- medRows: [{ name: '', startDate: '', durationText: '', time: '' }],
- currentMonth: '',
- monthEnd: '',
- // 多成员分别填写:memberId → 各自的表单快照 { form, lipidCholesterol, selectedDiseases, medRows }
- memberFormMap: {},
- activeFormMemberId: '',
- // 方案数据
- planData: {},
- sections: {
- nutrition: { content: '', confirmed: false },
- diet: { content: '', confirmed: false },
- exercise: { content: '', confirmed: false }
- },
- sectionFeedback: '',
- editing: false,
- // 查看模式
- viewMode: false,
- planDetail: null,
- planId: null,
- confirmingTasks: false,
- // UI 状态
- loading: false,
- taskSubmitted: false
- }
- },
- computed: {
- // 仅在问卷步骤暴露疾病清单,减小 step1 的 setData 体积
- diseaseGroups: function() {
- return this.step >= 2 ? DISEASE_GROUPS : []
- },
- canGenerate: function() {
- return this.selectedMemberIds.length > 0 && this.goalLen > 0 && !this.loading
- },
- allConfirmed: function() {
- return this.sections.nutrition.confirmed && this.sections.diet.confirmed && this.sections.exercise.confirmed
- },
- needsTaskConfirm: function() {
- return !!(this.planDetail && this.planDetail.status === 'draft')
- },
- steps: function() { return ['选人', '问卷', '方案'] },
- sectionKeys: function() { return ['nutrition', 'diet', 'exercise'] },
- sectionTitles: function() { return ['营养补充建议', '饮食建议', '运动计划'] },
- sectionIcons: function() { return ['💊', '🍽️', '🏃'] },
- sectionHints: function() { return [
- '包含益生菌、维生素、矿物质等营养补充建议',
- '包含早餐、午餐、晚餐的饮食建议',
- '包含有氧运动、力量训练等运动计划'
- ]},
- currentSectionKey: function() {
- var idx = this.step - 3
- return this.sectionKeys[idx] || ''
- },
- currentSectionTitle: function() {
- var idx = this.step - 3
- return this.sectionTitles[idx] || ''
- },
- currentSectionHint: function() {
- var idx = this.step - 3
- return this.sectionHints[idx] || ''
- },
- selectedNames: function() {
- return this.selectedDiseases.map(function(d) { return d.name })
- },
- selectedMembers: function() {
- var self = this
- var out = []
- this.selectedMemberIds.forEach(function(id) {
- var found = null
- for (var i = 0; i < self.pickerMembers.length; i++) {
- var mid = self.pickerMembers[i].memberId || self.pickerMembers[i].id
- if (String(mid) === String(id)) { found = self.pickerMembers[i]; break }
- }
- // 规整为标准结构(兼容 pickerMembers 只用 id 或 nickname 的情况)
- if (found) {
- out.push({
- memberId: String(found.memberId || found.id),
- name: found.name || found.nickname || '成员'
- })
- }
- })
- return out
- },
- currentMemberName: function() {
- var members = this.selectedMembers
- for (var i = 0; i < members.length; i++) {
- if (String(members[i].memberId) === String(this.activeFormMemberId)) {
- return members[i].name
- }
- }
- return '成员'
- }
- },
- onLoad: function(options) {
- var self = this
- // 处理初始成员ID
- if (options.initialMemberIds) {
- this.selectedMemberIds = options.initialMemberIds.split(',').filter(function(id) { return id }).map(function(id) { return String(parseInt(id) || id) })
- } else if (options.memberId) {
- this.selectedMemberIds = [String(options.memberId)]
- }
- // 处理 planId 查看模式
- this.planId = options.planId ? parseInt(options.planId) : null
- if (this.planId) {
- this.viewMode = true
- this.loadPlanDetail()
- }
- // 初始化健康问卷时间
- var now = new Date()
- var y = now.getFullYear()
- var m = now.getMonth() + 1
- var mStr = m < 10 ? '0' + m : '' + m
- this.currentMonth = y + '-' + mStr
- var lastDay = new Date(y, m, 0).getDate()
- this.monthEnd = y + '-' + mStr + '-' + (lastDay < 10 ? '0' + lastDay : '' + lastDay)
- // 健康问卷数据在成员选择组件 loaded 后按成员异步加载
- // 自动执行
- if (options.autoExecute === '1') {
- setTimeout(function() { self.doAutoExecute() }, 1500)
- }
- },
- onShow: function() {
- var self = this
- // 从购买/开通页返回:自动继续生成方案
- if (uni.getStorageSync('PLAN_RESUME_AFTER_PURCHASE') === '1') {
- uni.removeStorageSync('PLAN_RESUME_AFTER_PURCHASE')
- if (this.selectedMemberIds && this.selectedMemberIds.length > 0 && !this.viewMode) {
- setTimeout(function() { self.doAutoExecute() }, 800)
- }
- }
- if (this.needReloadMembers) {
- this.needReloadMembers = false
- if (this.$refs.memberPicker && this.$refs.memberPicker.reload) {
- this.$refs.memberPicker.reload()
- }
- }
- },
- methods: {
- onGoalInput: function(e) {
- var val = (e && e.detail && e.detail.value) || this.userGoal || ''
- this.goalLen = String(val).length
- },
- onPickerChange: function(payload) {
- if (!payload) return
- this.selectedMemberIds = payload.selectedIds || []
- this.pickerMembers = payload.members || []
- },
- onPickerLoaded: function(payload) {
- if (!payload) return
- this.selectedMemberIds = payload.selectedIds || []
- this.pickerMembers = payload.members || []
- var formId = (payload.selectedIds && payload.selectedIds[0]) || ''
- if (formId) {
- this.activeFormMemberId = String(formId)
- var self = this
- setTimeout(function() { self.loadHealthForm(formId) }, 100)
- }
- },
- onAddMember: function() {
- this.needReloadMembers = true
- },
- // ===== 时间格式化 =====
- formatTime: function(iso) {
- if (!iso) return ''
- var s = String(iso)
- if (s.length >= 19) s = s.substring(0, 19)
- return s.replace('T', ' ')
- },
- // ===== 步骤导航 =====
- goToStep2: function() {
- if (this.expandedGroups.length === 0) {
- this.expandedGroups = DISEASE_GROUPS.slice(0, 3).map(function(g) { return g.key })
- }
- // 多成员调研:确保 activeFormMemberId 指向第一个选中成员,并加载其表单
- if (!this.activeFormMemberId && this.selectedMemberIds.length > 0) {
- this.activeFormMemberId = String(this.selectedMemberIds[0])
- var self = this
- setTimeout(function() { self.loadHealthForm(self.activeFormMemberId) }, 100)
- }
- this.step = 2
- },
- skipToGenerate: function() {
- this.step = 3
- this.generatePlan()
- },
- // ===== 健康问卷方法 (从 health-status-form.vue 移植,支持按成员加载) =====
- loadHealthForm: function(memberId) {
- var self = this
- var mid = memberId || self.activeFormMemberId || ''
- // 已有该成员的本地快照 → 直接恢复
- if (mid && self.memberFormMap[mid]) {
- self.applyFormSnapshot(self.memberFormMap[mid])
- return
- }
- getHealthStatus(mid || undefined).then(function(res) {
- if (res && res.code === 200 && res.data) {
- var d = res.data
- var snapshot = {
- form: {
- heightCm: d.heightCm || '',
- weightKg: d.weightKg || '',
- bloodPressure: d.bloodPressure || '',
- bloodGlucose: d.bloodGlucose || '',
- notes: d.notes || ''
- },
- lipidCholesterol: self.parseLipidCholesterol(d.bloodLipids),
- selectedDiseases: [],
- medRows: self.parseMeds(d.medications)
- }
- self.memberFormMap[mid || '__account'] = snapshot
- self.loadDiseasesInto(snapshot.selectedDiseases, d.diseaseHistory)
- if (mid) self.applyFormSnapshot(snapshot)
- else self.applyFormSnapshot({
- form: snapshot.form,
- lipidCholesterol: snapshot.lipidCholesterol,
- selectedDiseases: snapshot.selectedDiseases,
- medRows: snapshot.medRows
- })
- }
- }).catch(function() {})
- },
- // 切换成员时先保存当前编辑内容,再加载目标成员快照
- switchMember: function(memberId) {
- var self = this
- if (String(memberId) === String(self.activeFormMemberId)) return
- // 保存当前成员到快照
- self.memberFormMap[self.activeFormMemberId || '__account'] = {
- form: JSON.parse(JSON.stringify(self.form)),
- lipidCholesterol: self.lipidCholesterol,
- selectedDiseases: JSON.parse(JSON.stringify(self.selectedDiseases)),
- medRows: JSON.parse(JSON.stringify(self.medRows))
- }
- self.activeFormMemberId = String(memberId)
- // 目标成员已有快照 → 恢复;无则按该成员从服务端加载
- if (self.memberFormMap[self.activeFormMemberId]) {
- self.applyFormSnapshot(self.memberFormMap[self.activeFormMemberId])
- } else {
- self.resetFormFields()
- self.loadHealthForm(self.activeFormMemberId)
- }
- },
- applyFormSnapshot: function(snapshot) {
- if (!snapshot) return
- this.form = JSON.parse(JSON.stringify(snapshot.form || {}))
- this.lipidCholesterol = snapshot.lipidCholesterol || ''
- this.selectedDiseases = JSON.parse(JSON.stringify(snapshot.selectedDiseases || []))
- this.medRows = snapshot.medRows && snapshot.medRows.length > 0 ? JSON.parse(JSON.stringify(snapshot.medRows)) : [{ name: '', startDate: '', durationText: '', time: '' }]
- var self = this
- this.selectedDiseases.forEach(function(item) {
- var gk = self.findGroupKey(item.name)
- if (gk && self.expandedGroups.indexOf(gk) < 0) self.expandedGroups.push(gk)
- })
- },
- resetFormFields: function() {
- this.form = { heightCm: '', weightKg: '', bloodPressure: '', bloodGlucose: '', notes: '' }
- this.lipidCholesterol = ''
- this.selectedDiseases = []
- this.medRows = [{ name: '', startDate: '', durationText: '', time: '' }]
- this.diseaseInput = ''
- },
- parseLipidCholesterol: function(json) {
- try {
- var arr = JSON.parse(json || '[]')
- if (arr instanceof Array && arr.length > 0 && arr[0].value) {
- return String(arr[0].value)
- }
- } catch (e) {}
- return ''
- },
- loadDiseasesInto: function(targetList, json) {
- var self = this
- try {
- var arr = JSON.parse(json || '[]')
- if (arr instanceof Array) {
- arr.forEach(function(item) {
- if (item.name && self.selectedNames.indexOf(item.name) < 0) {
- targetList.push({
- name: item.name,
- diagnosedAt: item.diagnosedAt || '',
- custom: false
- })
- var gk = self.findGroupKey(item.name)
- if (gk && self.expandedGroups.indexOf(gk) < 0) {
- self.expandedGroups.push(gk)
- }
- }
- })
- }
- } catch (e) {}
- },
- parseMeds: function(json) {
- var list = []
- try {
- var arr = JSON.parse(json || '[]')
- if (arr instanceof Array) {
- arr.forEach(function(item) {
- list.push({
- name: item.name || '',
- startDate: item.startDate || '',
- durationText: item.durationText || '',
- time: item.time || ''
- })
- })
- }
- } catch (e) {}
- if (list.length === 0) list.push({ name: '', startDate: '', durationText: '', time: '' })
- return list
- },
- toggleGroup: function(key) {
- var idx = this.expandedGroups.indexOf(key)
- if (idx >= 0) this.expandedGroups.splice(idx, 1)
- else this.expandedGroups.push(key)
- },
- findGroupKey: function(name) {
- for (var i = 0; i < DISEASE_GROUPS.length; i++) {
- var items = DISEASE_GROUPS[i].items
- for (var j = 0; j < items.length; j++) {
- if (items[j].name === name) return DISEASE_GROUPS[i].key
- }
- }
- return ''
- },
- toggleDisease: function(name) {
- var idx = -1
- for (var i = 0; i < this.selectedDiseases.length; i++) {
- if (this.selectedDiseases[i].name === name) { idx = i; break }
- }
- if (idx >= 0) {
- this.selectedDiseases.splice(idx, 1)
- } else {
- this.selectedDiseases.push({ name: name, diagnosedAt: '', custom: false })
- var gk = this.findGroupKey(name)
- if (gk && this.expandedGroups.indexOf(gk) < 0) {
- this.expandedGroups.push(gk)
- }
- }
- },
- showRiskTip: function(name) {
- uni.showModal({
- title: '菌群报告关联',
- content: '「' + name + '」与肠道菌群报告相关,勾选后出方案时可结合菌群报告风险值评估。',
- showCancel: false
- })
- },
- addCustomDisease: function() {
- var name = (this.diseaseInput || '').trim()
- if (!name) return
- if (this.selectedNames.indexOf(name) < 0) {
- this.selectedDiseases.push({ name: name, diagnosedAt: '', custom: true })
- }
- this.diseaseInput = ''
- },
- getSelKey: function(i) { return 's' + i },
- removeSelected: function(idx) {
- this.selectedDiseases.splice(idx, 1)
- },
- onDiagChange: function(idx, e) {
- this.selectedDiseases[idx].diagnosedAt = e.detail.value
- },
- addMed: function() {
- this.medRows.push({ name: '', startDate: '', durationText: '', time: '' })
- },
- onMedStartChange: function(idx, e) {
- var start = e.detail.value
- this.medRows[idx].startDate = start
- this.medRows[idx].durationText = this.calcDuration(start)
- },
- onForgetClick: function(idx) {
- var self = this
- uni.showModal({
- title: '记不清开始日期',
- editable: true,
- placeholderText: '如:约 3 个月',
- success: function(res) {
- if (res.confirm && res.content) {
- self.medRows[idx].durationText = res.content.trim()
- }
- }
- })
- },
- calcDuration: function(startDate) {
- if (!startDate) return ''
- var parts = startDate.split('-')
- if (parts.length !== 2) return ''
- var sy = parseInt(parts[0], 10)
- var sm = parseInt(parts[1], 10)
- var now = new Date()
- var months = (now.getFullYear() - sy) * 12 + (now.getMonth() + 1 - sm)
- if (months < 1) months = 1
- if (months < 12) return '已服 ' + months + ' 个月'
- return '已服 ' + Math.floor(months / 12) + ' 年'
- },
- removeMed: function(idx) {
- if (this.medRows.length > 1) this.medRows.splice(idx, 1)
- },
- buildHealthPayload: function(snapshot) {
- var s = snapshot || {
- form: this.form,
- lipidCholesterol: this.lipidCholesterol,
- selectedDiseases: this.selectedDiseases,
- medRows: this.medRows
- }
- var lipids = []
- if (s.lipidCholesterol) {
- lipids.push({ name: '总胆固醇', value: parseFloat(s.lipidCholesterol), unit: 'mmol/L', note: '' })
- }
- var diseases = []
- ;(s.selectedDiseases || []).forEach(function(item) {
- var d = { name: item.name }
- if (item.diagnosedAt) d.diagnosedAt = item.diagnosedAt
- diseases.push(d)
- })
- var meds = []
- ;(s.medRows || []).forEach(function(item) {
- if (item.name) {
- var m = { name: item.name }
- if (item.startDate) m.startDate = item.startDate
- if (item.durationText) m.durationText = item.durationText
- if (item.time) m.time = item.time
- meds.push(m)
- }
- })
- var payload = {}
- if (s.form.heightCm) payload.heightCm = Number(s.form.heightCm)
- if (s.form.weightKg) payload.weightKg = Number(s.form.weightKg)
- if (s.form.bloodPressure) payload.bloodPressure = s.form.bloodPressure
- if (s.form.bloodGlucose) payload.bloodGlucose = Number(s.form.bloodGlucose)
- if (lipids.length > 0) payload.bloodLipids = JSON.stringify(lipids)
- if (diseases.length > 0) payload.diseaseHistory = JSON.stringify(diseases)
- if (meds.length > 0) payload.medications = JSON.stringify(meds)
- if (s.form.notes) payload.notes = s.form.notes
- return payload
- },
- // 当前成员快照(无改动数据时返回 null)
- snapshotForMember: function(memberId) {
- var key = String(memberId)
- // 优先用已保存的成员快照,其次当前活动表单
- if (this.memberFormMap[key]) {
- var s = this.memberFormMap[key]
- var p = this.buildHealthPayload(s)
- return this.hasPayload(p) ? p : null
- }
- // 活动成员直接用当前表单
- if (key === String(this.activeFormMemberId)) {
- var activePayload = this.buildHealthPayload()
- return this.hasPayload(activePayload) ? activePayload : null
- }
- return null
- },
- hasPayload: function(payload) {
- if (!payload) return false
- return payload.heightCm || payload.weightKg || payload.bloodPressure || payload.bloodGlucose ||
- payload.bloodLipids || payload.diseaseHistory || payload.medications || payload.notes
- },
- // 先保存当前活动成员的编辑内容到快照,再逐个成员保存健康档案,最后生成方案
- saveAndGenerate: function() {
- var self = this
- // 保存当前活动成员到快照
- if (self.activeFormMemberId) {
- self.memberFormMap[self.activeFormMemberId] = {
- form: JSON.parse(JSON.stringify(self.form)),
- lipidCholesterol: self.lipidCholesterol,
- selectedDiseases: JSON.parse(JSON.stringify(self.selectedDiseases)),
- medRows: JSON.parse(JSON.stringify(self.medRows))
- }
- }
- // 血压格式校验(检查所有成员)
- var members = self.selectedMemberIds.slice()
- for (var i = 0; i < members.length; i++) {
- var payload = self.snapshotForMember(members[i])
- if (payload && payload.bloodPressure && !/^\d{2,3}\/\d{2,3}$/.test(payload.bloodPressure)) {
- uni.showToast({ title: '血压格式应为 120/80', icon: 'none' })
- return
- }
- }
- self.loading = true
- var saveTasks = []
- members.forEach(function(mid) {
- var payload = self.snapshotForMember(mid)
- if (!payload) return
- payload.memberId = mid
- saveTasks.push(saveHealthStatus(payload))
- })
- if (saveTasks.length === 0) {
- // 无健康数据,直接生成方案
- self.loading = false
- self.generatePlan()
- return
- }
- Promise.all(saveTasks).then(function() {
- uni.showToast({ title: '健康信息已保存', icon: 'success' })
- self.loading = false
- self.generatePlan()
- }).catch(function() {
- // 保存失败也继续生成
- self.loading = false
- self.generatePlan()
- })
- },
- // ===== 方案生成与编辑 (保持原有逻辑) =====
- generatePlan: function() {
- var self = this
- if (!self.canGenerate) return
- self.loading = true
- var params = {
- memberIds: self.selectedMemberIds.join(','),
- dimensions: 'body',
- goal: self.userGoal.trim()
- }
- generateHealthPlan(params).then(function(res) {
- self.loading = false
- if (res && res.code === 200 && res.data) {
- var data = res.data
- if (typeof data === 'string') {
- self.planData = { overview: data }
- self.step = 3
- } else if (data.overview) {
- self.planData = data
- if (data.sections && data.sections instanceof Array) {
- data.sections.forEach(function(sec) {
- if (sec.key && self.sections[sec.key]) {
- self.sections[sec.key].content = sec.content || ''
- self.sections[sec.key].items = sec.items || []
- }
- })
- }
- self.step = 3
- } else if (data.raw) {
- self.planData = { overview: data.raw.substring(0, 200) }
- self.step = 3
- } else {
- uni.showToast({ title: '生成失败,请重试', icon: 'none' })
- }
- } else {
- uni.showToast({ title: res && res.message ? res.message : '生成失败', icon: 'none' })
- }
- }).catch(function(err) {
- self.loading = false
- // 付费墙:家庭会员或健康启航计划均未开通(request 封装对非 200 code 统一 reject,此处拿到 err.code)
- if (err && err.code === 4001) {
- self.showPaywallSheet()
- } else {
- uni.showToast({ title: '请求失败,请检查网络', icon: 'none' })
- }
- })
- },
- // 付费墙引导:三选一(购买家庭会员 / 购买健康启航计划 / 取消)
- showPaywallSheet: function() {
- uni.showActionSheet({
- itemList: ['购买家庭会员', '购买健康启航计划'],
- success: function(sheetRes) {
- if (sheetRes.tapIndex === 0) {
- uni.setStorageSync('PLAN_RESUME_AFTER_PURCHASE', '1')
- uni.navigateTo({ url: '/pages/membership/upgrade' })
- } else if (sheetRes.tapIndex === 1) {
- uni.setStorageSync('PLAN_RESUME_AFTER_PURCHASE', '1')
- uni.navigateTo({ url: '/pages/purchase/index' })
- }
- }
- })
- },
- doAutoExecute: function() {
- if (this.selectedMemberIds.length === 0) return
- if (!this.userGoal) this.userGoal = '基于菌群检测报告的个性化健康管理方案'
- this.generatePlan()
- },
- regenerateCurrentSection: function() {
- var self = this
- var key = self.currentSectionKey
- var existing = self.sections[key].content
- if (!existing) return
- self.loading = true
- var params = {
- section: key,
- feedback: self.sectionFeedback || '',
- existing_section_content: existing,
- memberIds: self.selectedMemberIds.join(','),
- dimensions: 'body',
- goal: self.userGoal.trim()
- }
- regenerateSection(params).then(function(res) {
- self.loading = false
- if (res && res.code === 200 && res.data) {
- self.sections[key].content = res.data.content || res.data
- if (res.data.tasks) {
- self.sections[key].tasks = res.data.tasks
- }
- uni.showToast({ title: '生成成功', icon: 'success' })
- } else {
- uni.showToast({ title: res && res.message ? res.message : '生成失败', icon: 'none' })
- }
- }).catch(function() {
- self.loading = false
- uni.showToast({ title: '请求失败', icon: 'none' })
- })
- },
- confirmCurrentSection: function() {
- var self = this
- self.sections[self.currentSectionKey].confirmed = true
- self.editing = false
- self.sectionFeedback = ''
- // 检查是否全部确认
- if (self.allConfirmed) {
- // 保持在 step 3,显示最终确认区域
- }
- },
- startEdit: function() {
- this.editing = true
- this.sectionFeedback = ''
- },
- exitEditMode: function() {
- this.editing = false
- this.sectionFeedback = ''
- },
- submitPlan: function() {
- var self = this
- self.loading = true
- var planJson = JSON.stringify({
- overview: self.planData.overview || '',
- sections: self.sectionKeys.map(function(key) {
- return { key: key, content: self.sections[key].content, items: self.sections[key].items || [], tasks: self.sections[key].tasks || [] }
- })
- })
- var saveData = {
- memberIds: self.selectedMemberIds.join(','),
- dimensions: 'body',
- goal: self.userGoal.trim(),
- planContent: self.buildPlanContent(),
- planJson: planJson,
- status: 'published'
- }
- saveHealthPlan(saveData).then(function(res) {
- self.loading = false
- if (res && res.code === 200) {
- uni.showToast({ title: '方案已生成', icon: 'none', duration: 2000 })
- setTimeout(function() { uni.navigateBack() }, 1200)
- self.taskSubmitted = true
- } else {
- uni.showToast({ title: res && res.message ? res.message : '保存失败', icon: 'none' })
- }
- }).catch(function() {
- self.loading = false
- uni.showToast({ title: '请求失败', icon: 'none' })
- })
- },
- buildPlanContent: function() {
- var sb = []
- if (this.planData.overview) {
- sb.push('## 方案概述\n\n' + this.planData.overview + '\n\n')
- }
- var titles = { nutrition: '营养补充建议', diet: '饮食建议', exercise: '运动计划' }
- var self = this
- this.sectionKeys.forEach(function(key) {
- if (self.sections[key].content) {
- sb.push('## ' + (titles[key] || key) + '\n\n' + self.sections[key].content + '\n\n')
- }
- })
- return sb.join('')
- },
- goToTeacherService: function() {
- uni.navigateTo({ url: '/pages/health/teacher-service' })
- },
- // ===== 查看模式 (保持原有) =====
- loadPlanDetail: function() {
- var self = this
- getHealthPlanDetail({ planId: self.planId }).then(function(res) {
- if (res && res.code === 200 && res.data) {
- self.planDetail = res.data
- self.userGoal = res.data.goal != null ? String(res.data.goal) : ''
- if (res.data.memberIds) {
- self.selectedMemberIds = res.data.memberIds.split(',').filter(function(id) { return id }).map(function(id) { return String(parseInt(id) || id) })
- }
- try {
- var pj = JSON.parse(res.data.planJson || '{}')
- if (pj.overview) self.planData.overview = pj.overview
- if (pj.sections && pj.sections instanceof Array) {
- pj.sections.forEach(function(sec) {
- if (sec.key && self.sections[sec.key]) {
- self.sections[sec.key].content = sec.content || ''
- self.sections[sec.key].items = sec.items || []
- self.sections[sec.key].confirmed = true
- }
- })
- }
- } catch (e) {
- if (res.data.planContent) {
- self.planData.overview = res.data.planContent.substring(0, 200)
- }
- }
- }
- }).catch(function() {
- uni.showToast({ title: '方案加载失败', icon: 'none' })
- setTimeout(function() { uni.navigateBack() }, 1500)
- })
- },
- confirmPlanTasks: function() {
- var self = this
- if (!self.planId || self.confirmingTasks) return
- self.confirmingTasks = true
- uni.showLoading({ title: '正在分解任务...' })
- confirmPlan({ planId: self.planId }).then(function(res) {
- uni.hideLoading()
- self.confirmingTasks = false
- if (res && res.code === 200) {
- // 同步当前查看成员为方案执行成员,便于首页/任务中心看到新任务
- var planMemberId = self.getPlanMemberId(null)
- if (planMemberId) {
- uni.setStorageSync('currentMemberId', planMemberId)
- uni.setStorageSync('currentChildId', planMemberId)
- }
- self.loadPlanDetail()
- uni.showModal({
- title: '提示',
- content: '任务已生成,请前往首页-任务中打卡',
- showCancel: false,
- confirmText: '知道了'
- })
- } else {
- uni.showToast({ title: (res && res.message) || '操作失败', icon: 'none' })
- }
- }).catch(function() {
- uni.hideLoading()
- self.confirmingTasks = false
- uni.showToast({ title: '请求失败', icon: 'none' })
- })
- },
- applyForTeacher: function() {
- // 预留:申请规划师接口与业务待定
- },
- getPlanMemberId: function(item) {
- if (item && item.familyMemberId) {
- return item.familyMemberId
- }
- if (this.planDetail && this.planDetail.memberIds) {
- var parts = this.planDetail.memberIds.split(',')
- if (parts.length > 0 && parts[0]) {
- return parseInt(parts[0]) || parts[0]
- }
- }
- var stored = uni.getStorageSync('currentChildId') || uni.getStorageSync('currentMemberId')
- return stored || null
- },
- completePlanTask: function(item) {
- var self = this
- if (!item || !item.id || item.status === 'completed' || item.status === 'pending_review') {
- return
- }
- var memberId = self.getPlanMemberId(item)
- if (!memberId) {
- uni.showToast({ title: '未找到执行成员', icon: 'none' })
- return
- }
- uni.showModal({
- title: '完成任务',
- content: '确定完成「' + item.title + '」吗?',
- success: function(res) {
- if (!res.confirm) return
- completeTask(item.id, memberId, '').then(function(r) {
- if (r && r.code === 200) {
- var needReview = r.data && r.data.needReview
- if (needReview) {
- uni.showToast({ title: '已提交,待审核', icon: 'none' })
- } else {
- var points = (r.data && r.data.pointsEarned) || item.points || 0
- uni.showToast({ title: '获得 ' + points + ' 积分', icon: 'success' })
- }
- self.loadPlanTasks()
- } else {
- uni.showToast({ title: (r && r.message) || '完成失败', icon: 'none' })
- }
- }).catch(function() {
- uni.showToast({ title: '完成失败', icon: 'none' })
- })
- }
- })
- },
- goToTaskCenter: function() {
- uni.navigateTo({ url: '/pages/tasks/tasks' })
- },
- reCreate: function() {
- this.step = 1
- this.viewMode = false
- this.planId = null
- this.planDetail = null
- this.taskSubmitted = false
- this.editing = false
- this.sectionFeedback = ''
- this.selectedMemberIds = []
- this.userGoal = ''
- this.planData = {}
- this.sections = {
- nutrition: { content: '', confirmed: false },
- diet: { content: '', confirmed: false },
- exercise: { content: '', confirmed: false }
- }
- // 重置健康问卷
- this.form = { heightCm: '', weightKg: '', bloodPressure: '', bloodGlucose: '', notes: '' }
- this.lipidCholesterol = ''
- this.selectedDiseases = []
- this.diseaseInput = ''
- this.medRows = [{ name: '', startDate: '', durationText: '', time: '' }]
- this.expandedGroups = DISEASE_GROUPS.slice(0, 3).map(function(g) { return g.key })
- this.memberFormMap = {}
- this.activeFormMemberId = ''
- this.pickerMembers = []
- this.goalLen = 0
- if (this.$refs.memberPicker && this.$refs.memberPicker.reload) {
- this.$refs.memberPicker.reload()
- }
- },
- goBack: function() {
- uni.navigateBack()
- }
- }
- }
- </script>
- <style scoped>
- .plan-page {
- min-height: 100vh;
- background: #F5F7FA;
- padding-bottom: 40rpx;
- }
- /* 步骤条 */
- .steps-bar {
- display: flex;
- align-items: center;
- padding: 24rpx 20rpx;
- background: #fff;
- margin-bottom: 4rpx;
- overflow-x: auto;
- }
- .step {
- display: flex;
- flex-direction: column;
- align-items: center;
- flex-shrink: 0;
- min-width: 80rpx;
- }
- .step-num {
- width: 44rpx;
- height: 44rpx;
- border-radius: 22rpx;
- background: #e0e0e0;
- color: #999;
- font-size: 24rpx;
- font-weight: bold;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.3s;
- }
- .step.active .step-num { background: #F97316; color: #fff; }
- .step.done .step-num { background: #4CAF50; color: #fff; }
- .step-label {
- font-size: 20rpx;
- color: #999;
- white-space: nowrap;
- margin-top: 4rpx;
- }
- .step.active .step-label, .step.done .step-label { color: #333; font-weight: 500; }
- /* 通用区块 */
- .section {
- margin: 20rpx 30rpx;
- background: #fff;
- border-radius: 20rpx;
- padding: 28rpx;
- box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
- }
- .section-title-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 10rpx;
- }
- .section-title {
- font-size: 30rpx;
- font-weight: bold;
- color: #333;
- }
- .section-badge {
- font-size: 22rpx;
- color: #F97316;
- background: #FFF3E0;
- padding: 4rpx 16rpx;
- border-radius: 20rpx;
- }
- .hint-text {
- font-size: 24rpx;
- color: #999;
- margin-bottom: 20rpx;
- }
- /* 成员选择 chips(与 report-list 同模式) */
- .member-scroll {
- width: 100%;
- white-space: nowrap;
- margin-bottom: 16rpx;
- }
- .member-chips {
- display: inline-flex;
- flex-direction: row;
- padding: 4rpx 0;
- }
- .member-chip {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 12rpx 28rpx;
- margin-right: 16rpx;
- border-radius: 32rpx;
- background: #F1F5F9;
- border: 2rpx solid transparent;
- }
- .member-chip-on {
- background: #FFF7ED;
- border-color: #F97316;
- }
- .member-chip-text {
- font-size: 26rpx;
- color: #334155;
- font-weight: 600;
- }
- .member-chip-on .member-chip-text {
- color: #F97316;
- }
- /* 目标输入 */
- .goal-textarea {
- width: 100%;
- min-height: 320rpx;
- font-size: 28rpx;
- color: #333;
- background: #F5F7FA;
- border: none;
- border-radius: 16rpx;
- padding: 24rpx 28rpx;
- line-height: 1.7;
- box-sizing: border-box;
- }
- .goal-footer {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-top: 20rpx;
- }
- .goal-count { font-size: 24rpx; color: #bbb; }
- .goal-footer .btn-next {
- width: auto;
- min-width: 200rpx;
- height: 72rpx;
- line-height: 72rpx;
- padding: 0 48rpx;
- border-radius: 36rpx;
- font-size: 28rpx;
- }
- /* 按钮 */
- .btn-next, .btn-confirm, .btn-edit, .btn-done, .btn-teacher, .btn-skip, .btn-generate {
- width: 100%;
- height: 88rpx;
- line-height: 88rpx;
- border-radius: 44rpx;
- border: none;
- font-size: 30rpx;
- text-align: center;
- }
- .btn-next, .btn-confirm, .btn-done, .btn-generate {
- background: linear-gradient(135deg, #F97316, #FB923C);
- color: #fff;
- }
- .btn-next[disabled], .btn-confirm[disabled], .btn-generate[disabled] { opacity: 0.4; }
- .btn-edit {
- background: #fff;
- color: #F97316;
- font-size: 28rpx;
- border: 2rpx solid #F97316;
- margin-bottom: 16rpx;
- }
- .btn-teacher {
- background: #fff;
- color: #3B82F6;
- border: 2rpx solid #3B82F6;
- }
- .btn-skip {
- background: #fff;
- color: #999;
- border: 2rpx solid #E5E7EB;
- }
- .full-btn { margin-top: 20rpx; }
- /* 并排按钮容器 */
- .btn-pair {
- display: flex;
- gap: 16rpx;
- margin-top: 20rpx;
- }
- .btn-pair .btn-confirm,
- .btn-pair .btn-teacher,
- .btn-pair .btn-skip,
- .btn-pair .btn-generate {
- flex: 1;
- margin-top: 0;
- }
- /* 加载状态 */
- .loading-state {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 80rpx 20rpx;
- }
- .loading-icon { font-size: 80rpx; margin-bottom: 20rpx; }
- .loading-text { font-size: 30rpx; color: #333; margin-bottom: 8rpx; }
- .loading-hint { font-size: 24rpx; color: #999; }
- /* 总览卡片 */
- .overview-card {
- background: #FFF7ED;
- border-radius: 16rpx;
- padding: 24rpx;
- border-left: 4rpx solid #F97316;
- margin-bottom: 20rpx;
- }
- .overview-text {
- font-size: 28rpx;
- color: #555;
- line-height: 1.8;
- }
- /* section 预览列表 */
- .section-preview-list {
- display: flex;
- flex-direction: column;
- gap: 16rpx;
- margin: 20rpx 0;
- }
- .section-preview-item {
- display: flex;
- align-items: center;
- gap: 16rpx;
- padding: 20rpx;
- background: #FAFAFA;
- border-radius: 16rpx;
- border: 2rpx solid #eee;
- }
- .sec-icon { font-size: 40rpx; }
- .sec-info { flex: 1; }
- .sec-title { font-size: 28rpx; color: #333; font-weight: 600; display: block; }
- .sec-status { font-size: 22rpx; color: #999; }
- .sec-status.done { color: #4CAF50; }
- .sec-arrow { color: #ccc; font-size: 28rpx; }
- /* content 展示/编辑 */
- .content-display {
- background: #F5F7FA;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 20rpx;
- }
- .content-text {
- font-size: 28rpx;
- color: #333;
- line-height: 1.8;
- white-space: pre-wrap;
- word-break: break-all;
- }
- .edit-textarea {
- width: 100%;
- min-height: 300rpx;
- font-size: 28rpx;
- color: #333;
- background: #F5F7FA;
- border: 2rpx solid #F97316;
- border-radius: 16rpx;
- padding: 20rpx 24rpx;
- line-height: 1.7;
- box-sizing: border-box;
- margin-bottom: 16rpx;
- }
- .feedback-input {
- width: 100%;
- height: 80rpx;
- background: #FFF7ED;
- border: 2rpx solid #F97316;
- border-radius: 40rpx;
- padding: 0 24rpx;
- font-size: 26rpx;
- color: #333;
- box-sizing: border-box;
- }
- .section-actions {
- display: flex;
- gap: 16rpx;
- margin-top: 16rpx;
- }
- .section-actions .btn-edit, .section-actions .btn-confirm {
- flex: 1;
- margin-bottom: 0;
- }
- .back-link {
- text-align: center;
- font-size: 26rpx;
- color: #999;
- margin-top: 20rpx;
- }
- .back-link:active { opacity: 0.7; }
- /* 最终确认 */
- .final-review {
- display: flex;
- flex-direction: column;
- gap: 16rpx;
- margin: 20rpx 0;
- }
- .review-item {
- background: #FAFAFA;
- border-radius: 16rpx;
- padding: 20rpx;
- border: 2rpx solid #eee;
- }
- .review-title {
- font-size: 28rpx;
- font-weight: 600;
- color: #333;
- display: block;
- margin-bottom: 8rpx;
- }
- .review-preview {
- font-size: 24rpx;
- color: #888;
- line-height: 1.6;
- }
- /* 查看模式 */
- .plan-view-section { margin-top: 0; }
- .plan-meta {
- display: flex;
- flex-direction: column;
- gap: 6rpx;
- padding: 16rpx 20rpx;
- background: #FFF7ED;
- border-radius: 12rpx;
- margin-bottom: 20rpx;
- }
- .plan-meta-item { font-size: 24rpx; color: #92400E; }
- .plan-preview {
- background: #F5F7FA;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 20rpx;
- }
- .plan-preview-text {
- font-size: 28rpx;
- color: #333;
- line-height: 1.8;
- white-space: pre-wrap;
- }
- .plan-confirm-actions {
- margin-bottom: 20rpx;
- }
- /* 成功遮罩 */
- .success-overlay {
- position: fixed;
- top: 0; left: 0; right: 0; bottom: 0;
- background: rgba(0,0,0,0.55);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 100;
- }
- .success-card {
- background: #fff;
- border-radius: 32rpx;
- padding: 60rpx 48rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 560rpx;
- }
- .success-icon { font-size: 100rpx; color: #4CAF50; margin-bottom: 20rpx; }
- .success-title { font-size: 36rpx; font-weight: bold; color: #333; margin-bottom: 12rpx; }
- .success-sub { font-size: 28rpx; color: #888; margin-bottom: 40rpx; }
- /* ===== 多成员调研 tab 页签 ===== */
- .member-tabs {
- display: flex;
- flex-wrap: wrap;
- gap: 16rpx;
- margin-bottom: 16rpx;
- }
- .member-tab {
- padding: 14rpx 32rpx;
- border-radius: 32rpx;
- background: #F1F5F9;
- border: 2rpx solid transparent;
- font-size: 26rpx;
- color: #334155;
- font-weight: 500;
- transition: all 0.2s;
- }
- .member-tab.active {
- background: #FFF7ED;
- border-color: #F97316;
- color: #F97316;
- font-weight: 600;
- }
- .form-member-hint {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 20rpx;
- padding: 16rpx 20rpx;
- background: #FFF7ED;
- border-radius: 12rpx;
- }
- .form-member-label {
- font-size: 26rpx;
- color: #92400E;
- font-weight: 600;
- }
- .form-member-save {
- font-size: 22rpx;
- color: #B45309;
- }
- /* ===== 健康问卷样式 (从 health-status-form.vue 移植) ===== */
- .form-section {
- margin: 0 0 24rpx;
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- }
- .form-title {
- font-size: 30rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 20rpx;
- }
- .form-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 20rpx;
- }
- .form-label {
- font-size: 28rpx;
- color: #555;
- }
- .form-input {
- flex: 1;
- margin-left: 30rpx;
- height: 68rpx;
- background: #F5F7FA;
- border-radius: 10rpx;
- padding: 0 20rpx;
- font-size: 28rpx;
- text-align: right;
- }
- /* 疾病标签 */
- .disease-group { margin-bottom: 8rpx; }
- .group-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12rpx 0;
- }
- .group-title { font-size: 28rpx; font-weight: bold; color: #333; }
- .group-arrow { font-size: 24rpx; color: #999; }
- .risk-badge { margin-left: 6rpx; color: #E64340; font-size: 24rpx; }
- .tag-wrap { display: flex; flex-wrap: wrap; }
- .tag-item {
- padding: 12rpx 24rpx;
- background: #F5F7FA;
- border-radius: 30rpx;
- font-size: 26rpx;
- color: #666;
- margin: 0 16rpx 16rpx 0;
- }
- .tag-active { background: #F97316; color: #fff; }
- /* 已选疾病 */
- .selected-area { margin-top: 16rpx; padding: 16rpx; background: #FFF7E6; border-radius: 12rpx; }
- .selected-title { font-size: 26rpx; color: #8A6D3B; margin-bottom: 8rpx; }
- .sel-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 10rpx 0;
- border-bottom: 1rpx solid #F0E6D0;
- }
- .sel-name { font-size: 28rpx; color: #333; flex: 1; }
- .sel-diag { font-size: 26rpx; color: #F97316; }
- .sel-del { font-size: 30rpx; color: #E64340; margin-left: 16rpx; }
- /* 自定义疾病输入 */
- .custom-row { display: flex; align-items: center; margin-top: 8rpx; }
- .custom-input {
- flex: 1;
- height: 64rpx;
- background: #F5F7FA;
- border-radius: 10rpx;
- padding: 0 20rpx;
- font-size: 26rpx;
- margin-right: 20rpx;
- }
- .add-link { font-size: 28rpx; color: #F97316; }
- /* 药物行 */
- .med-row {
- border: 1rpx solid #F0F0F0;
- border-radius: 12rpx;
- padding: 16rpx;
- margin-bottom: 16rpx;
- }
- .med-line {
- display: flex;
- align-items: center;
- margin-bottom: 12rpx;
- }
- .med-line:last-child { margin-bottom: 0; }
- .med-name {
- flex: 1;
- height: 64rpx;
- background: #F5F7FA;
- border-radius: 10rpx;
- padding: 0 16rpx;
- font-size: 26rpx;
- margin-right: 12rpx;
- }
- .med-picker {
- font-size: 26rpx;
- color: #F97316;
- padding: 8rpx 16rpx;
- background: #F5F7FA;
- border-radius: 10rpx;
- }
- .med-forget { font-size: 26rpx; color: #999; margin-left: 16rpx; }
- .med-duration { font-size: 26rpx; color: #8A6D3B; }
- .med-time {
- flex: 1;
- height: 64rpx;
- background: #F5F7FA;
- border-radius: 10rpx;
- padding: 0 16rpx;
- font-size: 26rpx;
- margin-right: 12rpx;
- }
- .med-del { font-size: 26rpx; color: #E64340; }
- /* 备注 */
- .notes-area {
- width: 100%;
- height: 160rpx;
- background: #F5F7FA;
- border-radius: 10rpx;
- padding: 16rpx 20rpx;
- font-size: 26rpx;
- box-sizing: border-box;
- }
- /* 空状态 */
- .empty-state {
- width: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 40rpx 0 24rpx;
- box-sizing: border-box;
- }
- .empty-text { font-size: 26rpx; color: #999; }
- .member-load-hint {
- font-size: 22rpx;
- color: #F97316;
- margin-bottom: 12rpx;
- }
- .empty-action {
- margin-top: 20rpx;
- padding: 12rpx 32rpx;
- font-size: 26rpx;
- color: #F97316;
- border: 2rpx solid #F97316;
- border-radius: 32rpx;
- }
- </style>
|