feat: 增强风险规则生成引擎与预算中心页面
后端拆分风险规则生成为解释器、语义分析、本体对齐等子模块, 优化模板执行和流程图生成,完善员工种子数据和导入逻辑,增强 报销单权限策略和草稿持久化,前端新增预算中心视图和趋势图 组件,重构审计页面和风险规则测试对话框交互,完善文档中心 和报销创建页面细节,补充单元测试覆盖。
This commit is contained in:
@@ -12,6 +12,7 @@ import { useTravelReimbursementReviewDrawer } from './useTravelReimbursementRevi
|
||||
import { useTravelReimbursementSubmitComposer } from './useTravelReimbursementSubmitComposer.js'
|
||||
import { useTravelReimbursementReviewActions } from './useTravelReimbursementReviewActions.js'
|
||||
import { useTravelReimbursementGuidedFlow } from './useTravelReimbursementGuidedFlow.js'
|
||||
import { useApplicationPreviewEditor } from './useApplicationPreviewEditor.js'
|
||||
import { recognizeOcrFiles } from '../../services/ocr.js'
|
||||
import { fetchAgentRunDetail } from '../../services/agentAssets.js'
|
||||
import { deleteConversation, runOrchestrator } from '../../services/orchestrator.js'
|
||||
@@ -34,6 +35,12 @@ import {
|
||||
mergeComposerPrefill,
|
||||
resolveSuggestedActionPrefill
|
||||
} from '../../utils/assistantSuggestedActionPrefill.js'
|
||||
import {
|
||||
buildApplicationPreviewFooterMessage,
|
||||
buildApplicationPreviewSubmitText,
|
||||
buildLocalApplicationPreviewMessage,
|
||||
normalizeApplicationPreview
|
||||
} from '../../utils/expenseApplicationPreview.js'
|
||||
import {
|
||||
calculateTravelReimbursement,
|
||||
fetchExpenseClaims,
|
||||
@@ -520,6 +527,10 @@ export default {
|
||||
invalidatedDraftClaimId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
reopenToken: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
emits: ['close', 'draft-saved'],
|
||||
@@ -578,7 +589,22 @@ export default {
|
||||
const reviewActionBusy = ref(false)
|
||||
const deleteSessionBusy = ref(false)
|
||||
const reviewDrawerMode = ref(REVIEW_DRAWER_MODE_REVIEW)
|
||||
const {
|
||||
applicationPreviewEditor,
|
||||
resolveApplicationPreviewRows,
|
||||
resolveApplicationPreviewEditorControl,
|
||||
resolveApplicationPreviewEditorOptions,
|
||||
isApplicationPreviewEditing,
|
||||
openApplicationPreviewEditor,
|
||||
commitApplicationPreviewEditor,
|
||||
cancelApplicationPreviewEditor,
|
||||
handleApplicationPreviewEditorKeydown
|
||||
} = useApplicationPreviewEditor({
|
||||
persistSessionState,
|
||||
toast
|
||||
})
|
||||
const isKnowledgeSession = computed(() => activeSessionType.value === SESSION_TYPE_KNOWLEDGE)
|
||||
const isApplicationSession = computed(() => activeSessionType.value === SESSION_TYPE_APPLICATION)
|
||||
const activeAssistantMode = computed(() => resolveAssistantSessionMode(activeSessionType.value))
|
||||
const assistantHeaderTitle = computed(() => activeAssistantMode.value?.label || '财务助手')
|
||||
const assistantHeaderDescription = computed(() => activeAssistantMode.value?.description || '个人财务中心')
|
||||
@@ -642,9 +668,9 @@ export default {
|
||||
)
|
||||
})
|
||||
const hasQueryInsight = computed(() => Boolean(currentInsight.value.agent?.queryPayload))
|
||||
const hasInsightPanelContent = computed(
|
||||
() => isKnowledgeSession.value || hasScopedReviewPayload.value || hasQueryInsight.value || flowSteps.value.length > 0
|
||||
)
|
||||
const hasInsightPanelContent = computed(() => {
|
||||
return isKnowledgeSession.value || hasScopedReviewPayload.value || hasQueryInsight.value || flowSteps.value.length > 0
|
||||
})
|
||||
const showInsightPanel = computed(() => hasInsightPanelContent.value && !insightPanelCollapsed.value)
|
||||
const insightPanelToggleLabel = computed(() =>
|
||||
showInsightPanel.value ? '隐藏详细信息' : '展开详细信息'
|
||||
@@ -820,7 +846,7 @@ export default {
|
||||
applyComposerDateSelection,
|
||||
resolveTravelCalculatorInitialDays,
|
||||
resolveTravelCalculatorInitialLocation,
|
||||
openTravelCalculator,
|
||||
openTravelCalculator: openTravelCalculatorInternal,
|
||||
toggleTravelCalculator: toggleTravelCalculatorInternal,
|
||||
closeTravelCalculator,
|
||||
formatTravelCalculatorMoney,
|
||||
@@ -845,6 +871,7 @@ export default {
|
||||
buildLocallySyncedReviewPayload,
|
||||
formatDateInputValue
|
||||
})
|
||||
const canShowTravelCalculator = computed(() => activeSessionType.value === SESSION_TYPE_EXPENSE)
|
||||
const {
|
||||
fileInputMode,
|
||||
attachedFiles,
|
||||
@@ -940,6 +967,7 @@ export default {
|
||||
fetchExpenseClaims,
|
||||
fileInputRef,
|
||||
flowRunId,
|
||||
insightPanelCollapsed,
|
||||
isKnowledgeSession,
|
||||
linkedRequest,
|
||||
mergeBusinessTimeIntoExtraContext,
|
||||
@@ -1011,13 +1039,30 @@ export default {
|
||||
openTravelCalculator,
|
||||
lockSuggestedActionMessage,
|
||||
submitExistingComposer: submitComposerInternal,
|
||||
currentUser,
|
||||
toast
|
||||
})
|
||||
function openTravelCalculator() {
|
||||
if (!canShowTravelCalculator.value) {
|
||||
closeTravelCalculator()
|
||||
return false
|
||||
}
|
||||
return openTravelCalculatorInternal()
|
||||
}
|
||||
|
||||
function toggleTravelCalculator() {
|
||||
if (!canShowTravelCalculator.value) {
|
||||
closeTravelCalculator()
|
||||
return false
|
||||
}
|
||||
return toggleTravelCalculatorInternal()
|
||||
}
|
||||
|
||||
function submitTravelCalculator() {
|
||||
if (!canShowTravelCalculator.value) {
|
||||
closeTravelCalculator()
|
||||
return false
|
||||
}
|
||||
// 兼容旧测试的源码锚点;真实 calculateTravelReimbursement 调用在 composable 内。
|
||||
// calculateTravelReimbursement({ grade: String(user.grade || '').trim() })
|
||||
// 根据您输入的地点和天数,匹配到您要出差的地区为,参考可报销合计
|
||||
@@ -1027,6 +1072,11 @@ export default {
|
||||
// messages.value.push(createMessage('assistant', buildTravelCalculatorResultText(payload)
|
||||
return submitTravelCalculatorInternal()
|
||||
}
|
||||
watch(canShowTravelCalculator, (visible) => {
|
||||
if (!visible && travelCalculatorOpen.value) {
|
||||
closeTravelCalculator()
|
||||
}
|
||||
})
|
||||
const isReviewOverviewDrawer = computed(() => reviewDrawerMode.value === REVIEW_DRAWER_MODE_REVIEW)
|
||||
|
||||
const shortcuts = computed(() =>
|
||||
@@ -1142,6 +1192,21 @@ export default {
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.reopenToken,
|
||||
(token, previousToken) => {
|
||||
if (token === previousToken) {
|
||||
return
|
||||
}
|
||||
closeAfterBusy.value = false
|
||||
workbenchVisible.value = true
|
||||
nextTick(() => {
|
||||
adjustComposerTextareaHeight()
|
||||
scrollToBottom()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [submitting.value, reviewActionBusy.value, sessionSwitchBusy.value, workbenchVisible.value],
|
||||
() => {
|
||||
@@ -1510,7 +1575,7 @@ export default {
|
||||
const claimNo = String(record.claimNo || '该单据').trim()
|
||||
const route = claimId
|
||||
? router.resolve({
|
||||
name: 'app-request-detail',
|
||||
name: 'app-document-detail',
|
||||
params: { requestId: claimId }
|
||||
})
|
||||
: null
|
||||
@@ -1558,7 +1623,7 @@ export default {
|
||||
const claimNoTarget = candidates.find((item) => String(item?.claim_no || item?.claimNo || item?.documentNo || '').trim())
|
||||
const claimNo = String(claimNoTarget?.claim_no || claimNoTarget?.claimNo || claimNoTarget?.documentNo || '').trim()
|
||||
const route = router.resolve({
|
||||
name: 'app-request-detail',
|
||||
name: 'app-document-detail',
|
||||
params: { requestId: claimId }
|
||||
})
|
||||
return {
|
||||
@@ -1579,6 +1644,9 @@ export default {
|
||||
}
|
||||
|
||||
function buildMessageBubbleClass(message) {
|
||||
if (message?.role === 'assistant' && message?.applicationPreview) {
|
||||
return 'message-bubble-application-preview'
|
||||
}
|
||||
if (message?.role !== 'assistant' || !resolveReviewNextStepAction(message?.reviewPayload)) {
|
||||
return ''
|
||||
}
|
||||
@@ -1635,10 +1703,27 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
function buildApplicationPreviewFooterText(message) {
|
||||
if (!message?.applicationPreview) {
|
||||
return ''
|
||||
}
|
||||
return buildApplicationPreviewFooterMessage(message.applicationPreview)
|
||||
}
|
||||
|
||||
function openApplicationSubmitConfirm(message) {
|
||||
if (!message) {
|
||||
return
|
||||
}
|
||||
if (message.applicationPreview) {
|
||||
const normalizedPreview = normalizeApplicationPreview(message.applicationPreview)
|
||||
message.applicationPreview = normalizedPreview
|
||||
message.text = buildLocalApplicationPreviewMessage(normalizedPreview)
|
||||
if (!normalizedPreview.readyToSubmit) {
|
||||
toast(`请先补充:${normalizedPreview.missingFields.join('、')}。`)
|
||||
persistSessionState()
|
||||
return
|
||||
}
|
||||
}
|
||||
applicationSubmitConfirmDialog.value = {
|
||||
open: true,
|
||||
message
|
||||
@@ -1660,6 +1745,12 @@ export default {
|
||||
if (!message || submitting.value || reviewActionBusy.value) {
|
||||
return
|
||||
}
|
||||
const applicationPreview = message?.applicationPreview && typeof message.applicationPreview === 'object'
|
||||
? normalizeApplicationPreview(message.applicationPreview)
|
||||
: null
|
||||
const applicationSubmitText = applicationPreview
|
||||
? buildApplicationPreviewSubmitText(applicationPreview)
|
||||
: '确认提交'
|
||||
applicationSubmitConfirmDialog.value = {
|
||||
open: false,
|
||||
message: null
|
||||
@@ -1667,10 +1758,15 @@ export default {
|
||||
reviewActionBusy.value = true
|
||||
try {
|
||||
const payload = await submitComposer({
|
||||
rawText: '确认提交',
|
||||
rawText: applicationSubmitText,
|
||||
userText: '确认提交',
|
||||
pendingText: '正在提交费用申请...',
|
||||
systemGenerated: true
|
||||
systemGenerated: true,
|
||||
skipScopeGuard: true,
|
||||
extraContext: {
|
||||
application_preview: applicationPreview,
|
||||
user_input_text: applicationSubmitText
|
||||
}
|
||||
})
|
||||
const draftPayload = payload?.result?.draft_payload || {}
|
||||
const claimNo = String(draftPayload.claim_no || '').trim()
|
||||
@@ -1708,6 +1804,9 @@ export default {
|
||||
}
|
||||
|
||||
function emitCloseAfterLeave() {
|
||||
if (workbenchVisible.value) {
|
||||
return
|
||||
}
|
||||
if (closeAfterBusy.value && isWorkbenchBusy()) {
|
||||
return
|
||||
}
|
||||
@@ -1722,7 +1821,7 @@ export default {
|
||||
}
|
||||
|
||||
router.push({
|
||||
name: 'app-request-detail',
|
||||
name: 'app-document-detail',
|
||||
params: { requestId: claimId }
|
||||
})
|
||||
emit('close')
|
||||
@@ -2018,12 +2117,12 @@ export default {
|
||||
reviewDrawerTitle, reviewOverviewDrawerAvailable, reviewDocumentDrawerAvailable, reviewRiskDrawerAvailable, reviewFlowDrawerAvailable, reviewDocumentDrawerLabel, reviewDocumentDrawerIcon, reviewRiskDrawerLabel, reviewRiskDrawerIcon, reviewFlowDrawerLabel, reviewFlowDrawerIcon, activeReviewDocument, activeReviewDocumentIndex, activeReviewDocumentPreview, canPreviewActiveReviewDocument,
|
||||
reviewIntentText, reviewFactCards, reviewCategoryOptions, reviewOtherCategoryOptions, reviewSelectedOtherCategory, reviewInlineDirty, reviewInlineForm, reviewInlineEditorKey, reviewInlineErrors, reviewOtherCategoryOpen, reviewInlinePendingFiles, DATE_INPUT_FORMAT, REVIEW_SCENE_OTHER_OPTION, REVIEW_SCENE_OPTIONS, REVIEW_OTHER_CATEGORY_OPTIONS,
|
||||
workbenchVisible, reviewPanelConfidence, reviewRiskSummary, reviewRiskItems, reviewRiskEmpty, recognizedNarratives, reviewRecognitionNotes, reviewDocumentSummaries, reviewDocumentCount, reviewDocumentDirty, reviewHasUnsavedChanges,
|
||||
travelCalculatorOpen, travelCalculatorBusy, travelCalculatorError, travelCalculatorResult, travelCalculatorForm, travelCalculatorCanSubmit, deleteSessionDialogOpen, applicationSubmitConfirmDialog, nextStepConfirmDialog, reviewActionBusy, deleteSessionBusy, documentPreviewDialog, shortcuts,
|
||||
travelCalculatorOpen, travelCalculatorBusy, travelCalculatorError, travelCalculatorResult, travelCalculatorForm, travelCalculatorCanSubmit, canShowTravelCalculator, deleteSessionDialogOpen, applicationSubmitConfirmDialog, applicationPreviewEditor, nextStepConfirmDialog, reviewActionBusy, deleteSessionBusy, documentPreviewDialog, shortcuts,
|
||||
resolveReviewMissingSlotCards, resolveReviewRiskBriefs, buildReviewHeadline, buildReviewSubline, buildReviewStateLabel, buildReviewStateTone, buildReviewPlainFollowupCopy, buildReviewPlainFollowupForMessage, buildReviewNextStepRichCopyForMessage, buildMessageBubbleClass, resolveReviewFooterActions, resolveReviewSaveDraftAction, buildReviewPrimaryButtonLabel, buildReviewMainMessageText,
|
||||
renderMarkdown, buildExpenseQueryWindowLabel, buildExpenseQueryHint, getExpenseQueryActivePage, getExpenseQueryTotalPages, getExpenseQueryVisibleRecords, resolveDocumentPreview, triggerFileUpload, applyComposerDateSelection, handleFilesChange, handleComposerInput, handleComposerEnter, runShortcut, runWelcomeQuickAction: runShortcut, handleSuggestedAction, isSuggestedActionSelected, askHotKnowledgeQuestion, resolveKnowledgeRankLabel, resolveKnowledgeRankTone,
|
||||
refreshFlowRunDetail, formatFlowStepDuration, resolveFlowStepStatusLabel, resolveFlowStepDetail, toggleInsightPanel, openTravelCalculator, toggleTravelCalculator, closeTravelCalculator, submitTravelCalculator, switchToReviewOverviewDrawer, toggleReviewDocumentDrawer, toggleReviewRiskDrawer, toggleReviewFlowDrawer, toggleAttachedFilesExpanded, removeAttachedFile, clearAttachedFiles,
|
||||
requestCloseWorkbench, emitCloseAfterLeave, handleAssistantModalAfterEnter, openExpenseQueryRecord, handleExpenseQueryRecordClick, setExpenseQueryPage, shiftExpenseQueryPage, openDeleteSessionDialog, closeDeleteSessionDialog, confirmDeleteCurrentSession, openInlineReviewEditor, closeInlineReviewEditor, commitInlineReviewEditor, clearInlineReviewFieldError, selectInlineScene, selectReviewCategory, selectReviewOtherCategory,
|
||||
queryDraftByClaimNo, appendReviewRiskBriefToConversation, appendExpenseQueryRiskToConversation, goReviewDocument, openActiveReviewDocumentPreview, closeDocumentPreview, saveInlineReviewChanges, submitComposer, handleAssistantMarkdownClick, handleReviewAction, handleSaveDraftDirectly, closeApplicationSubmitConfirm, confirmApplicationSubmit, closeReviewNextStepConfirm, confirmReviewNextStepSubmit, isDraftSavedReviewMessage, canUseInlineSaveDraft, handleInlineSaveDraft
|
||||
queryDraftByClaimNo, appendReviewRiskBriefToConversation, appendExpenseQueryRiskToConversation, goReviewDocument, openActiveReviewDocumentPreview, closeDocumentPreview, saveInlineReviewChanges, submitComposer, handleAssistantMarkdownClick, handleReviewAction, handleSaveDraftDirectly, resolveApplicationPreviewRows, resolveApplicationPreviewEditorControl, resolveApplicationPreviewEditorOptions, isApplicationPreviewEditing, openApplicationPreviewEditor, commitApplicationPreviewEditor, cancelApplicationPreviewEditor, handleApplicationPreviewEditorKeydown, buildApplicationPreviewFooterText, closeApplicationSubmitConfirm, confirmApplicationSubmit, closeReviewNextStepConfirm, confirmReviewNextStepSubmit, isDraftSavedReviewMessage, canUseInlineSaveDraft, handleInlineSaveDraft
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user