feat: 会议提交剩余时间+解冻 + 会务/劳务材料批量下载 + 多角色人员/账号模块完善

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
郭庆泰
2026-08-25 06:53:18 +08:00
co-authored by Claude
parent 0c882ae846
commit 591a43fe61
126 changed files with 3789 additions and 1334 deletions
+6 -8
View File
@@ -2,8 +2,8 @@
* 会议阶段显示 (事实驱动, 与后端 StageDeriver 镜像).
*
* biz_meeting 现在存「事实」: is_executed / is_frozen / is_settled / is_finished
* + material_audit_stage / voucher_audit_stage (4 值: NOT_SUBMITTED/SUBMITTED/APPROVED/REJECTED)
* + material/voucher_compliance_approved (区分两级审核) + 审核时间.
* + material_audit_stage (4 值: NOT_SUBMITTED/SUBMITTED/APPROVED/REJECTED)
* + material_compliance_approved (区分两级审核) + 审核时间.
*
* 各角色看到的「阶段名称」由这些事实实时推导, 不再是单一 current_stage 枚举投影.
* current_stage 仍是物理阶段缓存 (10 值), 仅供列表筛选精确匹配.
@@ -15,14 +15,12 @@ function isTrue(v) {
return v === 1 || v === '1' || v === true
}
/** 待结算: 材料+凭证都通过 且 最晚审核时间已超 24h */
/** 待结算: 材料审核通过 且 材料审核时间已超 24h */
function settlementReady(row) {
if (row.voucherAuditStage !== 'APPROVED') return false
if (row.materialAuditStage !== 'APPROVED') return false
const mat = row.materialAuditTime ? new Date(row.materialAuditTime).getTime() : 0
const vch = row.voucherAuditTime ? new Date(row.voucherAuditTime).getTime() : 0
const later = Math.max(mat, vch)
if (!later) return false
return Date.now() - later >= H24
if (!mat) return false
return Date.now() - mat >= H24
}
/**
+6 -3
View File
@@ -36,14 +36,17 @@ request.interceptors.response.use(
const redirect = window.location.hash.slice(1) || ''
window.location.href = import.meta.env.BASE_URL + '#/login' + (redirect ? '?redirect=' + encodeURIComponent(redirect) : '')
}
// __silentError: 调用方已经接管错误 toast (如把红错改成黄警), 拦截器不再重复弹
if (!res.config?.__silentError) {
// 写操作 (POST/PUT/DELETE) 的错误由调用方 catch 自己 toast (避免拦截器 + catch 双 toast);
// 读操作 (GET) 没有统一 catch toast, 由拦截器兜底提示。__silentError: 调用方已接管, 拦截器不弹。
const isMutation = ['post', 'put', 'delete'].includes(String(res.config?.method || '').toLowerCase())
if (!res.config?.__silentError && !isMutation) {
ElMessage.error(data?.msg || '请求失败')
}
return Promise.reject(Object.assign(new Error(data?.msg || '请求失败'), { msg: data?.msg, code: data?.code, silentError: !!res.config?.__silentError }))
},
(err) => {
if (!err.config?.__silentError) {
const isMutation = ['post', 'put', 'delete'].includes(String(err.config?.method || '').toLowerCase())
if (!err.config?.__silentError && !isMutation) {
ElMessage.error(err.message || '网络错误')
}
return Promise.reject(err)
+20 -17
View File
@@ -1,24 +1,27 @@
/**
* biz_person.role enum key → 中文 label 翻译
* 跟 sys_user.role_type 模式一致: DB 存 enum key, UI 显示中文
*
* 适用场景:
* - executor/People.vue 列表/详情显示 role
* - 各处 NewPerson 表单 el-option (label=中文, value=key)
* - 查询条件 role=key
* 角色展示 = f(unitType, accountType) — 单一可信源.
* biz_person.role 列已废弃 (DROP COLUMN), 角色由账号层级 (sys_user.account_type) + 单位类型推导.
*/
export const PERSON_ROLE_LABEL = {
meetingExecutor: '会议执行',
supervisor: '监察员',
admin: '管理员'
/**
* 角色 = f(unitType, accountType) — biz_person.role 与 sys_user.account_type 合并后的单一可信源.
* 不再读 biz_person.role, 直接由账号层级 (MAIN/SUB) + 单位类型推导:
* 支持方 (sponsor): MAIN=管理员, SUB=监察员
* 执行方 (executor): MAIN=管理员, SUB=执行人员
* @param {string} unitType 'sponsor' | 'executor'
* @param {string} accountType 'MAIN' | 'SUB'
* @returns {string} 推导不出时返回 ''
*/
export function accountTypeRoleLabel(unitType, accountType) {
if (accountType === 'MAIN') return '管理员'
if (accountType === 'SUB') return unitType === 'executor' ? '执行人员' : '监察员'
return ''
}
/**
* 翻译 biz_person.role enum key → 中文 label
* @param {string} key
* @returns {string} 没匹配到时返回原 key, 避免显示 'undefined'
* 角色 tag 类型: 主账号高亮 (warning), 子账号普通 (primary)
* @param {string} accountType
*/
export function translatePersonRole(key) {
if (!key) return ''
return PERSON_ROLE_LABEL[key] || key
export function accountTypeRoleTagType(accountType) {
return accountType === 'MAIN' ? 'warning' : 'primary'
}