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:
@@ -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' },
|
||||
|
||||
@@ -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('/')
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user