|
@@ -27,10 +27,83 @@
|
|
|
<!-- 基本信息 -->
|
|
<!-- 基本信息 -->
|
|
|
<view class="card">
|
|
<view class="card">
|
|
|
<view class="card-title">基本信息</view>
|
|
<view class="card-title">基本信息</view>
|
|
|
- <view class="field-row">
|
|
|
|
|
|
|
+ <view class="field-row member-select-row" @click="toggleMemberPanel">
|
|
|
<text class="field-label">被检测人</text>
|
|
<text class="field-label">被检测人</text>
|
|
|
- <text class="field-value" v-if="!needBind">{{ extractedName || '未识别' }}</text>
|
|
|
|
|
- <text class="field-value binding-required" v-else>需手动绑定</text>
|
|
|
|
|
|
|
+ <text class="field-value" :class="{ 'binding-required': needBind && !selectedMemberObj }">{{ memberDisplayText }}</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) }"
|
|
|
|
|
+ @click="selectAndClose(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>
|
|
|
|
|
+ <text class="candidate-fields" v-if="cand.matchFields">匹配: {{ cand.matchFields }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="candidate-right">
|
|
|
|
|
+ <view class="score-bar-track">
|
|
|
|
|
+ <view class="score-bar-fill" :style="'width:' + (cand.similarityScore * 100) + '%'"></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="score-text">{{ Math.round(cand.similarityScore * 100) }}%</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 全量家庭成员列表(后备) -->
|
|
|
|
|
+ <view class="member-list" v-if="familyMembers.length > 0 && (candidates.length === 0 || !showCandidatesOnly)">
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="member-item"
|
|
|
|
|
+ v-for="(member, mIdx) in familyMembers"
|
|
|
|
|
+ :key="mIdx"
|
|
|
|
|
+ :class="{ 'member-selected': selectedMemberId === (member.userId || member.id) }"
|
|
|
|
|
+ @click="selectAndClose(member)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="member-avatar">{{ (member.nickname || '?').substring(0, 1) }}</text>
|
|
|
|
|
+ <text class="member-name">{{ member.nickname || '未知' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 新建成员入口 -->
|
|
|
|
|
+ <view class="create-member-entry" @click="toggleAutoCreate">
|
|
|
|
|
+ <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' }" @click="newMember.gender = 'male'">男</text>
|
|
|
|
|
+ <text class="gender-option" :class="{ 'gender-selected': newMember.gender === 'female' }" @click="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 }" @click="doAutoCreate">
|
|
|
|
|
+ <text v-if="creatingMember">创建中...</text>
|
|
|
|
|
+ <text v-else>确认创建</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
<view class="field-row" v-if="extractedGender">
|
|
<view class="field-row" v-if="extractedGender">
|
|
|
<text class="field-label">性别</text>
|
|
<text class="field-label">性别</text>
|
|
@@ -54,82 +127,6 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 家庭成员绑定(含候选推荐) -->
|
|
|
|
|
- <view class="card" v-if="needBind">
|
|
|
|
|
- <view class="card-title">选择家庭成员</view>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 候选匹配列表(按置信度排序) -->
|
|
|
|
|
- <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) }"
|
|
|
|
|
- @click="selectCandidate(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>
|
|
|
|
|
- <text class="candidate-fields" v-if="cand.matchFields">匹配: {{ cand.matchFields }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view class="candidate-right">
|
|
|
|
|
- <view class="score-bar-track">
|
|
|
|
|
- <view class="score-bar-fill" :style="'width:' + (cand.similarityScore * 100) + '%'"></view>
|
|
|
|
|
- </view>
|
|
|
|
|
- <text class="score-text">{{ Math.round(cand.similarityScore * 100) }}%</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 全量家庭成员列表(后备) -->
|
|
|
|
|
- <view class="member-list" v-if="familyMembers.length > 0 && (candidates.length === 0 || !showCandidatesOnly)">
|
|
|
|
|
- <view
|
|
|
|
|
- class="member-item"
|
|
|
|
|
- v-for="(member, mIdx) in familyMembers"
|
|
|
|
|
- :key="mIdx"
|
|
|
|
|
- :class="{ 'member-selected': selectedMemberId === (member.userId || member.id) }"
|
|
|
|
|
- @click="selectMember(member)"
|
|
|
|
|
- >
|
|
|
|
|
- <text class="member-avatar">{{ (member.nickname || '?').substring(0, 1) }}</text>
|
|
|
|
|
- <text class="member-name">{{ member.nickname || '未知' }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 新建成员入口 -->
|
|
|
|
|
- <view class="create-member-entry" @click="toggleAutoCreate">
|
|
|
|
|
- <text class="create-member-icon">+</text>
|
|
|
|
|
- <text class="create-member-text">{{ showAutoCreateForm ? '收起新建表单' : '不在列表中?新建家庭成员' }}</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 新建家庭成员表单 -->
|
|
|
|
|
- <view class="card auto-create-card" v-if="showAutoCreateForm">
|
|
|
|
|
- <view class="card-title">新建家庭成员</view>
|
|
|
|
|
- <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' }" @click="newMember.gender = 'male'">男</text>
|
|
|
|
|
- <text class="gender-option" :class="{ 'gender-selected': newMember.gender === 'female' }" @click="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 }" @click="doAutoCreate">
|
|
|
|
|
- <text v-if="creatingMember">创建中...</text>
|
|
|
|
|
- <text v-else>确认创建</text>
|
|
|
|
|
- </view>
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
<!-- 舌诊指标(只读展示) -->
|
|
<!-- 舌诊指标(只读展示) -->
|
|
|
<view class="card" v-if="payload.type === 'tongue' && payload.indicators && payload.indicators.length">
|
|
<view class="card" v-if="payload.type === 'tongue' && payload.indicators && payload.indicators.length">
|
|
|
<view class="card-title">舌象指标</view>
|
|
<view class="card-title">舌象指标</view>
|
|
@@ -306,6 +303,9 @@ export default {
|
|
|
matchLabel: '',
|
|
matchLabel: '',
|
|
|
candidates: [],
|
|
candidates: [],
|
|
|
showCandidatesOnly: false,
|
|
showCandidatesOnly: false,
|
|
|
|
|
+ // 被检测人选择面板
|
|
|
|
|
+ showMemberPanel: false,
|
|
|
|
|
+ selectedMemberObj: null,
|
|
|
// 新建成员
|
|
// 新建成员
|
|
|
showAutoCreateForm: false,
|
|
showAutoCreateForm: false,
|
|
|
newMember: {
|
|
newMember: {
|
|
@@ -350,6 +350,12 @@ export default {
|
|
|
if (this.matchLabel === 'no_match') return '无法匹配到现有家庭成员,可新建成员'
|
|
if (this.matchLabel === 'no_match') return '无法匹配到现有家庭成员,可新建成员'
|
|
|
return ''
|
|
return ''
|
|
|
},
|
|
},
|
|
|
|
|
+ memberDisplayText() {
|
|
|
|
|
+ var m = this.selectedMemberObj
|
|
|
|
|
+ if (m && (m.nickname || m.name)) return m.nickname || m.name
|
|
|
|
|
+ if (this.needBind) return '需手动绑定'
|
|
|
|
|
+ return this.extractedName || '未识别'
|
|
|
|
|
+ },
|
|
|
// 指标按类型(category)分组,保持解析顺序,统计需处理(非正常)数量
|
|
// 指标按类型(category)分组,保持解析顺序,统计需处理(非正常)数量
|
|
|
indicatorGroups() {
|
|
indicatorGroups() {
|
|
|
var groups = []
|
|
var groups = []
|
|
@@ -429,8 +435,10 @@ export default {
|
|
|
this.showCandidatesOnly = true
|
|
this.showCandidatesOnly = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 始终加载家庭成员(面板始终可点击,needBind=false 也允许改选)
|
|
|
|
|
+ this.loadFamilyMembers()
|
|
|
|
|
+
|
|
|
if (this.needBind) {
|
|
if (this.needBind) {
|
|
|
- this.loadFamilyMembers()
|
|
|
|
|
// 高置信度且已匹配 → 直接选中
|
|
// 高置信度且已匹配 → 直接选中
|
|
|
if (this.matchedMemberId && this.matchConfidence >= 0.8) {
|
|
if (this.matchedMemberId && this.matchConfidence >= 0.8) {
|
|
|
this.selectedMemberId = this.matchedMemberId
|
|
this.selectedMemberId = this.matchedMemberId
|
|
@@ -450,13 +458,41 @@ export default {
|
|
|
return { id: c.id, nickname: c.nickname, gender: c.gender, age: c.age, role: 'child' }
|
|
return { id: c.id, nickname: c.nickname, gender: c.gender, age: c.age, role: 'child' }
|
|
|
})
|
|
})
|
|
|
self.familyMembers = parents.concat(children)
|
|
self.familyMembers = parents.concat(children)
|
|
|
|
|
+ self.syncSelectedMemberObj()
|
|
|
}).catch(function(e) {
|
|
}).catch(function(e) {
|
|
|
console.log('加载家庭成员失败', e)
|
|
console.log('加载家庭成员失败', e)
|
|
|
self.familyMembers = []
|
|
self.familyMembers = []
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
- selectMember(member) {
|
|
|
|
|
- this.selectedMemberId = member.userId || member.id
|
|
|
|
|
|
|
+ toggleMemberPanel() {
|
|
|
|
|
+ this.showMemberPanel = !this.showMemberPanel
|
|
|
|
|
+ if (!this.showMemberPanel) {
|
|
|
|
|
+ this.showAutoCreateForm = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ selectAndClose(member) {
|
|
|
|
|
+ var memberId = member.id || member.userId
|
|
|
|
|
+ this.selectedMemberId = memberId
|
|
|
|
|
+ this.selectedMemberObj = member
|
|
|
|
|
+ this.extractedName = member.nickname || member.name || this.extractedName
|
|
|
|
|
+ this.extractedGender = member.gender || this.extractedGender
|
|
|
|
|
+ this.extractedAge = member.age || this.extractedAge
|
|
|
|
|
+ this.showAutoCreateForm = false
|
|
|
|
|
+ this.showMemberPanel = false
|
|
|
|
|
+ },
|
|
|
|
|
+ syncSelectedMemberObj() {
|
|
|
|
|
+ if (!this.selectedMemberId) {
|
|
|
|
|
+ this.selectedMemberObj = null
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ var all = this.familyMembers.concat(this.candidates)
|
|
|
|
|
+ for (var i = 0; i < all.length; i++) {
|
|
|
|
|
+ var m = all[i]
|
|
|
|
|
+ if ((m.id || m.userId) === this.selectedMemberId) {
|
|
|
|
|
+ this.selectedMemberObj = m
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
},
|
|
},
|
|
|
// === Phase 2: 候选匹配与新建成员 ===
|
|
// === Phase 2: 候选匹配与新建成员 ===
|
|
|
genderText(gender) {
|
|
genderText(gender) {
|
|
@@ -464,10 +500,6 @@ export default {
|
|
|
if (gender === 'female') return '女'
|
|
if (gender === 'female') return '女'
|
|
|
return gender || ''
|
|
return gender || ''
|
|
|
},
|
|
},
|
|
|
- selectCandidate(candidate) {
|
|
|
|
|
- this.selectedMemberId = candidate.id || candidate.userId
|
|
|
|
|
- this.showAutoCreateForm = false
|
|
|
|
|
- },
|
|
|
|
|
toggleAutoCreate() {
|
|
toggleAutoCreate() {
|
|
|
this.showAutoCreateForm = !this.showAutoCreateForm
|
|
this.showAutoCreateForm = !this.showAutoCreateForm
|
|
|
if (this.showAutoCreateForm) {
|
|
if (this.showAutoCreateForm) {
|
|
@@ -497,7 +529,17 @@ export default {
|
|
|
if (memberId) {
|
|
if (memberId) {
|
|
|
self.createdMemberId = memberId
|
|
self.createdMemberId = memberId
|
|
|
self.selectedMemberId = memberId
|
|
self.selectedMemberId = memberId
|
|
|
|
|
+ self.selectedMemberObj = {
|
|
|
|
|
+ id: memberId,
|
|
|
|
|
+ nickname: self.newMember.name,
|
|
|
|
|
+ gender: self.newMember.gender,
|
|
|
|
|
+ age: self.newMember.age
|
|
|
|
|
+ }
|
|
|
|
|
+ self.extractedName = self.newMember.name
|
|
|
|
|
+ self.extractedGender = self.newMember.gender
|
|
|
|
|
+ self.extractedAge = self.newMember.age
|
|
|
self.showAutoCreateForm = false
|
|
self.showAutoCreateForm = false
|
|
|
|
|
+ self.showMemberPanel = false
|
|
|
uni.showToast({ title: '创建成功', icon: 'success' })
|
|
uni.showToast({ title: '创建成功', icon: 'success' })
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|