|
@@ -0,0 +1,1108 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="survey-wrap">
|
|
|
|
|
+ <!-- 顶部进度条 -->
|
|
|
|
|
+ <view class="progress-bar">
|
|
|
|
|
+ <view class="progress-track">
|
|
|
|
|
+ <view class="progress-fill" :style="'width:' + progressPercent + '%'"></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="progress-labels">
|
|
|
|
|
+ <text v-for="(m, i) in activeModules" :key="m"
|
|
|
|
|
+ class="progress-label" :class="{ 'progress-label-active': currentStep > i }">{{ moduleTitles[m] }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 步骤内容 -->
|
|
|
|
|
+ <view class="content">
|
|
|
|
|
+ <!-- ===== Step: 基本情况 basic ===== -->
|
|
|
|
|
+ <view v-if="currentModule === 'basic'">
|
|
|
|
|
+ <view class="step-head">
|
|
|
|
|
+ <text class="step-icon">📏</text>
|
|
|
|
|
+ <view class="step-head-text">
|
|
|
|
|
+ <text class="step-title">基本情况</text>
|
|
|
|
|
+ <text class="step-sub">身高、体重、血压、血糖、总胆固醇(快捷标签可一键填入参考值,再手改)</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">身高(cm)</text>
|
|
|
|
|
+ <view class="quick-chips">
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': heightQuick === 'normal' }" @tap="setHeightQuick('normal')"><text>正常</text></view>
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': heightQuick === 'high' }" @tap="setHeightQuick('high')"><text>偏高</text></view>
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': heightQuick === 'low' }" @tap="setHeightQuick('low')"><text>偏低</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <input class="num-input" type="digit" v-model="innerForm.heightCm" placeholder="如 170" />
|
|
|
|
|
+ <text class="unit-hint">单位:厘米</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">体重(kg)</text>
|
|
|
|
|
+ <view class="quick-chips">
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': weightQuick === 'normal' }" @tap="setWeightQuick('normal')"><text>正常</text></view>
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': weightQuick === 'high' }" @tap="setWeightQuick('high')"><text>偏重</text></view>
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': weightQuick === 'low' }" @tap="setWeightQuick('low')"><text>偏轻</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <input class="num-input" type="digit" v-model="innerForm.weightKg" placeholder="如 60" />
|
|
|
|
|
+ <text class="unit-hint">单位:公斤</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">血压</text>
|
|
|
|
|
+ <view class="quick-chips">
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': bpQuick === 'normal' }" @tap="setBpQuick('normal')"><text>正常</text></view>
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': bpQuick === 'high' }" @tap="setBpQuick('high')"><text>偏高</text></view>
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': bpQuick === 'low' }" @tap="setBpQuick('low')"><text>偏低</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <input class="num-input" type="text" v-model="innerForm.bloodPressure" placeholder="如 120/80" />
|
|
|
|
|
+ <text class="unit-hint">格式:收缩压/舒张压(mmHg)</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">空腹血糖(mmol/L)</text>
|
|
|
|
|
+ <view class="quick-chips">
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': glucoseQuick === 'normal' }" @tap="setGlucoseQuick('normal')"><text>正常</text></view>
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': glucoseQuick === 'high' }" @tap="setGlucoseQuick('high')"><text>偏高</text></view>
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': glucoseQuick === 'low' }" @tap="setGlucoseQuick('low')"><text>偏低</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <input class="num-input" type="digit" v-model="innerForm.bloodGlucose" placeholder="如 5.0" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">总胆固醇(mmol/L)</text>
|
|
|
|
|
+ <view class="quick-chips">
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': lipidQuick === 'normal' }" @tap="setLipidQuick('normal')"><text>正常</text></view>
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': lipidQuick === 'high' }" @tap="setLipidQuick('high')"><text>偏高</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <input class="num-input" type="digit" v-model="innerForm.lipidCholesterol" placeholder="如 4.5" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">其他补充(选填)</text></view>
|
|
|
|
|
+ <textarea class="notes-area" v-model="innerForm.notes" placeholder="其他想补充的健康信息" :maxlength="500" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ===== Step: 当前状态 status ===== -->
|
|
|
|
|
+ <view v-if="currentModule === 'status'">
|
|
|
|
|
+ <view class="step-head">
|
|
|
|
|
+ <text class="step-icon">🩺</text>
|
|
|
|
|
+ <view class="step-head-text">
|
|
|
|
|
+ <text class="step-title">当前状态</text>
|
|
|
|
|
+ <text class="step-sub">特殊身体状态;按性别/年龄显示人群专属问题</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">特殊状态</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in currentStatusOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.currentStatus === opt }"
|
|
|
|
|
+ @tap="setLifestyle('currentStatus', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <!-- 女性专属 -->
|
|
|
|
|
+ <view v-if="isFemale">
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">月经周期</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in menstrualOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.menstrualCycle === opt }"
|
|
|
|
|
+ @tap="setLifestyle('menstrualCycle', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">特殊时期</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in specialPeriodOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.specialPeriod === opt }"
|
|
|
|
|
+ @tap="setLifestyle('specialPeriod', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <!-- 儿童专属 -->
|
|
|
|
|
+ <view v-if="isChild">
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">生长发育</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in growthOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.growthDevelopment === opt }"
|
|
|
|
|
+ @tap="setLifestyle('growthDevelopment', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">挑食情况</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in pickyEatingOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.pickyEating === opt }"
|
|
|
|
|
+ @tap="setLifestyle('pickyEating', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <!-- 老年专属 -->
|
|
|
|
|
+ <view v-if="isElderly">
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">骨质疏松风险</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in osteoporosisOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.osteoporosisRisk === opt }"
|
|
|
|
|
+ @tap="setLifestyle('osteoporosisRisk', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">跌倒风险</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in fallRiskOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.fallRisk === opt }"
|
|
|
|
|
+ @tap="setLifestyle('fallRisk', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <!-- 青少年专属 -->
|
|
|
|
|
+ <view v-if="isTeenager">
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">屏幕时间</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in screenTimeOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.screenTime === opt }"
|
|
|
|
|
+ @tap="setLifestyle('screenTime', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">情绪状态</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in teenMoodOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.teenMood === opt }"
|
|
|
|
|
+ @tap="setLifestyle('teenMood', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ===== Step: 饮食偏好 diet ===== -->
|
|
|
|
|
+ <view v-if="currentModule === 'diet'">
|
|
|
|
|
+ <view class="step-head">
|
|
|
|
|
+ <text class="step-icon">🍽️</text>
|
|
|
|
|
+ <view class="step-head-text">
|
|
|
|
|
+ <text class="step-title">饮食偏好</text>
|
|
|
|
|
+ <text class="step-sub">忌口、口味、菜系、烹饪方式、饮食结构(过敏原在最后一步单独采集)</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="pref-none-row">
|
|
|
|
|
+ <button class="pref-none-btn" :class="{ 'pref-none-btn-active': innerDiet.noPreference }"
|
|
|
|
|
+ @tap="setNoPreference">{{ innerDiet.noPreference ? '已选:无特殊偏好' : '无特殊偏好,直接下一步' }}</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">绝对忌口</text><text class="card-hint">多选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="item in presetAvoid" :key="item" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.absoluteAvoid.indexOf(item) >= 0 }"
|
|
|
|
|
+ @tap="toggleDietChip('absoluteAvoid', item)"><text>{{ item }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="custom-row" v-if="customField === 'absoluteAvoid'">
|
|
|
|
|
+ <input class="custom-input" placeholder="输入其他忌口" v-model="customInput" confirm-type="done" @confirm="addCustom" />
|
|
|
|
|
+ <view class="custom-confirm" @tap="addCustom"><text>添加</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="custom-add" v-else @tap="openCustom('absoluteAvoid')"><text>+ 自定义添加</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">必吃食物</text><text class="card-hint">多选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="item in presetMustEat" :key="item" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.mustEat.indexOf(item) >= 0 }"
|
|
|
|
|
+ @tap="toggleDietChip('mustEat', item)"><text>{{ item }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">口味偏好</text><text class="card-hint">多选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="item in presetFlavor" :key="item" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.flavorPref.indexOf(item) >= 0 }"
|
|
|
|
|
+ @tap="toggleDietChip('flavorPref', item)"><text>{{ item }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">菜系偏好</text><text class="card-hint">多选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="item in presetCuisine" :key="item" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.cuisinePref.indexOf(item) >= 0 }"
|
|
|
|
|
+ @tap="toggleDietChip('cuisinePref', item)"><text>{{ item }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">烹饪方式</text><text class="card-hint">多选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="item in presetCooking" :key="item" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.cookingMethods.indexOf(item) >= 0 }"
|
|
|
|
|
+ @tap="toggleDietChip('cookingMethods', item)"><text>{{ item }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">健康目标</text><text class="card-hint">多选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="item in presetGoals" :key="item" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.healthGoals.indexOf(item) >= 0 }"
|
|
|
|
|
+ @tap="toggleDietChip('healthGoals', item)"><text>{{ item }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">辣度</text><text class="card-hint">{{ spiceLabels[innerDiet.spiceLevel || 0] }}</text></view>
|
|
|
|
|
+ <view class="slider-row">
|
|
|
|
|
+ <slider :value="innerDiet.spiceLevel || 0" min="0" max="5" step="1" activeColor="#FF8C42"
|
|
|
|
|
+ @changing="onSpiceChanging" @change="onSpiceChange" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">宗教饮食</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in religiousOptions" :key="opt.value" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.religiousDiet === opt.value }"
|
|
|
|
|
+ @tap="setDietValue('religiousDiet', opt.value)"><text>{{ opt.label }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">饮食结构现状</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="struct-row"><text class="struct-label">每日蔬果量</text>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in vegFruitOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.vegetableFruit === opt }"
|
|
|
|
|
+ @tap="setDietValue('vegetableFruit', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="struct-row"><text class="struct-label">乳制品摄入</text>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in dairyOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.dairy === opt }"
|
|
|
|
|
+ @tap="setDietValue('dairy', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="struct-row"><text class="struct-label">含糖饮料频率</text>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in sugaryDrinkOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.sugaryDrinks === opt }"
|
|
|
|
|
+ @tap="setDietValue('sugaryDrinks', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ===== Step: 疾病史 disease ===== -->
|
|
|
|
|
+ <view v-if="currentModule === 'disease'">
|
|
|
|
|
+ <view class="step-head">
|
|
|
|
|
+ <text class="step-icon">📋</text>
|
|
|
|
|
+ <view class="step-head-text">
|
|
|
|
|
+ <text class="step-title">疾病史</text>
|
|
|
|
|
+ <text class="step-sub">选择过往确诊疾病,可设确诊年月(★ 为重点关注项)</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-for="(group, gi) in diseaseGroups" :key="group.key" class="card">
|
|
|
|
|
+ <view class="card-head" @tap="toggleGroup(group.key)">
|
|
|
|
|
+ <text class="card-title">{{ group.title }}</text>
|
|
|
|
|
+ <text class="group-arrow">{{ expandedGroups.indexOf(group.key) >= 0 ? '收起' : '展开' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="expandedGroups.indexOf(group.key) >= 0" class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="(item, ii) in group.items" :key="getDiseaseKey(gi, ii)" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': selectedDiseaseNames.indexOf(item.name) >= 0 }"
|
|
|
|
|
+ @tap="toggleDisease(item.name)">
|
|
|
|
|
+ <text>{{ item.name }}</text>
|
|
|
|
|
+ <text v-if="item.risk" class="risk-badge">★</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">自定义疾病</text></view>
|
|
|
|
|
+ <view class="custom-row">
|
|
|
|
|
+ <input class="custom-input" type="text" v-model="diseaseInput" placeholder="输入其他疾病" />
|
|
|
|
|
+ <view class="custom-confirm" @tap="addCustomDisease"><text>添加</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card" v-if="innerForm.selectedDiseases.length > 0">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">已选疾病({{ innerForm.selectedDiseases.length }})</text></view>
|
|
|
|
|
+ <view v-for="(item, si) in innerForm.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" @tap="removeSelected(si)">×</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ===== Step: 排便及运动 bowel ===== -->
|
|
|
|
|
+ <view v-if="currentModule === 'bowel'">
|
|
|
|
|
+ <view class="step-head">
|
|
|
|
|
+ <text class="step-icon">🚽</text>
|
|
|
|
|
+ <view class="step-head-text">
|
|
|
|
|
+ <text class="step-title">排便及运动</text>
|
|
|
|
|
+ <text class="step-sub">排便习惯 + 运动频率与每次时长</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">排便频率</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in bowelFreqOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.bowelFrequency === opt }"
|
|
|
|
|
+ @tap="setLifestyle('bowelFrequency', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">粪便性状</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in bowelFormOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.bowelForm === opt }"
|
|
|
|
|
+ @tap="setLifestyle('bowelForm', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">运动频率</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in exerciseFreqOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.exerciseFrequency === opt }"
|
|
|
|
|
+ @tap="setLifestyle('exerciseFrequency', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">每次运动时长</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in exerciseDurationOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.exerciseDuration === opt }"
|
|
|
|
|
+ @tap="setLifestyle('exerciseDuration', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ===== Step: 睡眠情况 sleep ===== -->
|
|
|
|
|
+ <view v-if="currentModule === 'sleep'">
|
|
|
|
|
+ <view class="step-head">
|
|
|
|
|
+ <text class="step-icon">😴</text>
|
|
|
|
|
+ <view class="step-head-text">
|
|
|
|
|
+ <text class="step-title">睡眠情况</text>
|
|
|
|
|
+ <text class="step-sub">睡眠时长与入睡时间</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">睡眠时长</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in sleepDurationOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.sleepDuration === opt }"
|
|
|
|
|
+ @tap="setLifestyle('sleepDuration', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">入睡时间</text><text class="card-hint">单选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in bedtimeOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.bedtime === opt }"
|
|
|
|
|
+ @tap="setLifestyle('bedtime', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ===== Step: 用药情况 med ===== -->
|
|
|
|
|
+ <view v-if="currentModule === 'med'">
|
|
|
|
|
+ <view class="step-head">
|
|
|
|
|
+ <text class="step-icon">💊</text>
|
|
|
|
|
+ <view class="step-head-text">
|
|
|
|
|
+ <text class="step-title">用药情况</text>
|
|
|
|
|
+ <text class="step-sub">当前在服药物;如无可勾选「无长期用药」</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="quick-chip" :class="{ 'quick-chip-on': innerForm.noMedication }" @tap="toggleNoMedication">
|
|
|
|
|
+ <text>{{ innerForm.noMedication ? '✓ 已选' : '无长期用药' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="unit-hint" v-if="innerForm.noMedication">已标记无长期用药,可跳过药物填写</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="!innerForm.noMedication" v-for="(row, idx) in innerForm.medRows" :key="getMedKey(idx)" class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">用药 {{ idx + 1 }}</text>
|
|
|
|
|
+ <text class="card-del" @tap="removeMed(idx)">删除</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card-row"><text class="card-label">药名</text>
|
|
|
|
|
+ <input class="card-input" type="text" v-model="row.name" placeholder="药物品牌,如 立普妥" /></view>
|
|
|
|
|
+ <view class="card-row"><text class="card-label">开始年月</text>
|
|
|
|
|
+ <picker mode="date" fields="month" :value="row.startDate || currentMonth" :end="monthEnd" @change="onMedStartChange(idx, $event)">
|
|
|
|
|
+ <view class="picker-val">{{ row.startDate ? row.startDate : '选择开始日期' }}</view>
|
|
|
|
|
+ </picker>
|
|
|
|
|
+ <text class="link-forget" @tap="onForgetClick(idx)">记不清</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card-row" v-if="row.durationText"><text class="card-label">已服时长</text>
|
|
|
|
|
+ <text class="card-value">{{ row.durationText }}</text></view>
|
|
|
|
|
+ <view class="card-row"><text class="card-label">服药时间</text>
|
|
|
|
|
+ <input class="card-input" type="text" v-model="row.time" placeholder="如 早饭后一次" /></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="add-row" v-if="!innerForm.noMedication" @tap="addMed"><text class="add-icon">+</text><text class="add-text">添加药物/治疗</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ===== Step: 过敏源 allergy ===== -->
|
|
|
|
|
+ <view v-if="currentModule === 'allergy'">
|
|
|
|
|
+ <view class="step-head">
|
|
|
|
|
+ <text class="step-icon">⚠️</text>
|
|
|
|
|
+ <view class="step-head-text">
|
|
|
|
|
+ <text class="step-title">过敏源</text>
|
|
|
|
|
+ <text class="step-sub">过敏原与过敏症状均可多选,可自定义添加</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">过敏原</text><text class="card-hint">多选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="item in presetAllergies" :key="item" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': innerDiet.allergies.indexOf(item) >= 0 }"
|
|
|
|
|
+ @tap="toggleDietChip('allergies', item)"><text>{{ item }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="custom-row" v-if="customField === 'allergies'">
|
|
|
|
|
+ <input class="custom-input" placeholder="输入其他过敏原" v-model="customInput" confirm-type="done" @confirm="addCustom" />
|
|
|
|
|
+ <view class="custom-confirm" @tap="addCustom"><text>添加</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="custom-add" v-else @tap="openCustom('allergies')"><text>+ 自定义添加</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card">
|
|
|
|
|
+ <view class="card-head"><text class="card-title">过敏表现</text><text class="card-hint">多选</text></view>
|
|
|
|
|
+ <view class="chip-row-wrap">
|
|
|
|
|
+ <view v-for="opt in allergyOptions" :key="opt" class="chip-item-wrap"
|
|
|
|
|
+ :class="{ 'chip-active': lifestyle.allergySymptoms.indexOf(opt) >= 0 }"
|
|
|
|
|
+ @tap="toggleLifestyleArray('allergySymptoms', opt)"><text>{{ opt }}</text></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 底部固定栏 -->
|
|
|
|
|
+ <view class="footer-bar">
|
|
|
|
|
+ <button class="btn-prev" v-if="currentStep > 1" @tap="prevStep">上一步</button>
|
|
|
|
|
+ <button class="btn-next" v-if="currentStep < activeModules.length" @tap="nextStep">下一步</button>
|
|
|
|
|
+ <button class="btn-done" v-else :disabled="submitting" @tap="onSubmit">
|
|
|
|
|
+ {{ submitting ? '保存中...' : '完成' }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+// ===== 预设常量(与旧子组件 / preferences.vue 保持一致) =====
|
|
|
|
|
+var PRESET_ALLERGIES = ['海鲜', '花生', '乳糖', '鸡蛋', '牛奶', '豆类', '麸质', '坚果', '芒果', '其他']
|
|
|
|
|
+var PRESET_AVOID = ['牛肉', '羊肉', '猪肉', '内脏', '油腻', '辛辣', '生冷', '腌制']
|
|
|
|
|
+var PRESET_FLAVOR = ['酸', '甜', '咸', '鲜', '苦', '清淡', '浓郁']
|
|
|
|
|
+var PRESET_CUISINE = ['川', '粤', '鲁', '淮扬', '日料', '西餐', '东南亚', '韩餐']
|
|
|
|
|
+var PRESET_COOKING = ['蒸', '煮', '炒', '烤', '炖', '凉拌', '煎', '炸']
|
|
|
|
|
+var PRESET_MUSTEAT = ['蔬菜', '水果', '粗粮', '鱼类', '鸡蛋', '豆制品', '菌菇']
|
|
|
|
|
+var PRESET_GOALS = ['减脂', '增肌', '控糖', '肠道调理', '增强免疫']
|
|
|
|
|
+var VALID_MODULES = ['basic', 'status', 'diet', 'disease', 'bowel', 'sleep', 'med', 'allergy']
|
|
|
|
|
+// 8 组 53 项预置疾病(与旧 MedicalHistoryStep.vue 一致,risk=true 为重点关注项)
|
|
|
|
|
+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 }
|
|
|
|
|
+ ]}
|
|
|
|
|
+]
|
|
|
|
|
+// 新采集的 lifestyle key(旧数据里可能没有,默认空值)
|
|
|
|
|
+var LIFESTYLE_KEYS = [
|
|
|
|
|
+ 'currentStatus', 'menstrualCycle', 'specialPeriod', 'growthDevelopment', 'pickyEating',
|
|
|
|
|
+ 'osteoporosisRisk', 'fallRisk', 'screenTime', 'teenMood',
|
|
|
|
|
+ 'bowelFrequency', 'bowelForm', 'exerciseFrequency', 'exerciseDuration',
|
|
|
|
|
+ 'sleepDuration', 'bedtime', 'allergySymptoms',
|
|
|
|
|
+ 'mood', 'antibioticHistory'
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'ComprehensiveSurvey',
|
|
|
|
|
+ props: {
|
|
|
|
|
+ // 启用模块 key 列表(顺序决定步骤顺序;空/全非法 → 全量 8 模块)
|
|
|
|
|
+ modules: { type: Array, default: function() { return [] } },
|
|
|
|
|
+ // health_status 快照(结构化字段,lifestyle 为对象非 JSON 串)
|
|
|
|
|
+ form: { type: Object, default: function() { return {} } },
|
|
|
|
|
+ // diet_preferences 快照(13 key)
|
|
|
|
|
+ diet: { type: Object, default: function() { return {} } },
|
|
|
|
|
+ // 成员画像(gender/age 驱动条件渲染)
|
|
|
|
|
+ profile: { type: Object, default: function() { return {} } },
|
|
|
|
|
+ // 外部提交态
|
|
|
|
|
+ submitting: { type: Boolean, default: false }
|
|
|
|
|
+ },
|
|
|
|
|
+ data: function() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ currentStep: 1,
|
|
|
|
|
+ innerForm: {},
|
|
|
|
|
+ innerDiet: {},
|
|
|
|
|
+ lifestyle: {},
|
|
|
|
|
+ currentMonth: '',
|
|
|
|
|
+ monthEnd: '',
|
|
|
|
|
+ expandedGroups: [],
|
|
|
|
|
+ // 自定义输入
|
|
|
|
|
+ customField: '',
|
|
|
|
|
+ customInput: '',
|
|
|
|
|
+ diseaseInput: '',
|
|
|
|
|
+ // 快捷标签选中态(仅 UI 提示,值仍以输入框为准)
|
|
|
|
|
+ heightQuick: '', weightQuick: '', bpQuick: '', glucoseQuick: '', lipidQuick: '',
|
|
|
|
|
+ // 选项常量
|
|
|
|
|
+ presetAllergies: PRESET_ALLERGIES,
|
|
|
|
|
+ presetAvoid: PRESET_AVOID,
|
|
|
|
|
+ presetFlavor: PRESET_FLAVOR,
|
|
|
|
|
+ presetCuisine: PRESET_CUISINE,
|
|
|
|
|
+ presetCooking: PRESET_COOKING,
|
|
|
|
|
+ presetMustEat: PRESET_MUSTEAT,
|
|
|
|
|
+ presetGoals: PRESET_GOALS,
|
|
|
|
|
+ diseaseGroups: DISEASE_GROUPS,
|
|
|
|
|
+ moduleTitles: {
|
|
|
|
|
+ basic: '基本', status: '状态', diet: '饮食', disease: '疾病',
|
|
|
|
|
+ bowel: '排便', sleep: '睡眠', med: '用药', allergy: '过敏'
|
|
|
|
|
+ },
|
|
|
|
|
+ currentStatusOptions: ['正常', '哺乳期', '备孕', '怀孕', '住院期', '恢复期', '其他'],
|
|
|
|
|
+ menstrualOptions: ['规律(28-32天)', '不规律', '已停经', '尚未开始'],
|
|
|
|
|
+ specialPeriodOptions: ['无', '备孕中', '怀孕', '哺乳期'],
|
|
|
|
|
+ growthOptions: ['正常(在生长曲线内)', '偏慢', '偏快', '不清楚'],
|
|
|
|
|
+ pickyEatingOptions: ['无挑食', '轻微挑食', '明显挑食', '严重挑食'],
|
|
|
|
|
+ osteoporosisOptions: ['无风险', '有风险(曾骨折/骨密度低)', '不清楚'],
|
|
|
|
|
+ fallRiskOptions: ['近1年无跌倒', '近1年跌倒1次', '近1年跌倒2次及以上'],
|
|
|
|
|
+ screenTimeOptions: ['<1小时/天', '1-3小时/天', '3-5小时/天', '>5小时/天'],
|
|
|
|
|
+ teenMoodOptions: ['开朗', '一般', '易怒', '低落'],
|
|
|
|
|
+ bowelFreqOptions: ['每天1次(正常)', '隔天1次', '每周2-3次', '每周<2次'],
|
|
|
|
|
+ bowelFormOptions: ['干硬颗粒', '条状干硬', '正常香蕉状', '软便', '水样'],
|
|
|
|
|
+ exerciseFreqOptions: ['不运动', '每周1-2次', '每周3-4次', '每周5次以上'],
|
|
|
|
|
+ exerciseDurationOptions: ['30分钟以内', '30-60分钟', '60分钟以上'],
|
|
|
|
|
+ sleepDurationOptions: ['<6小时', '6-7小时', '7-8小时', '>8小时'],
|
|
|
|
|
+ bedtimeOptions: ['22:00前', '22:00-24:00', '24:00后'],
|
|
|
|
|
+ vegFruitOptions: ['<2份', '2-3份', '4份以上'],
|
|
|
|
|
+ dairyOptions: ['每天', '每周3-5次', '很少', '不喝'],
|
|
|
|
|
+ sugaryDrinkOptions: ['几乎不喝', '每周1-2次', '每周3-5次', '每天1次以上'],
|
|
|
|
|
+ allergyOptions: ['无', '反复皮疹', '鼻痒/打喷嚏', '眼痒/流泪', '哮喘样症状', '食物过敏'],
|
|
|
|
|
+ spiceLabels: ['不辣', '微辣', '中辣', '偏辣', '很辣', '特辣'],
|
|
|
|
|
+ religiousOptions: [
|
|
|
|
|
+ { value: 'none', label: '无限制' },
|
|
|
|
|
+ { value: 'halal', label: '清真' },
|
|
|
|
|
+ { value: 'vegetarian', label: '素食' },
|
|
|
|
|
+ { value: 'vegan', label: '纯素' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ // 过滤非法 key + 兜底全量 + 去重(保持顺序)
|
|
|
|
|
+ activeModules: function() {
|
|
|
|
|
+ var seen = {}
|
|
|
|
|
+ var list = (this.modules || []).filter(function(m) {
|
|
|
|
|
+ if (VALID_MODULES.indexOf(m) < 0 || seen[m]) return false
|
|
|
|
|
+ seen[m] = true
|
|
|
|
|
+ return true
|
|
|
|
|
+ })
|
|
|
|
|
+ if (list.length === 0) list = VALID_MODULES.slice()
|
|
|
|
|
+ return list
|
|
|
|
|
+ },
|
|
|
|
|
+ currentModule: function() {
|
|
|
|
|
+ return this.activeModules[this.currentStep - 1] || ''
|
|
|
|
|
+ },
|
|
|
|
|
+ progressPercent: function() {
|
|
|
|
|
+ return Math.round((this.currentStep / this.activeModules.length) * 100)
|
|
|
|
|
+ },
|
|
|
|
|
+ // 已选疾病名列表(供模板内联 indexOf,避免 :class 带参方法调用)
|
|
|
|
|
+ selectedDiseaseNames: function() {
|
|
|
|
|
+ return (this.innerForm.selectedDiseases || []).map(function(d) { return d.name })
|
|
|
|
|
+ },
|
|
|
|
|
+ isFemale: function() {
|
|
|
|
|
+ var g = this.profile && this.profile.gender
|
|
|
|
|
+ return g === 'female' || g === '女'
|
|
|
|
|
+ },
|
|
|
|
|
+ isChild: function() {
|
|
|
|
|
+ var age = this.profile && this.profile.age
|
|
|
|
|
+ return age != null && Number(age) > 0 && Number(age) < 12
|
|
|
|
|
+ },
|
|
|
|
|
+ isTeenager: function() {
|
|
|
|
|
+ var age = this.profile && this.profile.age
|
|
|
|
|
+ return age != null && Number(age) >= 12 && Number(age) < 18
|
|
|
|
|
+ },
|
|
|
|
|
+ isElderly: function() {
|
|
|
|
|
+ var age = this.profile && this.profile.age
|
|
|
|
|
+ return age != null && Number(age) >= 60
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ watch: {
|
|
|
|
|
+ // 仅在 prop 对象身份变化时重建工作副本(父页换快照/切成员 tab);
|
|
|
|
|
+ // 不用 deep:v-model 逐键输入会变更共享行对象,deep 会每次按键全量重建
|
|
|
|
|
+ form: {
|
|
|
|
|
+ handler: function(v) { this.initFromProps() },
|
|
|
|
|
+ immediate: true
|
|
|
|
|
+ },
|
|
|
|
|
+ diet: {
|
|
|
|
|
+ handler: function(v) { this.syncDiet(v) },
|
|
|
|
|
+ immediate: true
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ created: function() {
|
|
|
|
|
+ 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, now.getMonth(), 0).getDate()
|
|
|
|
|
+ this.monthEnd = y + '-' + mStr + '-' + (lastDay < 10 ? '0' + lastDay : '' + lastDay)
|
|
|
|
|
+ this.initFromProps()
|
|
|
|
|
+ this.syncDiet(this.diet)
|
|
|
|
|
+ // 默认展开前 3 组 + 已选疾病所在组
|
|
|
|
|
+ this.expandedGroups = DISEASE_GROUPS.slice(0, 3).map(function(g) { return g.key })
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ ;(this.innerForm.selectedDiseases || []).forEach(function(d) {
|
|
|
|
|
+ var gk = self.findGroupKey(d.name)
|
|
|
|
|
+ if (gk && self.expandedGroups.indexOf(gk) < 0) self.expandedGroups.push(gk)
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ // ===== 初始化 =====
|
|
|
|
|
+ initFromProps: function() {
|
|
|
|
|
+ var f = this.form || {}
|
|
|
|
|
+ this.innerForm = {
|
|
|
|
|
+ heightCm: f.heightCm != null ? f.heightCm : '',
|
|
|
|
|
+ weightKg: f.weightKg != null ? f.weightKg : '',
|
|
|
|
|
+ bloodPressure: f.bloodPressure != null ? f.bloodPressure : '',
|
|
|
|
|
+ bloodGlucose: f.bloodGlucose != null ? f.bloodGlucose : '',
|
|
|
|
|
+ notes: f.notes != null ? f.notes : '',
|
|
|
|
|
+ lipidCholesterol: f.lipidCholesterol != null ? f.lipidCholesterol : '',
|
|
|
|
|
+ selectedDiseases: Array.isArray(f.selectedDiseases) ? f.selectedDiseases.slice() : [],
|
|
|
|
|
+ medRows: Array.isArray(f.medRows) ? f.medRows.slice() : [],
|
|
|
|
|
+ noMedication: !!f.noMedication
|
|
|
|
|
+ }
|
|
|
|
|
+ var ls = f.lifestyle
|
|
|
|
|
+ var obj = {}
|
|
|
|
|
+ if (ls && typeof ls === 'object') {
|
|
|
|
|
+ LIFESTYLE_KEYS.forEach(function(k) {
|
|
|
|
|
+ obj[k] = ls[k] != null ? (ls[k] instanceof Array ? ls[k].slice() : ls[k]) : (k === 'allergySymptoms' ? [] : '')
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ LIFESTYLE_KEYS.forEach(function(k) {
|
|
|
|
|
+ obj[k] = k === 'allergySymptoms' ? [] : ''
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ this.lifestyle = obj
|
|
|
|
|
+ // 快捷标签回显
|
|
|
|
|
+ this.matchQuickLabels()
|
|
|
|
|
+ },
|
|
|
|
|
+ syncDiet: function(v) {
|
|
|
|
|
+ var d = v || {}
|
|
|
|
|
+ this.innerDiet = {
|
|
|
|
|
+ allergies: Array.isArray(d.allergies) ? d.allergies.slice() : [],
|
|
|
|
|
+ absoluteAvoid: Array.isArray(d.absoluteAvoid) ? d.absoluteAvoid.slice() : [],
|
|
|
|
|
+ mustEat: Array.isArray(d.mustEat) ? d.mustEat.slice() : [],
|
|
|
|
|
+ religiousDiet: d.religiousDiet || 'none',
|
|
|
|
|
+ spiceLevel: d.spiceLevel != null ? d.spiceLevel : 0,
|
|
|
|
|
+ flavorPref: Array.isArray(d.flavorPref) ? d.flavorPref.slice() : [],
|
|
|
|
|
+ cuisinePref: Array.isArray(d.cuisinePref) ? d.cuisinePref.slice() : [],
|
|
|
|
|
+ cookingMethods: Array.isArray(d.cookingMethods) ? d.cookingMethods.slice() : [],
|
|
|
|
|
+ healthGoals: Array.isArray(d.healthGoals) ? d.healthGoals.slice() : [],
|
|
|
|
|
+ noPreference: !!d.noPreference,
|
|
|
|
|
+ vegetableFruit: d.vegetableFruit || '',
|
|
|
|
|
+ dairy: d.dairy || '',
|
|
|
|
|
+ sugaryDrinks: d.sugaryDrinks || ''
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ matchQuickLabels: function() {
|
|
|
|
|
+ this.heightQuick = this.matchQuick('heightCm', 'height')
|
|
|
|
|
+ this.weightQuick = this.matchQuick('weightKg', 'weight')
|
|
|
|
|
+ this.bpQuick = this.matchQuick('bloodPressure', 'bloodPressure')
|
|
|
|
|
+ this.glucoseQuick = this.matchQuick('bloodGlucose', 'bloodGlucose')
|
|
|
|
|
+ this.lipidQuick = this.matchQuick('lipidCholesterol', 'lipid')
|
|
|
|
|
+ },
|
|
|
|
|
+ matchQuick: function(field, quickKey) {
|
|
|
|
|
+ var QUICK = {
|
|
|
|
|
+ height: { normal: '170', high: '185', low: '155' },
|
|
|
|
|
+ weight: { normal: '60', high: '80', low: '45' },
|
|
|
|
|
+ bloodPressure: { normal: '120/80', high: '140/90', low: '90/60' },
|
|
|
|
|
+ bloodGlucose: { normal: '5.0', high: '7.0', low: '3.5' },
|
|
|
|
|
+ lipid: { normal: '4.5', high: '6.2' }
|
|
|
|
|
+ }
|
|
|
|
|
+ var map = QUICK[quickKey]
|
|
|
|
|
+ var val = String(this.innerForm[field] || '')
|
|
|
|
|
+ var keys = Object.keys(map)
|
|
|
|
|
+ for (var i = 0; i < keys.length; i++) {
|
|
|
|
|
+ if (String(map[keys[i]]) === val) return keys[i]
|
|
|
|
|
+ }
|
|
|
|
|
+ return ''
|
|
|
|
|
+ },
|
|
|
|
|
+ // ===== 步骤导航(前进步时同步快照到父页) =====
|
|
|
|
|
+ prevStep: function() { if (this.currentStep > 1) this.currentStep-- },
|
|
|
|
|
+ nextStep: function() {
|
|
|
|
|
+ if (this.currentStep < this.activeModules.length) {
|
|
|
|
|
+ this.$emit('update-form', this.formCopy())
|
|
|
|
|
+ this.$emit('update-diet', this.dietCopy())
|
|
|
|
|
+ this.currentStep++
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // ===== 提交 =====
|
|
|
|
|
+ onSubmit: function() {
|
|
|
|
|
+ if (this.submitting) return
|
|
|
|
|
+ // 数值校验(与旧页一致)
|
|
|
|
|
+ if (this.activeModules.indexOf('basic') >= 0) {
|
|
|
|
|
+ var h = Number(this.innerForm.heightCm)
|
|
|
|
|
+ var w = Number(this.innerForm.weightKg)
|
|
|
|
|
+ var g = Number(this.innerForm.bloodGlucose)
|
|
|
|
|
+ var bp = this.innerForm.bloodPressure
|
|
|
|
|
+ if (this.innerForm.heightCm && (h < 30 || h > 250)) {
|
|
|
|
|
+ uni.showToast({ title: '身高应在 30-250 之间', icon: 'none' }); return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.innerForm.weightKg && (w < 3 || w > 300)) {
|
|
|
|
|
+ uni.showToast({ title: '体重应在 3-300 之间', icon: 'none' }); return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.innerForm.bloodGlucose && (g < 1 || g > 30)) {
|
|
|
|
|
+ uni.showToast({ title: '血糖应在 1-30 之间', icon: 'none' }); return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (bp && !/^\d{2,3}\/\d{2,3}$/.test(bp)) {
|
|
|
|
|
+ uni.showToast({ title: '血压格式应为 120/80', icon: 'none' }); return
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 空提交拦截
|
|
|
|
|
+ var formCopy = this.formCopy()
|
|
|
|
|
+ var dietCopy = this.dietCopy()
|
|
|
|
|
+ var diff = this.buildLifestyleDiff()
|
|
|
|
|
+ if (!this.hasAnyContent(formCopy, dietCopy, diff)) {
|
|
|
|
|
+ uni.showToast({ title: '请至少填写一项', icon: 'none' }); return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$emit('submit', { form: formCopy, diet: dietCopy, lifestyleDiff: diff })
|
|
|
|
|
+ },
|
|
|
|
|
+ hasAnyContent: function(form, diet, diff) {
|
|
|
|
|
+ var keys = ['heightCm', 'weightKg', 'bloodPressure', 'bloodGlucose', 'notes']
|
|
|
|
|
+ for (var i = 0; i < keys.length; i++) {
|
|
|
|
|
+ if (form[keys[i]] != null && form[keys[i]] !== '') return true
|
|
|
|
|
+ }
|
|
|
|
|
+ if (form.lipidCholesterol != null && form.lipidCholesterol !== '') return true
|
|
|
|
|
+ if (form.selectedDiseases && form.selectedDiseases.length > 0) return true
|
|
|
|
|
+ if (form.medRows && form.medRows.length > 0) return true
|
|
|
|
|
+ if (form.noMedication) return true
|
|
|
|
|
+ var dkeys = ['allergies', 'absoluteAvoid', 'mustEat', 'flavorPref', 'cuisinePref', 'cookingMethods', 'healthGoals', 'vegetableFruit', 'dairy', 'sugaryDrinks']
|
|
|
|
|
+ for (var j = 0; j < dkeys.length; j++) {
|
|
|
|
|
+ var dv = diet[dkeys[j]]
|
|
|
|
|
+ if (dv instanceof Array) { if (dv.length > 0) return true } else if (dv != null && dv !== '' && dv !== 'none') return true
|
|
|
|
|
+ }
|
|
|
|
|
+ if (diet.religiousDiet && diet.religiousDiet !== 'none') return true
|
|
|
|
|
+ if (diet.spiceLevel && Number(diet.spiceLevel) > 0) return true
|
|
|
|
|
+ if (diet.noPreference) return true
|
|
|
|
|
+ return Object.keys(diff).length > 0
|
|
|
|
|
+ },
|
|
|
|
|
+ // 结构化 form 副本(不含 lifestyle;JSON 转换由父页负责)
|
|
|
|
|
+ formCopy: function() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ heightCm: this.innerForm.heightCm,
|
|
|
|
|
+ weightKg: this.innerForm.weightKg,
|
|
|
|
|
+ bloodPressure: this.innerForm.bloodPressure,
|
|
|
|
|
+ bloodGlucose: this.innerForm.bloodGlucose,
|
|
|
|
|
+ notes: this.innerForm.notes,
|
|
|
|
|
+ lipidCholesterol: this.innerForm.lipidCholesterol,
|
|
|
|
|
+ selectedDiseases: this.innerForm.selectedDiseases || [],
|
|
|
|
|
+ medRows: this.innerForm.medRows || [],
|
|
|
|
|
+ noMedication: !!this.innerForm.noMedication
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ dietCopy: function() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ allergies: this.innerDiet.allergies || [],
|
|
|
|
|
+ absoluteAvoid: this.innerDiet.absoluteAvoid || [],
|
|
|
|
|
+ mustEat: this.innerDiet.mustEat || [],
|
|
|
|
|
+ religiousDiet: this.innerDiet.religiousDiet || 'none',
|
|
|
|
|
+ spiceLevel: this.innerDiet.spiceLevel || 0,
|
|
|
|
|
+ flavorPref: this.innerDiet.flavorPref || [],
|
|
|
|
|
+ cuisinePref: this.innerDiet.cuisinePref || [],
|
|
|
|
|
+ cookingMethods: this.innerDiet.cookingMethods || [],
|
|
|
|
|
+ healthGoals: this.innerDiet.healthGoals || [],
|
|
|
|
|
+ noPreference: !!this.innerDiet.noPreference,
|
|
|
|
|
+ vegetableFruit: this.innerDiet.vegetableFruit || '',
|
|
|
|
|
+ dairy: this.innerDiet.dairy || '',
|
|
|
|
|
+ sugaryDrinks: this.innerDiet.sugaryDrinks || ''
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // lifestyle diff:仅本次修改过的 key(未变化的 key 剔除,交由父页 merge 旧值)
|
|
|
|
|
+ buildLifestyleDiff: function() {
|
|
|
|
|
+ var diff = {}
|
|
|
|
|
+ var orig = (this.form && this.form.lifestyle) || {}
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ LIFESTYLE_KEYS.forEach(function(k) {
|
|
|
|
|
+ var val = self.lifestyle[k]
|
|
|
|
|
+ if (val == null) return
|
|
|
|
|
+ var a = JSON.stringify(val)
|
|
|
|
|
+ var b = JSON.stringify(orig[k] != null ? orig[k] : (k === 'allergySymptoms' ? [] : null))
|
|
|
|
|
+ if (a === b) return
|
|
|
|
|
+ if (val instanceof Array) { if (val.length > 0) diff[k] = val.slice() }
|
|
|
|
|
+ else if (val !== '') diff[k] = val
|
|
|
|
|
+ })
|
|
|
|
|
+ return diff
|
|
|
|
|
+ },
|
|
|
|
|
+ // ===== lifestyle 工具 =====
|
|
|
|
|
+ setLifestyle: function(key, val) {
|
|
|
|
|
+ this.$set(this.lifestyle, key, this.lifestyle[key] === val ? '' : val)
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleLifestyleArray: function(key, val) {
|
|
|
|
|
+ var arr = (this.lifestyle[key] || []).slice()
|
|
|
|
|
+ var idx = arr.indexOf(val)
|
|
|
|
|
+ if (idx >= 0) arr.splice(idx, 1); else arr.push(val)
|
|
|
|
|
+ this.$set(this.lifestyle, key, arr)
|
|
|
|
|
+ },
|
|
|
|
|
+ // ===== diet 工具 =====
|
|
|
|
|
+ setDietValue: function(key, val) {
|
|
|
|
|
+ this.$set(this.innerDiet, key, val)
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleDietChip: function(key, val) {
|
|
|
|
|
+ var arr = (this.innerDiet[key] || []).slice()
|
|
|
|
|
+ var idx = arr.indexOf(val)
|
|
|
|
|
+ if (idx >= 0) arr.splice(idx, 1); else arr.push(val)
|
|
|
|
|
+ this.$set(this.innerDiet, key, arr)
|
|
|
|
|
+ this.$set(this.innerDiet, 'noPreference', false)
|
|
|
|
|
+ },
|
|
|
|
|
+ setNoPreference: function() {
|
|
|
|
|
+ this.$set(this.innerDiet, 'noPreference', !this.innerDiet.noPreference)
|
|
|
|
|
+ },
|
|
|
|
|
+ onSpiceChanging: function(e) { this.$set(this.innerDiet, 'spiceLevel', e.detail.value) },
|
|
|
|
|
+ onSpiceChange: function(e) {
|
|
|
|
|
+ this.$set(this.innerDiet, 'spiceLevel', e.detail.value)
|
|
|
|
|
+ this.$set(this.innerDiet, 'noPreference', false)
|
|
|
|
|
+ },
|
|
|
|
|
+ // ===== 自定义添加 =====
|
|
|
|
|
+ openCustom: function(field) { this.customField = field; this.customInput = '' },
|
|
|
|
|
+ addCustom: function() {
|
|
|
|
|
+ var val = (this.customInput || '').trim()
|
|
|
|
|
+ if (!val) { this.customField = ''; return }
|
|
|
|
|
+ var arr = (this.innerDiet[this.customField] || []).slice()
|
|
|
|
|
+ if (arr.indexOf(val) < 0) {
|
|
|
|
|
+ arr.push(val)
|
|
|
|
|
+ this.$set(this.innerDiet, this.customField, arr)
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$set(this.innerDiet, 'noPreference', false)
|
|
|
|
|
+ this.customInput = ''
|
|
|
|
|
+ this.customField = ''
|
|
|
|
|
+ },
|
|
|
|
|
+ // ===== 体征快捷标签 =====
|
|
|
|
|
+ setHeightQuick: function(key) {
|
|
|
|
|
+ var v = { normal: '170', high: '185', low: '155' }[key]
|
|
|
|
|
+ this.$set(this.innerForm, 'heightCm', v); this.heightQuick = key
|
|
|
|
|
+ },
|
|
|
|
|
+ setWeightQuick: function(key) {
|
|
|
|
|
+ var v = { normal: '60', high: '80', low: '45' }[key]
|
|
|
|
|
+ this.$set(this.innerForm, 'weightKg', v); this.weightQuick = key
|
|
|
|
|
+ },
|
|
|
|
|
+ setBpQuick: function(key) {
|
|
|
|
|
+ var v = { normal: '120/80', high: '140/90', low: '90/60' }[key]
|
|
|
|
|
+ this.$set(this.innerForm, 'bloodPressure', v); this.bpQuick = key
|
|
|
|
|
+ },
|
|
|
|
|
+ setGlucoseQuick: function(key) {
|
|
|
|
|
+ var v = { normal: '5.0', high: '7.0', low: '3.5' }[key]
|
|
|
|
|
+ this.$set(this.innerForm, 'bloodGlucose', v); this.glucoseQuick = key
|
|
|
|
|
+ },
|
|
|
|
|
+ setLipidQuick: function(key) {
|
|
|
|
|
+ var v = { normal: '4.5', high: '6.2' }[key]
|
|
|
|
|
+ this.$set(this.innerForm, 'lipidCholesterol', v); this.lipidQuick = key
|
|
|
|
|
+ },
|
|
|
|
|
+ // ===== v-for key 方法(微信小程序 :key 不支持表达式,须用方法调用) =====
|
|
|
|
|
+ getDiseaseKey: function(gi, ii) { return 'g' + gi + '-' + ii },
|
|
|
|
|
+ getSelKey: function(si) { return 's' + si },
|
|
|
|
|
+ getMedKey: function(idx) { return 'm' + idx },
|
|
|
|
|
+ // ===== 疾病 =====
|
|
|
|
|
+ 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 ''
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleGroup: function(key) {
|
|
|
|
|
+ var idx = this.expandedGroups.indexOf(key)
|
|
|
|
|
+ if (idx >= 0) this.expandedGroups.splice(idx, 1); else this.expandedGroups.push(key)
|
|
|
|
|
+ },
|
|
|
|
|
+ isDiseaseSelected: function(name) {
|
|
|
|
|
+ return (this.innerForm.selectedDiseases || []).some(function(d) { return d.name === name })
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleDisease: function(name) {
|
|
|
|
|
+ var list = (this.innerForm.selectedDiseases || []).slice()
|
|
|
|
|
+ var idx = -1
|
|
|
|
|
+ for (var i = 0; i < list.length; i++) { if (list[i].name === name) { idx = i; break } }
|
|
|
|
|
+ if (idx >= 0) list.splice(idx, 1); else list.push({ name: name, diagnosedAt: '', custom: false })
|
|
|
|
|
+ this.$set(this.innerForm, 'selectedDiseases', list)
|
|
|
|
|
+ },
|
|
|
|
|
+ addCustomDisease: function() {
|
|
|
|
|
+ var name = (this.diseaseInput || '').trim()
|
|
|
|
|
+ if (!name) return
|
|
|
|
|
+ var list = (this.innerForm.selectedDiseases || []).slice()
|
|
|
|
|
+ if (!list.some(function(d) { return d.name === name })) {
|
|
|
|
|
+ list.push({ name: name, diagnosedAt: '', custom: true })
|
|
|
|
|
+ this.$set(this.innerForm, 'selectedDiseases', list)
|
|
|
|
|
+ }
|
|
|
|
|
+ this.diseaseInput = ''
|
|
|
|
|
+ },
|
|
|
|
|
+ removeSelected: function(idx) {
|
|
|
|
|
+ var list = (this.innerForm.selectedDiseases || []).slice()
|
|
|
|
|
+ list.splice(idx, 1)
|
|
|
|
|
+ this.$set(this.innerForm, 'selectedDiseases', list)
|
|
|
|
|
+ },
|
|
|
|
|
+ onDiagChange: function(idx, e) {
|
|
|
|
|
+ var list = (this.innerForm.selectedDiseases || []).slice()
|
|
|
|
|
+ list[idx] = Object.assign({}, list[idx], { diagnosedAt: e.detail.value })
|
|
|
|
|
+ this.$set(this.innerForm, 'selectedDiseases', list)
|
|
|
|
|
+ },
|
|
|
|
|
+ // ===== 用药 =====
|
|
|
|
|
+ toggleNoMedication: function() {
|
|
|
|
|
+ this.$set(this.innerForm, 'noMedication', !this.innerForm.noMedication)
|
|
|
|
|
+ },
|
|
|
|
|
+ addMed: function() {
|
|
|
|
|
+ var rows = (this.innerForm.medRows || []).slice()
|
|
|
|
|
+ rows.push({ name: '', startDate: '', durationText: '', time: '' })
|
|
|
|
|
+ this.$set(this.innerForm, 'medRows', rows)
|
|
|
|
|
+ },
|
|
|
|
|
+ removeMed: function(idx) {
|
|
|
|
|
+ var rows = (this.innerForm.medRows || []).slice()
|
|
|
|
|
+ rows.splice(idx, 1)
|
|
|
|
|
+ this.$set(this.innerForm, 'medRows', rows)
|
|
|
|
|
+ },
|
|
|
|
|
+ onMedStartChange: function(idx, e) {
|
|
|
|
|
+ var start = e.detail.value
|
|
|
|
|
+ var rows = (this.innerForm.medRows || []).slice()
|
|
|
|
|
+ rows[idx] = Object.assign({}, rows[idx], { startDate: start, durationText: this.calcDuration(start) })
|
|
|
|
|
+ this.$set(this.innerForm, 'medRows', rows)
|
|
|
|
|
+ },
|
|
|
|
|
+ onForgetClick: function(idx) {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '记不清开始日期',
|
|
|
|
|
+ editable: true,
|
|
|
|
|
+ placeholderText: '如:约 3 个月',
|
|
|
|
|
+ success: function(res) {
|
|
|
|
|
+ if (res.confirm && res.content) {
|
|
|
|
|
+ var rows = (self.innerForm.medRows || []).slice()
|
|
|
|
|
+ rows[idx] = Object.assign({}, rows[idx], { durationText: res.content.trim() })
|
|
|
|
|
+ self.$set(self.innerForm, 'medRows', rows)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ 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) + ' 年'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.survey-wrap { min-height: 100vh; background: linear-gradient(180deg, #FFF7ED, #FFF3E6); }
|
|
|
|
|
+.progress-bar { padding: 24rpx 32rpx 8rpx; background: #FFF7ED; }
|
|
|
|
|
+.progress-track { height: 8rpx; background: #FFE6D2; border-radius: 4rpx; overflow: hidden; }
|
|
|
|
|
+.progress-fill { height: 100%; background: #FF8C42; border-radius: 4rpx; transition: width 0.3s; }
|
|
|
|
|
+.progress-labels { display: flex; justify-content: space-between; margin-top: 12rpx; }
|
|
|
|
|
+.progress-label { font-size: 20rpx; color: #B08968; }
|
|
|
|
|
+.progress-label-active { color: #FF8C42; font-weight: 600; }
|
|
|
|
|
+.content { padding: 24rpx 32rpx 220rpx; }
|
|
|
|
|
+.step-head { display: flex; align-items: flex-start; margin-bottom: 24rpx; }
|
|
|
|
|
+.step-icon { font-size: 48rpx; margin-right: 20rpx; }
|
|
|
|
|
+.step-head-text { flex: 1; }
|
|
|
|
|
+.step-title { display: block; font-size: 36rpx; font-weight: 700; color: #4A3728; }
|
|
|
|
|
+.step-sub { display: block; font-size: 22rpx; color: #B08968; margin-top: 8rpx; line-height: 1.5; }
|
|
|
|
|
+.card { background: #FFFFFF; border-radius: 24rpx; padding: 28rpx; margin-bottom: 24rpx; box-shadow: 0 4rpx 16rpx rgba(255, 140, 66, 0.08); }
|
|
|
|
|
+.card-head { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; }
|
|
|
|
|
+.card-title { display: block; font-size: 28rpx; font-weight: 600; color: #4A3728; margin-bottom: 16rpx; }
|
|
|
|
|
+.card-head .card-title { margin-bottom: 0; }
|
|
|
|
|
+.card-hint { font-size: 22rpx; color: #B08968; margin-bottom: 16rpx; }
|
|
|
|
|
+.card-del { font-size: 24rpx; color: #E53E3E; padding: 8rpx 16rpx; }
|
|
|
|
|
+.card-value { font-size: 26rpx; color: #4A3728; }
|
|
|
|
|
+.group-arrow { font-size: 22rpx; color: #B08968; }
|
|
|
|
|
+.card-row { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 0; border-bottom: 1rpx solid #F7EFE6; }
|
|
|
|
|
+.card-row:last-child { border-bottom: none; }
|
|
|
|
|
+.card-label { font-size: 26rpx; color: #6B5744; }
|
|
|
|
|
+.card-input { flex: 1; text-align: right; font-size: 28rpx; color: #4A3728; }
|
|
|
|
|
+.picker-val { font-size: 26rpx; color: #FF8C42; }
|
|
|
|
|
+.num-input { display: block; width: 100%; background: #FFF3E6; border-radius: 16rpx; padding: 16rpx 20rpx; font-size: 28rpx; color: #4A3728; margin-top: 16rpx; }
|
|
|
|
|
+.unit-hint { display: block; font-size: 20rpx; color: #C4A98E; margin-top: 8rpx; }
|
|
|
|
|
+.notes-area { width: 100%; min-height: 120rpx; background: #FFF3E6; border-radius: 16rpx; padding: 16rpx 20rpx; font-size: 26rpx; color: #4A3728; margin-top: 16rpx; }
|
|
|
|
|
+.quick-chips { display: flex; gap: 12rpx; }
|
|
|
|
|
+.quick-chip { padding: 8rpx 20rpx; background: #FFF3E6; color: #6B5744; border-radius: 999rpx; font-size: 20rpx; border: 2rpx solid #FFE6D2; }
|
|
|
|
|
+.quick-chip-on { background: #FF8C42; color: #FFFFFF; border-color: #FF8C42; }
|
|
|
|
|
+.chip-row-wrap { display: flex; flex-wrap: wrap; gap: 16rpx; }
|
|
|
|
|
+.chip-item-wrap { display: flex; align-items: center; gap: 4rpx; padding: 12rpx 28rpx; background: #FFF3E6; color: #6B5744; border-radius: 999rpx; font-size: 24rpx; border: 2rpx solid transparent; }
|
|
|
|
|
+.chip-item-wrap.chip-active { background: #FF8C42; color: #FFFFFF; border-color: #FF8C42; font-weight: 600; }
|
|
|
|
|
+.risk-badge { color: #E53E3E; font-size: 20rpx; }
|
|
|
|
|
+.chip-active .risk-badge { color: #FFE6D2; }
|
|
|
|
|
+.custom-row { display: flex; align-items: center; margin-top: 16rpx; gap: 16rpx; }
|
|
|
|
|
+.custom-input { flex: 1; background: #FFF3E6; border-radius: 16rpx; padding: 16rpx 20rpx; font-size: 24rpx; color: #4A3728; }
|
|
|
|
|
+.custom-confirm { padding: 12rpx 24rpx; background: #FF8C42; color: #FFF; border-radius: 16rpx; font-size: 24rpx; }
|
|
|
|
|
+.custom-add { margin-top: 16rpx; font-size: 24rpx; color: #FF8C42; }
|
|
|
|
|
+.struct-row { margin-bottom: 24rpx; }
|
|
|
|
|
+.struct-row:last-child { margin-bottom: 0; }
|
|
|
|
|
+.struct-label { display: block; font-size: 24rpx; color: #6B5744; margin-bottom: 12rpx; }
|
|
|
|
|
+.slider-row { display: flex; align-items: center; gap: 24rpx; }
|
|
|
|
|
+.sel-row { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 0; border-bottom: 1rpx solid #F7EFE6; }
|
|
|
|
|
+.sel-row:last-child { border-bottom: none; }
|
|
|
|
|
+.sel-name { flex: 1; font-size: 26rpx; color: #4A3728; }
|
|
|
|
|
+.sel-diag { font-size: 24rpx; color: #FF8C42; padding: 8rpx 20rpx; background: #FFF3E6; border-radius: 999rpx; margin: 0 16rpx; }
|
|
|
|
|
+.sel-del { font-size: 32rpx; color: #B08968; padding: 0 12rpx; }
|
|
|
|
|
+.link-forget { font-size: 24rpx; color: #FF8C42; padding: 8rpx 16rpx; }
|
|
|
|
|
+.pref-none-row { margin-bottom: 24rpx; }
|
|
|
|
|
+.pref-none-btn { width: 100%; background: #FFF3E6; color: #6B5744; border-radius: 999rpx; font-size: 26rpx; padding: 16rpx; border: 2rpx solid #FFD4B8; }
|
|
|
|
|
+.pref-none-btn-active { background: #FF8C42; color: #FFF; border-color: #FF8C42; }
|
|
|
|
|
+.add-row { display: flex; align-items: center; justify-content: center; padding: 24rpx; border: 2rpx dashed #FFD4B8; border-radius: 24rpx; color: #FF8C42; font-size: 26rpx; background: #FFFFFF; }
|
|
|
|
|
+.add-icon { margin-right: 8rpx; }
|
|
|
|
|
+.footer-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom)); background: #FFFFFF; box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06); display: flex; gap: 20rpx; }
|
|
|
|
|
+.btn-prev, .btn-next, .btn-done { flex: 1; border-radius: 999rpx; font-size: 28rpx; font-weight: 600; padding: 20rpx 0; background: #FFF3E6; color: #6B5744; line-height: 1.6; }
|
|
|
|
|
+.btn-next, .btn-done { background: #FF8C42; color: #FFFFFF; }
|
|
|
|
|
+.btn-done[disabled] { opacity: 0.6; }
|
|
|
|
|
+</style>
|