feat: 新增归档中心页面并完善知识库与报销查询能力
新增前端归档中心视图及相关工具函数,扩充知识库文档分类和 提取器支持多种格式,增强编排器报销查询的多维度检索,优 化本体规则和用户代理审核消息,前端完善报销创建和审批详 情交互细节,补充单元测试覆盖。
This commit is contained in:
@@ -1,5 +1,171 @@
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
|
||||
const COMMON_DESTINATION_PREFIXES = [
|
||||
'上海',
|
||||
'北京',
|
||||
'广州',
|
||||
'深圳',
|
||||
'杭州',
|
||||
'南京',
|
||||
'苏州',
|
||||
'成都',
|
||||
'重庆',
|
||||
'武汉',
|
||||
'西安',
|
||||
'天津',
|
||||
'宁波',
|
||||
'青岛',
|
||||
'长沙',
|
||||
'郑州',
|
||||
'济南',
|
||||
'合肥',
|
||||
'福州',
|
||||
'厦门',
|
||||
'昆明',
|
||||
'南昌',
|
||||
'沈阳',
|
||||
'大连',
|
||||
'无锡',
|
||||
'佛山',
|
||||
'东莞'
|
||||
]
|
||||
|
||||
const CHINESE_DAY_NUMBERS = {
|
||||
一: 1,
|
||||
二: 2,
|
||||
两: 2,
|
||||
三: 3,
|
||||
四: 4,
|
||||
五: 5,
|
||||
六: 6,
|
||||
七: 7,
|
||||
八: 8,
|
||||
九: 9,
|
||||
十: 10
|
||||
}
|
||||
|
||||
function normalizeComposerText(value) {
|
||||
return String(value || '').trim().replace(/\s+/g, ' ')
|
||||
}
|
||||
|
||||
function parseDayCount(value) {
|
||||
const text = String(value || '').trim()
|
||||
const numericValue = Number.parseInt(text, 10)
|
||||
if (Number.isFinite(numericValue) && numericValue > 0) {
|
||||
return numericValue
|
||||
}
|
||||
if (text === '十') {
|
||||
return 10
|
||||
}
|
||||
if (/^十[一二三四五六七八九]$/.test(text)) {
|
||||
return 10 + (CHINESE_DAY_NUMBERS[text.slice(1)] || 0)
|
||||
}
|
||||
if (/^[一二两三四五六七八九]十$/.test(text)) {
|
||||
return (CHINESE_DAY_NUMBERS[text.slice(0, 1)] || 1) * 10
|
||||
}
|
||||
if (/^[一二两三四五六七八九]十[一二三四五六七八九]$/.test(text)) {
|
||||
return (CHINESE_DAY_NUMBERS[text.slice(0, 1)] || 1) * 10 + (CHINESE_DAY_NUMBERS[text.slice(2)] || 0)
|
||||
}
|
||||
return CHINESE_DAY_NUMBERS[text] || 0
|
||||
}
|
||||
|
||||
function calculateBusinessDays(businessTimeContext) {
|
||||
const startDate = String(businessTimeContext?.start_date || '').trim()
|
||||
const endDate = String(businessTimeContext?.end_date || startDate).trim()
|
||||
if (!startDate || !endDate || startDate > endDate) {
|
||||
return 0
|
||||
}
|
||||
const startAt = Date.parse(`${startDate}T00:00:00Z`)
|
||||
const endAt = Date.parse(`${endDate}T00:00:00Z`)
|
||||
if (!Number.isFinite(startAt) || !Number.isFinite(endAt)) {
|
||||
return 0
|
||||
}
|
||||
return Math.max(1, Math.round((endAt - startAt) / 86400000) + 1)
|
||||
}
|
||||
|
||||
function stripBusinessTimePrefix(text) {
|
||||
return normalizeComposerText(text)
|
||||
.replace(/^(?:业务)?发生时间[::]\s*[^,,。\n]+(?:至\s*[^,,。\n]+)?[,,。\s]*/u, '')
|
||||
.trim()
|
||||
}
|
||||
|
||||
function resolveDestinationFromText(text) {
|
||||
const normalized = normalizeComposerText(text).replace(/\s+/g, '')
|
||||
const targetMatch = normalized.match(/(?:去|到|赴|前往)([^,,。;;]+)/u)
|
||||
const targetText = String(targetMatch?.[1] || '').trim()
|
||||
if (!targetText) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const knownDestination = COMMON_DESTINATION_PREFIXES.find((item) => targetText.startsWith(item))
|
||||
if (knownDestination) {
|
||||
return knownDestination
|
||||
}
|
||||
|
||||
const verbIndex = targetText.search(/支撑|支持|部署|实施|驻场|出差|拜访|处理|办理|参加|进行|协助|服务器|项目/u)
|
||||
if (verbIndex > 0) {
|
||||
return targetText.slice(0, verbIndex)
|
||||
}
|
||||
return targetText.slice(0, 12)
|
||||
}
|
||||
|
||||
function resolveTripDaysFromText(text, businessTimeContext) {
|
||||
const dayMatch = normalizeComposerText(text).match(/(?:出差|共|总计)?\s*([0-9]+|[一二两三四五六七八九十]{1,3})\s*天/u)
|
||||
const explicitDays = parseDayCount(dayMatch?.[1])
|
||||
return explicitDays || calculateBusinessDays(businessTimeContext)
|
||||
}
|
||||
|
||||
function resolveReasonFromText(text, destination) {
|
||||
let reason = normalizeComposerText(text)
|
||||
.replace(/^(?:去|到|赴|前往)\s*/u, '')
|
||||
.trim()
|
||||
|
||||
if (destination && reason.startsWith(destination)) {
|
||||
reason = reason.slice(destination.length).trim()
|
||||
}
|
||||
|
||||
return reason
|
||||
.replace(/[,,。\s]*(?:出差|共|总计)?\s*(?:[0-9]+|[一二两三四五六七八九十]{1,3})\s*天/u, '')
|
||||
.replace(/[,,。\s]*(?:申请|发起|办理)?(?:差旅费|差旅|费用)?报销(?:申请)?[。.!!]?$/u, '')
|
||||
.replace(/^[,,。;;\s]+|[,,。;;\s]+$/gu, '')
|
||||
.trim()
|
||||
}
|
||||
|
||||
export function buildStructuredComposerSubmitText(rawText, businessTimeContext = null) {
|
||||
const normalizedText = normalizeComposerText(rawText)
|
||||
const timeDisplay = String(
|
||||
businessTimeContext?.business_time ||
|
||||
businessTimeContext?.time_range ||
|
||||
businessTimeContext?.display_value ||
|
||||
''
|
||||
).trim()
|
||||
if (!timeDisplay || !normalizedText) {
|
||||
return normalizedText
|
||||
}
|
||||
|
||||
const bodyText = stripBusinessTimePrefix(normalizedText)
|
||||
if (!bodyText) {
|
||||
return `发生时间:${timeDisplay}`
|
||||
}
|
||||
|
||||
const destination = resolveDestinationFromText(bodyText)
|
||||
const reason = resolveReasonFromText(bodyText, destination)
|
||||
const days = resolveTripDaysFromText(bodyText, businessTimeContext)
|
||||
const lines = [`发生时间:${timeDisplay}`]
|
||||
|
||||
if (destination) {
|
||||
lines.push(`地点:${destination}`)
|
||||
}
|
||||
if (reason) {
|
||||
lines.push(`事由:${reason}`)
|
||||
}
|
||||
if (days > 0 && (days > 1 || /出差|差旅|至/.test(timeDisplay) || /出差|差旅/.test(bodyText))) {
|
||||
lines.push(`天数:${days}天`)
|
||||
}
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
export function useTravelReimbursementComposerTools({
|
||||
currentUser,
|
||||
activeReviewPayload,
|
||||
@@ -51,12 +217,12 @@ 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() {
|
||||
@@ -156,6 +322,14 @@ export function useTravelReimbursementComposerTools({
|
||||
return `${tagPart},${draftPart}`
|
||||
}
|
||||
|
||||
function resolveComposerDisplaySubmitText(rawText) {
|
||||
const businessTimeContext = buildComposerBusinessTimeContext()
|
||||
if (!businessTimeContext) {
|
||||
return String(rawText || '').trim()
|
||||
}
|
||||
return buildStructuredComposerSubmitText(rawText, businessTimeContext)
|
||||
}
|
||||
|
||||
function toggleComposerDatePicker() {
|
||||
composerDatePickerOpen.value = !composerDatePickerOpen.value
|
||||
if (composerDatePickerOpen.value) {
|
||||
@@ -377,6 +551,7 @@ export function useTravelReimbursementComposerTools({
|
||||
mergeBusinessTimeIntoExtraContext,
|
||||
syncComposerBusinessTimeToReviewCard,
|
||||
resolveComposerSubmitText,
|
||||
resolveComposerDisplaySubmitText,
|
||||
toggleComposerDatePicker,
|
||||
closeComposerDatePicker,
|
||||
setComposerDateMode,
|
||||
|
||||
Reference in New Issue
Block a user