From 69a0c83311f1200dc0f79f053a380f37b4fb954c Mon Sep 17 00:00:00 2001 From: Jafeng <2998840497@qq.com> Date: Mon, 9 Feb 2026 16:29:46 +0800 Subject: [PATCH 1/8] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8Duserid=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../archive-detail/service-info-tab.vue | 159 +++++- pages/home/case-home.vue | 517 ++++++++++++++++-- utils/api.js | 2 + utils/send-message-helper.js | 66 +++ 4 files changed, 700 insertions(+), 44 deletions(-) diff --git a/pages/case/components/archive-detail/service-info-tab.vue b/pages/case/components/archive-detail/service-info-tab.vue index d351882..819da88 100644 --- a/pages/case/components/archive-detail/service-info-tab.vue +++ b/pages/case/components/archive-detail/service-info-tab.vue @@ -34,7 +34,7 @@ {{ i.timeStr }} - + {{ i.fileType === 'article' ? '查看文章' : '查看问卷' }} @@ -137,6 +137,11 @@ const loading = ref(false); const list = ref([]); const expandMap = ref({}); const userNameMap = ref({}); +const teamNameMap = ref({}); +const loadedTeamNameIds = new Set(); + +// 先隐藏“查看问卷/文章”入口(保留相关代码,后续可随时打开) +const showFileEntry = ref(false); const accountStore = useAccountStore(); const { account, doctorInfo } = storeToRefs(accountStore); @@ -183,6 +188,7 @@ function getExecutorId(r) { const row = r && typeof r === 'object' ? r : {}; return String( row.executorUserId || + row.executorUserID || row.executorId || row.executor || row.creatorUserId || @@ -194,11 +200,11 @@ function getExecutorId(r) { function executorText(r) { const row = r && typeof r === 'object' ? r : {}; - const fromRow = normalizeName(row.executorName || row.executorUserName || row.creatorName || row.updateUserName || ''); - if (fromRow) return fromRow; const uid = getExecutorId(row); const mapped = normalizeName(resolveUserName(uid)); - return mapped || (uid ? uid : '--'); + const fromRow = normalizeName(row.executorName || row.executorUserName || row.creatorName || row.updateUserName || ''); + if (mapped && uid && mapped !== uid && (!fromRow || fromRow === uid)) return mapped; + return fromRow || mapped || (uid ? uid : '--'); } function getExecuteTeamId(r) { @@ -209,9 +215,46 @@ function getExecuteTeamId(r) { function resolveTeamName(teamId) { const tid = String(teamId || '') || ''; if (!tid) return ''; + const cached = teamNameMap.value?.[tid]; + if (cached) return String(cached); const list = teamList.value || []; const hit = list.find((i) => i && i.value === tid); - return hit?.label ? String(hit.label) : ''; + if (hit?.label) return String(hit.label); + // 不阻塞渲染:后台补齐团队名 + void loadTeamName(tid); + return ''; +} + +async function loadTeamName(teamId) { + const tid = String(teamId || '') || ''; + if (!tid) return; + if (loadedTeamNameIds.has(tid)) return; + const corpId = getCorpId(); + if (!corpId) return; + + loadedTeamNameIds.add(tid); + try { + const res = await api('getTeamBaseInfo', { corpId, teamId: tid }); + if (res?.success) { + const data = res?.data && typeof res.data === 'object' ? res.data : {}; + const name = String(data?.name || data?.teamName || data?.team || '').trim(); + if (name) teamNameMap.value = { ...(teamNameMap.value || {}), [tid]: name }; + return; + } + } catch { + // ignore + } + + // 兜底:用 getTeamData 再试一次 + try { + const res = await api('getTeamData', { corpId, teamId: tid }); + if (!res?.success) return; + const data = res?.data && typeof res.data === 'object' ? res.data : {}; + const name = String(data?.name || data?.teamName || data?.team || '').trim(); + if (name) teamNameMap.value = { ...(teamNameMap.value || {}), [tid]: name }; + } catch { + // ignore + } } function executeTeamText(r) { @@ -264,21 +307,110 @@ async function loadTeamMembers(teamId) { const tid = String(teamId || '') || ''; if (!tid) return; if (loadedTeamMemberIds.has(tid)) return; - loadedTeamMemberIds.add(tid); await ensureDoctor(); const corpId = getCorpId(); if (!corpId) return; - const res = await api('getTeamData', { corpId, teamId: tid }); - if (!res?.success) return; - const t = res?.data && typeof res.data === 'object' ? res.data : {}; + loadedTeamMemberIds.add(tid); + + // 以 getTeamData 为准(getTeamMemberAvatarsAndName 存在不全/不准的情况) + const fallback = await api('getTeamData', { corpId, teamId: tid }); + if (!fallback?.success) { + loadedTeamMemberIds.delete(tid); + return; + } + const t = fallback?.data && typeof fallback.data === 'object' ? fallback.data : {}; const members = Array.isArray(t.memberList) ? t.memberList : []; const map = members.reduce((acc, m) => { - const uid = String(m?.userid || ''); + if (typeof m === 'string') { + const id = String(m || ''); + if (id) acc[id] = id; + return acc; + } + const uid = String(m?.userid || m?.userId || m?.corpUserId || m?._id || m?.id || ''); if (!uid) return acc; - acc[uid] = String(m?.anotherName || m?.name || m?.userid || '') || uid; + acc[uid] = String(m?.anotherName || m?.name || m?.userid || m?.userId || '') || uid; return acc; }, {}); userNameMap.value = { ...(userNameMap.value || {}), ...map }; + + // 补缺:仅当当前没有映射时才用 avatars 接口补齐,避免覆盖正确姓名 + try { + const res = await api('getTeamMemberAvatarsAndName', { corpId, teamId: tid }); + if (res?.success && res?.data && typeof res.data === 'object') { + const raw = res.data; + const patch = Object.keys(raw).reduce((acc, uid) => { + const id = String(uid || ''); + if (!id) return acc; + const existing = userNameMap.value?.[id]; + if (existing && existing !== id) return acc; + const name = String(raw?.[uid]?.name || raw?.[uid]?.anotherName || '').trim(); + if (!name || name === id) return acc; + acc[id] = name; + return acc; + }, {}); + if (Object.keys(patch).length) userNameMap.value = { ...(userNameMap.value || {}), ...patch }; + } + } catch { + // ignore + } +} + +let corpMemberBatchInflight = null; // Promise | null +async function batchLoadCorpMembers(userIds) { + const ids = Array.isArray(userIds) ? userIds.map((v) => String(v || '').trim()).filter(Boolean) : []; + if (!ids.length) return; + const uniq = Array.from(new Set(ids)); + const unknown = uniq.filter((id) => { + const existing = userNameMap.value?.[id]; + return !existing || existing === id; + }); + if (!unknown.length) return; + + if (corpMemberBatchInflight) return corpMemberBatchInflight; + + await ensureDoctor(); + const corpId = getCorpId(); + if (!corpId) return; + + corpMemberBatchInflight = (async () => { + try { + const res = await api( + 'getCorpMember', + { + page: 1, + pageSize: Math.min(Math.max(unknown.length, 10), 500), + params: { + corpId, + memberList: unknown, + }, + }, + false + ); + if (!res?.success) return; + + const rows = Array.isArray(res?.data) ? res.data : Array.isArray(res?.data?.data) ? res.data.data : []; + if (!rows.length) return; + + const patch = rows.reduce((acc, m) => { + const id = String(m?.userid || m?.userId || m?.corpUserId || '').trim(); + if (!id) return acc; + const existing = userNameMap.value?.[id]; + if (existing && existing !== id) return acc; + const display = String(m?.anotherName || m?.name || '').trim(); + if (!display || display === id) return acc; + acc[id] = display; + return acc; + }, {}); + + if (Object.keys(patch).length) userNameMap.value = { ...(userNameMap.value || {}), ...patch }; + } catch { + // ignore + } + })().finally(() => { + corpMemberBatchInflight = null; + }); + + return corpMemberBatchInflight; } const moreStatus = computed(() => { @@ -375,6 +507,10 @@ async function getMore() { // 尽量加载记录所属团队成员,用于执行人展示 const teamIds = mapped.map((i) => i.executeTeamId).filter(Boolean); Array.from(new Set(teamIds)).forEach((tid) => loadTeamMembers(tid)); + + // 补齐非团队成员执行人姓名(例如其他团队创建/操作) + const executorIds = mapped.map((i) => i.executorUserId).filter(Boolean); + void batchLoadCorpMembers(executorIds); } finally { loading.value = false; } @@ -669,6 +805,7 @@ watch( display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; + line-clamp: 3; overflow: hidden; } .pen { diff --git a/pages/home/case-home.vue b/pages/home/case-home.vue index bdb645a..6f6558a 100644 --- a/pages/home/case-home.vue +++ b/pages/home/case-home.vue @@ -163,6 +163,10 @@ const groupNameMap = computed(() => { // Team Members Map const userNameMap = ref({}); +const loadedMembersTeamId = ref(''); +const corpMemberNameInflight = new Map(); // userId -> Promise +const corpMemberNameTried = new Set(); // avoid retry storms on failures +let corpMemberBatchInflight = null; // Promise | null // 新增流程所需状态(认证相关) const managedArchiveCountAllTeams = ref(0); // 在管档案数(所有团队) @@ -196,6 +200,272 @@ function asArray(value) { return Array.isArray(value) ? value : []; } +function normalizeUserId(value) { + if (value === null || value === undefined) return ''; + if (typeof value === 'object') { + const obj = value; + const picked = + obj.userid || + obj.userId || + obj.userID || + obj.corpUserId || + obj.corpUserID || + obj._id || + obj.id || + ''; + return String(picked || '').trim(); + } + return String(value || '').trim(); +} + +function resolveUserName(userId) { + const id = normalizeUserId(userId); + if (!id) return ''; + // 优先使用当前登录人的信息(避免映射未命中时只显示 userid) + const d = doctorInfo.value || {}; + const doctorId = normalizeUserId(d.userid || d.userId || d.corpUserId || ''); + if (doctorId && doctorId === id) { + const display = String(d.anotherName || d.name || d.username || d.userid || '').trim(); + if (display) return display; + } + const mapped = userNameMap.value[id] || userNameMap.value[id.toLowerCase()]; + if (mapped) return mapped; + // 不阻塞渲染:后台补齐非团队成员姓名(例如其他团队成员创建) + if (isLikelyUserId(id)) void fetchCorpMemberDisplayName(id).catch(() => {}); + return id; +} + +function isLikelyUserId(value) { + const id = normalizeUserId(value); + if (!id) return false; + if (/[\u4e00-\u9fa5]/.test(id)) return false; // already looks like a name + if (/\s/.test(id)) return false; + return true; +} + +function extractDisplayNameFromAny(payload) { + if (!payload) return ''; + if (typeof payload === 'string') return payload.trim(); + if (Array.isArray(payload)) return extractDisplayNameFromAny(payload[0]); + if (typeof payload !== 'object') return ''; + const obj = payload; + const candidate = + obj.anotherName || + obj.name || + obj.realName || + obj.username || + obj.nickName || + obj.nickname || + obj.displayName || + obj.userName || + obj.userid || + obj.userId || + obj.corpUserId || + ''; + return String(candidate || '').trim(); +} + +function extractDisplayNameFromCorpMember(row) { + const m = row && typeof row === 'object' ? row : {}; + const name = String(m.anotherName || m.name || '').trim(); + return name; +} + +async function batchFetchCorpMemberDisplayNames(userIds) { + const ids = Array.isArray(userIds) ? userIds.map(normalizeUserId).filter(Boolean) : []; + if (!ids.length) return; + const uniq = Array.from(new Set(ids)); + + const unresolved = uniq.filter((id) => { + if (!isLikelyUserId(id)) return false; + const cached = userNameMap.value?.[id] || userNameMap.value?.[id.toLowerCase()]; + return !cached || cached === id; + }); + if (!unresolved.length) return; + + if (corpMemberBatchInflight) return corpMemberBatchInflight; + + const corpId = getCorpId() || String(account.value?.corpId || doctorInfo.value?.corpId || ''); + if (!corpId) return; + + corpMemberBatchInflight = (async () => { + try { + const res = await api( + 'getCorpMember', + { + page: 1, + pageSize: Math.min(Math.max(unresolved.length, 10), 500), + params: { + corpId, + memberList: unresolved, + }, + }, + false + ); + + if (!res?.success) return; + const rows = Array.isArray(res?.data) ? res.data : Array.isArray(res?.data?.data) ? res.data.data : []; + if (!rows.length) return; + + const next = { ...(userNameMap.value || {}) }; + rows.forEach((m) => { + const id = normalizeUserId(m?.userid || m?.userId || m?.corpUserId || ''); + if (!id) return; + const display = extractDisplayNameFromCorpMember(m) || id; + const existing = next[id] || next[id.toLowerCase()]; + // 仅补缺:不覆盖已有的非空/非同值映射 + if (existing && existing !== id) return; + if (display && display !== id) { + next[id] = display; + next[id.toLowerCase()] = display; + } + }); + userNameMap.value = next; + } catch { + // ignore + } + })().finally(() => { + corpMemberBatchInflight = null; + }); + + return corpMemberBatchInflight; +} + +async function fetchCorpMemberDisplayName(userId) { + const id = normalizeUserId(userId); + if (!id) return ''; + if (!isLikelyUserId(id)) return ''; + + const cached = userNameMap.value[id] || userNameMap.value[id.toLowerCase()]; + if (cached && cached !== id) return cached; + + if (corpMemberNameInflight.has(id)) return corpMemberNameInflight.get(id); + if (corpMemberNameTried.has(id)) return ''; + + const corpId = getCorpId() || String(account.value?.corpId || doctorInfo.value?.corpId || ''); + if (!corpId) return ''; + + const p = (async () => { + corpMemberNameTried.add(id); + + // 1) 首选:企业成员主页信息(更可能支持用 userid 查询) + try { + const res = await api('getCorpMemberHomepageInfo', { corpId, corpUserId: id }, false); + if (res?.success) { + const name = + extractDisplayNameFromAny(res?.data) || + extractDisplayNameFromAny(res?.data?.data) || + extractDisplayNameFromAny(res?.data?.member) || + ''; + if (name) return name; + } + } catch { + // ignore + } + + // 1.1) 有的后端参数名为 userId + try { + const res = await api('getCorpMemberHomepageInfo', { corpId, userId: id }, false); + if (res?.success) { + const name = + extractDisplayNameFromAny(res?.data) || + extractDisplayNameFromAny(res?.data?.data) || + extractDisplayNameFromAny(res?.data?.member) || + ''; + if (name) return name; + } + } catch { + // ignore + } + + // 2) 兜底:成员数据接口(部分环境可能支持 corpUserId) + try { + const res = await api('getCorpMemberData', { corpId, corpUserId: id }, false); + if (res?.success) { + const name = + extractDisplayNameFromAny(res?.data) || + extractDisplayNameFromAny(res?.data?.data) || + ''; + if (name) return name; + } + } catch { + // ignore + } + + // 2.1) 同样尝试 userId + try { + const res = await api('getCorpMemberData', { corpId, userId: id }, false); + if (res?.success) { + const name = + extractDisplayNameFromAny(res?.data) || + extractDisplayNameFromAny(res?.data?.data) || + ''; + if (name) return name; + } + } catch { + // ignore + } + + return ''; + })() + .then((name) => { + const display = String(name || '').trim(); + if (display) { + const next = { ...(userNameMap.value || {}) }; + next[id] = display; + next[id.toLowerCase()] = display; + userNameMap.value = next; + } + return display; + }) + .finally(() => { + corpMemberNameInflight.delete(id); + }); + + corpMemberNameInflight.set(id, p); + return p; +} + +async function prefetchUserNamesFromPatients(patients) { + const list = Array.isArray(patients) ? patients : []; + if (!list.length) return; + + const ids = new Set(); + list.forEach((p) => { + const u1 = normalizeUserId(p?.recentAddOperatorUserId); + const u2 = normalizeUserId(p?.creator); + if (u1) ids.add(u1); + if (u2) ids.add(u2); + }); + + const targets = Array.from(ids).filter((id) => { + if (!isLikelyUserId(id)) return false; + const cached = userNameMap.value[id] || userNameMap.value[id.toLowerCase()]; + return !cached || cached === id; + }); + if (!targets.length) return; + + // 优先批量补齐(现成后端接口 getCorpMember 支持 memberList) + await batchFetchCorpMemberDisplayNames(targets); + + const limit = 6; + let idx = 0; + const workers = Array.from({ length: Math.min(limit, targets.length) }, async () => { + while (idx < targets.length) { + const cur = targets[idx++]; + try { + const cached = userNameMap.value?.[cur] || userNameMap.value?.[cur.toLowerCase()]; + if (cached && cached !== cur) continue; + await fetchCorpMemberDisplayName(cur); + } catch { + // ignore + } + } + }); + + await Promise.allSettled(workers); +} + function normalizeTeam(raw) { if (!raw || typeof raw !== 'object') return null; const teamId = raw.teamId || raw.id || raw._id || ''; @@ -235,23 +505,76 @@ function ensureUserInfoForFeature() { return false; } +async function ensureDoctorForQuery() { + if (account.value?.openid) { + try { + const a = account.value || {}; + const accountId = normalizeUserId(a.userid || a.userId || a.corpUserId || ''); + const d = doctorInfo.value || {}; + const doctorId = normalizeUserId(d.userid || d.userId || d.corpUserId || ''); + // doctorInfo 可能是旧缓存:当与当前账号不一致时强制刷新 + if (!doctorId || (accountId && doctorId && accountId !== doctorId)) { + await getDoctorInfo(); + } + } catch { + // ignore + } + } + return Boolean(getUserId()); +} + async function loadTeamMembers() { const corpId = getCorpId(); const teamId = getTeamId(); if (!corpId || !teamId) return; + if (loadedMembersTeamId.value === teamId && Object.keys(userNameMap.value || {}).length > 0) return; try { + const nextMap = { ...(userNameMap.value || {}) }; + + // 以团队详情为准(getTeamMemberAvatarsAndName 存在不全/不准的情况) const res = await api('getTeamData', { corpId, teamId }); - if (!res?.success) return; - const t = res?.data && typeof res.data === 'object' ? res.data : {}; - const members = Array.isArray(t.memberList) ? t.memberList : []; - // Update map - members.forEach(m => { - const uid = String(m?.userid || ''); - if (uid) { - userNameMap.value[uid] = String(m?.anotherName || m?.name || m?.userid || '') || uid; - } - }); + if (res?.success) { + const t = res?.data && typeof res.data === 'object' ? res.data : {}; + const members = Array.isArray(t.memberList) ? t.memberList : []; + members.forEach((m) => { + if (typeof m === 'string') { + const k = normalizeUserId(m); + if (k) { + nextMap[k] = nextMap[k] || k; + nextMap[k.toLowerCase()] = nextMap[k.toLowerCase()] || nextMap[k] || k; + } + return; + } + const display = String(m?.anotherName || m?.name || m?.userid || m?.userId || m?.corpUserId || '').trim(); + const keys = [m?.userid, m?.userId, m?.corpUserId].map(normalizeUserId).filter(Boolean); + keys.forEach((k) => { + nextMap[k] = display || nextMap[k] || k; + nextMap[String(k).toLowerCase()] = display || nextMap[String(k).toLowerCase()] || k; + }); + }); + } + + // 补缺:仅当当前没有映射时才用 avatars 接口补齐,避免覆盖正确姓名 + try { + const avatarRes = await api('getTeamMemberAvatarsAndName', { corpId, teamId }); + const mapObj = avatarRes?.success && avatarRes?.data && typeof avatarRes.data === 'object' ? avatarRes.data : {}; + Object.entries(mapObj).forEach(([uid, info]) => { + const k = normalizeUserId(uid); + if (!k) return; + const existing = nextMap[k] || nextMap[k.toLowerCase()]; + if (existing && existing !== k) return; + const display = String(info?.name || info?.anotherName || info?.userid || '').trim(); + if (!display || display === k) return; + nextMap[k] = display; + nextMap[k.toLowerCase()] = display; + }); + } catch { + // ignore + } + + userNameMap.value = nextMap; + loadedMembersTeamId.value = teamId; } catch (e) { console.error('获取团队成员失败', e); } @@ -260,7 +583,7 @@ async function loadTeamMembers() { function resolveCreatorName(patient) { const val = patient.creator; if (!val) return ''; - return userNameMap.value[val] || val; + return resolveUserName(val); } function resolveRecentAddTime(patient) { @@ -268,9 +591,12 @@ function resolveRecentAddTime(patient) { } function resolveRecentAddOperatorName(patient) { - const uid = patient?.recentAddOperatorUserId || patient?.creator || ''; + const nameFromApi = String(patient?.recentAddOperatorName || '').trim(); + // 后端部分场景会把 userid 填到 name 字段里,此时仍需通过 userid 解析姓名 + if (nameFromApi && !isLikelyUserId(nameFromApi)) return nameFromApi; + const uid = patient?.recentAddOperatorUserId || nameFromApi || patient?.creator || ''; if (!uid) return ''; - return userNameMap.value[uid] || uid; + return resolveUserName(uid); } function resolveRecentAddAction(patient) { @@ -396,6 +722,123 @@ function parseCreateTime(value) { return d2.isValid() ? d2 : null; } +function normalizeMedicalType(raw) { + const s = String(raw || '').trim(); + if (!s) return ''; + const lower = s.toLowerCase(); + // 中文兜底(部分接口返回展示名) + if (s.includes('门诊')) return 'outpatient'; + if (s.includes('住院') || s.includes('入院')) return 'inhospital'; + if (s.includes('预问诊') || s.includes('问诊')) return 'preConsultation'; + if (s.includes('体检')) return 'physicalExaminationTemplate'; + if (lower.includes('preconsult') || (lower.includes('pre') && lower.includes('consult'))) return 'preConsultation'; + if (lower === 'outpatient' || lower === 'out_patient' || lower === 'out-patient') return 'outpatient'; + if (lower === 'inhospital' || lower === 'in_hospital' || lower === 'in-hospital' || lower === 'inpatient') return 'inhospital'; + if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate'; + if (s === 'outPatient') return 'outpatient'; + if (s === 'inHospital') return 'inhospital'; + if (s === 'preConsultation') return 'preConsultation'; + if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate'; + return s; +} + +function normalizeText(v) { + if (Array.isArray(v)) { + const parts = v + .map((i) => normalizeText(i)) + .filter((i) => i !== null && i !== undefined && String(i).trim()); + return parts.join(','); + } + if (v === 0) return '0'; + if (v && typeof v === 'object') { + const o = v; + const candidate = o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.diseaseName ?? o.code ?? ''; + return candidate ? String(candidate) : ''; + } + return v ? String(v) : ''; +} + +function formatAnyDate(value, fmt = 'YYYY-MM-DD') { + const d = parseCreateTime(value); + return d ? d.format(fmt) : ''; +} + +function resolveLatestRecord(lr) { + if (!lr || typeof lr !== 'object') return null; + + const formData = lr?.formData && typeof lr.formData === 'object' ? lr.formData : null; + const recordData = lr?.recordData && typeof lr.recordData === 'object' ? lr.recordData : null; + const data = lr?.data && typeof lr.data === 'object' ? lr.data : null; + + const hasValue = (v) => { + if (v === 0) return true; + if (Array.isArray(v)) return v.length > 0; + if (v && typeof v === 'object') return Object.keys(v).length > 0; + return v !== null && v !== undefined && String(v).trim() !== ''; + }; + + const pick = (...keys) => { + for (const k of keys) { + const v0 = lr?.[k]; + if (hasValue(v0)) return v0; + const v1 = formData?.[k]; + if (hasValue(v1)) return v1; + const v2 = recordData?.[k]; + if (hasValue(v2)) return v2; + const v3 = data?.[k]; + if (hasValue(v3)) return v3; + } + return undefined; + }; + + const rawType = String(pick('medicalType', 'templateType') || '').trim(); + const uiType = normalizeMedicalType(rawType || pick('type')); + const typeLabel = String(pick('tempName', 'templateName', 'name', 'type') || '').trim(); + + const rawDate = pick('date', 'visitTime', 'inhosDate', 'consultDate', 'inspectDate', 'sortTime'); + const rawDateStr = String(rawDate ?? '').trim(); + const date = (/^\d{10,13}$/.test(rawDateStr) ? (formatAnyDate(rawDateStr, 'YYYY-MM-DD') || rawDateStr) : rawDateStr) + || formatAnyDate(pick('visitTime', 'inhosDate', 'consultDate', 'inspectDate', 'sortTime'), 'YYYY-MM-DD') + || '-'; + + let third = ''; + if (uiType === 'outpatient' || uiType === 'inhospital') { + third = normalizeText(pick( + 'diagnosisName', + 'diagnosis', + 'diagnosisList', + 'diagnosisNames', + 'mainDiagnosis', + 'admissionDiagnosis', + 'inDiagnosis', + 'outDiagnosis', + 'outPatientDiagnosis', + 'inHospitalDiagnosis' + )); + } else if (uiType === 'preConsultation') { + third = normalizeText(pick('chiefComplaint', 'complaint', 'mainComplaint', 'mainSuit', 'chief')); + } else if (uiType === 'physicalExaminationTemplate') { + third = normalizeText(pick('summary', 'inspectSummary', 'conclusion', 'inspectConclusion', 'inspectResult', 'finalConclusion')); + } else { + third = normalizeText(pick('diagnosis', 'diagnosisName', 'summary', 'chiefComplaint')); + } + third = String(third || '').replace(/\s+/g, ' ').trim(); + if (!third) { + // 最后的兜底:避免经常展示为 '-' + third = normalizeText(pick('summary', 'inspectSummary', 'chiefComplaint', 'presentIllness', 'treatmentPlan', 'abstract', 'brief')); + third = String(third || '').replace(/\s+/g, ' ').trim(); + } + if (!String(third || '').trim()) third = '-'; + + const type = typeLabel || (uiType === 'outpatient' ? '门诊记录' + : uiType === 'inhospital' ? '住院记录' + : uiType === 'preConsultation' ? '预问诊记录' + : uiType === 'physicalExaminationTemplate' ? '体检档案' + : '-'); + + return { type, date, diagnosis: third }; +} + function formatPatient(raw) { const name = raw?.name || raw?.customerName || ''; const sex = raw?.sex || raw?.gender || ''; @@ -427,20 +870,16 @@ function formatPatient(raw) { // 解析病历信息 let record = null; - if (raw?.latestRecord && typeof raw.latestRecord === 'object') { - const lr = raw.latestRecord; - const type = lr.type || ''; - const date = lr.date || ''; - const diagnosis = lr.diagnosis || ''; - // 只有存在有效信息时才设置 record - if (type || date || diagnosis) { - record = { - type: type || '-', - date: date || '-', - diagnosis: diagnosis || '-' - }; - } - } + const latestRaw = + raw?.latestRecord ?? + raw?.latestMedicalRecord ?? + raw?.latestMedicalCase ?? + raw?.lastRecord ?? + raw?.lastMedicalRecord ?? + raw?.recentRecord ?? + null; + const latest = Array.isArray(latestRaw) ? latestRaw[0] : latestRaw; + if (latest && typeof latest === 'object') record = resolveLatestRecord(latest); return { ...raw, @@ -457,6 +896,14 @@ function formatPatient(raw) { recentAddTimeTs, recentAddType, recentAddOperatorUserId, + recentAddOperatorName: String( + raw?.recentAddOperatorName + || raw?.recentAddOperatorAnotherName + || raw?.recentAddOperatorUserName + || raw?.recentAddOperatorRealName + || raw?.recentAddOperatorDisplayName + || '' + ).trim(), creator: raw?.creatorName || raw?.creator || '', hospitalId: raw?.customerNumber || raw?.hospitalId || '', record, @@ -518,6 +965,7 @@ async function reload(reset = true) { if (!currentTeam.value) return; if (loading.value) return; + await ensureDoctorForQuery(); const userId = getUserId(); const corpId = getCorpId(); const teamId = getTeamId(); @@ -571,6 +1019,8 @@ async function reload(reset = true) { const list = Array.isArray(payload.list) ? payload.list : Array.isArray(payload.data) ? payload.data : []; const next = list.map(formatPatient); rawPatients.value = page.value === 1 ? next : [...rawPatients.value, ...next]; + // 补齐创建人/新增人姓名(部分创建人不在当前团队成员列表中) + void prefetchUserNamesFromPatients(next).catch(() => {}); pages.value = Number(payload.pages || 0) || 0; totalFromApi.value = Number(payload.total || 0) || rawPatients.value.length; managedArchiveCountAllTeams.value = @@ -665,13 +1115,13 @@ const toggleTeamPopup = () => { } uni.showActionSheet({ itemList: teams.value.map((i) => i.name), - success: function (res) { + success: async (res) => { currentTeam.value = teams.value[res.tapIndex] || teams.value[0] || null; if (currentTeam.value) uni.setStorageSync(CURRENT_TEAM_STORAGE_KEY, currentTeam.value); currentTabKey.value = 'all'; - loadGroups(); - loadTeamMembers(); - reload(true); + await loadGroups(); + await loadTeamMembers(); + await reload(true); } }); }; @@ -1016,6 +1466,7 @@ watch(currentTeam, (t) => { watch(currentTabKey, () => { if (!currentTeam.value) return; + loadTeamMembers(); reload(true); }); @@ -1032,7 +1483,7 @@ onLoad(async () => { await loadTeams(); if (currentTeam.value) { await loadGroups(); - loadTeamMembers(); + await loadTeamMembers(); await reload(true); } await refreshVerifyStatus(); @@ -1056,7 +1507,7 @@ onShow(async () => { } else { await loadGroups(); } - loadTeamMembers(); + await loadTeamMembers(); await refreshVerifyStatus(); }); diff --git a/utils/api.js b/utils/api.js index 18a53f9..188f62e 100644 --- a/utils/api.js +++ b/utils/api.js @@ -3,6 +3,8 @@ import request from "./http"; const urlsConfig = { corp: { getCorpMemberHomepageInfo: 'getCorpMemberHomepageInfo', + getCorpMember: 'getCorpMember', + getCorpMemberOptions: 'getCorpMemberOptions', // 企业信息/标签 getCorpInfo: 'getCorpInfo', getCorpTags: 'getCorpTags', diff --git a/utils/send-message-helper.js b/utils/send-message-helper.js index 9790add..a272e0d 100644 --- a/utils/send-message-helper.js +++ b/utils/send-message-helper.js @@ -6,6 +6,38 @@ import { globalTimChatManager } from './tim-chat.js'; import api from './api.js'; import { toast } from './widget.js'; const env = __VITE_ENV__; + +function nowTs() { + return Date.now(); +} + +async function tryAddServiceRecord(payload) { + try { + const res = await api('addServiceRecord', payload); + if (!res?.success) { + console.warn('写入服务记录失败:', res?.message || res); + } + } catch (e) { + console.error('写入服务记录异常:', e); + } +} + +function canWriteServiceRecord(options = {}) { + return Boolean(options?.corpId && options?.userId && options?.customerId); +} + +function normalizeServiceRecordBase(options = {}) { + return { + corpId: options.corpId, + executorUserId: String(options.userId || ''), + creatorUserId: String(options.userId || ''), + customerId: String(options.customerId || ''), + customerName: String(options.customerName || ''), + executeTeamId: String(options.teamId || options.executeTeamId || ''), + executionTime: nowTs(), + externalUserId: String(options.externalUserId || options.customerUserId || ''), + }; +} /** * 发送文字消息 * @param {string} content - 文字内容 @@ -168,6 +200,24 @@ export async function sendArticleMessage(article, options = {}) { console.error('记录文章发送失败:', err); }); } + + // 写入服务记录留痕(异步,不阻塞) + if (canWriteServiceRecord(options)) { + const base = normalizeServiceRecordBase(options); + tryAddServiceRecord({ + ...base, + eventType: 'ContentReminder', + taskContent: `推送文章:${article.title || '宣教文章'}`, + pannedEventSendFile: { + type: 'article', + articleId: article._id || options.articleId || '', + title: article.title || '', + url: article.url || '', + }, + pannedEventName: '宣教发送', + }); + } + return true; } else { toast(result?.error || '发送文章消息失败'); @@ -243,6 +293,22 @@ export async function sendSurveyMessage(survey, options = {}) { const result = await globalTimChatManager.sendCustomMessage(customMessageData); if (result?.success) { + // 写入服务记录留痕(异步,不阻塞) + if (canWriteServiceRecord(options)) { + const base = normalizeServiceRecordBase(options); + tryAddServiceRecord({ + ...base, + eventType: 'questionnaire', + taskContent: `推送问卷:${survey.name || '问卷'}`, + pannedEventSendFile: { + type: 'questionnaire', + surveryId: survey._id || survey.surveryId || '', + name: survey.name || '', + url: surveyLink || '', + }, + pannedEventName: '问卷调查', + }); + } return true; } else { toast(result?.error || '发送问卷消息失败'); From eead7920dce661e379d7162626573001abbb2665 Mon Sep 17 00:00:00 2001 From: Jafeng <2998840497@qq.com> Date: Mon, 9 Feb 2026 16:33:42 +0800 Subject: [PATCH 2/8] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8Dteamid=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../archive-detail/service-info-tab.vue | 95 ++++++++++++++----- utils/api.js | 1 + 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/pages/case/components/archive-detail/service-info-tab.vue b/pages/case/components/archive-detail/service-info-tab.vue index 819da88..e11f9aa 100644 --- a/pages/case/components/archive-detail/service-info-tab.vue +++ b/pages/case/components/archive-detail/service-info-tab.vue @@ -221,40 +221,80 @@ function resolveTeamName(teamId) { const hit = list.find((i) => i && i.value === tid); if (hit?.label) return String(hit.label); // 不阻塞渲染:后台补齐团队名 - void loadTeamName(tid); + void batchLoadTeamNames([tid]); return ''; } -async function loadTeamName(teamId) { - const tid = String(teamId || '') || ''; - if (!tid) return; - if (loadedTeamNameIds.has(tid)) return; +let teamNameBatchInflight = null; // Promise | null +async function batchLoadTeamNames(teamIds) { + const ids = Array.isArray(teamIds) ? teamIds.map((v) => String(v || '').trim()).filter(Boolean) : []; + if (!ids.length) return; + const uniq = Array.from(new Set(ids)); + + const unknown = uniq.filter((tid) => { + if (loadedTeamNameIds.has(tid)) return false; + const cached = teamNameMap.value?.[tid]; + if (cached) return false; + const list = teamList.value || []; + const hit = list.find((i) => i && i.value === tid); + return !hit?.label; + }); + if (!unknown.length) return; + + if (teamNameBatchInflight) return teamNameBatchInflight; + const corpId = getCorpId(); if (!corpId) return; + unknown.forEach((tid) => loadedTeamNameIds.add(tid)); - loadedTeamNameIds.add(tid); - try { - const res = await api('getTeamBaseInfo', { corpId, teamId: tid }); - if (res?.success) { - const data = res?.data && typeof res.data === 'object' ? res.data : {}; - const name = String(data?.name || data?.teamName || data?.team || '').trim(); - if (name) teamNameMap.value = { ...(teamNameMap.value || {}), [tid]: name }; - return; + teamNameBatchInflight = (async () => { + // 现成接口:getTeamById 支持 teamIds 批量查询,返回 team 列表(含 teamId/name) + try { + const res = await api('getTeamById', { corpId, teamIds: unknown }, false); + if (res?.success) { + const rows = Array.isArray(res?.data) ? res.data : Array.isArray(res?.data?.data) ? res.data.data : []; + const patch = rows.reduce((acc, t) => { + const id = String(t?.teamId || t?.id || t?._id || '').trim(); + if (!id) return acc; + const name = String(t?.name || t?.teamName || t?.team || '').trim(); + if (!name) return acc; + // 只补缺,不覆盖已有映射 + const existing = teamNameMap.value?.[id]; + if (existing) return acc; + acc[id] = name; + return acc; + }, {}); + if (Object.keys(patch).length) teamNameMap.value = { ...(teamNameMap.value || {}), ...patch }; + return; + } + } catch { + // ignore } - } catch { - // ignore - } - // 兜底:用 getTeamData 再试一次 - try { - const res = await api('getTeamData', { corpId, teamId: tid }); - if (!res?.success) return; - const data = res?.data && typeof res.data === 'object' ? res.data : {}; - const name = String(data?.name || data?.teamName || data?.team || '').trim(); - if (name) teamNameMap.value = { ...(teamNameMap.value || {}), [tid]: name }; - } catch { - // ignore - } + // 兜底:逐个 getTeamData(并发不高,避免卡顿) + const limit = 4; + let idx = 0; + const workers = Array.from({ length: Math.min(limit, unknown.length) }, async () => { + while (idx < unknown.length) { + const tid = unknown[idx++]; + try { + const res = await api('getTeamData', { corpId, teamId: tid }, false); + if (!res?.success) continue; + const data = res?.data && typeof res.data === 'object' ? res.data : {}; + const name = String(data?.name || data?.teamName || data?.team || '').trim(); + if (!name) continue; + if (!teamNameMap.value?.[tid]) teamNameMap.value = { ...(teamNameMap.value || {}), [tid]: name }; + } catch { + // ignore + } + } + }); + await Promise.allSettled(workers); + })().finally(() => { + teamNameBatchInflight = null; + }); + + return teamNameBatchInflight; } function executeTeamText(r) { @@ -508,6 +548,9 @@ async function getMore() { const teamIds = mapped.map((i) => i.executeTeamId).filter(Boolean); Array.from(new Set(teamIds)).forEach((tid) => loadTeamMembers(tid)); + // 批量补齐团队名(与 userid 补齐策略一致:缓存 + 只补缺,不阻塞渲染) + void batchLoadTeamNames(teamIds); + // 补齐非团队成员执行人姓名(例如其他团队创建/操作) const executorIds = mapped.map((i) => i.executorUserId).filter(Boolean); void batchLoadCorpMembers(executorIds); diff --git a/utils/api.js b/utils/api.js index 188f62e..7b43521 100644 --- a/utils/api.js +++ b/utils/api.js @@ -10,6 +10,7 @@ const urlsConfig = { getCorpTags: 'getCorpTags', getTeamBaseInfo: 'getTeamBaseInfo', getTeamData: 'getTeamData', + getTeamById: 'getTeamById', getTeamBymember: 'getTeamBymember', getCurrentTemplate: 'getCurrentTemplate', getTemplateGroup: 'getTemplateGroup', From cfa15a445ba70da726a1c88f14bc23021f25e5bc Mon Sep 17 00:00:00 2001 From: Jafeng <2998840497@qq.com> Date: Mon, 9 Feb 2026 17:02:40 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=9B=9E?= =?UTF-8?q?=E8=AE=BF=E5=86=85=E5=AE=B9=E8=B7=B3=E8=BD=AC=E5=88=B0=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E9=97=B4=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/case/archive-detail.vue | 45 ++- .../archive-detail/follow-up-manage-tab.vue | 278 +++++++++++++++--- pages/message/index.vue | 153 ++++++++-- 3 files changed, 385 insertions(+), 91 deletions(-) diff --git a/pages/case/archive-detail.vue b/pages/case/archive-detail.vue index e8a8a0f..e449530 100644 --- a/pages/case/archive-detail.vue +++ b/pages/case/archive-detail.vue @@ -334,18 +334,26 @@ async function fetchArchive() { await ensureDoctor(); loading('加载中...'); try { + const prevLocalChatGroupId = normalizeGroupId(chatGroupId.value || archive.value.chatGroupId || ''); const res = await api('getCustomerByCustomerId', { customerId: archiveId.value }); if (!res?.success) { toast(res?.message || '获取档案失败'); return; } - archive.value = { ...archive.value, ...normalizeArchiveFromApi(res.data) }; + + const normalized = normalizeArchiveFromApi(res.data); + // 后端经常不返回 chatGroupId(或为空),但本地已探测出可用群聊时不能被覆盖掉,否则返回页面会导致“去聊天”按钮短暂/持续消失 + if (!normalized.chatGroupId && prevLocalChatGroupId) { + normalized.chatGroupId = prevLocalChatGroupId; + } + + archive.value = { ...archive.value, ...normalized }; saveToStorage(); loadTeamMembers(); await fetchTeamGroups(true); chatGroupId.value = normalizeGroupId(archive.value.chatGroupId || ''); if (!chatGroupId.value) { - refreshChatRoom(); + await refreshChatRoom({ force: true }); } else { const meta = await getChatRoomMeta(chatGroupId.value); const ok = isChatRoomForArchive(meta, archiveId.value); @@ -354,7 +362,7 @@ async function fetchArchive() { if (!ok || (currentTeamId && metaTeamId && metaTeamId !== currentTeamId)) { chatGroupId.value = ''; archive.value.chatGroupId = ''; - refreshChatRoom(); + await refreshChatRoom({ force: true }); } else { archive.value.chatGroupId = chatGroupId.value; } @@ -618,7 +626,7 @@ const goEdit = () => { const goChat = async () => { let gid = normalizeGroupId(currentChatGroupId.value || ''); if (!gid) { - await refreshChatRoom(); + await refreshChatRoom({ force: true }); gid = normalizeGroupId(currentChatGroupId.value || ''); } if (!gid) { @@ -634,7 +642,7 @@ const goChat = async () => { chatGroupId.value = ''; archive.value.chatGroupId = ''; saveToStorage(); - await refreshChatRoom(); + await refreshChatRoom({ force: true }); gid = normalizeGroupId(currentChatGroupId.value || ''); } if (!gid) { @@ -650,6 +658,7 @@ const goChat = async () => { const isRefreshingChatRoom = ref(false); let lastRefreshChatRoomAt = 0; +let refreshChatRoomPromise = null; function isChatRoomForArchive(meta, customerId) { const cid = String(meta?.customerId || ''); @@ -681,16 +690,24 @@ function parseAnyTimeMs(v) { return d.isValid() ? d.valueOf() : 0; } -async function refreshChatRoom() { +async function refreshChatRoom(options = {}) { + const force = Boolean(options?.force); const customerId = String(archiveId.value || ''); if (!customerId) return; - if (isRefreshingChatRoom.value) return; + + // 如果正在刷新,复用同一个 promise,确保调用方可以 await 到结果 + if (isRefreshingChatRoom.value && refreshChatRoomPromise) { + await refreshChatRoomPromise; + return; + } + const now = Date.now(); - if (now - lastRefreshChatRoomAt < 5000) return; + if (!force && now - lastRefreshChatRoomAt < 5000) return; lastRefreshChatRoomAt = now; isRefreshingChatRoom.value = true; - try { + refreshChatRoomPromise = (async () => { + try { const corpId = getCorpId(); const teamId = getCurrentTeamId(); @@ -742,11 +759,15 @@ async function refreshChatRoom() { chatGroupId.value = ''; archive.value.chatGroupId = ''; } - } catch (e) { + } catch (e) { // ignore - } finally { + } finally { isRefreshingChatRoom.value = false; - } + refreshChatRoomPromise = null; + } + })(); + + await refreshChatRoomPromise; } const makeCall = () => { diff --git a/pages/case/components/archive-detail/follow-up-manage-tab.vue b/pages/case/components/archive-detail/follow-up-manage-tab.vue index be97c5c..894dfb7 100644 --- a/pages/case/components/archive-detail/follow-up-manage-tab.vue +++ b/pages/case/components/archive-detail/follow-up-manage-tab.vue @@ -90,9 +90,9 @@ @@ -216,7 +216,6 @@ import dayjs from "dayjs"; import api from "@/utils/api"; import useAccountStore from "@/store/account"; import { toast } from "@/utils/widget"; -import { handleFollowUpMessages } from "@/utils/send-message-helper"; import { getTodoEventTypeLabel, getTodoEventTypeOptions, @@ -235,12 +234,27 @@ const { account, doctorInfo } = storeToRefs(accountStore); const { getDoctorInfo } = accountStore; function getUserId() { - return doctorInfo.value?.userid || ""; + const d = doctorInfo.value || {}; + const a = account.value || {}; + return String(d.userid || d.userId || d.corpUserId || a.userid || a.userId || "") || ""; } function getCorpId() { const team = uni.getStorageSync("ykt_case_current_team") || {}; - return team.corpId || doctorInfo.value?.corpId || ""; + const d = doctorInfo.value || {}; + const a = account.value || {}; + return String(d.corpId || a.corpId || team.corpId || "") || ""; +} + +function getCurrentTeamId() { + const team = uni.getStorageSync("ykt_case_current_team") || {}; + return String(team?.teamId || team?._id || team?.id || "") || ""; +} + +function normalizeGroupId(v) { + const s = String(v || "").trim(); + if (!s) return ""; + return s.startsWith("GROUP") ? s.slice(5) : s; } const statusTabs = [ @@ -270,6 +284,11 @@ const query = reactive({ const list = ref([]); const total = ref(0); +const chatGroupId = ref(""); +const currentChatGroupId = computed(() => normalizeGroupId(chatGroupId.value || "")); + +const PENDING_FOLLOWUP_SEND_STORAGE_KEY = "ykt_followup_pending_send"; + const page = ref(1); const pageSize = 10; const pages = ref(1); @@ -511,75 +530,163 @@ function toDetail(todo) { }); } -async function sendFollowUp(todo) { - if (!todo.sendContent && (!todo.fileList || todo.fileList.length === 0)) { - toast("没有发送内容"); - return; - } +function hasSendContent(todo) { + return Boolean(todo?.sendContent) || (Array.isArray(todo?.fileList) && todo.fileList.length > 0); +} +function isExecutorMe(todo) { + const me = String(getUserId() || ""); + const executor = String(todo?.executorUserId || ""); + if (!me || !executor) return false; + return me === executor; +} + +function canShowSendButton(todo) { + if (!hasSendContent(todo)) return false; + if (!isExecutorMe(todo)) return false; + // 当前患者无会话则不展示 + return Boolean(currentChatGroupId.value); +} + +function buildFollowUpMessages(todo) { const messages = []; - - // 1. 发送文字内容 - if (todo.sendContent) { - messages.push({ - type: "text", - content: todo.sendContent, - }); + if (todo?.sendContent) { + messages.push({ type: "text", content: String(todo.sendContent) }); } - console.log("==============>fileList", todo.fileList); - // 2. 处理文件列表(图片、宣教文章、问卷) - if (Array.isArray(todo.fileList)) { + if (Array.isArray(todo?.fileList)) { for (const file of todo.fileList) { - if (file.type === "image" && file.URL) { - // 发送图片 + const outerType = String(file?.type || ""); + + let innerFile = file?.file; + if (typeof innerFile === "string") { + try { + innerFile = JSON.parse(innerFile); + } catch { + // ignore + } + } + innerFile = innerFile && typeof innerFile === "object" ? innerFile : null; + + const innerType = String(innerFile?.type || ""); + const outerUrl = String(file?.URL || file?.url || ""); + const innerUrl = String(innerFile?.url || ""); + + // 兼容 followup-detail.vue 的判定方式 + let fileType = ""; + if (outerType === "image" || innerType.includes("image")) fileType = "image"; + else if (innerType === "article") fileType = "article"; + else if (innerType === "questionnaire") fileType = "questionnaire"; + else fileType = outerType; + + const url = fileType === "article" || fileType === "questionnaire" ? (innerUrl || outerUrl) : (outerUrl || innerUrl); + + if (fileType === "image" && url) { messages.push({ type: "image", - content: file.URL, - name: file.file?.name || file.name || "图片", + content: url, + name: innerFile?.name || file?.name || "图片", }); - } else if (file.file.type === "article" && file.file?.url) { - // 发送宣教文章 - 从 URL 中解析 id - const articleId = extractIdFromUrl(file.file.url); + continue; + } + + if (fileType === "article") { + const fallbackArticleId = String(innerFile?._id || file?._id || innerFile?.articleId || file?.articleId || "") || ""; + const extractedId = extractIdFromUrl(url); + const articleId = String(extractedId || fallbackArticleId || ""); + + // url 兜底:如果后端没给文章跳转链接,则用 articleId+corpId 拼接 + let articleUrl = String(url || ""); + if (!articleUrl && articleId) { + const corpId = getCorpId(); + articleUrl = `${__VITE_ENV__?.MP_PATIENT_PAGE_BASE_URL || ""}pages/article/index?id=${encodeURIComponent( + articleId + )}&corpId=${encodeURIComponent(corpId || "")}`; + } + + // 没有 id 和 url 时,跳过 + if (!articleId && !articleUrl) continue; + messages.push({ type: "article", content: { _id: articleId, - title: file.file?.name || "宣教文章", - url: file.file?.url || file.URL, - subtitle: file.file?.subtitle || "", - cover: file.file?.cover || "", + title: innerFile?.name || file?.name || "宣教文章", + url: articleUrl, + subtitle: innerFile?.subtitle || "", + cover: innerFile?.cover || file?.URL || "", articleId: articleId, }, }); - } else if (file.file.type === "questionnaire" && file.file?.surveryId) { - // 发送问卷 + continue; + } + + if (fileType === "questionnaire") { + const surveryId = innerFile?.surveryId || file?.surveryId; + if (!surveryId) continue; + const surveyId = String(innerFile?._id || file?._id || surveryId || ""); messages.push({ type: "questionnaire", content: { - _id: file.file?._id || file._id, - name: file.file?.name || file.name || "问卷", - surveryId: file.file?.surveryId || file.surveryId, - url: file.file?.url || file.URL, + _id: surveyId, + name: innerFile?.name || file?.name || "问卷", + surveryId, + url: String(url || ""), + createBy: innerFile?.createBy, }, }); } } } - // 调用统一的消息发送处理函数 - const success = await handleFollowUpMessages(messages, { - userId: getUserId(), - customerId: props.archiveId, - customerName: props.data?.name || "", - corpId: getCorpId(), - env: __VITE_ENV__, + return messages; +} + +async function goChatAndSend(todo) { + if (!canShowSendButton(todo)) return; + if (!props.archiveId) return; + + let gid = normalizeGroupId(currentChatGroupId.value || ""); + if (!gid) { + await refreshChatRoom(); + gid = normalizeGroupId(currentChatGroupId.value || ""); + } + if (!gid) { + toast("暂无可进入的会话"); + return; + } + + const messages = buildFollowUpMessages(todo); + if (!messages.length) { + console.warn("[followup] buildFollowUpMessages empty:", { + sendContent: todo?.sendContent, + fileList: todo?.fileList, + }); + toast("发送内容解析失败"); + return; + } + + const conversationID = `GROUP${gid}`; + + uni.setStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY, { + createdAt: Date.now(), + groupId: gid, + conversationID, + messages, + context: { + userId: getUserId(), + customerId: props.archiveId, + customerName: props.data?.name || "", + corpId: getCorpId(), + env: __VITE_ENV__, + }, }); - if (success) { - toast("消息已发送"); - uni.navigateBack(); - } + uni.navigateTo({ + url: `/pages/message/index?conversationID=${encodeURIComponent( + conversationID + )}&groupID=${encodeURIComponent(gid)}&fromCase=true&pendingFollowUpSend=1`, + }); } /** @@ -606,6 +713,80 @@ function extractIdFromUrl(url) { } } +const isRefreshingChatRoom = ref(false); +let lastRefreshChatRoomAt = 0; + +function parseAnyTimeMs(v) { + if (v === null || v === undefined) return 0; + if (typeof v === "number") return v; + const s = String(v).trim(); + if (!s) return 0; + if (/^\d{10,13}$/.test(s)) return Number(s.length === 10 ? `${s}000` : s); + const d = dayjs(s); + return d.isValid() ? d.valueOf() : 0; +} + +async function refreshChatRoom() { + const customerId = String(props.archiveId || ""); + if (!customerId) return; + if (isRefreshingChatRoom.value) return; + const now = Date.now(); + if (now - lastRefreshChatRoomAt < 5000) return; + lastRefreshChatRoomAt = now; + + isRefreshingChatRoom.value = true; + try { + await ensureDoctor(); + const corpId = getCorpId(); + const teamId = getCurrentTeamId(); + + const baseQuery = { + corpId, + customerId, + page: 1, + pageSize: 50, + }; + + const queryWithTeam = teamId ? { ...baseQuery, teamId } : baseQuery; + let detailRes = await api("getGroupList", queryWithTeam, false); + let details = Array.isArray(detailRes?.data?.list) ? detailRes.data.list : []; + + if (!details.length && teamId) { + detailRes = await api("getGroupList", baseQuery, false); + details = Array.isArray(detailRes?.data?.list) ? detailRes.data.list : []; + } + + if (!detailRes?.success || !details.length) { + chatGroupId.value = ""; + return; + } + + const currentTeamId = getCurrentTeamId(); + const detailsForCurrentTeam = currentTeamId + ? details.filter((g) => String(g?.teamId || g?.team?._id || g?.team?.teamId || "") === currentTeamId) + : []; + const candidates = detailsForCurrentTeam.length ? detailsForCurrentTeam : details; + + const statusRank = (s) => (s === "processing" ? 3 : s === "pending" ? 2 : 1); + candidates.sort((a, b) => { + const ra = statusRank(String(a?.orderStatus || "")); + const rb = statusRank(String(b?.orderStatus || "")); + if (rb !== ra) return rb - ra; + const ta = parseAnyTimeMs(a?.updatedAt) || parseAnyTimeMs(a?.createdAt); + const tb = parseAnyTimeMs(b?.updatedAt) || parseAnyTimeMs(b?.createdAt); + return tb - ta; + }); + + const best = candidates[0] || {}; + const gid = normalizeGroupId(best.groupId || best.groupID || best.group_id || ""); + chatGroupId.value = gid ? String(gid) : ""; + } catch (e) { + // ignore + } finally { + isRefreshingChatRoom.value = false; + } +} + // ---- filter popup ---- const filterPopupRef = ref(null); const state = ref(null); @@ -692,6 +873,7 @@ onMounted(() => { if (userId && name) userNameMap.value = { ...(userNameMap.value || {}), [userId]: name }; loadTeams(); + refreshChatRoom(); resetList(); uni.$on("archive-detail:followup-changed", resetList); }); diff --git a/pages/message/index.vue b/pages/message/index.vue index 93e8de5..61681c5 100644 --- a/pages/message/index.vue +++ b/pages/message/index.vue @@ -182,6 +182,7 @@ import { onLoad, onShow, onHide } from "@dcloudio/uni-app"; import { storeToRefs } from "pinia"; import useAccountStore from "@/store/account.js"; import { globalTimChatManager, TIM } from "@/utils/tim-chat.js"; +import { handleFollowUpMessages } from "@/utils/send-message-helper"; import { startIMMonitoring, stopIMMonitoring, @@ -210,6 +211,63 @@ import AIAssistantButtons from "./components/ai-assistant-buttons.vue"; const timChatManager = globalTimChatManager; +const PENDING_FOLLOWUP_SEND_STORAGE_KEY = "ykt_followup_pending_send"; +const pendingFollowUpSendConsumed = ref(false); +const initialMessageListLoaded = ref(false); + +function normalizeGroupId(v) { + const s = String(v || "").trim(); + if (!s) return ""; + return s.startsWith("GROUP") ? s.slice(5) : s; +} + +async function tryConsumePendingFollowUpSend() { + if (pendingFollowUpSendConsumed.value) return; + + // 等待 IM 就绪与首屏消息加载完成,避免发送的本地消息被列表回调覆盖 + if (!timChatManager?.isLoggedIn) return; + if (!initialMessageListLoaded.value) return; + + const raw = uni.getStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY); + const payload = raw && typeof raw === "object" ? raw : null; + if (!payload) return; + + const createdAt = Number(payload.createdAt || 0) || 0; + // 过期就清理,避免误发送 + if (createdAt && Date.now() - createdAt > 5 * 60 * 1000) { + uni.removeStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY); + return; + } + + const payloadConversationID = String(payload.conversationID || ""); + const payloadGroupId = normalizeGroupId(payload.groupId || ""); + const currentConversationID = String(chatInfo.value.conversationID || ""); + const currentGroupId = normalizeGroupId(groupId.value || ""); + + // 必须匹配当前会话,才允许发送 + if (!payloadConversationID || payloadConversationID !== currentConversationID) return; + if (payloadGroupId && currentGroupId && payloadGroupId !== currentGroupId) return; + + const messages = Array.isArray(payload.messages) ? payload.messages : []; + const context = payload.context && typeof payload.context === "object" ? payload.context : {}; + if (!messages.length) { + uni.removeStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY); + return; + } + + pendingFollowUpSendConsumed.value = true; + // 先清理再发,避免页面重复初始化导致二次发送 + uni.removeStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY); + + await nextTick(); + const ok = await handleFollowUpMessages(messages, context); + if (ok) { + uni.showToast({ title: "消息已发送", icon: "success" }); + } else { + uni.showToast({ title: "部分发送失败", icon: "none" }); + } +} + // 获取环境变量 const env = __VITE_ENV__; const corpId = env.MP_CORP_ID || ""; @@ -367,35 +425,35 @@ function getBubbleClass(message) { return message.flow === "out" ? "user-bubble" : "doctor-bubble"; } -// 页面加载 -onLoad((options) => { - const decodeQueryValue = (v) => { - const s = typeof v === "string" ? v : String(v || ""); - if (!s) return ""; - try { - return decodeURIComponent(s); - } catch (e) { - return s; - } - }; - - const rawGroupId = decodeQueryValue(options.groupID || ""); - groupId.value = rawGroupId.startsWith("GROUP") ? rawGroupId.replace(/^GROUP/, "") : rawGroupId; - messageList.value = []; - isLoading.value = false; - if (options.conversationID) { - const cid = decodeQueryValue(options.conversationID); - chatInfo.value.conversationID = cid; - timChatManager.setConversationID(cid); - console.log("设置当前会话ID:", cid); - } - if (options.userID) { - chatInfo.value.userID = decodeQueryValue(options.userID); - } - - checkLoginAndInitTIM(); - updateNavigationTitle(); -}); +// 页面加载 +onLoad((options) => { + const decodeQueryValue = (v) => { + const s = typeof v === "string" ? v : String(v || ""); + if (!s) return ""; + try { + return decodeURIComponent(s); + } catch (e) { + return s; + } + }; + + const rawGroupId = decodeQueryValue(options.groupID || ""); + groupId.value = rawGroupId.startsWith("GROUP") ? rawGroupId.replace(/^GROUP/, "") : rawGroupId; + messageList.value = []; + isLoading.value = false; + if (options.conversationID) { + const cid = decodeQueryValue(options.conversationID); + chatInfo.value.conversationID = cid; + timChatManager.setConversationID(cid); + console.log("设置当前会话ID:", cid); + } + if (options.userID) { + chatInfo.value.userID = decodeQueryValue(options.userID); + } + + checkLoginAndInitTIM(); + updateNavigationTitle(); +}); // 检查登录状态并初始化IM const checkLoginAndInitTIM = async () => { @@ -527,15 +585,45 @@ const initTIMCallbacks = async () => { } }); - messageList.value = uniqueMessages; + // 合并现有 messageList,避免首屏加载覆盖刚发送的本地消息 + const merged = []; + const mergedSeen = new Set(); + const existing = Array.isArray(messageList.value) ? messageList.value : []; + + for (const m of existing) { + if (!m || !m.ID) continue; + if (m.conversationID !== chatInfo.value.conversationID) continue; + if (mergedSeen.has(m.ID)) continue; + mergedSeen.add(m.ID); + merged.push(m); + } + for (const m of uniqueMessages) { + if (!m || !m.ID) continue; + if (mergedSeen.has(m.ID)) continue; + mergedSeen.add(m.ID); + merged.push(m); + } + merged.sort((a, b) => { + const ta = Number(a?.lastTime || a?.time || 0) || 0; + const tb = Number(b?.lastTime || b?.time || 0) || 0; + return ta - tb; + }); + + messageList.value = merged; console.log( "消息列表已更新,原始", messages.length, "条,过滤后", - uniqueMessages.length, + messageList.value.length, "条消息" ); + if (!data.isPullUp && !data.isRefresh) { + initialMessageListLoaded.value = true; + // 首屏加载完成后再尝试消费待发送 payload + tryConsumePendingFollowUpSend(); + } + isCompleted.value = data.isCompleted || false; isLoadingMore.value = false; @@ -620,6 +708,9 @@ const loadMessageList = async () => { timChatManager.enterConversation(chatInfo.value.conversationID || "test1"); + // 若从病历回访记录带入待发送内容,则进入会话后自动发送 + tryConsumePendingFollowUpSend(); + // 标记会话为已读 - 确保清空未读数 if ( timChatManager.tim && From 3c3734d822152a545ebb36066a073631b716173b Mon Sep 17 00:00:00 2001 From: Jafeng <2998840497@qq.com> Date: Mon, 9 Feb 2026 17:09:44 +0800 Subject: [PATCH 4/8] =?UTF-8?q?fix:=E4=BC=98=E5=8C=96=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/archive-detail/follow-up-manage-tab.vue | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pages/case/components/archive-detail/follow-up-manage-tab.vue b/pages/case/components/archive-detail/follow-up-manage-tab.vue index 894dfb7..5fdedd0 100644 --- a/pages/case/components/archive-detail/follow-up-manage-tab.vue +++ b/pages/case/components/archive-detail/follow-up-manage-tab.vue @@ -75,6 +75,7 @@ Date: Mon, 9 Feb 2026 17:18:32 +0800 Subject: [PATCH 5/8] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/home/case-home.vue | 94 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 8 deletions(-) diff --git a/pages/home/case-home.vue b/pages/home/case-home.vue index 6f6558a..983638b 100644 --- a/pages/home/case-home.vue +++ b/pages/home/case-home.vue @@ -791,18 +791,52 @@ function resolveLatestRecord(lr) { return undefined; }; - const rawType = String(pick('medicalType', 'templateType') || '').trim(); - const uiType = normalizeMedicalType(rawType || pick('type')); - const typeLabel = String(pick('tempName', 'templateName', 'name', 'type') || '').trim(); + const rawType = String(pick('medicalType', 'templateType', 'type') || '').trim(); + const uiType = normalizeMedicalType(rawType); + // 注意:不要用 type 字段兜底“名称”,很多场景 type 是类型码(如 preConsultation / physicalExaminationTemplate) + const typeLabelRaw = String( + pick( + 'tempName', + 'templateName', + 'templateTitle', + 'tempTitle', + 'recordName', + 'medicalName', + 'medicalRecordName', + 'consultName', + 'preConsultationName', + 'inspectName', + 'physicalName', + 'name' + ) || '' + ).trim(); + const typeLabel = + typeLabelRaw && normalizeMedicalType(typeLabelRaw) === uiType ? '' : typeLabelRaw; - const rawDate = pick('date', 'visitTime', 'inhosDate', 'consultDate', 'inspectDate', 'sortTime'); + const rawDate = pick( + 'date', + 'visitTime', + 'inhosDate', + 'consultDate', + 'inspectDate', + 'sortTime', + 'createTime', + 'createdAt', + 'updateTime', + 'updatedAt', + 'time' + ); const rawDateStr = String(rawDate ?? '').trim(); const date = (/^\d{10,13}$/.test(rawDateStr) ? (formatAnyDate(rawDateStr, 'YYYY-MM-DD') || rawDateStr) : rawDateStr) - || formatAnyDate(pick('visitTime', 'inhosDate', 'consultDate', 'inspectDate', 'sortTime'), 'YYYY-MM-DD') + || formatAnyDate(pick('visitTime', 'inhosDate', 'consultDate', 'inspectDate', 'sortTime', 'createTime', 'updateTime'), 'YYYY-MM-DD') || '-'; let third = ''; - if (uiType === 'outpatient' || uiType === 'inhospital') { + // 后端若已计算出统一展示字段 diagnosis,则优先使用 + const directDiagnosis = normalizeText(pick('diagnosis', 'diagnosisName')); + if (String(directDiagnosis || '').trim()) { + third = directDiagnosis; + } else if (uiType === 'outpatient' || uiType === 'inhospital') { third = normalizeText(pick( 'diagnosisName', 'diagnosis', @@ -816,9 +850,53 @@ function resolveLatestRecord(lr) { 'inHospitalDiagnosis' )); } else if (uiType === 'preConsultation') { - third = normalizeText(pick('chiefComplaint', 'complaint', 'mainComplaint', 'mainSuit', 'chief')); + third = normalizeText( + pick( + 'diagnosis', + 'diagnosisName', + 'chiefComplaint', + 'chiefComplain', + 'chiefComplaintText', + 'chiefComplaintContent', + 'complaint', + 'complaintDesc', + 'complaintText', + 'mainComplaint', + 'mainSuit', + 'mainSuitText', + 'mainSuitContent', + 'chief', + 'zs', + 'zhuSu', + 'cc', + 'presentIllness', + 'historyOfPresentIllness', + 'currentIllness' + ) + ); } else if (uiType === 'physicalExaminationTemplate') { - third = normalizeText(pick('summary', 'inspectSummary', 'conclusion', 'inspectConclusion', 'inspectResult', 'finalConclusion')); + third = normalizeText( + pick( + 'diagnosis', + 'diagnosisName', + 'summary', + 'summaryText', + 'inspectSummary', + 'checkSummary', + 'examSummary', + 'physicalSummary', + 'briefSummary', + 'resultSummary', + 'conclusion', + 'conclusionText', + 'inspectConclusion', + 'inspectResult', + 'finalConclusion', + 'finalSummary', + 'reportConclusion', + 'reportSummary' + ) + ); } else { third = normalizeText(pick('diagnosis', 'diagnosisName', 'summary', 'chiefComplaint')); } From 7d445f7b8b504f15a090b6e13fa9aa5c5cb176cf Mon Sep 17 00:00:00 2001 From: Jafeng <2998840497@qq.com> Date: Mon, 9 Feb 2026 18:57:22 +0800 Subject: [PATCH 6/8] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=AE=8C=E5=96=84=E6=8F=90=E7=A4=BA=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=9B=B4=E6=96=B0=E6=90=9C=E7=B4=A2=E5=8D=A0?= =?UTF-8?q?=E4=BD=8D=E7=AC=A6=E5=92=8C=E6=8F=90=E7=A4=BA=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/useInfoCheck.js | 8 +- pages/case/search.vue | 165 +++++++++++++++++++++++++++++++++------ pages/home/case-home.vue | 19 +++-- 3 files changed, 159 insertions(+), 33 deletions(-) diff --git a/hooks/useInfoCheck.js b/hooks/useInfoCheck.js index d741127..d5989ee 100644 --- a/hooks/useInfoCheck.js +++ b/hooks/useInfoCheck.js @@ -9,11 +9,15 @@ export default function useInfoCheck() { function withInfo(fn) { return async (...args) => { if (!doctorInfo.value || !doctorInfo.value.anotherName) { - await confirm('请先完善您的个人信息,方可使用该功能!', { cancelText: '再等等', confirmText: '去完善' }) + try { + await confirm('请先完善您的个人信息,方可使用该功能!', { cancelText: '再等等', confirmText: '去完善' }) + } catch { + return; + } return uni.navigateTo({ url: '/pages/work/profile' }); } return fn(...args); } } return { withInfo } -} \ No newline at end of file +} diff --git a/pages/case/search.vue b/pages/case/search.vue index e4bad94..e8ffbe6 100644 --- a/pages/case/search.vue +++ b/pages/case/search.vue @@ -6,7 +6,7 @@ - 输入患者名称、手机号或院内ID号进行搜索 + 输入患者名称、手机号或病案号进行搜索 @@ -73,6 +73,7 @@ import { toast } from '@/utils/widget'; const searchQuery = ref(''); const searchResultsRaw = ref([]); const searching = ref(false); +const searchSeq = ref(0); const CURRENT_TEAM_STORAGE_KEY = 'ykt_case_current_team'; const DETAIL_STORAGE_KEY = 'ykt_case_archive_detail'; @@ -111,11 +112,29 @@ function formatPatient(raw) { const mobiles = asArray(raw?.mobiles).map(String).filter(Boolean); const mobile = raw?.mobile ? String(raw.mobile) : (mobiles[0] || ''); + const customerNumber = raw?.customerNumber || raw?.hospitalId || ''; + const customerProfileNo2 = raw?.customerProfileNo2 || ''; + const customerProfileNo3 = raw?.customerProfileNo3 || ''; + + function normalizeRecordTypeLabel(value) { + const s = String(value || '').trim(); + if (!s) return ''; + const lower = s.toLowerCase(); + const base = lower.endsWith('record') && s.length > 6 ? s.slice(0, -6) : s; + const baseLower = String(base).toLowerCase(); + if (baseLower === 'outpatient' || baseLower === 'out_patient' || baseLower === 'out-patient') return '门诊记录'; + if (baseLower === 'inhospital' || baseLower === 'in_hospital' || baseLower === 'in-hospital' || baseLower === 'inpatient') return '住院记录'; + if (baseLower === 'preconsultation' || baseLower === 'pre_consultation' || baseLower === 'pre-consultation' || baseLower === 'preconsultationtemplate') return '预问诊记录'; + if (baseLower === 'physicalexaminationtemplate' || baseLower === 'physicalexamination' || baseLower === 'physical_examination') return '体检记录'; + // 如果已经是中文(或后端已给展示名),直接返回 + return s; + } + // 解析病历信息 let record = null; if (raw?.latestRecord && typeof raw.latestRecord === 'object') { const lr = raw.latestRecord; - const type = lr.type || ''; + const type = normalizeRecordTypeLabel(lr.type || lr.medicalTypeName || lr.medicalType || ''); const date = lr.date || ''; const diagnosis = lr.diagnosis || ''; if (type || date || diagnosis) { @@ -139,10 +158,79 @@ function formatPatient(raw) { record, createTime: raw?.createTime || '', creator: raw?.creatorName || raw?.creator || '', - hospitalId: raw?.customerNumber || raw?.hospitalId || '', + hospitalId: customerNumber, + customerNumber, + customerProfileNo2, + customerProfileNo3, }; } +function normalizeText(value) { + return String(value || '').trim(); +} + +function normalizeDigits(value) { + const s = normalizeText(value); + return s.replace(/\D/g, ''); +} + +function isChinaMobile(value) { + const digits = normalizeDigits(value); + return /^1[3-9]\d{9}$/.test(digits); +} + +function isLikelyArchiveNo(value) { + const s = normalizeText(value); + if (!s) return false; + if (/[\u4e00-\u9fa5]/.test(s)) return false; // 中文一般是姓名 + if (isChinaMobile(s)) return false; + // 病案号一般为数字/字母组合(或纯数字) + return /^[0-9A-Za-z-]{3,}$/.test(s); +} + +function matchesAnyArchiveNo(patient, q) { + const needle = normalizeText(q).toLowerCase(); + if (!needle) return false; + const values = [ + patient?.customerNumber, + patient?.customerProfileNo2, + patient?.customerProfileNo3, + patient?.hospitalId, + ].map((v) => normalizeText(v).toLowerCase()).filter(Boolean); + + // 优先精确匹配 + if (values.some((v) => v === needle)) return true; + // 兜底:包含匹配(部分医院病案号前后可能带前缀/后缀) + return values.some((v) => v.includes(needle)); +} + +function matchesAnyMobile(patient, q) { + const needle = normalizeDigits(q); + if (!needle) return false; + const mobiles = [ + patient?.mobile, + ...(Array.isArray(patient?.mobiles) ? patient.mobiles : []), + patient?.phone, + patient?.phone1, + ].map(normalizeDigits).filter(Boolean); + return mobiles.some((m) => m === needle); +} + +async function fetchList(params) { + const res = await api('searchCorpCustomerForCaseList', params); + if (!res?.success) { + throw new Error(res?.message || '搜索失败'); + } + const payload = + res && typeof res === 'object' + ? res.data && typeof res.data === 'object' && !Array.isArray(res.data) + ? res.data + : res + : {}; + const list = Array.isArray(payload.list) ? payload.list : Array.isArray(payload.data) ? payload.data : []; + return list.map(formatPatient); +} + // Computed const searchResults = computed(() => { return searchResultsRaw.value || []; @@ -163,29 +251,58 @@ const doSearch = useDebounce(async () => { return; } + const seq = (searchSeq.value += 1); searching.value = true; - const res = await api('searchCorpCustomerForCaseList', { - corpId, - userId, - teamId, - name: q, - page: 1, - pageSize: 50, - }); - searching.value = false; + try { + if (isChinaMobile(q)) { + const digits = normalizeDigits(q); + const list = await fetchList({ + corpId, + userId, + teamId, + page: 1, + pageSize: 50, + mobile: digits, + }); + if (seq !== searchSeq.value) return; - if (!res?.success) { - toast(res?.message || '搜索失败'); - return; + // 后端为包含匹配,这里做一次精确过滤,避免“部分号段”带来误命中 + searchResultsRaw.value = list.filter((p) => matchesAnyMobile(p, digits)); + return; + } + + if (isLikelyArchiveNo(q)) { + const list = await fetchList({ + corpId, + userId, + teamId, + page: 1, + pageSize: 50, + archiveNo: q, + }); + if (seq !== searchSeq.value) return; + + // 兜底:后端字段不全时,仍然做一次本地匹配(包含 customerNumber/2/3) + searchResultsRaw.value = list.filter((p) => matchesAnyArchiveNo(p, q)); + return; + } + + const list = await fetchList({ + corpId, + userId, + teamId, + name: q, + page: 1, + pageSize: 50, + }); + if (seq !== searchSeq.value) return; + searchResultsRaw.value = list; + } catch (e) { + if (seq !== searchSeq.value) return; + toast(e?.message || '搜索失败'); + } finally { + if (seq === searchSeq.value) searching.value = false; } - const payload = - res && typeof res === 'object' - ? res.data && typeof res.data === 'object' && !Array.isArray(res.data) - ? res.data - : res - : {}; - const list = Array.isArray(payload.list) ? payload.list : Array.isArray(payload.data) ? payload.data : []; - searchResultsRaw.value = list.map(formatPatient); }, 600); const handleSearch = () => doSearch(); diff --git a/pages/home/case-home.vue b/pages/home/case-home.vue index 983638b..6febbee 100644 --- a/pages/home/case-home.vue +++ b/pages/home/case-home.vue @@ -130,6 +130,7 @@ import dayjs from 'dayjs'; import api from '@/utils/api'; import useAccountStore from '@/store/account'; +import useInfoCheck from '@/hooks/useInfoCheck'; import { confirm as uniConfirm, hideLoading, loading as showLoading, toast } from '@/utils/widget'; // State @@ -193,6 +194,7 @@ const currentTab = computed(() => tabs.value.find((t) => t.key === currentTabKey const accountStore = useAccountStore(); const { account, doctorInfo } = storeToRefs(accountStore); const { getDoctorInfo } = accountStore; +const { withInfo } = useInfoCheck(); const teamDisplay = computed(() => `${currentTeam.value?.name || ''}`); @@ -726,6 +728,10 @@ function normalizeMedicalType(raw) { const s = String(raw || '').trim(); if (!s) return ''; const lower = s.toLowerCase(); + // 常见后缀:xxxRecord(后端/历史数据可能返回此形式) + if (lower.endsWith('record') && s.length > 6) { + return normalizeMedicalType(s.slice(0, -6)); + } // 中文兜底(部分接口返回展示名) if (s.includes('门诊')) return 'outpatient'; if (s.includes('住院') || s.includes('入院')) return 'inhospital'; @@ -1211,15 +1217,14 @@ const goToSearch = () => { }); }; -const goToGroupManage = () => { +const goToGroupManage = withInfo(() => { if (checkBatchMode()) return; - if (!ensureUserInfoForFeature()) return; uni.navigateTo({ url: '/pages/case/group-manage' }); -}; +}); -const toggleBatchMode = () => { +const toggleBatchMode = withInfo(() => { if (isBatchMode.value) { // Already in batch mode, do nothing or prompt? // Prompt says "click Other operations... prompt please finish". @@ -1229,9 +1234,9 @@ const toggleBatchMode = () => { } isBatchMode.value = true; selectedItems.value = []; -}; +}); -const handleCreate = () => { +const handleCreate = withInfo(() => { if (checkBatchMode()) return; const rawMax = doctorInfo.value?.maxCustomerArchive; const hasMaxField = rawMax !== undefined && rawMax !== null && String(rawMax).trim() !== ''; @@ -1303,7 +1308,7 @@ const handleCreate = () => { } } }); -}; +}); // 新增流程:认证分支 const startVerifyFlow = () => { From 52fbf85b2a083bf3068373b5f751392b85b543d1 Mon Sep 17 00:00:00 2001 From: huxuejian Date: Mon, 9 Feb 2026 19:54:08 +0800 Subject: [PATCH 7/8] Update full-page.vue --- components/full-page.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/full-page.vue b/components/full-page.vue index 4453441..266b36f 100644 --- a/components/full-page.vue +++ b/components/full-page.vue @@ -22,7 +22,7 @@ - + From 4d6ad8489d005058bfd9cc698839f81fc6a58d25 Mon Sep 17 00:00:00 2001 From: huxuejian Date: Mon, 9 Feb 2026 19:59:54 +0800 Subject: [PATCH 8/8] Update .env.production --- .env.production | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.production b/.env.production index 0f7253f..2133616 100644 --- a/.env.production +++ b/.env.production @@ -4,7 +4,7 @@ MP_CACHE_PREFIX=production MP_WX_APP_ID=wx1d8337a40c11d66c MP_CORP_ID=wpLgjyawAA8N0gWmXgyJq8wpjGcOT7fg MP_TIM_SDK_APP_ID=1600123876 -MP_INVITE_TEAMMATE_QRCODE=https://ykt.youcan365.com/invite-teammate -MP_INVITE_PATIENT_QRCODE=https://ykt.youcan365.com/invite-patient +MP_INVITE_TEAMMATE_QRCODE=https://www.youcan365.com/invite-teammate +MP_INVITE_PATIENT_QRCODE=https://www.youcan365.com/invite-patient MP_PATIENT_PAGE_BASE_URL= 'https://www.youcan365.com/h5/#/' MP_SURVEY_URL= 'https://www.youcan365.com/surveyDev/#/pages/survey/survey'