diagnosis.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. var tcm = require('../../../../utils/tcm.js')
  2. var compliance = require('../../../../utils/compliance.js')
  3. var EXAMPLE_IMAGES = {
  4. tongue: 'https://cfohow.hiwavo.com/fohow-app/xcx/tcm/sheup.png',
  5. tongueBottom: 'https://cfohow.hiwavo.com/fohow-app/xcx/tcm/shedown.png',
  6. face: 'https://cfohow.hiwavo.com/fohow-app/xcx/tcm/face.png'
  7. }
  8. var USER_OPTION_LABELS = {
  9. country: {
  10. china: '中国',
  11. russia: '俄罗斯',
  12. kazakhstan: '哈萨克斯坦',
  13. mongolia: '蒙古',
  14. japan: '日本',
  15. korea: '韩国',
  16. usa: '美国',
  17. uk: '英国',
  18. germany: '德国',
  19. france: '法国',
  20. other: '其他'
  21. },
  22. race: {
  23. asian: '亚裔',
  24. caucasian: '白人',
  25. african: '非裔',
  26. hispanic: '拉丁裔',
  27. mixed: '混血',
  28. other: '其他'
  29. },
  30. occupation: {
  31. sedentary_office: '久坐办公(文员/行政/财务等)',
  32. it_programmer: 'IT/程序员',
  33. driver: '司机(出租车/货运/公交等)',
  34. teacher: '教师/培训师',
  35. medical_staff: '健康护理人员',
  36. service_industry: '服务行业(餐饮/零售/酒店等)',
  37. heavy_labor: '重体力劳动(建筑/搬运/制造等)',
  38. outdoor_worker: '户外工作者(农业/快递/外卖等)',
  39. student: '学生',
  40. homemaker: '家庭主妇/夫',
  41. retired: '退休人员',
  42. other: '其他'
  43. }
  44. }
  45. Page({
  46. data: {
  47. currentStep: 0,
  48. loadingUsers: true,
  49. loadingTips: true,
  50. loadingQuestions: false,
  51. submitting: false,
  52. showUserPopup: false,
  53. showExamplePopup: false,
  54. exampleTitle: '',
  55. exampleImage: '',
  56. currentUser: null,
  57. scaleUsers: [],
  58. photoTipsTitle: '拍摄注意事项',
  59. photoTips: [
  60. '自然光或均匀灯光下拍摄,避免逆光、偏暗与强曝光。',
  61. '保持镜头稳定,对焦清晰,确保舌面纹理可见。',
  62. '舌下与面部照片请正对镜头,避免大角度侧拍。'
  63. ],
  64. uploadCards: [
  65. {
  66. key: 'tongue',
  67. title: '舌面',
  68. desc: '请伸出舌头,拍摄舌面正面照,需清晰展示舌面颜色和纹理',
  69. required: true,
  70. localPath: '',
  71. url: '',
  72. uploading: false
  73. },
  74. {
  75. key: 'tongueBottom',
  76. title: '舌下',
  77. desc: '请翘起舌头,拍摄舌底照片,需清晰展示舌下纹理',
  78. required: true,
  79. localPath: '',
  80. url: '',
  81. uploading: false
  82. },
  83. {
  84. key: 'face',
  85. title: '面部',
  86. desc: '请正对镜头拍摄面部照片,需清晰展示面部整体状态',
  87. required: true,
  88. localPath: '',
  89. url: '',
  90. uploading: false
  91. },
  92. {
  93. key: 'body',
  94. title: '全身',
  95. desc: '请拍摄全身正面照片,用于辅助体态与整体状态分析,可选上传',
  96. required: false,
  97. localPath: '',
  98. url: '',
  99. uploading: false
  100. }
  101. ],
  102. questionList: [],
  103. extraNote: ''
  104. },
  105. onLoad: function () {
  106. this.loadBaseData()
  107. },
  108. onShow: function () {
  109. if (wx.getStorageSync('tcmUserChanged')) {
  110. wx.removeStorageSync('tcmUserChanged')
  111. this.loadUsers()
  112. }
  113. },
  114. onUnload: function () {
  115. wx.removeStorageSync('tcmEditScaleUser')
  116. },
  117. loadBaseData: function () {
  118. this.loadUsers()
  119. this.loadPhotoTips()
  120. },
  121. loadUsers: function () {
  122. var that = this
  123. that.setData({
  124. loadingUsers: true
  125. })
  126. tcm.getScaleUsers().then(function (list) {
  127. var scaleUsers = that.decorateScaleUsers(Array.isArray(list) ? list : [])
  128. var currentUser = null
  129. if (scaleUsers.length > 0) {
  130. currentUser = scaleUsers[0]
  131. for (var i = 0; i < scaleUsers.length; i++) {
  132. if (scaleUsers[i].isDefault === true || scaleUsers[i].isDefault === 1) {
  133. currentUser = scaleUsers[i]
  134. break
  135. }
  136. if (!currentUser && scaleUsers[i].active === true) {
  137. currentUser = scaleUsers[i]
  138. }
  139. }
  140. }
  141. that.setData({
  142. scaleUsers: scaleUsers,
  143. currentUser: currentUser,
  144. loadingUsers: false
  145. })
  146. }).catch(function () {
  147. that.setData({
  148. scaleUsers: [],
  149. currentUser: null,
  150. loadingUsers: false
  151. })
  152. wx.showToast({
  153. title: '用户列表加载失败',
  154. icon: 'none'
  155. })
  156. })
  157. },
  158. loadPhotoTips: function () {
  159. var that = this
  160. tcm.getPhotoTips().then(function (data) {
  161. if (!data) {
  162. return
  163. }
  164. that.setData({
  165. photoTipsTitle: data.title || '拍摄注意事项',
  166. photoTips: data.tips && data.tips.length ? data.tips : that.data.photoTips,
  167. loadingTips: false
  168. })
  169. }).catch(function () {
  170. that.setData({
  171. loadingTips: false
  172. })
  173. })
  174. },
  175. decorateScaleUsers: function (list) {
  176. var nextList = []
  177. for (var i = 0; i < list.length; i++) {
  178. nextList.push(this.decorateScaleUser(list[i]))
  179. }
  180. return nextList
  181. },
  182. decorateScaleUser: function (user) {
  183. if (!user) {
  184. return user
  185. }
  186. var detailTags = []
  187. if (user.country) {
  188. detailTags.push(this.getUserOptionLabel('country', user.country))
  189. }
  190. if (user.race) {
  191. detailTags.push(this.getUserOptionLabel('race', user.race))
  192. }
  193. if (user.occupation) {
  194. detailTags.push(this.getUserOptionLabel('occupation', user.occupation))
  195. }
  196. user.detailTags = detailTags
  197. return user
  198. },
  199. getUserOptionLabel: function (group, value) {
  200. if (!value || !USER_OPTION_LABELS[group]) {
  201. return ''
  202. }
  203. return USER_OPTION_LABELS[group][value] || value
  204. },
  205. openUserPopup: function () {
  206. this.setData({
  207. showUserPopup: true
  208. })
  209. },
  210. closeUserPopup: function () {
  211. this.setData({
  212. showUserPopup: false
  213. })
  214. },
  215. selectUser: function (e) {
  216. var user = this.decorateScaleUser(e.currentTarget.dataset.user)
  217. this.setData({
  218. currentUser: user,
  219. showUserPopup: false
  220. })
  221. if (user && user.ID) {
  222. tcm.setDefaultScaleUser(user.ID)
  223. }
  224. },
  225. goUserForm: function () {
  226. this.closeUserPopup()
  227. wx.navigateTo({
  228. url: '/packageWellness/pages/tcm/user-form/user-form'
  229. })
  230. },
  231. editUser: function (e) {
  232. var user = e.currentTarget.dataset.user
  233. wx.setStorageSync('tcmEditScaleUser', user)
  234. this.closeUserPopup()
  235. wx.navigateTo({
  236. url: '/packageWellness/pages/tcm/user-form/user-form?mode=edit'
  237. })
  238. },
  239. deleteUser: function (e) {
  240. var that = this
  241. var user = e.currentTarget.dataset.user
  242. if (!user || !user.ID) {
  243. return
  244. }
  245. wx.showModal({
  246. title: '删除成员',
  247. content: '确认删除“' + user.nickName + '”吗?删除后该成员的康养评估入口将不可用。',
  248. confirmColor: '#e35b4e',
  249. success: function (res) {
  250. if (!res.confirm) {
  251. return
  252. }
  253. tcm.deleteScaleUser(user.ID).then(function () {
  254. wx.showToast({
  255. title: '删除成功',
  256. icon: 'success'
  257. })
  258. if (that.data.currentUser && that.data.currentUser.ID === user.ID) {
  259. that.setData({
  260. currentUser: null
  261. })
  262. }
  263. that.loadUsers()
  264. that.closeUserPopup()
  265. }).catch(function (err) {
  266. wx.showToast({
  267. title: err && err.message ? err.message : '删除失败',
  268. icon: 'none'
  269. })
  270. })
  271. }
  272. })
  273. },
  274. viewRecords: function () {
  275. if (!this.data.currentUser || !this.data.currentUser.ID) {
  276. wx.showToast({
  277. title: '请先选择成员',
  278. icon: 'none'
  279. })
  280. return
  281. }
  282. wx.navigateTo({
  283. url: '/packageWellness/pages/tcm/report/report?scaleUserId=' + this.data.currentUser.ID
  284. })
  285. },
  286. chooseImageSource: function (e) {
  287. var kind = e.currentTarget.dataset.kind
  288. var that = this
  289. wx.showActionSheet({
  290. itemList: ['拍照', '从相册选择'],
  291. success: function (res) {
  292. var sourceType = res.tapIndex === 0 ? ['camera'] : ['album']
  293. wx.chooseImage({
  294. count: 1,
  295. sizeType: ['compressed'],
  296. sourceType: sourceType,
  297. success: function (chooseRes) {
  298. var tempPath = chooseRes.tempFilePaths[0]
  299. that.uploadImage(kind, tempPath)
  300. }
  301. })
  302. }
  303. })
  304. },
  305. uploadImage: function (kind, tempPath) {
  306. var that = this
  307. var cards = this.data.uploadCards.slice()
  308. var cardIndex = this.findUploadCardIndex(kind)
  309. if (cardIndex < 0) {
  310. return
  311. }
  312. cards[cardIndex].localPath = tempPath
  313. cards[cardIndex].uploading = true
  314. cards[cardIndex].url = ''
  315. that.setData({
  316. uploadCards: cards
  317. })
  318. tcm.uploadObservationImage(tempPath, kind).then(function (data) {
  319. var nextCards = that.data.uploadCards.slice()
  320. nextCards[cardIndex].uploading = false
  321. nextCards[cardIndex].url = data.url || data.urlOss || ''
  322. that.setData({
  323. uploadCards: nextCards
  324. })
  325. wx.showToast({
  326. title: '上传成功',
  327. icon: 'success'
  328. })
  329. }).catch(function (err) {
  330. var failCards = that.data.uploadCards.slice()
  331. failCards[cardIndex].uploading = false
  332. failCards[cardIndex].localPath = ''
  333. failCards[cardIndex].url = ''
  334. that.setData({
  335. uploadCards: failCards
  336. })
  337. wx.showToast({
  338. title: err && err.message ? err.message : '上传失败',
  339. icon: 'none',
  340. duration: 3000
  341. })
  342. })
  343. },
  344. findUploadCardIndex: function (kind) {
  345. var cards = this.data.uploadCards
  346. for (var i = 0; i < cards.length; i++) {
  347. if (cards[i].key === kind) {
  348. return i
  349. }
  350. }
  351. return -1
  352. },
  353. openExample: function (e) {
  354. var kind = e.currentTarget.dataset.kind
  355. var title = e.currentTarget.dataset.title
  356. this.setData({
  357. showExamplePopup: true,
  358. exampleTitle: title,
  359. exampleImage: EXAMPLE_IMAGES[kind] || ''
  360. })
  361. },
  362. closeExample: function () {
  363. this.setData({
  364. showExamplePopup: false
  365. })
  366. },
  367. noop: function () {},
  368. previewCurrentImage: function (e) {
  369. var src = e.currentTarget.dataset.src
  370. if (!src) {
  371. return
  372. }
  373. wx.previewImage({
  374. current: src,
  375. urls: [src]
  376. })
  377. },
  378. goBackStep: function () {
  379. if (this.data.currentStep === 1) {
  380. this.setData({
  381. currentStep: 0
  382. })
  383. return
  384. }
  385. wx.navigateBack()
  386. },
  387. goQuestionsStep: function () {
  388. if (!this.data.currentUser || !this.data.currentUser.ID) {
  389. wx.showToast({
  390. title: '请先选择成员',
  391. icon: 'none'
  392. })
  393. return
  394. }
  395. var missing = []
  396. for (var i = 0; i < this.data.uploadCards.length; i++) {
  397. if (this.data.uploadCards[i].required && !this.data.uploadCards[i].url) {
  398. missing.push(this.data.uploadCards[i].title)
  399. }
  400. }
  401. if (missing.length) {
  402. wx.showToast({
  403. title: '请先上传' + missing.join('、'),
  404. icon: 'none'
  405. })
  406. return
  407. }
  408. this.setData({
  409. currentStep: 1
  410. })
  411. if (!this.data.questionList.length) {
  412. this.loadQuestions()
  413. }
  414. },
  415. loadQuestions: function () {
  416. var that = this
  417. that.setData({
  418. loadingQuestions: true
  419. })
  420. tcm.getQuestions().then(function (questions) {
  421. var list = Array.isArray(questions) ? questions : []
  422. var nextList = []
  423. for (var i = 0; i < list.length; i++) {
  424. var item = list[i]
  425. nextList.push({
  426. key: item.key || '',
  427. title: compliance.sanitizeText(item.title || ''),
  428. hint: compliance.sanitizeText(item.hint || ''),
  429. required: item.required === true,
  430. multiSelect: item.multiSelect === true,
  431. exclusivePairs: item.exclusivePairs || [],
  432. showCustomInput: false,
  433. customText: '',
  434. options: (item.options || []).map(function (opt) {
  435. return {
  436. value: opt.value || '',
  437. label: compliance.sanitizeText(opt.label || ''),
  438. isCustom: opt.isCustom === true,
  439. placeholder: compliance.sanitizeText(opt.placeholder || ''),
  440. description: compliance.sanitizeText(opt.description || ''),
  441. isExclusive: opt.isExclusive === true,
  442. selected: false,
  443. disabled: false
  444. }
  445. })
  446. })
  447. }
  448. that.setData({
  449. questionList: nextList,
  450. loadingQuestions: false
  451. })
  452. }).catch(function () {
  453. that.setData({
  454. loadingQuestions: false
  455. })
  456. wx.showToast({
  457. title: '健康问答题目加载失败',
  458. icon: 'none'
  459. })
  460. })
  461. },
  462. toggleOption: function (e) {
  463. var qIndex = e.currentTarget.dataset.qindex
  464. var oIndex = e.currentTarget.dataset.oindex
  465. var list = this.data.questionList.slice()
  466. var question = JSON.parse(JSON.stringify(list[qIndex]))
  467. var option = question.options[oIndex]
  468. if (option.disabled) {
  469. return
  470. }
  471. if (option.isCustom) {
  472. question.showCustomInput = !question.showCustomInput
  473. option.selected = question.showCustomInput
  474. if (!question.showCustomInput) {
  475. question.customText = ''
  476. }
  477. } else if (option.isExclusive) {
  478. var shouldSelectExclusive = !option.selected
  479. for (var i = 0; i < question.options.length; i++) {
  480. if (!question.options[i].isCustom) {
  481. question.options[i].selected = false
  482. }
  483. }
  484. option.selected = shouldSelectExclusive
  485. question.showCustomInput = false
  486. question.customText = ''
  487. for (var a = 0; a < question.options.length; a++) {
  488. if (question.options[a].isCustom) {
  489. question.options[a].selected = false
  490. }
  491. }
  492. } else {
  493. for (var b = 0; b < question.options.length; b++) {
  494. if (question.options[b].isExclusive) {
  495. question.options[b].selected = false
  496. }
  497. }
  498. if (!question.multiSelect) {
  499. for (var c = 0; c < question.options.length; c++) {
  500. if (!question.options[c].isCustom) {
  501. question.options[c].selected = false
  502. }
  503. }
  504. option.selected = true
  505. } else {
  506. option.selected = !option.selected
  507. }
  508. if (option.selected) {
  509. for (var d = 0; d < question.options.length; d++) {
  510. if (!question.options[d].selected || question.options[d].value === option.value) {
  511. continue
  512. }
  513. if (this.isMutuallyExclusive(question, option.value, question.options[d].value)) {
  514. question.options[d].selected = false
  515. }
  516. }
  517. }
  518. }
  519. question = this.refreshQuestionDisabledState(question)
  520. list[qIndex] = question
  521. this.setData({
  522. questionList: list
  523. })
  524. },
  525. refreshQuestionDisabledState: function (question) {
  526. var selectedValues = []
  527. for (var i = 0; i < question.options.length; i++) {
  528. question.options[i].disabled = false
  529. if (question.options[i].selected && !question.options[i].isCustom) {
  530. selectedValues.push(question.options[i].value)
  531. }
  532. }
  533. for (var j = 0; j < selectedValues.length; j++) {
  534. var selectedValue = selectedValues[j]
  535. var selectedOption = null
  536. for (var m = 0; m < question.options.length; m++) {
  537. if (question.options[m].value === selectedValue) {
  538. selectedOption = question.options[m]
  539. break
  540. }
  541. }
  542. if (selectedOption && selectedOption.isExclusive) {
  543. for (var n = 0; n < question.options.length; n++) {
  544. if (!question.options[n].isCustom && question.options[n].value !== selectedValue) {
  545. question.options[n].disabled = true
  546. }
  547. }
  548. }
  549. for (var p = 0; p < question.options.length; p++) {
  550. if (question.options[p].selected || question.options[p].isCustom) {
  551. continue
  552. }
  553. if (this.isMutuallyExclusive(question, selectedValue, question.options[p].value)) {
  554. question.options[p].disabled = true
  555. }
  556. }
  557. }
  558. return question
  559. },
  560. isMutuallyExclusive: function (question, value1, value2) {
  561. var pairs = question.exclusivePairs || []
  562. for (var i = 0; i < pairs.length; i++) {
  563. if (pairs[i].length < 2) {
  564. continue
  565. }
  566. if ((pairs[i][0] === value1 && pairs[i][1] === value2) ||
  567. (pairs[i][0] === value2 && pairs[i][1] === value1)) {
  568. return true
  569. }
  570. }
  571. return false
  572. },
  573. updateCustomText: function (e) {
  574. var qIndex = e.currentTarget.dataset.qindex
  575. var list = this.data.questionList.slice()
  576. list[qIndex].customText = e.detail.value
  577. this.setData({
  578. questionList: list
  579. })
  580. },
  581. updateExtraNote: function (e) {
  582. this.setData({
  583. extraNote: e.detail.value
  584. })
  585. },
  586. getQuestionAnswerText: function (question) {
  587. var texts = []
  588. for (var i = 0; i < question.options.length; i++) {
  589. if (question.options[i].selected && !question.options[i].isCustom) {
  590. texts.push(question.options[i].label)
  591. }
  592. }
  593. if (question.customText) {
  594. texts.push(question.customText)
  595. }
  596. return texts.join(',')
  597. },
  598. submitExam: function () {
  599. var that = this
  600. if (that.data.submitting) {
  601. return
  602. }
  603. for (var i = 0; i < that.data.questionList.length; i++) {
  604. var question = that.data.questionList[i]
  605. var answerText = that.getQuestionAnswerText(question)
  606. if (question.required && !answerText) {
  607. wx.showToast({
  608. title: '请完成' + question.title,
  609. icon: 'none'
  610. })
  611. return
  612. }
  613. }
  614. wx.showModal({
  615. title: 'AI 健康评估知情同意',
  616. content: '本功能使用人工智能技术对舌象、面部与健康问答信息进行分析,结果仅供健康参考,不构成专业建议。',
  617. confirmText: '继续提交',
  618. confirmColor: '#eab86a',
  619. success: function (res) {
  620. if (res.confirm) {
  621. that.doSubmitExam()
  622. }
  623. }
  624. })
  625. },
  626. doSubmitExam: function () {
  627. var that = this
  628. var questionAnswers = {}
  629. var customAnswers = {}
  630. var observationImages = {}
  631. for (var i = 0; i < that.data.uploadCards.length; i++) {
  632. if (that.data.uploadCards[i].url) {
  633. observationImages[that.data.uploadCards[i].key] = [that.data.uploadCards[i].url]
  634. }
  635. }
  636. for (var j = 0; j < that.data.questionList.length; j++) {
  637. var question = that.data.questionList[j]
  638. var selectedValues = []
  639. for (var k = 0; k < question.options.length; k++) {
  640. if (question.options[k].selected && !question.options[k].isCustom) {
  641. selectedValues.push(question.options[k].value)
  642. }
  643. }
  644. if (selectedValues.length) {
  645. questionAnswers[question.key] = selectedValues
  646. }
  647. if (question.customText) {
  648. customAnswers[question.key] = compliance.sanitizeText(question.customText)
  649. }
  650. }
  651. that.setData({
  652. submitting: true
  653. })
  654. tcm.submitExam({
  655. scaleUserId: that.data.currentUser.ID,
  656. language: tcm.getLanguageCode(),
  657. questionAnswers: questionAnswers,
  658. customAnswers: customAnswers,
  659. observationImages: observationImages,
  660. extraNote: compliance.sanitizeText(that.data.extraNote || ''),
  661. additionalText: ''
  662. }).then(function (data) {
  663. wx.showToast({
  664. title: '提交成功',
  665. icon: 'success'
  666. })
  667. that.setData({
  668. submitting: false
  669. })
  670. setTimeout(function () {
  671. wx.redirectTo({
  672. url: '/packageWellness/pages/tcm/report/report?examId=' + data.examId
  673. })
  674. }, 400)
  675. }).catch(function (err) {
  676. that.setData({
  677. submitting: false
  678. })
  679. wx.showToast({
  680. title: err && err.message ? err.message : '提交失败,请重试',
  681. icon: 'none',
  682. duration: 3000
  683. })
  684. })
  685. }
  686. })