|
@@ -17,8 +17,9 @@
|
|
|
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">{{ row.status === 1 ? '启用' : '停用' }}</el-tag>
|
|
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">{{ row.status === 1 ? '启用' : '停用' }}</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="操作" width="200" fixed="right" align="center">
|
|
|
|
|
|
|
+ <el-table-column label="操作" width="260" fixed="right" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
|
|
+ <el-button link type="primary" :loading="pullingId === row.id" :disabled="!canPull(row)" @click.stop="onPull(row)">立即拉取</el-button>
|
|
|
<el-button link type="primary" @click.stop="openMappings(row)">字段映射</el-button>
|
|
<el-button link type="primary" @click.stop="openMappings(row)">字段映射</el-button>
|
|
|
<el-button link type="primary" @click.stop="openEdit(row)">编辑</el-button>
|
|
<el-button link type="primary" @click.stop="openEdit(row)">编辑</el-button>
|
|
|
<el-button link type="danger" :disabled="isProtected(row)" @click.stop="confirmDelete(row)">删除</el-button>
|
|
<el-button link type="danger" :disabled="isProtected(row)" @click.stop="confirmDelete(row)">删除</el-button>
|
|
@@ -74,14 +75,16 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { computed, reactive, ref, watch } from 'vue';
|
|
|
|
|
|
|
+import { computed, h, reactive, ref, watch } from 'vue';
|
|
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
|
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
|
|
|
import FieldMappingEditor from './FieldMappingEditor.vue';
|
|
import FieldMappingEditor from './FieldMappingEditor.vue';
|
|
|
import {
|
|
import {
|
|
|
createSyncTaskEntity,
|
|
createSyncTaskEntity,
|
|
|
deleteSyncEntity,
|
|
deleteSyncEntity,
|
|
|
fetchSyncTaskEntities,
|
|
fetchSyncTaskEntities,
|
|
|
|
|
+ pullEntityNow,
|
|
|
updateSyncEntity,
|
|
updateSyncEntity,
|
|
|
|
|
+ type MdpEntityPullResult,
|
|
|
type MdpEntityRow,
|
|
type MdpEntityRow,
|
|
|
type MdpEntityUpsertInput,
|
|
type MdpEntityUpsertInput,
|
|
|
type MdpSyncTaskRow,
|
|
type MdpSyncTaskRow,
|
|
@@ -99,6 +102,8 @@ const emit = defineEmits<{
|
|
|
|
|
|
|
|
const loading = ref(false);
|
|
const loading = ref(false);
|
|
|
const saving = ref(false);
|
|
const saving = ref(false);
|
|
|
|
|
+// 单行级 loading:只让被点的那一行按钮转圈,不禁用整个表格
|
|
|
|
|
+const pullingId = ref<number | null>(null);
|
|
|
const rows = ref<MdpEntityRow[]>([]);
|
|
const rows = ref<MdpEntityRow[]>([]);
|
|
|
const formVisible = ref(false);
|
|
const formVisible = ref(false);
|
|
|
const formMode = ref<'create' | 'edit'>('create');
|
|
const formMode = ref<'create' | 'edit'>('create');
|
|
@@ -232,6 +237,51 @@ function onMappingSaved() {
|
|
|
emit('saved');
|
|
emit('saved');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function canPull(row: MdpEntityRow) {
|
|
|
|
|
+ // 源表名与 API 路径都为空时,后端 MdpDbPullExecutor.PullAsync 的标识符硬校验必然失败,
|
|
|
|
|
+ // 与其让用户点了才报错,不如提前置灰。
|
|
|
|
|
+ return Boolean(row.sourceTableName?.trim() || row.sourceApiPath?.trim());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function onPull(row: MdpEntityRow) {
|
|
|
|
|
+ if (!props.task || pullingId.value != null) return;
|
|
|
|
|
+ pullingId.value = row.id;
|
|
|
|
|
+ let res: MdpEntityPullResult | null = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ res = await pullEntityNow(props.task.taskCode, row.entityCode);
|
|
|
|
|
+ } catch (e: unknown) {
|
|
|
|
|
+ ElMessage.error((e as Error)?.message || '拉取失败');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ pullingId.value = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!res) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 拉取行数与「实际是否全量」分开呈现:调度窗口为 FULL 时后端会把请求的增量强制成全量,
|
|
|
|
|
+ // 只报请求值会让用户误以为本次是增量。
|
|
|
|
|
+ const lines = [
|
|
|
|
|
+ `拉取行数:${res.rowsPulled}`,
|
|
|
|
|
+ `写入行数:${res.rowsWritten}`,
|
|
|
|
|
+ `请求全量:${res.requestedFullRefresh ? '是' : '否'}`,
|
|
|
|
|
+ `实际全量:${res.effectiveFullRefresh ? '是' : '否'}(窗口 ${res.effectiveWindowType || '—'})`,
|
|
|
|
|
+ `目标表:${res.targetTable || '—'}`,
|
|
|
|
|
+ `批次号:${res.batchId}`,
|
|
|
|
|
+ `新游标:${res.newCursor || '—'}`,
|
|
|
|
|
+ ];
|
|
|
|
|
+ if (res.message) lines.push(`后端消息:${res.message}`);
|
|
|
|
|
+
|
|
|
|
|
+ // 弹窗单独 try:用户按 ESC 关掉弹窗产生的 reject 不能被误报成「拉取失败」
|
|
|
|
|
+ try {
|
|
|
|
|
+ await ElMessageBox.alert(
|
|
|
|
|
+ h('div', { style: 'line-height:1.8;white-space:pre-wrap;word-break:break-all;' }, lines.join('\n')),
|
|
|
|
|
+ `实体 ${res.entityCode} 拉取完成`,
|
|
|
|
|
+ { type: 'success', confirmButtonText: '知道了' }
|
|
|
|
|
+ );
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ /* 用户关闭弹窗 */
|
|
|
|
|
+ }
|
|
|
|
|
+ await loadEntities();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
watch(
|
|
watch(
|
|
|
() => [props.visible, props.task?.taskCode] as const,
|
|
() => [props.visible, props.task?.taskCode] as const,
|
|
|
([vis, code]) => {
|
|
([vis, code]) => {
|