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:
@@ -30,7 +30,7 @@
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<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="项目评价"><a class="op-link" href="javascript:void(0)" @click="openScoreDialog">{{ fmtScore(row.ratingScore) }}</a></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="项目评价"><a class="op-link" href="javascript:void(0)" @click="openScoreDialog">{{ fmtScore(row.managerScore) }}</a></el-form-item></el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
@@ -360,7 +360,7 @@ async function submitScore() {
|
||||
scoreSpeed: scoreForm.scoreSpeed,
|
||||
scoreCoordination: scoreForm.scoreCoordination,
|
||||
scoreCompliance: scoreForm.scoreCompliance,
|
||||
ratingScore: totalScore.value
|
||||
managerScore: totalScore.value
|
||||
})
|
||||
ElMessage.success('评分已保存')
|
||||
scoreOpen.value = false
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结算:">
|
||||
<el-select v-model="q.isSettled" placeholder="请选择" clearable style="width:120px">
|
||||
<el-option label="已结算" value="1" /><el-option label="未结算" value="0" />
|
||||
<el-option label="已结算" value="Y" /><el-option label="未结算" value="N" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="评价得分:"><el-input v-model="q.ratingScore" placeholder="输入评分" clearable /></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>
|
||||
|
||||
@@ -58,9 +58,15 @@
|
||||
<el-table-column prop="availableAmount" label="可用金额" width="130" align="right" :formatter="fmtMoney" />
|
||||
<el-table-column prop="paidLaborAmount" label="已支付劳务费" width="140" align="right" :formatter="fmtMoney" />
|
||||
<el-table-column prop="paidMeetingAmount" label="已支付会务费" width="140" align="right" :formatter="fmtMoney" />
|
||||
<el-table-column prop="complianceScore" label="执行单位得分(合规)" width="140" align="center">
|
||||
<el-table-column prop="managerScore" label="执行单位得分(合规)" width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.ratingScore != null">{{ row.ratingScore }}</span>
|
||||
<span v-if="row.managerScore != null">{{ row.managerScore }}</span>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sponsorScore" label="执行单位得分(支持)" width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.sponsorScore != null">{{ row.sponsorScore }}</span>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -199,10 +205,10 @@
|
||||
<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="batchScoreForm.q1" :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="batchScoreForm.q2" :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="batchScoreForm.q3" :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="batchScoreForm.q4" :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="batchScoreForm.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="batchScoreForm.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="batchScoreForm.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="batchScoreForm.complianceScore" :min="0" :max="5" :step="1" controls-position="right" placeholder="1~5" /></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
@@ -255,10 +261,10 @@
|
||||
<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.q1" :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.q2" :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.q3" :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.q4" :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.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>
|
||||
@@ -277,10 +283,12 @@ import request from '@/utils/request'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowDown } from '@element-plus/icons-vue'
|
||||
import Preview from '@/components/Preview.vue'
|
||||
import { useUserStore } from '@/store/user'
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const q = ref({ sponsorOrgName: '', execOrgName: '', projectNo: '', projectName: '', projectForm: '', startTime: '', endTime: '', isFinished: '', isSettled: '', ratingScore: '' })
|
||||
const q = ref({ sponsorOrgName: '', execOrgName: '', projectNo: '', projectName: '', projectForm: '', startTime: '', endTime: '', isFinished: '', isSettled: '', managerScore: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
@@ -333,7 +341,7 @@ async function doDelete(row) {
|
||||
}
|
||||
}
|
||||
|
||||
function reset() { q.value = { sponsorOrgName:'', execOrgName:'', projectNo:'', projectName:'', projectForm:'', startTime:'', endTime:'', isFinished:'', isSettled:'', ratingScore:'' }; page.pageNum = 1; load() }
|
||||
function reset() { q.value = { sponsorOrgName:'', execOrgName:'', projectNo:'', projectName:'', projectForm:'', startTime:'', endTime:'', isFinished:'', isSettled:'', managerScore:'' }; page.pageNum = 1; load() }
|
||||
|
||||
// 导出
|
||||
function exportProjects() { ElMessage.info('导出项目功能开发中') }
|
||||
@@ -413,42 +421,59 @@ function confirmDeleteAnn() {
|
||||
.catch(() => ElMessage.error('删除失败'))
|
||||
}
|
||||
|
||||
function openSingleScore(row) {
|
||||
async function openSingleScore(row) {
|
||||
singleScoreTargetRow.value = row
|
||||
// 反显各维度原值 (从后端 ratingQ1~Q4 字段); 无值才归零
|
||||
singleScoreForm.q1 = row.ratingQ1 != null ? Number(row.ratingQ1) : 0
|
||||
singleScoreForm.q2 = row.ratingQ2 != null ? Number(row.ratingQ2) : 0
|
||||
singleScoreForm.q3 = row.ratingQ3 != null ? Number(row.ratingQ3) : 0
|
||||
singleScoreForm.q4 = row.ratingQ4 != null ? Number(row.ratingQ4) : 0
|
||||
singleScoreForm.qualityScore = 0
|
||||
singleScoreForm.responseScore = 0
|
||||
singleScoreForm.cooperationScore = 0
|
||||
singleScoreForm.complianceScore = 0
|
||||
singleScoreModalOpen.value = true
|
||||
}
|
||||
function confirmSingleScore() {
|
||||
if (!singleScoreTargetRow.value) return
|
||||
const dims = [singleScoreForm.q1, singleScoreForm.q2, singleScoreForm.q3, singleScoreForm.q4]
|
||||
const labels = ['履约质量', '时效响应', '配合度', '合规安全']
|
||||
// 4 个维度全部必填
|
||||
for (let i = 0; i < dims.length; i++) {
|
||||
if (dims[i] === 0 || dims[i] === '' || dims[i] == null) {
|
||||
return ElMessage.error(`请填写「${labels[i]}」评分`)
|
||||
// 反显当前登录管理员的历史明细
|
||||
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 === 'manager'
|
||||
)
|
||||
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
|
||||
}
|
||||
const v = Number(dims[i])
|
||||
if (isNaN(v) || v < 1 || v > 5) return ElMessage.error(`${labels[i]}必须是 1~5 的数字`)
|
||||
} catch (e) { /* 拉取失败不影响打开, 维持默认 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.manager_score
|
||||
await bizUpdate('project', { projectId, managerScore: Number(avg) })
|
||||
// 2. 写 4 维度明细到 biz_project_rating (rater_role='manager', 后端自动填 raterId)
|
||||
await request.post('/business/project/rate', {
|
||||
projectId,
|
||||
raterRole: 'manager',
|
||||
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 || '评分失败')
|
||||
}
|
||||
// 校验通过, 计算平均分
|
||||
const scores = dims.map(v => Number(v))
|
||||
const avg = (scores.reduce((s, v) => s + v, 0) / scores.length).toFixed(1)
|
||||
// 构造 payload: 平均分 + 4 个维度分
|
||||
const payload = {
|
||||
projectId: singleScoreTargetRow.value.projectId,
|
||||
ratingScore: Number(avg),
|
||||
ratingQ1: scores[0],
|
||||
ratingQ2: scores[1],
|
||||
ratingQ3: scores[2],
|
||||
ratingQ4: scores[3]
|
||||
}
|
||||
bizUpdate('project', payload)
|
||||
.then(() => { ElMessage.success(`评分完成,平均分:${avg}`); singleScoreModalOpen.value = false; load() })
|
||||
.catch(() => ElMessage.error('评分失败'))
|
||||
}
|
||||
function viewDetail(row) { router.push(`/manager/projects/detail/${row.projectId}`) }
|
||||
|
||||
@@ -474,7 +499,7 @@ const batchKind = ref('')
|
||||
const batchSubmitting = ref(false)
|
||||
const batchMap = { score: '批量评分', close: '批量结题', activate: '批量开通' }
|
||||
const batchScoreRows = ref([]) // 保留兼容旧引用 (实际不再使用)
|
||||
const batchScoreForm = reactive({ q1: 5, q2: 5, q3: 5, q4: 5 }) // 批量评分: 共享一份 4 维度
|
||||
const batchScoreForm = reactive({ qualityScore: 5, responseScore: 5, cooperationScore: 5, complianceScore: 5 }) // 批量评分: 共享一份 4 维度
|
||||
const activateDeadline = ref('')
|
||||
|
||||
// 5 个单行 modal 状态
|
||||
@@ -486,7 +511,7 @@ const delAnnModalOpen = ref(false)
|
||||
const delAnnTargetRow = ref(null)
|
||||
const singleScoreModalOpen = ref(false)
|
||||
const singleScoreTargetRow = ref(null)
|
||||
const singleScoreForm = reactive({ q1: 0, q2: 0, q3: 0, q4: 0 })
|
||||
const singleScoreForm = reactive({ qualityScore: 0, responseScore: 0, cooperationScore: 0, complianceScore: 0 })
|
||||
|
||||
// 新建项目已迁到独立页面 /manager/projects/new (ManagerProjectsNew.vue)
|
||||
function openNewProject() {
|
||||
@@ -518,7 +543,7 @@ function openBatch(kind) {
|
||||
batchKind.value = kind
|
||||
if (kind === 'score') {
|
||||
// 共享评分: 重置 form 为默认值 (5 分)
|
||||
Object.assign(batchScoreForm, { q1: 5, q2: 5, q3: 5, q4: 5 })
|
||||
Object.assign(batchScoreForm, { qualityScore: 5, responseScore: 5, cooperationScore: 5, complianceScore: 5 })
|
||||
}
|
||||
if (kind === 'activate') {
|
||||
activateDeadline.value = ''
|
||||
@@ -554,24 +579,29 @@ async function confirmBatch() {
|
||||
} catch { ElMessage.error('批量开通失败') }
|
||||
finally { batchSubmitting.value = false }
|
||||
} else if (batchKind.value === 'score') {
|
||||
const q1 = Number(batchScoreForm.q1 || 0)
|
||||
const q2 = Number(batchScoreForm.q2 || 0)
|
||||
const q3 = Number(batchScoreForm.q3 || 0)
|
||||
const q4 = Number(batchScoreForm.q4 || 0)
|
||||
const total = ((q1 + q2 + q3 + q4) / 4).toFixed(1)
|
||||
const f = batchScoreForm
|
||||
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 total = ((f.qualityScore + f.responseScore + f.cooperationScore + f.complianceScore) / 4).toFixed(1)
|
||||
try {
|
||||
batchSubmitting.value = true
|
||||
let ok = 0
|
||||
// 共享评分: 所有项目用同一组 q1..q4
|
||||
// 共享评分: 所有项目用同一组 4 维度
|
||||
for (const r of selection.value) {
|
||||
try {
|
||||
await bizUpdate('project', {
|
||||
projectId: r.projectId,
|
||||
ratingScore: Number(total),
|
||||
ratingQ1: q1,
|
||||
ratingQ2: q2,
|
||||
ratingQ3: q3,
|
||||
ratingQ4: q4
|
||||
managerScore: Number(total)
|
||||
})
|
||||
await request.post('/business/project/rate', {
|
||||
projectId: r.projectId,
|
||||
raterRole: 'manager',
|
||||
qualityScore: f.qualityScore,
|
||||
responseScore: f.responseScore,
|
||||
cooperationScore: f.cooperationScore,
|
||||
complianceScore: f.complianceScore
|
||||
})
|
||||
ok++
|
||||
} catch {}
|
||||
|
||||
@@ -70,7 +70,7 @@ async function loadStats() {
|
||||
stats.value.executedMeetings = em.total || em.data?.total || 0
|
||||
stats.value.pendingMeetings = meetingTotal - stats.value.executedMeetings
|
||||
// 已结算项目
|
||||
const sp = await bizList('project', { pageNum: 1, pageSize: 1, isSettled: '1' })
|
||||
const sp = await bizList('project', { pageNum: 1, pageSize: 1, isSettled: 'Y' })
|
||||
stats.value.settledProjects = sp.total || sp.data?.total || 0
|
||||
} catch (e) {
|
||||
console.warn('loadStats failed', e)
|
||||
|
||||
Reference in New Issue
Block a user