feat: 扩展风险规则体系、审批动态路由与预算中心列表化改造
- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制 - 引入费用审批动态路由、平台风险分级、预审与风险阶段管理 - 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板 - 新增 Hermes 风险线索收集器、Agent 链路追踪中心 - 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估 - 完善报销申请快速预览、权限控制与前端测试覆盖
This commit is contained in:
@@ -8,13 +8,14 @@ import {
|
||||
testBootstrapDatabase,
|
||||
testBootstrapRuntime
|
||||
} from '../services/bootstrap.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 { fetchCurrentAuthUser, 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 { normalizeAuthUserSnapshot } from '../utils/authUser.js'
|
||||
import {
|
||||
clearAuthSessionMetrics,
|
||||
finalizeAuthSession,
|
||||
@@ -140,10 +141,10 @@ function buildLegacyAdminUser(username = '') {
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePlatformAdminFlag(payload, roleCodes = []) {
|
||||
const username = String(payload?.username || payload?.account || '').trim().toLowerCase()
|
||||
const role = String(payload?.role || '').trim().toLowerCase()
|
||||
const normalizedRoleCodes = roleCodes.map((item) => String(item || '').trim().toLowerCase()).filter(Boolean)
|
||||
function resolvePlatformAdminFlag(payload, roleCodes = []) {
|
||||
const username = String(payload?.username || payload?.account || '').trim().toLowerCase()
|
||||
const role = String(payload?.role || '').trim().toLowerCase()
|
||||
const normalizedRoleCodes = roleCodes.map((item) => String(item || '').trim().toLowerCase()).filter(Boolean)
|
||||
|
||||
return (
|
||||
Boolean(payload?.isAdmin)
|
||||
@@ -152,46 +153,36 @@ function resolvePlatformAdminFlag(payload, roleCodes = []) {
|
||||
|| role === '管理员'
|
||||
|| role === '系统管理员'
|
||||
|| normalizedRoleCodes.includes('admin')
|
||||
)
|
||||
}
|
||||
|
||||
function readStoredUser() {
|
||||
if (typeof window === 'undefined') {
|
||||
return buildAnonymousUser()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeStoredAuthUser(payload = {}) {
|
||||
const user = normalizeAuthUserSnapshot(payload, {
|
||||
defaultName: DEFAULT_USER_NAME,
|
||||
defaultRole: DEFAULT_USER_ROLE
|
||||
})
|
||||
|
||||
return {
|
||||
...user,
|
||||
isAdmin: resolvePlatformAdminFlag(payload, user.roleCodes)
|
||||
}
|
||||
}
|
||||
|
||||
function readStoredUser() {
|
||||
if (typeof window === 'undefined') {
|
||||
return buildAnonymousUser()
|
||||
}
|
||||
|
||||
const raw = window.sessionStorage.getItem(AUTH_USER_KEY)
|
||||
|
||||
if (raw) {
|
||||
try {
|
||||
const payload = JSON.parse(raw)
|
||||
if (payload && typeof payload === 'object') {
|
||||
const username = String(payload.username || '').trim()
|
||||
const name = String(payload.name || username || DEFAULT_USER_NAME).trim()
|
||||
const roleCodes = Array.isArray(payload.roleCodes) ? payload.roleCodes.filter(Boolean) : []
|
||||
|
||||
return {
|
||||
username,
|
||||
name,
|
||||
role: String(payload.role || DEFAULT_USER_ROLE),
|
||||
department: String(payload.department || payload.departmentName || ''),
|
||||
departmentName: String(payload.departmentName || payload.department || ''),
|
||||
position: String(payload.position || ''),
|
||||
grade: String(payload.grade || ''),
|
||||
employeeNo: String(payload.employeeNo || payload.employee_no || ''),
|
||||
managerName: String(payload.managerName || payload.manager_name || ''),
|
||||
location: String(payload.location || ''),
|
||||
costCenter: String(payload.costCenter || payload.cost_center || ''),
|
||||
financeOwnerName: String(payload.financeOwnerName || payload.finance_owner_name || ''),
|
||||
riskProfile: payload.riskProfile && typeof payload.riskProfile === 'object' ? payload.riskProfile : {},
|
||||
roleCodes,
|
||||
email: String(payload.email || ''),
|
||||
avatar: String(payload.avatar || name.slice(0, 1).toUpperCase()),
|
||||
isAdmin: resolvePlatformAdminFlag(payload, roleCodes)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return buildLegacyAdminUser(readStoredUsername())
|
||||
try {
|
||||
const payload = JSON.parse(raw)
|
||||
if (payload && typeof payload === 'object') {
|
||||
return normalizeStoredAuthUser(payload)
|
||||
}
|
||||
} catch {
|
||||
return buildLegacyAdminUser(readStoredUsername())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,8 +232,18 @@ function persistAuthState(value, user = null, sessionId = '') {
|
||||
window.sessionStorage.removeItem(AUTH_LAST_ACTIVITY_KEY)
|
||||
clearAuthSessionMetrics()
|
||||
}
|
||||
|
||||
function clearSessionTimeout() {
|
||||
|
||||
function persistAuthUserSnapshot(user = {}) {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
const normalizedUser = user || buildAnonymousUser()
|
||||
window.sessionStorage.setItem(AUTH_USERNAME_KEY, String(normalizedUser.username || '').trim())
|
||||
window.sessionStorage.setItem(AUTH_USER_KEY, JSON.stringify(normalizedUser))
|
||||
}
|
||||
|
||||
function clearSessionTimeout() {
|
||||
if (typeof window === 'undefined' || !sessionTimeoutHandle) {
|
||||
return
|
||||
}
|
||||
@@ -337,10 +338,10 @@ function installSessionMonitoring() {
|
||||
window.addEventListener('beforeunload', handleSessionUnload, { passive: true })
|
||||
}
|
||||
|
||||
function syncAuthSession(options = {}) {
|
||||
const shouldNotify = Boolean(options.notify)
|
||||
|
||||
if (!readAuthState()) {
|
||||
function syncAuthSession(options = {}) {
|
||||
const shouldNotify = Boolean(options.notify)
|
||||
|
||||
if (!readAuthState()) {
|
||||
loggedIn.value = false
|
||||
currentUser.value = buildAnonymousUser()
|
||||
clearSessionTimeout()
|
||||
@@ -354,11 +355,31 @@ function syncAuthSession(options = {}) {
|
||||
|
||||
loggedIn.value = true
|
||||
currentUser.value = readStoredUser()
|
||||
scheduleSessionTimeout()
|
||||
return true
|
||||
}
|
||||
|
||||
function reconcileEntryRoute(router) {
|
||||
scheduleSessionTimeout()
|
||||
return true
|
||||
}
|
||||
|
||||
async function refreshCurrentUserFromBackend(options = {}) {
|
||||
if (!readAuthState()) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = await fetchCurrentAuthUser()
|
||||
const user = normalizeStoredAuthUser(payload)
|
||||
currentUser.value = user
|
||||
persistAuthUserSnapshot(user)
|
||||
return true
|
||||
} catch (error) {
|
||||
if (!options.silent) {
|
||||
toast(error.message || '当前用户信息刷新失败,请重新登录后再试。')
|
||||
}
|
||||
console.warn('Failed to refresh current user snapshot:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function reconcileEntryRoute(router) {
|
||||
const target = resolveEntryRoute()
|
||||
const current = router.currentRoute.value
|
||||
|
||||
@@ -376,10 +397,13 @@ export function installSessionNavigation(router) {
|
||||
}
|
||||
|
||||
fetchBootstrapState()
|
||||
.then((state) => {
|
||||
applyBootstrapState(state)
|
||||
setRuntimeApiBaseUrl(resolveBrowserApiBaseUrl(state))
|
||||
fetchSettings()
|
||||
.then((state) => {
|
||||
applyBootstrapState(state)
|
||||
setRuntimeApiBaseUrl(resolveBrowserApiBaseUrl(state))
|
||||
if (loggedIn.value) {
|
||||
refreshCurrentUserFromBackend({ silent: true })
|
||||
}
|
||||
fetchSettings()
|
||||
.then((snapshot) => {
|
||||
if (snapshot?.appearanceForm?.themeSkin) {
|
||||
setThemeSkin(snapshot.appearanceForm.themeSkin)
|
||||
@@ -653,11 +677,11 @@ async function handleLogin(credentials) {
|
||||
password: credentials.password
|
||||
})
|
||||
|
||||
const responseUser = response?.user || buildAnonymousUser()
|
||||
const responseRoleCodes = Array.isArray(responseUser.roleCodes) ? responseUser.roleCodes.filter(Boolean) : []
|
||||
const user = {
|
||||
...responseUser,
|
||||
roleCodes: responseRoleCodes,
|
||||
const responseUser = normalizeStoredAuthUser(response?.user || buildAnonymousUser())
|
||||
const responseRoleCodes = responseUser.roleCodes
|
||||
const user = {
|
||||
...responseUser,
|
||||
roleCodes: responseRoleCodes,
|
||||
isAdmin: resolvePlatformAdminFlag(responseUser, responseRoleCodes)
|
||||
}
|
||||
loggedIn.value = true
|
||||
@@ -738,8 +762,9 @@ export function useSystemState() {
|
||||
loginError,
|
||||
loginSubmitting,
|
||||
logout,
|
||||
resetFromClientEnv,
|
||||
resolveEntryRoute,
|
||||
resetFromClientEnv,
|
||||
refreshCurrentUserFromBackend,
|
||||
resolveEntryRoute,
|
||||
runtimeTestMessage,
|
||||
runtimeTestPassed,
|
||||
runtimeTesting,
|
||||
|
||||
Reference in New Issue
Block a user