LifestyleStep.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <!-- DEPRECATED: 已由 ComprehensiveSurvey.vue 替代,待确认无引用后清理(2026-10-16 标记) -->
  2. <template>
  3. <view class="lifestyle-step">
  4. <view class="step-title-row">
  5. <text class="step-title">生活方式与肠道健康</text>
  6. <text class="step-sub">问卷核心:心情/睡眠/排便/抗生素/过敏,帮助精准识别肠道状态</text>
  7. </view>
  8. <!-- 心情状态 -->
  9. <view class="form-card">
  10. <view class="card-head">
  11. <text class="card-title">心情状态</text>
  12. <text class="card-hint">单选</text>
  13. </view>
  14. <view class="chip-row">
  15. <view v-for="opt in moodOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.mood === opt }" @click="setField('mood', opt)"><text>{{ opt }}</text></view>
  16. </view>
  17. </view>
  18. <!-- 睡眠 -->
  19. <view class="form-card">
  20. <view class="card-head"><text class="card-title">睡眠时长</text><text class="card-hint">单选</text></view>
  21. <view class="chip-row">
  22. <view v-for="opt in sleepOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.sleepDuration === opt }" @click="setField('sleepDuration', opt)"><text>{{ opt }}</text></view>
  23. </view>
  24. </view>
  25. <view class="form-card">
  26. <view class="card-head"><text class="card-title">入睡时间</text><text class="card-hint">单选</text></view>
  27. <view class="chip-row">
  28. <view v-for="opt in bedtimeOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.bedtime === opt }" @click="setField('bedtime', opt)"><text>{{ opt }}</text></view>
  29. </view>
  30. </view>
  31. <!-- 排便情况 -->
  32. <view class="form-card">
  33. <view class="card-head"><text class="card-title">排便频率</text><text class="card-hint">单选</text></view>
  34. <view class="chip-row">
  35. <view v-for="opt in bowelFreqOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.bowelFrequency === opt }" @click="setField('bowelFrequency', opt)"><text>{{ opt }}</text></view>
  36. </view>
  37. </view>
  38. <view class="form-card">
  39. <view class="card-head"><text class="card-title">粪便性状</text><text class="card-hint">单选</text></view>
  40. <view class="chip-row">
  41. <view v-for="opt in bowelFormOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.bowelForm === opt }" @click="setField('bowelForm', opt)"><text>{{ opt }}</text></view>
  42. </view>
  43. </view>
  44. <!-- 抗生素用药史 -->
  45. <view class="form-card">
  46. <view class="card-head"><text class="card-title">近 2 年抗生素用药史</text><text class="card-hint">单选</text></view>
  47. <view class="chip-row">
  48. <view v-for="opt in antibioticOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.antibioticHistory === opt }" @click="setField('antibioticHistory', opt)"><text>{{ opt }}</text></view>
  49. </view>
  50. </view>
  51. <!-- 过敏表现 -->
  52. <view class="form-card">
  53. <view class="card-head"><text class="card-title">过敏表现</text><text class="card-hint">多选(可多个)</text></view>
  54. <view class="chip-row-wrap">
  55. <view v-for="opt in allergyOptions" :key="opt" class="chip-item-wrap" :class="{ 'chip-active': inner.allergySymptoms && inner.allergySymptoms.indexOf(opt) >= 0 }" @click="toggleChip('allergySymptoms', opt)"><text>{{ opt }}</text></view>
  56. </view>
  57. </view>
  58. <!-- ===== 按性别动态:女性专属 ===== -->
  59. <view v-if="isFemale">
  60. <view class="form-card">
  61. <view class="card-head"><text class="card-title">月经周期</text><text class="card-hint">单选</text></view>
  62. <view class="chip-row">
  63. <view v-for="opt in menstrualOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.menstrualCycle === opt }" @click="setField('menstrualCycle', opt)"><text>{{ opt }}</text></view>
  64. </view>
  65. </view>
  66. <view class="form-card">
  67. <view class="card-head"><text class="card-title">特殊时期</text><text class="card-hint">单选</text></view>
  68. <view class="chip-row">
  69. <view v-for="opt in specialPeriodOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.specialPeriod === opt }" @click="setField('specialPeriod', opt)"><text>{{ opt }}</text></view>
  70. </view>
  71. </view>
  72. </view>
  73. <!-- ===== 按年龄动态:儿童专属 ===== -->
  74. <view v-if="isChild">
  75. <view class="form-card">
  76. <view class="card-head"><text class="card-title">生长发育</text><text class="card-hint">单选</text></view>
  77. <view class="chip-row">
  78. <view v-for="opt in growthOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.growthDevelopment === opt }" @click="setField('growthDevelopment', opt)"><text>{{ opt }}</text></view>
  79. </view>
  80. </view>
  81. <view class="form-card">
  82. <view class="card-head"><text class="card-title">挑食情况</text><text class="card-hint">单选</text></view>
  83. <view class="chip-row">
  84. <view v-for="opt in pickyEatingOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.pickyEating === opt }" @click="setField('pickyEating', opt)"><text>{{ opt }}</text></view>
  85. </view>
  86. </view>
  87. </view>
  88. <!-- ===== 按年龄动态:老年专属 ===== -->
  89. <view v-if="isElderly">
  90. <view class="form-card">
  91. <view class="card-head"><text class="card-title">骨质疏松风险</text><text class="card-hint">单选</text></view>
  92. <view class="chip-row">
  93. <view v-for="opt in osteoporosisOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.osteoporosisRisk === opt }" @click="setField('osteoporosisRisk', opt)"><text>{{ opt }}</text></view>
  94. </view>
  95. </view>
  96. <view class="form-card">
  97. <view class="card-head"><text class="card-title">跌倒风险</text><text class="card-hint">单选</text></view>
  98. <view class="chip-row">
  99. <view v-for="opt in fallRiskOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.fallRisk === opt }" @click="setField('fallRisk', opt)"><text>{{ opt }}</text></view>
  100. </view>
  101. </view>
  102. </view>
  103. <!-- ===== 按年龄动态:青少年专属 ===== -->
  104. <view v-if="isTeenager">
  105. <view class="form-card">
  106. <view class="card-head"><text class="card-title">屏幕时间</text><text class="card-hint">单选</text></view>
  107. <view class="chip-row">
  108. <view v-for="opt in screenTimeOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.screenTime === opt }" @click="setField('screenTime', opt)"><text>{{ opt }}</text></view>
  109. </view>
  110. </view>
  111. <view class="form-card">
  112. <view class="card-head"><text class="card-title">情绪状态</text><text class="card-hint">单选</text></view>
  113. <view class="chip-row">
  114. <view v-for="opt in teenMoodOptions" :key="opt" class="chip-item" :class="{ 'chip-active': inner.teenMood === opt }" @click="setField('teenMood', opt)"><text>{{ opt }}</text></view>
  115. </view>
  116. </view>
  117. </view>
  118. </view>
  119. </template>
  120. <script>
  121. export default {
  122. name: 'LifestyleStep',
  123. props: {
  124. value: { type: Object, default: function() { return {} } },
  125. profile: { type: Object, default: function() { return {} } }
  126. },
  127. data: function() {
  128. return {
  129. inner: {
  130. mood: '', sleepDuration: '', bedtime: '', bowelFrequency: '', bowelForm: '', antibioticHistory: '', allergySymptoms: [],
  131. menstrualCycle: '', specialPeriod: '', growthDevelopment: '', pickyEating: '', osteoporosisRisk: '', fallRisk: '', screenTime: '', teenMood: ''
  132. }
  133. }
  134. },
  135. computed: {
  136. isFemale: function() {
  137. var g = this.profile && this.profile.gender
  138. return g === 'female' || g === '女'
  139. },
  140. isChild: function() {
  141. var age = this.profile && this.profile.age
  142. return age != null && Number(age) > 0 && Number(age) < 12
  143. },
  144. isTeenager: function() {
  145. var age = this.profile && this.profile.age
  146. return age != null && Number(age) >= 12 && Number(age) < 18
  147. },
  148. isElderly: function() {
  149. var age = this.profile && this.profile.age
  150. return age != null && Number(age) >= 60
  151. },
  152. moodOptions: function() { return ['良好', '一般', '低落', '焦虑'] },
  153. sleepOptions: function() { return ['<6小时', '6-7小时', '7-8小时', '>8小时'] },
  154. bedtimeOptions: function() { return ['22:00前', '22:00-24:00', '24:00后'] },
  155. bowelFreqOptions: function() { return ['每天1次(正常)', '隔天1次', '每周2-3次', '每周<2次'] },
  156. bowelFormOptions: function() { return ['干硬颗粒', '条状干硬', '正常香蕉状', '软便', '水样'] },
  157. antibioticOptions: function() { return ['无', '曾使用过(未用完疗程)', '按疗程使用完', '频繁使用(近2年≥3次)'] },
  158. allergyOptions: function() { return ['无', '反复皮疹', '鼻痒/打喷嚏', '眼痒/流泪', '哮喘样症状', '食物过敏'] },
  159. menstrualOptions: function() { return ['规律(28-32天)', '不规律', '已停经', '尚未开始'] },
  160. specialPeriodOptions: function() { return ['无', '备孕中', '怀孕', '哺乳期'] },
  161. growthOptions: function() { return ['正常(在生长曲线内)', '偏慢', '偏快', '不清楚'] },
  162. pickyEatingOptions: function() { return ['无挑食', '轻微挑食', '明显挑食', '严重挑食'] },
  163. osteoporosisOptions: function() { return ['无风险', '有风险(曾骨折/骨密度低)', '不清楚'] },
  164. fallRiskOptions: function() { return ['近1年无跌倒', '近1年跌倒1次', '近1年跌倒2次及以上'] },
  165. screenTimeOptions: function() { return ['<1小时/天', '1-3小时/天', '3-5小时/天', '>5小时/天'] },
  166. teenMoodOptions: function() { return ['开朗', '一般', '易怒', '低落'] }
  167. },
  168. watch: {
  169. value: { deep: true, handler: function(val) { this.syncFromValue(val) } },
  170. profile: { deep: true, handler: function(val) { this.syncProfile(val) } }
  171. },
  172. created: function() {
  173. this.syncFromValue(this.value)
  174. this.syncProfile(this.profile)
  175. },
  176. methods: {
  177. // 切换成员/更新 profile 时:仅清除当前性别/年龄不再适用的专属字段,
  178. // 保留适用字段的已存答案(先由 syncFromValue 回填,这里只做减法)
  179. syncProfile: function(p) {
  180. if (!p) return
  181. var g = p.gender
  182. var age = p.age != null ? Number(p.age) : null
  183. var isFemale = g === 'female' || g === '女'
  184. var isChild = age != null && age > 0 && age < 12
  185. var isTeen = age != null && age >= 12 && age < 18
  186. var isElderly = age != null && age >= 60
  187. if (!isFemale) {
  188. this.inner.menstrualCycle = ''
  189. this.inner.specialPeriod = ''
  190. }
  191. if (!isChild) {
  192. this.inner.growthDevelopment = ''
  193. this.inner.pickyEating = ''
  194. }
  195. if (!isTeen) {
  196. this.inner.screenTime = ''
  197. this.inner.teenMood = ''
  198. }
  199. if (!isElderly) {
  200. this.inner.osteoporosisRisk = ''
  201. this.inner.fallRisk = ''
  202. }
  203. },
  204. syncFromValue: function(val) {
  205. if (!val) return
  206. this.inner.mood = val.mood || ''
  207. this.inner.sleepDuration = val.sleepDuration || ''
  208. this.inner.bedtime = val.bedtime || ''
  209. this.inner.bowelFrequency = val.bowelFrequency || ''
  210. this.inner.bowelForm = val.bowelForm || ''
  211. this.inner.antibioticHistory = val.antibioticHistory || ''
  212. this.inner.allergySymptoms = Array.isArray(val.allergySymptoms) ? val.allergySymptoms.slice() : []
  213. // 兼容:按成员加载时若已有答案则回填(不在此处重置)
  214. this.inner.menstrualCycle = val.menstrualCycle || ''
  215. this.inner.specialPeriod = val.specialPeriod || ''
  216. this.inner.growthDevelopment = val.growthDevelopment || ''
  217. this.inner.pickyEating = val.pickyEating || ''
  218. this.inner.osteoporosisRisk = val.osteoporosisRisk || ''
  219. this.inner.fallRisk = val.fallRisk || ''
  220. this.inner.screenTime = val.screenTime || ''
  221. this.inner.teenMood = val.teenMood || ''
  222. },
  223. setField: function(field, value) {
  224. this.inner[field] = value
  225. this.emitInput()
  226. },
  227. toggleChip: function(field, value) {
  228. var list = this.inner[field] || []
  229. var idx = list.indexOf(value)
  230. if (idx >= 0) { this.inner[field] = list.slice(0, idx).concat(list.slice(idx + 1)) } else { this.inner[field] = list.concat([value]) }
  231. this.emitInput()
  232. },
  233. emitInput: function() {
  234. this.$emit('input', {
  235. mood: this.inner.mood,
  236. sleepDuration: this.inner.sleepDuration,
  237. bedtime: this.inner.bedtime,
  238. bowelFrequency: this.inner.bowelFrequency,
  239. bowelForm: this.inner.bowelForm,
  240. antibioticHistory: this.inner.antibioticHistory,
  241. allergySymptoms: (this.inner.allergySymptoms || []).slice(),
  242. menstrualCycle: this.inner.menstrualCycle,
  243. specialPeriod: this.inner.specialPeriod,
  244. growthDevelopment: this.inner.growthDevelopment,
  245. pickyEating: this.inner.pickyEating,
  246. osteoporosisRisk: this.inner.osteoporosisRisk,
  247. fallRisk: this.inner.fallRisk,
  248. screenTime: this.inner.screenTime,
  249. teenMood: this.inner.teenMood
  250. })
  251. }
  252. }
  253. }
  254. </script>
  255. <style scoped>
  256. .lifestyle-step { padding: 10rpx 0 30rpx; }
  257. .step-title-row { margin-bottom: 20rpx; }
  258. .step-title { font-size: 34rpx; font-weight: 700; color: #333; }
  259. .step-sub { display: block; font-size: 24rpx; color: #999; margin-top: 8rpx; }
  260. .form-card { background: #fff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 20rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04); }
  261. .card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16rpx; }
  262. .card-title { font-size: 28rpx; font-weight: 600; color: #333; }
  263. .card-hint { font-size: 22rpx; color: #94A3B8; }
  264. .chip-row { display: flex; flex-wrap: wrap; }
  265. .chip-row-wrap { display: flex; flex-wrap: wrap; }
  266. .chip-item, .chip-item-wrap { padding: 12rpx 28rpx; border-radius: 999rpx; background: #F1F5F9; font-size: 24rpx; color: #334155; margin: 0 16rpx 14rpx 0; }
  267. .chip-active { background: #FFF3E0; color: #F97316; font-weight: 600; }
  268. </style>