diff --git a/src/views/payment/PaymentDetailPrint.vue b/src/views/payment/PaymentDetailPrint.vue index 3ef44ad..95e5323 100644 --- a/src/views/payment/PaymentDetailPrint.vue +++ b/src/views/payment/PaymentDetailPrint.vue @@ -268,8 +268,8 @@ {{ idx + 1 }}、{{ template.flow_info?.no || `流程ID: ${template.flow_id}` }} - {{ template.flow_info?.title || template.flow_title || '-' }}({{ template.flow_info?.creator_name || '-' }} {{ formatFlowCreatedAt(template.flow_info?.created_at) }})
- - + + @@ -376,12 +376,10 @@ const collectedFlowIds = ref(new Set()) const renderedPrintTemplates = ref([]) // 存储渲染后的打印模版HTML const REPEATED_MEETING_MINUTES_MESSAGE = '会议纪要再次使用,仅展示标题,不重复打印' -let meetingMinutesUsageStack = [] let meetingMinutesUsageCache = {} let meetingMinutesSeenMap = new Map() const resetMeetingMinutesUsage = () => { - meetingMinutesUsageStack = [] meetingMinutesUsageCache = {} meetingMinutesSeenMap = new Map() } @@ -411,21 +409,38 @@ const getMeetingMinutesDisplayTitle = (rawVal) => { return '会议纪要' } -const extractMeetingMinuteId = (rawVal) => { +const extractMeetingMinutesContentHash = (rawVal) => { const parsed = parseMeetingMinutesValue(rawVal) if (!parsed || typeof parsed !== 'object') return null - const id = parsed.meeting_minute_id - if (id === null || id === undefined || id === '') return null - const normalized = Number(id) - if (!Number.isFinite(normalized) || normalized <= 0) return null - return normalized + + const candidates = [ + parsed.content_hash, + parsed.contentHash + ] + + for (const candidate of candidates) { + if (candidate === null || candidate === undefined) continue + const normalized = String(candidate).trim() + if (normalized) { + return normalized + } + } + return null } const buildMeetingMinutesFingerprint = (rawVal) => { - const meetingMinuteId = extractMeetingMinuteId(rawVal) - if (meetingMinuteId) { - return `meeting_minute_id:${meetingMinuteId}` + const contentHash = extractMeetingMinutesContentHash(rawVal) + if (contentHash) { + return `meeting_minutes_hash:${contentHash}` } + const parsed = parseMeetingMinutesValue(rawVal) + if (!parsed || typeof parsed !== 'object') return null + + const fallbackMeetingMinuteId = parsed.meeting_minute_id || parsed.original_meeting_minute_id || parsed.source_meeting_minute_id + if (fallbackMeetingMinuteId) { + return `meeting_minutes_id:${fallbackMeetingMinuteId}` + } + return null } @@ -456,12 +471,6 @@ const resolveMeetingMinutesUsage = (occurrenceKey, rawVal) => { meetingMinutesSeenMap.set(fingerprint, occurrenceKey) } - meetingMinutesUsageStack.push({ - occurrenceKey, - fingerprint, - title: state.title, - isDuplicate: state.isDuplicate - }) meetingMinutesUsageCache[occurrenceKey] = state return state } @@ -1518,6 +1527,37 @@ onMounted(async () => { font-weight: 600; } +.meeting-minutes-debug { + margin-top: 24px; +} + +.debug-block { + margin-top: 12px; + border: 1px solid #d1d5db; + border-radius: 8px; + background: #f9fafb; + overflow: hidden; +} + +.debug-title { + padding: 10px 12px; + font-size: 13px; + font-weight: 700; + color: #111827; + border-bottom: 1px solid #e5e7eb; + background: #f3f4f6; +} + +.debug-pre { + margin: 0; + padding: 12px; + white-space: pre-wrap; + word-break: break-all; + font-size: 12px; + line-height: 1.6; + color: #1f2937; +} + .breadcrumb { color: #909399; margin-left: 8px; diff --git a/src/views/payment/PaymentQuery.vue b/src/views/payment/PaymentQuery.vue index efc6f48..a4cce41 100644 --- a/src/views/payment/PaymentQuery.vue +++ b/src/views/payment/PaymentQuery.vue @@ -159,6 +159,15 @@ 查看 + + 清理失效支付 + import { ref, onMounted, computed, nextTick } from 'vue' import { useRouter } from 'vue-router' -import { ElMessage } from 'element-plus' +import { ElMessage, ElMessageBox } from 'element-plus' import { Search, Refresh, List } from '@element-plus/icons-vue' import { paymentAPI, paymentCategoryAPI, budgetDataAPI, plannedExpenditureAPI, detailTableFieldAPI, departmentAPI, userAPI, permissionAPI, oaFlowAPI } from '@/utils/api' import request from '@/utils/request' @@ -903,6 +912,42 @@ const handleView = (row) => { window.open(url, '_blank') } +const canCleanupInvalidPayment = (row) => { + return Boolean(row?.oa_flow_id) && !row?.flow_info && row?.status !== 'completed' +} + +const handleCleanupInvalidPayment = async (row) => { + if (!row?.id) return + + try { + await ElMessageBox.confirm( + `这会软删除支付单「${row.serial_number || row.id}」,用于清理已失效的 OA 流程关联。是否继续?`, + '清理失效支付', + { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + } + ) + } catch (error) { + if (error === 'cancel') return + return + } + + try { + const response = await paymentAPI.delete(row.id) + if (response.code === 0) { + ElMessage.success('失效支付已软删除') + await loadData() + return + } + ElMessage.error(response.msg || response.message || '清理失效支付失败') + } catch (error) { + console.error('清理失效支付失败:', error) + ElMessage.error(error.message || '清理失效支付失败') + } +} + // 加载审批流程详情 const loadApprovalFlowDetails = async (payment) => { if (!payment?.fields || !templateElements.value.length) return @@ -2089,4 +2134,3 @@ onMounted(async () => { -