var tcm = require('../../../../utils/tcm.js') var compliance = require('../../../../utils/compliance.js') var EXAMPLE_IMAGES = { tongue: 'https://cfohow.hiwavo.com/fohow-app/xcx/tcm/sheup.png', tongueBottom: 'https://cfohow.hiwavo.com/fohow-app/xcx/tcm/shedown.png', face: 'https://cfohow.hiwavo.com/fohow-app/xcx/tcm/face.png' } var USER_OPTION_LABELS = { country: { china: '中国', russia: '俄罗斯', kazakhstan: '哈萨克斯坦', mongolia: '蒙古', japan: '日本', korea: '韩国', usa: '美国', uk: '英国', germany: '德国', france: '法国', other: '其他' }, race: { asian: '亚裔', caucasian: '白人', african: '非裔', hispanic: '拉丁裔', mixed: '混血', other: '其他' }, occupation: { sedentary_office: '久坐办公(文员/行政/财务等)', it_programmer: 'IT/程序员', driver: '司机(出租车/货运/公交等)', teacher: '教师/培训师', medical_staff: '健康护理人员', service_industry: '服务行业(餐饮/零售/酒店等)', heavy_labor: '重体力劳动(建筑/搬运/制造等)', outdoor_worker: '户外工作者(农业/快递/外卖等)', student: '学生', homemaker: '家庭主妇/夫', retired: '退休人员', other: '其他' } } Page({ data: { currentStep: 0, loadingUsers: true, loadingTips: true, loadingQuestions: false, submitting: false, showUserPopup: false, showExamplePopup: false, exampleTitle: '', exampleImage: '', currentUser: null, scaleUsers: [], photoTipsTitle: '拍摄注意事项', photoTips: [ '自然光或均匀灯光下拍摄,避免逆光、偏暗与强曝光。', '保持镜头稳定,对焦清晰,确保舌面纹理可见。', '舌下与面部照片请正对镜头,避免大角度侧拍。' ], uploadCards: [ { key: 'tongue', title: '舌面', desc: '请伸出舌头,拍摄舌面正面照,需清晰展示舌面颜色和纹理', required: true, localPath: '', url: '', uploading: false }, { key: 'tongueBottom', title: '舌下', desc: '请翘起舌头,拍摄舌底照片,需清晰展示舌下纹理', required: true, localPath: '', url: '', uploading: false }, { key: 'face', title: '面部', desc: '请正对镜头拍摄面部照片,需清晰展示面部整体状态', required: true, localPath: '', url: '', uploading: false }, { key: 'body', title: '全身', desc: '请拍摄全身正面照片,用于辅助体态与整体状态分析,可选上传', required: false, localPath: '', url: '', uploading: false } ], questionList: [], extraNote: '' }, onLoad: function () { this.loadBaseData() }, onShow: function () { if (wx.getStorageSync('tcmUserChanged')) { wx.removeStorageSync('tcmUserChanged') this.loadUsers() } }, onUnload: function () { wx.removeStorageSync('tcmEditScaleUser') }, loadBaseData: function () { this.loadUsers() this.loadPhotoTips() }, loadUsers: function () { var that = this that.setData({ loadingUsers: true }) tcm.getScaleUsers().then(function (list) { var scaleUsers = that.decorateScaleUsers(Array.isArray(list) ? list : []) var currentUser = null if (scaleUsers.length > 0) { currentUser = scaleUsers[0] for (var i = 0; i < scaleUsers.length; i++) { if (scaleUsers[i].isDefault === true || scaleUsers[i].isDefault === 1) { currentUser = scaleUsers[i] break } if (!currentUser && scaleUsers[i].active === true) { currentUser = scaleUsers[i] } } } that.setData({ scaleUsers: scaleUsers, currentUser: currentUser, loadingUsers: false }) }).catch(function () { that.setData({ scaleUsers: [], currentUser: null, loadingUsers: false }) wx.showToast({ title: '用户列表加载失败', icon: 'none' }) }) }, loadPhotoTips: function () { var that = this tcm.getPhotoTips().then(function (data) { if (!data) { return } that.setData({ photoTipsTitle: data.title || '拍摄注意事项', photoTips: data.tips && data.tips.length ? data.tips : that.data.photoTips, loadingTips: false }) }).catch(function () { that.setData({ loadingTips: false }) }) }, decorateScaleUsers: function (list) { var nextList = [] for (var i = 0; i < list.length; i++) { nextList.push(this.decorateScaleUser(list[i])) } return nextList }, decorateScaleUser: function (user) { if (!user) { return user } var detailTags = [] if (user.country) { detailTags.push(this.getUserOptionLabel('country', user.country)) } if (user.race) { detailTags.push(this.getUserOptionLabel('race', user.race)) } if (user.occupation) { detailTags.push(this.getUserOptionLabel('occupation', user.occupation)) } user.detailTags = detailTags return user }, getUserOptionLabel: function (group, value) { if (!value || !USER_OPTION_LABELS[group]) { return '' } return USER_OPTION_LABELS[group][value] || value }, openUserPopup: function () { this.setData({ showUserPopup: true }) }, closeUserPopup: function () { this.setData({ showUserPopup: false }) }, selectUser: function (e) { var user = this.decorateScaleUser(e.currentTarget.dataset.user) this.setData({ currentUser: user, showUserPopup: false }) if (user && user.ID) { tcm.setDefaultScaleUser(user.ID) } }, goUserForm: function () { this.closeUserPopup() wx.navigateTo({ url: '/packageWellness/pages/tcm/user-form/user-form' }) }, editUser: function (e) { var user = e.currentTarget.dataset.user wx.setStorageSync('tcmEditScaleUser', user) this.closeUserPopup() wx.navigateTo({ url: '/packageWellness/pages/tcm/user-form/user-form?mode=edit' }) }, deleteUser: function (e) { var that = this var user = e.currentTarget.dataset.user if (!user || !user.ID) { return } wx.showModal({ title: '删除成员', content: '确认删除“' + user.nickName + '”吗?删除后该成员的康养评估入口将不可用。', confirmColor: '#e35b4e', success: function (res) { if (!res.confirm) { return } tcm.deleteScaleUser(user.ID).then(function () { wx.showToast({ title: '删除成功', icon: 'success' }) if (that.data.currentUser && that.data.currentUser.ID === user.ID) { that.setData({ currentUser: null }) } that.loadUsers() that.closeUserPopup() }).catch(function (err) { wx.showToast({ title: err && err.message ? err.message : '删除失败', icon: 'none' }) }) } }) }, viewRecords: function () { if (!this.data.currentUser || !this.data.currentUser.ID) { wx.showToast({ title: '请先选择成员', icon: 'none' }) return } wx.navigateTo({ url: '/packageWellness/pages/tcm/report/report?scaleUserId=' + this.data.currentUser.ID }) }, chooseImageSource: function (e) { var kind = e.currentTarget.dataset.kind var that = this wx.showActionSheet({ itemList: ['拍照', '从相册选择'], success: function (res) { var sourceType = res.tapIndex === 0 ? ['camera'] : ['album'] wx.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: sourceType, success: function (chooseRes) { var tempPath = chooseRes.tempFilePaths[0] that.uploadImage(kind, tempPath) } }) } }) }, uploadImage: function (kind, tempPath) { var that = this var cards = this.data.uploadCards.slice() var cardIndex = this.findUploadCardIndex(kind) if (cardIndex < 0) { return } cards[cardIndex].localPath = tempPath cards[cardIndex].uploading = true cards[cardIndex].url = '' that.setData({ uploadCards: cards }) tcm.uploadObservationImage(tempPath, kind).then(function (data) { var nextCards = that.data.uploadCards.slice() nextCards[cardIndex].uploading = false nextCards[cardIndex].url = data.url || data.urlOss || '' that.setData({ uploadCards: nextCards }) wx.showToast({ title: '上传成功', icon: 'success' }) }).catch(function (err) { var failCards = that.data.uploadCards.slice() failCards[cardIndex].uploading = false failCards[cardIndex].localPath = '' failCards[cardIndex].url = '' that.setData({ uploadCards: failCards }) wx.showToast({ title: err && err.message ? err.message : '上传失败', icon: 'none', duration: 3000 }) }) }, findUploadCardIndex: function (kind) { var cards = this.data.uploadCards for (var i = 0; i < cards.length; i++) { if (cards[i].key === kind) { return i } } return -1 }, openExample: function (e) { var kind = e.currentTarget.dataset.kind var title = e.currentTarget.dataset.title this.setData({ showExamplePopup: true, exampleTitle: title, exampleImage: EXAMPLE_IMAGES[kind] || '' }) }, closeExample: function () { this.setData({ showExamplePopup: false }) }, noop: function () {}, previewCurrentImage: function (e) { var src = e.currentTarget.dataset.src if (!src) { return } wx.previewImage({ current: src, urls: [src] }) }, goBackStep: function () { if (this.data.currentStep === 1) { this.setData({ currentStep: 0 }) return } wx.navigateBack() }, goQuestionsStep: function () { if (!this.data.currentUser || !this.data.currentUser.ID) { wx.showToast({ title: '请先选择成员', icon: 'none' }) return } var missing = [] for (var i = 0; i < this.data.uploadCards.length; i++) { if (this.data.uploadCards[i].required && !this.data.uploadCards[i].url) { missing.push(this.data.uploadCards[i].title) } } if (missing.length) { wx.showToast({ title: '请先上传' + missing.join('、'), icon: 'none' }) return } this.setData({ currentStep: 1 }) if (!this.data.questionList.length) { this.loadQuestions() } }, loadQuestions: function () { var that = this that.setData({ loadingQuestions: true }) tcm.getQuestions().then(function (questions) { var list = Array.isArray(questions) ? questions : [] var nextList = [] for (var i = 0; i < list.length; i++) { var item = list[i] nextList.push({ key: item.key || '', title: compliance.sanitizeText(item.title || ''), hint: compliance.sanitizeText(item.hint || ''), required: item.required === true, multiSelect: item.multiSelect === true, exclusivePairs: item.exclusivePairs || [], showCustomInput: false, customText: '', options: (item.options || []).map(function (opt) { return { value: opt.value || '', label: compliance.sanitizeText(opt.label || ''), isCustom: opt.isCustom === true, placeholder: compliance.sanitizeText(opt.placeholder || ''), description: compliance.sanitizeText(opt.description || ''), isExclusive: opt.isExclusive === true, selected: false, disabled: false } }) }) } that.setData({ questionList: nextList, loadingQuestions: false }) }).catch(function () { that.setData({ loadingQuestions: false }) wx.showToast({ title: '健康问答题目加载失败', icon: 'none' }) }) }, toggleOption: function (e) { var qIndex = e.currentTarget.dataset.qindex var oIndex = e.currentTarget.dataset.oindex var list = this.data.questionList.slice() var question = JSON.parse(JSON.stringify(list[qIndex])) var option = question.options[oIndex] if (option.disabled) { return } if (option.isCustom) { question.showCustomInput = !question.showCustomInput option.selected = question.showCustomInput if (!question.showCustomInput) { question.customText = '' } } else if (option.isExclusive) { var shouldSelectExclusive = !option.selected for (var i = 0; i < question.options.length; i++) { if (!question.options[i].isCustom) { question.options[i].selected = false } } option.selected = shouldSelectExclusive question.showCustomInput = false question.customText = '' for (var a = 0; a < question.options.length; a++) { if (question.options[a].isCustom) { question.options[a].selected = false } } } else { for (var b = 0; b < question.options.length; b++) { if (question.options[b].isExclusive) { question.options[b].selected = false } } if (!question.multiSelect) { for (var c = 0; c < question.options.length; c++) { if (!question.options[c].isCustom) { question.options[c].selected = false } } option.selected = true } else { option.selected = !option.selected } if (option.selected) { for (var d = 0; d < question.options.length; d++) { if (!question.options[d].selected || question.options[d].value === option.value) { continue } if (this.isMutuallyExclusive(question, option.value, question.options[d].value)) { question.options[d].selected = false } } } } question = this.refreshQuestionDisabledState(question) list[qIndex] = question this.setData({ questionList: list }) }, refreshQuestionDisabledState: function (question) { var selectedValues = [] for (var i = 0; i < question.options.length; i++) { question.options[i].disabled = false if (question.options[i].selected && !question.options[i].isCustom) { selectedValues.push(question.options[i].value) } } for (var j = 0; j < selectedValues.length; j++) { var selectedValue = selectedValues[j] var selectedOption = null for (var m = 0; m < question.options.length; m++) { if (question.options[m].value === selectedValue) { selectedOption = question.options[m] break } } if (selectedOption && selectedOption.isExclusive) { for (var n = 0; n < question.options.length; n++) { if (!question.options[n].isCustom && question.options[n].value !== selectedValue) { question.options[n].disabled = true } } } for (var p = 0; p < question.options.length; p++) { if (question.options[p].selected || question.options[p].isCustom) { continue } if (this.isMutuallyExclusive(question, selectedValue, question.options[p].value)) { question.options[p].disabled = true } } } return question }, isMutuallyExclusive: function (question, value1, value2) { var pairs = question.exclusivePairs || [] for (var i = 0; i < pairs.length; i++) { if (pairs[i].length < 2) { continue } if ((pairs[i][0] === value1 && pairs[i][1] === value2) || (pairs[i][0] === value2 && pairs[i][1] === value1)) { return true } } return false }, updateCustomText: function (e) { var qIndex = e.currentTarget.dataset.qindex var list = this.data.questionList.slice() list[qIndex].customText = e.detail.value this.setData({ questionList: list }) }, updateExtraNote: function (e) { this.setData({ extraNote: e.detail.value }) }, getQuestionAnswerText: function (question) { var texts = [] for (var i = 0; i < question.options.length; i++) { if (question.options[i].selected && !question.options[i].isCustom) { texts.push(question.options[i].label) } } if (question.customText) { texts.push(question.customText) } return texts.join(',') }, submitExam: function () { var that = this if (that.data.submitting) { return } for (var i = 0; i < that.data.questionList.length; i++) { var question = that.data.questionList[i] var answerText = that.getQuestionAnswerText(question) if (question.required && !answerText) { wx.showToast({ title: '请完成' + question.title, icon: 'none' }) return } } wx.showModal({ title: 'AI 健康评估知情同意', content: '本功能使用人工智能技术对舌象、面部与健康问答信息进行分析,结果仅供健康参考,不构成专业建议。', confirmText: '继续提交', confirmColor: '#eab86a', success: function (res) { if (res.confirm) { that.doSubmitExam() } } }) }, doSubmitExam: function () { var that = this var questionAnswers = {} var customAnswers = {} var observationImages = {} for (var i = 0; i < that.data.uploadCards.length; i++) { if (that.data.uploadCards[i].url) { observationImages[that.data.uploadCards[i].key] = [that.data.uploadCards[i].url] } } for (var j = 0; j < that.data.questionList.length; j++) { var question = that.data.questionList[j] var selectedValues = [] for (var k = 0; k < question.options.length; k++) { if (question.options[k].selected && !question.options[k].isCustom) { selectedValues.push(question.options[k].value) } } if (selectedValues.length) { questionAnswers[question.key] = selectedValues } if (question.customText) { customAnswers[question.key] = compliance.sanitizeText(question.customText) } } that.setData({ submitting: true }) tcm.submitExam({ scaleUserId: that.data.currentUser.ID, language: tcm.getLanguageCode(), questionAnswers: questionAnswers, customAnswers: customAnswers, observationImages: observationImages, extraNote: compliance.sanitizeText(that.data.extraNote || ''), additionalText: '' }).then(function (data) { wx.showToast({ title: '提交成功', icon: 'success' }) that.setData({ submitting: false }) setTimeout(function () { wx.redirectTo({ url: '/packageWellness/pages/tcm/report/report?examId=' + data.examId }) }, 400) }).catch(function (err) { that.setData({ submitting: false }) wx.showToast({ title: err && err.message ? err.message : '提交失败,请重试', icon: 'none', duration: 3000 }) }) } })