diff --git a/src/views/bg/bgpartymatter/BgPartymatterList.vue b/src/views/bg/bgpartymatter/BgPartymatterList.vue index 22e4487..2feb03f 100644 --- a/src/views/bg/bgpartymatter/BgPartymatterList.vue +++ b/src/views/bg/bgpartymatter/BgPartymatterList.vue @@ -4,27 +4,27 @@
- + - + - + - + - + - + 查询 重置 @@ -707,7 +707,7 @@ xs:24, sm:4, xl:6, - xxl:4 + xxl:8 }); const wrapperCol = reactive({ xs: 24, diff --git a/src/views/bg/bqtakepulse/BgTakepulseList.vue b/src/views/bg/bqtakepulse/BgTakepulseList.vue index 7ef4dc6..a0ce3a4 100644 --- a/src/views/bg/bqtakepulse/BgTakepulseList.vue +++ b/src/views/bg/bqtakepulse/BgTakepulseList.vue @@ -4,27 +4,27 @@
- + - + - + - + - + 查询 重置 @@ -631,7 +631,7 @@ xs:24, sm:4, xl:6, - xxl:4 + xxl:8 }); const wrapperCol = reactive({ xs: 24, diff --git a/src/views/bg/xispeak/BgXiSpeakList.vue b/src/views/bg/xispeak/BgXiSpeakList.vue index 1340f16..628fa93 100644 --- a/src/views/bg/xispeak/BgXiSpeakList.vue +++ b/src/views/bg/xispeak/BgXiSpeakList.vue @@ -4,17 +4,17 @@
- + - + - + @@ -24,7 +24,7 @@ - + 未开始 @@ -33,7 +33,7 @@ - + 查询 重置 @@ -229,13 +229,13 @@ import { PARTY_COMMITTEE_DEPT_ID, SDW_LEADER_ROLE_ID } from '/@/constant/supervi xs: 24, sm: 8, xl: 10, - xxl: 8, + xxl: 14, }); const wrapperCol = reactive({ xs: 24, sm: 16, xl: 14, - xxl: 16, + xxl: 10, }); // 高级查询配置 diff --git a/src/views/dq/inspectTask/DqInspectTaskList.vue b/src/views/dq/inspectTask/DqInspectTaskList.vue index c25b1fb..713adc6 100644 --- a/src/views/dq/inspectTask/DqInspectTaskList.vue +++ b/src/views/dq/inspectTask/DqInspectTaskList.vue @@ -4,22 +4,22 @@
- + - + - + - + 未开始 @@ -28,7 +28,7 @@ - + 查询 重置 @@ -574,7 +574,7 @@ xs: 24, sm: 4, xl: 6, - xxl: 4, + xxl: 9, }); const wrapperCol = reactive({ xs: 24, diff --git a/src/views/gh/improvementProposal/ImprovementProposal.api.ts b/src/views/gh/improvementProposal/ImprovementProposal.api.ts index b371f93..062ea3f 100644 --- a/src/views/gh/improvementProposal/ImprovementProposal.api.ts +++ b/src/views/gh/improvementProposal/ImprovementProposal.api.ts @@ -17,7 +17,13 @@ enum Api { getLatestProposalSequence = '/improvementproposal/improvementProposal/getLatestProposalSequence', startProcess = '/improvementproposal/improvementProposal/startProcess', bpmInitialReview = '/improvementproposal/improvementProposal/bpm/initialReview', + bpmMyDeptScore = '/improvementproposal/improvementProposal/bpm/myDeptScore', + bpmDeptScore = '/improvementproposal/improvementProposal/bpm/deptScore', + bpmSubmitDeptScore = '/improvementproposal/improvementProposal/bpm/submitDeptScore', bpmLeaderAward = '/improvementproposal/improvementProposal/bpm/leaderAward', + bpmInstituteVoteCandidates = '/improvementproposal/improvementProposal/bpm/instituteVoteCandidates', + bpmInstituteVoteMembers = '/improvementproposal/improvementProposal/bpm/instituteVoteMembers', + bpmSubmitInstituteVote = '/improvementproposal/improvementProposal/bpm/submitInstituteVote', bpmArchiveAward = '/improvementproposal/improvementProposal/bpm/archiveAward', bpmInstituteVote = '/improvementproposal/improvementProposal/bpm/instituteVote', } @@ -102,6 +108,12 @@ export const startProposalProcess = (id: string) => defHttp.post({ url: Api.star /** 流程节点专用接口会在任务提交前保存业务字段和网关变量。 */ export const saveInitialReview = (params) => defHttp.post({ url: Api.bpmInitialReview, params }); +export const queryMyDeptScore = (params) => defHttp.get({ url: Api.bpmMyDeptScore, params }); +export const saveDeptScore = (params) => defHttp.post({ url: Api.bpmDeptScore, params }); +export const submitDeptScore = (params) => defHttp.post({ url: Api.bpmSubmitDeptScore, params }); export const saveLeaderAward = (params) => defHttp.post({ url: Api.bpmLeaderAward, params }); +export const queryInstituteVoteCandidates = (params) => defHttp.get({ url: Api.bpmInstituteVoteCandidates, params }); +export const saveInstituteVoteMembers = (params) => defHttp.post({ url: Api.bpmInstituteVoteMembers, params }); export const saveArchiveAward = (params) => defHttp.post({ url: Api.bpmArchiveAward, params }); export const castInstituteVote = (params) => defHttp.post({ url: Api.bpmInstituteVote, params }); +export const submitInstituteVote = (params) => defHttp.post({ url: Api.bpmSubmitInstituteVote, params }); diff --git a/src/views/gh/improvementProposal/ImprovementProposal.data.ts b/src/views/gh/improvementProposal/ImprovementProposal.data.ts index 10f1848..e8a7ba8 100644 --- a/src/views/gh/improvementProposal/ImprovementProposal.data.ts +++ b/src/views/gh/improvementProposal/ImprovementProposal.data.ts @@ -131,6 +131,38 @@ export const improvementDeptScoreColumns: JVxeColumn[] = [ { required: true, message: '${title}不能为空' }, ], }, + { + title: '安全得分', + key: 'safetyScore', + type: JVxeTypes.inputNumber, + width:"200px", + placeholder: '请输入${title}', + defaultValue:'', + }, + { + title: '质量得分', + key: 'qualityScore', + type: JVxeTypes.inputNumber, + width:"200px", + placeholder: '请输入${title}', + defaultValue:'', + }, + { + title: '效率得分', + key: 'efficiencyScore', + type: JVxeTypes.inputNumber, + width:"200px", + placeholder: '请输入${title}', + defaultValue:'', + }, + { + title: '成本得分', + key: 'costScore', + type: JVxeTypes.inputNumber, + width:"200px", + placeholder: '请输入${title}', + defaultValue:'', + }, { title: '改善效果得分', key: 'effectScore', @@ -292,13 +324,17 @@ export const superQuerySchema = { scorerId: {title: '评委ID',order: 2,view: 'text', type: 'string',}, scorerName: {title: '评委姓名',order: 3,view: 'text', type: 'string',}, scoreStatus: {title: '评分状态:pending/completed',order: 4,view: 'text', type: 'string',}, - effectScore: {title: '改善效果得分',order: 5,view: 'number', type: 'number',}, - promotionScore: {title: '可推广度得分',order: 6,view: 'number', type: 'number',}, - originalityScore: {title: '独创性得分',order: 7,view: 'number', type: 'number',}, - effortScore: {title: '努力度得分',order: 8,view: 'number', type: 'number',}, - totalScore: {title: '加权总分',order: 9,view: 'number', type: 'number',}, - scoreOpinion: {title: '评分意见',order: 10,view: 'textarea', type: 'string',}, - scoreTime: {title: '评分时间',order: 11,view: 'datetime', type: 'string',}, + safetyScore: {title: '安全得分',order: 5,view: 'number', type: 'number',}, + qualityScore: {title: '质量得分',order: 6,view: 'number', type: 'number',}, + efficiencyScore: {title: '效率得分',order: 7,view: 'number', type: 'number',}, + costScore: {title: '成本得分',order: 8,view: 'number', type: 'number',}, + effectScore: {title: '改善效果平均得分',order: 9,view: 'number', type: 'number',}, + promotionScore: {title: '可推广度得分',order: 10,view: 'number', type: 'number',}, + originalityScore: {title: '独创性得分',order: 11,view: 'number', type: 'number',}, + effortScore: {title: '努力度得分',order: 12,view: 'number', type: 'number',}, + totalScore: {title: '加权总分',order: 13,view: 'number', type: 'number',}, + scoreOpinion: {title: '评分意见',order: 14,view: 'textarea', type: 'string',}, + scoreTime: {title: '评分时间',order: 15,view: 'datetime', type: 'string',}, } }, improvementInstituteVote: { diff --git a/src/views/gh/improvementProposal/components/ImprovementProposalBPMForm.vue b/src/views/gh/improvementProposal/components/ImprovementProposalBPMForm.vue index 47f06e6..d52df23 100644 --- a/src/views/gh/improvementProposal/components/ImprovementProposalBPMForm.vue +++ b/src/views/gh/improvementProposal/components/ImprovementProposalBPMForm.vue @@ -27,10 +27,6 @@
改善后附件
-
-
其他附件
- -
- @@ -44,50 +40,56 @@ - - {{ item.label }} - +
金奖、银奖进入部门评议小组打分;铜奖、参与奖直接进入部门负责人审核。
- - - {{ currentScore.scorerName || '-' }} - {{ currentScore.scoreStatus === 'completed' ? '已评分' : '待评分' }} - -
四个评分维度录入、加权计算和评分提交将在下一阶段开发。
+ + + + + + + + + + + + + + + + + + + + +
改善效果得分按安全、质量、效率、成本四项的平均分自动计算:{{ effectAveragePreview }}
+
+
+ + + + + + + + + + + + + + + +
+ + + + + 保存评分 + 评分已锁定,请重新提交流程办理 + +
@@ -131,16 +174,22 @@ import { computed, ref, watch } from 'vue'; import { useMessage } from '/@/hooks/web/useMessage'; import { useUserStore } from '/@/store/modules/user'; + import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue'; import SUploadFile from '/@/components/semri/fileComponent/SUploadFile.vue'; import TaskHandleInnerContent from '/@/views/super/bpm/process/personalOffice/myHandleTask/content/TaskHandleInnerContent.vue'; import { castInstituteVote, queryDataById, - queryImprovementDeptScoreListByMainId, + queryInstituteVoteCandidates, queryImprovementInstituteVoteListByMainId, + queryMyDeptScore, saveArchiveAward, + saveDeptScore, saveInitialReview, + saveInstituteVoteMembers, saveLeaderAward, + submitInstituteVote, + submitDeptScore, } from '../ImprovementProposal.api'; const props = defineProps({ @@ -152,14 +201,18 @@ const { createMessage } = useMessage(); const userStore = useUserStore(); const loading = ref(false); + const savingScore = ref(false); const savingVote = ref(false); const scoreModalVisible = ref(false); const proposal = ref({}); - const scoreRows = ref([]); + const currentScore = ref(null); const voteRows = ref([]); + const instituteVoteCandidates = ref([]); + const selectedInstituteVoterIds = ref([]); const initialAward = ref(''); const leaderFinalAward = ref(''); const finalAward = ref(''); + const scoreForm = ref(createEmptyScoreForm()); const labelCol = { xs: { span: 24 }, sm: { span: 5 } }; const wrapperCol = { xs: { span: 24 }, sm: { span: 19 } }; @@ -169,16 +222,6 @@ { value: '2', label: '铜奖' }, { value: '3', label: '参与奖' }, ]; - const scoreColumns = [ - { title: '评委', dataIndex: 'scorerName', width: 180 }, - { title: '状态', key: 'status', width: 120 }, - { title: '总分', dataIndex: 'totalScore', width: 120 }, - ]; - const voteMemberColumns = [ - { title: '评委', dataIndex: 'voterName', width: 180 }, - { title: '状态', dataIndex: 'voteStatus', width: 120 }, - ]; - // 流程引擎在运行待办中提供 taskDefKey,节点功能只依赖该稳定标识而非表单 URL 参数。 const processInfo = computed(() => ({ mainId: props.formData?.processDataId || props.formData?.dataId, @@ -194,11 +237,30 @@ })); const showProcessHandle = computed(() => props.formBpm && props.formData?.PROCESS_TAB_TYPE === 'run' && !!processInfo.value.taskId); const currentUserId = computed(() => String(userStore.getUserInfo?.id || '')); - const currentScore = computed(() => scoreRows.value.find((item) => String(item.scorerId) === currentUserId.value)); + const scoreLocked = computed(() => currentScore.value?.scoreStatus === 'completed'); + const scoreStatusLabel = computed(() => { + if (!currentScore.value) return '未保存'; + return scoreLocked.value ? '评分已锁定' : '已保存,待流程提交'; + }); + const effectAveragePreview = computed(() => { + const values = ['safetyScore', 'qualityScore', 'efficiencyScore', 'costScore'].map((key) => scoreForm.value[key]); + if (values.some((value) => value === undefined || value === null)) return '-'; + return (values.reduce((sum, value) => sum + Number(value), 0) / values.length).toFixed(2); + }); + // 部门评分完成后必须以系统建议奖项为基准,不能回退到初审奖项。 + const leaderAwardBase = computed(() => { + const award = proposal.value.needDeptScore === '1' ? proposal.value.suggestedAward : proposal.value.initialAward; + return awardOptions.some((item) => item.value === String(award)) ? String(award) : ''; + }); + const leaderAwardOptions = computed(() => { + if (!leaderAwardBase.value) return []; + return awardOptions.filter((item) => Number(item.value) >= Number(leaderAwardBase.value)); + }); const currentVote = computed(() => voteRows.value.find((item) => String(item.voterId) === currentUserId.value)); + const voteLocked = computed(() => currentVote.value?.voteStatus === 'voted'); watch( - () => [processInfo.value.mainId, processInfo.value.processInstanceId], + () => [processInfo.value.mainId, processInfo.value.processInstanceId, processInfo.value.taskId, processInfo.value.taskDefKey], () => loadProposal(), { immediate: true } ); @@ -207,23 +269,38 @@ const mainId = processInfo.value.mainId; if (!mainId) { proposal.value = {}; - scoreRows.value = []; + currentScore.value = null; voteRows.value = []; + instituteVoteCandidates.value = []; + selectedInstituteVoterIds.value = []; return; } loading.value = true; try { - const [main, scores, votes] = await Promise.all([ + const [main, votes, myScore] = await Promise.all([ queryDataById(mainId), - queryImprovementDeptScoreListByMainId(mainId), queryImprovementInstituteVoteListByMainId(mainId), + isTask('Task_improve002') && processInfo.value.processInstanceId && processInfo.value.taskId + ? queryMyDeptScore(processInfo.value) + : Promise.resolve(null), ]); proposal.value = main || {}; - scoreRows.value = scores || []; + currentScore.value = myScore || null; voteRows.value = votes || []; initialAward.value = proposal.value.initialAward || ''; leaderFinalAward.value = proposal.value.leaderFinalAward || ''; finalAward.value = proposal.value.finalAward || ''; + if (isTask('Task_improve004') && processInfo.value.processInstanceId && processInfo.value.taskId) { + instituteVoteCandidates.value = await queryInstituteVoteCandidates(processInfo.value); + const candidateIds = new Set(instituteVoteCandidates.value.map((item) => String(item.id))); + // 流程提交失败后重新打开 Task4 时,回显本次尚未流转的有效名单。 + selectedInstituteVoterIds.value = voteRows.value + .map((item) => String(item.voterId)) + .filter((voterId) => candidateIds.has(voterId)); + } else { + instituteVoteCandidates.value = []; + selectedInstituteVoterIds.value = []; + } } catch (error) { createMessage.warning('提案数据加载失败'); } finally { @@ -248,16 +325,38 @@ } await saveInitialReview(actionPayload({ initialAward: initialAward.value })); } + if (isTask('Task_improve002')) { + // 弹窗仅保存草稿;通用流程办理前才锁定评分,确保评委在此之前仍可修改。 + await submitDeptScore(actionPayload()); + } if (isTask('Task_improve003')) { + if (!leaderAwardBase.value) { + createMessage.warning('部门评议建议奖项尚未生成,不能确认负责人奖项'); + return false; + } if (!leaderFinalAward.value) { createMessage.warning('请选择负责人确认奖项'); return false; } + if (!leaderAwardOptions.value.some((item) => item.value === leaderFinalAward.value)) { + createMessage.warning('负责人确认奖项只能选择当前档位或降档奖项'); + return false; + } await saveLeaderAward(actionPayload({ leaderFinalAward: leaderFinalAward.value })); } - if (isTask('Task_improve005') && currentVote.value?.voteStatus !== 'voted') { - createMessage.warning('请先完成投票'); - return false; + if (isTask('Task_improve004')) { + if (!selectedInstituteVoterIds.value.length) { + createMessage.warning('请至少选择一名所级评议人员'); + return false; + } + await saveInstituteVoteMembers(actionPayload({ voterIds: selectedInstituteVoterIds.value })); + } + if (isTask('Task_improve005')) { + if (!currentVote.value?.voteResult) { + createMessage.warning('请先选择投票通过或投票反对'); + return false; + } + await submitInstituteVote(actionPayload()); } if (isTask('Task_improve006')) { if (!finalAward.value) { @@ -273,11 +372,11 @@ } } - async function castVote(voteResult: string) { + async function saveVoteDraft(voteResult: string) { savingVote.value = true; try { await castInstituteVote(actionPayload({ voteResult })); - createMessage.success('投票成功'); + createMessage.success('投票已保存,提交办理前可修改'); await loadProposal(); } catch (error: any) { createMessage.warning(error?.message || '投票失败'); @@ -287,16 +386,68 @@ } function openScoreModal() { - // 评分字段、权重计算和完成条件将由下一阶段单独实现,避免当前占位入口误写评分结果。 + const score = currentScore.value; + scoreForm.value = { + safetyScore: score?.safetyScore ?? undefined, + qualityScore: score?.qualityScore ?? undefined, + efficiencyScore: score?.efficiencyScore ?? undefined, + costScore: score?.costScore ?? undefined, + promotionScore: score?.promotionScore ?? undefined, + originalityScore: score?.originalityScore ?? undefined, + effortScore: score?.effortScore ?? undefined, + scoreOpinion: score?.scoreOpinion || '', + }; scoreModalVisible.value = true; } + async function saveScoreDraft() { + if (!hasValidScoreValues()) { + createMessage.warning('请完整填写 0 到 100 分之间的七项评分'); + return; + } + savingScore.value = true; + try { + await saveDeptScore(actionPayload(scoreForm.value)); + createMessage.success('评分保存成功,可在流程提交前继续修改'); + scoreModalVisible.value = false; + await loadProposal(); + } catch (error: any) { + createMessage.warning(error?.message || '评分保存失败'); + } finally { + savingScore.value = false; + } + } + + function createEmptyScoreForm() { + return { + safetyScore: undefined, + qualityScore: undefined, + efficiencyScore: undefined, + costScore: undefined, + promotionScore: undefined, + originalityScore: undefined, + effortScore: undefined, + scoreOpinion: '', + }; + } + + function hasValidScoreValues() { + return ['safetyScore', 'qualityScore', 'efficiencyScore', 'costScore', 'promotionScore', 'originalityScore', 'effortScore'].every((key) => { + const value = scoreForm.value[key]; + return value !== undefined && value !== null && Number(value) >= 0 && Number(value) <= 100; + }); + } + function awardLabel(value: string) { return awardOptions.find((item) => item.value === String(value))?.label || '-'; } function voteResultLabel(vote: Recordable) { - if (vote.voteStatus !== 'voted') return '待投票'; + if (vote.voteStatus !== 'voted') { + if (vote.voteResult === 'agree') return '已选择投票通过,待提交办理'; + if (vote.voteResult === 'disagree') return '已选择投票反对,待提交办理'; + return '待投票'; + } return vote.voteResult === 'agree' ? '已投同意票' : '已投不同意票'; } @@ -304,6 +455,10 @@ return value ? String(value).slice(0, 10) : '-'; } + function formatDateTime(value?: string) { + return value ? String(value) : '-'; + } + function handleSuccess() { emit('success'); } @@ -333,7 +488,7 @@ .attachment-grid { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } @@ -353,6 +508,40 @@ font-size: 12px; } + .score-range-alert { + margin-bottom: 16px; + } + + .vote-member-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 10px 12px; + width: 100%; + } + + .vote-member-option { + display: flex; + align-items: center; + min-height: 42px; + margin-inline-start: 0; + padding: 8px 10px; + border: 1px solid #d9e2ec; + border-radius: 4px; + background: #fff; + } + + .vote-member-name { + margin-left: 6px; + color: #1f2937; + font-weight: 500; + } + + .vote-member-username { + margin-left: 6px; + color: #64748b; + font-size: 12px; + } + @media (max-width: 768px) { .attachment-grid { grid-template-columns: 1fr; diff --git a/src/views/gh/improvementProposal/components/ImprovementProposalForm.vue b/src/views/gh/improvementProposal/components/ImprovementProposalForm.vue index 08b20bf..8e90a18 100644 --- a/src/views/gh/improvementProposal/components/ImprovementProposalForm.vue +++ b/src/views/gh/improvementProposal/components/ImprovementProposalForm.vue @@ -34,8 +34,8 @@
- - + + @@ -50,24 +50,24 @@ - - + + - - - + + + - - + + - - - + + + @@ -204,19 +204,19 @@ }; const longLabelCol = { xs: {span: 24}, - sm: {span: 5}, + sm: {span: 4}, }; const longWrapperCol = { xs: {span: 24}, - sm: {span: 16}, + sm: {span: 20}, }; const halfLabelCol = { xs: {span: 24}, - sm: {span: 5}, + sm: {span: 8}, }; const halfWrapperCol = { xs: {span: 24}, - sm: {span: 18}, + sm: {span: 16}, }; const disabled = computed(()=>{ @@ -230,8 +230,8 @@ return props.formDisabled; }); - // 普通新增没有业务 ID,不展示评分和投票子表;已有数据及流程表单正常展示。 - const showSubTables = computed(() => Boolean(formData.id || props.formData?.dataId)); + // 子表由流程节点维护,普通新增、编辑和详情弹窗均不展示,避免误改评分或投票数据。 + const showSubTables = computed(() => props.formBpm === true && Boolean(formData.id || props.formData?.dataId)); function getCurrentDate() { const now = new Date(); @@ -330,12 +330,6 @@ improvementDeptScoreTable.dataSource = [...improvementDeptScoreDataList]; const improvementInstituteVoteDataList = await queryImprovementInstituteVoteListByMainId(row['id']); improvementInstituteVoteTable.dataSource = [...improvementInstituteVoteDataList]; - // 加载改善前/后图片文件列表 - await nextTick(); - if (formData.id) { - beforeFileRef.value?.loadFileList(); - afterFileRef.value?.loadFileList(); - } } } @@ -360,11 +354,9 @@ async function edit(row) { //主表数据 await queryMainData(row.id); - //子表数据 - const improvementDeptScoreDataList = await queryImprovementDeptScoreListByMainId(row['id']); - improvementDeptScoreTable.dataSource = [...improvementDeptScoreDataList]; - const improvementInstituteVoteDataList = await queryImprovementInstituteVoteListByMainId(row['id']); - improvementInstituteVoteTable.dataSource = [...improvementInstituteVoteDataList]; + // 普通编辑不加载子表,并清理弹窗复用时可能残留的表格数据。 + improvementDeptScoreTable.dataSource = []; + improvementInstituteVoteTable.dataSource = []; // 加载改善前/后图片文件列表 await nextTick(); if (formData.id) { @@ -411,8 +403,8 @@ const mainData = await getFormData(); const isUpdate = Boolean(mainData.id); const values = Object.assign({}, dbData, mainData); - // 新增提案只保存主表;已有提案编辑或流程提交时才采集子表数据。 - if (isUpdate) { + // 仅流程表单提交子表,普通编辑不能覆盖后续流程维护的评分和投票记录。 + if (isUpdate && showSubTables.value) { const subData = await getSubFormAndTableData(); Object.assign(values, subData); }