feat(msg): 计数口径改消息总数 + 标识切换 appId
build / build (push) Successful in 43s

- 按分类计数改分类总数(sysTotal/bizTotal/approvalTotal,与列表内容对齐),已读=已读总数、全部=全部总数
- 按应用角标改 countByApp 按应用消息总数(含已读,已读后不清零),总数>0 才显示、总数降序
- 应用匹配/筛选全面改 appId:行与计数出参带 appId、筛选传 appId,appKey 不再出现在消息中心前端
This commit is contained in:
chenxing
2026-10-08 16:37:22 +08:00
parent 3250de382a
commit f9f97db272
2 changed files with 49 additions and 44 deletions
+13 -9
View File
@@ -107,18 +107,18 @@ class Base extends Abstract {
// ===== 消息中心代理(§3.5:经后端签名代理 UserMsg,前端不直连)=====
/**
* 消息中心分页列表({total, list[]};元素含改写后的绝对 jumpUrl)。
* 可选服务端筛选:msgType 0/1/2、appKey、isRead 0/1、keyword 标题/内容模糊。
* 消息中心分页列表({total, list[]};元素含改写后的绝对 jumpUrl、appId/appName)。
* 可选服务端筛选:msgType 0/1/2、appId(注册表应用 id)、isRead 0/1、keyword 标题/内容模糊。
* @memberof Basic
*/
getMsgCenterList(params: PageParams & { msgType?: number; appKey?: string; isRead?: number; keyword?: string }) {
getMsgCenterList(params: PageParams & { msgType?: number; appId?: string; isRead?: number; keyword?: string }) {
const url = path + "/msgCenter/list";
return this.getRequest<PageData>({ url, params });
}
/**
* 铃铛摘要:未读数 + 各应用待办角标 + 分类计数(sysUnread/bizUnread/approvalUnread/total/readCount,
* 旧版 UserMsg 无 summary 端点时这些字段缺省,前端按降级处理)
* 铃铛摘要:未读数 + 各应用待办角标 + 分类计数(sysUnread/bizUnread/approvalUnread 为未读口径,
* sysTotal/bizTotal/approvalTotal/total/readCount 为总数口径;旧版 UserMsg 无这些端点字段时缺省,前端按降级处理)
* @memberof Basic
*/
getMsgCenterSummary() {
@@ -129,18 +129,22 @@ class Base extends Abstract {
sysUnread?: number;
bizUnread?: number;
approvalUnread?: number;
sysTotal?: number;
bizTotal?: number;
approvalTotal?: number;
total?: number;
readCount?: number;
}>({ url });
}
/**
* 消息中心:按来源应用未读数映射(「按应用」清单角标):{appKey: 未读数},无未读的 appKey 不出现
* 消息中心:按来源应用消息计数(「按应用」清单角标):[{appId, appName, total, unread}],
* total 含已读;无消息的应用不出现
* @memberof Basic
*/
getMsgCenterUnreadCountByApp() {
const url = path + "/msgCenter/unreadCountByApp";
return this.getRequest<Record<string, number>>({ url });
getMsgCenterCountByApp() {
const url = path + "/msgCenter/countByApp";
return this.getRequest<{ appId: number | null; appName: string | null; total: number; unread: number }[]>({ url });
}
/**
+36 -35
View File
@@ -23,7 +23,7 @@
</div>
</div>
<!-- 模式二:按应用(应用清单=GetUserApp 用户应用全集;角标=unreadCountByApp 按应用未读数) -->
<!-- 模式二:按应用(应用清单=GetUserApp 用户应用全集;角标=countByApp 消息总数,0 不显示) -->
<div v-show="mode === 'app'">
<div class="side-title">全部应用</div>
<div :class="['cat', { active: curApp === 'all' }]" @click="onClickApp('all')">
@@ -32,9 +32,9 @@
<div class="side-sep"></div>
<div
v-for="a in appList"
:key="a.appKey"
:class="['cat', { active: curApp === a.appKey }]"
@click="onClickApp(a.appKey)"
:key="a.appId"
:class="['cat', { active: curApp === String(a.appId) }]"
@click="onClickApp(String(a.appId))"
>
<span class="dot dot-primary"></span>{{ a.name }}<span class="cnt">{{ a.cnt }}</span>
</div>
@@ -129,7 +129,7 @@ interface MsgRow {
content: string;
msgType: number; // 0 系统通知 / 1 业务提醒 / 2 审批待办
appName?: string | null;
appKey?: string | null;
appId?: number | null; // 注册表应用 id(appKey 不下发前端)
sendTime?: string; // yyyy-MM-dd HH:mm:ss
isRead: number; // 1=已读
jumpUrl?: string | null;
@@ -158,39 +158,40 @@ const loadError = ref(false);
const hasMore = computed(() => rows.value.length < total.value);
const refMsgBody = ref<HTMLElement | null>(null);
/** summary 左栏计数(旧版 UserMsg 无 summary 端点时分类字段缺省 → 显示 —) */
const catCounts = ref<{ sysUnread?: number; bizUnread?: number; approvalUnread?: number; total?: number; readCount?: number }>({});
/** summary 左栏计数(分类总数口径;旧版 UserMsg 无字段时缺省 → 显示 —) */
const catCounts = ref<{ sysTotal?: number; bizTotal?: number; approvalTotal?: number; total?: number; readCount?: number }>({});
const summaryTotalHint = computed(() => (catCounts.value.total == null ? "—" : String(catCounts.value.total)));
function isUnread(m: MsgRow) {
return m.isRead !== 1;
}
/** appName 缺失降级:按 appKey 匹配用户应用清单(与桌面网格同源),再退类型标签 */
/** appName 缺失降级:按 appId 匹配用户应用清单(与桌面网格同源),再退类型标签 */
function typeAppName(m: MsgRow) {
if (m.appKey) {
const hit = userApps.value.find(a => a.appKey === String(m.appKey));
if (m.appId != null) {
const hit = userApps.value.find(a => a.appId === m.appId);
if (hit && hit.name) return hit.name;
}
return typeName(m.msgType);
}
/** 分类导航(计数走 summary:分类未读数;「已读」= 已读总数) */
/** 分类导航(计数=分类消息总数含已读,与列表内容对齐;「全部」=全部总数、「已读」=已读总数) */
const catList = computed(() => [
{ key: "all", name: "全部消息", color: "#5979ea", cnt: catCounts.value.total == null ? "—" : catCounts.value.total },
{ key: "sys", name: "系统通知", color: "#5979ea", cnt: catCounts.value.sysUnread == null ? "—" : catCounts.value.sysUnread },
{ key: "biz", name: "业务提醒", color: "#e9b954", cnt: catCounts.value.bizUnread == null ? "—" : catCounts.value.bizUnread },
{ key: "appr", name: "审批待办", color: "#e8684a", cnt: catCounts.value.approvalUnread == null ? "—" : catCounts.value.approvalUnread },
{ key: "sys", name: "系统通知", color: "#5979ea", cnt: catCounts.value.sysTotal == null ? "—" : catCounts.value.sysTotal },
{ key: "biz", name: "业务提醒", color: "#e9b954", cnt: catCounts.value.bizTotal == null ? "—" : catCounts.value.bizTotal },
{ key: "appr", name: "审批待办", color: "#e8684a", cnt: catCounts.value.approvalTotal == null ? "—" : catCounts.value.approvalTotal },
{ key: "read", name: "已读", color: "#c0c6ce", cnt: catCounts.value.readCount == null ? "—" : catCounts.value.readCount },
]);
/** 应用导航:清单=用户应用全集(GetUserApp,BoaApp 自带 appKey/name,与桌面网格同源);角标=按应用未读数 */
const userApps = ref<{ appKey: string; name: string }[]>([]);
const unreadByApp = ref<Record<string, number>>({});
/** 应用导航:清单=用户应用全集(GetUserApp);角标=countByApp 按应用消息总数(含已读),0 不显示 */
const userApps = ref<{ appId: number; name: string }[]>([]);
const appCountByAppId = ref<Record<number, { total: number; unread: number }>>({});
const appList = computed(() =>
userApps.value
.map(a => ({ appKey: a.appKey, name: a.name, cnt: unreadByApp.value[a.appKey] || 0 }))
.sort((x, y) => y.cnt - x.cnt) // 未读多的排前(稳定排序,同计数保持注册表顺序)
.map(a => ({ appId: a.appId, name: a.name, cnt: appCountByAppId.value[a.appId]?.total || 0 }))
.filter(a => a.cnt > 0)
.sort((x, y) => y.cnt - x.cnt) // 总数多的排前(稳定排序,同计数保持注册表顺序)
);
/** 应用清单一次拉齐(GetUserApp;失败降级空,不打扰列表) */
@@ -199,8 +200,8 @@ async function loadApps() {
const groups: any[] = await BaseApi.getUserApp(store.getCurUserId, store.getCurSchId);
userApps.value = (groups || [])
.flatMap((g: any) => (g && Array.isArray(g.apps) ? g.apps : []))
.filter((a: any) => a && a.appKey != null)
.map((a: any) => ({ appKey: String(a.appKey), name: a.name }));
.filter((a: any) => a && a.id != null)
.map((a: any) => ({ appId: Number(a.id), name: a.name }));
} catch (e) {
/* 应用清单失败降级空 */
}
@@ -213,13 +214,13 @@ const curTitle = computed(() => {
return hit ? hit.name : "全部消息";
}
if (curApp.value === "all") return "全部应用消息";
const hit = appList.value.find(a => a.appKey === curApp.value);
const hit = appList.value.find(a => String(a.appId) === curApp.value);
return hit ? hit.name : "全部应用消息";
});
/** 当前筛选条件 → 服务端参数(筛选语义归服务端,本地不过滤) */
/** 当前筛选条件 → 服务端参数(筛选语义归服务端,本地不过滤;应用筛选传注册表 appId) */
function currentParams(extra: { page: number }) {
const params: { page: number; count: number; msgType?: number; appKey?: string; isRead?: number; keyword?: string } = {
const params: { page: number; count: number; msgType?: number; appId?: string; isRead?: number; keyword?: string } = {
page: extra.page,
count: PAGE_SIZE,
};
@@ -229,7 +230,7 @@ function currentParams(extra: { page: number }) {
else if (curCat.value === "appr") params.msgType = 2;
else if (curCat.value === "read") params.isRead = 1;
} else if (curApp.value !== "all") {
params.appKey = curApp.value;
params.appId = curApp.value;
}
const kw = searchText.value.trim();
if (kw) params.keyword = kw;
@@ -270,28 +271,28 @@ function onScroll() {
}
}
/** 左栏计数一次拉齐(未读总数进 store 与铃铛同源;按应用未读数一并拉取) */
/** 左栏计数一次拉齐(未读总数进 store 与铃铛同源;按应用消息计数一并拉取) */
async function loadSummary() {
try {
const [s, byApp] = await Promise.all([
BaseApi.getMsgCenterSummary(),
BaseApi.getMsgCenterUnreadCountByApp().catch(() => null),
BaseApi.getMsgCenterCountByApp().catch(() => null),
]);
if (s) {
store.setUnreadCount(s.unreadCount);
catCounts.value = {
sysUnread: (s as any).sysUnread,
bizUnread: (s as any).bizUnread,
approvalUnread: (s as any).approvalUnread,
sysTotal: (s as any).sysTotal,
bizTotal: (s as any).bizTotal,
approvalTotal: (s as any).approvalTotal,
total: (s as any).total,
readCount: (s as any).readCount,
};
}
const map: Record<string, number> = {};
if (byApp && typeof byApp === "object") {
Object.entries(byApp).forEach(([k, v]) => (map[k] = Number(v) || 0));
}
unreadByApp.value = map;
const map: Record<number, { total: number; unread: number }> = {};
((byApp as any) || []).forEach((e: any) => {
if (e && e.appId != null) map[Number(e.appId)] = { total: Number(e.total) || 0, unread: Number(e.unread) || 0 };
});
appCountByAppId.value = map;
} catch (e) {
/* 摘要失败不打扰列表 */
}