Files
nl-admin-view/apps/web-antd/src/views/code-generation/components/database-select.vue
李琦 eae4896e48
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
更新数据库管理
2026-01-23 17:13:48 +08:00

423 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { Button, Card, Input, message, Select, Table } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { codeGenerationForm } from '#/views/code-generation/config/form';
import type { CodeGenData } from '../utils/data-transformer';
import { validateData } from '../utils/data-transformer';
import {
generateFromTableApi,
getTableStructureApi,
getTablesApi,
} from '../api';
const emit = defineEmits<{
generate: [data: CodeGenData];
back: [];
}>();
// 表单相关
const [Form, formApi] = useVbenForm(codeGenerationForm);
const formData = ref<Record<string, any>>({
class_name: '',
class_comment: '',
icon: '',
sort: 9999,
pid: 0,
});
// 表列表
const tableList = ref<any[]>([]);
const tableLoading = ref(false);
const searchKeyword = ref('');
const selectedTable = ref<string>('');
// 表结构数据
const tableStructure = ref<CodeGenData | null>(null);
const structureLoading = ref(false);
// 过滤后的表列表
const filteredTableList = computed(() => {
if (!searchKeyword.value) {
return tableList.value;
}
return tableList.value.filter(
(table) =>
table.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
(table.comment && table.comment.toLowerCase().includes(searchKeyword.value.toLowerCase())),
);
});
// 加载表列表
const loadTableList = async () => {
tableLoading.value = true;
try {
const res = await getTablesApi();
tableList.value = res || [];
} catch (error: any) {
message.error(error.message || '加载表列表失败');
tableList.value = [];
} finally {
tableLoading.value = false;
}
};
// 选择表
const handleSelectTable = async (tableName: string) => {
if (!tableName) {
selectedTable.value = '';
tableStructure.value = null;
formApi.resetFields();
return;
}
selectedTable.value = tableName;
structureLoading.value = true;
try {
// 获取表结构
const structureRes = await getTableStructureApi(tableName);
const fields = structureRes || [];
// 生成类名(从表名转换)
const prefix = 'nl_';
let className = tableName.replace(prefix, '');
className = className
.split('_')
.map((word: string) => word.charAt(0).toUpperCase() + word.slice(1))
.join('');
// 获取表注释作为中文名称
const tableInfo = tableList.value.find((t) => t.name === tableName);
const classComment = tableInfo?.comment || tableName;
// 构建代码生成数据
tableStructure.value = {
class_name: className,
class_comment: classComment,
icon: '',
sort: 9999,
pid: 0,
field: fields,
};
// 更新表单数据
formData.value = {
class_name: className,
class_comment: classComment,
icon: '',
sort: 9999,
pid: 0,
};
formApi.setValues(formData.value);
} catch (error: any) {
message.error(error.message || '获取表结构失败');
tableStructure.value = null;
} finally {
structureLoading.value = false;
}
};
// 生成代码
const handleGenerate = async () => {
if (!selectedTable.value) {
message.warning('请先选择数据库表');
return;
}
if (!tableStructure.value) {
message.warning('表结构数据未加载,请重试');
return;
}
// 验证数据
const validation = validateData(tableStructure.value);
if (!validation.valid) {
message.error(validation.message);
return;
}
// 合并表单数据(用户可能修改了基础信息)
const formValues = await formApi.getValues();
const finalData: CodeGenData = {
...tableStructure.value,
class_name: formValues.class_name || tableStructure.value.class_name,
class_comment: formValues.class_comment || tableStructure.value.class_comment,
icon: formValues.icon || tableStructure.value.icon,
sort: formValues.sort ?? tableStructure.value.sort,
pid: formValues.pid ?? tableStructure.value.pid,
};
emit('generate', finalData);
};
// 表列配置
const tableColumns = [
{
title: '表名',
dataIndex: 'name',
key: 'name',
},
{
title: '注释',
dataIndex: 'comment',
key: 'comment',
},
{
title: '记录数',
dataIndex: 'rows',
key: 'rows',
width: 100,
},
];
onMounted(() => {
loadTableList();
});
</script>
<template>
<div class="database-select">
<!-- 顶部操作栏 -->
<Card class="mb-4">
<template #title>
<div class="flex items-center gap-2">
<Button type="text" @click="emit('back')">
<svg
class="mr-2 inline-block"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.75 3.5L5.25 7L8.75 10.5"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
返回
</Button>
<span class="text-lg font-semibold">数据库表模式</span>
</div>
</template>
<div class="mb-4">
<Input
v-model:value="searchKeyword"
placeholder="搜索表名或注释..."
allow-clear
class="mb-4"
>
<template #prefix>
<i class="fa-solid fa-search"></i>
</template>
</Input>
<div class="mb-4">
<div class="mb-2 text-sm text-gray-600 dark:text-gray-400">
选择数据库表
</div>
<Select
v-model:value="selectedTable"
placeholder="请选择数据库表"
show-search
:filter-option="false"
style="width: 100%"
:loading="tableLoading"
@change="handleSelectTable"
>
<Select.Option
v-for="table in filteredTableList"
:key="table.name"
:value="table.name"
>
<div class="flex items-center justify-between">
<span>{{ table.name }}</span>
<span class="ml-2 text-xs text-gray-400">
{{ table.comment || '-' }}
</span>
</div>
</Select.Option>
</Select>
</div>
<div v-if="selectedTable" class="rounded-lg bg-blue-50 p-3 text-sm text-blue-700 dark:bg-blue-900/20 dark:text-blue-400">
<i class="fa-solid fa-info-circle mr-2"></i>
已选择表<strong>{{ selectedTable }}</strong>
<span v-if="tableStructure">
{{ tableStructure.field?.length || 0 }} 个字段
</span>
</div>
</div>
</Card>
<!-- 内容区域 -->
<div v-if="tableStructure" class="content-area mb-24 grid grid-cols-1 gap-4 lg:grid-cols-2">
<Card title="基础信息" class="shadow-sm">
<Form />
</Card>
<Card title="字段预览" class="shadow-sm">
<div v-if="structureLoading" class="flex h-64 items-center justify-center">
<div class="text-center">
<i class="fa-solid fa-spinner fa-spin text-2xl text-primary mb-2"></i>
<p class="text-sm text-gray-500 dark:text-gray-400">加载表结构中...</p>
</div>
</div>
<div v-else class="max-h-[500px] overflow-auto">
<Table
:columns="[
{ title: '字段名', dataIndex: 'name', key: 'name' },
{ title: '类型', dataIndex: 'type', key: 'type' },
{ title: '长度', dataIndex: 'type_length', key: 'type_length' },
{ title: '注释', dataIndex: 'comment', key: 'comment' },
{ title: '必填', dataIndex: 'not_null', key: 'not_null', customRender: (text: any) => text ? '是' : '否' },
]"
:data-source="tableStructure.field"
:pagination="false"
size="small"
/>
</div>
<div class="mt-4 flex items-center gap-2 text-sm text-gray-500">
<i class="fa-solid fa-list mr-1"></i>
<span> {{ tableStructure.field?.length || 0 }} 个字段</span>
</div>
</Card>
</div>
<!-- 固定底部操作栏 -->
<div class="fixed-bottom-bar">
<div class="fixed-bottom-bar-wrapper">
<div class="fixed-bottom-bar-content">
<!-- 左侧状态信息 -->
<div class="flex items-center gap-3">
<div
class="flex h-10 w-10 items-center justify-center rounded-full transition-all"
:class="
tableStructure
? 'bg-primary/10 text-primary'
: 'bg-gray-100 text-gray-400 dark:bg-gray-800 dark:text-gray-600'
"
>
<i
:class="tableStructure ? 'fa-solid fa-check-circle' : 'fa-solid fa-circle-info'"
></i>
</div>
<div>
<div
class="text-sm font-medium"
:class="
tableStructure
? 'text-gray-900 dark:text-gray-100'
: 'text-gray-500 dark:text-gray-400'
"
>
<span v-if="tableStructure">
已加载 <span class="text-primary font-semibold">{{ tableStructure.field?.length || 0 }}</span> 个字段
</span>
<span v-else>请先选择数据库表</span>
</div>
<div class="text-xs text-gray-400 dark:text-gray-500">
{{ tableStructure ? '数据已就绪,可以生成代码' : '等待选择表...' }}
</div>
</div>
</div>
<!-- 右侧操作按钮 -->
<div class="flex items-center gap-2">
<Button
type="primary"
size="large"
class="action-btn-primary"
:disabled="!tableStructure"
@click="handleGenerate"
>
<i class="fa-solid fa-code mr-2"></i>生成代码
</Button>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.database-select {
animation: fade-slide-enter 0.3s ease-out;
padding-bottom: 120px;
}
.content-area {
min-height: 400px;
}
/* 固定底部操作栏 */
.fixed-bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
padding: 0 24px 24px;
pointer-events: none;
}
.fixed-bottom-bar-wrapper {
max-width: 1400px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 16px;
box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: all;
}
.dark .fixed-bottom-bar-wrapper {
background: rgba(31, 41, 55, 0.98);
border-color: rgba(255, 255, 255, 0.12);
box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.fixed-bottom-bar-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
gap: 24px;
}
.action-btn-primary {
border-radius: 8px;
font-weight: 600;
padding: 0 24px;
height: 44px;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
transition: all 0.2s ease;
}
.action-btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(24, 144, 255, 0.4);
}
@keyframes fade-slide-enter {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
</style>