Files
supervision-test-frontend-repo/src/views/bg/bgpartymatter/components/BgPartymatterBPMForm.vue
T
ye1023 7895c7e56c yxk-20260708
实际完成时间,修改为实际完成日期
2026-07-08 16:45:45 +08:00

1125 lines
38 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" :labelCol="compactLabelCol" :wrapperCol="compactWrapperCol">
<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" :labelCol="compactLabelCol" :wrapperCol="compactWrapperCol">
<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">
<j-dict-select-tag v-model:value="mainForm.year" :disabled="mainDisabled" dictCode="super_year" placeholder="请选择年份" 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 v-model:value="mainForm.superviseCount" :disabled="mainDisabled" 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-button v-if="showAddFeedback" class="section-action-button feedback-add-button" type="primary" @click="openFeedbackModal">
<Icon icon="ant-design:plus-outlined" />
新增反馈
</a-button>
<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 :span="12">
<a-form-item label="是否办结" :labelCol="wideLabelCol" :wrapperCol="wideWrapperCol">
<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" :span="12">
<a-form-item label="部门经办人" :labelCol="wideLabelCol" :wrapperCol="wideWrapperCol">
<sz-dept-user-modal
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>
<TaskHandleInnerContent
:form-data="formData"
:claim="claim"
:beforeHandle="saveMainFormBeforeProcessHandle"
:defaultReason="defaultHandleReason"
@success="handleProcessSuccess"
@claimSuccess="handleClaimSuccess"
/>
</template>
</div>
</a-spin>
</template>
<script lang="ts" setup>
import { computed, onMounted, 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, setProcessVariable } 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 as CHARGE_LEADER_DEPT_ID,
CHARGE_LEADER_ROLE_ID,
} from '/@/constant/supervision';
const { isDisabledAuth, hasPermission, 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);
console.log("@props.formData", props.formData);
console.log("@props", props);
const emit = defineEmits(['ok', 'success', 'claimSuccess']);
const { createMessage } = useMessage();
const queryByIdUrl = '/bgpartymatter/bgPartymatter/queryById';
const labelCol = { xs: { span: 24 }, sm: { span: 7 } };
const wrapperCol = { xs: { span: 24 }, sm: { span: 17 } };
const wideLabelCol = { xs: { span: 24 }, sm: { span: 3 } };
const wideWrapperCol = { xs: { span: 24 }, sm: { span: 21 } };
const compactLabelCol = { xs: { span: 24 }, sm: { span: 5 } };
const compactWrapperCol = { xs: { span: 24 }, sm: { span: 19 } };
const wideActionWrapperCol = { xs: { span: 24 }, sm: { span: 21, offset: 3 } };
const feedbackModalRef = ref();
const loading = ref(false);
const savingMain = ref(false);
const savingSubApproveUser = 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 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 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(
subApproveUser,
(value) => {
resolveSubApproveUserName(value);
},
{ immediate: true }
);
// 根据会议时间和议题序号自动生成业务序号,例如:2026-05-20 + 2 => 20260520-2。
watch(
() => [mainForm.matterTime, mainForm.itemNumber],
() => {
syncGeneratedNumber();
}
);
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 '已审批,请督办部门确认。';
}
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) {
return saveSubApproveUserVariable({ 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;
}
}
function saveProcessVariable(varField: string, varVal: any) {
return setProcessVariable({
processInstanceId: processInfo.value.processInstanceId,
taskId: processInfo.value.taskId,
local: true,
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;
}
.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>