增加风控列表总数并优化站内提醒

This commit is contained in:
zhangshy
2026-09-13 23:51:45 +08:00
parent 38fe6f3689
commit 2a3146e050
11 changed files with 160 additions and 24 deletions
@@ -6,6 +6,9 @@
.risk-priority { font-variant-numeric: tabular-nums; }
.risk-report-entry { display: grid; gap: var(--space-4); }
.risk-report-entry p { margin: 0; color: var(--muted); line-height: 1.7; }
.operations-pagination { align-items: center; justify-content: space-between; }
.pagination-summary { color: var(--muted); font-size: var(--fs-small); }
.pagination-actions { display: flex; gap: var(--space-2); }
.risk-assistant { padding: var(--space-5); }
.risk-assistant__intro { display: flex; justify-content: space-between; gap: var(--space-4); }
.risk-assistant__intro h2 { margin: 0; font-size: var(--fs-title); }
@@ -25,6 +28,7 @@
.risk-message--error { color: var(--danger); background: var(--danger-soft); }
.risk-message__tools { margin-top: var(--space-2); color: var(--muted); font-size: var(--fs-small); }
.risk-prompts { margin: var(--space-3) 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
.risk-prompts[hidden], .risk-context-bar .button[hidden] { display: none !important; }
.risk-prompts button { min-height: 34px; padding: 0 var(--space-3); color: var(--brand-dark); background: var(--brand-soft); border: 0; border-radius: var(--radius-sm); cursor: pointer; }
.risk-prompts button:disabled { cursor: not-allowed; opacity: .55; }
.risk-prompts--alert { padding-top: var(--space-2); border-top: 1px dashed var(--line); }
@@ -78,12 +82,16 @@
.business-section__raw summary { color: var(--brand-dark); cursor: pointer; }
.business-section__raw .json-view { margin-top: var(--space-2); }
.operations-hero__identity { display: flex; align-items: center; gap: var(--space-2); }
.hero-meta-button { width: 26px; min-width: 26px; height: 26px; color: rgba(247,251,250,.9); background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.25); }
.risk-notice-button { min-height: 34px; padding: 0 var(--space-3); display: inline-flex; align-items: center; gap: var(--space-2); color: #f7fbfa; background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.28); border-radius: 999px; cursor: pointer; font-size: var(--fs-small); font-weight: 700; transition: background 160ms ease, border-color 160ms ease, transform 160ms ease; }
.risk-notice-button:hover, .risk-notice-button:focus-visible { background: rgba(255,255,255,.2); border-color: rgba(255,255,255,.48); outline: none; transform: translateY(-1px); }
.risk-notice-button__icon { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.risk-notice-button__count { min-width: 20px; height: 20px; padding: 0 6px; display: inline-grid; place-items: center; color: #8c2e20; background: #ffe4df; border-radius: 999px; font-size: 11px; font-variant-numeric: tabular-nums; }
.system-tip-list { display: grid; gap: var(--space-3); }
.system-tip-item { padding: var(--space-3); background: var(--surface-soft); border-radius: var(--radius-sm); }
.system-tip-item__body { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-3); align-items: start; }
.system-tip-item__header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.system-tip-item__header strong { overflow-wrap: anywhere; }
.system-tip-item p { margin: var(--space-2) 0 0; color: var(--muted); line-height: 1.65; overflow-wrap: anywhere; }
.system-tip-item__meta { margin-top: var(--space-2); display: flex; flex-wrap: wrap; gap: var(--space-2); color: var(--muted); font-size: var(--fs-small); }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
@media (max-width: 620px) { .risk-assistant { padding: var(--space-4); } .risk-context-bar { align-items: flex-start; flex-direction: column; } .risk-alert-context-form { grid-template-columns: 1fr; } .risk-chat { height: 320px; } .risk-message { max-width: 92%; } .risk-composer { grid-template-columns: 1fr; } .risk-report-controls { align-items: stretch; flex-direction: column; } .risk-report-controls .button { width: 100%; } .data-table__detail-grid, .evidence-snapshot__grid, .business-record__grid { grid-template-columns: 1fr; } }
@media (max-width: 620px) { .risk-assistant { padding: var(--space-4); } .risk-context-bar { align-items: flex-start; flex-direction: column; } .risk-alert-context-form { grid-template-columns: 1fr; } .risk-chat { height: 320px; } .risk-message { max-width: 92%; } .risk-composer { grid-template-columns: 1fr; } .risk-report-controls { align-items: stretch; flex-direction: column; } .risk-report-controls .button { width: 100%; } .data-table__detail-grid, .evidence-snapshot__grid, .business-record__grid { grid-template-columns: 1fr; } .system-tip-item__body { grid-template-columns: 1fr; } }
@@ -1,4 +1,4 @@
import { apiClient } from '/static/portal/common/api-client.js?v=20260913-8';
import { apiClient } from '/static/portal/common/api-client.js?v=20260913-11';
import { getAuthContext, getPermissions, requireRiskStaff, updateAuthPermissions } from '/static/portal/common/auth.js?v=20260913';
import { escapeHtml, formatDateTime } from '/static/portal/common/formatters.js';
import { mountShell } from '/static/portal/common/layout/app-shell.js';
@@ -235,10 +235,17 @@ if (requireRiskStaff()) {
current.next = meta.next_cursor || null;
document.querySelector(`[data-${kind}-prev]`).disabled = current.page === 0;
document.querySelector(`[data-${kind}-next]`).disabled = !current.next;
const total = Number(meta.total ?? 0);
const pageSize = Number(meta.page_size ?? (kind === 'alert' ? 5 : 10));
const totalPages = total > 0 && pageSize > 0 ? Math.ceil(total / pageSize) : 0;
document.querySelector(`[data-${kind}-summary]`).textContent = total > 0
? `第 ${current.page + 1} / ${totalPages} 页,共 ${total} 条`
: '共 0 条';
}
async function loadAlerts() {
renderLoading(alertTable, 4);
document.querySelector('[data-alert-summary]').textContent = '正在加载';
try {
const response = await apiClient.get('RK002', { query: { ...state.alert.filters, cursor: state.alert.cursors[state.alert.page], limit: 5 } });
const items = Array.isArray(response.data) ? response.data : [];
@@ -263,6 +270,7 @@ if (requireRiskStaff()) {
async function loadEvidence() {
renderLoading(evidenceTable, 4);
document.querySelector('[data-evidence-summary]').textContent = '正在加载';
try {
updateEvidenceFilterVisibility();
const response = await apiClient.get('RK004', { pathParams: { source: state.evidence.source }, query: { ...state.evidence.filters, cursor: state.evidence.cursors[state.evidence.page], limit: 10 } });
@@ -281,6 +289,7 @@ if (requireRiskStaff()) {
async function loadNotifications() {
renderLoading(notificationTable, 4);
document.querySelector('[data-notification-summary]').textContent = '正在加载';
try {
const response = await apiClient.get('RK005', { query: { ...state.notification.filters, cursor: state.notification.cursors[state.notification.page], limit: 10 } });
const items = Array.isArray(response.data) ? response.data : [];
@@ -574,7 +583,7 @@ if (requireRiskStaff()) {
try {
const response = await apiClient.post('RK006', {});
showToast(`预警扫描完成,新建 ${response.data?.created_count ?? 0} 条`);
await Promise.all([loadOverview(), loadAlerts(), loadNotifications()]);
await Promise.all([loadOverview(), loadAlerts(), loadNotifications(), refreshSystemTipCount()]);
} catch (error) {
apiClient.reportError(error);
showToast(error.message || '预警扫描失败', 'error');
@@ -801,23 +810,63 @@ if (requireRiskStaff()) {
}
}
function isStationNotification(item) {
return String(item?.channel || '') === '站内提醒';
}
function updateSystemTipCount(items) {
const count = Array.isArray(items) ? items.filter(isStationNotification).length : 0;
const badge = document.querySelector('[data-system-tip-count]');
if (!badge) return;
badge.textContent = String(count);
badge.hidden = count === 0;
badge.title = count ? `最近站内提醒 ${count} 条` : '暂无站内提醒';
}
async function fetchStationNotifications() {
const response = await apiClient.get('RK005', { query: { limit: 10 } });
const items = Array.isArray(response.data) ? response.data : [];
const stationItems = items.filter(isStationNotification);
updateSystemTipCount(stationItems);
return stationItems;
}
async function refreshSystemTipCount() {
try {
await fetchStationNotifications();
} catch (error) {
updateSystemTipCount([]);
apiClient.reportError(error);
}
}
async function openSystemTips() {
const target = document.querySelector('[data-system-tips-list]');
if (!systemTipsDialog.open) systemTipsDialog.showModal();
renderLoading(target, 3);
try {
const response = await apiClient.get('RK005', { query: { limit: 10 } });
const items = Array.isArray(response.data) ? response.data : [];
const items = await fetchStationNotifications();
if (!items.length) {
renderEmpty(target, '暂无系统提示', '当前没有新的系统或通知记录。');
renderEmpty(target, '暂无站内提醒', '当前没有新的站内提醒。');
return;
}
target.innerHTML = `<div class="system-tip-list">${items.map((item) => `
<article class="system-tip-item">
<div class="system-tip-item__header"><strong>${escapeHtml(item.title || '系统提示')}</strong>${statusTag(item.send_status)}</div>
<p>${escapeHtml(item.content || '--')}</p>
<div class="system-tip-item__meta"><span>预警:${escapeHtml(item.alert_no || '--')}</span><span>渠道:${escapeHtml(item.channel || '--')}</span><span>时间:${escapeHtml(formatDateTime(item.send_time))}</span></div>
<div class="system-tip-item__body">
<div>
<div class="system-tip-item__header"><strong>${escapeHtml(item.title || '站内提醒')}</strong>${statusTag(item.send_status)}</div>
<p>${escapeHtml(item.content || '--')}</p>
<div class="system-tip-item__meta"><span>预警:${escapeHtml(item.alert_no || '--')}</span><span>时间:${escapeHtml(formatDateTime(item.send_time))}</span></div>
</div>
${item.alert_no ? `<button class="button table-action" type="button" data-open-tip-alert="${escapeHtml(item.alert_no)}">查看预警</button>` : ''}
</div>
</article>`).join('')}</div>`;
target.querySelectorAll('[data-open-tip-alert]').forEach((button) => {
button.addEventListener('click', () => {
systemTipsDialog.close();
openAlert(button.dataset.openTipAlert);
});
});
} catch (error) {
apiClient.reportError(error);
renderError(target, error, openSystemTips);
@@ -863,6 +912,10 @@ if (requireRiskStaff()) {
document.querySelectorAll('[data-close-report]').forEach((button) => button.addEventListener('click', () => reportDialog.close()));
document.querySelector('[data-system-tips]').addEventListener('click', openSystemTips);
document.querySelectorAll('[data-close-system-tips]').forEach((button) => button.addEventListener('click', () => systemTipsDialog.close()));
document.querySelector('[data-open-notification-records]').addEventListener('click', () => {
systemTipsDialog.close();
document.querySelector('[data-tab="notifications"]').click();
});
document.querySelector('[data-policy-tips]').addEventListener('click', () => policyDialog.showModal());
document.querySelectorAll('[data-close-policy]').forEach((button) => button.addEventListener('click', () => policyDialog.close()));
document.querySelector('[data-action-form]').addEventListener('submit', submitAction);
@@ -895,6 +948,7 @@ if (requireRiskStaff()) {
hydrateIdentity().catch((error) => { apiClient.reportError(error); showToast(error.message || '权限加载失败', 'error'); });
loadOverview();
loadAlerts();
refreshSystemTipCount();
updateChatContextUI();
updateEvidenceFilterVisibility();
}
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>风险工作台 · 南方财富</title>
<link rel="stylesheet" href="/static/portal/common/base.css">
<link rel="stylesheet" href="/static/portal/employee-risk/dashboard/dashboard.css?v=20260913-8">
<link rel="stylesheet" href="/static/portal/employee-risk/dashboard/dashboard.css?v=20260913-11">
</head>
<body>
<main id="main-content" class="page-shell operations-shell">
@@ -18,7 +18,14 @@
<div class="operations-hero__meta">
<div class="operations-hero__identity">
<strong data-staff-name>当前操作员</strong>
<button class="icon-button hero-meta-button" type="button" data-system-tips aria-label="系统提示" title="系统提示">i</button>
<button class="risk-notice-button" type="button" data-system-tips aria-haspopup="dialog" aria-label="站内提醒">
<svg class="risk-notice-button__icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M10.268 21a2 2 0 0 0 3.464 0"></path>
<path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"></path>
</svg>
<span>站内提醒</span>
<span class="risk-notice-button__count" data-system-tip-count hidden>0</span>
</button>
</div>
<span data-data-scope>数据范围加载中</span>
</div>
@@ -45,7 +52,7 @@
<div class="operations-toolbar__actions"><button class="button" type="submit">查询</button><button class="button button--primary" type="button" data-scan data-permission-required="risk:alert:scan">手动扫描</button></div>
</form>
<div data-alert-table></div>
<div class="operations-pagination"><button class="button table-action" type="button" data-alert-prev disabled>上一页</button><button class="button table-action" type="button" data-alert-next disabled>下一页</button></div>
<div class="operations-pagination"><span class="pagination-summary" data-alert-summary></span><div class="pagination-actions"><button class="button table-action" type="button" data-alert-prev disabled>上一页</button><button class="button table-action" type="button" data-alert-next disabled>下一页</button></div></div>
</section>
<section class="operations-view" data-view="evidence" hidden>
@@ -56,7 +63,7 @@
<div class="operations-toolbar__actions"><button class="button" type="submit">查询证据</button></div>
</form>
<div data-evidence-table></div>
<div class="operations-pagination"><button class="button table-action" type="button" data-evidence-prev disabled>上一页</button><button class="button table-action" type="button" data-evidence-next disabled>下一页</button></div>
<div class="operations-pagination"><span class="pagination-summary" data-evidence-summary></span><div class="pagination-actions"><button class="button table-action" type="button" data-evidence-prev disabled>上一页</button><button class="button table-action" type="button" data-evidence-next disabled>下一页</button></div></div>
</section>
<section class="operations-view" data-view="notifications" hidden>
@@ -66,7 +73,7 @@
<div class="operations-toolbar__actions"><button class="button" type="submit">查询通知</button></div>
</form>
<div data-notification-table></div>
<div class="operations-pagination"><button class="button table-action" type="button" data-notification-prev disabled>上一页</button><button class="button table-action" type="button" data-notification-next disabled>下一页</button></div>
<div class="operations-pagination"><span class="pagination-summary" data-notification-summary></span><div class="pagination-actions"><button class="button table-action" type="button" data-notification-prev disabled>上一页</button><button class="button table-action" type="button" data-notification-next disabled>下一页</button></div></div>
</section>
<section class="operations-view risk-assistant" data-view="assistant" hidden>
@@ -138,9 +145,9 @@
</dialog>
<dialog class="operations-dialog utility-dialog" data-system-tips-dialog>
<div class="operations-dialog__header"><h2>系统提示</h2><button class="button icon-button" type="button" data-close-system-tips aria-label="关闭">×</button></div>
<div class="operations-dialog__header"><h2>站内提醒</h2><button class="button icon-button" type="button" data-close-system-tips aria-label="关闭">×</button></div>
<div class="operations-dialog__body"><div data-system-tips-list></div></div>
<div class="operations-dialog__footer"><button class="button" type="button" data-close-system-tips>关闭</button></div>
<div class="operations-dialog__footer"><button class="button" type="button" data-open-notification-records>查看全部通知记录</button><button class="button" type="button" data-close-system-tips>关闭</button></div>
</dialog>
<dialog class="operations-dialog utility-dialog" data-policy-dialog>
@@ -149,6 +156,6 @@
<div class="operations-dialog__footer"><button class="button" type="button" data-close-policy>关闭</button></div>
</dialog>
<script type="module" src="/static/portal/employee-risk/dashboard/dashboard.js?v=20260913-8"></script>
<script type="module" src="/static/portal/employee-risk/dashboard/dashboard.js?v=20260913-11"></script>
</body>
</html>