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

@@ -44,6 +44,9 @@ const CHINESE_DAY_NUMBERS = {
: 10
}
const COMPOSER_DATE_RANGE_PREFIX_RE = /^20\d{2}-\d{1,2}-\d{1,2}(?:\s*至\s*20\d{2}-\d{1,2}-\d{1,2})?[,。\s]*/u
const COMPOSER_LABELED_TIME_PREFIX_RE = /^(?:业务)?发生时间[:]\s*[^,。\n]+(?:至\s*[^,。\n]+)?[,。\s]*/u
function normalizeComposerText(value) {
return String(value || '').trim().replace(/\s+/g, ' ')
}
@@ -85,7 +88,8 @@ function calculateBusinessDays(businessTimeContext) {
function stripBusinessTimePrefix(text) {
return normalizeComposerText(text)
.replace(/^(?:业务)?发生时间[:]\s*[^,。\n]+(?:至\s*[^,。\n]+)?[,。\s]*/u, '')
.replace(COMPOSER_LABELED_TIME_PREFIX_RE, '')
.replace(COMPOSER_DATE_RANGE_PREFIX_RE, '')
.trim()
}
@@ -183,7 +187,8 @@ export function useTravelReimbursementComposerTools({
buildReviewSlotMap,
isValidIsoDateString,
buildLocallySyncedReviewPayload,
formatDateInputValue
formatDateInputValue,
onComposerDateSelection
}) {
const composerDatePickerOpen = ref(false)
const composerDateMode = ref('single')
@@ -217,23 +222,19 @@ export function useTravelReimbursementComposerTools({
)
function buildComposerBusinessTimeLabel() {
if (composerDateMode.value === 'single') {
return `发生时间:${composerSingleDate.value}`
return composerSingleDate.value
}
if (composerRangeStartDate.value === composerRangeEndDate.value) {
return `发生时间:${composerRangeStartDate.value}`
return composerRangeStartDate.value
}
return `发生时间:${composerRangeStartDate.value}${composerRangeEndDate.value}`
return `${composerRangeStartDate.value}${composerRangeEndDate.value}`
}
function hasComposerBusinessTimeSelection() {
return composerBusinessTimeTags.value.length > 0 || composerBusinessTimeDraftTouched.value
}
function buildComposerBusinessTimeContext() {
if (!hasComposerBusinessTimeSelection()) {
return null
}
function buildComposerBusinessTimeContextFromSelection() {
const mode = composerDateMode.value === 'range' ? 'range' : 'single'
const startDate = String(mode === 'range' ? composerRangeStartDate.value : composerSingleDate.value).trim()
const endDate = String(mode === 'range' ? composerRangeEndDate.value : startDate).trim()
@@ -255,6 +256,28 @@ export function useTravelReimbursementComposerTools({
}
}
function buildComposerBusinessTimeContext() {
if (!hasComposerBusinessTimeSelection()) {
return null
}
return buildComposerBusinessTimeContextFromSelection()
}
function buildComposerBusinessTimeSelection() {
const context = buildComposerBusinessTimeContextFromSelection()
if (!context) {
return null
}
return {
label: buildComposerBusinessTimeLabel(),
context,
mode: context.mode,
startDate: context.start_date,
endDate: context.end_date
}
}
function mergeBusinessTimeIntoExtraContext(extraContext, businessTimeContext) {
if (!businessTimeContext) {
return extraContext
@@ -345,9 +368,60 @@ export function useTravelReimbursementComposerTools({
composerDateMode.value = mode === 'range' ? 'range' : 'single'
}
function handleComposerDateInputChange() {
composerBusinessTimeDraftTouched.value = true
syncComposerBusinessTimeToReviewCard(buildComposerBusinessTimeContext())
async function commitComposerDateSelection({ closePicker = true, focusComposer = true } = {}) {
if (!composerCanApplyDateSelection.value) {
return false
}
const selection = buildComposerBusinessTimeSelection()
if (!selection) {
return false
}
const handled = onComposerDateSelection?.(selection) === true
if (handled) {
composerBusinessTimeDraftTouched.value = false
composerBusinessTimeTags.value = []
} else {
composerBusinessTimeDraftTouched.value = true
composerBusinessTimeTags.value = [
{
id: `biz-time-${Date.now()}`,
label: selection.label
}
]
syncComposerBusinessTimeToReviewCard(selection.context)
}
if (closePicker) {
composerDatePickerOpen.value = false
}
await nextTick()
adjustComposerTextareaHeight()
if (focusComposer) {
composerTextareaRef.value?.focus()
}
return true
}
function handleComposerDateInputChange(part = 'single') {
if (composerDateMode.value !== 'range' || part === 'single') {
void commitComposerDateSelection()
return
}
if (part === 'range-start') {
if (!composerRangeEndDate.value || composerRangeEndDate.value < composerRangeStartDate.value) {
composerRangeEndDate.value = composerRangeStartDate.value
}
if (!onComposerDateSelection) {
composerBusinessTimeDraftTouched.value = true
syncComposerBusinessTimeToReviewCard(buildComposerBusinessTimeContextFromSelection())
}
return
}
void commitComposerDateSelection()
}
function removeComposerBusinessTimeTag(tagId) {
@@ -376,22 +450,7 @@ export function useTravelReimbursementComposerTools({
}
async function applyComposerDateSelection() {
if (!composerCanApplyDateSelection.value) {
return
}
composerBusinessTimeDraftTouched.value = true
composerBusinessTimeTags.value = [
{
id: `biz-time-${Date.now()}`,
label: buildComposerBusinessTimeLabel()
}
]
syncComposerBusinessTimeToReviewCard(buildComposerBusinessTimeContext())
composerDatePickerOpen.value = false
await nextTick()
adjustComposerTextareaHeight()
composerTextareaRef.value?.focus()
await commitComposerDateSelection()
}
function resolveTravelCalculatorInitialDays() {
@@ -547,6 +606,7 @@ export function useTravelReimbursementComposerTools({
travelCalculatorCanSubmit,
buildComposerBusinessTimeLabel,
hasComposerBusinessTimeSelection,
buildComposerBusinessTimeSelection,
buildComposerBusinessTimeContext,
mergeBusinessTimeIntoExtraContext,
syncComposerBusinessTimeToReviewCard,