中间表采集新增采集配置,实现可视化可控采集
This commit is contained in:
@@ -14,10 +14,10 @@ enum Api {
|
||||
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
export const checkActionName = (params: { actionName: string; dataId?: string }) =>
|
||||
export const checkActionName = (params: { equipmentId?: string; actionName: string; dataId?: string }) =>
|
||||
defHttp.get({ url: Api.checkActionName, params }, { successMessageMode: 'none', errorMessageMode: 'none' });
|
||||
|
||||
export const checkActionCode = (params: { actionCode: string; dataId?: string }) =>
|
||||
export const checkActionCode = (params: { equipmentId?: string; actionCode: string; dataId?: string }) =>
|
||||
defHttp.get({ url: Api.checkActionCode, params }, { successMessageMode: 'none', errorMessageMode: 'none' });
|
||||
|
||||
export const deleteOne = (params, handleSuccess) =>
|
||||
|
||||
@@ -2,9 +2,11 @@ import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
import { checkActionCode, checkActionName } from './MesXslMixerAction.api';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{ title: '设备名称', align: 'center', dataIndex: 'equipmentId_dictText', width: 180 },
|
||||
{ title: '设备名称', align: 'center', dataIndex: 'equipmentName', width: 180 },
|
||||
{ title: '机台编号', align: 'center', dataIndex: 'equipId', width: 110 },
|
||||
{ title: '机台类型', align: 'center', dataIndex: 'equipType', width: 110 },
|
||||
{ title: '动作名称', align: 'center', dataIndex: 'actionName', width: 180 },
|
||||
{ title: '动作代号', align: 'center', dataIndex: 'actionCode', width: 160 },
|
||||
{ title: '动作代号', align: 'center', dataIndex: 'actionCode', width: 120 },
|
||||
{ title: '创建时间', align: 'center', dataIndex: 'createTime', width: 170 },
|
||||
];
|
||||
|
||||
@@ -16,6 +18,7 @@ export const searchFormSchema: FormSchema[] = [
|
||||
componentProps: { dictCode: 'mes_xsl_equipment_ledger,equipment_name,id' },
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{ label: '机台编号', field: 'equipId', component: 'Input', colProps: { span: 6 } },
|
||||
{ label: '动作名称', field: 'actionName', component: 'Input', colProps: { span: 6 } },
|
||||
{ label: '动作代号', field: 'actionCode', component: 'Input', colProps: { span: 6 } },
|
||||
];
|
||||
@@ -29,6 +32,21 @@ export const formSchema: FormSchema[] = [
|
||||
required: true,
|
||||
componentProps: { dictCode: 'mes_xsl_equipment_ledger,equipment_name,id', placeholder: '请选择设备台账中的设备' },
|
||||
},
|
||||
// 采集冗余字段:仅采集数据有值,只读展示
|
||||
{
|
||||
label: '机台编号',
|
||||
field: 'equipId',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
ifShow: ({ values }) => !!values.equipId,
|
||||
},
|
||||
{
|
||||
label: '机台类型',
|
||||
field: 'equipType',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
ifShow: ({ values }) => !!values.equipType,
|
||||
},
|
||||
{
|
||||
label: '动作名称',
|
||||
field: 'actionName',
|
||||
@@ -41,10 +59,10 @@ export const formSchema: FormSchema[] = [
|
||||
const v = value == null ? '' : String(value).trim();
|
||||
if (!v) return Promise.resolve();
|
||||
try {
|
||||
await checkActionName({ actionName: v, dataId: model?.id });
|
||||
await checkActionName({ equipmentId: model?.equipmentId, actionName: v, dataId: model?.id });
|
||||
return Promise.resolve();
|
||||
} catch (e: any) {
|
||||
return Promise.reject(e?.response?.data?.message || e?.message || '动作名称不能重复');
|
||||
return Promise.reject(e?.response?.data?.message || e?.message || '同一设备下动作名称不能重复');
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
@@ -63,10 +81,10 @@ export const formSchema: FormSchema[] = [
|
||||
const v = value == null ? '' : String(value).trim();
|
||||
if (!v) return Promise.resolve();
|
||||
try {
|
||||
await checkActionCode({ actionCode: v, dataId: model?.id });
|
||||
await checkActionCode({ equipmentId: model?.equipmentId, actionCode: v, dataId: model?.id });
|
||||
return Promise.resolve();
|
||||
} catch (e: any) {
|
||||
return Promise.reject(e?.response?.data?.message || e?.message || '动作代号不能重复');
|
||||
return Promise.reject(e?.response?.data?.message || e?.message || '同一设备下动作代号不能重复');
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" title="采集操作" :width="560" @ok="handleOk">
|
||||
<a-spin :spinning="loading">
|
||||
<a-form :labelCol="{ span: 7 }" :wrapperCol="{ span: 16 }">
|
||||
<a-form-item label="采集配置">
|
||||
<span>{{ configName || '-' }}</span>
|
||||
<span v-if="sourceTable" style="color: #999"> (源表:{{ sourceTable }})</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否采集">
|
||||
<a-switch v-model:checked="enabled" checkedChildren="采集中" unCheckedChildren="已停止" />
|
||||
</a-form-item>
|
||||
<a-form-item label="采集间隔">
|
||||
<a-input-number v-model:value="intervalSeconds" :min="1" :precision="0" addonAfter="秒" style="width: 100%" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="采集模式">
|
||||
<a-radio-group v-model:value="syncMode" button-style="solid">
|
||||
<a-radio-button value="FULL">全量匹配</a-radio-button>
|
||||
<a-radio-button value="TIME">时间匹配</a-radio-button>
|
||||
<a-radio-button value="INCR">增量匹配</a-radio-button>
|
||||
</a-radio-group>
|
||||
<div style="color: #999; font-size: 12px; margin-top: 4px">{{ modeHint }}</div>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="syncMode === 'TIME'">
|
||||
<a-form-item label="时间列" required>
|
||||
<a-select v-model:value="incrColumn" :options="sourceColumnOptions" showSearch allowClear placeholder="选择源表的时间列(如 WriteTime)" />
|
||||
</a-form-item>
|
||||
<a-form-item label="时间范围">
|
||||
<a-radio-group v-model:value="timeWindow">
|
||||
<a-radio value="TODAY">当天</a-radio>
|
||||
<a-radio value="LAST7">最近七天</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else-if="syncMode === 'INCR'">
|
||||
<a-form-item label="标记列" required>
|
||||
<a-select v-model:value="incrColumn" :options="sourceColumnOptions" showSearch allowClear placeholder="选择源表用于标记是否已采集的列" />
|
||||
<div style="color: #999; font-size: 12px">需在字段映射中勾选匹配键(如 GUID)作为回写主键,并开启中间库写入开关。</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="采集条件" required>
|
||||
<a-radio-group v-model:value="flagCondition" button-style="solid">
|
||||
<a-radio-button value="IS_NULL">为空</a-radio-button>
|
||||
<a-radio-button value="EQ_EMPTY">等于</a-radio-button>
|
||||
<a-radio-button value="NE_EMPTY">不等于</a-radio-button>
|
||||
</a-radio-group>
|
||||
<!-- update-begin---author:GHT ---date:20260617 for:【MES上辅机】等于/不等于支持自定义匹配值----------- -->
|
||||
<a-input
|
||||
v-if="flagCondition === 'EQ_EMPTY' || flagCondition === 'NE_EMPTY'"
|
||||
v-model:value="flagMatchValue"
|
||||
:placeholder="`填写要${flagCondition === 'EQ_EMPTY' ? '等于' : '不等于'}的值(留空表示空字符串“”)`"
|
||||
allowClear
|
||||
style="width: 100%; margin-top: 6px"
|
||||
/>
|
||||
<!-- update-end---author:GHT ---date:20260617 for:【MES上辅机】等于/不等于支持自定义匹配值----------- -->
|
||||
<div style="color: #999; font-size: 12px">仅采集“标记列”满足此条件的行。</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="回写值">
|
||||
<a-input v-model:value="flagWriteValue" placeholder="采集完成后写回标记列的值,默认 1" allowClear style="width: 100%" />
|
||||
<div style="color: #999; font-size: 12px">采集完成后把标记列回写成该值,使这些行不再满足上面的采集条件(默认“1”)。</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="每轮最大行数">
|
||||
<a-input-number v-model:value="batchLimit" :min="100" :step="500" :precision="0" addonAfter="行" style="width: 100%" />
|
||||
<div style="color: #999; font-size: 12px">每个采集周期最多取这么多行,分批吃完历史未采集数据。</div>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item v-if="lastSyncResult" label="最近采集">
|
||||
<span style="color: #999">{{ lastSyncResult }}</span>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { queryById, getByBizType, saveCollect, getSourceColumns } from '../mcsSyncConfig.api';
|
||||
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const loading = ref(false);
|
||||
const configId = ref('');
|
||||
const configName = ref('');
|
||||
const sourceTable = ref('');
|
||||
const enabled = ref(false);
|
||||
const intervalSeconds = ref(1);
|
||||
const syncMode = ref('FULL');
|
||||
const incrColumn = ref<string | undefined>(undefined);
|
||||
const timeWindow = ref('TODAY');
|
||||
const batchLimit = ref(2000);
|
||||
const flagCondition = ref('IS_NULL');
|
||||
const flagMatchValue = ref('');
|
||||
const flagWriteValue = ref('1');
|
||||
const lastWatermark = ref('');
|
||||
const lastSyncResult = ref('');
|
||||
const sourceColumnOptions = ref<any[]>([]);
|
||||
|
||||
const modeHint = computed(() => {
|
||||
if (syncMode.value === 'TIME') return '只采集时间列在所选范围内的数据,再按匹配键更新/新增。适合中大型表只关注近期数据。';
|
||||
if (syncMode.value === 'INCR') return '标记位回写:只采集“标记列”为空的行,采完回写“1”,下轮不再重复。适合带 GUID 主键、无可靠递增列的流水表。';
|
||||
return '全表读取并按匹配键更新/新增。适合数据量小、以更新为主的表。';
|
||||
});
|
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
setModalProps({ confirmLoading: false });
|
||||
loading.value = true;
|
||||
try {
|
||||
reset();
|
||||
const cfg: any = data?.id ? await queryById(data.id) : await getByBizType(data?.bizType || 'MIX_ACT');
|
||||
if (!cfg || !cfg.id) {
|
||||
createMessage.warning('未找到采集配置,请先在「采集配置」中新增');
|
||||
return;
|
||||
}
|
||||
configId.value = cfg.id;
|
||||
configName.value = cfg.configName || cfg.bizName || cfg.sourceTable;
|
||||
sourceTable.value = cfg.sourceTable || '';
|
||||
enabled.value = cfg.running === true || cfg.status === '1';
|
||||
intervalSeconds.value = cfg.intervalSeconds || 1;
|
||||
syncMode.value = cfg.syncMode || 'FULL';
|
||||
incrColumn.value = cfg.incrColumn || undefined;
|
||||
timeWindow.value = cfg.timeWindow || 'TODAY';
|
||||
batchLimit.value = cfg.batchLimit || 2000;
|
||||
flagCondition.value = cfg.flagCondition || 'IS_NULL';
|
||||
flagMatchValue.value = cfg.flagMatchValue ?? '';
|
||||
flagWriteValue.value = cfg.flagWriteValue ?? '1';
|
||||
lastWatermark.value = cfg.lastWatermark || '';
|
||||
lastSyncResult.value = cfg.lastSyncResult || '';
|
||||
// 载入源表列供时间列/增量列选择(中间库未连接时静默忽略)
|
||||
if (sourceTable.value) {
|
||||
try {
|
||||
const cols: any = await getSourceColumns(sourceTable.value);
|
||||
sourceColumnOptions.value = (cols || []).map((c: any) => ({
|
||||
label: c.columnName + (c.columnComment ? ` - ${c.columnComment}` : '') + (c.dataType ? ` (${c.dataType})` : ''),
|
||||
value: c.columnName,
|
||||
}));
|
||||
} catch (e) {
|
||||
sourceColumnOptions.value = [];
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
function reset() {
|
||||
configId.value = '';
|
||||
configName.value = '';
|
||||
sourceTable.value = '';
|
||||
enabled.value = false;
|
||||
intervalSeconds.value = 1;
|
||||
syncMode.value = 'FULL';
|
||||
incrColumn.value = undefined;
|
||||
timeWindow.value = 'TODAY';
|
||||
batchLimit.value = 2000;
|
||||
flagCondition.value = 'IS_NULL';
|
||||
flagMatchValue.value = '';
|
||||
flagWriteValue.value = '1';
|
||||
lastWatermark.value = '';
|
||||
lastSyncResult.value = '';
|
||||
sourceColumnOptions.value = [];
|
||||
}
|
||||
|
||||
async function handleOk() {
|
||||
if (!configId.value) {
|
||||
closeModal();
|
||||
return;
|
||||
}
|
||||
if (!intervalSeconds.value || intervalSeconds.value < 1) {
|
||||
createMessage.warning('采集间隔不能小于1秒');
|
||||
return;
|
||||
}
|
||||
if ((syncMode.value === 'TIME' || syncMode.value === 'INCR') && !incrColumn.value) {
|
||||
createMessage.warning(syncMode.value === 'TIME' ? '请选择时间列' : '请选择标记列');
|
||||
return;
|
||||
}
|
||||
setModalProps({ confirmLoading: true });
|
||||
try {
|
||||
await saveCollect({
|
||||
id: configId.value,
|
||||
status: enabled.value ? '1' : '0',
|
||||
intervalSeconds: intervalSeconds.value,
|
||||
syncMode: syncMode.value,
|
||||
incrColumn: incrColumn.value,
|
||||
timeWindow: timeWindow.value,
|
||||
batchLimit: batchLimit.value,
|
||||
flagCondition: flagCondition.value,
|
||||
flagMatchValue: flagMatchValue.value,
|
||||
flagWriteValue: flagWriteValue.value,
|
||||
});
|
||||
closeModal();
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="960" @ok="handleSubmit" :confirmLoading="confirmLoading">
|
||||
<a-spin :spinning="loading">
|
||||
<a-form :model="form" :labelCol="{ span: 6 }" :wrapperCol="{ span: 16 }">
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="配置名称" required>
|
||||
<a-input v-model:value="form.configName" placeholder="如:密炼机动作采集" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="采集间隔">
|
||||
<a-input-number v-model:value="form.intervalSeconds" :min="1" :precision="0" addonAfter="秒" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="中间库源表" required>
|
||||
<a-select
|
||||
v-model:value="form.sourceTable"
|
||||
:options="sourceTableOptions"
|
||||
showSearch
|
||||
placeholder="选择中间库的表"
|
||||
style="width: 100%"
|
||||
@change="onSourceTableChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="MES目标表" required>
|
||||
<a-select
|
||||
v-model:value="form.targetTable"
|
||||
:options="targetTableOptions"
|
||||
showSearch
|
||||
placeholder="选择MES的表(mes_xsl_)"
|
||||
style="width: 100%"
|
||||
@change="onTargetTableChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-divider style="margin: 8px 0">字段映射(左:中间库字段 / 右:MES接收字段;勾选匹配键作为去重Upsert唯一键)</a-divider>
|
||||
|
||||
<a-table
|
||||
:columns="mappingColumns"
|
||||
:dataSource="mappingRows"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
rowKey="sourceField"
|
||||
:scroll="{ y: 360 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'sourceField'">
|
||||
<div>{{ record.sourceField }}</div>
|
||||
<div style="color: #999; font-size: 12px">{{ record.sourceFieldComment }}</div>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'targetField'">
|
||||
<a-select
|
||||
v-model:value="record.targetField"
|
||||
:options="targetColumnOptions"
|
||||
allowClear
|
||||
showSearch
|
||||
placeholder="选择MES接收字段"
|
||||
style="width: 100%"
|
||||
:disabled="!form.targetTable"
|
||||
@change="(v) => onTargetFieldChange(record, v)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'matchKey'">
|
||||
<a-checkbox v-model:checked="record.matchKey" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<div v-if="mappingRows.length === 0" style="text-align: center; color: #999; padding: 16px">请选择中间库源表以载入字段</div>
|
||||
</a-spin>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { queryById, saveOrUpdate, getSourceTables, getSourceColumns, getTargetTables, getTargetColumns } from '../mcsSyncConfig.api';
|
||||
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const loading = ref(false);
|
||||
const confirmLoading = ref(false);
|
||||
|
||||
const form = reactive<any>({
|
||||
id: undefined,
|
||||
configName: '',
|
||||
bizType: '',
|
||||
sourceTable: undefined,
|
||||
sourceTableComment: '',
|
||||
targetTable: undefined,
|
||||
targetTableComment: '',
|
||||
intervalSeconds: 1,
|
||||
});
|
||||
|
||||
const sourceTableOptions = ref<any[]>([]);
|
||||
const targetTableOptions = ref<any[]>([]);
|
||||
const targetColumnOptions = ref<any[]>([]);
|
||||
const mappingRows = ref<any[]>([]);
|
||||
|
||||
const title = computed(() => (isUpdate.value ? '编辑采集配置' : '新增采集配置'));
|
||||
|
||||
const mappingColumns = [
|
||||
{ title: '中间库字段', dataIndex: 'sourceField', width: 240 },
|
||||
{ title: '类型', dataIndex: 'sourceFieldType', width: 90 },
|
||||
{ title: 'MES接收字段', dataIndex: 'targetField' },
|
||||
{ title: '匹配键', dataIndex: 'matchKey', width: 70, align: 'center' },
|
||||
];
|
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
setModalProps({ confirmLoading: false });
|
||||
loading.value = true;
|
||||
try {
|
||||
resetForm();
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
// 表清单
|
||||
const [srcTables, tgtTables] = await Promise.all([safeGet(getSourceTables), getTargetTables()]);
|
||||
sourceTableOptions.value = (srcTables || []).map((t: any) => ({
|
||||
label: t.tableName + (t.tableComment ? ` - ${t.tableComment}` : ''),
|
||||
value: t.tableName,
|
||||
comment: t.tableComment,
|
||||
}));
|
||||
targetTableOptions.value = (tgtTables || []).map((t: any) => ({
|
||||
label: t.tableName + (t.tableComment ? ` - ${t.tableComment}` : ''),
|
||||
value: t.tableName,
|
||||
comment: t.tableComment,
|
||||
}));
|
||||
|
||||
if (isUpdate.value && data?.record?.id) {
|
||||
const cfg: any = await queryById(data.record.id);
|
||||
Object.assign(form, {
|
||||
id: cfg.id,
|
||||
configName: cfg.configName,
|
||||
bizType: cfg.bizType,
|
||||
sourceTable: cfg.sourceTable,
|
||||
sourceTableComment: cfg.sourceTableComment,
|
||||
targetTable: cfg.targetTable,
|
||||
targetTableComment: cfg.targetTableComment,
|
||||
intervalSeconds: cfg.intervalSeconds || 1,
|
||||
});
|
||||
// 载入目标字段选项
|
||||
if (cfg.targetTable) {
|
||||
targetColumnOptions.value = buildColumnOptions(await getTargetColumns(cfg.targetTable));
|
||||
}
|
||||
// 字段映射来自已存配置
|
||||
mappingRows.value = (cfg.fieldList || []).map((f: any) => ({
|
||||
sourceField: f.sourceField,
|
||||
sourceFieldComment: f.sourceFieldComment,
|
||||
sourceFieldType: f.sourceFieldType,
|
||||
targetField: f.targetField,
|
||||
targetFieldComment: f.targetFieldComment,
|
||||
matchKey: f.matchKey === '1',
|
||||
}));
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
function resetForm() {
|
||||
Object.assign(form, {
|
||||
id: undefined,
|
||||
configName: '',
|
||||
bizType: '',
|
||||
sourceTable: undefined,
|
||||
sourceTableComment: '',
|
||||
targetTable: undefined,
|
||||
targetTableComment: '',
|
||||
intervalSeconds: 1,
|
||||
});
|
||||
targetColumnOptions.value = [];
|
||||
mappingRows.value = [];
|
||||
}
|
||||
|
||||
async function safeGet(fn: () => Promise<any>) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function buildColumnOptions(cols: any[]) {
|
||||
return (cols || []).map((c: any) => ({
|
||||
label: c.columnName + (c.columnComment ? ` - ${c.columnComment}` : ''),
|
||||
value: c.columnName,
|
||||
comment: c.columnComment,
|
||||
}));
|
||||
}
|
||||
|
||||
async function onSourceTableChange(val: string) {
|
||||
const opt = sourceTableOptions.value.find((o) => o.value === val);
|
||||
form.sourceTableComment = opt?.comment || '';
|
||||
if (!val) {
|
||||
mappingRows.value = [];
|
||||
return;
|
||||
}
|
||||
const cols = await getSourceColumns(val);
|
||||
mappingRows.value = (cols || []).map((c: any) => {
|
||||
// 同名自动匹配 MES 字段
|
||||
const guess = targetColumnOptions.value.find((o) => o.value.toLowerCase() === String(c.columnName).toLowerCase());
|
||||
return {
|
||||
sourceField: c.columnName,
|
||||
sourceFieldComment: c.columnComment,
|
||||
sourceFieldType: c.dataType,
|
||||
targetField: guess ? guess.value : undefined,
|
||||
targetFieldComment: guess ? guess.comment : '',
|
||||
matchKey: false,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function onTargetTableChange(val: string) {
|
||||
const opt = targetTableOptions.value.find((o) => o.value === val);
|
||||
form.targetTableComment = opt?.comment || '';
|
||||
targetColumnOptions.value = val ? buildColumnOptions(await getTargetColumns(val)) : [];
|
||||
}
|
||||
|
||||
function onTargetFieldChange(record: any, val: string) {
|
||||
const opt = targetColumnOptions.value.find((o) => o.value === val);
|
||||
record.targetFieldComment = opt?.comment || '';
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.configName) {
|
||||
createMessage.warning('请填写配置名称');
|
||||
return;
|
||||
}
|
||||
if (!form.sourceTable || !form.targetTable) {
|
||||
createMessage.warning('请选择中间库源表与MES目标表');
|
||||
return;
|
||||
}
|
||||
const fieldList = mappingRows.value
|
||||
.filter((r) => r.targetField)
|
||||
.map((r, idx) => ({
|
||||
sourceField: r.sourceField,
|
||||
sourceFieldComment: r.sourceFieldComment,
|
||||
sourceFieldType: r.sourceFieldType,
|
||||
targetField: r.targetField,
|
||||
targetFieldComment: r.targetFieldComment,
|
||||
matchKey: r.matchKey ? '1' : '0',
|
||||
sortNo: idx,
|
||||
}));
|
||||
if (fieldList.length === 0) {
|
||||
createMessage.warning('请至少为一个中间库字段选择 MES 接收字段');
|
||||
return;
|
||||
}
|
||||
confirmLoading.value = true;
|
||||
try {
|
||||
await saveOrUpdate({ ...form, fieldList }, isUpdate.value);
|
||||
closeModal();
|
||||
emit('success');
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
64
jeecgboot-vue3/src/views/xslmesMcs/mcsSyncConfig/index.vue
Normal file
64
jeecgboot-vue3/src/views/xslmesMcs/mcsSyncConfig/index.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" v-auth="'xslmes:mcsSyncConfig:add'" preIcon="ant-design:plus-outlined" @click="handleAdd">新增采集配置</a-button>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<SyncConfigModal @register="registerEditModal" @success="reload" />
|
||||
<CollectModal @register="registerCollectModal" @success="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="xslmes-mcs-mcsSyncConfig" setup>
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import SyncConfigModal from './components/SyncConfigModal.vue';
|
||||
import CollectModal from './components/CollectModal.vue';
|
||||
import { columns, searchFormSchema } from './mcsSyncConfig.data';
|
||||
import { list, deleteOne } from './mcsSyncConfig.api';
|
||||
|
||||
const [registerEditModal, { openModal: openEditModal }] = useModal();
|
||||
const [registerCollectModal, { openModal: openCollectModal }] = useModal();
|
||||
|
||||
const { tableContext } = useListPage({
|
||||
tableProps: {
|
||||
title: '采集配置',
|
||||
api: list,
|
||||
columns,
|
||||
canResize: true,
|
||||
formConfig: { labelWidth: 100, schemas: searchFormSchema, autoSubmitOnEnter: true },
|
||||
actionColumn: { width: 200, fixed: 'right' },
|
||||
},
|
||||
});
|
||||
const [registerTable, { reload }] = tableContext;
|
||||
|
||||
function handleAdd() {
|
||||
openEditModal(true, { isUpdate: false });
|
||||
}
|
||||
function handleEdit(record) {
|
||||
openEditModal(true, { isUpdate: true, record });
|
||||
}
|
||||
function handleCollect(record) {
|
||||
openCollectModal(true, { id: record.id });
|
||||
}
|
||||
async function handleDelete(record) {
|
||||
await deleteOne(record.id, reload);
|
||||
}
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{ label: '编辑', onClick: handleEdit.bind(null, record), auth: 'xslmes:mcsSyncConfig:edit' },
|
||||
{ label: '采集操作', onClick: handleCollect.bind(null, record), auth: 'xslmes:mcsSyncConfig:setting' },
|
||||
{
|
||||
label: '删除',
|
||||
color: 'error',
|
||||
popConfirm: { title: '确认删除该采集配置?', confirm: handleDelete.bind(null, record) },
|
||||
auth: 'xslmes:mcsSyncConfig:delete',
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,44 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/xslmes/mcs/syncConfig/list',
|
||||
queryById = '/xslmes/mcs/syncConfig/queryById',
|
||||
getByBizType = '/xslmes/mcs/syncConfig/getByBizType',
|
||||
add = '/xslmes/mcs/syncConfig/add',
|
||||
edit = '/xslmes/mcs/syncConfig/edit',
|
||||
deleteOne = '/xslmes/mcs/syncConfig/delete',
|
||||
saveCollect = '/xslmes/mcs/syncConfig/saveCollect',
|
||||
sourceTables = '/xslmes/mcs/syncConfig/meta/sourceTables',
|
||||
sourceColumns = '/xslmes/mcs/syncConfig/meta/sourceColumns',
|
||||
targetTables = '/xslmes/mcs/syncConfig/meta/targetTables',
|
||||
targetColumns = '/xslmes/mcs/syncConfig/meta/targetColumns',
|
||||
}
|
||||
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
export const queryById = (id: string) => defHttp.get({ url: Api.queryById, params: { id } });
|
||||
|
||||
export const getByBizType = (bizType = 'MIX_ACT') => defHttp.get({ url: Api.getByBizType, params: { bizType } });
|
||||
|
||||
export const saveOrUpdate = (params, isUpdate: boolean) => defHttp.post({ url: isUpdate ? Api.edit : Api.add, params });
|
||||
|
||||
export const deleteOne = (id: string, handleSuccess) =>
|
||||
defHttp.delete({ url: Api.deleteOne, params: { id } }, { joinParamsToUrl: true }).then(() => handleSuccess());
|
||||
|
||||
// 采集操作:status '1'/'0' 表示是否采集;syncMode FULL/TIME/INCR
|
||||
export const saveCollect = (params: {
|
||||
id: string;
|
||||
status: string;
|
||||
intervalSeconds: number;
|
||||
syncMode?: string;
|
||||
incrColumn?: string;
|
||||
timeWindow?: string;
|
||||
batchLimit?: number;
|
||||
flagCondition?: string;
|
||||
flagWriteValue?: string;
|
||||
}) => defHttp.post({ url: Api.saveCollect, params });
|
||||
|
||||
export const getSourceTables = () => defHttp.get({ url: Api.sourceTables }, { errorMessageMode: 'message' });
|
||||
export const getSourceColumns = (table: string) => defHttp.get({ url: Api.sourceColumns, params: { table } }, { errorMessageMode: 'message' });
|
||||
export const getTargetTables = () => defHttp.get({ url: Api.targetTables });
|
||||
export const getTargetColumns = (table: string) => defHttp.get({ url: Api.targetColumns, params: { table } });
|
||||
@@ -0,0 +1,41 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{ title: '配置名称', align: 'center', dataIndex: 'configName', width: 160 },
|
||||
{
|
||||
title: '中间库源表',
|
||||
align: 'center',
|
||||
dataIndex: 'sourceTable',
|
||||
width: 200,
|
||||
customRender: ({ record }) => (record.sourceTableComment ? `${record.sourceTable}(${record.sourceTableComment})` : record.sourceTable),
|
||||
},
|
||||
{
|
||||
title: 'MES目标表',
|
||||
align: 'center',
|
||||
dataIndex: 'targetTable',
|
||||
width: 200,
|
||||
customRender: ({ record }) => (record.targetTableComment ? `${record.targetTable}(${record.targetTableComment})` : record.targetTable),
|
||||
},
|
||||
{
|
||||
title: '采集模式',
|
||||
align: 'center',
|
||||
dataIndex: 'syncMode',
|
||||
width: 100,
|
||||
customRender: ({ record }) => ({ FULL: '全量匹配', TIME: '时间匹配', INCR: '增量匹配' }[record.syncMode] || '全量匹配'),
|
||||
},
|
||||
{ title: '采集间隔(秒)', align: 'center', dataIndex: 'intervalSeconds', width: 100 },
|
||||
{
|
||||
title: '状态',
|
||||
align: 'center',
|
||||
dataIndex: 'running',
|
||||
width: 90,
|
||||
customRender: ({ record }) => (record.running ? '采集中' : '已停止'),
|
||||
},
|
||||
{ title: '最近采集时间', align: 'center', dataIndex: 'lastSyncTime', width: 160 },
|
||||
{ title: '最近采集结果', align: 'center', dataIndex: 'lastSyncResult', width: 200 },
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{ label: '配置名称', field: 'configName', component: 'Input', colProps: { span: 6 } },
|
||||
{ label: '中间库源表', field: 'sourceTable', component: 'Input', colProps: { span: 6 } },
|
||||
];
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<a-button type="primary" v-auth="'xslmes:mcsSyncConfig:setting'" preIcon="ant-design:sync-outlined" @click="openCollect"> 采集操作 </a-button>
|
||||
<a-button type="link" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
@@ -10,6 +11,7 @@
|
||||
</template>
|
||||
</BasicTable>
|
||||
<McsToMesMixActModal @register="registerModal" />
|
||||
<CollectModal @register="registerCollectModal" @success="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -19,11 +21,13 @@
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import McsToMesMixActModal from './components/McsToMesMixActModal.vue';
|
||||
import CollectModal from '../mcsSyncConfig/components/CollectModal.vue';
|
||||
import { columns, searchFormSchema } from './McsToMesMixAct.data';
|
||||
import { list, getExportUrl } from './McsToMesMixAct.api';
|
||||
|
||||
const queryParam = reactive<any>({});
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const [registerCollectModal, { openModal: openCollectModal }] = useModal();
|
||||
|
||||
const { tableContext, onExportXls } = useListPage({
|
||||
tableProps: {
|
||||
@@ -49,8 +53,15 @@
|
||||
const [registerTable, { reload }] = tableContext;
|
||||
const superQueryConfig = reactive({});
|
||||
|
||||
// 采集操作:弹窗维护是否采集 + 采集间隔(绑定密炼动作采集配置 MIX_ACT)
|
||||
function openCollect() {
|
||||
openCollectModal(true, { bizType: 'MIX_ACT' });
|
||||
}
|
||||
|
||||
function handleSuperQuery(params) {
|
||||
Object.keys(params).map((k) => { queryParam[k] = params[k]; });
|
||||
Object.keys(params).map((k) => {
|
||||
queryParam[k] = params[k];
|
||||
});
|
||||
reload();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user