feat: 完善文档中心与报销申请交互及侧边栏重构
后端优化编排器报销查询和本体检测精度,增强报销单草稿保 存和附件回填逻辑,前端重构侧边栏组件支持折叠和图标导 航,完善文档中心状态筛选和详情提示,报销创建和审批详情 页优化会话管理和费用明细交互,新增助手应用服务和预设动 作工具函数,补充单元测试覆盖。
This commit is contained in:
@@ -193,7 +193,10 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in visibleRows" :key="row.documentKey" @click="openDocument(row)">
|
||||
<td><strong class="doc-id">{{ row.documentNo }}</strong></td>
|
||||
<td>
|
||||
<span v-if="row.isNewDocument" class="new-document-badge">NEW</span>
|
||||
<strong class="doc-id">{{ row.documentNo }}</strong>
|
||||
</td>
|
||||
<td>{{ row.createdAtDisplay }}</td>
|
||||
<td v-if="showStayTimeColumn">{{ row.stayTimeDisplay }}</td>
|
||||
<td><span class="doc-kind-tag" :class="row.documentTypeCode">{{ row.documentTypeLabel }}</span></td>
|
||||
@@ -259,31 +262,31 @@ import TableEmptyState from '../components/shared/TableEmptyState.vue'
|
||||
import TableLoadingState from '../components/shared/TableLoadingState.vue'
|
||||
import { mapExpenseClaimToRequest } from '../composables/useRequests.js'
|
||||
import { fetchApprovalExpenseClaims, fetchArchivedExpenseClaims } from '../services/reimbursements.js'
|
||||
import {
|
||||
extractDateText,
|
||||
formatDocumentListTime,
|
||||
resolveDocumentSortTime,
|
||||
resolveDocumentStayTimeDisplay
|
||||
} from '../utils/documentCenterTime.js'
|
||||
import { countNewDocuments, isNewDocument, markDocumentViewed, readDocumentScope, readViewedDocumentKeys, writeDocumentScope } from '../utils/documentCenterNewState.js'
|
||||
import { extractDateText, formatDocumentListTime, resolveDocumentSortTime, resolveDocumentStayTimeDisplay } from '../utils/documentCenterTime.js'
|
||||
import { normalizeRequestForUi } from '../utils/requestViewModel.js'
|
||||
|
||||
const DOCUMENT_TYPE_ALL = 'all'
|
||||
const DOCUMENT_TYPE_APPLICATION = 'application'
|
||||
const DOCUMENT_TYPE_REIMBURSEMENT = 'reimbursement'
|
||||
const SCENE_ALL = 'all'
|
||||
const DOCUMENT_SCOPE_ALL = '全部'
|
||||
const DOCUMENT_SCOPE_APPLICATION = '申请单'
|
||||
const DOCUMENT_SCOPE_REIMBURSEMENT = '报销单'
|
||||
const DOCUMENT_SCOPE_REVIEW = '审核单'
|
||||
const DOCUMENT_SCOPE_ARCHIVE = '归档'
|
||||
|
||||
const scopeTabs = [
|
||||
DOCUMENT_SCOPE_APPLICATION,
|
||||
DOCUMENT_SCOPE_REIMBURSEMENT,
|
||||
DOCUMENT_SCOPE_REVIEW,
|
||||
DOCUMENT_SCOPE_ARCHIVE
|
||||
]
|
||||
const scopeTabs = [DOCUMENT_SCOPE_ALL, DOCUMENT_SCOPE_APPLICATION, DOCUMENT_SCOPE_REIMBURSEMENT, DOCUMENT_SCOPE_REVIEW, DOCUMENT_SCOPE_ARCHIVE]
|
||||
const statusTabs = ['全部', '草稿', '待提交', '审批中', '待补充', '已完成']
|
||||
const FILTER_CONFIG_BY_SCOPE = {
|
||||
[DOCUMENT_SCOPE_ALL]: {
|
||||
searchPlaceholder: '搜索单号、事项、费用场景...',
|
||||
sceneFallbackLabel: '单据场景',
|
||||
dateLabel: '单据时间',
|
||||
statusTitle: '单据状态',
|
||||
statusTabs,
|
||||
showDocumentType: true
|
||||
},
|
||||
[DOCUMENT_SCOPE_APPLICATION]: {
|
||||
searchPlaceholder: '搜索申请单号、申请事项、申请场景...',
|
||||
sceneFallbackLabel: '申请场景',
|
||||
@@ -339,7 +342,7 @@ const emit = defineEmits([
|
||||
'summary-change'
|
||||
])
|
||||
|
||||
const activeScopeTab = ref(DOCUMENT_SCOPE_APPLICATION)
|
||||
const activeScopeTab = ref(readDocumentScope(DOCUMENT_SCOPE_ALL, scopeTabs))
|
||||
const activeStatusTab = ref('全部')
|
||||
const activeDocumentType = ref(DOCUMENT_TYPE_ALL)
|
||||
const activeScene = ref(SCENE_ALL)
|
||||
@@ -357,6 +360,7 @@ const archiveRows = ref([])
|
||||
const approvalRows = ref([])
|
||||
const supportingLoading = ref(false)
|
||||
const supportingError = ref('')
|
||||
const viewedDocumentKeys = ref(readViewedDocumentKeys())
|
||||
|
||||
const activeFilterConfig = computed(() =>
|
||||
FILTER_CONFIG_BY_SCOPE[activeScopeTab.value] || FILTER_CONFIG_BY_SCOPE[DOCUMENT_SCOPE_APPLICATION]
|
||||
@@ -389,13 +393,14 @@ const ownedRows = computed(() =>
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
const allSummaryRows = computed(() => mergeDocumentRows([...ownedRows.value, ...approvalRows.value, ...archiveRows.value]))
|
||||
const nonArchivedRows = computed(() => mergeDocumentRows([...ownedRows.value, ...approvalRows.value]))
|
||||
|
||||
const scopeNewCountMap = computed(() => ({
|
||||
[DOCUMENT_SCOPE_APPLICATION]: allSummaryRows.value.filter((row) => row.documentTypeCode === DOCUMENT_TYPE_APPLICATION).length,
|
||||
[DOCUMENT_SCOPE_REIMBURSEMENT]: ownedRows.value.filter((row) => row.documentTypeCode === DOCUMENT_TYPE_REIMBURSEMENT).length,
|
||||
[DOCUMENT_SCOPE_REVIEW]: approvalRows.value.length,
|
||||
[DOCUMENT_SCOPE_ARCHIVE]: archiveRows.value.length
|
||||
[DOCUMENT_SCOPE_ALL]: countNewDocuments(nonArchivedRows.value, viewedDocumentKeys.value),
|
||||
[DOCUMENT_SCOPE_APPLICATION]: countNewDocuments(nonArchivedRows.value.filter((row) => row.documentTypeCode === DOCUMENT_TYPE_APPLICATION), viewedDocumentKeys.value),
|
||||
[DOCUMENT_SCOPE_REIMBURSEMENT]: countNewDocuments(ownedRows.value.filter((row) => row.documentTypeCode === DOCUMENT_TYPE_REIMBURSEMENT), viewedDocumentKeys.value),
|
||||
[DOCUMENT_SCOPE_REVIEW]: countNewDocuments(approvalRows.value, viewedDocumentKeys.value),
|
||||
[DOCUMENT_SCOPE_ARCHIVE]: countNewDocuments(archiveRows.value, viewedDocumentKeys.value)
|
||||
}))
|
||||
|
||||
const scopeTabItems = computed(() =>
|
||||
@@ -407,8 +412,10 @@ const scopeTabItems = computed(() =>
|
||||
)
|
||||
|
||||
const activeScopeRows = computed(() => {
|
||||
if (activeScopeTab.value === DOCUMENT_SCOPE_ALL) return nonArchivedRows.value
|
||||
|
||||
if (activeScopeTab.value === DOCUMENT_SCOPE_APPLICATION) {
|
||||
return allSummaryRows.value.filter((row) => row.documentTypeCode === DOCUMENT_TYPE_APPLICATION)
|
||||
return nonArchivedRows.value.filter((row) => row.documentTypeCode === DOCUMENT_TYPE_APPLICATION)
|
||||
}
|
||||
|
||||
if (activeScopeTab.value === DOCUMENT_SCOPE_REIMBURSEMENT) {
|
||||
@@ -423,7 +430,7 @@ const activeScopeRows = computed(() => {
|
||||
return archiveRows.value
|
||||
}
|
||||
|
||||
return allSummaryRows.value.filter((row) => row.documentTypeCode === DOCUMENT_TYPE_APPLICATION)
|
||||
return nonArchivedRows.value
|
||||
})
|
||||
|
||||
const sceneFilterOptions = computed(() => {
|
||||
@@ -487,7 +494,7 @@ const showStayTimeColumn = computed(() =>
|
||||
)
|
||||
|
||||
const documentSummary = computed(() => {
|
||||
const rows = allSummaryRows.value
|
||||
const rows = nonArchivedRows.value
|
||||
return {
|
||||
total: rows.length,
|
||||
toSubmit: rows.filter((row) => ['draft', 'pending_submit'].includes(row.statusGroup)).length,
|
||||
@@ -507,9 +514,9 @@ const emptyState = computed(() => {
|
||||
title: '当前还没有申请单数据',
|
||||
desc: '费用申请功能接入后,差旅、会务、办公采购等前置申请会统一汇总到这里。',
|
||||
icon: 'mdi mdi-file-sign-outline',
|
||||
actionLabel: '发起申请',
|
||||
actionIcon: 'mdi mdi-file-plus-outline',
|
||||
tone: 'sky',
|
||||
actionLabel: '',
|
||||
actionIcon: '',
|
||||
tone: 'emerald',
|
||||
artLabel: 'APPLY',
|
||||
tips: ['旧报销中心仍保留', '申请批准后可继续发起报销']
|
||||
}
|
||||
@@ -522,9 +529,9 @@ const emptyState = computed(() => {
|
||||
? '可以清空当前分类下的筛选条件后再看看。'
|
||||
: '当前视角暂无可展示单据,可以切换其他视角或发起一笔报销。',
|
||||
icon: filtered ? 'mdi mdi-magnify-scan' : 'mdi mdi-file-document-multiple-outline',
|
||||
actionLabel: filtered ? '清空筛选' : '发起报销',
|
||||
actionIcon: filtered ? 'mdi mdi-filter-remove-outline' : 'mdi mdi-plus-circle-outline',
|
||||
tone: filtered ? 'sky' : 'emerald',
|
||||
actionLabel: '',
|
||||
actionIcon: '',
|
||||
tone: 'emerald',
|
||||
artLabel: filtered ? 'FILTER' : 'DOCS',
|
||||
tips: ['单据中心已接入当前报销单据', '归档视角会同步归档中心数据']
|
||||
}
|
||||
@@ -543,13 +550,17 @@ function buildDocumentRow(request, options = {}) {
|
||||
const claimId = normalized.claimId || normalized.id || documentNo
|
||||
const createdAtSource = normalized.createdAt || normalized.submittedAt || normalized.applyTime || normalized.updatedAt
|
||||
const updatedAtSource = normalized.updatedAt || normalized.submittedAt || normalized.createdAt || normalized.applyTime
|
||||
const documentTypeCode = normalized.documentTypeCode || DOCUMENT_TYPE_REIMBURSEMENT
|
||||
const documentTypeLabel =
|
||||
normalized.documentTypeLabel
|
||||
|| (documentTypeCode === DOCUMENT_TYPE_APPLICATION ? '申请单' : '报销单')
|
||||
|
||||
return {
|
||||
...normalized,
|
||||
rawRequest: request,
|
||||
documentKey: `${options.source || 'owned'}:${claimId || documentNo}`,
|
||||
documentTypeCode: DOCUMENT_TYPE_REIMBURSEMENT,
|
||||
documentTypeLabel: '报销单',
|
||||
documentTypeCode,
|
||||
documentTypeLabel,
|
||||
claimId,
|
||||
documentNo,
|
||||
node: archived ? '财务归档' : (normalized.node || normalized.workflowNode || '待提交'),
|
||||
@@ -560,6 +571,7 @@ function buildDocumentRow(request, options = {}) {
|
||||
archived,
|
||||
createdAtDisplay: formatDocumentListTime(createdAtSource),
|
||||
stayTimeDisplay: resolveDocumentStayTimeDisplay(normalized),
|
||||
isNewDocument: isNewDocument({ ...normalized, source: options.source || 'owned', claimId, documentNo }, viewedDocumentKeys.value),
|
||||
updatedAtDisplay: formatDocumentListTime(updatedAtSource),
|
||||
sortTime: resolveDocumentSortTime(updatedAtSource)
|
||||
}
|
||||
@@ -703,6 +715,8 @@ function changePageSize(size) {
|
||||
}
|
||||
|
||||
function openDocument(row) {
|
||||
writeDocumentScope(activeScopeTab.value, scopeTabs)
|
||||
viewedDocumentKeys.value = markDocumentViewed(row, viewedDocumentKeys.value)
|
||||
emit('open-document', row.rawRequest || row)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user