|
|
@@ -253,6 +253,81 @@
|
|
|
<view class="btn-cancel" @tap="cancelEdit">取消</view>
|
|
|
<view class="btn-save" @tap="saveEdit">保存</view>
|
|
|
</view>
|
|
|
+ <!-- draft 模式:成员选择 -->
|
|
|
+ <view class="member-select-wrap" v-if="isDraftMode && !confirming && !parsing">
|
|
|
+ <view class="member-select-row" @tap="toggleMemberPanel">
|
|
|
+ <text class="field-label">被检测人</text>
|
|
|
+ <text class="field-value">{{ draftMemberText }}</text>
|
|
|
+ <text class="member-select-arrow">{{ selectedMemberObj ? '修改 ›' : '选择 ›' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="member-panel" v-if="showMemberPanel">
|
|
|
+ <!-- 候选匹配列表 -->
|
|
|
+ <view class="candidate-section" v-if="candidates.length > 0">
|
|
|
+ <text class="candidate-section-hint">以下成员与报告信息匹配,请选择:</text>
|
|
|
+ <view
|
|
|
+ class="candidate-item"
|
|
|
+ v-for="(cand, idx) in candidates"
|
|
|
+ :key="idx"
|
|
|
+ :class="{ 'candidate-selected': selectedMemberId === (cand.id || cand.userId) }"
|
|
|
+ @tap="selectAndCloseDraft(cand)"
|
|
|
+ >
|
|
|
+ <view class="candidate-left">
|
|
|
+ <text class="candidate-avatar">{{ (cand.nickname || cand.name || '?').substring(0, 1) }}</text>
|
|
|
+ <view class="candidate-info">
|
|
|
+ <text class="candidate-name">{{ cand.nickname || cand.name || '未知' }}</text>
|
|
|
+ <text class="candidate-detail" v-if="cand.gender || cand.age">{{ genderText(cand.gender) }}{{ cand.age ? ' · ' + cand.age + '岁' : '' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 全量成员 -->
|
|
|
+ <view class="member-list" v-if="familyMembers.length > 0">
|
|
|
+ <text class="member-list-hint">家庭成员</text>
|
|
|
+ <view
|
|
|
+ class="member-item"
|
|
|
+ v-for="(member, mIdx) in familyMembers"
|
|
|
+ :key="mIdx"
|
|
|
+ :class="{ 'member-selected': selectedMemberId === (member.userId || member.id) }"
|
|
|
+ @tap="selectAndCloseDraft(member)"
|
|
|
+ >
|
|
|
+ <text class="member-avatar">{{ (member.nickname || '?').substring(0, 1) }}</text>
|
|
|
+ <text class="member-name">{{ member.nickname || '未知' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 不绑定成员 -->
|
|
|
+ <view class="member-item unbind-member" @tap="unbindDraftMember">
|
|
|
+ <text class="member-avatar unbind-avatar">✕</text>
|
|
|
+ <text class="member-name">不绑定成员</text>
|
|
|
+ </view>
|
|
|
+ <!-- 新建成员入口 -->
|
|
|
+ <view class="create-member-entry" @tap="toggleAutoCreateDraft">
|
|
|
+ <text class="create-member-icon">+</text>
|
|
|
+ <text class="create-member-text">{{ showAutoCreateForm ? '收起新建表单' : '不在列表中?新建家庭成员' }}</text>
|
|
|
+ </view>
|
|
|
+ <!-- 新建成员表单 -->
|
|
|
+ <view class="auto-create-inline" v-if="showAutoCreateForm">
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="form-item-label">姓名</text>
|
|
|
+ <input class="form-item-input" v-model="newMember.name" placeholder="请输入姓名" />
|
|
|
+ </view>
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="form-item-label">性别</text>
|
|
|
+ <view class="gender-selector">
|
|
|
+ <text class="gender-option" :class="{ 'gender-selected': newMember.gender === 'male' }" @tap="newMember.gender = 'male'">男</text>
|
|
|
+ <text class="gender-option" :class="{ 'gender-selected': newMember.gender === 'female' }" @tap="newMember.gender = 'female'">女</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="form-item">
|
|
|
+ <text class="form-item-label">年龄</text>
|
|
|
+ <input class="form-item-input" type="number" v-model="newMember.age" placeholder="请输入年龄" />
|
|
|
+ </view>
|
|
|
+ <view class="btn-create-submit" :class="{ 'btn-disabled': creatingMember }" @tap="doAutoCreateDraft">
|
|
|
+ <text v-if="creatingMember">创建中...</text>
|
|
|
+ <text v-else>确认创建</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
<view class="draft-bar" v-if="isDraftMode && !confirming && !parsing">
|
|
|
<view class="btn-discard" @tap="doDiscard">放弃</view>
|
|
|
<view class="btn-confirm" @tap="doConfirm">确认入库</view>
|
|
|
@@ -271,7 +346,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { getReportDetail, editHealthReport, queryKnowledgeBase, queryIndicatorKnowledge, getGutFloraAnalysis, confirmReportPreview, discardReportDraft, parseReportDraft, getHealthFoods, getFoodCautionList } from '../../utils/api.js'
|
|
|
+import { getReportDetail, editHealthReport, queryKnowledgeBase, queryIndicatorKnowledge, getGutFloraAnalysis, confirmReportPreview, discardReportDraft, parseReportDraft, getHealthFoods, getFoodCautionList, getFamilyMemberList, addFamilyMember } from '../../utils/api.js'
|
|
|
import HealthKnowledgePopup from '../../components/health-knowledge-popup.vue'
|
|
|
import FeedbackPopup from '../../components/feedback-popup.vue'
|
|
|
import { parseDate } from '../../utils/format.js'
|
|
|
@@ -321,7 +396,23 @@ export default {
|
|
|
isDraftMode: false,
|
|
|
payload: null,
|
|
|
confirming: false,
|
|
|
- parsing: false
|
|
|
+ parsing: false,
|
|
|
+ // 被检测人选择(draft 模式)
|
|
|
+ needBind: false,
|
|
|
+ selectedMemberId: null,
|
|
|
+ selectedMemberObj: null,
|
|
|
+ familyMembers: [],
|
|
|
+ showMemberPanel: false,
|
|
|
+ showAutoCreateForm: false,
|
|
|
+ newMember: { name: '', gender: 'male', age: '' },
|
|
|
+ creatingMember: false,
|
|
|
+ createdMemberId: null,
|
|
|
+ matchConfidence: null,
|
|
|
+ matchLabel: '',
|
|
|
+ candidates: [],
|
|
|
+ extractedName: '',
|
|
|
+ extractedGender: '',
|
|
|
+ extractedAge: '',
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
@@ -357,6 +448,11 @@ export default {
|
|
|
if (level !== '低风险') count++
|
|
|
}
|
|
|
return count
|
|
|
+ },
|
|
|
+ draftMemberText: function() {
|
|
|
+ if (this.selectedMemberObj) return this.selectedMemberObj.nickname || this.selectedMemberObj.name || '已选择'
|
|
|
+ if (this.extractedName) return this.extractedName + '(待绑定)'
|
|
|
+ return '未选择'
|
|
|
}
|
|
|
},
|
|
|
onLoad: function(options) {
|
|
|
@@ -399,6 +495,75 @@ export default {
|
|
|
var map = { '良好': 'good', '注意': 'warning', '需关注': 'danger', '关注': 'danger', '正常': 'normal', '偏高': 'high', '偏低': 'low' }
|
|
|
return map[s] || 'normal'
|
|
|
},
|
|
|
+ genderText: function(g) {
|
|
|
+ if (g === 'male') return '男'
|
|
|
+ if (g === 'female') return '女'
|
|
|
+ return g || ''
|
|
|
+ },
|
|
|
+ toggleMemberPanel: function() {
|
|
|
+ this.showMemberPanel = !this.showMemberPanel
|
|
|
+ if (this.showMemberPanel && this.familyMembers.length === 0) {
|
|
|
+ this.loadFamilyMembers()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ loadFamilyMembers: function() {
|
|
|
+ var self = this
|
|
|
+ getFamilyMemberList({ visibleOnly: true }).then(function(res) {
|
|
|
+ if (res.code === 200) {
|
|
|
+ self.familyMembers = res.data || []
|
|
|
+ }
|
|
|
+ }).catch(function() {})
|
|
|
+ },
|
|
|
+ selectAndCloseDraft: function(member) {
|
|
|
+ this.selectedMemberId = member.id || member.userId
|
|
|
+ this.selectedMemberObj = member
|
|
|
+ this.createdMemberId = null
|
|
|
+ this.needBind = false
|
|
|
+ this.showMemberPanel = false
|
|
|
+ this.showAutoCreateForm = false
|
|
|
+ },
|
|
|
+ unbindDraftMember: function() {
|
|
|
+ this.selectedMemberId = null
|
|
|
+ this.selectedMemberObj = null
|
|
|
+ this.createdMemberId = null
|
|
|
+ this.needBind = false
|
|
|
+ this.showMemberPanel = false
|
|
|
+ this.showAutoCreateForm = false
|
|
|
+ },
|
|
|
+ toggleAutoCreateDraft: function() {
|
|
|
+ this.showAutoCreateForm = !this.showAutoCreateForm
|
|
|
+ },
|
|
|
+ doAutoCreateDraft: function() {
|
|
|
+ var self = this
|
|
|
+ if (!self.newMember.name) {
|
|
|
+ uni.showToast({ title: '请填写姓名', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ self.creatingMember = true
|
|
|
+ addFamilyMember({
|
|
|
+ name: self.newMember.name,
|
|
|
+ gender: self.newMember.gender,
|
|
|
+ age: self.newMember.age ? parseInt(self.newMember.age) : null
|
|
|
+ }).then(function(res) {
|
|
|
+ self.creatingMember = false
|
|
|
+ if (res.code === 200) {
|
|
|
+ var member = res.data || {}
|
|
|
+ self.selectedMemberId = member.id || member.userId
|
|
|
+ self.selectedMemberObj = member
|
|
|
+ self.createdMemberId = self.selectedMemberId
|
|
|
+ self.needBind = false
|
|
|
+ self.showMemberPanel = false
|
|
|
+ self.showAutoCreateForm = false
|
|
|
+ self.loadFamilyMembers()
|
|
|
+ uni.showToast({ title: '创建成功', icon: 'success' })
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '创建失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ }).catch(function() {
|
|
|
+ self.creatingMember = false
|
|
|
+ uni.showToast({ title: '创建异常', icon: 'none' })
|
|
|
+ })
|
|
|
+ },
|
|
|
loadData: function() {
|
|
|
var self = this
|
|
|
getReportDetail(this.reportId).then(function(res) {
|
|
|
@@ -470,7 +635,28 @@ export default {
|
|
|
}
|
|
|
self.payload = res.data.payload || {}
|
|
|
self.isDraftMode = true
|
|
|
+ // 填充成员匹配信息(后端解析时已按姓名/性别/年龄尝试匹配家庭成员)
|
|
|
+ self.extractedName = res.data.extractedName || ''
|
|
|
+ self.extractedGender = res.data.extractedGender || ''
|
|
|
+ self.extractedAge = res.data.extractedAge || ''
|
|
|
+ self.matchedMemberId = res.data.matchedMemberId || null
|
|
|
+ self.needBind = !!res.data.needBind
|
|
|
+ self.matchConfidence = res.data.confidence !== undefined ? parseFloat(res.data.confidence) : null
|
|
|
+ self.matchLabel = res.data.matchLabel || ''
|
|
|
+ self.candidates = res.data.candidates || []
|
|
|
+ // 高置信度自动选中
|
|
|
+ if (self.matchedMemberId && self.matchConfidence >= 0.8) {
|
|
|
+ self.selectedMemberId = self.matchedMemberId
|
|
|
+ // 在 candidates 中找对应对象
|
|
|
+ for (var i = 0; i < self.candidates.length; i++) {
|
|
|
+ if ((self.candidates[i].id || self.candidates[i].userId) === self.selectedMemberId) {
|
|
|
+ self.selectedMemberObj = self.candidates[i]
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
self.applyDraftPayload(self.payload)
|
|
|
+ self.loadFamilyMembers()
|
|
|
}).catch(function() {
|
|
|
self.parsing = false
|
|
|
uni.showToast({ title: '加载异常', icon: 'none' })
|
|
|
@@ -714,7 +900,10 @@ report.gutType = s.gutType || ''
|
|
|
var self = this
|
|
|
if (!self.draftId) return
|
|
|
self.confirming = true
|
|
|
- confirmReportPreview({ draftId: self.draftId, payload: self.payload }).then(function(res) {
|
|
|
+ // 有选中成员时传 subjectId,否则传 null:后端确认入库但不绑定任何用户/家庭,也不重算维度
|
|
|
+ var req = { draftId: self.draftId, payload: self.payload }
|
|
|
+ if (self.selectedMemberId) req.subjectId = self.selectedMemberId
|
|
|
+ confirmReportPreview(req).then(function(res) {
|
|
|
self.confirming = false
|
|
|
if (res.code === 200) {
|
|
|
var reportId = res.data && res.data.reportId
|