feat: 完善文档中心与报销申请交互及侧边栏重构

后端优化编排器报销查询和本体检测精度,增强报销单草稿保
存和附件回填逻辑,前端重构侧边栏组件支持折叠和图标导
航,完善文档中心状态筛选和详情提示,报销创建和审批详情
页优化会话管理和费用明细交互,新增助手应用服务和预设动
作工具函数,补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-25 13:35:39 +08:00
parent 50b1c3f9a9
commit d0e946cf47
59 changed files with 5117 additions and 416 deletions

View File

@@ -12,7 +12,9 @@ import { buildDetailAlerts } from '../utils/detailAlerts.js'
import { normalizeRequestForUi } from '../utils/requestViewModel.js'
import { buildWorkbenchSummary } from '../utils/workbenchSummary.js'
const SESSION_TYPE_EXPENSE = 'expense'
const SESSION_TYPE_EXPENSE = 'expense'
const SMART_ENTRY_SOURCE_APPLICATION = 'application'
const SMART_ENTRY_SOURCE_REIMBURSEMENT = 'topbar'
export function useAppShell() {
const route = useRoute()
@@ -111,13 +113,18 @@ export function useAppShell() {
buildWorkbenchSummary(requests.value, currentUser.value)
)
const topBarView = computed(() => {
if (detailMode.value) {
return {
title: '报销单详情',
desc: '查看报销明细、票据材料、审批进度与风险提示。'
}
}
const topBarView = computed(() => {
if (detailMode.value) {
const request = selectedRequest.value || {}
const claimNo = request.claimNo || request.claim_no || request.documentNo || request.id || ''
const isApplicationDocument = isApplicationDocumentPayload(request, claimNo)
return {
title: isApplicationDocument ? '申请单详情' : '报销单详情',
desc: isApplicationDocument
? '查看申请信息、预计金额、审批进度与预算管理口径。'
: '查看报销明细、票据材料、审批进度与风险提示。'
}
}
if (logDetailMode.value) {
return {
@@ -170,18 +177,26 @@ export function useAppShell() {
setView(view)
}
function openTravelCreate() {
smartEntryOpen.value = true
smartEntryContext.value = {
prompt: '',
source: 'topbar',
request: null,
files: [],
conversation: null,
scope: null
}
smartEntrySessionId.value += 1
}
function openFinancialAssistantCreate(source) {
smartEntryOpen.value = true
smartEntryContext.value = {
prompt: '',
source,
request: null,
files: [],
conversation: null,
scope: null
}
smartEntrySessionId.value += 1
}
function openTravelCreate() {
openFinancialAssistantCreate(SMART_ENTRY_SOURCE_REIMBURSEMENT)
}
function openExpenseApplicationCreate() {
openFinancialAssistantCreate(SMART_ENTRY_SOURCE_APPLICATION)
}
function resolveCurrentUserId() {
const user = currentUser.value || {}
@@ -204,9 +219,27 @@ export function useAppShell() {
return { type: 'claim', claimId }
}
function isDetailClaimScopedPayload(payload = {}) {
return String(payload.source || '').trim() === 'detail' && Boolean(resolveSmartEntryClaimScope(payload))
}
function isDetailClaimScopedPayload(payload = {}) {
return String(payload.source || '').trim() === 'detail' && Boolean(resolveSmartEntryClaimScope(payload))
}
function isApplicationDocumentPayload(payload = {}, claimNo = '') {
const documentType = String(
payload.documentType
|| payload.document_type
|| payload.documentTypeCode
|| payload.document_type_code
|| payload.draftType
|| payload.draft_type
|| ''
).trim()
const normalizedClaimNo = String(claimNo || payload.claimNo || payload.claim_no || '').trim().toUpperCase()
return (
documentType === 'application'
|| documentType === 'expense_application'
|| normalizedClaimNo.startsWith('APP-')
)
}
async function resolveSmartEntryConversation(payload = {}) {
if (payload.conversation) {
@@ -254,19 +287,28 @@ export function useAppShell() {
}
async function handleDraftSaved(payload = {}) {
const claimNo = String(payload.claimNo || payload.claim_no || '').trim()
const status = String(payload.status || payload.claimStatus || '').trim()
const approvalStage = String(payload.approvalStage || payload.approval_stage || '').trim()
await reloadRequests()
if (status === 'submitted') {
smartEntryOpen.value = false
void refreshApprovalInbox()
toast(`${claimNo || '该'}单据已完成 AI预审${approvalStage ? `,当前节点:${approvalStage}` : ',并已提交审批'}`)
router.push({ name: activeView.value === 'documents' ? 'app-documents' : 'app-requests' })
return
}
toast(`${claimNo || '该'}单据已保存为草稿,可继续上传票据或补充信息`)
}
const claimNo = String(payload.claimNo || payload.claim_no || '').trim()
const status = String(payload.status || payload.claimStatus || '').trim()
const approvalStage = String(payload.approvalStage || payload.approval_stage || '').trim()
const isApplicationDocument = isApplicationDocumentPayload(payload, claimNo)
await reloadRequests()
if (status === 'submitted') {
smartEntryOpen.value = false
void refreshApprovalInbox()
toast(
isApplicationDocument
? `${claimNo || '该'}申请单已提交${approvalStage ? `,当前节点:${approvalStage}` : ',等待直属领导审批'}`
: `${claimNo || '该'}单据已完成 AI预审${approvalStage ? `,当前节点:${approvalStage}` : ',并已提交审批'}`
)
router.push({ name: activeView.value === 'documents' ? 'app-documents' : 'app-requests' })
return
}
toast(
isApplicationDocument
? `${claimNo || '该'}申请单已保存为草稿,可继续补充申请信息。`
: `${claimNo || '该'}单据已保存为草稿,可继续上传票据或补充信息。`
)
}
function openRequestDetail(request) {
selectedRequestSnapshot.value = request || null
@@ -315,10 +357,11 @@ export function useAppShell() {
handleNavigate,
handleReject,
handleRequestDeleted,
handleRequestUpdated,
navItems,
openRequestDetail,
openSmartEntry,
handleRequestUpdated,
navItems,
openExpenseApplicationCreate,
openRequestDetail,
openSmartEntry,
openTravelCreate,
ranges,
requestSummary,