feat: 本体字段治理与风险规则模板执行器重构
- 新增本体字段注册表与字段治理审计脚本 - 重构风险规则模板执行器、DSL 验证与清单分类器 - 完善票据夹服务与差旅请求详情页交互 - 优化趋势图表与总览页数据展示 - 增强报销平台风险分级与模拟公司筛选 - 补充本体字段、风险规则生成与票据夹服务测试覆盖
This commit is contained in:
@@ -231,6 +231,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import EnterprisePagination from '../components/shared/EnterprisePagination.vue'
|
||||
import TableEmptyState from '../components/shared/TableEmptyState.vue'
|
||||
import TableLoadingState from '../components/shared/TableLoadingState.vue'
|
||||
@@ -252,6 +253,17 @@ const DOCUMENT_SCOPE_REVIEW = '审核单'
|
||||
const DOCUMENT_SCOPE_ARCHIVE = '归档'
|
||||
const scopeTabs = [DOCUMENT_SCOPE_ALL, DOCUMENT_SCOPE_APPLICATION, DOCUMENT_SCOPE_REIMBURSEMENT, DOCUMENT_SCOPE_REVIEW, DOCUMENT_SCOPE_ARCHIVE]
|
||||
const DOCUMENT_LOADING_MIN_VISIBLE_MS = 720
|
||||
const DOCUMENT_CENTER_QUERY_KEYS = new Set([
|
||||
'dc_page',
|
||||
'dc_page_size',
|
||||
'dc_scope',
|
||||
'dc_status',
|
||||
'dc_doc_type',
|
||||
'dc_scene',
|
||||
'dc_q',
|
||||
'dc_start',
|
||||
'dc_end'
|
||||
])
|
||||
const statusTabs = ['全部', '草稿', '待提交', '审批中', '待补充', '待付款', '已完成']
|
||||
const FILTER_CONFIG_BY_SCOPE = {
|
||||
[DOCUMENT_SCOPE_ALL]: {
|
||||
@@ -296,11 +308,14 @@ const FILTER_CONFIG_BY_SCOPE = {
|
||||
}
|
||||
}
|
||||
const pageSizeOptions = [10, 20, 50].map((size) => ({ label: `${size} 条/页`, value: size }))
|
||||
const pageSizeValues = pageSizeOptions.map((item) => item.value)
|
||||
const documentTypeOptions = [
|
||||
{ value: DOCUMENT_TYPE_ALL, label: '单据类型' },
|
||||
{ value: DOCUMENT_TYPE_APPLICATION, label: '申请单' },
|
||||
{ value: DOCUMENT_TYPE_REIMBURSEMENT, label: '报销单' }
|
||||
]
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
filteredRequests: { type: Array, required: true },
|
||||
hasData: { type: Boolean, default: false },
|
||||
@@ -315,19 +330,91 @@ const emit = defineEmits([
|
||||
'reload',
|
||||
'summary-change'
|
||||
])
|
||||
const activeScopeTab = ref(readDocumentScope(DOCUMENT_SCOPE_ALL, scopeTabs))
|
||||
const activeStatusTab = ref('全部')
|
||||
const activeDocumentType = ref(DOCUMENT_TYPE_ALL)
|
||||
const activeScene = ref(SCENE_ALL)
|
||||
|
||||
function readDocumentCenterQueryText(key) {
|
||||
const value = route.query?.[key]
|
||||
return String(Array.isArray(value) ? value[0] || '' : value || '').trim()
|
||||
}
|
||||
|
||||
function readDocumentCenterQueryNumber(key, fallback) {
|
||||
const parsed = Number(readDocumentCenterQueryText(key))
|
||||
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback
|
||||
}
|
||||
|
||||
function resolveInitialScopeTab() {
|
||||
const queryScope = readDocumentCenterQueryText('dc_scope')
|
||||
if (scopeTabs.includes(queryScope)) {
|
||||
return queryScope
|
||||
}
|
||||
|
||||
return readDocumentScope(DOCUMENT_SCOPE_ALL, scopeTabs)
|
||||
}
|
||||
|
||||
function resolveInitialStatusTab(scope) {
|
||||
const queryStatus = readDocumentCenterQueryText('dc_status') || '全部'
|
||||
const config = FILTER_CONFIG_BY_SCOPE[scope] || FILTER_CONFIG_BY_SCOPE[DOCUMENT_SCOPE_ALL]
|
||||
return config.statusTabs.includes(queryStatus) ? queryStatus : '全部'
|
||||
}
|
||||
|
||||
function resolveInitialDocumentType() {
|
||||
const queryType = readDocumentCenterQueryText('dc_doc_type')
|
||||
return documentTypeOptions.some((item) => item.value === queryType)
|
||||
? queryType
|
||||
: DOCUMENT_TYPE_ALL
|
||||
}
|
||||
|
||||
function resolveInitialPageSize() {
|
||||
const queryPageSize = readDocumentCenterQueryNumber('dc_page_size', 20)
|
||||
return pageSizeValues.includes(queryPageSize) ? queryPageSize : 20
|
||||
}
|
||||
|
||||
function buildDocumentCenterRouteQuery() {
|
||||
const nextQuery = {}
|
||||
Object.entries(route.query || {}).forEach(([key, value]) => {
|
||||
if (!DOCUMENT_CENTER_QUERY_KEYS.has(key)) {
|
||||
nextQuery[key] = value
|
||||
}
|
||||
})
|
||||
|
||||
if (currentPage.value > 1) nextQuery.dc_page = String(currentPage.value)
|
||||
if (pageSize.value !== 20) nextQuery.dc_page_size = String(pageSize.value)
|
||||
if (activeScopeTab.value !== DOCUMENT_SCOPE_ALL) nextQuery.dc_scope = activeScopeTab.value
|
||||
if (activeStatusTab.value !== '全部') nextQuery.dc_status = activeStatusTab.value
|
||||
if (showDocumentTypeFilter.value && activeDocumentType.value !== DOCUMENT_TYPE_ALL) {
|
||||
nextQuery.dc_doc_type = activeDocumentType.value
|
||||
}
|
||||
if (activeScene.value !== SCENE_ALL) nextQuery.dc_scene = activeScene.value
|
||||
if (listKeyword.value.trim()) nextQuery.dc_q = listKeyword.value.trim()
|
||||
if (appliedStart.value) nextQuery.dc_start = appliedStart.value
|
||||
if (appliedEnd.value) nextQuery.dc_end = appliedEnd.value
|
||||
|
||||
return nextQuery
|
||||
}
|
||||
|
||||
function routeQueryEquals(left, right) {
|
||||
const leftEntries = Object.entries(left || {}).map(([key, value]) => [key, Array.isArray(value) ? value.join(',') : String(value ?? '')])
|
||||
const rightEntries = Object.entries(right || {}).map(([key, value]) => [key, Array.isArray(value) ? value.join(',') : String(value ?? '')])
|
||||
if (leftEntries.length !== rightEntries.length) return false
|
||||
const rightMap = new Map(rightEntries)
|
||||
return leftEntries.every(([key, value]) => rightMap.get(key) === value)
|
||||
}
|
||||
|
||||
const initialScopeTab = resolveInitialScopeTab()
|
||||
const initialAppliedStart = readDocumentCenterQueryText('dc_start')
|
||||
const initialAppliedEnd = readDocumentCenterQueryText('dc_end')
|
||||
const activeScopeTab = ref(initialScopeTab)
|
||||
const activeStatusTab = ref(resolveInitialStatusTab(initialScopeTab))
|
||||
const activeDocumentType = ref(resolveInitialDocumentType())
|
||||
const activeScene = ref(readDocumentCenterQueryText('dc_scene') || SCENE_ALL)
|
||||
const openFilterKey = ref('')
|
||||
const listKeyword = ref('')
|
||||
const listKeyword = ref(readDocumentCenterQueryText('dc_q'))
|
||||
const datePopover = ref(false)
|
||||
const rangeStart = ref('')
|
||||
const rangeEnd = ref('')
|
||||
const appliedStart = ref('')
|
||||
const appliedEnd = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const rangeStart = ref(initialAppliedStart)
|
||||
const rangeEnd = ref(initialAppliedEnd)
|
||||
const appliedStart = ref(initialAppliedStart)
|
||||
const appliedEnd = ref(initialAppliedEnd)
|
||||
const currentPage = ref(readDocumentCenterQueryNumber('dc_page', 1))
|
||||
const pageSize = ref(resolveInitialPageSize())
|
||||
const archiveRows = ref([])
|
||||
const approvalRows = ref([])
|
||||
const supportingLoading = ref(false)
|
||||
@@ -795,6 +882,20 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
[currentPage, pageSize, activeScopeTab, activeStatusTab, activeDocumentType, activeScene, listKeyword, appliedStart, appliedEnd],
|
||||
() => {
|
||||
if (route.name !== 'app-documents') {
|
||||
return
|
||||
}
|
||||
|
||||
const nextQuery = buildDocumentCenterRouteQuery()
|
||||
if (!routeQueryEquals(route.query, nextQuery)) {
|
||||
router.replace({ name: 'app-documents', query: nextQuery })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(activeFilterConfig, () => {
|
||||
openFilterKey.value = ''
|
||||
datePopover.value = false
|
||||
|
||||
Reference in New Issue
Block a user