钉钉回调事件处理

This commit is contained in:
geht
2026-06-09 17:52:33 +08:00
parent fd5205e33e
commit 5b8bd2797a
50 changed files with 2861 additions and 428 deletions

View File

@@ -11,6 +11,9 @@ import { useDesign } from '/@/hooks/web/useDesign';
import { filterObj } from '/@/utils/common/compUtils';
import { isFunction } from '@/utils/is';
import { registerImPageListProvider } from '/@/views/system/im/imPageListRegistry';
//update-begin---author:GHT ---date:20260609 for【审批注册中心】列表页静态注入审批痕迹列默认隐藏-----------
import { traceColumns } from '/@/views/xslmes/approval/integration/traceColumns';
//update-end---author:GHT ---date:20260609 for【审批注册中心】列表页静态注入审批痕迹列默认隐藏-----------
//update-begin---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】列表选中行同步到审批发起上下文-----
import { useApprovalSelection } from '/@/components/ApprovalLaunch/useApprovalSelection';
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】列表选中行同步到审批发起上下文-----
@@ -573,6 +576,14 @@ export function useListTable(tableProps: TableProps): [
}
}
//update-begin---author:GHT ---date:20260609 for【审批注册中心】静态追加审批痕迹列默认隐藏无需注册中心判断-----------
const existingCols: any[] = (defaultTableProps.columns as any[]) ?? [];
const hasTrace = existingCols.some((c) => String(c.dataIndex ?? '').startsWith('trace'));
if (!hasTrace) {
defaultTableProps.columns = [...existingCols, ...traceColumns];
}
//update-end---author:GHT ---date:20260609 for【审批注册中心】静态追加审批痕迹列默认隐藏无需注册中心判断-----------
return [
...useTable(defaultTableProps),
{

View File

@@ -383,6 +383,10 @@
};
// 清空历史 HTTP 回调配置,避免与集成方案双写
form.value.props.callbackActions = { onNodeApprove: [], onApprove: [], onReject: [] };
// update-begin---author:GHT ---date:2026-06-09 for【审批流设计】打开节点配置时强制刷新集成方案下拉避免先开设计器后生成方案导致缓存空值-----
integrationPlansCacheKey.value = '';
loadIntegrationPlans();
// update-end---author:GHT ---date:2026-06-09 for【审批流设计】打开节点配置时强制刷新集成方案下拉避免先开设计器后生成方案导致缓存空值-----
}
open.value = true;
}

View File

@@ -2,13 +2,6 @@ import { BasicColumn, FormSchema } from '/@/components/Table';
const STAGE_DICT = 'mes_xsl_approval_stage';
function hasStage(values: Recordable, stage: string) {
const raw = values?.enabledStages;
if (!raw) return false;
if (Array.isArray(raw)) return raw.includes(stage);
return String(raw).split(',').includes(stage);
}
export const columns: BasicColumn[] = [
{ title: '业务编码', dataIndex: 'docCode', width: 140, align: 'left' },
{ title: '物理表名', dataIndex: 'tableName', width: 200, align: 'left' },
@@ -84,46 +77,11 @@ export const formSchema: FormSchema[] = [
componentProps: { placeholder: '默认 status' },
},
{
label: '校对人字段',
field: 'proofreadByField',
label: '列表接口路径',
field: 'listApiPath',
component: 'Input',
defaultValue: 'proofread_by',
ifShow: ({ values }) => hasStage(values, 'proofread'),
},
{
label: '校对时间字段',
field: 'proofreadTimeField',
component: 'Input',
defaultValue: 'proofread_time',
ifShow: ({ values }) => hasStage(values, 'proofread'),
},
{
label: '审核人字段',
field: 'auditByField',
component: 'Input',
defaultValue: 'audit_by',
ifShow: ({ values }) => hasStage(values, 'audit'),
},
{
label: '审核时间字段',
field: 'auditTimeField',
component: 'Input',
defaultValue: 'audit_time',
ifShow: ({ values }) => hasStage(values, 'audit'),
},
{
label: '批准人字段',
field: 'approveByField',
component: 'Input',
defaultValue: 'approve_by',
ifShow: ({ values }) => hasStage(values, 'approve'),
},
{
label: '批准时间字段',
field: 'approveTimeField',
component: 'Input',
defaultValue: 'approve_time',
ifShow: ({ values }) => hasStage(values, 'approve'),
slot: 'listApiPath',
helpMessage: '从二级菜单选取后自动填入路径;配置后列表响应自动追加 traceProofreadBy / traceAuditBy / traceApproveBy 等6个字段',
},
{
label: '备注',

View File

@@ -122,6 +122,7 @@
{ title: '流程节点', dataIndex: 'nodeName', width: 200 },
{ title: '识别环节', dataIndex: 'stage', width: 130 },
{ title: '前置状态', dataIndex: 'expectedFromLabel', width: 90 },
{ title: '通过后状态', dataIndex: 'statusAfterLabel', width: 100 },
{ title: '生成方案', dataIndex: 'willGenerate', width: 88 },
{ title: '未配置原因', dataIndex: 'unconfiguredReason', ellipsis: true },
];
@@ -182,6 +183,12 @@
return '环节未完整配置';
}
function resolveStatusAfter(stage?: string, statusChain?: any[]) {
if (!stage) return null;
const hit = (statusChain || []).find((item) => item.value === stage);
return hit ? stage : null;
}
function resolveExpectedFrom(bindings: any[], index: number, statusChain: any[], initialStatus: string) {
const current = bindings[index];
if (!current?.stage) {
@@ -226,6 +233,8 @@
record.triggerPhase = null;
record.expectedFrom = null;
record.expectedFromLabel = '-';
record.statusAfter = null;
record.statusAfterLabel = '-';
return;
}
const cfgIdx = configuredBindings.indexOf(record);
@@ -234,6 +243,9 @@
const expectedFrom = resolveExpectedFrom(bindings, bindings.indexOf(record), statusChain, initialStatus);
record.expectedFrom = expectedFrom;
record.expectedFromLabel = labelOfStatusChain(statusChain, expectedFrom);
const statusAfter = resolveStatusAfter(record.stage, statusChain);
record.statusAfter = statusAfter;
record.statusAfterLabel = statusAfter ? labelOfStatusChain(statusChain, statusAfter) : '需手配';
});
preview.value.configuredNodeCount = configuredBindings.length;

View File

@@ -1,6 +1,10 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="640" @ok="handleSubmit">
<BasicForm @register="registerForm" />
<BasicForm @register="registerForm">
<template #listApiPath="{ model, field }">
<RegistryMenuSelect :value="model[field]" @change="(val) => (model[field] = val)" />
</template>
</BasicForm>
</BasicModal>
</template>
@@ -10,6 +14,7 @@
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../MesXslBizDocRegistry.data';
import { saveOrUpdate } from '../MesXslBizDocRegistry.api';
import RegistryMenuSelect from './RegistryMenuSelect.vue';
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(false);

View File

@@ -96,10 +96,11 @@
if (record.actionType === 'REGISTRY_STAGE_SYNC') {
const stage = cfg.registryStage?.stage || cfg.stage;
const from = cfg.registryStage?.expectedFrom || cfg.expectedFrom;
return `环节→${STAGE_LABELS[stage] || stage || '?'}${from ? `,前置=${from}` : ''}`;
const after = cfg.registryStage?.statusAfter || cfg.statusAfter || stage;
return `环节→${STAGE_LABELS[stage] || stage || '?'}${from ? `,前置=${from}` : ''},通过后=${after}`;
}
const target = cfg.registryStage?.targetStage || cfg.targetStage || 'compile';
return `回退→${target}`;
const target = cfg.registryStage?.targetStage ?? cfg.targetStage;
return target !== undefined && target !== null && target !== '' ? `回退→${target}` : '回退→未配置';
} catch {
return record.actionConfig || '-';
}

View File

@@ -0,0 +1,120 @@
<template>
<div>
<a-select
:value="selectedPaths"
mode="multiple"
allow-clear
show-search
placeholder="从二级菜单选取,自动填入列表接口路径;也可在下方直接编辑"
:filter-option="filterOption"
style="width: 100%"
@change="handleSelectChange"
>
<a-select-opt-group v-for="group in menuGroups" :key="group.path" :label="group.title">
<a-select-option
v-for="item in group.children"
:key="item.apiPath"
:value="item.apiPath"
:title="item.apiPath"
>
<span>{{ item.title }}</span>
<span style="margin-left: 8px; color: #8c8c8c; font-size: 11px">{{ item.apiPath }}</span>
</a-select-option>
</a-select-opt-group>
</a-select>
<a-input
:value="inputVal"
style="margin-top: 6px"
placeholder="列表接口路径(多个逗号分隔,可手动编辑)"
@change="handleInputChange"
/>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { usePermissionStoreWithOut } from '/@/store/modules/permission';
import type { Menu } from '/@/router/types';
interface MenuGroup {
path: string;
title: string;
children: { apiPath: string; title: string; path: string }[];
}
const props = defineProps<{ value?: string }>();
const emit = defineEmits(['change', 'update:value']);
const permStore = usePermissionStoreWithOut();
const menuGroups = computed<MenuGroup[]>(() => {
const list = permStore.getBackMenuList as Menu[];
if (!list || list.length === 0) return [];
const groups: MenuGroup[] = [];
for (const parent of list) {
if (!parent.children || parent.children.length === 0) continue;
const leafChildren = parent.children.filter((c) => !c.hideMenu);
if (leafChildren.length === 0) continue;
groups.push({
path: parent.path,
title: (parent.meta?.title as string) || parent.name,
children: leafChildren.map((c) => ({
path: c.path,
apiPath: c.path + '/list',
title: (c.meta?.title as string) || c.name,
})),
});
}
return groups;
});
const allApiPaths = computed<string[]>(() =>
menuGroups.value.flatMap((g) => g.children.map((c) => c.apiPath)),
);
// 当前文本框的值(原始逗号分隔串)
const inputVal = ref(props.value || '');
// 从文本框值解析出在 menuGroups 里的路径(用于 Select 的高亮选中)
const selectedPaths = computed<string[]>(() => {
if (!inputVal.value) return [];
return inputVal.value
.split(',')
.map((p) => p.trim())
.filter((p) => allApiPaths.value.includes(p));
});
watch(
() => props.value,
(val) => {
inputVal.value = val || '';
},
);
function handleSelectChange(vals: string[]) {
// 将已有的手动路径(不在菜单里的)保留,把菜单选取结果合并进去
const manualPaths = inputVal.value
.split(',')
.map((p) => p.trim())
.filter((p) => p && !allApiPaths.value.includes(p));
const merged = [...manualPaths, ...vals].filter(Boolean).join(',');
inputVal.value = merged;
emit('change', merged);
emit('update:value', merged);
}
function handleInputChange(e: Event) {
const val = (e.target as HTMLInputElement).value;
inputVal.value = val;
emit('change', val);
emit('update:value', val);
}
function filterOption(input: string, option: any) {
const title = option?.children?.[0]?.children || '';
const path = option.value || '';
const lc = input.toLowerCase();
return String(title).toLowerCase().includes(lc) || String(path).toLowerCase().includes(lc);
}
</script>

View File

@@ -58,7 +58,7 @@
type="info"
show-icon
style="margin-bottom: 14px"
message="审批注册中心配置自动更新源单状态、操作人/时间,并双写审批痕迹明细,无需绑定 Java 校对/审核/批准接口。"
message="审批环节仅用于匹配审批流与写入痕迹;业务表 status 由「通过后状态」控制。操作人/时间写入痕迹表,无需绑定 Java 接口。"
/>
<template v-if="vc.registryStage">
<a-form-item v-if="vc.visualType === 'REGISTRY_STAGE_SYNC'" label="审批环节" required>
@@ -73,6 +73,9 @@
<div v-if="!registryStageOptions.length" style="font-size: 12px; color: #faad14; margin-top: 4px">
未配置启用环节请先在审批注册中心配置 enabled_stages
</div>
<div v-else style="font-size: 12px; color: #888; margin-top: 4px">
仅参与审批流匹配与痕迹同步proofread/audit/approve不会直接写入业务表 status
</div>
</a-form-item>
<a-form-item v-if="vc.visualType === 'REGISTRY_STAGE_SYNC'" label="前置状态">
<a-select
@@ -88,24 +91,45 @@
<a-input
v-else
v-model:value="vc.registryStage.expectedFrom"
placeholder="未解析到状态字典,可手填 compile / proofread / audit"
placeholder="未解析到状态字典,可手填状态字典值"
/>
<div style="font-size: 12px; color: #888; margin-top: 4px">
取自触发表{{ sourceStatusFieldName }}字段字典{{ sourceStatusDictCode ? `${sourceStatusDictCode}` : '' }}留空则自动推断仅当前状态等于此前置值时才执行
</div>
</a-form-item>
<a-form-item v-if="vc.visualType === 'REGISTRY_STAGE_REVERT'" label="回退目标">
<a-form-item v-if="vc.visualType === 'REGISTRY_STAGE_SYNC'" label="通过后状态" required>
<a-select
v-if="sourceStatusDictItems.length"
v-model:value="vc.registryStage.targetStage"
v-model:value="vc.registryStage.statusAfter"
:options="sourceStatusDictItems"
placeholder="默认 compile编制态"
allow-clear
placeholder="请选择本环节通过后业务表应变为的状态"
show-search
option-filter-prop="label"
style="width: 100%"
/>
<a-input v-else v-model:value="vc.registryStage.targetStage" placeholder="默认 compile编制态" />
<a-input
v-else
v-model:value="vc.registryStage.statusAfter"
placeholder="未解析到状态字典,可手填业务状态值"
/>
<div style="font-size: 12px; color: #888; margin-top: 4px">
本环节审批通过后将触发表{{ sourceStatusFieldName }}更新为此值与审批环节码无关按各单据自己的状态字典配置
</div>
</a-form-item>
<a-form-item v-if="vc.visualType === 'REGISTRY_STAGE_REVERT'" label="回退目标" required>
<a-select
v-if="sourceStatusDictItems.length"
v-model:value="vc.registryStage.targetStage"
:options="sourceStatusDictItems"
placeholder="请选择驳回后回退到的业务状态"
show-search
option-filter-prop="label"
style="width: 100%"
/>
<a-input v-else v-model:value="vc.registryStage.targetStage" placeholder="未解析到状态字典可手填字典键值item_value" />
<div style="font-size: 12px; color: #888; margin-top: 4px">
保存为字典键值item_value执行时原样写入触发表{{ sourceStatusFieldName }}与界面显示文字无关
</div>
</a-form-item>
</template>
</template>
@@ -305,6 +329,7 @@
interface RegistryStageConfig {
stage?: string;
expectedFrom?: string;
statusAfter?: string;
targetStage?: string;
}
@@ -321,10 +346,18 @@
const emit = defineEmits<{ success: [action: any] }>();
const { createMessage } = useMessage();
/** 审批环节码固定中文名(与业务 status 字典无关) */
const APPROVAL_STAGE_LABELS: Record<string, string> = {
proofread: '校对',
audit: '审核',
approve: '批准',
};
/** 触发表 status 字段未带字典注释时的兜底映射 */
const SOURCE_TABLE_STATUS_DICT: Record<string, string> = {
mes_xsl_mixer_ps_compile: 'xslmes_mixer_ps_status',
mes_xsl_formula_spec: 'xslmes_formula_spec_status',
mes_xsl_raw_material_entry: 'xslmes_entry_status',
};
/** 目标表 status 字段未带字典注释时的兜底映射 */
@@ -362,7 +395,7 @@
.split(',')
.map((s) => s.trim())
.filter(Boolean)
.map((v) => ({ value: v, label: dictLabelMap[v] || v }));
.map((v) => ({ value: v, label: APPROVAL_STAGE_LABELS[v] || dictLabelMap[v] || v }));
});
const targetColumns = ref<ColMeta[]>([]);
@@ -387,6 +420,7 @@
const defaultRegistryStage = (): RegistryStageConfig => ({
stage: '',
expectedFrom: '',
statusAfter: '',
targetStage: '',
});
@@ -428,7 +462,12 @@
if (parsed.expectedFrom !== undefined && parsed.expectedFrom !== null) {
merged.registryStage!.expectedFrom = parsed.expectedFrom;
}
if (parsed.targetStage) merged.registryStage!.targetStage = parsed.targetStage;
if (parsed.statusAfter !== undefined && parsed.statusAfter !== null) {
merged.registryStage!.statusAfter = parsed.statusAfter;
}
if (parsed.targetStage !== undefined && parsed.targetStage !== null) {
merged.registryStage!.targetStage = parsed.targetStage;
}
return merged;
}
@@ -461,7 +500,7 @@
},
);
// 审批环节变化时,前置状态留空则填入默认推断值
// 审批环节变化时,前置/通过后状态留空则填入默认推断值
watch(
() => vc.value.registryStage?.stage,
(stage) => {
@@ -469,6 +508,9 @@
if (!vc.value.registryStage.expectedFrom) {
vc.value.registryStage.expectedFrom = defaultExpectedFromForStage(stage);
}
if (!vc.value.registryStage.statusAfter) {
vc.value.registryStage.statusAfter = defaultStatusAfterForStage(stage);
}
},
);
@@ -548,6 +590,16 @@
return items.length ? items[0].value : '';
}
/** 推断通过后业务状态:字典含环节码时用环节码,否则不自动填充(需用户手选) */
function defaultStatusAfterForStage(stage?: string): string {
if (!stage) return '';
const items = sourceStatusDictItems.value;
if (items.some((i) => i.value === stage)) {
return stage;
}
return '';
}
function defaultRevertTargetStage(): string {
const items = sourceStatusDictItems.value;
if (!items.length) return 'compile';
@@ -565,7 +617,12 @@
if (config.registryStage?.expectedFrom !== undefined && config.registryStage?.expectedFrom !== null) {
payload.expectedFrom = config.registryStage.expectedFrom;
}
if (config.registryStage?.targetStage) payload.targetStage = config.registryStage.targetStage;
if (config.registryStage?.statusAfter !== undefined && config.registryStage?.statusAfter !== null) {
payload.statusAfter = config.registryStage.statusAfter;
}
if (config.registryStage?.targetStage !== undefined && config.registryStage?.targetStage !== null) {
payload.targetStage = config.registryStage.targetStage;
}
return JSON.stringify(payload);
}
@@ -607,6 +664,7 @@
if (type === 'REGISTRY_STAGE_SYNC' && registryStageOptions.value.length && !vc.value.registryStage?.stage) {
vc.value.registryStage!.stage = registryStageOptions.value[0].value;
vc.value.registryStage!.expectedFrom = defaultExpectedFromForStage(vc.value.registryStage!.stage);
vc.value.registryStage!.statusAfter = defaultStatusAfterForStage(vc.value.registryStage!.stage);
}
if (type === 'REGISTRY_STAGE_REVERT' && !vc.value.registryStage?.targetStage) {
vc.value.registryStage!.targetStage = defaultRevertTargetStage();
@@ -687,6 +745,7 @@
if (registryStageOptions.value.length) {
vc.value.registryStage!.stage = registryStageOptions.value[0].value;
vc.value.registryStage!.expectedFrom = defaultExpectedFromForStage(vc.value.registryStage!.stage);
vc.value.registryStage!.statusAfter = defaultStatusAfterForStage(vc.value.registryStage!.stage);
}
vc.value.registryStage!.targetStage = defaultRevertTargetStage();
formRef.value?.clearValidate?.();
@@ -702,6 +761,10 @@
createMessage.warning('请选择审批环节');
return;
}
if (!vc.value.registryStage?.statusAfter) {
createMessage.warning('请选择通过后状态');
return;
}
emit('success', {
...form.value,
actionType: 'REGISTRY_STAGE_SYNC',
@@ -712,6 +775,10 @@
return;
}
if (vc.value.visualType === 'REGISTRY_STAGE_REVERT') {
if (vc.value.registryStage?.targetStage === undefined || vc.value.registryStage?.targetStage === null || vc.value.registryStage?.targetStage === '') {
createMessage.warning('请选择回退目标(业务状态字典项)');
return;
}
emit('success', {
...form.value,
actionType: 'REGISTRY_STAGE_REVERT',
@@ -777,10 +844,19 @@
}
await loadSourceStatusDict();
// 旧数据兼容:未配置 statusAfter 时,若字典含环节码则回填,否则保持空由用户手选
if (isUpdate.value && vc.value.registryStage?.stage && !vc.value.registryStage?.statusAfter) {
const legacy = defaultStatusAfterForStage(vc.value.registryStage.stage);
if (legacy) {
vc.value.registryStage.statusAfter = legacy;
}
}
if (!isUpdate.value) {
if (registryStageOptions.value.length && !vc.value.registryStage?.stage) {
vc.value.registryStage!.stage = registryStageOptions.value[0].value;
vc.value.registryStage!.expectedFrom = defaultExpectedFromForStage(vc.value.registryStage!.stage);
vc.value.registryStage!.statusAfter = defaultStatusAfterForStage(vc.value.registryStage!.stage);
}
if (!vc.value.registryStage?.targetStage) {
vc.value.registryStage!.targetStage = defaultRevertTargetStage();

View File

@@ -0,0 +1,24 @@
import { BasicColumn } from '/@/components/Table';
/**
* 按 sentinel key 分组的审批痕迹列。
* key = 后端注入的字段名traceProofreadBy / traceAuditBy / traceApproveBy
* 只要响应里出现该 key就注入对应两列。
*/
export const traceColumnsByStage: Record<string, BasicColumn[]> = {
traceProofreadBy: [
{ title: '校对人', dataIndex: 'traceProofreadBy', width: 100, align: 'center', defaultHidden: true },
{ title: '校对时间', dataIndex: 'traceProofreadTime', width: 165, align: 'center', defaultHidden: true },
],
traceAuditBy: [
{ title: '审核人', dataIndex: 'traceAuditBy', width: 100, align: 'center', defaultHidden: true },
{ title: '审核时间', dataIndex: 'traceAuditTime', width: 165, align: 'center', defaultHidden: true },
],
traceApproveBy: [
{ title: '批准人', dataIndex: 'traceApproveBy', width: 100, align: 'center', defaultHidden: true },
{ title: '批准时间', dataIndex: 'traceApproveTime', width: 165, align: 'center', defaultHidden: true },
],
};
/** 全量痕迹列密炼PS等已知需要全部的场景直接引用 */
export const traceColumns: BasicColumn[] = Object.values(traceColumnsByStage).flat();

View File

@@ -0,0 +1,16 @@
import { useTable } from '/@/components/Table';
import type { BasicTableProps } from '/@/components/Table';
import { traceColumns } from './traceColumns';
/**
* 替换 useTable不经过 useListPage 的特殊场景):自动追加审批痕迹列(默认隐藏)。
* 普通列表页已由 useListPage 统一注入,无需使用本函数。
*/
export function useTraceTable(tableProps: BasicTableProps) {
const columns = tableProps.columns as any[] | undefined;
const alreadyHasTrace = columns?.some((c) => c.dataIndex === 'traceProofreadBy');
return useTable({
...tableProps,
columns: alreadyHasTrace ? columns : [...(columns ?? []), ...traceColumns],
});
}

View File

@@ -0,0 +1,64 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
list = '/xslmes/mesXslDingCallbackLog/list',
save = '/xslmes/mesXslDingCallbackLog/add',
edit = '/xslmes/mesXslDingCallbackLog/edit',
deleteOne = '/xslmes/mesXslDingCallbackLog/delete',
deleteBatch = '/xslmes/mesXslDingCallbackLog/deleteBatch',
exportXlsUrl = '/xslmes/mesXslDingCallbackLog/exportXls',
importExcelUrl = '/xslmes/mesXslDingCallbackLog/importExcel',
}
/**
* 列表分页查询
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 删除单条
*/
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
/**
* 批量删除
*/
export const batchDelete = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
},
});
};
/**
* 保存或新增
*/
export const saveOrUpdate = (params, isUpdate) => {
const url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url, params });
};
/**
* 导出 XLS
*/
export const getExportUrl = Api.exportXlsUrl;
/**
* 导入 XLS
*/
export const getImportUrl = Api.importExcelUrl;

View File

@@ -0,0 +1,158 @@
import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Form';
import { rules } from '/@/utils/helper/validator';
export const columns: BasicColumn[] = [
{
title: '钉钉事件ID',
align: 'center',
dataIndex: 'eventId',
width: 180,
},
{
title: '事件类型',
align: 'center',
dataIndex: 'eventType',
width: 160,
},
{
title: '审批实例ID',
align: 'center',
dataIndex: 'processInstanceId',
width: 200,
},
{
title: '接收时间',
align: 'center',
dataIndex: 'receivedTime',
width: 160,
},
{
title: '是否已处理',
align: 'center',
dataIndex: 'processed_dictText',
width: 100,
},
{
title: '关联业务表',
align: 'center',
dataIndex: 'bizTable',
width: 140,
},
{
title: '关联业务记录ID',
align: 'center',
dataIndex: 'bizDataId',
width: 160,
},
{
title: '关联审批台账ID',
align: 'center',
dataIndex: 'recordId',
width: 160,
},
];
export const searchFormSchema: FormSchema[] = [
{
label: '事件类型',
field: 'eventType',
component: 'Input',
colProps: { span: 6 },
},
{
label: '审批实例ID',
field: 'processInstanceId',
component: 'Input',
colProps: { span: 6 },
},
{
label: '是否已处理',
field: 'processed',
component: 'JDictSelectTag',
componentProps: { dictCode: 'yn' },
colProps: { span: 6 },
},
{
label: '接收时间',
field: 'receivedTime',
component: 'RangePicker',
componentProps: { valueType: 'Date', showTime: true, format: 'YYYY-MM-DD HH:mm:ss' },
colProps: { span: 12 },
},
{
label: '关联业务表',
field: 'bizTable',
component: 'Input',
colProps: { span: 6 },
},
];
export const formSchema: FormSchema[] = [
{ label: '', field: 'id', component: 'Input', show: false },
{
label: '钉钉事件ID',
field: 'eventId',
component: 'Input',
colProps: { span: 12 },
},
{
label: '事件类型',
field: 'eventType',
component: 'Input',
componentProps: { placeholder: '如 bpms_instance_change' },
colProps: { span: 12 },
},
{
label: '审批实例ID',
field: 'processInstanceId',
component: 'Input',
colProps: { span: 12 },
},
{
label: '接收时间',
field: 'receivedTime',
component: 'DatePicker',
componentProps: { showTime: true, format: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'YYYY-MM-DD HH:mm:ss', style: { width: '100%' } },
colProps: { span: 12 },
},
{
label: '是否已处理',
field: 'processed',
component: 'JDictSelectTag',
componentProps: { dictCode: 'yn', placeholder: '请选择' },
colProps: { span: 12 },
},
{
label: '关联业务表',
field: 'bizTable',
component: 'Input',
colProps: { span: 12 },
},
{
label: '关联业务记录ID',
field: 'bizDataId',
component: 'Input',
colProps: { span: 12 },
},
{
label: '关联审批台账ID',
field: 'recordId',
component: 'Input',
colProps: { span: 12 },
},
{
label: '原始推送数据',
field: 'rawData',
component: 'InputTextArea',
componentProps: { rows: 6, placeholder: 'JSON 原始推送内容' },
colProps: { span: 24 },
},
{
label: '处理备注',
field: 'processRemark',
component: 'InputTextArea',
componentProps: { rows: 3, placeholder: '处理结果或失败原因' },
colProps: { span: 24 },
},
];

View File

@@ -0,0 +1,107 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<a-button type="primary" @click="handleAdd" preIcon="ant-design:plus-outlined">新增</a-button>
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button>
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
<a-button type="primary" danger preIcon="ant-design:delete-outlined" @click="batchHandleDelete">批量删除</a-button>
</template>
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<MesXslDingCallbackLogModal @register="registerModal" @success="handleSuccess" />
</div>
</template>
<script lang="ts" name="xslmes-mesXslDingCallbackLog" setup>
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import MesXslDingCallbackLogModal from './components/MesXslDingCallbackLogModal.vue';
import { columns, searchFormSchema } from './MesXslDingCallbackLog.data';
import { list, deleteOne, batchDelete, getExportUrl, getImportUrl } from './MesXslDingCallbackLog.api';
const [registerModal, { openModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '钉钉回调日志',
api: list,
columns,
canResize: false,
formConfig: {
labelWidth: 90,
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: true,
fieldMapToTime: [['receivedTime', ['receivedTime_begin', 'receivedTime_end'], 'YYYY-MM-DD HH:mm:ss']],
},
actionColumn: {
width: 120,
fixed: 'right',
},
},
exportConfig: {
name: '钉钉回调日志',
url: getExportUrl,
},
importConfig: {
url: getImportUrl,
success: handleSuccess,
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });
}
function handleEdit(record: Recordable) {
openModal(true, { record, isUpdate: true, showFooter: true });
}
function handleDetail(record: Recordable) {
openModal(true, { record, isUpdate: true, showFooter: false });
}
function handleDelete(record: Recordable) {
deleteOne({ id: record.id }, handleSuccess);
}
function batchHandleDelete() {
batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
function handleSuccess() {
reload();
}
function getTableAction(record: Recordable) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
},
];
}
function getDropDownAction(record: Recordable) {
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
},
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
placement: 'topLeft',
},
},
];
}
</script>

View File

@@ -0,0 +1,52 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="1000" @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form';
import { formSchema } from '../MesXslDingCallbackLog.data';
import { saveOrUpdate } from '../MesXslDingCallbackLog.api';
const emit = defineEmits(['success', 'register']);
const isUpdate = ref(true);
const isDetail = ref(false);
const [registerForm, { setProps, resetFields, setFieldsValue, validate }] = useForm({
labelWidth: 110,
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: { span: 12 },
});
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
resetFields();
setModalProps({ confirmLoading: false, showOkBtn: !!data?.showFooter });
isUpdate.value = !!data?.isUpdate;
isDetail.value = !data?.showFooter;
setProps({ disabled: isDetail.value });
if (unref(isUpdate) && data?.record) {
setFieldsValue({ ...data.record });
}
});
const title = computed(() => (!unref(isUpdate) ? '新增' : unref(isDetail) ? '详情' : '编辑'));
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
await saveOrUpdate(values, unref(isUpdate));
closeModal();
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>

View File

@@ -19,7 +19,7 @@ const deptSelectProps = {
};
const hasWorkflowInfo = ({ values }) =>
!!(values.proofreadBy || values.proofreadTime || values.auditBy || values.auditTime || values.approveBy || values.approveTime);
!!(values.traceProofreadBy || values.traceProofreadTime || values.traceAuditBy || values.traceAuditTime || values.traceApproveBy || values.traceApproveTime);
function sectionDivider(label: string, field: string, ifShow?: FormSchema['ifShow']): FormSchema {
return {
@@ -51,12 +51,6 @@ export const columns: BasicColumn[] = [
width: 100,
customRender: ({ record }) => record?.createBy_dictText || record?.createBy || '',
},
{ title: '校对人', align: 'center', dataIndex: 'proofreadBy', width: 100, defaultHidden: true },
{ title: '校对时间', align: 'center', dataIndex: 'proofreadTime', width: 165, defaultHidden: true },
{ title: '审核人', align: 'center', dataIndex: 'auditBy', width: 100, defaultHidden: true },
{ title: '审核时间', align: 'center', dataIndex: 'auditTime', width: 165, defaultHidden: true },
{ title: '批准人', align: 'center', dataIndex: 'approveBy', width: 100, defaultHidden: true },
{ title: '批准时间', align: 'center', dataIndex: 'approveTime', width: 165, defaultHidden: true },
{ title: '所属工厂', align: 'center', dataIndex: 'factoryName', width: 120, defaultHidden: true },
{ title: '施工代号', align: 'center', dataIndex: 'constructionCode_dictText', width: 110, defaultHidden: true },
{ title: '创建人', align: 'center', dataIndex: 'createBy', width: 100, defaultHidden: true },
@@ -222,51 +216,51 @@ export const formSchema: FormSchema[] = [
sectionDivider('审批记录', 'dividerWorkflow', hasWorkflowInfo),
{
label: '校对人',
field: 'proofreadBy',
field: 'traceProofreadBy',
component: 'Input',
componentProps: { disabled: true, bordered: false },
colProps: colHalf,
ifShow: ({ values }) => !!values.proofreadBy,
ifShow: ({ values }) => !!values.traceProofreadBy,
},
{
label: '校对时间',
field: 'proofreadTime',
field: 'traceProofreadTime',
component: 'Input',
componentProps: { disabled: true, bordered: false },
colProps: colHalf,
ifShow: ({ values }) => !!values.proofreadTime,
ifShow: ({ values }) => !!values.traceProofreadTime,
},
{
label: '审核人',
field: 'auditBy',
field: 'traceAuditBy',
component: 'Input',
componentProps: { disabled: true, bordered: false },
colProps: colHalf,
ifShow: ({ values }) => !!values.auditBy,
ifShow: ({ values }) => !!values.traceAuditBy,
},
{
label: '审核时间',
field: 'auditTime',
field: 'traceAuditTime',
component: 'Input',
componentProps: { disabled: true, bordered: false },
colProps: colHalf,
ifShow: ({ values }) => !!values.auditTime,
ifShow: ({ values }) => !!values.traceAuditTime,
},
{
label: '批准人',
field: 'approveBy',
field: 'traceApproveBy',
component: 'Input',
componentProps: { disabled: true, bordered: false },
colProps: colHalf,
ifShow: ({ values }) => !!values.approveBy,
ifShow: ({ values }) => !!values.traceApproveBy,
},
{
label: '批准时间',
field: 'approveTime',
field: 'traceApproveTime',
component: 'Input',
componentProps: { disabled: true, bordered: false },
colProps: colHalf,
ifShow: ({ values }) => !!values.approveTime,
ifShow: ({ values }) => !!values.traceApproveTime,
},
];