chore: biz_org 合并 / BizProject 重构 / 各角色页面调整 (上次重构遗留)

涵盖改动范围:
- 后端 BizOrg / BizProject / BizProjectRating / BizSpecialPlan controller + domain + mapper + service
- 前端 admin (BizSpecialPlanAdmin, ProjectCategory) / executor / leader / manager / sponsor 多页调整
- api/dict.js + AdminLayout.vue 同步
This commit is contained in:
郭庆泰
2026-08-18 00:30:34 +08:00
parent c9fc6ebdb8
commit b52a334184
32 changed files with 744 additions and 454 deletions
+52 -77
View File
@@ -1,34 +1,27 @@
<template>
<div class="page-card manager-plans">
<div class="breadcrumb">首页 / 项目策划方案</div>
<div class="hint-list">
<p>*批量审核策划案名称投稿ID审核结果审核意见只能审核不通过</p>
</div>
<!-- ========== 筛选区 ( People.vue 风格一致) ========== -->
<el-form inline :model="q" class="filter-form">
<el-form-item label="项目编号"><el-input v-model="q.projectNo" placeholder="输入项目编号" clearable /></el-form-item>
<el-form-item label="策划方案名称"><el-input v-model="q.planName" placeholder="输入策划方案名称" clearable /></el-form-item>
<el-form-item label="项目方向">
<el-select v-model="q.planDirection" placeholder="请选择" clearable>
<el-option label="专项计划一" value="专项计划一" /><el-option label="专项计划二" value="专项计划二" />
<el-select v-model="q.planDirectionId" placeholder="请选择" clearable style="width:200px">
<el-option v-for="opt in planDirectionOptions" :key="opt.id" :label="opt.title" :value="opt.id" />
</el-select>
</el-form-item>
<el-form-item label="项目类别">
<el-select v-model="q.planCategory" placeholder="请选择" clearable>
<el-option label="共识" value="共识" /><el-option label="对外交流" value="对外交流" />
<el-option label="学术项目" value="学术项目" /><el-option label="科研项目" value="科研项目" />
<el-option label="指南" value="指南" /><el-option label="会议项目" value="会议项目" />
</el-select>
<dict-select v-model="q.planCategory" dict-type="biz_project_category" value-field="label" style="width:160px" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="q.status" placeholder="请选择" clearable>
<el-option label="待审核" value="0" /><el-option label="审核通过" value="1" /><el-option label="已退回" value="2" />
<el-select v-model="q.status" placeholder="请选择" clearable style="width:140px">
<el-option label="待审核" value="1" /><el-option label="通过" value="2" /><el-option label="拒绝" value="3" />
</el-select>
</el-form-item>
<el-form-item label="是否结算">
<el-select v-model="q.isSettled" placeholder="请选择" clearable>
<el-option label="已结算" value="1" /><el-option label="未结算" value="0" />
<el-select v-model="q.isSettled" placeholder="请选择" clearable style="width:140px">
<el-option label="已结算" value="Y" /><el-option label="未结算" value="N" />
</el-select>
</el-form-item>
<el-form-item label="备注"><el-input v-model="q.remark" placeholder="输入备注" clearable /></el-form-item>
@@ -40,7 +33,6 @@
<!-- ========== 批量按钮 ( People.vue 风格一致) ========== -->
<div class="batch-bar">
<el-button type="primary" :disabled="!selection.length" @click="batchAudit">批量审核</el-button>
<el-button type="primary" :disabled="!selection.length" @click="batchSettle">批量结算</el-button>
<span v-if="selection.length" class="filter-tip">已选 {{ selection.length }} </span>
</div>
@@ -49,7 +41,7 @@
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSel">
<el-table-column type="selection" width="44" />
<el-table-column prop="planName" label="策划方案名称" min-width="200" fixed show-overflow-tooltip />
<el-table-column prop="planDirection" label="项目方向" min-width="220" show-overflow-tooltip />
<el-table-column prop="planDirectionTitle" label="项目方向" min-width="220" show-overflow-tooltip />
<el-table-column prop="planCategory" label="项目类别" width="100" align="center" />
<el-table-column prop="projectForm" label="项目形式" width="100" align="center" />
<el-table-column label="设计文件" width="160" align="center">
@@ -63,15 +55,13 @@
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag size="small" :type="row.status==='1' ? 'success' : row.status==='2' ? 'danger' : 'warning'">
{{ row.status==='1' ? '审核通过' : row.status==='2' ? '已退回' : '待审核' }}
</el-tag>
<audit-status-tag :status="row.status" />
</template>
</el-table-column>
<el-table-column label="是否结算" width="100" align="center">
<template #default="{ row }">
<el-tag size="small" :type="row.isSettled==='1' ? 'success' : 'warning'">
{{ row.isSettled==='1' ? '已结算' : '未结算' }}
<el-tag size="small" :type="row.isSettled==='Y' ? 'success' : 'warning'">
{{ row.isSettled==='Y' ? '已结算' : '未结算' }}
</el-tag>
</template>
</el-table-column>
@@ -81,7 +71,7 @@
<template #default="{ row }">
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
<el-button link type="primary" @click="onEdit(row)">修改</el-button>
<el-button link type="primary" @click="onSettle(row)" v-if="row.isSettled!=='1'">结算</el-button>
<el-button link type="primary" @click="onSettle(row)" v-if="row.isSettled!=='Y'">结算</el-button>
<el-button link type="primary" v-if="row.designFileUrl" @click="downloadFile(row)">下载</el-button>
</template>
</el-table-column>
@@ -90,19 +80,17 @@
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
</div>
<!-- 审核 dialog (按原型: 通过/退回 radio + 项目编号通过时必填 + 审核意见 + 确认/取消) -->
<!-- 审核 dialog: 策划案名称 + 投稿ID + 审核结果(固定 退回) + 审核意见 -->
<el-dialog v-model="auditOpen" title="审核策划方案" width="500px">
<el-form :model="auditForm" :rules="auditRules" ref="auditFormRef" label-width="100px">
<el-form-item label="案名称"><span>{{ auditForm.planName }}</span></el-form-item>
<el-form-item label="策划案名称"><span>{{ auditForm.planName }}</span></el-form-item>
<el-form-item label="投稿ID"><span>{{ auditForm.planId }}</span></el-form-item>
<el-form-item label="审核结果">
<el-radio-group v-model="auditForm.result">
<el-radio label="1">通过</el-radio><el-radio label="2">退回</el-radio>
</el-radio-group>
<span style="color:#f56c6c">退回审核不通过</span>
</el-form-item>
<el-form-item v-if="auditForm.result==='1'" label="项目编号" prop="projectNo">
<el-input v-model="auditForm.projectNo" placeholder="通过时项目编号为必填" />
<el-form-item label="审核意见" prop="opinion">
<el-input v-model="auditForm.opinion" type="textarea" rows="3" placeholder="请填写审核不通过原因" />
</el-form-item>
<el-form-item label="审核意见"><el-input v-model="auditForm.opinion" type="textarea" rows="3" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="auditOpen=false">取消</el-button>
@@ -116,16 +104,12 @@
<el-form-item label="方案名称" prop="planName"><el-input v-model="editForm.planName" /></el-form-item>
<el-form-item label="项目编号" prop="projectNo"><el-input v-model="editForm.projectNo" /></el-form-item>
<el-form-item label="项目方向">
<el-select v-model="editForm.planDirection" placeholder="请选择" style="width:100%">
<el-option label="专项计划一" value="专项计划一" /><el-option label="专项计划二" value="专项计划二" />
<el-select v-model="editForm.planDirectionId" placeholder="请选择" style="width:100%">
<el-option v-for="opt in planDirectionOptions" :key="opt.id" :label="opt.title" :value="opt.id" />
</el-select>
</el-form-item>
<el-form-item label="项目类别">
<el-select v-model="editForm.planCategory" placeholder="请选择" style="width:100%">
<el-option label="共识" value="共识" /><el-option label="对外交流" value="对外交流" />
<el-option label="学术项目" value="学术项目" /><el-option label="科研项目" value="科研项目" />
<el-option label="指南" value="指南" /><el-option label="会议项目" value="会议项目" />
</el-select>
<dict-select v-model="editForm.planCategory" dict-type="biz_project_category" value-field="label" style="width:100%" />
</el-form-item>
<el-form-item label="项目形式">
<el-select v-model="editForm.projectForm" placeholder="请选择" style="width:100%">
@@ -159,10 +143,22 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { bizList, bizUpdate } from '@/api/public'
import request from '@/utils/request'
import { ElMessage } from 'element-plus'
import Preview from '@/components/Preview.vue'
import DictSelect from '@/components/DictSelect.vue'
import AuditStatusTag from '@/components/AuditStatusTag.vue'
const q = ref({ planName: '', projectNo: '', planDirection: '', planCategory: '', status: '', isSettled: '', remark: '' })
// 专项计划下拉选项 (项目方向): [{id, title}]
const planDirectionOptions = ref([])
async function loadPlanDirectionOptions() {
try {
const { data } = await request.get('/business/specialPlan/options')
planDirectionOptions.value = (data && data.data) || data || []
} catch { planDirectionOptions.value = [] }
}
const q = ref({ planName: '', projectNo: '', planDirectionId: '', planCategory: '', status: '', isSettled: '', remark: '' })
const rows = ref([]); const selection = ref([])
const loading = ref(false)
const submitting = ref(false)
@@ -177,28 +173,15 @@ async function load() {
} catch { rows.value = []; page.total = 0 }
finally { loading.value = false }
}
function reset() { q.value = { planName:'', projectNo:'', planDirection:'', planCategory:'', status:'', isSettled:'', remark:'' }; page.pageNum = 1; load() }
function reset() { q.value = { planName:'', projectNo:'', planDirectionId:'', planCategory:'', status:'', isSettled:'', remark:'' }; page.pageNum = 1; load() }
function onSel(rs) { selection.value = rs }
async function batchAudit() {
if (!selection.value.length) return ElMessage.warning('请先勾选行')
let ok = 0
submitting.value = true
for (const r of selection.value) {
try { await bizUpdate('projectPlan', { planId: r.planId, status: '2', auditOpinion: '批量审核不通过' }); ok++ } catch {}
}
submitting.value = false
ElMessage.success(`批量审核 ${ok}/${selection.value.length}`)
selection.value = []
load()
}
async function batchSettle() {
if (!selection.value.length) return ElMessage.warning('请先勾选行')
submitting.value = true
let ok = 0
for (const r of selection.value) {
try { await bizUpdate('projectPlan', { planId: r.planId, isSettled: '1' }); ok++ } catch {}
try { await bizUpdate('projectPlan', { planId: r.planId, isSettled: 'Y' }); ok++ } catch {}
}
submitting.value = false
ElMessage.success(`批量结算 ${ok}/${selection.value.length}`)
@@ -208,17 +191,23 @@ async function batchSettle() {
const auditOpen = ref(false)
const auditFormRef = ref(null)
const auditForm = reactive({ planId: '', planName: '', projectNo: '', result: '1', opinion: '' })
// 审核: 只能审核不通过, 固定写死 status='2'
const auditForm = reactive({ planId: '', planName: '', opinion: '' })
const auditRules = {
projectNo: [{ required: true, message: '通过时项目编号为必填', trigger: 'blur' }]
opinion: [{ required: true, message: '请填写审核不通过原因', trigger: 'blur' }]
}
function onAudit(row) {
auditForm.planId = row.planId
auditForm.planName = row.planName
auditForm.opinion = ''
auditOpen.value = true
}
function onAudit(row) { auditForm.planId = row.planId; auditForm.planName = row.planName; auditForm.projectNo = row.projectNo || ''; auditForm.result = '1'; auditForm.opinion = ''; auditOpen.value = true }
async function submitAudit() {
try {
await auditFormRef.value.validate()
submitting.value = true
await bizUpdate('projectPlan', { planId: auditForm.planId, status: auditForm.result, auditOpinion: auditForm.opinion, projectNo: auditForm.result === '1' ? auditForm.projectNo : undefined })
ElMessage.success('审核已确认')
await bizUpdate('projectPlan', { planId: auditForm.planId, status: '3', auditOpinion: auditForm.opinion })
ElMessage.success('已退回')
auditOpen.value = false
load()
} catch (e) { if (e?.message) ElMessage.error(e.message) }
@@ -227,17 +216,17 @@ async function submitAudit() {
const editOpen = ref(false)
const editFormRef = ref(null)
const editForm = reactive({ planId:'', planName:'', projectNo:'', planDirection:'', planCategory:'', projectForm:'', designFileUrl:'', remark:'' })
const editForm = reactive({ planId:'', planName:'', projectNo:'', planDirectionId:'', planCategory:'', projectForm:'', designFileUrl:'', remark:'' })
const editRules = { planName:[{required:true,message:'请输入方案名称',trigger:'blur'}] }
function onEdit(row) {
Object.assign(editForm, {
planId: row.planId, planName: row.planName, projectNo: row.projectNo,
planDirection: row.planDirection, planCategory: row.planCategory,
planDirectionId: row.planDirectionId ?? null, planCategory: row.planCategory,
projectForm: row.projectForm, designFileUrl: row.designFileUrl || '', remark: row.remark || ''
})
editOpen.value = true
}
function resetEditForm() { Object.assign(editForm, { planId:'', planName:'', projectNo:'', planDirection:'', planCategory:'', projectForm:'', designFileUrl:'', remark:'' }) }
function resetEditForm() { Object.assign(editForm, { planId:'', planName:'', projectNo:'', planDirectionId:'', planCategory:'', projectForm:'', designFileUrl:'', remark:'' }) }
async function submitEdit() {
await editFormRef.value.validate()
try {
@@ -256,7 +245,7 @@ function onSettle(row) { settleForm.planId = row.planId; settleForm.planName = r
async function submitSettle() {
try {
submitting.value = true
await bizUpdate('projectPlan', { planId: settleForm.planId, isSettled: '1' })
await bizUpdate('projectPlan', { planId: settleForm.planId, isSettled: 'Y' })
ElMessage.success('已结算')
settleOpen.value = false
load()
@@ -275,7 +264,7 @@ function downloadFile(row) {
a.click()
}
onMounted(load)
onMounted(() => { loadPlanDirectionOptions(); load() })
</script>
<style scoped>
@@ -295,18 +284,4 @@ onMounted(load)
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
.filter-tip { color: #909399; font-size: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
/* 红字提示 (与 People.vue 一致) */
.hint-list {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 8px;
padding: 12px 20px;
margin-bottom: 16px;
font-size: 12px;
color: #ff4d4f;
line-height: 1.8;
}
.hint-list p { margin-bottom: 4px; }
.hint-list p:last-child { margin-bottom: 0; }
</style>