feat: 代码生成
This commit is contained in:
45
apps/web-antd/src/views/infra/codegen/modules/basic-info.vue
Normal file
45
apps/web-antd/src/views/infra/codegen/modules/basic-info.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts" setup>
|
||||
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { useBasicInfoFormSchema } from '../data';
|
||||
|
||||
const props = defineProps<{
|
||||
table: InfraCodegenApi.CodegenTable;
|
||||
}>();
|
||||
|
||||
/** 表单实例 */
|
||||
const [Form, formApi] = useVbenForm({
|
||||
// 配置表单布局为两列
|
||||
wrapperClass: 'grid grid-cols-1 md:grid-cols-2 gap-4',
|
||||
schema: useBasicInfoFormSchema(),
|
||||
layout: 'horizontal',
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
/** 动态更新表单值 */
|
||||
watch(
|
||||
() => props.table,
|
||||
(val: any) => {
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
formApi.setValues(val);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** 暴露出表单校验方法和表单值获取方法 */
|
||||
defineExpose({
|
||||
validate: async () => {
|
||||
const { valid } = await formApi.validate();
|
||||
return valid;
|
||||
},
|
||||
getValues: formApi.getValues,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Form />
|
||||
</template>
|
||||
152
apps/web-antd/src/views/infra/codegen/modules/column-info.vue
Normal file
152
apps/web-antd/src/views/infra/codegen/modules/column-info.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<script lang="ts" setup>
|
||||
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||
import type { SystemDictTypeApi } from '#/api/system/dict/type';
|
||||
|
||||
import { Checkbox, Input, Select } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getSimpleDictTypeList } from '#/api/system/dict/type';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { useCodegenColumnTableColumns } from '../data';
|
||||
|
||||
defineOptions({ name: 'InfraCodegenColumInfoForm' });
|
||||
|
||||
const props = defineProps<{
|
||||
columns?: InfraCodegenApi.CodegenColumn[];
|
||||
}>();
|
||||
|
||||
/** 表格配置 */
|
||||
const [Grid, extendedApi] = useVbenVxeGrid({
|
||||
gridOptions: {
|
||||
columns: useCodegenColumnTableColumns(),
|
||||
border: true,
|
||||
showOverflow: true,
|
||||
height: 'auto',
|
||||
autoResize: true,
|
||||
keepSource: true,
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
pagerConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
toolbarConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** 监听外部传入的列数据 */
|
||||
watch(
|
||||
() => props.columns,
|
||||
(columns) => {
|
||||
if (!columns) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
extendedApi.grid?.loadData(columns);
|
||||
}, 100);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
/** 提供获取表格数据的方法供父组件调用 */
|
||||
defineExpose({
|
||||
getData: (): InfraCodegenApi.CodegenColumn[] => extendedApi.grid.getData(),
|
||||
});
|
||||
|
||||
/** 字典类型选项 */
|
||||
const dictTypeOptions = ref<{ label: string; value: string }[]>([]);
|
||||
const loadDictTypeOptions = async () => {
|
||||
const dictTypes = await getSimpleDictTypeList();
|
||||
dictTypeOptions.value = dictTypes.map((dict: SystemDictTypeApi.SystemDictType) => ({
|
||||
label: dict.name,
|
||||
value: dict.type,
|
||||
}));
|
||||
};
|
||||
|
||||
loadDictTypeOptions();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Grid>
|
||||
<!-- 字段描述 -->
|
||||
<template #columnComment="{ row }">
|
||||
<Input v-model:value="row.columnComment" />
|
||||
</template>
|
||||
|
||||
<!-- Java类型 -->
|
||||
<template #javaType="{ row, column }">
|
||||
<Select v-model:value="row.javaType" style="width: 100%">
|
||||
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<!-- Java属性 -->
|
||||
<template #javaField="{ row }">
|
||||
<Input v-model:value="row.javaField" />
|
||||
</template>
|
||||
|
||||
<!-- 插入 -->
|
||||
<template #createOperation="{ row }">
|
||||
<Checkbox v-model:checked="row.createOperation" />
|
||||
</template>
|
||||
|
||||
<!-- 编辑 -->
|
||||
<template #updateOperation="{ row }">
|
||||
<Checkbox v-model:checked="row.updateOperation" />
|
||||
</template>
|
||||
|
||||
<!-- 列表 -->
|
||||
<template #listOperationResult="{ row }">
|
||||
<Checkbox v-model:checked="row.listOperationResult" />
|
||||
</template>
|
||||
|
||||
<!-- 查询 -->
|
||||
<template #listOperation="{ row }">
|
||||
<Checkbox v-model:checked="row.listOperation" />
|
||||
</template>
|
||||
|
||||
<!-- 查询方式 -->
|
||||
<template #listOperationCondition="{ row, column }">
|
||||
<Select v-model:value="row.listOperationCondition" style="width: 100%">
|
||||
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<!-- 允许空 -->
|
||||
<template #nullable="{ row }">
|
||||
<Checkbox v-model:checked="row.nullable" />
|
||||
</template>
|
||||
|
||||
<!-- 显示类型 -->
|
||||
<template #htmlType="{ row, column }">
|
||||
<Select v-model:value="row.htmlType" style="width: 100%">
|
||||
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<!-- 字典类型 -->
|
||||
<template #dictType="{ row }">
|
||||
<Select v-model:value="row.dictType" style="width: 100%" allow-clear show-search>
|
||||
<Select.Option v-for="option in dictTypeOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<!-- 示例 -->
|
||||
<template #example="{ row }">
|
||||
<Input v-model:value="row.example" />
|
||||
</template>
|
||||
</Grid>
|
||||
</template>
|
||||
@@ -0,0 +1,161 @@
|
||||
<script lang="ts" setup>
|
||||
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { getCodegenTableList } from '#/api/infra/codegen';
|
||||
import { InfraCodegenTemplateTypeEnum } from '#/utils/constants';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useGenerationInfoBaseFormSchema, useSubTableFormSchema, useTreeTableFormSchema } from '../data';
|
||||
|
||||
defineOptions({ name: 'InfraCodegenGenerateInfoForm' });
|
||||
|
||||
const props = defineProps<{
|
||||
columns?: InfraCodegenApi.CodegenColumn[];
|
||||
table?: InfraCodegenApi.CodegenTable;
|
||||
}>();
|
||||
|
||||
const tables = ref<InfraCodegenApi.CodegenTable[]>([]);
|
||||
const currentTemplateType = ref<number>();
|
||||
const wrapperClass = 'grid grid-cols-1 md:grid-cols-2 gap-4 mb-4'; // 一行两列布局
|
||||
/** 计算当前模板类型 */
|
||||
const isTreeTable = computed(() => currentTemplateType.value === InfraCodegenTemplateTypeEnum.TREE);
|
||||
const isSubTable = computed(() => currentTemplateType.value === InfraCodegenTemplateTypeEnum.SUB);
|
||||
|
||||
/** 基础表单实例 */
|
||||
const [BaseForm, baseFormApi] = useVbenForm({
|
||||
wrapperClass,
|
||||
layout: 'horizontal',
|
||||
showDefaultActions: false,
|
||||
schema: useGenerationInfoBaseFormSchema(),
|
||||
handleValuesChange: (values) => {
|
||||
// 监听模板类型变化
|
||||
if (values.templateType !== undefined && values.templateType !== currentTemplateType.value) {
|
||||
currentTemplateType.value = values.templateType;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/** 树表信息表单实例 */
|
||||
const [TreeForm, treeFormApi] = useVbenForm({
|
||||
wrapperClass,
|
||||
layout: 'horizontal',
|
||||
showDefaultActions: false,
|
||||
schema: [],
|
||||
});
|
||||
|
||||
/** 主子表信息表单实例 */
|
||||
const [SubForm, subFormApi] = useVbenForm({
|
||||
wrapperClass,
|
||||
layout: 'horizontal',
|
||||
showDefaultActions: false,
|
||||
schema: [],
|
||||
});
|
||||
|
||||
/** 更新树表信息表单 schema */
|
||||
function updateTreeSchema(): void {
|
||||
const schema = useTreeTableFormSchema(props.columns);
|
||||
treeFormApi.setState({ schema });
|
||||
}
|
||||
|
||||
/** 更新主子表信息表单 schema */
|
||||
function updateSubSchema(): void {
|
||||
const schema = useSubTableFormSchema(props.columns, tables.value);
|
||||
subFormApi.setState({ schema });
|
||||
}
|
||||
|
||||
/** 获取合并的表单值 */
|
||||
async function getAllFormValues(): Promise<Record<string, any>> {
|
||||
// 基础表单值
|
||||
const baseValues = await baseFormApi.getValues();
|
||||
|
||||
// 根据模板类型获取对应的额外表单值
|
||||
let extraValues = {};
|
||||
if (isTreeTable.value) {
|
||||
extraValues = await treeFormApi.getValues();
|
||||
} else if (isSubTable.value) {
|
||||
extraValues = await subFormApi.getValues();
|
||||
}
|
||||
|
||||
// 合并表单值
|
||||
return { ...baseValues, ...extraValues };
|
||||
}
|
||||
|
||||
/** 验证所有表单 */
|
||||
async function validateAllForms() {
|
||||
let validateResult: boolean;
|
||||
// 验证基础表单
|
||||
const { valid: baseFormValid } = await baseFormApi.validate();
|
||||
validateResult = baseFormValid;
|
||||
// 根据模板类型验证对应的额外表单
|
||||
if (isTreeTable.value) {
|
||||
const { valid: treeFormValid } = await treeFormApi.validate();
|
||||
validateResult = baseFormValid && treeFormValid;
|
||||
} else if (isSubTable.value) {
|
||||
const { valid: subFormValid } = await subFormApi.validate();
|
||||
validateResult = baseFormValid && subFormValid;
|
||||
}
|
||||
return validateResult;
|
||||
}
|
||||
|
||||
/** 设置表单值 */
|
||||
function setAllFormValues(values: Record<string, any>): void {
|
||||
if (!values) return;
|
||||
|
||||
// 记录模板类型
|
||||
currentTemplateType.value = values.templateType;
|
||||
|
||||
// 设置基础表单值
|
||||
baseFormApi.setValues(values);
|
||||
|
||||
// 根据模板类型设置对应的额外表单值
|
||||
if (isTreeTable.value) {
|
||||
treeFormApi.setValues(values);
|
||||
} else if (isSubTable.value) {
|
||||
subFormApi.setValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听表格数据变化 */
|
||||
watch(
|
||||
() => props.table,
|
||||
async (val) => {
|
||||
if (!val || isEmpty(val)) {
|
||||
return;
|
||||
}
|
||||
// 初始化树表的schema
|
||||
updateTreeSchema();
|
||||
// 设置表单值
|
||||
setAllFormValues(val);
|
||||
// 获取表数据,用于主子表选择
|
||||
if (typeof val.dataSourceConfigId === undefined) {
|
||||
return;
|
||||
}
|
||||
tables.value = await getCodegenTableList(val.dataSourceConfigId);
|
||||
// 初始化子表 schema
|
||||
updateSubSchema();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** 暴露出表单校验方法和表单值获取方法 */
|
||||
defineExpose({
|
||||
validate: validateAllForms,
|
||||
getValues: getAllFormValues,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 基础表单 -->
|
||||
<BaseForm />
|
||||
|
||||
<!-- 树表信息表单 -->
|
||||
<TreeForm v-if="isTreeTable" />
|
||||
|
||||
<!-- 主子表信息表单 -->
|
||||
<SubForm v-if="isSubTable" />
|
||||
</div>
|
||||
</template>
|
||||
141
apps/web-antd/src/views/infra/codegen/modules/import-table.vue
Normal file
141
apps/web-antd/src/views/infra/codegen/modules/import-table.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { createCodegenList, getSchemaTableList } from '#/api/infra/codegen';
|
||||
import { getDataSourceConfigList } from '#/api/infra/data-source-config';
|
||||
import { reactive, ref, unref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { useImportTableFormSchema } from '#/views/infra/codegen/data';
|
||||
|
||||
/** 定义组件事件 */
|
||||
const emit = defineEmits<{
|
||||
(e: 'success'): void;
|
||||
}>();
|
||||
|
||||
const dataSourceConfigList = ref<InfraDataSourceConfigApi.InfraDataSourceConfig[]>([]);
|
||||
const formData = reactive<InfraCodegenApi.CodegenCreateListReq>({
|
||||
dataSourceConfigId: undefined,
|
||||
tableNames: [], // 已选择的表列表
|
||||
});
|
||||
/** 表格实例 */
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useImportTableFormSchema([]),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: [
|
||||
{ type: 'checkbox', width: 40 },
|
||||
{ field: 'name', title: '表名称', minWidth: 200 },
|
||||
{ field: 'comment', title: '表描述', minWidth: 200 },
|
||||
],
|
||||
height: '600px',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
if (formValues.dataSourceConfigId === undefined) {
|
||||
if (unref(dataSourceConfigList).length > 0) {
|
||||
formValues.dataSourceConfigId = unref(dataSourceConfigList)[0]?.id;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
formData.dataSourceConfigId = formValues.dataSourceConfigId;
|
||||
return await getSchemaTableList({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'name',
|
||||
},
|
||||
toolbarConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
range: true,
|
||||
},
|
||||
pagerConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
} as VxeTableGridOptions<InfraCodegenApi.DatabaseTable>,
|
||||
gridEvents: {
|
||||
checkboxChange: ({ records }: { records: InfraCodegenApi.DatabaseTable[] }) => {
|
||||
formData.tableNames = records.map((item) => item.name);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** 模态框实例 */
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '导入表',
|
||||
class: 'w-2/3',
|
||||
async onConfirm() {
|
||||
modalApi.lock();
|
||||
// 1. 获取表单值
|
||||
if (formData?.dataSourceConfigId === undefined) {
|
||||
message.error('请选择数据源');
|
||||
return;
|
||||
}
|
||||
// 2. 校验是否选择了表
|
||||
if (formData.tableNames.length === 0) {
|
||||
message.error('请选择需要导入的表');
|
||||
return;
|
||||
}
|
||||
// 3. 提交请求
|
||||
const hideLoading = message.loading({
|
||||
content: '导入中...',
|
||||
duration: 0,
|
||||
key: 'import_loading',
|
||||
});
|
||||
try {
|
||||
await createCodegenList(formData);
|
||||
// 关闭并提示
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.operationSuccess'),
|
||||
key: 'action_process_msg',
|
||||
});
|
||||
} finally {
|
||||
hideLoading();
|
||||
modalApi.lock(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/** 获取数据源配置列表 */
|
||||
async function initDataSourceConfig() {
|
||||
try {
|
||||
dataSourceConfigList.value = await getDataSourceConfigList();
|
||||
gridApi.setState({
|
||||
formOptions: {
|
||||
schema: useImportTableFormSchema(dataSourceConfigList.value),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取数据源配置失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
initDataSourceConfig();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal>
|
||||
<Grid />
|
||||
</Modal>
|
||||
</template>
|
||||
332
apps/web-antd/src/views/infra/codegen/modules/preview-code.vue
Normal file
332
apps/web-antd/src/views/infra/codegen/modules/preview-code.vue
Normal file
@@ -0,0 +1,332 @@
|
||||
<script lang="ts" setup>
|
||||
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||
|
||||
import { computed, h, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { Copy } from '@vben/icons';
|
||||
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { Button, message, Tree } from 'ant-design-vue';
|
||||
import hljs from 'highlight.js/lib/core';
|
||||
import java from 'highlight.js/lib/languages/java';
|
||||
import javascript from 'highlight.js/lib/languages/javascript';
|
||||
import sql from 'highlight.js/lib/languages/sql';
|
||||
import typescript from 'highlight.js/lib/languages/typescript';
|
||||
import xml from 'highlight.js/lib/languages/xml';
|
||||
|
||||
import { previewCodegen } from '#/api/infra/codegen';
|
||||
|
||||
/** 注册代码高亮语言 */
|
||||
hljs.registerLanguage('java', java);
|
||||
hljs.registerLanguage('xml', xml);
|
||||
hljs.registerLanguage('html', xml);
|
||||
hljs.registerLanguage('vue', xml);
|
||||
hljs.registerLanguage('javascript', javascript);
|
||||
hljs.registerLanguage('sql', sql);
|
||||
hljs.registerLanguage('typescript', typescript);
|
||||
|
||||
/** 文件树类型 */
|
||||
interface FileNode {
|
||||
key: string;
|
||||
title: string;
|
||||
parentKey: string;
|
||||
isLeaf?: boolean;
|
||||
children?: FileNode[];
|
||||
}
|
||||
|
||||
/** 组件状态 */
|
||||
const loading = ref(false);
|
||||
const fileTree = ref<FileNode[]>([]);
|
||||
const previewFiles = ref<InfraCodegenApi.CodegenPreview[]>([]);
|
||||
const activeKey = ref<string>('');
|
||||
const highlightedCode = ref<string>('');
|
||||
|
||||
/** 当前活动文件的语言 */
|
||||
const activeLanguage = computed(() => {
|
||||
return activeKey.value.split('.').pop() || '';
|
||||
});
|
||||
|
||||
/** 复制代码 */
|
||||
const copyCode = async () => {
|
||||
const { copy } = useClipboard();
|
||||
const file = previewFiles.value.find(
|
||||
(item) => item.filePath === activeKey.value,
|
||||
);
|
||||
if (file) {
|
||||
await copy(file.code);
|
||||
message.success('复制成功');
|
||||
}
|
||||
};
|
||||
|
||||
/** 文件节点点击事件 */
|
||||
const handleNodeClick = (_: any[], e: any) => {
|
||||
if (e.node.isLeaf) {
|
||||
activeKey.value = e.node.key;
|
||||
const file = previewFiles.value.find(
|
||||
(item) => item.filePath === activeKey.value,
|
||||
);
|
||||
if (file) {
|
||||
const lang = file.filePath.split('.').pop() || '';
|
||||
try {
|
||||
highlightedCode.value = hljs.highlight(file.code, {
|
||||
language: lang,
|
||||
}).value;
|
||||
} catch {
|
||||
highlightedCode.value = file.code;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理文件树 */
|
||||
const handleFiles = (data: InfraCodegenApi.CodegenPreview[]): FileNode[] => {
|
||||
const exists: Record<string, boolean> = {};
|
||||
const files: FileNode[] = [];
|
||||
|
||||
// 处理文件路径
|
||||
for (const item of data) {
|
||||
const paths = item.filePath.split('/');
|
||||
let fullPath = '';
|
||||
|
||||
// 处理Java文件路径
|
||||
const newPaths = [];
|
||||
let i = 0;
|
||||
while (i < paths.length) {
|
||||
const path = paths[i];
|
||||
|
||||
if (path === 'java' && i + 1 < paths.length) {
|
||||
newPaths.push(path);
|
||||
|
||||
// 合并包路径
|
||||
let packagePath = '';
|
||||
i++;
|
||||
while (i < paths.length) {
|
||||
const nextPath = paths[i] || '';
|
||||
if (['controller','convert','dal','dataobject','enums','mysql','service','vo'].includes(nextPath)) {
|
||||
break;
|
||||
}
|
||||
packagePath = packagePath ? `${packagePath}.${nextPath}` : nextPath;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (packagePath) {
|
||||
newPaths.push(packagePath);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
newPaths.push(path);
|
||||
i++;
|
||||
}
|
||||
|
||||
// 构建文件树
|
||||
for (let i = 0; i < newPaths.length; i++) {
|
||||
const oldFullPath = fullPath;
|
||||
fullPath =
|
||||
fullPath.length === 0
|
||||
? newPaths[i] || ''
|
||||
: `${fullPath.replaceAll('.', '/')}/${newPaths[i]}`;
|
||||
|
||||
if (exists[fullPath]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
exists[fullPath] = true;
|
||||
files.push({
|
||||
key: fullPath,
|
||||
title: newPaths[i] || '',
|
||||
parentKey: oldFullPath || '/',
|
||||
isLeaf: i === newPaths.length - 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 构建树形结构 */
|
||||
const buildTree = (parentKey: string): FileNode[] => {
|
||||
return files
|
||||
.filter((file) => file.parentKey === parentKey)
|
||||
.map((file) => ({
|
||||
...file,
|
||||
children: buildTree(file.key),
|
||||
}));
|
||||
};
|
||||
|
||||
return buildTree('/');
|
||||
};
|
||||
|
||||
/** 模态框实例 */
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
footer: false,
|
||||
class: 'w-3/5',
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
previewFiles.value = [];
|
||||
fileTree.value = [];
|
||||
activeKey.value = '';
|
||||
highlightedCode.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const row = modalApi.getData<InfraCodegenApi.CodegenTable>();
|
||||
if (!row) return;
|
||||
|
||||
// 加载预览数据
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await previewCodegen(row.id);
|
||||
previewFiles.value = data;
|
||||
fileTree.value = handleFiles(data);
|
||||
|
||||
// 默认选中第一个文件
|
||||
if (data.length > 0) {
|
||||
activeKey.value = data[0]?.filePath || '';
|
||||
const lang = activeKey.value.split('.').pop() || '';
|
||||
const code = data[0]?.code || '';
|
||||
try {
|
||||
highlightedCode.value = hljs.highlight(code, {
|
||||
language: lang,
|
||||
}).value;
|
||||
} catch {
|
||||
highlightedCode.value = code;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal title="代码预览">
|
||||
<div class="h-1/1 flex" v-loading="loading">
|
||||
<!-- 文件树 -->
|
||||
<div class="w-1/3 border-r border-gray-200 pr-4 dark:border-gray-700">
|
||||
<Tree
|
||||
:selected-keys="[activeKey]"
|
||||
:tree-data="fileTree"
|
||||
@select="handleNodeClick"
|
||||
/>
|
||||
</div>
|
||||
<!-- 代码预览 -->
|
||||
<div class="w-2/3 pl-4">
|
||||
<div class="mb-2 flex justify-between">
|
||||
<div class="text-lg font-medium dark:text-gray-200">
|
||||
{{ activeKey.split('/').pop() }}
|
||||
<span class="ml-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
({{ activeLanguage }})
|
||||
</span>
|
||||
</div>
|
||||
<Button type="primary" ghost @click="copyCode" :icon="h(Copy)">
|
||||
复制代码
|
||||
</Button>
|
||||
</div>
|
||||
<div class="h-[calc(100%-40px)] overflow-auto">
|
||||
<pre
|
||||
class="overflow-auto rounded-md bg-gray-50 p-4 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
|
||||
>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<code v-html="highlightedCode" class="code-highlight"></code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
|
||||
/* 代码高亮样式 - 支持暗黑模式 */
|
||||
:deep(.code-highlight) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 关键字 */
|
||||
:deep(.hljs-keyword) {
|
||||
@apply text-purple-600 dark:text-purple-400;
|
||||
}
|
||||
|
||||
/* 字符串 */
|
||||
:deep(.hljs-string) {
|
||||
@apply text-green-600 dark:text-green-400;
|
||||
}
|
||||
|
||||
/* 注释 */
|
||||
:deep(.hljs-comment) {
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
/* 函数 */
|
||||
:deep(.hljs-function) {
|
||||
@apply text-blue-600 dark:text-blue-400;
|
||||
}
|
||||
|
||||
/* 数字 */
|
||||
:deep(.hljs-number) {
|
||||
@apply text-orange-600 dark:text-orange-400;
|
||||
}
|
||||
|
||||
/* 类 */
|
||||
:deep(.hljs-class) {
|
||||
@apply text-yellow-600 dark:text-yellow-400;
|
||||
}
|
||||
|
||||
/* 标题/函数名 */
|
||||
:deep(.hljs-title) {
|
||||
@apply font-bold text-blue-600 dark:text-blue-400;
|
||||
}
|
||||
|
||||
/* 参数 */
|
||||
:deep(.hljs-params) {
|
||||
@apply text-gray-700 dark:text-gray-300;
|
||||
}
|
||||
|
||||
/* 内置对象 */
|
||||
:deep(.hljs-built_in) {
|
||||
@apply text-teal-600 dark:text-teal-400;
|
||||
}
|
||||
|
||||
/* HTML标签 */
|
||||
:deep(.hljs-tag) {
|
||||
@apply text-blue-600 dark:text-blue-400;
|
||||
}
|
||||
|
||||
/* 属性 */
|
||||
:deep(.hljs-attribute),
|
||||
:deep(.hljs-attr) {
|
||||
@apply text-green-600 dark:text-green-400;
|
||||
}
|
||||
|
||||
/* 字面量 */
|
||||
:deep(.hljs-literal) {
|
||||
@apply text-purple-600 dark:text-purple-400;
|
||||
}
|
||||
|
||||
/* 元信息 */
|
||||
:deep(.hljs-meta) {
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
/* 选择器标签 */
|
||||
:deep(.hljs-selector-tag) {
|
||||
@apply text-blue-600 dark:text-blue-400;
|
||||
}
|
||||
|
||||
/* XML/HTML名称 */
|
||||
:deep(.hljs-name) {
|
||||
@apply text-blue-600 dark:text-blue-400;
|
||||
}
|
||||
|
||||
/* 变量 */
|
||||
:deep(.hljs-variable) {
|
||||
@apply text-orange-600 dark:text-orange-400;
|
||||
}
|
||||
|
||||
/* 属性 */
|
||||
:deep(.hljs-property) {
|
||||
@apply text-red-600 dark:text-red-400;
|
||||
}
|
||||
/* stylelint-enable selector-class-pattern */
|
||||
</style>
|
||||
Reference in New Issue
Block a user