feat: 会议ID应用赋值 + 会议表单/详情优化 + admin工作台统计与用户管理

- meetingId 由 DB 自增改为应用赋值 10 位数字 (yyMMdd + 4 位序列, Redis 按日期计数)
- 会议开始时间不能晚于结束时间校验; 参会人表单身份证附件/角色单独一行
- 会议详情左右列比例 7:3 -> 8:2 (材料审核列收窄)
- admin/workbench 用户总数/角色类型数/各角色分布统计修复 (改用 listByRole 按 role_type 过滤)
- 新增 admin 用户管理接口 + 门户页脚 + H5 签到/上传优化
This commit is contained in:
郭庆泰
2026-08-25 23:33:14 +08:00
parent 591a43fe61
commit ea2024d083
66 changed files with 1454 additions and 1228 deletions
+3 -14
View File
@@ -9,20 +9,10 @@
* current_stage 仍是物理阶段缓存 (10 值), 仅供列表筛选精确匹配.
*/
const H24 = 24 * 3600 * 1000
function isTrue(v) {
return v === 1 || v === '1' || v === true
}
/** 待结算: 材料审核通过 且 材料审核时间已超 24h */
function settlementReady(row) {
if (row.materialAuditStage !== 'APPROVED') return false
const mat = row.materialAuditTime ? new Date(row.materialAuditTime).getTime() : 0
if (!mat) return false
return Date.now() - mat >= H24
}
/**
* 10 值物理阶段 (镜像后端 StageDeriver.derivePhysicalStage), 用于颜色/筛选.
*/
@@ -33,7 +23,7 @@ export function derivePhysicalStage(row) {
if (isTrue(row.isSettled)) return 'SETTLED'
const material = row.materialAuditStage
if (material === 'REJECTED') return 'RECTIFYING'
if (material === 'APPROVED') return settlementReady(row) ? 'AWAITING_SETTLEMENT' : 'SUPERVISION_APPROVED'
if (material === 'APPROVED') return 'AWAITING_SETTLEMENT'
if (material === 'SUBMITTED') return isTrue(row.materialComplianceApproved) ? 'AWAITING_SUPERVISION' : 'AWAITING_COMPLIANCE'
return isTrue(row.isExecuted) ? 'RUNNING' : 'NOT_STARTED'
}
@@ -55,9 +45,9 @@ export function deriveStage(role, row) {
return role === 'executor' ? '已退回' : '待整改'
}
// 材料已支持方通过
// 材料已支持方通过 → 待结算 (不再有 24h 慢路径)
if (material === 'APPROVED') {
return settlementReady(row) ? '待结算' : '审核通过'
return '待结算'
}
// 材料在审 (SUBMITTED)
@@ -120,7 +110,6 @@ export const STAGE_OPTIONS = [
{ label: '执行中', value: 'RUNNING' },
{ label: '待合规审核', value: 'AWAITING_COMPLIANCE' },
{ label: '待支持方审核', value: 'AWAITING_SUPERVISION' },
{ label: '审核通过', value: 'SUPERVISION_APPROVED' },
{ label: '待整改', value: 'RECTIFYING' },
{ label: '待结算', value: 'AWAITING_SETTLEMENT' },
{ label: '已结算', value: 'SETTLED' },
+11 -3
View File
@@ -45,8 +45,14 @@ function getMimeByExt(name) {
export async function uploadToOss(file, dir) {
const sign = await getOssSign(dir)
const ext = (file.name || '').split('.').pop() || 'bin'
const key = sign.dir + Date.now() + '_' + Math.random().toString(36).slice(2, 8) + '.' + ext
// 原文件名进 OSS key: 原始名称_时间戳_随机串.原扩展名 (保留中文, 仅清洗 URL/路径危险字符)
// 这样上传控件能从 URL 里还原原名显示, 无需额外 name 字段/列
const rawName = file.name || 'file'
const dot = rawName.lastIndexOf('.')
const ext = (dot > 0 ? rawName.slice(dot) : '').replace(/[\\/:*?"<>|%\s]+/g, '')
const base = (dot > 0 ? rawName.slice(0, dot) : rawName)
.replace(/[\\/:*?"<>|%\s]+/g, '_') || 'file'
const key = sign.dir + base + '_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8) + ext
// 显式传 Content-Type,让 OSS 按此存文件元数据, 浏览器拿到 application/pdf 即可内嵌预览
const mime = getMimeByExt(file.name)
@@ -68,5 +74,7 @@ export async function uploadToOss(file, dir) {
if (!resp.ok) {
throw new Error('OSS 上传失败: HTTP ' + resp.status)
}
return sign.host + '/' + key
// 返回 URL 时对路径逐段编码 (保留 / 分隔), 让含中文/特殊字符的 key 在所有下载场景下都是 ASCII 安全 URL
// OSS 对象 key 本身仍是原始值 (FormData 的 key 字段未编码), 下载时 OSS 会自动把 %XX 还原
return sign.host + '/' + key.split('/').map(encodeURIComponent).join('/')
}
+18
View File
@@ -0,0 +1,18 @@
import { ref } from 'vue'
// navbar 顶栏动态标题 (AdminLayout 顶栏优先读它, 覆盖 route.meta.title)
// 页面组件进入时 setPageTitle, 离开时 onBeforeUnmount(clearPageTitle),
// 否则切到别的页面会残留上一个页面的标题.
const dynamicTitle = ref('')
export function setPageTitle(t) {
dynamicTitle.value = t || ''
}
export function clearPageTitle() {
dynamicTitle.value = ''
}
export function usePageTitle() {
return dynamicTitle
}