Files
qhmes/jeecgboot-vue3/src/views/xslmes/dingtalk/mesXslDingProcessTpl/MesXslDingProcessTplList.vue

455 lines
19 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>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<!--update-begin---author:GHT ---date:2026-06-04 forMESToDing审批配置新增审批模板创建草稿+打开设计器-->
<a-button
type="primary"
v-auth="'xslmes:mes_xsl_ding_process_tpl:add'"
preIcon="ant-design:dingtalk-outlined"
@click="handleAddNewTemplate"
>
新增审批模板
</a-button>
<!--update-end---author:GHT ---date:2026-06-04 forMESToDing审批配置新增审批模板创建草稿+打开设计器-->
<a-button v-auth="'xslmes:mes_xsl_ding_process_tpl:add'" preIcon="ant-design:plus-outlined" @click="handleAdd">
快速录入
</a-button>
<a-button type="primary" v-auth="'xslmes:mes_xsl_ding_process_tpl:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls">
导出
</a-button>
<j-upload-button type="primary" v-auth="'xslmes:mes_xsl_ding_process_tpl:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls">
导入
</j-upload-button>
<!--update-begin---author:GHT ---date:2026-06-03 forMESToDing审批配置从钉钉同步按钮-->
<a-button preIcon="ant-design:sync-outlined" :loading="syncLoading" @click="handleSyncFromDingtalk">
从钉钉同步
</a-button>
<!--update-end---author:GHT ---date:2026-06-03 forMESToDing审批配置从钉钉同步按钮-->
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="batchHandleDelete">
<Icon icon="ant-design:delete-outlined" />
删除
</a-menu-item>
</a-menu>
</template>
<a-button v-auth="'xslmes:mes_xsl_ding_process_tpl:deleteBatch'">
批量操作
<Icon icon="mdi:chevron-down" />
</a-button>
</a-dropdown>
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
</template>
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<MesXslDingProcessTplModal @register="registerModal" @success="handleSuccess" />
<!--update-begin---author:GHT ---date:2026-06-03 forMESToDing审批配置钉钉字段详情弹窗只读-->
<a-modal
v-model:open="schemaVisible"
title="钉钉模板字段详情"
width="720px"
:footer="null"
destroy-on-close
@cancel="schemaVisible = false"
>
<a-spin :spinning="schemaLoading">
<template v-if="schemaData">
<a-descriptions :column="2" bordered size="small" style="margin-bottom:14px">
<a-descriptions-item label="模板名称">{{ schemaData.tplName }}</a-descriptions-item>
<a-descriptions-item label="业务类型">{{ schemaData.bizType || '—' }}</a-descriptions-item>
<a-descriptions-item label="processCode" :span="2">
<a-typography-text v-if="schemaData.processCode" code copyable>{{ schemaData.processCode }}</a-typography-text>
<a-tag v-else color="orange">未创建</a-tag>
</a-descriptions-item>
</a-descriptions>
<a-alert v-if="schemaData.schemaError" type="warning" :message="schemaData.schemaError" show-icon style="margin-bottom:12px" />
<template v-if="schemaData.dingFields?.length">
<div style="font-weight:600;margin-bottom:8px">
钉钉表单字段
<a-tag color="blue" style="margin-left:6px;font-weight:400">{{ schemaData.dingFields.length }} </a-tag>
</div>
<a-table
:dataSource="schemaData.dingFields"
:columns="dingFieldColumns"
:pagination="false"
size="small"
:rowKey="(_, i) => i"
:scroll="{ y: 300 }"
/>
</template>
<div v-else-if="!schemaData.schemaError" style="color:#999;text-align:center;padding:20px">
未从钉钉获取到字段模板可能无 processCode 或字段为空
</div>
<a-collapse style="margin-top:14px" :bordered="false">
<a-collapse-panel key="json" header="原始 JSON 数据" style="background:#fafafa">
<pre style="font-size:12px;margin:0;max-height:200px;overflow:auto;white-space:pre-wrap;word-break:break-all">{{ JSON.stringify(schemaData, null, 2) }}</pre>
</a-collapse-panel>
</a-collapse>
</template>
</a-spin>
</a-modal>
<!--update-end---author:GHT ---date:2026-06-03 forMESToDing审批配置钉钉字段详情弹窗只读-->
<!--update-begin---author:GHT ---date:2026-06-03 forMESToDing审批配置表单设计器-->
<DingTplDesigner ref="designerRef" @success="handleSuccess" />
<DingTplCreateModal ref="createModalRef" @success="onNewTemplateCreated" />
<!--update-end---author:GHT ---date:2026-06-03 forMESToDing审批配置表单设计器-->
<!--update-begin---author:GHT ---date:2026-06-03 forMESToDing审批配置手动填表发起钉钉审批-->
<DingApprovalLaunchModal ref="launchModalRef" @success="handleLaunchSuccess" />
<!--update-end---author:GHT ---date:2026-06-03 forMESToDing审批配置手动填表发起钉钉审批-->
<!--update-begin---author:GHT ---date:20260610 forMESToDing审批配置操作列绑定审批流程-->
<BindApprovalFlowModal ref="bindFlowModalRef" @success="handleSuccess" />
<!--update-end---author:GHT ---date:20260610 forMESToDing审批配置操作列绑定审批流程-->
<!--update-begin---author:GHT ---date:2026-06-03 forMESToDing审批配置钉钉同步结果弹窗-->
<a-modal
v-model:open="syncVisible"
title="从钉钉同步审批模板"
width="780px"
:confirmLoading="importLoading"
okText="导入选中"
cancelText="取消"
@ok="handleBatchImport"
@cancel="syncVisible = false"
>
<a-spin :spinning="syncLoading">
<a-alert v-if="syncList.length === 0 && !syncLoading" message="未获取到钉钉审批模板,请确认钉钉配置及账号绑定" type="warning" show-icon style="margin-bottom:12px" />
<a-table
v-if="syncList.length > 0"
:dataSource="syncList"
:columns="syncColumns"
:rowSelection="{ type: 'checkbox', selectedRowKeys: syncSelectedKeys, onChange: onSyncSelectChange }"
:rowKey="(r) => r.processCode"
:pagination="false"
size="small"
:scroll="{ y: 380 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'imported'">
<a-tag v-if="record.linkDraft" color="orange">待回填本地</a-tag>
<a-tag v-else :color="record.imported ? 'green' : 'default'">{{ record.imported ? '已导入' : '未导入' }}</a-tag>
</template>
</template>
</a-table>
</a-spin>
</a-modal>
<!--update-end---author:GHT ---date:2026-06-03 forMESToDing审批配置钉钉同步结果弹窗-->
</div>
</template>
<script lang="ts" name="xslmes-mesXslDingProcessTpl" setup>
import { ref, reactive } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import { useMessage } from '/@/hooks/web/useMessage';
import Icon from '/@/components/Icon';
import MesXslDingProcessTplModal from './components/MesXslDingProcessTplModal.vue';
import DingTplDesigner from './components/DingTplDesigner.vue';
import DingTplCreateModal from './components/DingTplCreateModal.vue';
//update-begin---author:GHT ---date:2026-06-03 for【MESToDing审批配置】手动填表发起钉钉审批
import DingApprovalLaunchModal from './components/DingApprovalLaunchModal.vue';
//update-end---author:GHT ---date:2026-06-03 for【MESToDing审批配置】手动填表发起钉钉审批
//update-begin---author:GHT ---date:20260610 for【MESToDing审批配置】操作列绑定审批流程
import BindApprovalFlowModal from './components/BindApprovalFlowModal.vue';
//update-end---author:GHT ---date:20260610 for【MESToDing审批配置】操作列绑定审批流程
import { columns, searchFormSchema, superQuerySchema } from './MesXslDingProcessTpl.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl, syncFromDingtalk, batchImport, getTemplateDetail, toggleTplStatus } from './MesXslDingProcessTpl.api';
const { createMessage } = useMessage();
const queryParam = reactive<any>({});
const [registerModal, { openModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '钉钉审批模板配置',
api: list,
columns,
canResize: true,
//update-begin---author:GHT ---date:2026-06-10 for【钉钉审批模板】操作列加宽避免按钮挤压-----------
scroll: { x: 1700 },
//update-end---author:GHT ---date:2026-06-10 for【钉钉审批模板】操作列加宽避免按钮挤压-----------
formConfig: {
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: true,
},
actionColumn: {
title: '操作',
dataIndex: 'action',
//update-begin---author:GHT ---date:2026-06-10 for【钉钉审批模板】操作列加宽避免按钮挤压-----------
width: 540,
//update-end---author:GHT ---date:2026-06-10 for【钉钉审批模板】操作列加宽避免按钮挤压-----------
fixed: 'right',
slots: { customRender: 'action' },
},
beforeFetch: (params) => Object.assign(params, queryParam),
},
exportConfig: { name: '钉钉审批模板配置', url: getExportUrl, params: queryParam },
importConfig: { url: getImportUrl, success: handleSuccess },
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const superQueryConfig = reactive(superQuerySchema);
function handleSuperQuery(params) {
Object.keys(params).forEach((k) => (queryParam[k] = params[k]));
reload();
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });
}
const createModalRef = ref();
function handleAddNewTemplate() {
createModalRef.value?.open();
}
function onNewTemplateCreated({ record, openDesigner }: { record: Recordable; openDesigner: boolean }) {
reload();
if (openDesigner && record?.id) {
designerRef.value?.open(record);
}
}
function handleEdit(record: Recordable) {
openModal(true, { record, isUpdate: true, showFooter: true });
}
function handleDetail(record: Recordable) {
openModal(true, { record, isUpdate: true, showFooter: false });
}
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
function isTplEnabled(record: Recordable) {
return record.status === '1' || record.status === 1;
}
async function handleToggleStatus(record: Recordable) {
try {
const msg = await toggleTplStatus(record.id);
createMessage.success(typeof msg === 'string' ? msg : isTplEnabled(record) ? '已停用' : '已启用');
reload();
} catch (e: any) {
createMessage.error(e?.message || '操作失败');
}
}
function getTableAction(record) {
const enabled = isTplEnabled(record);
return [
{ label: '编辑', onClick: handleEdit.bind(null, record), auth: 'xslmes:mes_xsl_ding_process_tpl:edit' },
//update-begin---author:GHT ---date:20260610 for【钉钉审批模板】操作列停用/启用-----------
{
label: enabled ? '停用' : '启用',
color: enabled ? 'warning' : 'success',
auth: 'xslmes:mes_xsl_ding_process_tpl:edit',
popConfirm: {
title: enabled
? '停用后,已绑定该模板的业务页面将不再显示「钉钉审批」按钮,确认停用?'
: '确认启用该审批模板?启用后业务页将恢复显示钉钉审批按钮。',
confirm: handleToggleStatus.bind(null, record),
placement: 'topLeft',
},
},
//update-end---author:GHT ---date:20260610 for【钉钉审批模板】操作列停用/启用-----------
//update-begin---author:GHT ---date:20260610 for【MESToDing审批配置】操作列绑定审批流程
{
label: '绑定审批流程',
icon: 'ant-design:apartment-outlined',
auth: 'xslmes:mes_xsl_ding_process_tpl:edit',
onClick: handleBindApprovalFlow.bind(null, record),
},
//update-end---author:GHT ---date:20260610 for【MESToDing审批配置】操作列绑定审批流程
//update-begin---author:GHT ---date:2026-06-10 for【MESToDing审批配置】设计模板移至操作列、发起审批改名为测试审批
{
label: '设计模板',
icon: 'ant-design:layout-outlined',
auth: 'xslmes:mes_xsl_ding_process_tpl:edit',
onClick: handleDesignTemplate.bind(null, record),
},
{
label: '测试审批',
icon: 'ant-design:send-outlined',
color: 'success',
disabled: !enabled || !record.processCode,
tooltip: !enabled
? '模板已停用'
: record.processCode
? '手动填表后测试发起钉钉审批'
: '请先配置 processCode',
onClick: handleLaunchApproval.bind(null, record),
},
//update-end---author:GHT ---date:2026-06-10 for【MESToDing审批配置】设计模板移至操作列、发起审批改名为测试审批
];
}
function getDropDownAction(record) {
const actions: any[] = [
{ label: '详情', onClick: handleDetail.bind(null, record) },
];
if (!record.processCode) {
actions.push({
label: '创建钉钉模板',
icon: 'ant-design:dingtalk-outlined',
onClick: handleDesignTemplate.bind(null, record),
auth: 'xslmes:mes_xsl_ding_process_tpl:edit',
});
}
actions.push(
{ label: '查看钉钉字段', onClick: handleShowDingSchema.bind(null, record), icon: 'ant-design:dingtalk-outlined' },
{
label: '删除',
popConfirm: { title: '是否确认删除', confirm: handleDelete.bind(null, record), placement: 'topLeft' },
auth: 'xslmes:mes_xsl_ding_process_tpl:delete',
},
);
return actions;
}
// ===== 绑定审批流程 =====
//update-begin---author:GHT ---date:20260610 for【MESToDing审批配置】操作列绑定审批流程
const bindFlowModalRef = ref();
function handleBindApprovalFlow(record: Recordable) {
bindFlowModalRef.value?.open(record);
}
//update-end---author:GHT ---date:20260610 for【MESToDing审批配置】操作列绑定审批流程
// ===== 手动填表发起钉钉审批 =====
//update-begin---author:GHT ---date:2026-06-03 for【MESToDing审批配置】手动填表发起钉钉审批
const launchModalRef = ref();
function handleLaunchApproval(record: Recordable) {
if (!isTplEnabled(record)) {
createMessage.warning('该模板已停用,请先启用后再发起审批');
return;
}
if (!record.processCode) {
createMessage.warning('该模板尚未配置 processCode请先完成模板配置');
return;
}
launchModalRef.value?.open(record);
}
function handleLaunchSuccess() {
// 发起成功后可按需刷新列表(本期无需刷新,审批实例不在此列表)
}
//update-end---author:GHT ---date:2026-06-03 for【MESToDing审批配置】手动填表发起钉钉审批
// ===== 表单设计器 =====
const designerRef = ref();
function handleDesignTemplate(record: Recordable) {
designerRef.value?.open(record);
}
// ===== 钉钉字段详情(只读 schema 查看器)=====
const schemaVisible = ref(false);
const schemaLoading = ref(false);
const schemaData = ref<any>(null);
const dingFieldColumns = [
{ title: '控件标题(钉钉字段名)', dataIndex: 'label' },
{ title: '控件类型', dataIndex: 'componentName', width: 160 },
{ title: '必填', dataIndex: 'required', width: 70 },
];
async function handleShowDingSchema(record: Recordable) {
schemaVisible.value = true;
schemaLoading.value = true;
schemaData.value = null;
try {
schemaData.value = await getTemplateDetail(record.id);
} catch (e: any) {
createMessage.error(e?.message || '获取模板字段失败');
schemaVisible.value = false;
} finally {
schemaLoading.value = false;
}
}
// ===== 从钉钉同步 =====
const syncVisible = ref(false);
const syncLoading = ref(false);
const importLoading = ref(false);
const syncList = ref<any[]>([]);
const syncSelectedKeys = ref<string[]>([]);
const syncColumns = [
{ title: '模板名称', dataIndex: 'name', width: 200 },
{ title: 'processCode', dataIndex: 'processCode', width: 300 },
{ title: '描述', dataIndex: 'description', ellipsis: true },
{ title: '状态', dataIndex: 'imported', width: 90 },
];
async function handleSyncFromDingtalk() {
syncVisible.value = true;
syncLoading.value = true;
syncList.value = [];
syncSelectedKeys.value = [];
try {
const data = await syncFromDingtalk();
syncList.value = data || [];
syncSelectedKeys.value = (syncList.value as any[])
.filter((r) => !r.imported || r.linkDraft)
.map((r) => r.processCode);
} catch (e: any) {
createMessage.error(e?.message || '从钉钉同步失败');
syncVisible.value = false;
} finally {
syncLoading.value = false;
}
}
function onSyncSelectChange(keys: string[]) {
syncSelectedKeys.value = keys;
}
async function handleBatchImport() {
if (syncSelectedKeys.value.length === 0) {
createMessage.warning('请勾选要导入的模板');
return;
}
const selected = syncList.value.filter((r) => syncSelectedKeys.value.includes(r.processCode));
importLoading.value = true;
try {
const msg = await batchImport(selected);
createMessage.success(typeof msg === 'string' ? msg : '导入成功');
syncVisible.value = false;
reload();
} catch (e: any) {
createMessage.error(e?.message || '批量导入失败');
} finally {
importLoading.value = false;
}
}
</script>
<style lang="less" scoped>
:deep(.ant-picker-range) {
width: 100%;
}
</style>