Files
supervision-test-frontend-repo/src/views/bg/bgpartymatter/components/BgPartymatterBPMForm.vue
T
wsm 2b939ed260 !159 fix(bqtakepulse): 筛选条件文案改为意见类型
* fix(bqtakepulse): 筛选条件文案改为意见类型
* fix(bgpartymatter): 反馈弹窗字段对齐
* fix(inspectProblem): 上级问题字段按问题种类动态显示必填星号
* fix(bqtakepulse): 督办次数改为数字输入
* fix(bqtakepulse): 反馈弹窗字段对齐并支持长label换行
* feat(bpm): 各流程表单默认抄送改用角色查询接口
* fix(bgpartymatter): 所办负责人审批改为单选并调整编辑表单布局
* fix(modal): 详情弹窗支持点击遮罩关闭
2026-08-18 02:58:09 +00:00

1264 lines
43 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<a-spin :spinning="loading">
<div class="bg-partymatter-bpm-form">
<a-form
class="main-form"
:class="{ 'main-form-readonly': mainDisabled }"
labelAlign="left"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
:model="mainForm"
>
<div class="base-info-header section-toolbar">
<div class="section-title">基础信息</div>
<a-space class="section-actions">
<a-button v-if="!mainDisabled" class="base-save-button" type="link" size="small" :loading="savingMain" @click="submitForm">
<Icon icon="ant-design:save-outlined" />
保存
</a-button>
<a-button
class="section-action-button"
:type="showBaseInfoDetail ? 'default' : 'link'"
size="small"
@click="showBaseInfoDetail = !showBaseInfoDetail"
>
<Icon :icon="showBaseInfoDetail ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
{{ showBaseInfoDetail ? '收起基础信息' : '展开全部信息' }}
</a-button>
</a-space>
</div>
<a-row class="base-info-row" :gutter="[12, 4]">
<a-col :xs="24" :md="12">
<a-form-item label="事项名称" name="title">
<a-input v-model:value="mainForm.title" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
<a-col :xs="24" :md="12">
<a-form-item label="会议决议" name="content">
<a-textarea v-model:value="mainForm.content" :auto-size="{ minRows: 1, maxRows: 7 }" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
<template v-if="showBaseInfoDetail">
<a-divider />
<a-col :span="12">
<a-form-item label="年份" name="year">
<a-date-picker
v-model:value="mainForm.year"
picker="year"
value-format="YYYY"
:disabled="mainDisabled"
placeholder="请选择年份"
style="width: 100%"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="密级" name="secretLevel">
<j-dict-select-tag v-model:value="mainForm.secretLevel" disabled dictCode="secret_level" placeholder="请选择密级" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="序号" name="number">
<a-input v-model:value="mainForm.number" :disabled="mainDisabled" :readonly="true" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="议题序号" name="itemNumber">
<a-input-number v-model:value="mainForm.itemNumber" :disabled="mainDisabled" style="width: 100%" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="会议时间" name="matterTime">
<a-date-picker
v-model:value="mainForm.matterTime"
value-format="YYYY-MM-DD"
:disabled="mainDisabled"
style="width: 100%"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="事项目录编码" name="matterCode">
<a-input v-model:value="mainForm.matterCode" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="抄告单编号" name="noticeNumber">
<a-input v-model:value="mainForm.noticeNumber" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="是否为三重一大决策" name="isImportant">
<j-dict-select-tag v-model:value="mainForm.isImportant" :disabled="mainDisabled" dictCode="yn" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="分管所领导" name="manageSuoleader">
<DeptRoleUserSelectDropDown
v-model:value="mainForm.manageSuoleader"
:deptId="CHARGE_LEADER_DEPT_ID"
:roleId="CHARGE_LEADER_ROLE_ID"
:disabled="mainDisabled"
placeholder="请选择分管所领导"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="责任部门" name="responDeptid">
<j-select-dept
v-model:value="mainForm.responDeptid"
:disabled="mainDisabled"
:multiple="true"
checkStrictly
allow-clear
@change="handleDeptChange('responDeptid', $event)"
@select="handleDeptSelect('responDeptid', $event)"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="协办部门" name="assistDeptid">
<j-select-dept
v-model:value="mainForm.assistDeptid"
:disabled="mainDisabled"
:multiple="true"
checkStrictly
allow-clear
@change="handleDeptChange('assistDeptid', $event)"
@select="handleDeptSelect('assistDeptid', $event)"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="要求完成日期" name="deadline">
<a-date-picker v-model:value="mainForm.deadline" value-format="YYYY-MM-DD" :disabled="mainDisabled" style="width: 100%" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="是否急件" name="urgencyLevel">
<j-dict-select-tag
v-model:value="mainForm.urgencyLevel"
:disabled="mainDisabled"
dictCode="yn"
placeholder="请选择是否急件"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="督办次数" name="superviseCount">
<a-input-number
v-model:value="mainForm.superviseCount"
:disabled="mainDisabled"
:min="0"
:precision="0"
:step="1"
style="width: 100%"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="完成状态" name="bpmStatus">
<j-dict-select-tag v-model:value="mainForm.bpmStatus" disabled dictCode="super_status" allow-clear />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="实际执行情况" name="actualExect" :labelCol="wideLabelCol" :wrapperCol="wideWrapperCol">
<a-textarea v-model:value="mainForm.actualExect" :auto-size="{ minRows: 1, maxRows: 7 }" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
</template>
</a-row>
</a-form>
<a-divider />
<div class="feedback-header section-toolbar">
<div class="section-title">办理情况</div>
<a-space class="section-actions">
<a-tooltip v-if="showAddFeedback" title="可以多次新增反馈">
<a-button class="section-action-button feedback-add-button" type="primary" @click="openFeedbackModal">
<Icon icon="ant-design:plus-outlined" />
新增反馈
</a-button>
</a-tooltip>
<a-button
class="section-action-button"
:type="showFeedbackDetail ? 'default' : 'link'"
size="small"
@click="showFeedbackDetail = !showFeedbackDetail"
>
<Icon :icon="showFeedbackDetail ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
{{ showFeedbackDetail ? '收起办理情况' : '展开办理情况' }}
</a-button>
</a-space>
</div>
<a-table
v-if="showFeedbackDetail"
rowKey="id"
size="small"
bordered
:columns="feedbackColumns"
:data-source="feedbackList"
:pagination="false"
:scroll="{ x: feedbackTableScrollX }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'attachments'">
<SUploadFile
v-if="record.id"
:key="getFeedbackAttachmentKey(record)"
class="feedback-attachment-list"
:bussiness-id="record.id"
:disabled="true"
:multiple="true"
/>
<span v-else>-</span>
</template>
<template v-else-if="canManageFeedback && column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="openFeedbackEditModal(record)">编辑</a-button>
<a-popconfirm title="是否确认删除" @confirm="handleFeedbackDelete(record)">
<a-button type="link" size="small" danger>删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<BgPartymatterBPMFeedbackModal ref="feedbackModalRef" @success="loadFeedbackList" />
<template v-if="showProcessHandle">
<a-divider />
<div class="process-header section-toolbar">
<div class="section-title">流程办理</div>
</div>
<a-form v-if="showSubApproveUser" class="process-variable-form" labelAlign="left" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-row :gutter="16">
<a-col :xs="24" :md="{ span: 22, offset: 1 }">
<a-form-item label="是否办结" :labelCol="processLabelCol" :wrapperCol="processWideWrapperCol">
<a-radio-group v-model:value="isEnd" :disabled="savingSubApproveUser">
<a-radio :value="0">分配部门经办人</a-radio>
<a-radio :value="1">办结</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col v-if="showSubApproveUserSelector" :xs="24" :md="{ span: 22, offset: 1 }">
<a-form-item label="部门经办人" :labelCol="processLabelCol" :wrapperCol="processUserWrapperCol">
<sz-dept-user-modal class="process-user-selector" v-model:value="subApproveUser" :disabled="savingSubApproveUser" />
</a-form-item>
</a-col>
<!-- <a-col :span="24">-->
<!-- <a-form-item :wrapperCol="wideActionWrapperCol">-->
<!-- <a-button type="primary" :loading="savingSubApproveUser" @click="saveSubApproveUserVariable()">保存</a-button>-->
<!-- </a-form-item>-->
<!-- </a-col>-->
</a-row>
</a-form>
<a-form v-if="showFinishSelector" class="process-variable-form" labelAlign="left" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="是否已完成" :labelCol="wideLabelCol" :wrapperCol="wideWrapperCol">
<a-radio-group v-model:value="isFinish" :disabled="savingFinish">
<a-radio value="0">未完成</a-radio>
<a-radio value="1">已完成</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
</a-form>
<TaskHandleInnerContent
:form-data="formData"
:claim="claim"
:beforeHandle="saveMainFormBeforeProcessHandle"
:defaultReason="defaultHandleReason"
:defaultCc="defaultCcUsers"
@success="handleProcessSuccess"
@claimSuccess="handleClaimSuccess"
/>
</template>
</div>
</a-spin>
</template>
<script lang="ts" setup>
import { computed, onMounted, provide, reactive, ref, watch } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { getUserList, queryDepartTreeSync } from '/@/api/common/api';
import { useMessage } from '/@/hooks/web/useMessage';
import JSelectDept from '/@/components/Form/src/jeecg/components/JSelectDept.vue';
import Icon from '/@/components/Icon/index';
import { bgPartymatterFeedbackDelete, bgPartymatterFeedbackList, saveBpmForm, setBpmVariableLocal } from '../BgPartymatter.api';
import { usePermission } from '/@/hooks/web/usePermission';
import TaskHandleInnerContent from '/@/views/super/bpm/process/personalOffice/myHandleTask/content/TaskHandleInnerContent.vue';
import JDictSelectTag from '../../../../components/Form/src/jeecg/components/JDictSelectTag.vue';
import BgPartymatterBPMFeedbackModal from './BgPartymatterBPMFeedbackModal.vue';
import SUploadFile from '/@/components/semri/fileComponent/SUploadFile.vue';
import SzDeptUserModal from '/@/components/semri/deptUserComponent/SzDeptUserModal.vue';
import DeptRoleUserSelectDropDown from '/@/components/semri/userComponent/DeptRoleUserSelectDropDown.vue';
import { PARTY_COMMITTEE_DEPT_ID, PARTY_COMMITTEE_DEPT_ID as CHARGE_LEADER_DEPT_ID, CHARGE_LEADER_ROLE_ID } from '/@/constant/supervision';
const { initBpmFormData } = usePermission();
const props = defineProps({
formDisabled: { type: Boolean, default: false },
formData: { type: Object, default: () => ({}) },
formBpm: { type: Boolean, default: true },
claim: { type: Boolean, default: false },
});
initBpmFormData(props.formData);
const emit = defineEmits(['ok', 'success', 'claimSuccess']);
const { createMessage } = useMessage();
const queryByIdUrl = '/bgpartymatter/bgPartymatter/queryById';
const labelCol = { xs: { span: 24 }, sm: { span: 8 } };
const wrapperCol = { xs: { span: 24 }, sm: { span: 16 } };
const wideLabelCol = { xs: { span: 24 }, sm: { span: 4 } };
const wideWrapperCol = { xs: { span: 24 }, sm: { span: 20 } };
// 流程变量区桌面端整体右移并缩短标签列,兼顾左侧留白与标签、控件间距。
const processLabelCol = { xs: { span: 24 }, sm: { span: 4 }, md: { span: 2 } };
const processWideWrapperCol = { xs: { span: 24 }, sm: { span: 20 }, md: { span: 22 } };
const processUserWrapperCol = { xs: { span: 24 }, sm: { span: 20 }, md: { span: 9 } };
const feedbackModalRef = ref();
const loading = ref(false);
const savingMain = ref(false);
const savingSubApproveUser = ref(false);
const savingFinish = ref(false);
const showBaseInfoDetail = ref(false);
const showFeedbackDetail = ref(true);
const feedbackList = ref<any[]>([]);
const feedbackAttachmentRefreshKey = ref(0);
const deptNameText = reactive<Record<string, string>>({
responDeptid: '',
assistDeptid: '',
});
const deptOptionCache = reactive<Record<string, Record<string, string>>>({
responDeptid: {},
assistDeptid: {},
});
const deptResolveVersion = ref(0);
const subApproveUser = ref('');
const subApproveUserNameText = ref('');
const userResolveVersion = ref(0);
const isEnd = ref(0);
const isFinish = ref('0');
const defaultCcUsers = ref<Array<{ username: string; realname: string }>>([]);
// 办理内容组件通过 inject 读取默认抄送,需与 defaultCc 开关保持一致地提供
provide('defaultCcUsers', defaultCcUsers);
const lastGeneratedNumber = ref('');
const mainForm = reactive<Record<string, any>>({
id: '',
year: '',
secretLevel: '',
number: '',
itemNumber: undefined,
matterTime: '',
title: '',
content: '',
matterCode: '',
noticeNumber: '',
isImportant: '',
manageSuoleader: '',
responDeptid: '',
assistDeptid: '',
actualExect: '',
bpmStatus: '',
superviseCount: '',
deadline: '',
urgencyLevel: '',
isNeedAppro: '',
supDeptleaderid: '',
isNeedSuoleader: '',
suoleaderid: '',
isNeedInterval: '',
startCount: '',
intervalType: '',
intervalStartTime: '',
meetingType: 'party',
});
const feedbackBaseColumns = [
{ title: '责任部门名称', dataIndex: 'responDeptname', align: 'center', width: 160 },
{ title: '反馈人姓名', dataIndex: 'responPersonname', align: 'center', width: 140 },
{ title: '计划完成目标', dataIndex: 'planExect', align: 'center', width: 260 },
{
title: '计划完成日期',
dataIndex: 'planTime',
align: 'center',
width: 140,
customRender: ({ text }) => (!text ? '' : String(text).slice(0, 10)),
},
{ title: '实际执行情况', dataIndex: 'actualExect', align: 'center', width: 280 },
{
title: '实际完成日期',
dataIndex: 'actualTime',
align: 'center',
width: 180,
customRender: ({ text }) => (!text ? '' : String(text).slice(0, 10)),
},
{
title: '完成状态',
dataIndex: 'bpmStatus',
align: 'center',
width: 120,
customRender: ({ record }) => record?.bpmStatus_dictText || record?.bpmStatus,
},
{ title: '附件', dataIndex: 'id', key: 'attachments', align: 'left', width: 340 },
];
const feedbackActionColumn = { title: '操作', dataIndex: 'action', key: 'action', align: 'center', width: 120, fixed: 'right' };
const feedbackColumns = computed(() => {
return canManageFeedback.value ? [...feedbackBaseColumns, feedbackActionColumn] : feedbackBaseColumns;
});
const feedbackTableScrollX = computed(() => {
return canManageFeedback.value ? 1740 : 1620;
});
const mainDisabled = computed(() => {
if (props.formBpm) {
return props.formData?.disabled !== false;
}
return props.formDisabled;
});
// 只有待办办理入口展示流程办理区,历史/抄送查看即使有 taskId 也只展示业务表单。
const showProcessHandle = computed(() => props.formBpm && props.formData?.PROCESS_TAB_TYPE === 'run' && !!props.formData?.taskId);
const canManageFeedback = computed(() => props.formBpm && props.formData?.PROCESS_TAB_TYPE === 'run' && !!props.formData?.taskId);
const showAddFeedback = computed(() => String(props.formData?.extendUrlParams?.addfeedback ?? '') === '1');
const showSubApproveUser = computed(() => String(props.formData?.extendUrlParams?.selectuser ?? '') === '1');
const showFinishSelector = computed(() => String(props.formData?.extendUrlParams?.selectfinish ?? '') === '1');
const showSubApproveUserSelector = computed(() => showSubApproveUser.value && String(isEnd.value) !== '1');
const processInfo = computed(() => ({
taskId: props.formData?.taskId,
taskDefKey: props.formData?.taskDefKey,
processInstanceId:
props.formData?.procInsId ||
props.formData?.procInstId ||
props.formData?.processInstanceId ||
props.formData?.vars?.BPM_INST_ID ||
props.formData?.vars?.JG_LOCAL_PROCESS_ID ||
props.formData?.vars?.process_inst_id ||
props.formData?.vars?.processInstanceId,
processDataId: props.formData?.processDataId,
processTableName: props.formData?.processTableName,
}));
const handleReasonTemplateMap: Record<string, () => string> = {
Task_137y5vq: buildLeaderDeptReason,
Task_0d4iflp: buildDeptLeaderAssignReason,
Task_01ynnpq: buildDeptHandlerFeedbackReason,
Task_1s7zk9h: buildDeptLeaderApproveReason,
Task_0mpxssh: buildSuperviseHandlerConfirmReason,
Task_1g8igx5: buildSuperviseHandlerArchiveReason,
};
const defaultHandleReason = computed(() => {
const builder = handleReasonTemplateMap[String(processInfo.value.taskDefKey || '')];
return builder ? builder() : '';
});
onMounted(() => {
initFormData();
});
watch([() => props.formData?.dataId, () => processInfo.value.processInstanceId], () => initFormData());
watch(
() => props.formData?.vars?.isEnd,
(value) => {
isEnd.value = String(value ?? '0') === '1' ? 1 : 0;
},
{ immediate: true }
);
watch(
() => props.formData?.vars?.isFinish,
(value) => {
isFinish.value = String(value ?? '0') === '1' ? '1' : '0';
},
{ immediate: true }
);
watch(
subApproveUser,
(value) => {
resolveSubApproveUserName(value);
},
{ immediate: true }
);
// 根据会议时间和议题序号自动生成业务序号,例如:2026-05-20 + 2 => 20260520-2。
watch(
() => [mainForm.matterTime, mainForm.itemNumber],
() => {
syncGeneratedNumber();
}
);
const showDefaultCc = computed(() => String(props.formData?.extendUrlParams?.defaultCc ?? '') === '1');
async function fetchDefaultCcUsers() {
try {
// 默认抄送按分管所领导角色跨部门查询,与台账分管所领导下拉逻辑一致
const res = await defHttp.get({
url: '/sys/user/queryUserByRoleId',
params: { roleId: CHARGE_LEADER_ROLE_ID, searchSecurityLevel: 5, pageNo: 1, pageSize: 999 },
});
const records = res?.records || res?.result?.records || [];
defaultCcUsers.value = records.sort((a, b) => (a.sortno ?? Infinity) - (b.sortno ?? Infinity));
} catch {
defaultCcUsers.value = [];
}
}
watch(
() => showDefaultCc.value,
() => {
if (showDefaultCc.value) {
fetchDefaultCcUsers();
} else {
defaultCcUsers.value = [];
}
},
{ immediate: true }
);
async function initFormData() {
const dataId = props.formData?.dataId;
if (!dataId) {
return;
}
showBaseInfoDetail.value = false;
loading.value = true;
try {
const data = await defHttp.get({ url: queryByIdUrl, params: { id: dataId } });
if (dataId !== props.formData?.dataId) {
return;
}
setMainForm(data || {});
await syncDeptNamesFromRecord(data || {});
if (dataId !== props.formData?.dataId) {
return;
}
await loadFeedbackList();
} finally {
loading.value = false;
}
}
function setMainForm(record: Record<string, any>) {
Object.keys(mainForm).forEach((key) => {
mainForm[key] = record[key] ?? '';
});
// 部门选择组件未展开时不会立刻回传 label,先使用详情接口的字典文本生成默认办理意见。
deptNameText.responDeptid = normalizeDeptNames(record.responDeptid_dictText || record.responDeptname);
deptNameText.assistDeptid = normalizeDeptNames(record.assistDeptid_dictText || record.assistDeptname);
}
async function syncDeptNamesFromRecord(record: Record<string, any>) {
const resolveVersion = ++deptResolveVersion.value;
const [responDept, assistDept] = await Promise.all([
resolveDeptNameText('responDeptid', record.responDeptid, record.responDeptid_dictText || record.responDeptname),
resolveDeptNameText('assistDeptid', record.assistDeptid, record.assistDeptid_dictText || record.assistDeptname),
]);
if (resolveVersion !== deptResolveVersion.value) {
return;
}
applyResolvedDeptName(responDept);
applyResolvedDeptName(assistDept);
}
async function resolveDeptNameText(field: 'responDeptid' | 'assistDeptid', ids: any, fallbackText: any) {
const deptIds = normalizeDeptIds(ids);
const fallbackNames = splitDeptNames(fallbackText);
if (!deptIds.length) {
return { field, cache: {}, nameText: '' };
}
if (fallbackNames.length) {
return { field, cache: buildDeptOptionCache(deptIds, fallbackNames), nameText: normalizeDeptNames(fallbackNames.join(',')) };
}
try {
const records = (await queryDepartTreeSync({ ids: deptIds.join(','), primaryKey: 'id' })) || [];
const cache = records.reduce((cache: Record<string, string>, item: any) => {
const id = String(item?.id || item?.value || item?.key || '').trim();
const name = String(item?.departName || item?.title || item?.label || '').trim();
if (id && name) {
cache[id] = name;
}
return cache;
}, {});
const names = deptIds.map((id) => cache[id]).filter(Boolean);
return { field, cache, nameText: normalizeDeptNames(names.join(',')) };
} catch {
return { field, cache: {}, nameText: '' };
}
}
function applyResolvedDeptName(result: { field: 'responDeptid' | 'assistDeptid'; cache: Record<string, string>; nameText: string }) {
deptOptionCache[result.field] = result.cache;
deptNameText[result.field] = result.nameText;
}
function handleDeptSelect(field: 'responDeptid' | 'assistDeptid', options?: any[] | null) {
const nextCache: Record<string, string> = {};
if (Array.isArray(options)) {
options.forEach((item) => {
const value = String(item?.value || item?.id || item?.key || '').trim();
const label = String(item?.label || item?.title || item?.departName || item?.text || '').trim();
if (value && label) {
nextCache[value] = label;
}
});
}
deptOptionCache[field] = nextCache;
syncDeptNameText(field, mainForm[field]);
}
function handleDeptChange(field: 'responDeptid' | 'assistDeptid', value: any) {
syncDeptNameText(field, value);
}
function buildLeaderDeptReason() {
const responDeptName = normalizeDeptNames(deptNameText.responDeptid);
if (!responDeptName) {
return '';
}
const assistDeptName = normalizeDeptNames(deptNameText.assistDeptid);
return assistDeptName ? `同意。请${responDeptName}${assistDeptName}反馈办理情况;` : `同意。请${responDeptName}反馈办理情况。`;
}
function buildDeptLeaderAssignReason() {
if (String(isEnd.value) === '1') {
return '已反馈完成情况,请确认。';
}
const userNameText = normalizeDeptNames(subApproveUserNameText.value || subApproveUser.value);
return userNameText ? `请${userNameText}办理。` : '';
}
function buildDeptHandlerFeedbackReason() {
return '已完成反馈,请领导审批。';
}
function buildDeptLeaderApproveReason() {
return String(isFinish.value) === '1' ? '已审批,请督办部门确认。' : '请反馈完成情况。';
}
function buildSuperviseHandlerConfirmReason() {
return '已确认。';
}
function buildSuperviseHandlerArchiveReason() {
return '已归档确认。';
}
function normalizeDeptNames(value: any) {
return splitDeptNames(value).join('、');
}
function splitDeptNames(value: any) {
return String(value || '')
.split(/[,;;、]/)
.map((item) => item.trim())
.filter(Boolean);
}
function buildDeptOptionCache(ids: string[], names: string[]) {
return ids.reduce((cache: Record<string, string>, id, index) => {
const name = names[index];
if (id && name) {
cache[id] = name;
}
return cache;
}, {});
}
function syncDeptNameText(field: 'responDeptid' | 'assistDeptid', value: any) {
const ids = normalizeDeptIds(value);
if (!ids.length) {
deptNameText[field] = '';
return;
}
const names = ids.map((id) => deptOptionCache[field][id]).filter(Boolean);
if (names.length) {
deptNameText[field] = normalizeDeptNames(names.join(','));
}
}
function normalizeDeptIds(value: any) {
if (Array.isArray(value)) {
return value.map((item) => String(item || '').trim()).filter(Boolean);
}
return String(value || '')
.split(',')
.map((item) => item.trim())
.filter(Boolean);
}
async function resolveSubApproveUserName(value: any) {
const resolveVersion = ++userResolveVersion.value;
const usernames = normalizeUsernameList(value);
if (!usernames) {
subApproveUserNameText.value = '';
return;
}
// JSelectUser 的 v-model 存 username,办理意见展示需要尽量翻译为真实姓名。
subApproveUserNameText.value = normalizeDeptNames(usernames);
try {
const res = await getUserList({ username: usernames, isMultiTranslate: 'true', pageNo: 1, pageSize: 999 });
if (resolveVersion !== userResolveVersion.value) {
return;
}
const records = res?.records || res?.result?.records || res || [];
const userNameMap = Array.isArray(records)
? records.reduce((cache: Record<string, string>, item: any) => {
const username = String(item?.username || '').trim();
const realname = String(item?.realname || item?.name || '').trim();
if (username && realname) {
cache[username] = realname;
}
return cache;
}, {})
: {};
const names = usernames
.split(',')
.map((username) => userNameMap[username] || username)
.filter(Boolean);
subApproveUserNameText.value = normalizeDeptNames(names.join(','));
} catch {
if (resolveVersion === userResolveVersion.value) {
subApproveUserNameText.value = normalizeDeptNames(usernames);
}
}
}
function syncGeneratedNumber() {
const generatedNumber = buildGeneratedNumber(mainForm.matterTime, mainForm.itemNumber);
if (generatedNumber) {
mainForm.number = generatedNumber;
lastGeneratedNumber.value = generatedNumber;
return;
}
if (lastGeneratedNumber.value && mainForm.number === lastGeneratedNumber.value) {
mainForm.number = '';
lastGeneratedNumber.value = '';
}
}
// 只在两个必要字段都填写后生成,避免用户未填完整时写入半截序号。
function buildGeneratedNumber(matterTime, itemNumber) {
const dateText = formatMatterTime(matterTime);
if (!dateText || itemNumber === undefined || itemNumber === null || itemNumber === '') {
return '';
}
return `${dateText}-${itemNumber}`;
}
// 兼容日期选择器返回的字符串、Date、dayjs/moment 对象。
function formatMatterTime(value) {
if (!value) {
return '';
}
if (typeof value === 'string') {
return value.slice(0, 10).replace(/-/g, '');
}
if (value instanceof Date) {
const year = value.getFullYear();
const month = String(value.getMonth() + 1).padStart(2, '0');
const day = String(value.getDate()).padStart(2, '0');
return `${year}${month}${day}`;
}
if (typeof value.format === 'function') {
return value.format('YYYYMMDD');
}
return '';
}
async function loadFeedbackList() {
const bpmProcessId = processInfo.value.processInstanceId;
if (!mainForm.id || !bpmProcessId) {
feedbackList.value = [];
feedbackAttachmentRefreshKey.value += 1;
return;
}
const res = await bgPartymatterFeedbackList({ mainId: mainForm.id, bpmProcessId, pageNo: 1, pageSize: 999 });
feedbackList.value = res?.records || res?.result?.records || [];
feedbackAttachmentRefreshKey.value += 1;
}
function getFeedbackAttachmentKey(record: Recordable) {
return `${record?.id || ''}-${feedbackAttachmentRefreshKey.value}`;
}
async function submitForm() {
return saveMainForm();
}
async function saveMainForm(options: { showMessage?: boolean; emitOk?: boolean } = {}) {
const { showMessage = true, emitOk = true } = options;
savingMain.value = true;
try {
const processInstanceId = processInfo.value.processInstanceId;
console.log('BgPartymatter BPM process info', { ...processInfo.value, processInstanceId });
const res = await saveBpmForm({
processInstanceId,
varField: 'json_data',
formData: { ...mainForm },
});
if (res.success) {
if (showMessage) {
createMessage.success(res.message || '保存成功');
}
if (emitOk) {
emit('ok');
}
return true;
}
createMessage.warning(res.message || '保存失败');
return false;
} catch (error) {
createMessage.warning('保存失败');
return false;
} finally {
savingMain.value = false;
}
}
async function saveMainFormBeforeProcessHandle() {
if (!mainDisabled.value) {
const mainSaved = await saveMainForm({ showMessage: false, emitOk: false });
if (!mainSaved) {
return false;
}
}
if (showSubApproveUser.value) {
const subApproveUserSaved = await saveSubApproveUserVariable({ showSuccessMessage: false });
if (!subApproveUserSaved) {
return false;
}
}
if (showFinishSelector.value) {
return saveFinishVariable({ showSuccessMessage: false });
}
return true;
}
function openFeedbackModal() {
if (!mainForm.id) {
createMessage.warning('主表数据不存在,无法新增反馈');
return;
}
const processInstanceId = processInfo.value.processInstanceId;
if (!processInstanceId) {
createMessage.warning('流程实例ID不能为空,无法新增反馈');
return;
}
feedbackModalRef.value?.open(mainForm.id, mainForm.secretLevel, processInstanceId);
}
function openFeedbackEditModal(record: Recordable) {
if (!canManageFeedback.value) {
createMessage.warning('当前流程状态不允许编辑反馈');
return;
}
if (!record?.id) {
createMessage.warning('反馈记录不存在,无法编辑');
return;
}
feedbackModalRef.value?.open(mainForm.id, mainForm.secretLevel, processInfo.value.processInstanceId, record);
}
async function handleFeedbackDelete(record: Recordable) {
if (!canManageFeedback.value) {
createMessage.warning('当前流程状态不允许删除反馈');
return;
}
if (!record?.id) {
createMessage.warning('反馈记录不存在,无法删除');
return;
}
try {
await bgPartymatterFeedbackDelete({ id: record.id }, () => undefined);
createMessage.success('删除成功');
await loadFeedbackList();
} catch (error) {
createMessage.warning('删除失败');
}
}
async function saveSubApproveUserVariable(options: { showSuccessMessage?: boolean } = {}) {
const { showSuccessMessage = true } = options;
const processInstanceId = processInfo.value.processInstanceId;
const isEndValue = String(isEnd.value);
const usernameValue = normalizeUsernameList(subApproveUser.value);
if (!processInstanceId) {
createMessage.warning('流程实例ID不能为空');
return false;
}
if (isEndValue !== '1' && !usernameValue) {
createMessage.warning('请选择部门经办人');
return false;
}
savingSubApproveUser.value = true;
try {
const isEndResult = await saveProcessVariable('isEnd', isEndValue);
if (!isEndResult?.success) {
createMessage.warning(isEndResult.message || '保存失败');
return false;
}
if (isEndValue !== '1') {
const subApproveUserResult = await saveProcessVariable('subApproveUser', usernameValue);
if (!subApproveUserResult?.success) {
createMessage.warning(subApproveUserResult.message || '保存失败');
return false;
}
}
if (showSuccessMessage) {
createMessage.success('保存成功');
}
return true;
} catch (error) {
createMessage.warning('保存失败');
return false;
} finally {
savingSubApproveUser.value = false;
}
}
async function saveFinishVariable(options: { showSuccessMessage?: boolean } = {}) {
const { showSuccessMessage = true } = options;
const processInstanceId = processInfo.value.processInstanceId;
if (!processInstanceId) {
createMessage.warning('流程实例ID不能为空');
return false;
}
savingFinish.value = true;
try {
// Gateway_isfinish 读取当前执行分支的局部变量,需在办理动作前写入。
const result = await saveProcessVariable('isFinish', String(isFinish.value));
if (!result?.success) {
createMessage.warning(result.message || '保存失败');
return false;
}
if (showSuccessMessage) {
createMessage.success('保存成功');
}
return true;
} catch (error) {
createMessage.warning('保存失败');
return false;
} finally {
savingFinish.value = false;
}
}
function saveProcessVariable(varField: string, varVal: any) {
return setBpmVariableLocal({
processInstanceId: processInfo.value.processInstanceId,
taskId: processInfo.value.taskId,
parentLevel: 2,
varField,
varVal,
});
}
function normalizeUsernameList(value: string) {
return String(value || '')
.split(',')
.map((item) => item.trim())
.filter(Boolean)
.join(',');
}
function handleProcessSuccess() {
emit('success');
}
function handleClaimSuccess() {
emit('claimSuccess');
}
defineExpose({
submitForm,
initFormData,
loadFeedbackList,
});
</script>
<style lang="less" scoped>
.bg-partymatter-bpm-form {
padding: 8px 12px 14px;
}
.main-form {
background: #fff;
}
.main-form,
.process-variable-form {
:deep(.ant-form-item-label) {
text-align: left;
}
:deep(.ant-form-item-label > label) {
justify-content: flex-start;
color: #1f2937;
font-weight: 500;
}
:deep(.ant-form-item-control),
:deep(.ant-form-item-control-input),
:deep(.ant-form-item-control-input-content) {
text-align: left;
}
}
.section-title {
margin-bottom: 8px;
padding-left: 8px;
border-left: 3px solid #1677ff;
color: #1f2937;
font-size: 14px;
font-weight: 600;
line-height: 16px;
}
.section-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
padding: 6px 10px;
background: #f6fbff;
border: 1px solid #d6e8ff;
border-radius: 6px;
.section-title {
margin-bottom: 0;
}
.section-actions {
display: inline-flex;
align-items: center;
gap: 4px;
}
.section-action-button {
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 58px;
justify-content: center;
font-weight: 500;
box-shadow: none;
}
.base-save-button {
display: inline-flex;
align-items: center;
gap: 4px;
height: 24px;
padding: 0 6px;
color: #64748b;
font-weight: 400;
}
}
.base-info-header {
margin-top: 0;
}
.base-info-row {
padding: 0 10px;
:deep(.ant-form-item) {
margin-bottom: 6px;
}
:deep(.ant-form-item-label) {
padding-bottom: 0;
}
:deep(.ant-form-item-label > label) {
width: 100%;
}
:deep(.ant-input),
:deep(.ant-input-number),
:deep(.ant-picker),
:deep(.ant-select-selector) {
min-height: 30px;
}
}
.main-form-readonly {
:deep(.ant-input[disabled]),
:deep(.ant-input-disabled),
:deep(.ant-input-affix-wrapper-disabled),
:deep(.ant-input-affix-wrapper-disabled.ant-input-outlined),
:deep(.ant-input-affix-wrapper-disabled:hover),
:deep(.ant-input-affix-wrapper-disabled:focus),
:deep(.ant-input-affix-wrapper-disabled-focused),
:deep(.ant-input-outlined[disabled]),
:deep(.ant-input-outlined.ant-input-disabled),
:deep(.ant-input-number-disabled),
:deep(.ant-input-number-disabled:hover),
:deep(.ant-input-number-disabled:focus),
:deep(textarea.ant-input[disabled]),
:deep(.ant-picker-disabled),
:deep(.ant-select-disabled .ant-select-selector) {
background: transparent !important;
border-color: transparent !important;
color: #1f2937 !important;
box-shadow: none !important;
cursor: default !important;
opacity: 1 !important;
}
:deep(.ant-input[disabled]),
:deep(.ant-input-disabled),
:deep(.ant-input-number-disabled),
:deep(.ant-input-affix-wrapper-disabled),
:deep(textarea.ant-input[disabled]) {
padding-left: 0 !important;
padding-right: 0 !important;
}
:deep(.ant-input-affix-wrapper-disabled .ant-input) {
background: transparent !important;
color: #1f2937 !important;
cursor: default !important;
}
:deep(.ant-input-number-disabled .ant-input-number-input) {
background: transparent !important;
color: #1f2937 !important;
-webkit-text-fill-color: #1f2937 !important;
cursor: default !important;
}
:deep(.ant-input[disabled]::placeholder),
:deep(.ant-input-disabled::placeholder),
:deep(.ant-input-affix-wrapper-disabled .ant-input::placeholder) {
color: #9ca3af !important;
}
:deep(.ant-picker-disabled input),
:deep(.ant-select-disabled .ant-select-selection-item),
:deep(.ant-select-disabled .ant-select-selection-item span),
:deep(.ant-select-disabled .ant-select-selection-placeholder),
:deep(.ant-select-disabled .ant-select-selection-placeholder span) {
color: #1f2937 !important;
-webkit-text-fill-color: #1f2937 !important;
cursor: default !important;
}
:deep(.ant-picker-disabled),
:deep(.ant-select-disabled .ant-select-selector) {
padding-left: 0 !important;
}
:deep(.ant-select-disabled),
:deep(.ant-select-disabled *),
:deep(.ant-select-disabled.ant-select-outlined),
:deep(.ant-select-disabled.ant-select-outlined .ant-select-selector) {
color: #1f2937 !important;
-webkit-text-fill-color: #1f2937 !important;
opacity: 1 !important;
}
:deep(.ant-select-disabled .ant-select-arrow),
:deep(.ant-picker-disabled .ant-picker-suffix),
:deep(.ant-input-disabled .ant-input-clear-icon),
:deep(.ant-input-affix-wrapper-disabled .ant-input-clear-icon),
:deep(.ant-input-number-disabled .ant-input-number-handler-wrap),
:deep(.ant-select-disabled .ant-select-clear) {
display: none;
}
}
.feedback-header {
margin-bottom: 8px;
.feedback-add-button {
min-width: 104px;
}
}
.process-variable-form {
margin-bottom: 8px;
}
// 限制经办人选人框与按钮的整体宽度,窄屏下仍可缩小到可用区域内。
.process-user-selector {
width: 300px;
max-width: 100%;
}
.sub-approve-user-row {
display: flex;
width: 100%;
:deep(.ant-input-affix-wrapper),
:deep(.ant-input) {
min-width: 280px;
}
}
:deep(.ant-divider-horizontal) {
margin: 10px 0;
}
.feedback-attachment-list {
min-width: 300px;
:deep(.file-list-wrapper) {
min-height: 0 !important;
padding: 4px 8px;
}
:deep(.ant-spin-nested-loading),
:deep(.ant-spin-container) {
min-height: 0 !important;
line-height: 20px;
}
:deep(.file-list-header) {
margin-bottom: 6px;
padding-bottom: 6px;
}
:deep(.file-list .file-item) {
padding: 8px;
margin-bottom: 6px;
}
:deep(.ant-empty) {
margin: 0 !important;
display: flex;
align-items: center;
min-height: 20px;
text-align: left;
}
:deep(.ant-empty-image) {
display: none !important;
}
:deep(.ant-empty-description) {
color: #94a3b8;
font-size: 12px;
line-height: 20px;
}
}
</style>