feat: 新增风险图谱算法与系统仪表盘及操作反馈体系
后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL 校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计, 优化 agent 运行和编排执行链路,清理旧开发文档,前端新增 系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈 对话框和工作台日期选择器,优化报销创建和审批详情交互, 补充单元测试覆盖。
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user