feat: 会议提交剩余时间+解冻 + 会务/劳务材料批量下载 + 多角色人员/账号模块完善
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="支持单位:">
|
||||
<el-select
|
||||
v-model="q.sponsorAdminUserIds"
|
||||
v-model="q.sponsorOrgIds"
|
||||
multiple
|
||||
filterable
|
||||
remote
|
||||
@@ -17,12 +17,12 @@
|
||||
placeholder="输入支持单位名称搜索"
|
||||
style="width:260px"
|
||||
>
|
||||
<el-option v-for="o in sponsorOrgOptions" :key="o.userId" :label="o.orgName" :value="o.userId" />
|
||||
<el-option v-for="o in sponsorOrgOptions" :key="o.orgId" :label="o.orgName" :value="o.orgId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="服务机构:">
|
||||
<el-select
|
||||
v-model="q.execAdminUserIds"
|
||||
v-model="q.execOrgIds"
|
||||
multiple
|
||||
filterable
|
||||
remote
|
||||
@@ -34,7 +34,7 @@
|
||||
placeholder="输入服务机构名称搜索"
|
||||
style="width:260px"
|
||||
>
|
||||
<el-option v-for="o in executorOrgOptions" :key="o.userId" :label="o.orgName" :value="o.userId" />
|
||||
<el-option v-for="o in executorOrgOptions" :key="o.orgId" :label="o.orgName" :value="o.orgId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目编号:"><el-input v-model="q.projectNo" placeholder="输入项目编号" clearable /></el-form-item>
|
||||
@@ -60,7 +60,6 @@
|
||||
<el-option label="已结算" value="Y" /><el-option label="未结算" value="N" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="评价得分:"><el-input v-model="q.managerScore" placeholder="输入评分" clearable /></el-form-item>
|
||||
<el-form-item><el-button type="primary" @click="load">查找</el-button><el-button @click="reset">重置</el-button></el-form-item>
|
||||
</el-form>
|
||||
|
||||
@@ -119,22 +118,22 @@
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="op-cell">
|
||||
<el-button link type="primary" @click="viewDetail(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="doEdit(row)">编辑</el-button>
|
||||
<el-button link type="primary" @click="doCreateMeeting(row)">建会</el-button>
|
||||
<el-button link type="primary" @click="doAssign(row)">分配</el-button>
|
||||
<el-dropdown trigger="click" @command="(cmd) => onAction(cmd, row)">
|
||||
<el-button link type="primary" class="op-dropdown">
|
||||
更多<el-icon class="op-caret"><ArrowDown /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="createMeeting">建会</el-dropdown-item>
|
||||
<el-dropdown-item v-if="row.isFinished !== '1' && row.isFinished !== 1" command="close">结题</el-dropdown-item>
|
||||
<el-dropdown-item command="assign">项目分配</el-dropdown-item>
|
||||
<el-dropdown-item command="view">查看</el-dropdown-item>
|
||||
<el-dropdown-item command="edit">编辑</el-dropdown-item>
|
||||
<el-dropdown-item command="activate">开通</el-dropdown-item>
|
||||
<el-dropdown-item command="delAnn">删除公告</el-dropdown-item>
|
||||
<el-dropdown-item command="rate">执行单位评分</el-dropdown-item>
|
||||
<el-dropdown-item command="exportExperts" divided>导出报名专家</el-dropdown-item>
|
||||
<el-dropdown-item v-if="row.isPublished === '1' || row.publishUrl" command="activate" divided>开通</el-dropdown-item>
|
||||
<el-dropdown-item v-if="row.isPublished === '1' || row.publishUrl" command="delAnn" divided>删除公告</el-dropdown-item>
|
||||
<el-dropdown-item v-if="canDelete" command="delete" divided>删除项目</el-dropdown-item>
|
||||
<el-dropdown-item command="exportExperts">导出报名专家</el-dropdown-item>
|
||||
<el-dropdown-item v-if="canDelete" command="delete">删除项目</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -180,7 +179,7 @@
|
||||
<!-- 开通专用字段 -->
|
||||
<template v-else-if="batchKind==='activate'">
|
||||
<p>已选择 <b>{{ selection.length }}</b> 个项目。</p>
|
||||
<el-form-item label="开通截止时间"><el-date-picker v-model="activateDeadline" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="开通截止时间"><el-date-picker v-model="activateDeadline" type="date" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="batchOpen=false">取消</el-button>
|
||||
@@ -196,8 +195,11 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ============= 单行开通 modal ============= -->
|
||||
<el-dialog v-model="activateModalOpen" title="您确定要开通吗?" width="420px" :show-close="false">
|
||||
<!-- ============= 单行开通 modal (置 open_status=Y + 开通截止时间 open_deadline) ============= -->
|
||||
<el-dialog v-model="activateModalOpen" title="开通项目" width="420px" :show-close="false">
|
||||
<el-form-item label="开通截止时间">
|
||||
<el-date-picker v-model="activateDeadlineSingle" type="date" value-format="YYYY-MM-DD" style="width:100%" />
|
||||
</el-form-item>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="confirmActivate">确定</el-button>
|
||||
<el-button @click="activateModalOpen=false">取消</el-button>
|
||||
@@ -248,7 +250,7 @@ const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const q = ref({ sponsorAdminUserIds: [], execAdminUserIds: [], projectNo: '', projectName: '', projectForm: '', startTime: '', endTime: '', isFinished: '', isSettled: '', managerScore: '' })
|
||||
const q = ref({ sponsorOrgIds: [], execOrgIds: [], projectNo: '', projectName: '', projectForm: '', startTime: '', endTime: '', isFinished: '', isSettled: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
@@ -266,19 +268,19 @@ async function load() {
|
||||
}
|
||||
// 操作列按钮 handlers
|
||||
function onAction(cmd, row) {
|
||||
if (cmd === 'createMeeting') doCreateMeeting(row)
|
||||
else if (cmd === 'close') openClose(row)
|
||||
else if (cmd === 'assign') doAssign(row)
|
||||
else if (cmd === 'rate') openSingleScore(row)
|
||||
else if (cmd === 'exportExperts') exportExperts(row)
|
||||
if (cmd === 'close') openClose(row)
|
||||
else if (cmd === 'view') viewDetail(row)
|
||||
else if (cmd === 'edit') doEdit(row)
|
||||
else if (cmd === 'activate') openActivate(row)
|
||||
else if (cmd === 'delAnn') openDeleteAnnouncement(row)
|
||||
else if (cmd === 'rate') openSingleScore(row)
|
||||
else if (cmd === 'exportExperts') exportExperts(row)
|
||||
else if (cmd === 'delete') doDelete(row)
|
||||
}
|
||||
// 仅 admin / manager 可见删除按钮 (前端 v-if, 后端 controller 再兜底 role 校验)
|
||||
// 仅 admin 可见删除按钮 (前端 v-if, 后端 controller 再兜底 role 校验)
|
||||
const canDelete = computed(() => {
|
||||
const r = userStore.role
|
||||
return r === 'admin' || r === 'manager'
|
||||
return r === 'admin'
|
||||
})
|
||||
function doCreateMeeting(row) { router.push(`/manager/meetings/new?projectId=${row.projectId}`) }
|
||||
function doAssign(row) {
|
||||
@@ -288,7 +290,7 @@ function doAssign(row) {
|
||||
function doEdit(row) { router.push(`/manager/projects/edit/${row.projectId}`) }
|
||||
|
||||
// 删除项目 (软删除 + 级联置 5 张项目表 + 项目下所有会议 is_deleted=1)
|
||||
// 后端 BizProjectController.remove 已强制 role in (admin, manager), 前端只做可见性 v-if
|
||||
// 后端 BizProjectController.remove 已强制仅 admin, 前端只做可见性 v-if
|
||||
async function doDelete(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
@@ -335,7 +337,7 @@ async function onBatchDelete() {
|
||||
}
|
||||
}
|
||||
|
||||
function reset() { q.value = { sponsorAdminUserIds:[], execAdminUserIds:[], projectNo:'', projectName:'', projectForm:'', startTime:'', endTime:'', isFinished:'', isSettled:'', managerScore:'' }; page.pageNum = 1; load() }
|
||||
function reset() { q.value = { sponsorOrgIds:[], execOrgIds:[], projectNo:'', projectName:'', projectForm:'', startTime:'', endTime:'', isFinished:'', isSettled:'' }; page.pageNum = 1; load() }
|
||||
|
||||
// 读 URL query 写入 q (Workbench KPI 卡跳转时带 ?isFinished=1 / ?isSettled=Y 等, 让列表页自动应用筛选)
|
||||
// 注意: 只读不改 URL — 列表页内 reset()/search 不反向写 URL, Workbench 是 source of truth
|
||||
@@ -393,11 +395,13 @@ function confirmClose() {
|
||||
|
||||
function openActivate(row) {
|
||||
activateTargetRow.value = row
|
||||
activateDeadlineSingle.value = row.openDeadline || ''
|
||||
activateModalOpen.value = true
|
||||
}
|
||||
function confirmActivate() {
|
||||
if (!activateTargetRow.value) return
|
||||
bizUpdate('project', { projectId: activateTargetRow.value.projectId, isFinished: '0' })
|
||||
if (!activateDeadlineSingle.value) return ElMessage.warning('请选择开通截止时间')
|
||||
bizUpdate('project', { projectId: activateTargetRow.value.projectId, openStatus: 'Y', openDeadline: activateDeadlineSingle.value })
|
||||
.then(() => { ElMessage.success('已开通'); activateModalOpen.value = false; load() })
|
||||
.catch(() => ElMessage.error('开通失败'))
|
||||
}
|
||||
@@ -408,15 +412,8 @@ function openDeleteAnnouncement(row) {
|
||||
}
|
||||
function confirmDeleteAnn() {
|
||||
if (!delAnnTargetRow.value) return
|
||||
// 清空全部公示 URL + 取消发布状态
|
||||
// (mapper.updateByPrimaryKey 的 trim prefix="SET" 必须有字段, 不能只传空字符串)
|
||||
bizUpdate('project', {
|
||||
projectId: delAnnTargetRow.value.projectId,
|
||||
publishUrl: '',
|
||||
invitationUrl: '',
|
||||
supportLetterUrl: '',
|
||||
isPublished: '0'
|
||||
})
|
||||
// 删除公告: 后端专用端点, 将 3 个公示 URL 置 NULL (未发布由 URL 为空判定, 无独立 is_published 列)
|
||||
request.delete(`/business/project/${delAnnTargetRow.value.projectId}/announcement`)
|
||||
.then(() => { ElMessage.success(`已删除项目 ${delAnnTargetRow.value.projectNo} 的公告`); delAnnModalOpen.value = false; load() })
|
||||
.catch(() => ElMessage.error('删除失败'))
|
||||
}
|
||||
@@ -500,6 +497,7 @@ const batchSubmitting = ref(false)
|
||||
const batchMap = { score: '批量评分', close: '批量结题', activate: '批量开通' }
|
||||
const batchScoreForm = reactive({ qualityScore: 5, responseScore: 5, cooperationScore: 5, complianceScore: 5 }) // 批量评分: 共享一份 4 维度
|
||||
const activateDeadline = ref('')
|
||||
const activateDeadlineSingle = ref('')
|
||||
|
||||
// 5 个单行 modal 状态
|
||||
const closeModalOpen = ref(false)
|
||||
@@ -564,7 +562,7 @@ async function confirmBatch() {
|
||||
if (!activateDeadline.value) return ElMessage.warning('请选择开通截止时间')
|
||||
try {
|
||||
batchSubmitting.value = true
|
||||
await Promise.all(selection.value.map(r => bizUpdate('project', { projectId: r.projectId, endTime: activateDeadline.value })))
|
||||
await Promise.all(selection.value.map(r => bizUpdate('project', { projectId: r.projectId, openStatus: 'Y', openDeadline: activateDeadline.value })))
|
||||
ElMessage.success(`已批量开通 ${selection.value.length} 个项目`)
|
||||
batchOpen.value = false
|
||||
selection.value = []
|
||||
@@ -632,12 +630,12 @@ function searchSponsorOrgsForFilter(query) {
|
||||
if (query) params.orgName = query
|
||||
const r = await listSponsorOrgs(params)
|
||||
sponsorOrgOptions.value = r.data || []
|
||||
// 已选项不在结果里时按 userId 单独拉回 (multi-select 必须保证每个已选 tag 都有 label)
|
||||
const ids = q.value.sponsorAdminUserIds || []
|
||||
for (const uid of ids) {
|
||||
if (!sponsorOrgOptions.value.find(u => u.userId === uid)) {
|
||||
const r2 = await listSponsorOrgs({ userId: uid })
|
||||
const sel = (r2.data || []).find(u => u.userId === uid)
|
||||
// 已选项不在结果里时按 orgId 单独拉回 (multi-select 必须保证每个已选 tag 都有 label)
|
||||
const ids = q.value.sponsorOrgIds || []
|
||||
for (const id of ids) {
|
||||
if (!sponsorOrgOptions.value.find(u => u.orgId === id)) {
|
||||
const r2 = await listSponsorOrgs({ orgId: id })
|
||||
const sel = (r2.data || []).find(u => u.orgId === id)
|
||||
if (sel) sponsorOrgOptions.value = [sel, ...sponsorOrgOptions.value]
|
||||
}
|
||||
}
|
||||
@@ -659,12 +657,12 @@ function searchExecutorOrgsForFilter(query) {
|
||||
if (query) params.orgName = query
|
||||
const r = await listExecutorOrgs(params)
|
||||
executorOrgOptions.value = r.data || []
|
||||
// 已选项不在结果里时按 userId 单独拉回 (multi-select 必须保证每个已选 tag 都有 label)
|
||||
const ids = q.value.execAdminUserIds || []
|
||||
for (const uid of ids) {
|
||||
if (!executorOrgOptions.value.find(u => u.userId === uid)) {
|
||||
const r2 = await listExecutorOrgs({ userId: uid })
|
||||
const sel = (r2.data || []).find(u => u.userId === uid)
|
||||
// 已选项不在结果里时按 orgId 单独拉回 (multi-select 必须保证每个已选 tag 都有 label)
|
||||
const ids = q.value.execOrgIds || []
|
||||
for (const id of ids) {
|
||||
if (!executorOrgOptions.value.find(u => u.orgId === id)) {
|
||||
const r2 = await listExecutorOrgs({ orgId: id })
|
||||
const sel = (r2.data || []).find(u => u.orgId === id)
|
||||
if (sel) executorOrgOptions.value = [sel, ...executorOrgOptions.value]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user