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:
郭庆泰
2026-08-24 01:00:09 +08:00
co-authored by Claude
parent 80a276e661
commit 904e28710f
123 changed files with 6008 additions and 1706 deletions
+48 -56
View File
@@ -41,12 +41,14 @@
</el-form-item>
</el-form>
<div class="toolbar">
<!-- SUB 子账号: 仅查看被分配的项目, 不参与重新分配, 隐藏"项目分配"按钮 -->
<div v-if="!isSub" class="toolbar">
<el-button type="primary" @click="onAssignBatch">项目分配</el-button>
</div>
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSelectionChange">
<el-table-column type="selection" width="40" />
<!-- SUB 子账号无需勾选 (不参与分配) -->
<el-table-column v-if="!isSub" type="selection" width="40" />
<el-table-column label="项目编号" width="160">
<template #default="{ row }">
<a class="link">{{ row.projectNo }}</a>
@@ -88,7 +90,8 @@
<el-table-column label="操作" width="240" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openSingleScore(row)">项目评价</el-button>
<el-button link type="primary" @click="openAssign(row)">分配</el-button>
<!-- SUB 子账号仅查看, 不参与分配 -->
<el-button v-if="!isSub" link type="primary" @click="openAssign(row)">分配</el-button>
<el-button link type="primary" @click="onView(row)">查看</el-button>
</template>
</el-table-column>
@@ -140,39 +143,38 @@
</div>
<el-form :model="assignForm" label-width="100px" class="modal-form">
<el-form-item label="分配给监察员">
<el-input v-model="assignForm.monitorInput" placeholder="输入监察员姓名筛选" clearable style="width: 240px" @input="filterMonitors" />
</el-form-item>
<el-form-item label="候选监察员">
<div class="chip-list">
<el-tag v-for="m in candidateMonitors" :key="m.userId" class="chip" type="info" effect="plain" @click="pickMonitor(m)">{{ m.name }}</el-tag>
<span v-if="!candidateMonitors.length" style="color:#909399">无可分配监察员</span>
</div>
</el-form-item>
<el-form-item label="分配说明">
<el-input v-model="assignForm.assignDesc" type="textarea" :rows="3" placeholder="请输入分配说明" />
</el-form-item>
<el-form-item label="监察要点">
<el-input v-model="assignForm.assignPoints" type="textarea" :rows="3" placeholder="请输入监察要点" />
<el-select v-model="assignForm.monitorUserIds" multiple collapse-tags collapse-tags-tooltip filterable placeholder="请选择监察员 (姓名 / 手机号, 可多选)" style="width: 100%">
<el-option
v-for="m in allMonitors"
:key="m.userId"
:label="m.name + ' (' + m.phone + ')'"
:value="m.userId" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="assignVisible = false">取消</el-button>
<el-button type="primary" :loading="assignSubmitting" @click="saveAssign">确定</el-button>
<el-button type="primary" :loading="assignSubmitting" :disabled="!assignForm.monitorUserIds.length" @click="saveAssign">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'
import { listSponsorProjects, rateProject, sponsorAssignProject, sponsorAssignBatch } from '@/api/business/project'
import { listSponsorProjects, rateProject, sponsorAssignProject, sponsorAssignBatch, getSponsorAssigns } from '@/api/business/project'
import { bizUpdate } from '@/api/public'
import { listBizPerson } from '@/api/business/person'
import { listSponsorPerson } from '@/api/business/person'
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
const router = useRouter()
// SUB 子账号: 只能看被分配的项目, 不能修改分配
const isSub = computed(() => userStore.isSub)
const loading = ref(false)
const rows = ref([])
@@ -228,7 +230,8 @@ function reset() {
function onSelectionChange(val) { selected.value = val }
function onView(row) {
ElMessageBox.alert(`项目编号: ${row.projectNo}\n项目名称: ${row.projectName}`, '项目详情', { confirmButtonText: '确定' })
// 跳项目详情页 (复用 manager/ManagerProjectDetail.vue, goBack 自动按角色回退)
router.push(`/sponsor/projects/detail/${row.projectId}`)
}
// ========== 执行单位评分 (与 manager/Projects.vue 完全相同的样式和逻辑) ==========
@@ -299,26 +302,25 @@ const assignBatchRows = ref([]) // 批量模式下被分配的项目列
const assignSubmitting = ref(false)
const assignForm = reactive({
projectId: null, projectNo: '', projectName: '',
monitorInput: '', monitorUserId: null, monitorUserName: '',
assignDesc: '', assignPoints: ''
monitorUserIds: []
})
const allMonitors = ref([])
const candidateMonitors = ref([])
async function loadMonitors() {
// 业务隔离: 仅查 login 主账号创建的人员 (parent_user_id = mainUserId)
const mainUid = userStore.id
const res = await listBizPerson({ parentUserId: mainUid, role: 'supervisor', pageNum: 1, pageSize: 100 })
// 跟 sponsor/people (人员管理) 完全一致: 走 sponsorList, SQL 硬编码 unit_type='sponsor'
// + (u.parent_user_id = mainUid OR u.user_id = mainUid), 自动取当前主账号
// 仅筛 role='supervisor' (监察员候选人)
const res = await listSponsorPerson({ role: 'supervisor', pageNum: 1, pageSize: 100 })
allMonitors.value = res.rows || []
candidateMonitors.value = allMonitors.value.slice()
}
function filterMonitors() {
const kw = assignForm.monitorInput.trim()
candidateMonitors.value = kw ? allMonitors.value.filter(m => m.name.includes(kw)) : allMonitors.value.slice()
}
function pickMonitor(m) {
assignForm.monitorUserId = m.userId
assignForm.monitorUserName = m.name
assignForm.monitorInput = m.name
/** 拉项目当前已分配监察员 → 回显 monitorUserIds (单选模式) */
async function loadCurrentAssigns(projectId) {
try {
const resp = await getSponsorAssigns(projectId)
const list = (resp && resp.data) || []
assignForm.monitorUserIds = (Array.isArray(list) ? list : []).map(x => x.monitorUserId).filter(Boolean)
} catch (e) {
assignForm.monitorUserIds = []
}
}
function openAssign(row) {
assignBatchMode.value = false
@@ -326,40 +328,32 @@ function openAssign(row) {
assignForm.projectId = row.projectId
assignForm.projectNo = row.projectNo
assignForm.projectName = row.projectName
assignForm.monitorInput = ''
assignForm.monitorUserId = null
assignForm.monitorUserName = ''
assignForm.assignDesc = ''
assignForm.assignPoints = ''
assignForm.monitorUserIds = []
assignVisible.value = true
loadMonitors()
// 异步回显当前已分配 (等候选加载好, 让 el-select 找得到 option)
loadCurrentAssigns(row.projectId)
}
function openAssignBatch(rows) {
assignBatchMode.value = true
assignBatchRows.value = rows.map(r => ({ projectId: r.projectId, projectNo: r.projectNo, projectName: r.projectName }))
assignForm.monitorInput = ''
assignForm.monitorUserId = null
assignForm.monitorUserName = ''
assignForm.assignDesc = ''
assignForm.assignPoints = ''
// 批量模式: 不同项目原监察员可能不同, 不回显 (提交时按当前多选覆盖全部)
assignForm.monitorUserIds = []
assignVisible.value = true
loadMonitors()
}
async function saveAssign() {
if (!assignForm.monitorUserId) return ElMessage.warning('请选择监察员')
if (!assignForm.assignDesc) return ElMessage.warning('请填写分配说明')
if (!assignForm.monitorUserIds.length) return ElMessage.warning('请至少选择一位监察员')
assignSubmitting.value = true
try {
if (assignBatchMode.value) {
// 批量模式: 循环逐条 POST (后端先删后插, 每个项目都会被覆盖)
// 批量模式: 每个项目都按当前多选覆盖 (后端先删后插 N 条)
let ok = 0, errs = []
for (const r of assignBatchRows.value) {
try {
await sponsorAssignProject({
projectId: r.projectId,
monitorUserId: assignForm.monitorUserId,
assignDesc: assignForm.assignDesc,
assignPoints: assignForm.assignPoints
monitorUserIds: [...assignForm.monitorUserIds]
})
ok++
} catch (e) { errs.push(`${r.projectNo}: ${e?.msg || e.message}`) }
@@ -367,12 +361,10 @@ async function saveAssign() {
if (errs.length) ElMessageBox.alert(`批量分配: 成功 ${ok}/${assignBatchRows.value.length}\n\n失败:\n${errs.join('\n')}`, '分配结果', { type: 'warning' })
else ElMessage.success(`批量分配成功 (${ok}/${assignBatchRows.value.length})`)
} else {
// 单条模式
// 单条模式: 多选一次提交
await sponsorAssignProject({
projectId: assignForm.projectId,
monitorUserId: assignForm.monitorUserId,
assignDesc: assignForm.assignDesc,
assignPoints: assignForm.assignPoints
monitorUserIds: [...assignForm.monitorUserIds]
})
ElMessage.success('分配成功')
}