feat: 新增风险图谱算法与系统仪表盘及操作反馈体系

后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL
校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计,
优化 agent 运行和编排执行链路,清理旧开发文档,前端新增
系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈
对话框和工作台日期选择器,优化报销创建和审批详情交互,
补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-30 15:46:51 +08:00
parent 4c59941ec6
commit 7989f3a159
314 changed files with 30073 additions and 20626 deletions

View File

@@ -8,18 +8,24 @@ import {
testBootstrapDatabase,
testBootstrapRuntime
} from '../services/bootstrap.js'
import { login as loginByAccount } from '../services/auth.js'
import { login as loginByAccount } from '../services/auth.js'
import { setRuntimeApiBaseUrl } from '../services/api.js'
import { checkBackendHealth } from './useBackendHealth.js'
import { resolveDefaultAuthorizedRoute } from '../utils/accessControl.js'
import { useToast } from './useToast.js'
import { fetchSettings } from '../services/settings.js'
import { setThemeSkin } from './useThemeSkin.js'
import { fetchSettings } from '../services/settings.js'
import { setThemeSkin } from './useThemeSkin.js'
import {
clearAuthSessionMetrics,
finalizeAuthSession,
incrementAuthActivityCount,
persistAuthSessionMetrics
} from '../utils/authSessionMetrics.js'
const AUTH_STORAGE_KEY = 'x-financial-authenticated'
const AUTH_USERNAME_KEY = 'x-financial-auth-username'
const AUTH_USER_KEY = 'x-financial-auth-user'
const AUTH_LAST_ACTIVITY_KEY = 'x-financial-auth-last-activity'
const AUTH_USERNAME_KEY = 'x-financial-auth-username'
const AUTH_USER_KEY = 'x-financial-auth-user'
const AUTH_LAST_ACTIVITY_KEY = 'x-financial-auth-last-activity'
const DEFAULT_USER_NAME = '系统管理员'
const DEFAULT_USER_ROLE = '管理员'
const SESSION_ACTIVITY_EVENTS = ['pointerdown', 'keydown', 'scroll', 'touchstart', 'visibilitychange']
@@ -193,13 +199,13 @@ function readStoredUser() {
return legacyUsername ? buildLegacyAdminUser(legacyUsername) : buildAnonymousUser()
}
function readLastActivityAt() {
if (typeof window === 'undefined') {
return 0
}
return Number(window.sessionStorage.getItem(AUTH_LAST_ACTIVITY_KEY) || 0)
}
function readLastActivityAt() {
if (typeof window === 'undefined') {
return 0
}
return Number(window.sessionStorage.getItem(AUTH_LAST_ACTIVITY_KEY) || 0)
}
function isSessionExpired(now = Date.now()) {
if (!readAuthState()) {
@@ -215,24 +221,26 @@ function isSessionExpired(now = Date.now()) {
return now - lastActivityAt > authIdleTimeoutMs
}
function persistAuthState(value, user = null) {
if (typeof window === 'undefined') {
return
}
function persistAuthState(value, user = null, sessionId = '') {
if (typeof window === 'undefined') {
return
}
if (value) {
window.sessionStorage.setItem(AUTH_STORAGE_KEY, 'true')
const normalizedUser = user || buildAnonymousUser()
window.sessionStorage.setItem(AUTH_USERNAME_KEY, String(normalizedUser.username || '').trim())
window.sessionStorage.setItem(AUTH_USER_KEY, JSON.stringify(normalizedUser))
return
}
const normalizedUser = user || buildAnonymousUser()
window.sessionStorage.setItem(AUTH_USERNAME_KEY, String(normalizedUser.username || '').trim())
window.sessionStorage.setItem(AUTH_USER_KEY, JSON.stringify(normalizedUser))
persistAuthSessionMetrics(sessionId)
return
}
window.sessionStorage.removeItem(AUTH_STORAGE_KEY)
window.sessionStorage.removeItem(AUTH_USERNAME_KEY)
window.sessionStorage.removeItem(AUTH_USER_KEY)
window.sessionStorage.removeItem(AUTH_LAST_ACTIVITY_KEY)
}
window.sessionStorage.removeItem(AUTH_USERNAME_KEY)
window.sessionStorage.removeItem(AUTH_USER_KEY)
window.sessionStorage.removeItem(AUTH_LAST_ACTIVITY_KEY)
clearAuthSessionMetrics()
}
function clearSessionTimeout() {
if (typeof window === 'undefined' || !sessionTimeoutHandle) {
@@ -294,19 +302,27 @@ function touchAuthActivity(force = false) {
scheduleSessionTimeout()
return
}
window.sessionStorage.setItem(AUTH_LAST_ACTIVITY_KEY, String(now))
incrementAuthActivityCount()
lastActivityWriteAt = now
scheduleSessionTimeout()
}
window.sessionStorage.setItem(AUTH_LAST_ACTIVITY_KEY, String(now))
lastActivityWriteAt = now
scheduleSessionTimeout()
}
function handleSessionActivity(event) {
if (typeof document !== 'undefined' && event?.type === 'visibilitychange' && document.visibilityState !== 'visible') {
return
}
touchAuthActivity()
}
function handleSessionActivity(event) {
if (typeof document !== 'undefined' && event?.type === 'visibilitychange' && document.visibilityState !== 'visible') {
return
}
touchAuthActivity()
}
function handleSessionUnload(event) {
if (event?.type === 'pagehide' && event.persisted) {
return
}
finalizeAuthSession('pagehide', { unload: true })
}
function installSessionMonitoring() {
if (sessionMonitoringInstalled || typeof window === 'undefined') {
@@ -314,10 +330,12 @@ function installSessionMonitoring() {
}
sessionMonitoringInstalled = true
SESSION_ACTIVITY_EVENTS.forEach((eventName) => {
window.addEventListener(eventName, handleSessionActivity, { passive: true })
})
}
SESSION_ACTIVITY_EVENTS.forEach((eventName) => {
window.addEventListener(eventName, handleSessionActivity, { passive: true })
})
window.addEventListener('pagehide', handleSessionUnload, { passive: true })
window.addEventListener('beforeunload', handleSessionUnload, { passive: true })
}
function syncAuthSession(options = {}) {
const shouldNotify = Boolean(options.notify)
@@ -641,11 +659,11 @@ async function handleLogin(credentials) {
...responseUser,
roleCodes: responseRoleCodes,
isAdmin: resolvePlatformAdminFlag(responseUser, responseRoleCodes)
}
loggedIn.value = true
persistAuthState(true, user)
currentUser.value = user
touchAuthActivity(true)
}
loggedIn.value = true
persistAuthState(true, user, response?.sessionId || '')
currentUser.value = user
touchAuthActivity(true)
return true
} catch (error) {
logout('invalid', { redirect: false })
@@ -658,12 +676,13 @@ async function handleLogin(credentials) {
}
function logout(reason = 'manual', options = {}) {
const notify = options.notify ?? reason === 'timeout'
const redirect = options.redirect ?? reason !== 'invalid'
loggedIn.value = false
persistAuthState(false)
currentUser.value = buildAnonymousUser()
const notify = options.notify ?? reason === 'timeout'
const redirect = options.redirect ?? reason !== 'invalid'
finalizeAuthSession(reason)
loggedIn.value = false
persistAuthState(false)
currentUser.value = buildAnonymousUser()
clearSessionTimeout()
if (notify) {