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:
@@ -1,8 +1,12 @@
|
||||
<template>
|
||||
<div class="page-card">
|
||||
<!-- 面包屑 -->
|
||||
<!-- 面包屑 (按角色显示: sponsor → 我的项目; manager/admin → 项目管理) -->
|
||||
<div class="breadcrumb">
|
||||
首页 / 项目管理 / <span class="current">项目详情</span>
|
||||
首页 /
|
||||
<span v-if="userStore?.user?.roleType === 'sponsor'">我的项目</span>
|
||||
<span v-else-if="userStore?.user?.roleType === 'executor'">项目列表</span>
|
||||
<span v-else>项目管理</span>
|
||||
/ <span class="current">项目详情</span>
|
||||
</div>
|
||||
|
||||
<!-- ================= 1. 项目信息 (11 字段, 两列, 只读) ================= -->
|
||||
@@ -32,11 +36,27 @@
|
||||
<el-col :span="12"><el-form-item label="支持公司"><span class="info-value">{{ row.sponsorAdminUserName || '-' }}</span></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="项目评价"><span class="info-value">{{ fmtScore(row.managerScore) }}</span></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="服务公司">
|
||||
<el-table :data="assigns" border size="small" class="assign-table-el">
|
||||
<el-table-column type="index" label="#" width="48" align="center" />
|
||||
<el-table-column prop="orgName" label="名称" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="sessions" label="场次" width="90" align="right" />
|
||||
<el-table-column label="金额" width="130" align="right">
|
||||
<template #default="{ row }">¥ {{ fmtMoney(row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<span style="color:#c0c4cc">暂无分配 (到 项目管理 / 项目分配 添加)</span>
|
||||
</template>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<!-- ================= 2. 角色劳务设置 (只读) ================= -->
|
||||
<div class="new-card-title">角色劳务设置</div>
|
||||
<p class="hint-text">*角色劳务设置针对该项目所有会议生效</p>
|
||||
<table class="info-table role-labor-table">
|
||||
<colgroup>
|
||||
<col style="width:120px">
|
||||
@@ -59,7 +79,6 @@
|
||||
|
||||
<!-- ================= 3. 支持单位监督员 (只读) ================= -->
|
||||
<div class="new-card-title">支持单位监督员</div>
|
||||
<p class="hint-text">*增加只显示已点支持意向的</p>
|
||||
<div class="monitor-list">
|
||||
<div v-for="(m, idx) in monitors" :key="idx" class="monitor-row">
|
||||
<span class="monitor-num">{{ idx + 1 }}.</span>
|
||||
@@ -68,6 +87,8 @@
|
||||
<div v-if="!monitors.length" class="empty-hint">暂无监督员</div>
|
||||
</div>
|
||||
|
||||
<!-- (已分配执行方 已折叠进上方"项目信息"section, 此处不再重复) -->
|
||||
|
||||
<!-- ================= 4. 已发布公告 (只读, 仅查看) ================= -->
|
||||
<div class="new-card-title">已发布公告</div>
|
||||
<el-table v-loading="loading" :data="announcements" border>
|
||||
@@ -90,16 +111,23 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { bizGet, bizList } from '@/api/public'
|
||||
import { listExecutorOrgs } from '@/api/system'
|
||||
import request from '@/utils/request'
|
||||
import Preview from '@/components/Preview.vue'
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// ===================== 状态 =====================
|
||||
const projectId = ref('')
|
||||
const row = ref({})
|
||||
const loading = ref(false)
|
||||
|
||||
// 已分配执行方 (服务公司) — 数据源: GET /business/project/{id}/assigns + listExecutorOrgs 补 org_name
|
||||
const assigns = ref([])
|
||||
|
||||
// 角色劳务 (从 biz_project.role_labor JSON 字段读; 空数组 → 模板 "暂无角色" 行兜底)
|
||||
const form = reactive({
|
||||
roleRows: []
|
||||
@@ -149,11 +177,7 @@ async function loadProject() {
|
||||
const rl = JSON.parse(row.value.roleLabor || '[]')
|
||||
if (Array.isArray(rl) && rl.length) form.roleRows = rl
|
||||
} catch (e) { /* keep empty */ }
|
||||
// 监督员 (JSON 字段)
|
||||
try {
|
||||
const mn = JSON.parse(row.value.monitors || '[]')
|
||||
if (Array.isArray(mn)) monitors.value = mn
|
||||
} catch (e) { /* keep empty */ }
|
||||
// 监督员 (走 /sponsorAssigns 端点, 不在 loadProject 里)
|
||||
} catch (e) {
|
||||
console.error('[project-detail] load failed', e)
|
||||
row.value = {}
|
||||
@@ -173,8 +197,68 @@ async function loadAnnouncements() {
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 已分配执行方 (服务公司) =====================
|
||||
/**
|
||||
* 数据源同 manager/projects/edit/{id} 的 loadAssigns:
|
||||
* 1) GET /business/project/{id}/assigns 拿原始分配行 (assignId, execUserId, sessions, amount)
|
||||
* 2) 对每个 execUserId 调 listExecutorOrgs({ userId }) 补 org_name
|
||||
* (后端 SQL 已限定 parent_user_id IS NULL, 自动取 MAIN 账号对应的执行公司)
|
||||
* N+1 但项目分配数一般 < 10, 简单可控; 真要优化再加聚合 JOIN 接口
|
||||
*/
|
||||
async function loadAssigns() {
|
||||
try {
|
||||
const resp = await request({ url: `/business/project/${projectId.value}/assigns`, method: 'get' })
|
||||
const list = (resp && resp.data) || []
|
||||
if (!Array.isArray(list) || !list.length) { assigns.value = []; return }
|
||||
// 并发查每个执行方公司名
|
||||
const enriched = await Promise.all(list.map(async (a) => {
|
||||
let orgName = '-'
|
||||
try {
|
||||
const opts = await listExecutorOrgs({ userId: a.execUserId })
|
||||
const org = (opts && opts.data && opts.data[0]) || null
|
||||
if (org && org.orgName) orgName = org.orgName
|
||||
} catch (e) { /* 单条失败不影响其它, 默认显示 '-' */ }
|
||||
return {
|
||||
assignId: a.assignId,
|
||||
execUserId: a.execUserId,
|
||||
orgName,
|
||||
sessions: a.sessions || 0,
|
||||
amount: a.amount || 0
|
||||
}
|
||||
}))
|
||||
assigns.value = enriched
|
||||
} catch (e) {
|
||||
console.error('[project-detail] assigns load failed', e)
|
||||
assigns.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 监督员 (sponsor 视角的监察员) =====================
|
||||
// 数据源: GET /business/project/{id}/sponsorAssigns (返回 [{monitorUserId, monitorUserName, ...}])
|
||||
// 注: 不再读 biz_project.monitors — 该字段在 biz_project 表不存在 (历史 bug)
|
||||
async function loadMonitors() {
|
||||
if (!projectId.value) return
|
||||
try {
|
||||
const resp = await request({ url: `/business/project/${projectId.value}/sponsorAssigns`, method: 'get' })
|
||||
const list = (resp && resp.data) || []
|
||||
monitors.value = (Array.isArray(list) ? list : []).map(x => ({
|
||||
userId: x.monitorUserId,
|
||||
userName: x.monitorUserName || ('(userId=' + x.monitorUserId + ')')
|
||||
}))
|
||||
} catch (e) {
|
||||
console.error('[project-detail] monitors load failed', e)
|
||||
monitors.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 导航 =====================
|
||||
function goBack() { router.push('/manager/projects') }
|
||||
// 按角色回退: sponsor → 我的项目, manager → 项目管理, admin → 项目管理
|
||||
function goBack() {
|
||||
const role = userStore?.role || userStore?.user?.roleType
|
||||
if (role === 'sponsor') router.push('/sponsor/my-projects')
|
||||
else if (role === 'executor') router.push('/executor/projects')
|
||||
else router.push('/manager/projects')
|
||||
}
|
||||
function goEdit() { router.push(`/manager/projects/edit/${projectId.value}`) }
|
||||
|
||||
// ===================== 预览 =====================
|
||||
@@ -188,6 +272,7 @@ function openPreview(url, title) {
|
||||
// ===================== 初始化 =====================
|
||||
onMounted(async () => {
|
||||
await loadProject()
|
||||
await Promise.all([loadAssigns(), loadMonitors()])
|
||||
await loadAnnouncements()
|
||||
})
|
||||
</script>
|
||||
@@ -235,6 +320,9 @@ onMounted(async () => {
|
||||
.role-labor-table td { border-right: 1px solid #ebeef5; }
|
||||
.role-labor-table th:last-child,
|
||||
.role-labor-table td:last-child { border-right: none; }
|
||||
/* 服务公司表格 (el-table in form): 紧凑, 不要撑满整张卡片 */
|
||||
.assign-table-el { width: auto; max-width: 560px; }
|
||||
.assign-table-el :deep(.el-table__empty-block) { min-height: 48px; }
|
||||
.info-table th { background: #fafafa; padding: 10px 12px; text-align: left; color: #1a1a1a; font-weight: 600; border-bottom: 1px solid #f0f0f0; white-space: nowrap; }
|
||||
.info-table td { padding: 10px 12px; border-bottom: 1px solid #f5f5f5; color: #595959; vertical-align: middle; }
|
||||
.empty-row { text-align: center; color: #c0c4cc; }
|
||||
|
||||
Reference in New Issue
Block a user