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'