Merge branch '20260519-3.9.2版本-葛昊天分支'
This commit is contained in:
@@ -1,94 +0,0 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
/**
|
||||
* 审批流「回调接口」录制器。
|
||||
*
|
||||
* 审批流设计器是覆盖在业务页面之上的(同一前端应用、同一个 defHttp 实例),
|
||||
* 录制时全局拦截器会把每次业务请求的 url/method/参数 上报到这里,
|
||||
* 捕获用户点击目标按钮所触发的真实请求,自动回填到节点的回调接口配置。
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】可视化录制业务按钮接口
|
||||
*/
|
||||
|
||||
export interface FlowCapturedApi {
|
||||
/** 请求路径(servlet 内路径,如 /xslmes/xxx/approve) */
|
||||
url: string;
|
||||
/** 请求方法 GET/POST/PUT/DELETE */
|
||||
method: string;
|
||||
/** 请求体(POST/PUT 时) */
|
||||
data?: any;
|
||||
/** 查询参数 */
|
||||
params?: any;
|
||||
}
|
||||
|
||||
/** 录制中状态(供设计器隐藏遮罩、显示录制条) */
|
||||
export const flowApiRecording = ref(false);
|
||||
|
||||
let resolver: ((c: FlowCapturedApi | null) => void) | null = null;
|
||||
|
||||
/** 开始录制,返回 Promise;捕获到请求或取消时 resolve(取消为 null) */
|
||||
export function startFlowApiRecord(): Promise<FlowCapturedApi | null> {
|
||||
// 若已有未完成的录制,先取消
|
||||
cancelFlowApiRecord();
|
||||
flowApiRecording.value = true;
|
||||
return new Promise((resolve) => {
|
||||
resolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
/** 取消录制 */
|
||||
export function cancelFlowApiRecord() {
|
||||
if (resolver) {
|
||||
const r = resolver;
|
||||
resolver = null;
|
||||
flowApiRecording.value = false;
|
||||
r(null);
|
||||
} else {
|
||||
flowApiRecording.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 由全局请求拦截器调用,上报每次请求。
|
||||
* 仅在录制中生效,默认只捕获「非 GET」业务请求(按钮动作通常是写操作),
|
||||
* 并跳过审批流自身接口,避免误捕获。
|
||||
*/
|
||||
export function notifyFlowApiRecorder(config: any) {
|
||||
if (!flowApiRecording.value || !resolver) {
|
||||
return;
|
||||
}
|
||||
const method = String(config?.method || 'get').toUpperCase();
|
||||
if (method === 'GET') {
|
||||
return;
|
||||
}
|
||||
const url = config?.url;
|
||||
if (!url || typeof url !== 'string') {
|
||||
return;
|
||||
}
|
||||
// 跳过审批流/审批办理自身接口
|
||||
if (url.includes('/approvalFlow') || url.includes('/approvalHandle') || url.includes('/sys/dict')) {
|
||||
return;
|
||||
}
|
||||
const captured: FlowCapturedApi = {
|
||||
url,
|
||||
method,
|
||||
data: safeClone(config?.data),
|
||||
params: safeClone(config?.params),
|
||||
};
|
||||
const r = resolver;
|
||||
resolver = null;
|
||||
flowApiRecording.value = false;
|
||||
r(captured);
|
||||
}
|
||||
|
||||
function safeClone(v: any) {
|
||||
if (v == null || typeof v !== 'object') {
|
||||
return v;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(v));
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,6 @@ import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { joinTimestamp, formatRequestDate } from './helper';
|
||||
import { useUserStoreWithOut } from '/@/store/modules/user';
|
||||
import { cloneDeep } from "lodash-es";
|
||||
//update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】回调接口录制-----
|
||||
import { notifyFlowApiRecorder } from '/@/utils/flowApiRecorder';
|
||||
//update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】回调接口录制-----
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix;
|
||||
const { createMessage, createErrorModal } = useMessage();
|
||||
@@ -93,9 +90,6 @@ const transform: AxiosTransform = {
|
||||
|
||||
// 请求之前处理config
|
||||
beforeRequestHook: (config, options) => {
|
||||
//update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】录制时捕获原始servlet路径(未加前缀/上下文)-----
|
||||
notifyFlowApiRecorder({ url: config.url, method: config.method, data: config.data, params: config.params });
|
||||
//update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】录制时捕获原始servlet路径(未加前缀/上下文)-----
|
||||
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
|
||||
|
||||
// http开头的请求url,不加前缀
|
||||
|
||||
@@ -17,6 +17,9 @@ enum Api {
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】当前页设计上下文-----
|
||||
designContext = '/xslmes/approvalFlow/designContext',
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】当前页设计上下文-----
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】业务表可选回调动作-----
|
||||
bizActions = '/xslmes/approvalFlow/bizActions',
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】业务表可选回调动作-----
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,3 +76,11 @@ export const batchDeleteApprovalFlow = (params, handleSuccess) => {
|
||||
*/
|
||||
export const getApprovalDesignContext = (routePath: string) => defHttp.get({ url: Api.designContext, params: { routePath } });
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】当前页设计上下文(解析阶段字段+取/建草稿流程)-----
|
||||
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】业务表可选回调动作(后端@ApprovalBizAction注解扫描)-----
|
||||
/**
|
||||
* 查询某业务表已标注 @ApprovalBizAction 的可选回调动作,供节点「回调接口」下拉选择。
|
||||
* 返回 [{ name, url, method, table, phase, perms }]
|
||||
*/
|
||||
export const getApprovalBizActions = (table: string) => defHttp.get({ url: Api.bizActions, params: { table } });
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】业务表可选回调动作-----
|
||||
|
||||
@@ -10,6 +10,9 @@ enum Api {
|
||||
status = '/xslmes/approvalHandle/status',
|
||||
approve = '/xslmes/approvalHandle/approve',
|
||||
reject = '/xslmes/approvalHandle/reject',
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人撤销-----
|
||||
cancel = '/xslmes/approvalHandle/cancel',
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人撤销-----
|
||||
}
|
||||
|
||||
/** 查看单据全部字段 + 审批进度/历史 */
|
||||
@@ -23,3 +26,8 @@ export const approveApproval = (params: { instanceId: string; comment?: string }
|
||||
|
||||
/** 驳回(需填写理由) */
|
||||
export const rejectApproval = (params: { instanceId: string; reason: string }) => defHttp.post({ url: Api.reject, data: params });
|
||||
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人撤销-----
|
||||
/** 撤销(仅发起人本人,审批中可撤回,业务单据恢复到发起时状态) */
|
||||
export const cancelApproval = (params: { instanceId: string; reason?: string }) => defHttp.post({ url: Api.cancel, data: params });
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人撤销-----
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
@date 2026-05-29 for:【QH-MES审批流设计】新增审批流可视化设计
|
||||
-->
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="modalTitle" defaultFullscreen :canFullscreen="false" :showOkBtn="!readonly" :okText="'保存并发布'" @ok="handleSave" :wrapClassName="flowApiRecording ? 'flow-design-recording-hide' : ''">
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="modalTitle" defaultFullscreen :canFullscreen="false" :showOkBtn="!readonly" :okText="'保存并发布'" @ok="handleSave">
|
||||
<div class="fd-design">
|
||||
<div class="fd-toolbar">
|
||||
<span class="fd-tb-item">绑定单据:<b>{{ record.bizTableName || record.bizTable }}</b></span>
|
||||
@@ -58,7 +58,6 @@
|
||||
} from './flowTypes';
|
||||
import type { FlowNode as FlowNodeType, NodeType, StageField } from './flowTypes';
|
||||
import { saveApprovalFlowDesign, getApprovalFlowById } from '../approvalFlow.api';
|
||||
import { flowApiRecording } from '/@/utils/flowApiRecorder';
|
||||
|
||||
defineOptions({ name: 'ApprovalFlowDesign' });
|
||||
|
||||
@@ -69,6 +68,9 @@
|
||||
const readonly = ref(false);
|
||||
const record = reactive<any>({ id: '', flowName: '', bizTable: '', bizTableName: '' });
|
||||
const drawerRef = ref();
|
||||
// 当前审批流绑定的业务表,供节点配置按表查可选回调动作
|
||||
const bizTableRef = ref('');
|
||||
provide('approvalBizTable', bizTableRef);
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】当前页识别到的候选阶段字段----- -->
|
||||
const paletteStages = ref<StageField[]>([]);
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】当前页识别到的候选阶段字段----- -->
|
||||
@@ -105,6 +107,7 @@
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
readonly.value = !!data?.readonly;
|
||||
flowCtx.readonly = readonly.value;
|
||||
bizTableRef.value = data?.record?.bizTable || '';
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】接收当前页解析出的候选阶段字段----- -->
|
||||
paletteStages.value = Array.isArray(data?.paletteStages) ? data.paletteStages : [];
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】接收当前页解析出的候选阶段字段----- -->
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
@date 2026-05-29 for:【QH-MES审批流设计】新增审批流可视化设计
|
||||
-->
|
||||
<template>
|
||||
<a-drawer :title="title" :width="480" :open="open && !flowApiRecording" @close="onClose" :maskClosable="!readonly">
|
||||
<a-drawer :title="title" :width="480" :open="open" @close="onClose" :maskClosable="!readonly">
|
||||
<template v-if="form">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="节点名称">
|
||||
@@ -80,13 +80,17 @@
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<!-- update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】节点回调接口可视化配置(录制业务按钮接口)----- -->
|
||||
<!-- update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】节点回调接口可视化配置(自动识别页面按钮)----- -->
|
||||
<a-divider style="margin: 16px 0 12px">回调接口(审批联动业务)</a-divider>
|
||||
<a-alert
|
||||
type="info"
|
||||
show-icon
|
||||
style="margin-bottom: 12px"
|
||||
message="审批到对应时机时,系统会以「当前处理人」身份调用所选业务接口(自动带上单据ID)。点击「录制」后,设计器会临时隐藏,请到页面上点击目标按钮(如“批准”),系统自动识别其接口并回填。"
|
||||
:message="
|
||||
pageActionOptions.length
|
||||
? '以下为该业务已标注(@ApprovalBizAction)的可选接口。审批到对应时机时,系统会以「当前处理人」身份调用所选接口(自动带上单据ID)。'
|
||||
: '该业务暂未标注可选接口(在后端 Controller 方法上加 @ApprovalBizAction 注解即可出现在此),也可手动填写接口路径。'
|
||||
"
|
||||
/>
|
||||
<div v-for="phase in callbackPhases" :key="phase.key" class="fd-cb-block">
|
||||
<div class="fd-cb-title">{{ phase.label }}</div>
|
||||
@@ -96,15 +100,28 @@
|
||||
<a-input v-model:value="a.url" placeholder="接口路径 /xxx" :disabled="readonly" style="flex: 1; min-width: 120px" />
|
||||
<Icon v-if="!readonly" icon="ant-design:minus-circle-outlined" class="fd-cb-del" @click="removeAction(phase.key, i)" />
|
||||
</div>
|
||||
<a-space v-if="!readonly" style="margin-top: 4px">
|
||||
<a-button size="small" @click="recordInto(phase.key)">
|
||||
<Icon icon="ant-design:aim-outlined" />
|
||||
<span>录制接口</span>
|
||||
</a-button>
|
||||
<a-space v-if="!readonly" style="margin-top: 4px" :size="6" wrap>
|
||||
<a-select
|
||||
v-if="pageActionOptions.length"
|
||||
style="width: 240px"
|
||||
placeholder="从页面按钮中选择…"
|
||||
:options="pageActionOptions"
|
||||
v-model:value="actionPicker[phase.key]"
|
||||
show-search
|
||||
option-filter-prop="label"
|
||||
@select="(v) => addFromButton(phase.key, v)"
|
||||
/>
|
||||
<a-button size="small" @click="addAction(phase.key)">手动添加</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<!-- update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】节点回调接口可视化配置(录制业务按钮接口)----- -->
|
||||
<!-- update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】驳回统一回退,无需逐节点配置----- -->
|
||||
<a-alert
|
||||
type="success"
|
||||
show-icon
|
||||
style="margin-top: 4px"
|
||||
message="驳回 / 撤销 已全局统一:系统会自动执行该业务标注为「驳回时执行(@ApprovalBizAction onReject)」的接口完成回退,无需在此逐节点、逐流程配置。"
|
||||
/>
|
||||
<!-- update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】驳回统一回退,无需逐节点配置----- -->
|
||||
</template>
|
||||
|
||||
<!-- 抄送人 -->
|
||||
@@ -173,20 +190,11 @@
|
||||
</a-space>
|
||||
</template>
|
||||
</a-drawer>
|
||||
|
||||
<!-- update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】录制中悬浮提示条----- -->
|
||||
<Teleport to="body">
|
||||
<div v-if="flowApiRecording" class="flow-record-banner">
|
||||
<Icon icon="ant-design:aim-outlined" class="flow-record-banner-icon" />
|
||||
<span class="flow-record-banner-text">录制中:请点击页面上要绑定的业务按钮(如“批准/反审核”),系统会自动识别其接口</span>
|
||||
<a-button size="small" danger @click="cancelRecord">取消录制</a-button>
|
||||
</div>
|
||||
</Teleport>
|
||||
<!-- update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】录制中悬浮提示条----- -->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, inject, watch } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { ApiSelect } from '/@/components/Form';
|
||||
@@ -194,25 +202,63 @@
|
||||
import { OPERATOR_OPTIONS } from './flowTypes';
|
||||
import type { FlowNode } from './flowTypes';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { flowApiRecording, startFlowApiRecord, cancelFlowApiRecord } from '/@/utils/flowApiRecorder';
|
||||
import { getApprovalBizActions } from '../approvalFlow.api';
|
||||
|
||||
const props = defineProps<{ readonly?: boolean }>();
|
||||
const emit = defineEmits(['confirm']);
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
// 当前审批流绑定的业务表(由 FlowDesign 注入),据此向后端查可选回调动作
|
||||
const bizTable = inject<Ref<string>>('approvalBizTable', ref(''));
|
||||
// 后端 @ApprovalBizAction 标注的可选业务动作
|
||||
const bizActions = ref<any[]>([]);
|
||||
const bizActionsTable = ref('');
|
||||
|
||||
async function loadBizActions() {
|
||||
const table = bizTable.value || '';
|
||||
if (!table || bizActionsTable.value === table) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await getApprovalBizActions(table);
|
||||
bizActions.value = Array.isArray(res) ? res : [];
|
||||
bizActionsTable.value = table;
|
||||
} catch (e) {
|
||||
bizActions.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 可选动作下拉项(含真实 url/method)
|
||||
const pageActionOptions = computed(() =>
|
||||
(bizActions.value || []).map((a) => ({
|
||||
label: `${a.name}(${a.method} ${a.url})`,
|
||||
value: a.url,
|
||||
raw: a,
|
||||
})),
|
||||
);
|
||||
|
||||
watch(
|
||||
bizTable,
|
||||
() => {
|
||||
loadBizActions();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const open = ref(false);
|
||||
const node = ref<FlowNode | null>(null);
|
||||
const form = ref<any>(null);
|
||||
// 「从页面按钮中选择」下拉的临时选中值(仅作选择器,选完即清空,避免跨节点残留上次的选项)
|
||||
const actionPicker = ref<Record<string, any>>({ onNodeApprove: undefined, onApprove: undefined, onReject: undefined });
|
||||
|
||||
const operatorOptions = OPERATOR_OPTIONS;
|
||||
const readonly = computed(() => !!props.readonly);
|
||||
|
||||
// 回调接口配置:三个触发时机
|
||||
// 回调接口配置:通过类时机需按节点配置;驳回类(onReject)已全局统一(后端按 @ApprovalBizAction 自动执行),无需在此逐节点维护
|
||||
const callbackPhases = [
|
||||
{ key: 'onNodeApprove', label: '本节点通过时执行' },
|
||||
{ key: 'onApprove', label: '流程最终通过时执行' },
|
||||
{ key: 'onReject', label: '驳回时执行' },
|
||||
];
|
||||
const methodOptions = [
|
||||
{ label: 'POST', value: 'POST' },
|
||||
@@ -230,6 +276,8 @@
|
||||
|
||||
function openDrawer(n: FlowNode) {
|
||||
node.value = n;
|
||||
// 切换节点时清空下拉选择器的残留值
|
||||
actionPicker.value = { onNodeApprove: undefined, onApprove: undefined, onReject: undefined };
|
||||
// 编辑副本,确定时回写,避免取消后脏数据
|
||||
form.value = { name: n.name, props: cloneDeep(n.props) };
|
||||
// 审批人节点确保回调接口配置结构存在
|
||||
@@ -252,17 +300,21 @@
|
||||
form.value.props.callbackActions[phaseKey].splice(i, 1);
|
||||
}
|
||||
|
||||
/** 录制:临时隐藏设计器,捕获用户点击业务按钮的请求并回填 */
|
||||
async function recordInto(phaseKey: string) {
|
||||
const cap = await startFlowApiRecord();
|
||||
if (cap) {
|
||||
form.value.props.callbackActions[phaseKey].push({ name: '', method: cap.method, url: cap.url, body: cap.data });
|
||||
createMessage.success(`已录制接口:${cap.method} ${cap.url}`);
|
||||
/** 从「已标注的业务动作」中选择一个接口加入回调(按 url 值查回原始动作) */
|
||||
function addFromButton(phaseKey: string, url: string) {
|
||||
const opt = pageActionOptions.value.find((o) => o.value === url);
|
||||
const raw = opt?.raw;
|
||||
if (!raw || !raw.url) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function cancelRecord() {
|
||||
cancelFlowApiRecord();
|
||||
const list = form.value.props.callbackActions[phaseKey];
|
||||
// 选完即清空下拉,使其回到 placeholder(仅作选择器用)
|
||||
actionPicker.value[phaseKey] = undefined;
|
||||
if (list.some((a) => a.url === raw.url)) {
|
||||
createMessage.info('该接口已添加');
|
||||
return;
|
||||
}
|
||||
list.push({ name: raw.name, method: raw.method || 'POST', url: raw.url });
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
@@ -327,33 +379,3 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 录制提示条 Teleport 到 body,需非 scoped 全局样式 -->
|
||||
<style lang="less">
|
||||
.flow-record-banner {
|
||||
position: fixed;
|
||||
top: 16px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
max-width: 92vw;
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
background: #fffbe6;
|
||||
border: 1px solid #ffe58f;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||
color: #614700;
|
||||
font-size: 13px;
|
||||
|
||||
.flow-record-banner-icon {
|
||||
color: #fa8c16;
|
||||
font-size: 18px;
|
||||
}
|
||||
.flow-record-banner-text {
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,11 +5,6 @@
|
||||
*/
|
||||
@line-color: #cacaca;
|
||||
|
||||
/* 录制回调接口时,隐藏整个设计器弹窗(含遮罩),让用户能点击业务页面上的真实按钮 */
|
||||
.flow-design-recording-hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.fd-design {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -35,6 +35,13 @@
|
||||
</template>
|
||||
<!-- 不可办理(已处理/已流转/非当前处理人):置灰提示 -->
|
||||
<span v-else-if="!props.mine && disabledText" class="im-biz-record-disabled">{{ disabledText }}</span>
|
||||
<!-- 发起人:审批中可撤销 -->
|
||||
<a-button v-if="canCancel" size="small" danger :loading="cancelling" @click="openCancel(singleItem)">
|
||||
<Icon icon="ant-design:rollback-outlined" />
|
||||
<span>撤销</span>
|
||||
</a-button>
|
||||
<!-- 发起人:审批已结束的状态提示 -->
|
||||
<span v-else-if="mineEndedText" class="im-biz-record-disabled">{{ mineEndedText }}</span>
|
||||
<a v-if="canLocate" class="im-biz-record-link" @click.prevent="handleLinkClick(singleItem.linkPath)">
|
||||
<Icon icon="ant-design:unordered-list-outlined" />
|
||||
<span>跳转至列表</span>
|
||||
@@ -90,17 +97,30 @@
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 撤销弹窗 -->
|
||||
<a-modal v-model:open="cancelOpen" title="撤销审批" :confirmLoading="cancelling" okText="确认撤销" @ok="confirmCancel">
|
||||
<a-alert type="warning" show-icon :message="'撤销后流程将终止,单据将恢复到发起审批前的状态。'" style="margin-bottom: 12px" />
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="撤销原因">
|
||||
<a-textarea v-model:value="cancelReason" :rows="3" placeholder="可选填写撤销原因" :maxlength="500" show-count />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, onMounted, watch } from 'vue';
|
||||
import { computed, ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
import type { ImBizRecordItem, ImBizRecordPayload } from './imBizRecordMessage';
|
||||
import { getImBizRecordFieldValueByLabel, resolveImBizRecordItemFields, resolveImBizRecordListColumnLabels } from './imBizRecordMessage';
|
||||
import { navigateImBizRecordLink } from './imRecordLocate';
|
||||
import { hasImBizRecordPagePermission } from './imBizRecordPermission';
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流完善】订阅会话消息更新,撤销/流转后处理人卡片按钮实时置灰-----
|
||||
import { onImMessagesUpdated } from './imCache';
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流完善】订阅会话消息更新,撤销/流转后处理人卡片按钮实时置灰-----
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { approveApproval, rejectApproval, getApprovalStatus } from '/@/views/approval/flow/approvalHandle.api';
|
||||
import { approveApproval, rejectApproval, cancelApproval, getApprovalStatus } from '/@/views/approval/flow/approvalHandle.api';
|
||||
import ImApprovalDetailModal from './ImApprovalDetailModal.vue';
|
||||
|
||||
defineOptions({ name: 'ImBizRecordMessageContent' });
|
||||
@@ -122,8 +142,14 @@
|
||||
const rejectOpen = ref(false);
|
||||
const rejectReason = ref('');
|
||||
const rejectItem = ref<ImBizRecordItem | null>(null);
|
||||
// 本地办理结果:approved / rejected(卡片消息为静态,办理后本地标记)
|
||||
const actionDone = ref<'' | 'approved' | 'rejected'>('');
|
||||
// 本地办理结果:approved / rejected / cancelled(卡片消息为静态,办理后本地标记)
|
||||
const actionDone = ref<'' | 'approved' | 'rejected' | 'cancelled'>('');
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人撤销-----
|
||||
const cancelling = ref(false);
|
||||
const cancelOpen = ref(false);
|
||||
const cancelReason = ref('');
|
||||
const cancelItem = ref<ImBizRecordItem | null>(null);
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人撤销-----
|
||||
|
||||
// 审批实例实时状态(用于旧节点卡片置灰)
|
||||
interface LiveStatus {
|
||||
@@ -184,11 +210,38 @@
|
||||
return '等待他人处理';
|
||||
});
|
||||
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人撤销-----
|
||||
// 是否可撤销:审批卡片 且 本人发起(mine) 且 审批中 且 本地未撤销
|
||||
const canCancel = computed(() => {
|
||||
if (!isApprovalCard.value || !props.mine || actionDone.value) {
|
||||
return false;
|
||||
}
|
||||
const s = liveStatus.value;
|
||||
return !!s && s.exists === true && s.status === '0';
|
||||
});
|
||||
|
||||
// 发起人视角的结束态提示
|
||||
const mineEndedText = computed(() => {
|
||||
if (!isApprovalCard.value || !props.mine) {
|
||||
return '';
|
||||
}
|
||||
if (actionDone.value === 'cancelled') return '已撤销';
|
||||
const s = liveStatus.value;
|
||||
if (!s || !s.exists) return '';
|
||||
if (s.status === '1') return '已通过';
|
||||
if (s.status === '2') return '已驳回';
|
||||
if (s.status === '3') return '已撤销';
|
||||
return '';
|
||||
});
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人撤销-----
|
||||
|
||||
async function loadLiveStatus() {
|
||||
const id = singleItem.value?.instanceId;
|
||||
if (!isApprovalCard.value || props.mine || !id) {
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人卡片也需加载状态以支持撤销-----
|
||||
if (!isApprovalCard.value || !id) {
|
||||
return;
|
||||
}
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人卡片也需加载状态以支持撤销-----
|
||||
try {
|
||||
const res: any = await getApprovalStatus(id);
|
||||
liveStatus.value = (res || { exists: false }) as LiveStatus;
|
||||
@@ -202,6 +255,33 @@
|
||||
onMounted(loadLiveStatus);
|
||||
watch(() => singleItem.value?.instanceId, loadLiveStatus);
|
||||
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流完善】订阅会话消息更新,撤销/流转后处理人卡片按钮实时置灰-----
|
||||
// 撤销/驳回/流转时后端会向处理人推送IM消息,WS到达会触发 onImMessagesUpdated,
|
||||
// 借此实时重新拉取实例状态,使本卡片的「审批/拒绝」按钮即时置灰、不可点击。
|
||||
let unsubscribeMsgUpdated: (() => void) | null = null;
|
||||
onMounted(() => {
|
||||
if (!isApprovalCard.value) {
|
||||
return;
|
||||
}
|
||||
unsubscribeMsgUpdated = onImMessagesUpdated(() => {
|
||||
// 本地已办理的卡片无需再刷新,避免覆盖本地置灰文案
|
||||
if (actionDone.value) {
|
||||
return;
|
||||
}
|
||||
// 已是终态(已通过/已驳回/已撤销/已失效)的卡片无需重复拉取
|
||||
const s = liveStatus.value;
|
||||
if (s && (s.exists === false || (!!s.status && s.status !== '0'))) {
|
||||
return;
|
||||
}
|
||||
loadLiveStatus();
|
||||
});
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
unsubscribeMsgUpdated?.();
|
||||
unsubscribeMsgUpdated = null;
|
||||
});
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流完善】订阅会话消息更新,撤销/流转后处理人卡片按钮实时置灰-----
|
||||
|
||||
const hasPagePermission = computed(() => hasImBizRecordPagePermission(props.payload.pagePath));
|
||||
// 审批卡片即使无列表页权限也应可办理/查看详情,仅"跳转至列表"受页面权限约束
|
||||
const showNoPermission = computed(() => !props.mine && !hasPagePermission.value && !isApprovalCard.value);
|
||||
@@ -231,6 +311,13 @@
|
||||
|
||||
async function handleApprove(item: ImBizRecordItem) {
|
||||
if (!item.instanceId || approving.value) return;
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流完善】办理前二次校验最新状态,撤销后防误点击-----
|
||||
await loadLiveStatus();
|
||||
if (!liveActionable.value) {
|
||||
createMessage.warning(disabledText.value || '该审批已无法办理');
|
||||
return;
|
||||
}
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流完善】办理前二次校验最新状态,撤销后防误点击-----
|
||||
try {
|
||||
approving.value = true;
|
||||
const res: any = await approveApproval({ instanceId: item.instanceId });
|
||||
@@ -257,6 +344,14 @@
|
||||
createMessage.warning('请填写驳回理由');
|
||||
return;
|
||||
}
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流完善】办理前二次校验最新状态,撤销后防误点击-----
|
||||
await loadLiveStatus();
|
||||
if (!liveActionable.value) {
|
||||
createMessage.warning(disabledText.value || '该审批已无法办理');
|
||||
rejectOpen.value = false;
|
||||
return;
|
||||
}
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流完善】办理前二次校验最新状态,撤销后防误点击-----
|
||||
try {
|
||||
rejecting.value = true;
|
||||
await rejectApproval({ instanceId: item.instanceId, reason: rejectReason.value.trim() });
|
||||
@@ -269,6 +364,30 @@
|
||||
rejecting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人撤销-----
|
||||
function openCancel(item: ImBizRecordItem) {
|
||||
cancelItem.value = item;
|
||||
cancelReason.value = '';
|
||||
cancelOpen.value = true;
|
||||
}
|
||||
|
||||
async function confirmCancel() {
|
||||
const item = cancelItem.value;
|
||||
if (!item?.instanceId || cancelling.value) return;
|
||||
try {
|
||||
cancelling.value = true;
|
||||
await cancelApproval({ instanceId: item.instanceId, reason: cancelReason.value.trim() });
|
||||
createMessage.success('已撤销,单据已恢复到发起前状态');
|
||||
actionDone.value = 'cancelled';
|
||||
cancelOpen.value = false;
|
||||
await loadLiveStatus();
|
||||
emit('handled');
|
||||
} finally {
|
||||
cancelling.value = false;
|
||||
}
|
||||
}
|
||||
// update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起人撤销-----
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
Reference in New Issue
Block a user