feat: 重构数据中枢工作台与接入管理
- 新增统一的数据源、目录、纳管表与 Excel 处理后端能力 - 重建管理端数据中枢工作台并替换旧表管理页面 - 补充数据中枢迁移脚本、连接器底座与说明字段支持
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance } from 'element-plus';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { EasyFlowFormModal } from '@easyflow/common-ui';
|
||||
|
||||
import { EasyFlowButton } from '@easyflow-core/shadcn-ui';
|
||||
|
||||
import { CircleCheckFilled, CircleCloseFilled } from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElIcon,
|
||||
ElInput,
|
||||
ElOption,
|
||||
ElSelect,
|
||||
} from 'element-plus';
|
||||
|
||||
import {
|
||||
sourceTypeLabels,
|
||||
sourceTypeOptions,
|
||||
} from '../composables/datacenter-constants';
|
||||
import { useSourceForm } from '../composables/use-source-form';
|
||||
import SourceBrandIcon from './SourceBrandIcon.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
saving: boolean;
|
||||
testing: boolean;
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
save: [form: Record<string, any>];
|
||||
test: [form: Record<string, any>];
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
const formRef = ref<FormInstance>();
|
||||
const testResult = ref<any>(null);
|
||||
const showAdvanced = ref(false);
|
||||
|
||||
const {
|
||||
form,
|
||||
rules,
|
||||
supportsExternalConnection,
|
||||
resetForm: baseResetForm,
|
||||
} = useSourceForm();
|
||||
|
||||
const modalTitle = computed(() => (form.id ? '编辑连接' : '新建连接'));
|
||||
const testButtonLabel = computed(() => {
|
||||
if (props.testing) return '测试中...';
|
||||
if (testResult.value?.success === true) return '连接成功';
|
||||
if (testResult.value?.success === false) return '连接失败';
|
||||
return '测试连接';
|
||||
});
|
||||
const testButtonClass = computed(() => ({
|
||||
'is-test-success': testResult.value?.success === true,
|
||||
'is-test-failed': testResult.value?.success === false,
|
||||
}));
|
||||
const selectedSourceTypeLabel = computed(
|
||||
() => sourceTypeLabels[form.sourceType] || form.sourceType,
|
||||
);
|
||||
|
||||
function resetDialogState() {
|
||||
testResult.value = null;
|
||||
showAdvanced.value = false;
|
||||
}
|
||||
|
||||
function open(row?: any) {
|
||||
baseResetForm(row);
|
||||
resetDialogState();
|
||||
emit('update:visible', true);
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('update:visible', false);
|
||||
resetDialogState();
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
await formRef.value?.validate();
|
||||
emit('save', { ...form });
|
||||
}
|
||||
|
||||
async function handleTest() {
|
||||
await formRef.value?.validate();
|
||||
testResult.value = null;
|
||||
emit('test', { ...form });
|
||||
}
|
||||
|
||||
function handleBeforeClose() {
|
||||
resetDialogState();
|
||||
}
|
||||
|
||||
function setTestResult(result: any) {
|
||||
testResult.value = result;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [
|
||||
form.sourceName,
|
||||
form.sourceType,
|
||||
form.host,
|
||||
form.port,
|
||||
form.databaseName,
|
||||
form.username,
|
||||
form.password,
|
||||
form.jdbcUrl,
|
||||
form.driverClassName,
|
||||
form.configJson?.serviceName,
|
||||
form.configJson?.informixServer,
|
||||
],
|
||||
() => {
|
||||
if (!props.testing && testResult.value) {
|
||||
testResult.value = null;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
defineExpose({ close, open, setTestResult });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EasyFlowFormModal
|
||||
:open="visible"
|
||||
:title="modalTitle"
|
||||
:before-close="handleBeforeClose"
|
||||
:confirm-loading="saving"
|
||||
confirm-text="保存"
|
||||
:submitting="saving"
|
||||
width="800px"
|
||||
@update:open="emit('update:visible', $event)"
|
||||
@confirm="handleSave"
|
||||
>
|
||||
<ElForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
class="easyflow-modal-form easyflow-modal-form--compact source-form"
|
||||
>
|
||||
<div class="form-grid">
|
||||
<ElFormItem label="连接名称" prop="sourceName">
|
||||
<ElInput v-model="form.sourceName" placeholder="例如:华东 MySQL" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="连接类型" prop="sourceType">
|
||||
<ElSelect
|
||||
v-model="form.sourceType"
|
||||
class="w-full"
|
||||
:disabled="Boolean(form.builtinFlag)"
|
||||
>
|
||||
<template #label>
|
||||
<span class="source-type-select-label">
|
||||
<span class="source-type-select-icon">
|
||||
<SourceBrandIcon :source-type="form.sourceType" />
|
||||
</span>
|
||||
<span>{{ selectedSourceTypeLabel }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<ElOption
|
||||
v-for="item in sourceTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
<div class="source-type-option">
|
||||
<span class="source-type-option__icon">
|
||||
<SourceBrandIcon :source-type="item.value" />
|
||||
</span>
|
||||
<span class="source-type-option__label">{{ item.label }}</span>
|
||||
</div>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
|
||||
<template v-if="supportsExternalConnection">
|
||||
<div class="form-grid">
|
||||
<ElFormItem label="主机">
|
||||
<ElInput v-model="form.host" placeholder="db.example.com" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="端口">
|
||||
<ElInput v-model="form.port" placeholder="5432" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="库名" prop="databaseName">
|
||||
<ElInput v-model="form.databaseName" placeholder="例如:easyflow" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="用户名">
|
||||
<ElInput v-model="form.username" placeholder="username" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="密码" class="span-2">
|
||||
<ElInput
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="password"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-advanced"
|
||||
@click="showAdvanced = !showAdvanced"
|
||||
>
|
||||
{{ showAdvanced ? '收起高级设置' : '高级设置' }}
|
||||
</button>
|
||||
|
||||
<div v-if="showAdvanced" class="form-grid form-grid--advanced">
|
||||
<ElFormItem label="连接地址" class="span-2">
|
||||
<ElInput v-model="form.jdbcUrl" placeholder="留空由系统自动生成" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="驱动类" class="span-2">
|
||||
<ElInput
|
||||
v-model="form.driverClassName"
|
||||
placeholder="留空由系统自动生成"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="form.sourceType === 'ORACLE'"
|
||||
label="Oracle 服务名"
|
||||
class="span-2"
|
||||
>
|
||||
<ElInput
|
||||
v-model="form.configJson.serviceName"
|
||||
placeholder="可选,未填写时默认使用数据库名"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="form.sourceType === 'GBASE_8S'"
|
||||
label="GBase 实例名"
|
||||
class="span-2"
|
||||
>
|
||||
<ElInput
|
||||
v-model="form.configJson.informixServer"
|
||||
placeholder="默认 gbasedbt_server"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
</template>
|
||||
</ElForm>
|
||||
|
||||
<template #footer-extra>
|
||||
<EasyFlowButton
|
||||
variant="outline"
|
||||
:loading="testing"
|
||||
:disabled="saving"
|
||||
class="test-button"
|
||||
:class="testButtonClass"
|
||||
@click="handleTest"
|
||||
>
|
||||
<ElIcon v-if="!testing && testResult?.success === true">
|
||||
<CircleCheckFilled />
|
||||
</ElIcon>
|
||||
<ElIcon v-else-if="!testing && testResult?.success === false">
|
||||
<CircleCloseFilled />
|
||||
</ElIcon>
|
||||
{{ testButtonLabel }}
|
||||
</EasyFlowButton>
|
||||
</template>
|
||||
</EasyFlowFormModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.source-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0 16px;
|
||||
}
|
||||
|
||||
.form-grid--advanced {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.source-type-select-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.source-type-select-icon,
|
||||
.source-type-option__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.source-type-option {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.source-type-option__label {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.span-2 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.toggle-advanced {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: hsl(var(--primary));
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-advanced:hover {
|
||||
color: hsl(var(--primary) / 0.86);
|
||||
}
|
||||
|
||||
.test-button {
|
||||
min-width: 112px;
|
||||
transition:
|
||||
color 0.16s,
|
||||
border-color 0.16s,
|
||||
background-color 0.16s;
|
||||
}
|
||||
|
||||
.test-button :deep(.el-icon) {
|
||||
margin-right: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.test-button.is-test-success {
|
||||
color: hsl(var(--success));
|
||||
border-color: hsl(var(--success) / 0.24);
|
||||
background: hsl(var(--success) / 0.08);
|
||||
}
|
||||
|
||||
.test-button.is-test-success:hover {
|
||||
color: hsl(var(--success));
|
||||
border-color: hsl(var(--success) / 0.28);
|
||||
background: hsl(var(--success) / 0.12);
|
||||
}
|
||||
|
||||
.test-button.is-test-failed {
|
||||
color: hsl(var(--destructive));
|
||||
border-color: hsl(var(--destructive) / 0.22);
|
||||
background: hsl(var(--destructive) / 0.08);
|
||||
}
|
||||
|
||||
.test-button.is-test-failed:hover {
|
||||
color: hsl(var(--destructive));
|
||||
border-color: hsl(var(--destructive) / 0.28);
|
||||
background: hsl(var(--destructive) / 0.12);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user