Files
supervision-test-frontend-repo/src/views/bg/bqtakepulse/components/BgTakepulseBPMForm.vue
T
wsm 11f6ba3961 !112 refactor(bg): 年份字段从字典组件替换为年份选择器
* refactor(bg): 年份字段从字典组件替换为年份选择器
* feat(bpm): 待办/已办/待阅/已阅弹窗标题改为业务标题
* feat(bpmform): 反馈新增时计划完成目标/日期必填可编辑,编辑时禁用并提示
* style(bpmform): 统一表单标签宽度和对齐,清理调试代码
* feat(bg): 再次发起督办弹窗标题显示中文数字督办次数
* style(bpmform): 新增反馈按钮添加悬浮提示"可以多次新增反馈"
2026-07-24 03:16:38 +00:00

1183 lines
41 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-takepulse-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="mainDisabled" dictCode="secret_level" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="会议类型" name="taskType">
<j-dict-select-tag v-model:value="mainForm.taskType" :disabled="mainDisabled" dictCode="super_takepulse_type" 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" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="类别" name="type">
<a-input v-model:value="mainForm.type" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="提出人" name="proposer">
<j-select-user v-model:value="mainForm.proposer" :disabled="mainDisabled" 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="suoleader">
<j-select-user v-model:value="mainForm.suoleader" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="牵头部门" name="responDeptid">
<sz-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">
<sz-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="implementDeptid">
<a-input v-model:value="mainForm.implementDeptid" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="是否采纳" name="isAdopt">
<j-switch v-model:value="mainForm.isAdopt" :disabled="mainDisabled" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="落实措施/解释说明" name="measure" :labelCol="wideLabelCol" :wrapperCol="wideWrapperCol">
<a-textarea v-model:value="mainForm.measure" :auto-size="{ minRows: 1, maxRows: 7 }" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="措施数量" name="measureNumber">
<a-input-number v-model:value="mainForm.measureNumber" :disabled="mainDisabled" style="width: 100%" />
</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="24">
<a-form-item label="成果形式" name="achievement" :labelCol="wideLabelCol" :wrapperCol="wideWrapperCol">
<a-textarea v-model:value="mainForm.achievement" :auto-size="{ minRows: 1, maxRows: 7 }" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="实际完成时间" name="actualTime">
<a-date-picker v-model:value="mainForm.actualTime" showTime value-format="YYYY-MM-DD HH:mm:ss" :disabled="mainDisabled" 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="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="urgencyLevel">
<j-dict-select-tag v-model:value="mainForm.urgencyLevel" :disabled="mainDisabled" dictCode="yn" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="提出人确认" name="proposerConfirm">
<a-input v-model:value="mainForm.proposerConfirm" :disabled="mainDisabled" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="备注" name="remark">
<a-input v-model:value="mainForm.remark" :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>
<BgTakepulseBPMFeedbackModal 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-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, 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 SzSelectDept from '/@/components/Form/src/jeecg/components/SzSelectDept.vue';
import JSelectUser from '/@/components/Form/src/jeecg/components/JSelectUser.vue';
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import JSwitch from '/@/components/Form/src/jeecg/components/JSwitch.vue';
import Icon from '/@/components/Icon/index';
import { bgTakepulseFeedbackDelete, bgTakepulseFeedbackList, saveBpmForm, setBpmVariableLocal } from '../BgTakepulse.api';
import { usePermission } from '/@/hooks/web/usePermission';
import TaskHandleInnerContent from '/@/views/super/bpm/process/personalOffice/myHandleTask/content/TaskHandleInnerContent.vue';
import BgTakepulseBPMFeedbackModal from './BgTakepulseBPMFeedbackModal.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,
SDW_LEADER_ROLE_ID,
SUOBAN_DEPT_ID,
MEASURE_RES_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 = '/bqtakepulse/bgTakepulse/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 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 }>>([]);
const mainForm = reactive<Record<string, any>>({
id: '',
secretLevel: '',
year: '',
number: '',
taskType: '',
type: '',
proposer: '',
title: '',
content: '',
manageSuoleader: '',
suoleader: '',
responDeptid: '',
assistDeptid: '',
implementDeptid: '',
isAdopt: '',
measure: '',
measureNumber: undefined,
achievement: '',
deadline: '',
remark: '',
actualExect: '',
actualTime: '',
proposerConfirm: '',
bpmStatus: '',
actSt: '',
superviseCount: '',
urgencyLevel: '',
isNeedAppro: '',
supDeptleaderid: '',
delFlag: '',
});
const feedbackBaseColumns = [
{ title: '责任部门名称', dataIndex: 'responDeptname', align: 'center', width: 160 },
{ title: '反馈人姓名', dataIndex: 'responPersonname', align: 'center', width: 110 },
{ title: '计划完成目标', dataIndex: 'planExect', align: 'left', width: 260 },
{
title: '计划完成日期',
dataIndex: 'planTime',
align: 'center',
width: 120,
customRender: ({ text }) => (!text ? '' : String(text).slice(0, 10)),
},
{ title: '实际执行情况', dataIndex: 'actualExect', align: 'left', width: 280 },
{ title: '实际完成日期', dataIndex: 'actualTime', align: 'center', width: 155, customRender: ({ text }) => (!text ? '' : String(text).slice(0, 10)) },
{ title: '调整落实措施及执行情况', dataIndex: 'actSt', align: 'left', width: 260 },
{ title: '完成状态', dataIndex: 'bpmStatus', align: 'center', width: 95, 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 ? 1900 : 1780;
});
const mainDisabled = computed(() => {
if (props.formBpm) {
return props.formData?.disabled !== false;
}
return props.formDisabled;
});
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());
const showDefaultCc = computed(() => String(props.formData?.extendUrlParams?.defaultCc ?? '') === '1');
async function fetchDefaultCcUsers() {
try {
const [res1, res2] = await Promise.all([
defHttp.get({
url: '/sys/user/queryUserRoleComponentData',
params: { departId: PARTY_COMMITTEE_DEPT_ID, roleId: SDW_LEADER_ROLE_ID, searchSecurityLevel: 5, pageNo: 1, pageSize: 999 },
}),
defHttp.get({
url: '/sys/user/queryUserRoleComponentData',
params: { departId: SUOBAN_DEPT_ID, roleId: MEASURE_RES_LEADER_ROLE_ID, searchSecurityLevel: 5, pageNo: 1, pageSize: 999 },
}),
]);
const records1 = res1?.records || res1?.result?.records || [];
const records2 = res2?.records || res2?.result?.records || [];
defaultCcUsers.value = [...records1, ...records2];
} catch {
defaultCcUsers.value = [];
}
}
watch(
() => showDefaultCc.value,
() => {
if (showDefaultCc.value) {
fetchDefaultCcUsers();
} else {
defaultCcUsers.value = [];
}
},
{ immediate: true }
);
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 }
);
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] ?? '';
});
// 基础信息默认折叠时部门组件不会挂载,先使用详情里的字典文本生成默认办理意见。
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 deptNames = [deptNameText.responDeptid, deptNameText.assistDeptid].map(normalizeDeptNames).filter(Boolean);
if (!deptNames.length) {
return '';
}
return `同意。请${deptNames.join('、')}反馈办理情况。`;
}
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);
}
}
}
async function loadFeedbackList() {
const bpmProcessId = processInfo.value.processInstanceId;
if (!mainForm.id || !bpmProcessId) {
feedbackList.value = [];
feedbackAttachmentRefreshKey.value += 1;
return;
}
const res = await bgTakepulseFeedbackList({ 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;
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 bgTakepulseFeedbackDelete({ 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 读取当前分支局部变量,需在办理动作前写入外层分支 execution。
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-takepulse-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;
}
: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>