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
+81 -62
View File
@@ -43,7 +43,6 @@
<div class="toolbar">
<el-button type="primary" @click="onAssignBatch">项目分配</el-button>
<el-button @click="onEvalBatch">批量评价</el-button>
</div>
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSelectionChange">
@@ -80,15 +79,15 @@
<span>{{ row.isFinished === '1' ? '已结题' : '未结题' }}</span>
</template>
</el-table-column>
<el-table-column label="执行单位评价" width="120" align="center">
<el-table-column label="执行单位评价(支持方)" width="150" align="center">
<template #default="{ row }">
<a v-if="row.ratingScore" class="link">{{ row.ratingScore }}</a>
<a v-if="row.sponsorScore" class="link">{{ row.sponsorScore }}</a>
<span v-else style="color:#909399"></span>
</template>
</el-table-column>
<el-table-column label="操作" width="240" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openEval(row)">项目评价</el-button>
<el-button link type="primary" @click="openSingleScore(row)">项目评价</el-button>
<el-button link type="primary" @click="openAssign(row)">分配</el-button>
<el-button link type="primary" @click="onView(row)">查看</el-button>
</template>
@@ -106,37 +105,29 @@
@current-change="load"
/>
<!-- ============ Modal 1: 项目评价 ============ -->
<el-dialog v-model="evalVisible" title="执行单位评分" width="600px" append-to-body destroy-on-close>
<div class="modal-project-info">
<div><span class="lbl">项目编号</span><span>{{ evalForm.projectNo }}</span></div>
<div><span class="lbl">项目名称</span><span>{{ evalForm.projectName }}</span></div>
</div>
<el-form :model="evalForm" label-width="120px" class="modal-form">
<el-form-item label="履约质量">
<el-rate v-model="evalForm.q1" :max="5" :colors="['#99A9BF','#F7BA2A','#FF9900']" show-text :texts="['1', '2', '3', '4', '5']" />
</el-form-item>
<el-form-item label="时效响应">
<el-rate v-model="evalForm.q2" :max="5" :colors="['#99A9BF','#F7BA2A','#FF9900']" show-text :texts="['1', '2', '3', '4', '5']" />
</el-form-item>
<el-form-item label="配合度">
<el-rate v-model="evalForm.q3" :max="5" :colors="['#99A9BF','#F7BA2A','#FF9900']" show-text :texts="['1', '2', '3', '4', '5']" />
</el-form-item>
<el-form-item label="合规安全">
<el-rate v-model="evalForm.q4" :max="5" :colors="['#99A9BF','#F7BA2A','#FF9900']" show-text :texts="['1', '2', '3', '4', '5']" />
</el-form-item>
<el-form-item label="评价备注">
<el-input v-model="evalForm.remark" type="textarea" :rows="4" placeholder="请输入评价备注" />
</el-form-item>
</el-form>
<!-- ============ Modal 1: 项目评价 ( manager/Projects.vue 完全相同的样式) ============ -->
<el-dialog v-model="singleScoreModalOpen" title="执行单位评分" width="626px" :show-close="false">
<table class="score-table" style="width:100%;border-collapse:collapse;font-size:13px">
<thead><tr style="background:#fafafa">
<th style="width:110px;padding:10px 12px;text-align:left">评价维度</th>
<th style="padding:10px 12px;text-align:left">评价内容(简洁版)</th>
<th style="width:130px;padding:10px 12px;text-align:center">评分标准(1~5 )</th>
</tr></thead>
<tbody>
<tr><td style="padding:10px 12px;border-top:1px solid #f5f5f5">履约质量</td><td style="padding:10px 12px;border-top:1px solid #f5f5f5">服务/活动效果达标度</td><td style="padding:10px 12px;border-top:1px solid #f5f5f5;text-align:center"><el-input-number v-model="singleScoreForm.qualityScore" :min="0" :max="5" :step="1" controls-position="right" placeholder="1~5" /></td></tr>
<tr><td style="padding:10px 12px;border-top:1px solid #f5f5f5">时效响应</td><td style="padding:10px 12px;border-top:1px solid #f5f5f5">执行 & 售后响应速度</td><td style="padding:10px 12px;border-top:1px solid #f5f5f5;text-align:center"><el-input-number v-model="singleScoreForm.responseScore" :min="0" :max="5" :step="1" controls-position="right" placeholder="1~5" /></td></tr>
<tr><td style="padding:10px 12px;border-top:1px solid #f5f5f5">配合度</td><td style="padding:10px 12px;border-top:1px solid #f5f5f5">沟通配合 & 问题处理</td><td style="padding:10px 12px;border-top:1px solid #f5f5f5;text-align:center"><el-input-number v-model="singleScoreForm.cooperationScore" :min="0" :max="5" :step="1" controls-position="right" placeholder="1~5" /></td></tr>
<tr><td style="padding:10px 12px;border-top:1px solid #f5f5f5">合规安全</td><td style="padding:10px 12px;border-top:1px solid #f5f5f5">流程合规 & 无安全事故</td><td style="padding:10px 12px;border-top:1px solid #f5f5f5;text-align:center"><el-input-number v-model="singleScoreForm.complianceScore" :min="0" :max="5" :step="1" controls-position="right" placeholder="1~5" /></td></tr>
</tbody>
</table>
<template #footer>
<el-button @click="evalVisible = false">取消</el-button>
<el-button type="primary" @click="saveEval">确定</el-button>
<el-button type="primary" @click="confirmSingleScore">确定</el-button>
<el-button @click="singleScoreModalOpen=false">取消</el-button>
</template>
</el-dialog>
<!-- ============ Modal 2: 项目分配 (单选 / 批量复用) ============ -->
<el-dialog :title="assignBatchMode ? `批量项目分配 (${assignBatchRows.length} 个项目)` : '项目分配'" width="600px" append-to-body destroy-on-close>
<el-dialog v-model="assignVisible" :title="assignBatchMode ? `批量项目分配 (${assignBatchRows.length} 个项目)` : '项目分配'" width="600px" append-to-body destroy-on-close>
<!-- 单选模式: 显示项目编号 / 名称 -->
<div v-if="!assignBatchMode" class="modal-project-info">
<div><span class="lbl">项目编号</span><span>{{ assignForm.projectNo }}</span></div>
@@ -175,7 +166,9 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'
import { listSponsorProjects, rateProject, sponsorAssignProject, sponsorAssignBatch } from '@/api/business/project'
import { bizUpdate } from '@/api/public'
import { listBizPerson } from '@/api/business/person'
import { useUserStore } from '@/store/user'
@@ -238,39 +231,65 @@ function onView(row) {
ElMessageBox.alert(`项目编号: ${row.projectNo}\n项目名称: ${row.projectName}`, '项目详情', { confirmButtonText: '确定' })
}
// ========== Modal 1: 项目评价 ==========
const evalVisible = ref(false)
const evalForm = reactive({ projectId: null, projectNo: '', projectName: '', q1: 5, q2: 5, q3: 5, q4: 5, remark: '' })
function openEval(row) {
evalForm.projectId = row.projectId
evalForm.projectNo = row.projectNo
evalForm.projectName = row.projectName
evalForm.q1 = row.sponsorQ1 || 5
evalForm.q2 = row.sponsorQ2 || 5
evalForm.q3 = row.sponsorQ3 || 5
evalForm.q4 = row.sponsorQ4 || 5
evalForm.remark = row.sponsorRemark || ''
evalVisible.value = true
// ========== 执行单位评分 (与 manager/Projects.vue 完全相同的样式和逻辑) ==========
const singleScoreModalOpen = ref(false)
const singleScoreTargetRow = ref(null)
const singleScoreForm = reactive({ qualityScore: 0, responseScore: 0, cooperationScore: 0, complianceScore: 0 })
async function openSingleScore(row) {
singleScoreTargetRow.value = row
singleScoreForm.qualityScore = 0
singleScoreForm.responseScore = 0
singleScoreForm.cooperationScore = 0
singleScoreForm.complianceScore = 0
singleScoreModalOpen.value = true
// 回显当前 sponsor 在该项目上的历史评分
const uid = userStore.user?.userId
if (!uid) return
try {
const r = await request({ url: '/business/project/ratings', method: 'get', params: { projectId: row.projectId } })
const list = (r.data && (Array.isArray(r.data) ? r.data : r.data.rows)) || r.rows || []
const mine = list.find(x =>
String(x.projectId) === String(row.projectId) &&
String(x.raterId) === String(uid) &&
x.raterRole === 'sponsor'
)
if (mine) {
singleScoreForm.qualityScore = Number(mine.qualityScore) || 0
singleScoreForm.responseScore = Number(mine.responseScore) || 0
singleScoreForm.cooperationScore = Number(mine.cooperationScore) || 0
singleScoreForm.complianceScore = Number(mine.complianceScore) || 0
}
} catch (e) { /* 拉取失败不影响打开, 维持默认 0 */ }
}
async function saveEval() {
const avg = ((evalForm.q1 + evalForm.q2 + evalForm.q3 + evalForm.q4) / 4).toFixed(2)
await rateProject({
projectId: evalForm.projectId,
sponsorRating: avg,
sponsorQ1: evalForm.q1,
sponsorQ2: evalForm.q2,
sponsorQ3: evalForm.q3,
sponsorQ4: evalForm.q4,
sponsorRemark: evalForm.remark
})
ElMessage.success('评价已保存')
evalVisible.value = false
load()
}
function onEvalBatch() {
if (!selected.value.length) return ElMessage.warning('请先选择项目')
if (selected.value.length > 1) return ElMessage.warning('批量评价一次仅可评价一个项目')
openEval(selected.value[0])
async function confirmSingleScore() {
if (!singleScoreTargetRow.value) return
const f = singleScoreForm
if (!f.qualityScore || f.qualityScore < 1 || f.qualityScore > 5) return ElMessage.error('履约质量必须是 1~5 的数字')
if (!f.responseScore || f.responseScore < 1 || f.responseScore > 5) return ElMessage.error('时效响应必须是 1~5 的数字')
if (!f.cooperationScore || f.cooperationScore < 1 || f.cooperationScore > 5) return ElMessage.error('配合度必须是 1~5 的数字')
if (!f.complianceScore || f.complianceScore < 1 || f.complianceScore > 5) return ElMessage.error('合规安全必须是 1~5 的数字')
const avg = ((f.qualityScore + f.responseScore + f.cooperationScore + f.complianceScore) / 4).toFixed(1)
const projectId = singleScoreTargetRow.value.projectId
try {
// 1. 写聚合分到 biz_project.sponsor_score
await bizUpdate('project', { projectId, sponsorScore: Number(avg) })
// 2. 写 4 维度明细到 biz_project_rating (rater_role='sponsor', 后端自动填 raterId)
await rateProject({
projectId,
raterRole: 'sponsor',
qualityScore: f.qualityScore,
responseScore: f.responseScore,
cooperationScore: f.cooperationScore,
complianceScore: f.complianceScore
})
ElMessage.success(`评分完成,平均分:${avg}`)
singleScoreModalOpen.value = false
load()
} catch (e) {
ElMessage.error(e?.msg || '评分失败')
}
}
// ========== Modal 2: 项目分配 (单选 / 批量) ==========