feat: 会议阶段劳务/会务前缀投影 + 结算前未保存拦截

- 阶段投影: 待审核/审核通过/待整改/待结算 按劳务/会务前缀区分 (前后端镜像)
- 劳务材料提交门槛改为参会人员列表≥1人 (非上传文件)
- sponsor/manager 操作列「审核」「结算」提为直链
- 会议结算时间轴节点: 待结算态显示「补充凭证并结算」
- 结算前检测未保存改动, 弹「保存并结算」拦截

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
郭庆泰
2026-08-29 20:02:33 +08:00
co-authored by Claude
parent e47f76478b
commit 1876601ba7
7 changed files with 171 additions and 30 deletions
+53 -1
View File
@@ -813,7 +813,11 @@ function fixedNodeStatus(slot) {
function nodeDesc(slot) {
const s = derivePhysicalStage(row.value)
if (slot === 'PRE') return s !== 'NOT_STARTED' && s !== 'FROZEN' ? '已执行' : '待执行'
if (slot === 'POST') return s === 'AWAITING_SETTLEMENT' || s === 'SETTLED' || s === 'FINISHED' ? '已结算' : '未结算'
if (slot === 'POST') {
if (s === 'SETTLED' || s === 'FINISHED') return '已结算'
if (s === 'AWAITING_SETTLEMENT') return '补充凭证并结算'
return '未结算'
}
if (slot === 'DONE') return isOne(row.value.isFinished) ? '已完结' : '未完结'
return '-'
}
@@ -877,9 +881,35 @@ async function loadMaterials() {
target.fileName = item.fileName || ''
}
})
captureSnapshot()
} catch (e) { console.error('[meeting-detail] loadMaterials failed', e) }
}
// ===================== 未保存改动检测 (结算前拦截用) =====================
/** 已落库快照 (load 后 + 保存后各刷新一次): 4 组上传行 url + 海报/邀请函 */
const savedSnapshot = ref(null)
function captureSnapshot() {
const urls = new Map()
;[...serviceMaterialRows.value, ...laborMaterialRows.value,
...laborVoucherRows.value, ...serviceVoucherRows.value]
.forEach(r => urls.set(r.subType, (r.url || '').trim()))
savedSnapshot.value = {
urls,
scheduleUrl: scheduleUrl.value || '',
invitationUrl: invitationUrl.value || '',
}
}
/** 是否有「已上传但未落库」的改动 (材料/凭证/海报/邀请函) */
const hasUnsavedChanges = computed(() => {
const s = savedSnapshot.value
if (!s) return false
if ((scheduleUrl.value || '') !== s.scheduleUrl) return true
if ((invitationUrl.value || '') !== s.invitationUrl) return true
return [...serviceMaterialRows.value, ...laborMaterialRows.value,
...laborVoucherRows.value, ...serviceVoucherRows.value]
.some(r => (r.url || '').trim() !== (s.urls.get(r.subType) || ''))
})
// ===================== 参会人 CRUD =====================
/** 会议下的参会人列表 (放在 劳务材料 tab 表格) */
const attendeeRows = ref([])
@@ -1714,6 +1744,7 @@ async function doSave() {
if (canUploadVoucher.value) await saveVouchers()
// 材料落库 → 会议费用待重算, 刷新状态并轮询到汇总完成
refreshFee()
captureSnapshot()
return ocrCount
}
@@ -1888,6 +1919,27 @@ async function confirmAudit() {
// ===================== 结算 / 完结 (合规/管理员 手动点击) =====================
async function onSettle() {
// ① 有「已上传未保存」的改动 → 先保存再结算
if (hasUnsavedChanges.value) {
try {
await ElMessageBox.confirm(
'检测到尚未保存的上传内容(材料/付款凭证等),是否先保存再结算?',
'未保存的改动',
{ type: 'warning', confirmButtonText: '保存并结算', cancelButtonText: '取消' }
)
} catch { return }
busy.value.settle = true
try {
await doSave()
} catch (e) {
ElMessage.error('保存失败,已取消结算:' + (e?.msg || e?.message || ''))
return
} finally {
busy.value.settle = false
}
ElMessage.success('已保存')
}
// ② 结算确认
try {
await ElMessageBox.confirm('确认结算? 结算前请确保已上传劳务/会务付款凭证。', '结算确认', { type: 'warning' })
} catch { return }