feat: 生产部署 + 会议全链路 (执行方分配/费用结算/签到脱敏/H5相机)
- 生产 nginx 三路径: ry-vue3 /hg/, ry-h5 /camera/, API /hg-api, .env 分环境 - 签约链接/摄像头 base-url 走 yml, 不再硬编码 /hg 与 localhost - 新增 biz_project_executor_assign (镜像 sponsor_assign) 执行方项目级分配 - 会议费用后台汇总 FeeCalcScheduler + 结算回写项目金额 + 状态流转调度 - 签到表拍照高斯模糊脱敏 extra_oss_url, 新增 PosterService/StageDeriver - 清理 biz_support_intent 旧表 (6 Java/XML 删除) - ry-h5 相机黑屏修复: 显式 video.play() + 动态 apiBase 上传 - 资源: simhei 字体 / logo.png / qrcode_1.png / favicon.ico Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 会议阶段显示 (事实驱动, 与后端 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 (区分两级审核) + 审核时间.
|
||||
*
|
||||
* 各角色看到的「阶段名称」由这些事实实时推导, 不再是单一 current_stage 枚举投影.
|
||||
* current_stage 仍是物理阶段缓存 (10 值), 仅供列表筛选精确匹配.
|
||||
*/
|
||||
|
||||
const H24 = 24 * 3600 * 1000
|
||||
|
||||
function isTrue(v) {
|
||||
return v === 1 || v === '1' || v === true
|
||||
}
|
||||
|
||||
/** 待结算: 材料+凭证都通过 且 最晚审核时间已超 24h */
|
||||
function settlementReady(row) {
|
||||
if (row.voucherAuditStage !== '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
|
||||
}
|
||||
|
||||
/**
|
||||
* 10 值物理阶段 (镜像后端 StageDeriver.derivePhysicalStage), 用于颜色/筛选.
|
||||
*/
|
||||
export function derivePhysicalStage(row) {
|
||||
if (!row) return 'NOT_STARTED'
|
||||
if (isTrue(row.isFrozen)) return 'FROZEN'
|
||||
if (isTrue(row.isFinished)) return 'FINISHED'
|
||||
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 === 'SUBMITTED') return isTrue(row.materialComplianceApproved) ? 'AWAITING_SUPERVISION' : 'AWAITING_COMPLIANCE'
|
||||
return isTrue(row.isExecuted) ? 'RUNNING' : 'NOT_STARTED'
|
||||
}
|
||||
|
||||
/**
|
||||
* 各角色展示阶段名 (镜像后端 StageDeriver.deriveDisplay).
|
||||
* role ∈ {executor, sponsor, manager, admin, doctor, expert}; 非流程角色回退 admin 中性.
|
||||
*/
|
||||
export function deriveStage(role, row) {
|
||||
if (!row) return '-'
|
||||
if (isTrue(row.isFrozen)) return '冻结中'
|
||||
if (isTrue(row.isFinished)) return '已完结'
|
||||
if (isTrue(row.isSettled)) return '已结算'
|
||||
|
||||
const material = row.materialAuditStage
|
||||
|
||||
// 退回: 执行方看「已退回」, 其他方看「待整改」
|
||||
if (material === 'REJECTED') {
|
||||
return role === 'executor' ? '已退回' : '待整改'
|
||||
}
|
||||
|
||||
// 材料已支持方通过
|
||||
if (material === 'APPROVED') {
|
||||
return settlementReady(row) ? '待结算' : '审核通过'
|
||||
}
|
||||
|
||||
// 材料在审 (SUBMITTED)
|
||||
if (material === 'SUBMITTED') {
|
||||
if (isTrue(row.materialComplianceApproved)) {
|
||||
// 支持方审中
|
||||
return role === 'manager' ? '审核通过' : '待审核'
|
||||
}
|
||||
// 合规审中
|
||||
return role === 'sponsor' ? '已执行未传材料' : '待审核'
|
||||
}
|
||||
|
||||
// 未提交
|
||||
if (isTrue(row.isExecuted)) {
|
||||
return role === 'executor' ? '执行中' : '已执行未传材料'
|
||||
}
|
||||
return '未执行'
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧签名: stageLabel(role, row) — row 现在传完整会议对象 (含事实字段).
|
||||
*/
|
||||
export function stageLabel(role, row) {
|
||||
return deriveStage(role, row)
|
||||
}
|
||||
|
||||
/** 颜色 class (业务专用 tag), 基于物理阶段 */
|
||||
export function stageClass(row) {
|
||||
const s = derivePhysicalStage(row)
|
||||
if (s === 'NOT_STARTED') return 'pending'
|
||||
if (s === 'RUNNING') return 'running'
|
||||
if (s === 'AWAITING_COMPLIANCE' || s === 'AWAITING_SUPERVISION') return 'reviewing'
|
||||
if (s === 'SUPERVISION_APPROVED') return 'done'
|
||||
if (s === 'RECTIFYING') return 'waiting'
|
||||
if (s === 'AWAITING_SETTLEMENT') return 'waiting'
|
||||
if (s === 'SETTLED') return 'done'
|
||||
if (s === 'FINISHED') return 'done'
|
||||
if (s === 'FROZEN') return 'frozen'
|
||||
return 'default'
|
||||
}
|
||||
|
||||
/** el-tag type (doctor 列表用), 基于物理阶段 */
|
||||
export function stageTag(row) {
|
||||
const s = derivePhysicalStage(row)
|
||||
if (s === 'NOT_STARTED') return 'info'
|
||||
if (s === 'RUNNING' || s === 'RECTIFYING') return 'primary'
|
||||
if (s === 'AWAITING_COMPLIANCE' || s === 'AWAITING_SUPERVISION') return 'warning'
|
||||
if (s === 'SUPERVISION_APPROVED' || s === 'SETTLED' || s === 'FINISHED') return 'success'
|
||||
if (s === 'AWAITING_SETTLEMENT') return 'warning'
|
||||
if (s === 'FROZEN') return 'info'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
/**
|
||||
* 物理状态机全量 10 值 (用于筛选下拉: label=中性名, value=physical code).
|
||||
* 筛选按物理状态精确匹配, 显示层才按角色折叠, 避免多值 IN 复杂化.
|
||||
*/
|
||||
export const STAGE_OPTIONS = [
|
||||
{ label: '未执行', value: 'NOT_STARTED' },
|
||||
{ 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' },
|
||||
{ label: '已完结', value: 'FINISHED' },
|
||||
{ label: '冻结中', value: 'FROZEN' },
|
||||
]
|
||||
@@ -2,7 +2,7 @@ import axios from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const request = axios.create({
|
||||
baseURL: '/dev-api',
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
timeout: 15000
|
||||
})
|
||||
|
||||
@@ -34,7 +34,7 @@ request.interceptors.response.use(
|
||||
localStorage.removeItem('ry_user')
|
||||
// 带 redirect 跳登录, 登录成功后回跳原页面
|
||||
const redirect = window.location.hash.slice(1) || ''
|
||||
window.location.href = '/#/login' + (redirect ? '?redirect=' + encodeURIComponent(redirect) : '')
|
||||
window.location.href = import.meta.env.BASE_URL + '#/login' + (redirect ? '?redirect=' + encodeURIComponent(redirect) : '')
|
||||
}
|
||||
// __silentError: 调用方已经接管错误 toast (如把红错改成黄警), 拦截器不再重复弹
|
||||
if (!res.config?.__silentError) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
||||
|
||||
const SSE_PATH = '/dev-api/business/message/stream'
|
||||
const SSE_PATH = import.meta.env.VITE_APP_BASE_API + '/business/message/stream'
|
||||
|
||||
// 监听器表 (允许多组件订阅)
|
||||
const listeners = { new_message: new Set(), connected: new Set() }
|
||||
|
||||
Reference in New Issue
Block a user