| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676 |
- <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 }
- }
- if (found) out.push(found)
- })
- return out
- },
- currentMemberName: function() {
- var members = this.selectedMembers
- for (var i = 0; i < members.length; i++) {
- if (String(members[i].memberId || members[i].id) === String(this.activeFormMemberId)) {
- return members[i].name || members[i].nickname || '成员'
- }
- }
- 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() {
- 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 })
- }
- 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() {
- self.loading = false
- uni.showToast({ title: '请求失败,请检查网络', icon: 'none' })
- })
- },
- 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: 160rpx;
- font-size: 28rpx;
- color: #333;
- background: #F5F7FA;
- border: none;
- border-radius: 16rpx;
- padding: 20rpx 24rpx;
- line-height: 1.7;
- box-sizing: border-box;
- }
- .goal-footer {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-top: 16rpx;
- }
- .goal-count { font-size: 24rpx; color: #bbb; }
- /* 按钮 */
- .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; }
- /* ===== 健康问卷样式 (从 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>
|