功能(提醒):统一项目提醒中心与桌面通知链路
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled

增加通用提醒状态、数据库迁移和定时同步,覆盖风险时效、文件回执、项目里程碑、访视窗口与协作申请。

新增网页端和桌面端提醒中心、真实投递诊断与固定隐私通知正文,并补齐登录来源聚合、测试和说明文档。
This commit is contained in:
Cheng Zhou
2026-07-16 16:50:34 +08:00
parent 88bd0c5942
commit 3e77127687
50 changed files with 2894 additions and 247 deletions
@@ -260,6 +260,9 @@ describe("ApiEndpointPermissions.vue", () => {
]);
expect((wrapper.vm as any).filteredOperations.filter((operation: any) => operation.operation_key === "subject_aes:read")).toHaveLength(2);
expect((wrapper.vm as any).filteredOperations.filter((operation: any) => operation.operation_key === "subject_pds:list")).toHaveLength(2);
expect((wrapper.vm as any).displayOperations).toHaveLength(5);
expect((wrapper.vm as any).totalPermissionCount).toBe(3);
expect((wrapper.vm as any).filteredPermissionCount).toBe(3);
});
it("keeps contract fee permissions as one module section", () => {
@@ -6,7 +6,7 @@
<span class="toolbar-title">有效权限矩阵</span>
<el-tag size="small" effect="plain" type="info">只读</el-tag>
</div>
<span class="toolbar-result">显示 {{ filteredOperations.length }} / {{ displayOperations.length }} 项权限</span>
<span class="toolbar-result">显示 {{ filteredPermissionCount }} / {{ totalPermissionCount }} 项权限</span>
</div>
<div class="toolbar-filters">
<el-input
@@ -266,6 +266,12 @@ const filteredOperations = computed(() => {
return filtered;
});
const countUniquePermissions = (rows: DisplayOperation[]): number =>
new Set(rows.map((operation) => operation.operation_key)).size;
const totalPermissionCount = computed(() => countUniquePermissions(displayOperations.value));
const filteredPermissionCount = computed(() => countUniquePermissions(filteredOperations.value));
// 权限类型、模块与模块细分列合并行
const spanMethod = ({ row, rowIndex, columnIndex }: { row: DisplayOperation; rowIndex: number; column: any; columnIndex: number }) => {
if (columnIndex === 0) {
+34
View File
@@ -296,6 +296,18 @@
<div v-else class="reminder-empty">
{{ headerRemindersLoading ? TEXT.common.loading : TEXT.common.labels.projectRemindersEmpty }}
</div>
<div class="reminder-actions">
<el-dropdown-item command="notification:center" class="reminder-footer">
查看全部提醒
</el-dropdown-item>
<el-dropdown-item
v-if="headerReminderTotal > 0"
command="notification:read-all"
class="reminder-footer is-secondary"
>
全部已读
</el-dropdown-item>
</div>
</div>
</el-dropdown-menu>
</template>
@@ -2148,6 +2160,28 @@ useDesktopShortcuts(
text-align: center;
}
.reminder-actions {
display: grid;
grid-template-columns: 1fr 1fr;
margin-top: 6px;
border-top: 1px solid rgba(138, 155, 176, 0.2);
}
.reminder-actions .reminder-footer:only-child {
grid-column: 1 / -1;
}
.reminder-footer {
justify-content: center;
color: #2f6fed !important;
font-size: 12px;
font-weight: 700;
}
.reminder-footer.is-secondary {
color: #718096 !important;
}
.activity-button {
position: relative;
}
+22 -8
View File
@@ -15,6 +15,7 @@ const readTauriLibSource = () => readSource(resolve(__dirname, "../../src-tauri/
const readDesktopPreferencesSource = () => readSource(resolve(__dirname, "../views/DesktopPreferences.vue"));
const readDesktopServerSettingsSource = () => readSource(resolve(__dirname, "../views/DesktopServerSettings.vue"));
const readDesktopUpdateManagerSource = () => readSource(resolve(__dirname, "../session/desktopUpdateManager.ts"));
const readDesktopNotificationManagerSource = () => readSource(resolve(__dirname, "../session/desktopNotificationManager.ts"));
const readDesktopActivityCenterSource = () => readSource(resolve(__dirname, "../session/desktopActivityCenter.ts"));
const readProfileSettingsSource = () => readSource(resolve(__dirname, "../views/ProfileSettings.vue"));
const readLoginSource = () => readSource(resolve(__dirname, "../views/Login.vue"));
@@ -484,9 +485,10 @@ describe("desktop layout shell", () => {
expect(navigation).toContain("item.children?.length ? item.children : [item]");
});
it("keeps notification and update preferences lightweight", () => {
it("shows the real notification delivery path and keeps native notification access constrained", () => {
const serverSettings = readDesktopServerSettingsSource();
const preferences = readDesktopPreferencesSource();
const desktopNotificationManager = readDesktopNotificationManagerSource();
const desktopUpdateManager = readDesktopUpdateManagerSource();
const desktopLayout = readDesktopLayoutSource();
@@ -503,17 +505,25 @@ describe("desktop layout shell", () => {
expect(preferences).not.toContain("服务器连通性正常");
expect(preferences).not.toContain("服务器连接已确认");
expect(preferences).toContain("auth.logout({ rememberCurrentStudy: false })");
expect(preferences).not.toContain("系统通知已开启");
expect(preferences).not.toContain("系统通知已关闭");
expect(preferences).not.toContain("已授权");
expect(preferences).toContain("showNotificationPermissionTag");
expect(preferences).toContain("系统权限:{{ notificationPermissionText }}");
expect(preferences).toContain("服务端订阅");
expect(preferences).toContain("真实业务路径");
expect(preferences).toContain("授权并开启");
expect(preferences).toContain("重新检测权限");
expect(preferences).toContain("立即检查业务提醒");
expect(preferences).toContain("系统设置 → 通知 → CTMS");
expect(preferences).toContain("设置 → 系统 → 通知 → CTMS");
expect(preferences).toContain("showSystemNotificationProbe");
expect(preferences).toContain("sendDesktopNotificationTest");
expect(preferences).toContain("测试通知");
expect(preferences).not.toContain("通知诊断");
expect(preferences).toContain("发送测试通知");
expect(preferences).toContain("openProjectNotificationCenter");
expect(preferences).toContain("listenDesktopNotificationStatus");
expect(preferences).not.toContain("更新诊断");
expect(preferences).not.toContain("listenDesktopNotificationDiagnostics");
expect(preferences).not.toContain(["@tauri-apps", "plugin-notification"].join("/"));
expect(desktopNotificationManager).toContain("claimDesktopNotifications");
expect(desktopNotificationManager).toContain("acknowledgeDesktopNotifications");
expect(desktopNotificationManager).toContain('state: "permission-required"');
expect(desktopNotificationManager).toContain('state: deliveredIds.length ? "delivered" : "ready"');
expect(preferences).toContain("listenDesktopUpdateStatus");
expect(preferences).toContain("promptForPendingDesktopUpdate");
expect(preferences).toContain("当前已是最新版本");
@@ -603,6 +613,10 @@ describe("desktop layout shell", () => {
expect(styles).toContain(".desktop-workbench .desktop-route-shell .el-table td.el-table__cell");
expect(styles).toContain("padding: 7px 10px;");
expect(styles).toContain(".desktop-workbench .desktop-route-shell .el-pagination");
expect(styles).toContain(".desktop-workbench .desktop-route-shell .el-radio-button__inner");
expect(styles).toContain("display: inline-flex;");
expect(styles).toContain("align-items: center;");
expect(styles).toContain("justify-content: center;");
expect(styles).toContain(".desktop-workbench .desktop-route-shell .el-drawer__body");
expect(styles).toContain(":root[data-ctms-theme=\"dark\"] .desktop-workbench .desktop-route-shell .hero-stat");
});
+27 -1
View File
@@ -289,7 +289,18 @@
<div v-else class="header-reminder-empty">
{{ headerRemindersLoading ? TEXT.common.loading : TEXT.common.labels.projectRemindersEmpty }}
</div>
<div class="header-reminder-actions">
<el-dropdown-item command="notification:center" class="header-reminder-footer">
查看全部提醒
</el-dropdown-item>
<el-dropdown-item
v-if="headerReminderTotal > 0"
command="notification:read-all"
class="header-reminder-footer is-secondary"
>
全部已读
</el-dropdown-item>
</div>
</div>
</el-dropdown-menu>
</template>
@@ -1848,6 +1859,21 @@ useDesktopShortcuts(
font-weight: 600;
}
.header-reminder-actions {
display: grid;
grid-template-columns: 1fr 1fr;
margin-top: 6px;
border-top: 1px solid rgba(148, 163, 184, 0.2);
}
.header-reminder-actions .header-reminder-footer:only-child {
grid-column: 1 / -1;
}
.header-reminder-footer.is-secondary {
color: #64748b !important;
}
.collapse-btn {
border: 1px solid transparent;
color: var(--ctms-text-secondary);