Merge branch 'master' of gitee.com:sz_30/supervision-vue into wsq-20260727

Signed-off-by: ye1023 <12588209+ye1023@user.noreply.gitee.com>
This commit is contained in:
ye1023
2026-07-30 07:26:40 +00:00
committed by Gitee
4 changed files with 315 additions and 50 deletions
@@ -86,6 +86,12 @@ export const columns: BasicColumn[] = [
dataIndex: 'bpmStatus_dictText', dataIndex: 'bpmStatus_dictText',
width:90 width:90
dataIndex: 'finalAward_dictText'
},
{
title: '流程状态',
align:"center",
dataIndex: 'bpmStatus_dictText'
}, },
]; ];
@@ -325,7 +331,7 @@ export const superQuerySchema = {
problemDesc: {title: '问题描述',order: 9,view: 'textarea', type: 'string',}, problemDesc: {title: '问题描述',order: 9,view: 'textarea', type: 'string',},
improvementMethod: {title: '改善方法及措施',order: 10,view: 'textarea', type: 'string',}, improvementMethod: {title: '改善方法及措施',order: 10,view: 'textarea', type: 'string',},
finalAward: {title: '最终奖项',order: 14,view: 'text', type: 'string',}, finalAward: {title: '最终奖项',order: 14,view: 'text', type: 'string',},
bpmStatus: {title: '业务展示状态',order: 15,view: 'text', type: 'string',}, bpmStatus: {title: '流程状态',order: 15,view: 'text', type: 'string',},
//子表高级查询 //子表高级查询
improvementDeptScore: { improvementDeptScore: {
title: '部门评议评分表', title: '部门评议评分表',
@@ -20,6 +20,8 @@
<a-form-item name="bpmStatus"> <a-form-item name="bpmStatus">
<template #label><span title="业务状态">业务状态</span></template> <template #label><span title="业务状态">业务状态</span></template>
<JDictSelectTag v-model:value="queryParam.bpmStatus" dictCode="super_status" placeholder="请选择业务状态" allow-clear /> <JDictSelectTag v-model:value="queryParam.bpmStatus" dictCode="super_status" placeholder="请选择业务状态" allow-clear />
<template #label><span title="流程状态">流程状态</span></template>
<JDictSelectTag v-model:value="queryParam.bpmStatus" dictCode="super_status" placeholder="请选择流程状态" allow-clear />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col v-if="toggleSearchStatus" :lg="6"> <a-col v-if="toggleSearchStatus" :lg="6">
@@ -14,7 +14,7 @@
<a-descriptions-item label="提案编号">{{ proposal.proposalNo || '-' }}</a-descriptions-item> <a-descriptions-item label="提案编号">{{ proposal.proposalNo || '-' }}</a-descriptions-item>
<a-descriptions-item label="提案名称">{{ proposal.proposalName || '-' }}</a-descriptions-item> <a-descriptions-item label="提案名称">{{ proposal.proposalName || '-' }}</a-descriptions-item>
<a-descriptions-item label="提出日期">{{ formatDate(proposal.proposalDate) }}</a-descriptions-item> <a-descriptions-item label="提出日期">{{ formatDate(proposal.proposalDate) }}</a-descriptions-item>
<a-descriptions-item label="提案类别">{{ proposal.proposalCategory || '-' }}</a-descriptions-item> <a-descriptions-item label="提案类别">{{ proposal.proposalCategory_dictText || proposal.proposalCategory || '-' }}</a-descriptions-item>
<a-descriptions-item label="提案部门">{{ proposal.proposalDeptName || '-' }}</a-descriptions-item> <a-descriptions-item label="提案部门">{{ proposal.proposalDeptName || '-' }}</a-descriptions-item>
<a-descriptions-item label="申请人">{{ proposal.applicantName || '-' }}</a-descriptions-item> <a-descriptions-item label="申请人">{{ proposal.applicantName || '-' }}</a-descriptions-item>
<a-descriptions-item label="团队名称" :span="2">{{ proposal.teamName || '-' }}</a-descriptions-item> <a-descriptions-item label="团队名称" :span="2">{{ proposal.teamName || '-' }}</a-descriptions-item>
@@ -152,49 +152,101 @@
/> />
</template> </template>
<a-modal v-model:open="scoreModalVisible" title="部门评议打分" :footer="null" :mask-closable="false" :body-style="{ padding: '24px 28px 20px' }"> <a-modal
v-model:open="scoreModalVisible"
title="部门评议打分"
:footer="null"
:mask-closable="false"
:width="820"
:style="{ maxWidth: 'calc(100vw - 24px)' }"
:body-style="{ padding: '24px 28px 20px' }"
>
<a-form layout="vertical"> <a-form layout="vertical">
<a-alert class="score-range-alert" type="info" show-icon message="所有评分项均须填写 0-100 分,最多可保留两位小数。" /> <a-alert class="score-range-alert" type="info" show-icon message="所有评分项均须填写 0-100 分,最多可保留两位小数。" />
<a-row :gutter="12"> <div class="score-standard-entry">
<a-col :span="24"> <span>评分标准附件</span>
<a-form-item label="改善效果(权重 50%" required> <a-button type="link" size="small" class="score-standard-button" @click="openScoreStandard">
<a-row :gutter="12"> <Icon icon="ant-design:paper-clip-outlined" />
<a-col :xs="24" :sm="12" :lg="6"> 查看评分标准
<a-form-item label="安全" required><a-input-number v-model:value="scoreForm.safetyScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /></a-form-item> </a-button>
</a-col> </div>
<a-col :xs="24" :sm="12" :lg="6"> <!-- 四个大类维度仅调整展示层级字段绑定和加权计算规则保持不变 -->
<a-form-item label="质量" required><a-input-number v-model:value="scoreForm.qualityScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /></a-form-item> <div class="score-dimension-list">
</a-col> <section class="score-dimension score-effect-dimension">
<a-col :xs="24" :sm="12" :lg="6"> <div class="score-dimension-header">
<a-form-item label="效率" required><a-input-number v-model:value="scoreForm.efficiencyScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /></a-form-item> <div class="score-dimension-name">
</a-col> <span class="score-dimension-index">01</span>
<a-col :xs="24" :sm="12" :lg="6"> <strong class="score-dimension-title">改善效果</strong>
<a-form-item label="成本" required><a-input-number v-model:value="scoreForm.costScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /></a-form-item> </div>
</a-col> <span class="score-dimension-weight">权重 50%</span>
</a-row> </div>
<div class="form-help">改善效果得分按安全质量效率成本四项的平均分自动计算{{ effectAveragePreview }}</div> <a-row :gutter="[12, 12]">
</a-form-item> <a-col :xs="24" :sm="12" :lg="6">
</a-col> <a-form-item class="score-subitem" label="安全" required><a-input-number v-model:value="scoreForm.safetyScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /></a-form-item>
<a-col :xs="24" :sm="12"> </a-col>
<a-form-item label="可推广度(权重 20%" required> <a-col :xs="24" :sm="12" :lg="6">
<a-input-number v-model:value="scoreForm.promotionScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /> <a-form-item class="score-subitem" label="质量" required><a-input-number v-model:value="scoreForm.qualityScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /></a-form-item>
</a-form-item> </a-col>
</a-col> <a-col :xs="24" :sm="12" :lg="6">
<a-col :xs="24" :sm="12"> <a-form-item class="score-subitem" label="效率" required><a-input-number v-model:value="scoreForm.efficiencyScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /></a-form-item>
<a-form-item label="独创性(权重 20%" required> </a-col>
<a-input-number v-model:value="scoreForm.originalityScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /> <a-col :xs="24" :sm="12" :lg="6">
</a-form-item> <a-form-item class="score-subitem" label="成本" required><a-input-number v-model:value="scoreForm.costScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /></a-form-item>
</a-col> </a-col>
<a-col :xs="24" :sm="12"> </a-row>
<a-form-item label="努力度(权重 10%" required> <div class="effect-score-summary">
<a-input-number v-model:value="scoreForm.effortScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" /> <span>安全质量效率成本四项平均分</span>
</a-form-item> <strong>{{ effectAveragePreview }}</strong>
</a-col> </div>
</a-row> </section>
<a-space>
<a-row class="score-dimension-grid" :gutter="[12, 12]">
<a-col :xs="24" :md="8">
<section class="score-dimension">
<div class="score-dimension-header">
<div class="score-dimension-name">
<span class="score-dimension-index">02</span>
<strong class="score-dimension-title">可推广度</strong>
</div>
<span class="score-dimension-weight">权重 20%</span>
</div>
<a-input-number v-model:value="scoreForm.promotionScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" />
</section>
</a-col>
<a-col :xs="24" :md="8">
<section class="score-dimension">
<div class="score-dimension-header">
<div class="score-dimension-name">
<span class="score-dimension-index">03</span>
<strong class="score-dimension-title">独创性</strong>
</div>
<span class="score-dimension-weight">权重 20%</span>
</div>
<a-input-number v-model:value="scoreForm.originalityScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" />
</section>
</a-col>
<a-col :xs="24" :md="8">
<section class="score-dimension">
<div class="score-dimension-header">
<div class="score-dimension-name">
<span class="score-dimension-index">04</span>
<strong class="score-dimension-title">努力度</strong>
</div>
<span class="score-dimension-weight">权重 10%</span>
</div>
<a-input-number v-model:value="scoreForm.effortScore" :min="0" :max="100" :precision="2" :disabled="scoreLocked" style="width: 100%" />
</section>
</a-col>
</a-row>
</div>
<div class="weighted-total-summary">
<span>加权总分</span>
<strong>{{ weightedTotalPreview }}</strong>
</div>
<div class="score-form-actions">
<a-button v-if="!scoreLocked" type="primary" :loading="savingScore" @click="saveScoreDraft">保存评分</a-button> <a-button v-if="!scoreLocked" type="primary" :loading="savingScore" @click="saveScoreDraft">保存评分</a-button>
<span v-else>评分已锁定请重新提交流程办理</span> <span v-else>评分已锁定请重新提交流程办理</span>
</a-space> </div>
</a-form> </a-form>
</a-modal> </a-modal>
</section> </section>
@@ -255,6 +307,8 @@
{ value: '2', label: '铜奖' }, { value: '2', label: '铜奖' },
{ value: '3', label: '参与奖' }, { value: '3', label: '参与奖' },
]; ];
// 评分标准由 MinIO 提供,使用完整地址避免受前端部署路径影响。
const scoreStandardUrl = 'http://localhost:9000/api/v1/buckets/supervision/objects/download?prefix=%E6%94%B9%E5%96%84%E6%8F%90%E6%A1%88%E8%AF%84%E5%88%86%E6%A0%87%E5%87%86(1).xlsx&version_id=null';
// 流程引擎在运行待办中提供 taskDefKey,节点功能只依赖该稳定标识而非表单 URL 参数。 // 流程引擎在运行待办中提供 taskDefKey,节点功能只依赖该稳定标识而非表单 URL 参数。
const processInfo = computed(() => ({ const processInfo = computed(() => ({
mainId: props.formData?.processDataId || props.formData?.dataId, mainId: props.formData?.processDataId || props.formData?.dataId,
@@ -275,10 +329,18 @@
if (!currentScore.value) return '未保存'; if (!currentScore.value) return '未保存';
return scoreLocked.value ? '评分已锁定' : '已保存,待流程提交'; return scoreLocked.value ? '评分已锁定' : '已保存,待流程提交';
}); });
const effectAveragePreview = computed(() => { // 以前端“分”的整数计算,匹配服务端 BigDecimal 在改善效果和总分阶段的两次 HALF_UP 取整。
const values = ['safetyScore', 'qualityScore', 'efficiencyScore', 'costScore'].map((key) => scoreForm.value[key]); const effectScoreCents = computed(() => calculateEffectScoreCents());
if (values.some((value) => value === undefined || value === null)) return '-'; const effectAveragePreview = computed(() => formatScoreCents(effectScoreCents.value));
return (values.reduce((sum, value) => sum + Number(value), 0) / values.length).toFixed(2); const weightedTotalPreview = computed(() => {
const effectCents = effectScoreCents.value;
const promotionCents = getScoreCents(scoreForm.value.promotionScore);
const originalityCents = getScoreCents(scoreForm.value.originalityScore);
const effortCents = getScoreCents(scoreForm.value.effortScore);
if (effectCents === null || promotionCents === null || originalityCents === null || effortCents === null) return '-';
const totalCents = Math.round(effectCents * 0.5 + promotionCents * 0.2 + originalityCents * 0.2 + effortCents * 0.1);
return formatScoreCents(totalCents);
}); });
// 部门评分完成后必须以系统建议奖项为基准,不能回退到初审奖项。 // 部门评分完成后必须以系统建议奖项为基准,不能回退到初审奖项。
const leaderAwardBase = computed(() => { const leaderAwardBase = computed(() => {
@@ -448,6 +510,10 @@
scoreModalVisible.value = true; scoreModalVisible.value = true;
} }
function openScoreStandard() {
window.open(scoreStandardUrl, '_blank', 'noopener,noreferrer');
}
async function saveScoreDraft() { async function saveScoreDraft() {
if (!hasValidScoreValues()) { if (!hasValidScoreValues()) {
createMessage.warning('请完整填写 0 到 100 分之间的七项评分'); createMessage.warning('请完整填写 0 到 100 分之间的七项评分');
@@ -478,6 +544,22 @@
}; };
} }
function calculateEffectScoreCents(): number | null {
const values = ['safetyScore', 'qualityScore', 'efficiencyScore', 'costScore'].map((key) => getScoreCents(scoreForm.value[key]));
if (values.some((value) => value === null)) return null;
return Math.round((values as number[]).reduce((sum, value) => sum + value, 0) / values.length);
}
function getScoreCents(value: unknown): number | null {
if (value === undefined || value === null || value === '') return null;
const score = Number(value);
return Number.isFinite(score) ? Math.round(score * 100) : null;
}
function formatScoreCents(scoreCents: number | null): string {
return scoreCents === null ? '-' : (scoreCents / 100).toFixed(2);
}
function hasValidScoreValues() { function hasValidScoreValues() {
return ['safetyScore', 'qualityScore', 'efficiencyScore', 'costScore', 'promotionScore', 'originalityScore', 'effortScore'].every((key) => { return ['safetyScore', 'qualityScore', 'efficiencyScore', 'costScore', 'promotionScore', 'originalityScore', 'effortScore'].every((key) => {
const value = scoreForm.value[key]; const value = scoreForm.value[key];
@@ -594,6 +676,145 @@
margin-bottom: 16px; margin-bottom: 16px;
} }
.score-standard-entry {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 2px;
margin: -8px 0 16px;
color: #64748b;
font-size: 14px;
}
.score-standard-button {
display: inline-flex;
align-items: center;
gap: 4px;
padding-inline: 2px;
}
.score-dimension-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.score-dimension {
min-height: 126px;
padding: 14px;
border-left: 3px solid #1677ff;
background: #f6fbff;
}
.score-dimension-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
}
.score-dimension-name {
display: inline-flex;
align-items: center;
min-width: 0;
gap: 8px;
}
.score-dimension-index {
flex: 0 0 auto;
color: #1677ff;
font-size: 14px;
font-weight: 700;
line-height: 1;
}
.score-dimension-title {
color: #1f2937;
font-size: 17px;
font-weight: 700;
line-height: 1.4;
}
.score-dimension-weight {
flex: 0 0 auto;
color: #1677ff;
font-size: 14px;
font-weight: 600;
white-space: nowrap;
}
.score-effect-dimension {
min-height: auto;
}
.score-subitem {
margin-bottom: 0;
:deep(.ant-form-item-label > label) {
color: #334155;
font-size: 15px;
font-weight: 600;
}
}
.score-dimension {
:deep(.ant-input-number-input) {
color: #1f2937;
font-size: 16px;
font-weight: 600;
}
}
.effect-score-summary {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
margin-top: 14px;
padding-top: 10px;
border-top: 1px solid #d6e8ff;
color: #475569;
font-size: 14px;
strong {
flex: 0 0 auto;
color: #1677ff;
font-size: 20px;
font-weight: 700;
}
}
.weighted-total-summary {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
margin-top: 16px;
padding: 14px 16px;
border-left: 4px solid #1677ff;
background: #e6f4ff;
color: #1f2937;
font-size: 16px;
font-weight: 700;
strong {
flex: 0 0 auto;
color: #0958d9;
font-size: 28px;
font-weight: 700;
line-height: 1;
}
}
.score-form-actions {
display: flex;
align-items: center;
justify-content: flex-end;
min-height: 32px;
margin-top: 20px;
}
.institute-vote-summary { .institute-vote-summary {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -702,6 +923,37 @@
} }
@media (max-width: 576px) { @media (max-width: 576px) {
.score-dimension {
min-height: auto;
padding: 12px;
}
.score-dimension-header {
margin-bottom: 12px;
}
.effect-score-summary {
align-items: flex-start;
flex-direction: column;
gap: 2px;
}
.weighted-total-summary {
padding: 12px;
strong {
font-size: 24px;
}
}
.score-form-actions {
justify-content: stretch;
:deep(.ant-btn) {
width: 100%;
}
}
.base-info-header { .base-info-header {
align-items: flex-start; align-items: flex-start;
} }
@@ -134,6 +134,7 @@
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue'; import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import SUploadFile from '/@/components/semri/fileComponent/SUploadFile.vue'; import SUploadFile from '/@/components/semri/fileComponent/SUploadFile.vue';
import { useUserStore } from '/@/store/modules/user'; import { useUserStore } from '/@/store/modules/user';
import { useMessage } from '/@/hooks/web/useMessage';
const useForm = Form.useForm; const useForm = Form.useForm;
export default defineComponent({ export default defineComponent({
@@ -154,6 +155,7 @@
}, },
emits:['success'], emits:['success'],
setup(props, {emit}) { setup(props, {emit}) {
const { createMessage } = useMessage();
const loading = ref(false); const loading = ref(false);
const formRef = ref(); const formRef = ref();
const beforeFileRef = ref(); const beforeFileRef = ref();
@@ -417,12 +419,15 @@
const result = await saveOrUpdate(values, isUpdate); const result = await saveOrUpdate(values, isUpdate);
// 新增阶段附件暂不绑定;主表生成 ID 后统一关联,保证普通表单和 BPM 使用同一查询键。 // 新增阶段附件暂不绑定;主表生成 ID 后统一关联,保证普通表单和 BPM 使用同一查询键。
if (!isUpdate) { if (!isUpdate) {
const newId = typeof result === 'string' ? result : (result?.id || result); const newId = typeof result?.id === 'string' ? result.id.trim() : '';
if (newId) { if (!newId) {
formData.id = newId; // 阻断成功文案等非业务 ID 写入附件表,避免后续 BPM 无法按主表 ID 查询附件。
await beforeFileRef.value?.bindBussinessId(`${formData.id}:before`); createMessage.error('新增成功但未返回提案ID,附件未关联,请刷新后重试');
await afterFileRef.value?.bindBussinessId(`${formData.id}:after`); return;
} }
formData.id = newId;
await beforeFileRef.value?.bindBussinessId(`${formData.id}:before`);
await afterFileRef.value?.bindBussinessId(`${formData.id}:after`);
} }
emit('success'); emit('success');
} }