295 lines
9.1 KiB
Vue
295 lines
9.1 KiB
Vue
<!--
|
||
全局「发起审批」悬浮按钮
|
||
与「钉钉审批」按钮一致:当前页存在 mes_xsl_ding_tpl_bind 绑定且钉钉模板启用时显示;
|
||
弹窗内可选该页业务表下已发布的 MES 审批流。
|
||
支持两种发起方式:
|
||
1)列表多选联动:在列表勾选数据后点击,弹窗自动带入选中单据并可批量发起;
|
||
2)手动选择:未勾选时,在弹窗内搜索选择单条单据发起。
|
||
@author GHT
|
||
@date 2026-05-29 for:【QH-MES审批流设计】发起审批运行时
|
||
-->
|
||
<template>
|
||
<div v-if="show" class="approval-float" :style="floatStyle">
|
||
<div class="approval-float-btn" title="发起审批" @click="openModal">
|
||
<Icon icon="ant-design:audit-outlined" :size="20" />
|
||
<span class="approval-float-text">发起审批</span>
|
||
</div>
|
||
|
||
<a-modal v-model:open="visible" title="发起审批" :width="540" :confirmLoading="loading" okText="发起审批" @ok="handleLaunch">
|
||
<a-form layout="vertical" style="margin-top: 8px">
|
||
<a-form-item label="单据类型(审批流)" required>
|
||
<a-select v-model:value="flowId" placeholder="请选择审批流" :options="flowOptions" @change="onFlowChange" allowClear />
|
||
</a-form-item>
|
||
|
||
<!-- 批量模式:直接展示列表勾选的单据 -->
|
||
<a-form-item v-if="isBatch" :label="`已选单据(共 ${batchItems.length} 条)`" required>
|
||
<div class="approval-float-batch">
|
||
<div v-for="it in batchItems" :key="it.bizDataId" class="approval-float-batch-item">
|
||
<Icon icon="ant-design:file-text-outlined" :size="14" />
|
||
<span class="approval-float-batch-title">{{ it.bizTitle }}</span>
|
||
</div>
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<!-- 手动模式:搜索选择单条单据 -->
|
||
<a-form-item v-else label="选择单据" required>
|
||
<a-select
|
||
v-model:value="bizDataId"
|
||
show-search
|
||
placeholder="请选择需要发起审批的单据"
|
||
:filter-option="false"
|
||
:options="recordOptions"
|
||
:disabled="!flowId"
|
||
@search="onSearch"
|
||
@change="onRecordChange"
|
||
>
|
||
<template #notFoundContent>
|
||
<a-spin v-if="recordLoading" size="small" />
|
||
<span v-else>无单据数据</span>
|
||
</template>
|
||
</a-select>
|
||
<div v-if="flowId && !recordOptions.length && !recordLoading" class="approval-float-tip">
|
||
该单据暂无数据,或审批流未配置“单据标题字段”
|
||
</div>
|
||
</a-form-item>
|
||
</a-form>
|
||
</a-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { computed, reactive, ref, watch } from 'vue';
|
||
import { useRouter } from 'vue-router';
|
||
import { debounce } from 'lodash-es';
|
||
import { useMessage } from '/@/hooks/web/useMessage';
|
||
import { getPublishedFlows, getBizRecords, launchApproval, launchApprovalBatch } from '/@/views/approval/flow/launch.api';
|
||
import { getBindingByRoute } from '/@/views/xslmes/dingtalk/dingTplBind/dingTplBind.api';
|
||
import { useApprovalSelection } from './useApprovalSelection';
|
||
|
||
defineOptions({ name: 'ApprovalLaunchFloat' });
|
||
|
||
const { createMessage } = useMessage();
|
||
const { currentRoute } = useRouter();
|
||
const approvalSelection = useApprovalSelection();
|
||
|
||
const visible = ref(false);
|
||
const loading = ref(false);
|
||
const recordLoading = ref(false);
|
||
|
||
const flowId = ref<string>();
|
||
const bizDataId = ref<string>();
|
||
const bizTitle = ref<string>('');
|
||
|
||
const binding = ref<any>(null);
|
||
const flowList = ref<any[]>([]);
|
||
const recordList = ref<any[]>([]);
|
||
// 打开弹窗时快照的列表勾选行(批量模式数据源)
|
||
const batchRows = ref<any[]>([]);
|
||
|
||
// 悬浮位置(右下角)
|
||
const floatStyle = reactive({ right: '24px', bottom: '120px' });
|
||
|
||
// 与钉钉审批按钮一致:按 mes_xsl_ding_tpl_bind + 路由解析是否显示
|
||
const show = computed(() => !!binding.value);
|
||
|
||
const matchedFlows = computed(() => flowList.value);
|
||
|
||
const flowOptions = computed(() =>
|
||
matchedFlows.value.map((f) => ({
|
||
label: `${f.flowName}(${f.bizTableName || f.bizTable})`,
|
||
value: f.id,
|
||
}))
|
||
);
|
||
|
||
// 当前选中的审批流对象
|
||
const currentFlow = computed(() => matchedFlows.value.find((f) => f.id === flowId.value));
|
||
|
||
// 是否批量模式(列表有勾选)
|
||
const isBatch = computed(() => batchRows.value.length > 0);
|
||
|
||
// 批量模式下的单据项(用审批流的标题字段取展示标题)
|
||
const batchItems = computed(() => {
|
||
const titleField = currentFlow.value?.titleField;
|
||
return batchRows.value
|
||
.filter((r) => r && r.id != null)
|
||
.map((r) => ({
|
||
bizDataId: String(r.id),
|
||
bizTitle: titleField && r[titleField] != null ? String(r[titleField]) : String(r.id),
|
||
}));
|
||
});
|
||
|
||
const recordOptions = computed(() =>
|
||
recordList.value.map((r) => ({
|
||
label: r.title ?? r.id,
|
||
value: r.id,
|
||
}))
|
||
);
|
||
|
||
watch(
|
||
() => currentRoute.value?.path,
|
||
async (path) => {
|
||
binding.value = null;
|
||
flowList.value = [];
|
||
if (!path || path === '/' || path.startsWith('/login')) {
|
||
return;
|
||
}
|
||
try {
|
||
const bind = await getBindingByRoute(path);
|
||
binding.value = bind || null;
|
||
if (binding.value) {
|
||
flowList.value = (await getPublishedFlows(path)) || [];
|
||
}
|
||
} catch {
|
||
binding.value = null;
|
||
flowList.value = [];
|
||
}
|
||
},
|
||
{ immediate: true },
|
||
);
|
||
|
||
function resetSelection() {
|
||
flowId.value = undefined;
|
||
bizDataId.value = undefined;
|
||
bizTitle.value = '';
|
||
recordList.value = [];
|
||
batchRows.value = [];
|
||
}
|
||
|
||
async function openModal() {
|
||
if (!matchedFlows.value.length) {
|
||
createMessage.warning('当前页面暂无已发布的 MES 审批流,请先在审批流设计中发布');
|
||
return;
|
||
}
|
||
visible.value = true;
|
||
resetSelection();
|
||
// 读取当前列表页的勾选行
|
||
batchRows.value = approvalSelection.getRowsByPath(currentRoute.value?.path || '');
|
||
// 当前页只匹配到一个审批流时自动选中
|
||
if (matchedFlows.value.length === 1) {
|
||
flowId.value = matchedFlows.value[0].id;
|
||
}
|
||
// 手动模式且已确定审批流时预加载单据列表
|
||
if (!isBatch.value && flowId.value) {
|
||
await loadRecords();
|
||
}
|
||
}
|
||
|
||
async function loadRecords(keyword?: string) {
|
||
if (!flowId.value) return;
|
||
try {
|
||
recordLoading.value = true;
|
||
recordList.value = (await getBizRecords({ flowId: flowId.value, keyword })) || [];
|
||
} finally {
|
||
recordLoading.value = false;
|
||
}
|
||
}
|
||
|
||
function onFlowChange() {
|
||
bizDataId.value = undefined;
|
||
bizTitle.value = '';
|
||
recordList.value = [];
|
||
if (!isBatch.value && flowId.value) loadRecords();
|
||
}
|
||
|
||
const onSearch = debounce((val: string) => {
|
||
loadRecords(val);
|
||
}, 350);
|
||
|
||
function onRecordChange() {
|
||
const hit = recordList.value.find((r) => r.id === bizDataId.value);
|
||
bizTitle.value = hit ? hit.title ?? hit.id : '';
|
||
}
|
||
|
||
async function handleLaunch() {
|
||
if (!flowId.value) {
|
||
createMessage.warning('请选择审批流');
|
||
return;
|
||
}
|
||
try {
|
||
loading.value = true;
|
||
if (isBatch.value) {
|
||
await launchApprovalBatch({ flowId: flowId.value, items: batchItems.value });
|
||
createMessage.success(`已发起 ${batchItems.value.length} 条审批!`);
|
||
} else {
|
||
if (!bizDataId.value) {
|
||
createMessage.warning('请选择需要发起审批的单据');
|
||
return;
|
||
}
|
||
await launchApproval({ flowId: flowId.value, bizDataId: bizDataId.value, bizTitle: bizTitle.value });
|
||
createMessage.success('发起成功!');
|
||
}
|
||
visible.value = false;
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.approval-float {
|
||
position: fixed;
|
||
z-index: 999;
|
||
|
||
.approval-float-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 2px;
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #3296fa, #1668dc);
|
||
color: #fff;
|
||
cursor: pointer;
|
||
box-shadow: 0 4px 14px rgba(50, 150, 250, 0.45);
|
||
transition: transform 0.18s, box-shadow 0.18s;
|
||
user-select: none;
|
||
|
||
.approval-float-text {
|
||
font-size: 11px;
|
||
line-height: 1;
|
||
transform: scale(0.92);
|
||
}
|
||
|
||
&:hover {
|
||
transform: scale(1.08);
|
||
box-shadow: 0 6px 20px rgba(50, 150, 250, 0.6);
|
||
}
|
||
}
|
||
}
|
||
|
||
.approval-float-batch {
|
||
max-height: 220px;
|
||
overflow-y: auto;
|
||
border: 1px solid #f0f0f0;
|
||
border-radius: 6px;
|
||
padding: 6px 8px;
|
||
|
||
.approval-float-batch-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 4px 2px;
|
||
font-size: 13px;
|
||
border-bottom: 1px dashed #f0f0f0;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.approval-float-batch-title {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
}
|
||
|
||
.approval-float-tip {
|
||
margin-top: 6px;
|
||
font-size: 12px;
|
||
color: #faad14;
|
||
}
|
||
</style>
|