增加风控列表总数并优化站内提醒
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user