feat: 优化数据中枢 Excel 连接创建交互
- 在新增连接中完成 Excel 上传并自动填写连接名称 - 限制上传格式并提供空表和无表头错误反馈 - 移除旧导入入口和无效测试连接操作
This commit is contained in:
@@ -1,37 +1,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { TreeNode } from './composables/use-connection-tree';
|
import type { TreeNode } from './composables/use-connection-tree';
|
||||||
|
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EasyFlowButton,
|
|
||||||
ResizableHandle,
|
ResizableHandle,
|
||||||
ResizablePanel,
|
ResizablePanel,
|
||||||
ResizablePanelGroup,
|
ResizablePanelGroup,
|
||||||
} from '@easyflow-core/shadcn-ui';
|
} from '@easyflow-core/shadcn-ui';
|
||||||
|
|
||||||
import {
|
import { ElEmpty, ElMessage, ElMessageBox } from 'element-plus';
|
||||||
ElDropdown,
|
|
||||||
ElDropdownItem,
|
|
||||||
ElDropdownMenu,
|
|
||||||
ElEmpty,
|
|
||||||
ElMessageBox,
|
|
||||||
} from 'element-plus';
|
|
||||||
|
|
||||||
import ConnectionTree from './components/ConnectionTree.vue';
|
import ConnectionTree from './components/ConnectionTree.vue';
|
||||||
import ExcelActionDrawer from './components/ExcelActionDrawer.vue';
|
|
||||||
import SourceFormDrawer from './components/SourceFormDrawer.vue';
|
import SourceFormDrawer from './components/SourceFormDrawer.vue';
|
||||||
import TableDetailView from './components/TableDetailView.vue';
|
import TableDetailView from './components/TableDetailView.vue';
|
||||||
import TableListView from './components/TableListView.vue';
|
import TableListView from './components/TableListView.vue';
|
||||||
import { useConnectionTree } from './composables/use-connection-tree';
|
import { useConnectionTree } from './composables/use-connection-tree';
|
||||||
import { useDatacenterExcel } from './composables/use-datacenter-excel';
|
|
||||||
import { useDatacenterSources } from './composables/use-datacenter-sources';
|
import { useDatacenterSources } from './composables/use-datacenter-sources';
|
||||||
import { useDatacenterTables } from './composables/use-datacenter-tables';
|
import { useDatacenterTables } from './composables/use-datacenter-tables';
|
||||||
|
|
||||||
const sourceFormRef = ref<InstanceType<typeof SourceFormDrawer>>();
|
const sourceFormRef = ref<InstanceType<typeof SourceFormDrawer>>();
|
||||||
const excelActionRef = ref<InstanceType<typeof ExcelActionDrawer>>();
|
|
||||||
const sourceFormVisible = ref(false);
|
const sourceFormVisible = ref(false);
|
||||||
const excelActionVisible = ref(false);
|
|
||||||
const workspaceRef = ref<HTMLElement>();
|
const workspaceRef = ref<HTMLElement>();
|
||||||
const workspaceHeight = ref('100%');
|
const workspaceHeight = ref('100%');
|
||||||
|
|
||||||
@@ -42,6 +31,7 @@ const {
|
|||||||
loading,
|
loading,
|
||||||
saving,
|
saving,
|
||||||
testing,
|
testing,
|
||||||
|
importExcelSource,
|
||||||
loadSources,
|
loadSources,
|
||||||
removeSource,
|
removeSource,
|
||||||
saveSource,
|
saveSource,
|
||||||
@@ -76,7 +66,7 @@ async function reloadAll(options?: {
|
|||||||
resetTable?: boolean;
|
resetTable?: boolean;
|
||||||
}) {
|
}) {
|
||||||
await loadSources();
|
await loadSources();
|
||||||
if (!selectedSourceId.value) {
|
if (selectedSourceId.value === null) {
|
||||||
selectedCatalogId.value = null;
|
selectedCatalogId.value = null;
|
||||||
selectedTableId.value = null;
|
selectedTableId.value = null;
|
||||||
selectedNodeKey.value = '';
|
selectedNodeKey.value = '';
|
||||||
@@ -97,61 +87,11 @@ async function reloadAll(options?: {
|
|||||||
viewMode.value = 'list';
|
viewMode.value = 'list';
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
|
||||||
actionLoading,
|
|
||||||
pendingUploadFile,
|
|
||||||
splitForm,
|
|
||||||
mergeForm,
|
|
||||||
deriveForm,
|
|
||||||
exportForm,
|
|
||||||
resetSplitForm,
|
|
||||||
resetMergeForm,
|
|
||||||
resetDeriveForm,
|
|
||||||
resetExportForm,
|
|
||||||
handleImport,
|
|
||||||
handleSplit,
|
|
||||||
handleMerge,
|
|
||||||
handleDerive,
|
|
||||||
handleExport,
|
|
||||||
} = useDatacenterExcel(
|
|
||||||
selectedSourceId,
|
|
||||||
selectedCatalogId,
|
|
||||||
selectedTableId,
|
|
||||||
reloadAll,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 视图状态:empty | list | detail
|
// 视图状态:empty | list | detail
|
||||||
const selectedNodeKey = ref('');
|
const selectedNodeKey = ref('');
|
||||||
const viewMode = ref<'detail' | 'empty' | 'list'>('empty');
|
const viewMode = ref<'detail' | 'empty' | 'list'>('empty');
|
||||||
|
|
||||||
const isExcelContext = computed(() => {
|
|
||||||
const st =
|
|
||||||
selectedSource.value?.sourceType || schema.value?.source?.sourceType;
|
|
||||||
return st === 'EXCEL' || st === 'EXCEL_MATERIALIZED';
|
|
||||||
});
|
|
||||||
const canImportExcel = computed(
|
|
||||||
() => selectedSource.value?.sourceType === 'EXCEL',
|
|
||||||
);
|
|
||||||
const canExport = computed(() =>
|
|
||||||
Boolean(selectedTable.value || selectedSource.value),
|
|
||||||
);
|
|
||||||
const canShowMoreActions = computed(
|
|
||||||
() =>
|
|
||||||
isExcelContext.value &&
|
|
||||||
(Boolean(selectedTable.value) ||
|
|
||||||
managedTables.value.length > 1 ||
|
|
||||||
canExport.value),
|
|
||||||
);
|
|
||||||
const showToolbar = computed(
|
|
||||||
() => canImportExcel.value || canShowMoreActions.value,
|
|
||||||
);
|
|
||||||
const { treeData, parseNodeKey } = useConnectionTree(sources);
|
const { treeData, parseNodeKey } = useConnectionTree(sources);
|
||||||
const selectedFieldOptions = computed(() =>
|
|
||||||
(schema.value?.fields || []).map((f: any) => ({
|
|
||||||
label: f.fieldName,
|
|
||||||
value: f.fieldName,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
async function handleNodeSelect(node: TreeNode) {
|
async function handleNodeSelect(node: TreeNode) {
|
||||||
selectedNodeKey.value = node.id;
|
selectedNodeKey.value = node.id;
|
||||||
@@ -188,7 +128,10 @@ async function handleBatchRegister(rows: any[]) {
|
|||||||
async function handleBatchRemove(rows: any[]) {
|
async function handleBatchRemove(rows: any[]) {
|
||||||
const removedIds = rows.map((row) => row?.id).filter(Boolean);
|
const removedIds = rows.map((row) => row?.id).filter(Boolean);
|
||||||
await batchRemoveTables(rows);
|
await batchRemoveTables(rows);
|
||||||
if (selectedTableId.value && removedIds.includes(selectedTableId.value)) {
|
if (
|
||||||
|
selectedTableId.value !== null &&
|
||||||
|
removedIds.includes(selectedTableId.value)
|
||||||
|
) {
|
||||||
viewMode.value = 'list';
|
viewMode.value = 'list';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,8 +188,18 @@ async function handleRemoveSource(node: TreeNode) {
|
|||||||
await reloadAll({ focus: 'source', resetTable: true });
|
await reloadAll({ focus: 'source', resetTable: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSaveSource(formData: Record<string, any>) {
|
async function handleSaveSource(
|
||||||
|
formData: Record<string, any>,
|
||||||
|
excelFile?: File,
|
||||||
|
) {
|
||||||
|
if (formData.sourceType === 'EXCEL' && !formData.id) {
|
||||||
|
if (!excelFile) return;
|
||||||
|
await importExcelSource(excelFile, formData.sourceName);
|
||||||
|
ElMessage.success('Excel 已导入');
|
||||||
|
} else {
|
||||||
await saveSource(formData);
|
await saveSource(formData);
|
||||||
|
ElMessage.success(formData.id ? '连接已更新' : '连接已创建');
|
||||||
|
}
|
||||||
sourceFormRef.value?.close();
|
sourceFormRef.value?.close();
|
||||||
sourceFormVisible.value = false;
|
sourceFormVisible.value = false;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@@ -259,58 +212,6 @@ async function handleTestConnection(formData: Record<string, any>) {
|
|||||||
if (formData.id) await loadSources();
|
if (formData.id) await loadSources();
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExcelAction(
|
|
||||||
action: 'derive' | 'export' | 'import' | 'merge' | 'split',
|
|
||||||
) {
|
|
||||||
switch (action) {
|
|
||||||
case 'derive': {
|
|
||||||
resetDeriveForm();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'export': {
|
|
||||||
resetExportForm();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'merge': {
|
|
||||||
resetMergeForm();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'split': {
|
|
||||||
resetSplitForm();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
excelActionRef.value?.open(action);
|
|
||||||
excelActionVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleExcelAction(action: string) {
|
|
||||||
let success = false;
|
|
||||||
switch (action) {
|
|
||||||
case 'derive': {
|
|
||||||
success = await handleDerive();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'export': {
|
|
||||||
success = await handleExport(loadTableRuntime);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'import': {
|
|
||||||
success = await handleImport();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'merge': {
|
|
||||||
success = await handleMerge();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'split': {
|
|
||||||
success = await handleSplit();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (success) excelActionVisible.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadAll() {
|
async function loadAll() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -346,53 +247,6 @@ onBeforeUnmount(() => {
|
|||||||
:style="{ height: workspaceHeight }"
|
:style="{ height: workspaceHeight }"
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
>
|
>
|
||||||
<!-- 顶部工具栏 -->
|
|
||||||
<div v-if="showToolbar" class="workspace-toolbar">
|
|
||||||
<div class="toolbar-left">
|
|
||||||
<EasyFlowButton
|
|
||||||
v-if="canImportExcel"
|
|
||||||
class="toolbar-button"
|
|
||||||
variant="outline"
|
|
||||||
@click="openExcelAction('import')"
|
|
||||||
>
|
|
||||||
导入 Excel
|
|
||||||
</EasyFlowButton>
|
|
||||||
<ElDropdown v-if="canShowMoreActions">
|
|
||||||
<EasyFlowButton class="toolbar-button" variant="outline">
|
|
||||||
更多操作
|
|
||||||
</EasyFlowButton>
|
|
||||||
<template #dropdown>
|
|
||||||
<ElDropdownMenu>
|
|
||||||
<ElDropdownItem
|
|
||||||
v-if="canExport"
|
|
||||||
@click="openExcelAction('export')"
|
|
||||||
>
|
|
||||||
导出
|
|
||||||
</ElDropdownItem>
|
|
||||||
<ElDropdownItem
|
|
||||||
v-if="selectedTable"
|
|
||||||
@click="openExcelAction('split')"
|
|
||||||
>
|
|
||||||
拆分
|
|
||||||
</ElDropdownItem>
|
|
||||||
<ElDropdownItem
|
|
||||||
v-if="managedTables.length > 1"
|
|
||||||
@click="openExcelAction('merge')"
|
|
||||||
>
|
|
||||||
合并
|
|
||||||
</ElDropdownItem>
|
|
||||||
<ElDropdownItem
|
|
||||||
v-if="selectedTable"
|
|
||||||
@click="openExcelAction('derive')"
|
|
||||||
>
|
|
||||||
生成新表
|
|
||||||
</ElDropdownItem>
|
|
||||||
</ElDropdownMenu>
|
|
||||||
</template>
|
|
||||||
</ElDropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 双栏主体 -->
|
<!-- 双栏主体 -->
|
||||||
<ResizablePanelGroup direction="horizontal" class="workspace-body">
|
<ResizablePanelGroup direction="horizontal" class="workspace-body">
|
||||||
<ResizablePanel :default-size="20" :min-size="16" :max-size="30">
|
<ResizablePanel :default-size="20" :min-size="16" :max-size="30">
|
||||||
@@ -447,25 +301,6 @@ onBeforeUnmount(() => {
|
|||||||
@save="handleSaveSource"
|
@save="handleSaveSource"
|
||||||
@test="handleTestConnection"
|
@test="handleTestConnection"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExcelActionDrawer
|
|
||||||
ref="excelActionRef"
|
|
||||||
v-model:visible="excelActionVisible"
|
|
||||||
v-model:split-form="splitForm"
|
|
||||||
v-model:merge-form="mergeForm"
|
|
||||||
v-model:derive-form="deriveForm"
|
|
||||||
v-model:export-form="exportForm"
|
|
||||||
:action-loading="actionLoading"
|
|
||||||
:managed-tables="managedTables"
|
|
||||||
:field-options="selectedFieldOptions"
|
|
||||||
:pending-upload-file="pendingUploadFile"
|
|
||||||
@update:pending-upload-file="(f) => (pendingUploadFile = f)"
|
|
||||||
@import="handleExcelAction('import')"
|
|
||||||
@split="handleExcelAction('split')"
|
|
||||||
@merge="handleExcelAction('merge')"
|
|
||||||
@derive="handleExcelAction('derive')"
|
|
||||||
@export="handleExcelAction('export')"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -481,25 +316,6 @@ onBeforeUnmount(() => {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-toolbar {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding: 0 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar-left {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar-button {
|
|
||||||
min-width: 84px;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-body {
|
.workspace-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormInstance } from 'element-plus';
|
import type { FormInstance, UploadFile, UploadInstance } from 'element-plus';
|
||||||
|
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
@@ -7,20 +7,31 @@ import { EasyFlowFormModal } from '@easyflow/common-ui';
|
|||||||
|
|
||||||
import { EasyFlowButton } from '@easyflow-core/shadcn-ui';
|
import { EasyFlowButton } from '@easyflow-core/shadcn-ui';
|
||||||
|
|
||||||
import { CircleCheckFilled, CircleCloseFilled } from '@element-plus/icons-vue';
|
import {
|
||||||
|
CircleCheckFilled,
|
||||||
|
CircleCloseFilled,
|
||||||
|
UploadFilled,
|
||||||
|
} from '@element-plus/icons-vue';
|
||||||
import {
|
import {
|
||||||
ElForm,
|
ElForm,
|
||||||
ElFormItem,
|
ElFormItem,
|
||||||
ElIcon,
|
ElIcon,
|
||||||
ElInput,
|
ElInput,
|
||||||
|
ElMessage,
|
||||||
ElOption,
|
ElOption,
|
||||||
ElSelect,
|
ElSelect,
|
||||||
|
ElUpload,
|
||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
sourceTypeLabels,
|
sourceTypeLabels,
|
||||||
sourceTypeOptions,
|
sourceTypeOptions,
|
||||||
} from '../composables/datacenter-constants';
|
} from '../composables/datacenter-constants';
|
||||||
|
import {
|
||||||
|
EXCEL_FILE_ACCEPT,
|
||||||
|
isSupportedExcelFileName,
|
||||||
|
sourceNameFromExcelFileName,
|
||||||
|
} from '../composables/datacenter-excel-upload';
|
||||||
import { useSourceForm } from '../composables/use-source-form';
|
import { useSourceForm } from '../composables/use-source-form';
|
||||||
import SourceBrandIcon from './SourceBrandIcon.vue';
|
import SourceBrandIcon from './SourceBrandIcon.vue';
|
||||||
|
|
||||||
@@ -31,13 +42,17 @@ const props = defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
save: [form: Record<string, any>];
|
save: [form: Record<string, any>, excelFile?: File];
|
||||||
test: [form: Record<string, any>];
|
test: [form: Record<string, any>];
|
||||||
'update:visible': [value: boolean];
|
'update:visible': [value: boolean];
|
||||||
}>();
|
}>();
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
|
const excelUploadRef = ref<UploadInstance>();
|
||||||
const testResult = ref<any>(null);
|
const testResult = ref<any>(null);
|
||||||
const showAdvanced = ref(false);
|
const showAdvanced = ref(false);
|
||||||
|
const pendingExcelFile = ref<File | null>(null);
|
||||||
|
const excelFileError = ref('');
|
||||||
|
const lastGeneratedSourceName = ref('');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
form,
|
form,
|
||||||
@@ -60,10 +75,24 @@ const testButtonClass = computed(() => ({
|
|||||||
const selectedSourceTypeLabel = computed(
|
const selectedSourceTypeLabel = computed(
|
||||||
() => sourceTypeLabels[form.sourceType] || form.sourceType,
|
() => sourceTypeLabels[form.sourceType] || form.sourceType,
|
||||||
);
|
);
|
||||||
|
const isExcelCreate = computed(() => form.sourceType === 'EXCEL' && !form.id);
|
||||||
|
const confirmDisabled = computed(
|
||||||
|
() =>
|
||||||
|
isExcelCreate.value &&
|
||||||
|
(!pendingExcelFile.value || !String(form.sourceName || '').trim()),
|
||||||
|
);
|
||||||
|
|
||||||
|
function resetExcelFile() {
|
||||||
|
excelUploadRef.value?.clearFiles();
|
||||||
|
pendingExcelFile.value = null;
|
||||||
|
excelFileError.value = '';
|
||||||
|
lastGeneratedSourceName.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
function resetDialogState() {
|
function resetDialogState() {
|
||||||
testResult.value = null;
|
testResult.value = null;
|
||||||
showAdvanced.value = false;
|
showAdvanced.value = false;
|
||||||
|
resetExcelFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
function open(row?: any) {
|
function open(row?: any) {
|
||||||
@@ -78,8 +107,16 @@ function close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
|
if (isExcelCreate.value && !pendingExcelFile.value) {
|
||||||
|
excelFileError.value = '请选择需要导入的 Excel 文件';
|
||||||
|
return;
|
||||||
|
}
|
||||||
await formRef.value?.validate();
|
await formRef.value?.validate();
|
||||||
emit('save', { ...form });
|
emit(
|
||||||
|
'save',
|
||||||
|
{ ...form },
|
||||||
|
isExcelCreate.value ? pendingExcelFile.value || undefined : undefined,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTest() {
|
async function handleTest() {
|
||||||
@@ -96,6 +133,33 @@ function setTestResult(result: any) {
|
|||||||
testResult.value = result;
|
testResult.value = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleUploadChange(file: UploadFile) {
|
||||||
|
if (!isSupportedExcelFileName(file.name)) {
|
||||||
|
excelUploadRef.value?.clearFiles();
|
||||||
|
pendingExcelFile.value = null;
|
||||||
|
excelFileError.value = '仅支持 .xls 和 .xlsx 格式的 Excel 文件';
|
||||||
|
ElMessage.warning(excelFileError.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const generatedSourceName = sourceNameFromExcelFileName(file.name);
|
||||||
|
if (
|
||||||
|
!String(form.sourceName || '').trim() ||
|
||||||
|
form.sourceName === lastGeneratedSourceName.value
|
||||||
|
) {
|
||||||
|
form.sourceName = generatedSourceName;
|
||||||
|
formRef.value?.clearValidate('sourceName');
|
||||||
|
}
|
||||||
|
lastGeneratedSourceName.value = generatedSourceName;
|
||||||
|
pendingExcelFile.value = file.raw || null;
|
||||||
|
excelFileError.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUploadRemove() {
|
||||||
|
pendingExcelFile.value = null;
|
||||||
|
excelFileError.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [
|
() => [
|
||||||
form.sourceName,
|
form.sourceName,
|
||||||
@@ -117,6 +181,15 @@ watch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => form.sourceType,
|
||||||
|
(_sourceType, previousSourceType) => {
|
||||||
|
if (previousSourceType && previousSourceType !== form.sourceType) {
|
||||||
|
resetExcelFile();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
defineExpose({ close, open, setTestResult });
|
defineExpose({ close, open, setTestResult });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -125,6 +198,7 @@ defineExpose({ close, open, setTestResult });
|
|||||||
:open="visible"
|
:open="visible"
|
||||||
:title="modalTitle"
|
:title="modalTitle"
|
||||||
:before-close="handleBeforeClose"
|
:before-close="handleBeforeClose"
|
||||||
|
:confirm-disabled="confirmDisabled"
|
||||||
:confirm-loading="saving"
|
:confirm-loading="saving"
|
||||||
confirm-text="保存"
|
confirm-text="保存"
|
||||||
:submitting="saving"
|
:submitting="saving"
|
||||||
@@ -141,13 +215,19 @@ defineExpose({ close, open, setTestResult });
|
|||||||
>
|
>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<ElFormItem label="连接名称" prop="sourceName">
|
<ElFormItem label="连接名称" prop="sourceName">
|
||||||
<ElInput v-model="form.sourceName" placeholder="例如:华东 MySQL" />
|
<ElInput
|
||||||
|
v-model="form.sourceName"
|
||||||
|
maxlength="100"
|
||||||
|
:placeholder="
|
||||||
|
isExcelCreate ? '选择文件后自动填写' : '例如:华东 MySQL'
|
||||||
|
"
|
||||||
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="连接类型" prop="sourceType">
|
<ElFormItem label="连接类型" prop="sourceType">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="form.sourceType"
|
v-model="form.sourceType"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
:disabled="Boolean(form.builtinFlag)"
|
:disabled="Boolean(form.builtinFlag || form.id)"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="source-type-select-label">
|
<span class="source-type-select-label">
|
||||||
@@ -174,6 +254,35 @@ defineExpose({ close, open, setTestResult });
|
|||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ElFormItem
|
||||||
|
v-if="isExcelCreate"
|
||||||
|
label="表格文件"
|
||||||
|
required
|
||||||
|
:error="excelFileError"
|
||||||
|
class="excel-file-field"
|
||||||
|
>
|
||||||
|
<ElUpload
|
||||||
|
ref="excelUploadRef"
|
||||||
|
class="excel-upload"
|
||||||
|
drag
|
||||||
|
:accept="EXCEL_FILE_ACCEPT"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="true"
|
||||||
|
:limit="1"
|
||||||
|
:disabled="saving"
|
||||||
|
:on-change="handleUploadChange"
|
||||||
|
:on-remove="handleUploadRemove"
|
||||||
|
>
|
||||||
|
<ElIcon class="excel-upload__icon">
|
||||||
|
<UploadFilled />
|
||||||
|
</ElIcon>
|
||||||
|
<div class="excel-upload__title">拖拽或点击选择 Excel 文件</div>
|
||||||
|
<div class="excel-upload__description">
|
||||||
|
仅支持 .xls、.xlsx;工作表首行需包含表头
|
||||||
|
</div>
|
||||||
|
</ElUpload>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
<template v-if="supportsExternalConnection">
|
<template v-if="supportsExternalConnection">
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<ElFormItem label="主机">
|
<ElFormItem label="主机">
|
||||||
@@ -242,6 +351,7 @@ defineExpose({ close, open, setTestResult });
|
|||||||
|
|
||||||
<template #footer-extra>
|
<template #footer-extra>
|
||||||
<EasyFlowButton
|
<EasyFlowButton
|
||||||
|
v-if="supportsExternalConnection"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:loading="testing"
|
:loading="testing"
|
||||||
:disabled="saving"
|
:disabled="saving"
|
||||||
@@ -308,6 +418,62 @@ defineExpose({ close, open, setTestResult });
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.excel-file-field {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-upload {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-upload :deep(.el-upload) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-upload :deep(.el-upload-dragger) {
|
||||||
|
width: 100%;
|
||||||
|
padding: 24px;
|
||||||
|
background: hsl(var(--surface-subtle) / 72%);
|
||||||
|
border-color: hsl(var(--border) / 76%);
|
||||||
|
border-radius: var(--radius-panel);
|
||||||
|
transition:
|
||||||
|
background-color 0.16s,
|
||||||
|
border-color 0.16s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-upload :deep(.el-upload-dragger:hover),
|
||||||
|
.excel-upload :deep(.el-upload-dragger:focus-visible) {
|
||||||
|
background: hsl(var(--primary) / 5%);
|
||||||
|
border-color: hsl(var(--primary) / 56%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-upload__icon {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 28px;
|
||||||
|
color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-upload__title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-upload__description {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-upload :deep(.el-upload-list) {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.excel-upload :deep(.el-upload-list__item) {
|
||||||
|
border-radius: var(--radius-control);
|
||||||
|
}
|
||||||
|
|
||||||
.toggle-advanced {
|
.toggle-advanced {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ async function saveFieldDescription(field: any) {
|
|||||||
v-for="field in schema?.fields || []"
|
v-for="field in schema?.fields || []"
|
||||||
:key="field.fieldName"
|
:key="field.fieldName"
|
||||||
:prop="field.fieldName"
|
:prop="field.fieldName"
|
||||||
:label="field.fieldName"
|
:label="field.fieldDesc || field.fieldName"
|
||||||
min-width="140"
|
min-width="140"
|
||||||
/>
|
/>
|
||||||
</ElTable>
|
</ElTable>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
isSupportedExcelFileName,
|
||||||
|
sourceNameFromExcelFileName,
|
||||||
|
} from './datacenter-excel-upload';
|
||||||
|
|
||||||
|
describe('datacenter Excel upload', () => {
|
||||||
|
it('accepts only xls and xlsx extensions', () => {
|
||||||
|
expect(isSupportedExcelFileName('预算表.XLS')).toBe(true);
|
||||||
|
expect(isSupportedExcelFileName('预算表.xlsx')).toBe(true);
|
||||||
|
expect(isSupportedExcelFileName('预算表.xlsm')).toBe(false);
|
||||||
|
expect(isSupportedExcelFileName('预算表.csv')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('derives the connection name from the final extension', () => {
|
||||||
|
expect(sourceNameFromExcelFileName('ama 实验基线模型预算.xlsx')).toBe(
|
||||||
|
'ama 实验基线模型预算',
|
||||||
|
);
|
||||||
|
expect(sourceNameFromExcelFileName('模型预算.v2.xls')).toBe('模型预算.v2');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export const EXCEL_FILE_ACCEPT = '.xls,.xlsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断文件名是否属于数据中枢支持的 Excel 格式。
|
||||||
|
*/
|
||||||
|
export function isSupportedExcelFileName(fileName: string) {
|
||||||
|
const normalizedFileName = fileName.trim().toLowerCase();
|
||||||
|
return (
|
||||||
|
normalizedFileName.endsWith('.xls') || normalizedFileName.endsWith('.xlsx')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Excel 文件名生成默认连接名称。
|
||||||
|
*/
|
||||||
|
export function sourceNameFromExcelFileName(fileName: string) {
|
||||||
|
const trimmedFileName = fileName.trim();
|
||||||
|
const extensionIndex = trimmedFileName.lastIndexOf('.');
|
||||||
|
return extensionIndex > 0
|
||||||
|
? trimmedFileName.slice(0, extensionIndex)
|
||||||
|
: trimmedFileName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* 数据中枢接口中的雪花主键会以字符串返回,避免 JavaScript Number 丢失精度。
|
||||||
|
*/
|
||||||
|
export type DatacenterId = string;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
import type { DatacenterId } from './datacenter-id';
|
||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { formatSourceType, formatTestStatus } from './datacenter-constants';
|
import { formatSourceType, formatTestStatus } from './datacenter-constants';
|
||||||
@@ -49,7 +51,7 @@ export function useConnectionTree(sources: Ref<any[]>) {
|
|||||||
const id = idParts.join('-');
|
const id = idParts.join('-');
|
||||||
return {
|
return {
|
||||||
type: type as 'source' | 'table',
|
type: type as 'source' | 'table',
|
||||||
id: Number(id),
|
id: id as DatacenterId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { DatacenterId } from './datacenter-id';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { api } from '#/api/request';
|
import { api } from '#/api/request';
|
||||||
@@ -6,7 +8,7 @@ import { mergeConfigJsonBySourceType } from './datacenter-constants';
|
|||||||
|
|
||||||
export function useDatacenterSources() {
|
export function useDatacenterSources() {
|
||||||
const sources = ref<any[]>([]);
|
const sources = ref<any[]>([]);
|
||||||
const selectedSourceId = ref<null | number>(null);
|
const selectedSourceId = ref<DatacenterId | null>(null);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
const testing = ref(false);
|
const testing = ref(false);
|
||||||
@@ -32,7 +34,7 @@ export function useDatacenterSources() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!selectedSourceId.value ||
|
selectedSourceId.value === null ||
|
||||||
!sources.value.some((item) => item.id === selectedSourceId.value)
|
!sources.value.some((item) => item.id === selectedSourceId.value)
|
||||||
) {
|
) {
|
||||||
selectedSourceId.value = sources.value[0].id;
|
selectedSourceId.value = sources.value[0].id;
|
||||||
@@ -54,7 +56,7 @@ export function useDatacenterSources() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function markSourceRuntimeUnavailable(sourceId?: null | number | string) {
|
function markSourceRuntimeUnavailable(sourceId?: null | number | string) {
|
||||||
if (!sourceId) return;
|
if (sourceId === null || sourceId === undefined) return;
|
||||||
sources.value = sources.value.map((item) =>
|
sources.value = sources.value.map((item) =>
|
||||||
String(item.id) === String(sourceId)
|
String(item.id) === String(sourceId)
|
||||||
? { ...item, runtimeUnavailable: true }
|
? { ...item, runtimeUnavailable: true }
|
||||||
@@ -63,7 +65,7 @@ export function useDatacenterSources() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearSourceRuntimeUnavailable(sourceId?: null | number | string) {
|
function clearSourceRuntimeUnavailable(sourceId?: null | number | string) {
|
||||||
if (!sourceId) return;
|
if (sourceId === null || sourceId === undefined) return;
|
||||||
sources.value = sources.value.map((item) =>
|
sources.value = sources.value.map((item) =>
|
||||||
String(item.id) === String(sourceId)
|
String(item.id) === String(sourceId)
|
||||||
? { ...item, runtimeUnavailable: false }
|
? { ...item, runtimeUnavailable: false }
|
||||||
@@ -71,7 +73,7 @@ export function useDatacenterSources() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeSourceFromState(sourceId: number) {
|
function removeSourceFromState(sourceId: DatacenterId) {
|
||||||
sources.value = sources.value.filter((item) => item.id !== sourceId);
|
sources.value = sources.value.filter((item) => item.id !== sourceId);
|
||||||
syncSelectedSourceAfterMutation();
|
syncSelectedSourceAfterMutation();
|
||||||
}
|
}
|
||||||
@@ -126,6 +128,26 @@ export function useDatacenterSources() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function importExcelSource(file: File, sourceName: string) {
|
||||||
|
saving.value = true;
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('sourceName', sourceName.trim());
|
||||||
|
const res = await api.postFile(
|
||||||
|
'/api/v1/datacenterExcel/import',
|
||||||
|
formData,
|
||||||
|
{
|
||||||
|
timeout: 10 * 60 * 1000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
selectedSourceId.value = res.data?.sourceId || null;
|
||||||
|
return res.data;
|
||||||
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function testConnection(target: Record<string, any>) {
|
async function testConnection(target: Record<string, any>) {
|
||||||
testing.value = true;
|
testing.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -164,7 +186,7 @@ export function useDatacenterSources() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeSource(sourceId: number) {
|
async function removeSource(sourceId: DatacenterId) {
|
||||||
await api.post('/api/v1/datacenterSource/remove', { sourceId });
|
await api.post('/api/v1/datacenterSource/remove', { sourceId });
|
||||||
removeSourceFromState(sourceId);
|
removeSourceFromState(sourceId);
|
||||||
}
|
}
|
||||||
@@ -176,6 +198,7 @@ export function useDatacenterSources() {
|
|||||||
loading,
|
loading,
|
||||||
saving,
|
saving,
|
||||||
testing,
|
testing,
|
||||||
|
importExcelSource,
|
||||||
loadSources,
|
loadSources,
|
||||||
removeSource,
|
removeSource,
|
||||||
removeSourceFromState,
|
removeSourceFromState,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
import type { DatacenterId } from './datacenter-id';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
@@ -7,12 +9,12 @@ import { ElMessage } from 'element-plus';
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
interface DatacenterTableRuntimeOptions {
|
interface DatacenterTableRuntimeOptions {
|
||||||
clearSourceRuntimeUnavailable?: (sourceId?: null | number | string) => void;
|
clearSourceRuntimeUnavailable?: (sourceId?: DatacenterId | null) => void;
|
||||||
markSourceRuntimeUnavailable?: (sourceId?: null | number | string) => void;
|
markSourceRuntimeUnavailable?: (sourceId?: DatacenterId | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDatacenterTables(
|
export function useDatacenterTables(
|
||||||
selectedSourceId: Ref<null | number>,
|
selectedSourceId: Ref<DatacenterId | null>,
|
||||||
options: DatacenterTableRuntimeOptions = {},
|
options: DatacenterTableRuntimeOptions = {},
|
||||||
) {
|
) {
|
||||||
const SOURCE_MISSING_MESSAGE = '连接不存在';
|
const SOURCE_MISSING_MESSAGE = '连接不存在';
|
||||||
@@ -24,8 +26,8 @@ export function useDatacenterTables(
|
|||||||
const previewRows = ref<any[]>([]);
|
const previewRows = ref<any[]>([]);
|
||||||
const jobs = ref<any[]>([]);
|
const jobs = ref<any[]>([]);
|
||||||
const sourceUnavailable = ref(false);
|
const sourceUnavailable = ref(false);
|
||||||
const selectedCatalogId = ref<null | number>(null);
|
const selectedCatalogId = ref<DatacenterId | null>(null);
|
||||||
const selectedTableId = ref<null | number>(null);
|
const selectedTableId = ref<DatacenterId | null>(null);
|
||||||
const previewLoading = ref(false);
|
const previewLoading = ref(false);
|
||||||
|
|
||||||
const selectedCatalog = computed(
|
const selectedCatalog = computed(
|
||||||
@@ -66,7 +68,7 @@ export function useDatacenterTables(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadCatalogs() {
|
async function loadCatalogs() {
|
||||||
if (!selectedSourceId.value) {
|
if (selectedSourceId.value === null) {
|
||||||
catalogs.value = [];
|
catalogs.value = [];
|
||||||
selectedCatalogId.value = null;
|
selectedCatalogId.value = null;
|
||||||
sourceUnavailable.value = false;
|
sourceUnavailable.value = false;
|
||||||
@@ -87,7 +89,7 @@ export function useDatacenterTables(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!selectedCatalogId.value ||
|
selectedCatalogId.value === null ||
|
||||||
!catalogs.value.some((item) => item.id === selectedCatalogId.value)
|
!catalogs.value.some((item) => item.id === selectedCatalogId.value)
|
||||||
) {
|
) {
|
||||||
selectedCatalogId.value = catalogs.value[0].id;
|
selectedCatalogId.value = catalogs.value[0].id;
|
||||||
@@ -107,7 +109,7 @@ export function useDatacenterTables(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadSourceTables() {
|
async function loadSourceTables() {
|
||||||
if (!selectedSourceId.value) {
|
if (selectedSourceId.value === null) {
|
||||||
sourceTables.value = [];
|
sourceTables.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -130,7 +132,7 @@ export function useDatacenterTables(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadManagedTables() {
|
async function loadManagedTables() {
|
||||||
if (!selectedSourceId.value) {
|
if (selectedSourceId.value === null) {
|
||||||
managedTables.value = [];
|
managedTables.value = [];
|
||||||
selectedTableId.value = null;
|
selectedTableId.value = null;
|
||||||
return;
|
return;
|
||||||
@@ -158,7 +160,7 @@ export function useDatacenterTables(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!selectedTableId.value ||
|
selectedTableId.value === null ||
|
||||||
!managedTables.value.some((item) => item.id === selectedTableId.value)
|
!managedTables.value.some((item) => item.id === selectedTableId.value)
|
||||||
) {
|
) {
|
||||||
selectedTableId.value = managedTables.value[0].id;
|
selectedTableId.value = managedTables.value[0].id;
|
||||||
@@ -166,7 +168,7 @@ export function useDatacenterTables(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadTableRuntime() {
|
async function loadTableRuntime() {
|
||||||
if (!selectedTableId.value) {
|
if (selectedTableId.value === null) {
|
||||||
schema.value = null;
|
schema.value = null;
|
||||||
previewRows.value = [];
|
previewRows.value = [];
|
||||||
jobs.value = [];
|
jobs.value = [];
|
||||||
@@ -279,7 +281,10 @@ export function useDatacenterTables(
|
|||||||
tableIds,
|
tableIds,
|
||||||
});
|
});
|
||||||
ElMessage.success(`已去除 ${tableIds.length} 张表`);
|
ElMessage.success(`已去除 ${tableIds.length} 张表`);
|
||||||
if (selectedTableId.value && tableIds.includes(selectedTableId.value)) {
|
if (
|
||||||
|
selectedTableId.value !== null &&
|
||||||
|
tableIds.includes(selectedTableId.value)
|
||||||
|
) {
|
||||||
selectedTableId.value = null;
|
selectedTableId.value = null;
|
||||||
schema.value = null;
|
schema.value = null;
|
||||||
previewRows.value = [];
|
previewRows.value = [];
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function useSourceForm() {
|
|||||||
id: undefined,
|
id: undefined,
|
||||||
sourceName: '',
|
sourceName: '',
|
||||||
sourceCode: '',
|
sourceCode: '',
|
||||||
sourceType: 'MYSQL',
|
sourceType: 'EXCEL',
|
||||||
accessMode: 'READ_ONLY',
|
accessMode: 'READ_ONLY',
|
||||||
driverClassName: '',
|
driverClassName: '',
|
||||||
jdbcUrl: '',
|
jdbcUrl: '',
|
||||||
@@ -33,6 +33,11 @@ export function useSourceForm() {
|
|||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
sourceName: [
|
sourceName: [
|
||||||
{ required: true, message: '请输入连接名称', trigger: 'blur' },
|
{ required: true, message: '请输入连接名称', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
max: 100,
|
||||||
|
message: '连接名称不能超过 100 个字符',
|
||||||
|
trigger: ['blur', 'change'],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
sourceType: [
|
sourceType: [
|
||||||
{ required: true, message: '请选择连接类型', trigger: 'change' },
|
{ required: true, message: '请选择连接类型', trigger: 'change' },
|
||||||
@@ -138,7 +143,7 @@ export function useSourceForm() {
|
|||||||
form.id = row?.id;
|
form.id = row?.id;
|
||||||
form.sourceName = row?.sourceName || '';
|
form.sourceName = row?.sourceName || '';
|
||||||
form.sourceCode = row?.sourceCode || '';
|
form.sourceCode = row?.sourceCode || '';
|
||||||
form.sourceType = row?.sourceType || 'MYSQL';
|
form.sourceType = row?.sourceType || 'EXCEL';
|
||||||
form.accessMode = row?.accessMode || 'READ_ONLY';
|
form.accessMode = row?.accessMode || 'READ_ONLY';
|
||||||
form.driverClassName = row?.driverClassName || '';
|
form.driverClassName = row?.driverClassName || '';
|
||||||
form.jdbcUrl = row?.jdbcUrl || '';
|
form.jdbcUrl = row?.jdbcUrl || '';
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface Props {
|
|||||||
centered?: boolean;
|
centered?: boolean;
|
||||||
closable?: boolean;
|
closable?: boolean;
|
||||||
closeOnClickModal?: boolean;
|
closeOnClickModal?: boolean;
|
||||||
|
confirmDisabled?: boolean;
|
||||||
confirmLoading?: boolean;
|
confirmLoading?: boolean;
|
||||||
confirmText?: string;
|
confirmText?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@@ -40,6 +41,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
centered: false,
|
centered: false,
|
||||||
closable: true,
|
closable: true,
|
||||||
closeOnClickModal: false,
|
closeOnClickModal: false,
|
||||||
|
confirmDisabled: false,
|
||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
confirmText: '',
|
confirmText: '',
|
||||||
description: '',
|
description: '',
|
||||||
@@ -124,6 +126,7 @@ function handleConfirm() {
|
|||||||
:centered="centered"
|
:centered="centered"
|
||||||
:closable="closable"
|
:closable="closable"
|
||||||
:close-on-click-modal="closeOnClickModal"
|
:close-on-click-modal="closeOnClickModal"
|
||||||
|
:confirm-disabled="confirmDisabled || submitting"
|
||||||
:confirm-loading="confirmLoading"
|
:confirm-loading="confirmLoading"
|
||||||
content-class="p-0"
|
content-class="p-0"
|
||||||
:description="description"
|
:description="description"
|
||||||
@@ -164,7 +167,7 @@ function handleConfirm() {
|
|||||||
</EasyFlowButton>
|
</EasyFlowButton>
|
||||||
<EasyFlowButton
|
<EasyFlowButton
|
||||||
v-if="showConfirmButton"
|
v-if="showConfirmButton"
|
||||||
:disabled="submitting"
|
:disabled="confirmDisabled || submitting"
|
||||||
:loading="confirmLoading || submitting"
|
:loading="confirmLoading || submitting"
|
||||||
@click="handleConfirm"
|
@click="handleConfirm"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user