feat: 新增 ele infra 代码生成器模块
parent
ffc7e21d4a
commit
18adeceaed
|
|
@ -0,0 +1,591 @@
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
import type { SystemMenuApi } from '#/api/system/menu';
|
||||||
|
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { useAccess } from '@vben/access';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { handleTree } from '@vben/utils';
|
||||||
|
|
||||||
|
import { getDataSourceConfigList } from '#/api/infra/data-source-config';
|
||||||
|
import { getMenuList } from '#/api/system/menu';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||||
|
|
||||||
|
const { hasAccessByCodes } = useAccess();
|
||||||
|
|
||||||
|
/** 导入数据库表的表单 */
|
||||||
|
export function useImportTableFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'dataSourceConfigId',
|
||||||
|
label: '数据源',
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
api: async () => {
|
||||||
|
const data = await getDataSourceConfigList();
|
||||||
|
return data.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
autoSelect: 'first',
|
||||||
|
placeholder: '请选择数据源',
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '表名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入表名称',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'comment',
|
||||||
|
label: '表描述',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入表描述',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导入数据库表表格列定义 */
|
||||||
|
export function useImportTableColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{ type: 'checkbox', width: 40 },
|
||||||
|
{ field: 'name', title: '表名称', minWidth: 200 },
|
||||||
|
{ field: 'comment', title: '表描述', minWidth: 200 },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 基本信息表单的 schema */
|
||||||
|
export function useBasicInfoFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'tableName',
|
||||||
|
label: '表名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入仓库名称',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'tableComment',
|
||||||
|
label: '表描述',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入表描述',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'className',
|
||||||
|
label: '实体类名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入实体类名称',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
help: '默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'author',
|
||||||
|
label: '作者',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入作者',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
component: 'Textarea',
|
||||||
|
componentProps: {
|
||||||
|
rows: 3,
|
||||||
|
placeholder: '请输入备注',
|
||||||
|
},
|
||||||
|
formItemClass: 'md:col-span-2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 生成信息表单基础 schema */
|
||||||
|
export function useGenerationInfoBaseFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'templateType',
|
||||||
|
label: '生成模板',
|
||||||
|
componentProps: {
|
||||||
|
options: getDictOptions(
|
||||||
|
DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE,
|
||||||
|
'number',
|
||||||
|
),
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'frontType',
|
||||||
|
label: '前端类型',
|
||||||
|
componentProps: {
|
||||||
|
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE, 'number'),
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'scene',
|
||||||
|
label: '生成场景',
|
||||||
|
componentProps: {
|
||||||
|
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE, 'number'),
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'parentMenuId',
|
||||||
|
label: '上级菜单',
|
||||||
|
help: '分配到指定菜单下,例如 系统管理',
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
api: async () => {
|
||||||
|
const data = await getMenuList();
|
||||||
|
data.unshift({
|
||||||
|
id: 0,
|
||||||
|
name: '顶级菜单',
|
||||||
|
} as SystemMenuApi.Menu);
|
||||||
|
return handleTree(data);
|
||||||
|
},
|
||||||
|
class: 'w-full',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
childrenField: 'children',
|
||||||
|
placeholder: '请选择上级菜单',
|
||||||
|
filterTreeNode(input: string, node: Recordable<any>) {
|
||||||
|
if (!input || input.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const name: string = node.label ?? '';
|
||||||
|
if (!name) return false;
|
||||||
|
return name.includes(input) || $t(name).includes(input);
|
||||||
|
},
|
||||||
|
showSearch: true,
|
||||||
|
treeDefaultExpandedKeys: [0],
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
renderComponentContent() {
|
||||||
|
return {
|
||||||
|
title({ label, icon }: { icon: string; label: string }) {
|
||||||
|
const components = [];
|
||||||
|
if (!label) return '';
|
||||||
|
if (icon) {
|
||||||
|
components.push(h(IconifyIcon, { class: 'size-4', icon }));
|
||||||
|
}
|
||||||
|
components.push(h('span', { class: '' }, $t(label || '')));
|
||||||
|
return h('div', { class: 'flex items-center gap-1' }, components);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'moduleName',
|
||||||
|
label: '模块名',
|
||||||
|
help: '模块名,即一级目录,例如 system、infra、tool 等等',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'businessName',
|
||||||
|
label: '业务名',
|
||||||
|
help: '业务名,即二级目录,例如 user、permission、dict 等等',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'className',
|
||||||
|
label: '类名称',
|
||||||
|
help: '类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'classComment',
|
||||||
|
label: '类描述',
|
||||||
|
help: '用作类描述,例如 用户',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 树表信息 schema */
|
||||||
|
export function useGenerationInfoTreeFormSchema(
|
||||||
|
columns: InfraCodegenApi.CodegenColumn[] = [],
|
||||||
|
): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Divider',
|
||||||
|
fieldName: 'treeDivider',
|
||||||
|
label: '',
|
||||||
|
renderComponentContent: () => {
|
||||||
|
return {
|
||||||
|
default: () => ['树表信息'],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
formItemClass: 'md:col-span-2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'treeParentColumnId',
|
||||||
|
label: '父编号字段',
|
||||||
|
help: '树显示的父编码字段名,例如 parent_Id',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择',
|
||||||
|
options: columns.map((column) => ({
|
||||||
|
label: column.columnName,
|
||||||
|
value: column.id,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'treeNameColumnId',
|
||||||
|
label: '名称字段',
|
||||||
|
help: '树节点显示的名称字段,一般是 name',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择名称字段',
|
||||||
|
options: columns.map((column) => ({
|
||||||
|
label: column.columnName,
|
||||||
|
value: column.id,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 主子表信息 schema */
|
||||||
|
export function useGenerationInfoSubTableFormSchema(
|
||||||
|
columns: InfraCodegenApi.CodegenColumn[] = [],
|
||||||
|
tables: InfraCodegenApi.CodegenTable[] = [],
|
||||||
|
): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Divider',
|
||||||
|
fieldName: 'subDivider',
|
||||||
|
label: '',
|
||||||
|
renderComponentContent: () => {
|
||||||
|
return {
|
||||||
|
default: () => ['主子表信息'],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
formItemClass: 'md:col-span-2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'masterTableId',
|
||||||
|
label: '关联的主表',
|
||||||
|
help: '关联主表(父表)的表名, 如:system_user',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择',
|
||||||
|
options: tables.map((table) => ({
|
||||||
|
label: `${table.tableName}:${table.tableComment}`,
|
||||||
|
value: table.id,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'subJoinColumnId',
|
||||||
|
label: '子表关联的字段',
|
||||||
|
help: '子表关联的字段, 如:user_id',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择',
|
||||||
|
options: columns.map((column) => ({
|
||||||
|
label: `${column.columnName}:${column.columnComment}`,
|
||||||
|
value: column.id,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
rules: 'selectRequired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
fieldName: 'subJoinMany',
|
||||||
|
label: '关联关系',
|
||||||
|
help: '主表与子表的关联关系',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '一对多',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '一对一',
|
||||||
|
value: 'false',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 列表的搜索表单 */
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'tableName',
|
||||||
|
label: '表名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入表名称',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'tableComment',
|
||||||
|
label: '表描述',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入表描述',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'createTime',
|
||||||
|
label: '创建时间',
|
||||||
|
component: 'RangePicker',
|
||||||
|
componentProps: {
|
||||||
|
...getRangePickerDefaultProps(),
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 列表的字段 */
|
||||||
|
export function useGridColumns<T = InfraCodegenApi.CodegenTable>(
|
||||||
|
onActionClick: OnActionClickFn<T>,
|
||||||
|
getDataSourceConfigName?: (dataSourceConfigId: number) => string | undefined,
|
||||||
|
): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'dataSourceConfigId',
|
||||||
|
title: '数据源',
|
||||||
|
minWidth: 120,
|
||||||
|
formatter: (row) => getDataSourceConfigName?.(row.cellValue) || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tableName',
|
||||||
|
title: '表名称',
|
||||||
|
minWidth: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tableComment',
|
||||||
|
title: '表描述',
|
||||||
|
minWidth: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'className',
|
||||||
|
title: '实体',
|
||||||
|
minWidth: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'createTime',
|
||||||
|
title: '创建时间',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'updateTime',
|
||||||
|
title: '更新时间',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'operation',
|
||||||
|
title: '操作',
|
||||||
|
width: 300,
|
||||||
|
fixed: 'right',
|
||||||
|
align: 'center',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'tableName',
|
||||||
|
nameTitle: '代码生成',
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
code: 'preview',
|
||||||
|
text: '预览',
|
||||||
|
show: hasAccessByCodes(['infra:codegen:preview']),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'edit',
|
||||||
|
show: hasAccessByCodes(['infra:codegen:update']),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'delete',
|
||||||
|
show: hasAccessByCodes(['infra:codegen:delete']),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'sync',
|
||||||
|
text: '同步',
|
||||||
|
show: hasAccessByCodes(['infra:codegen:update']),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'generate',
|
||||||
|
text: '生成代码',
|
||||||
|
show: hasAccessByCodes(['infra:codegen:download']),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 代码生成表格列定义 */
|
||||||
|
export function useCodegenColumnTableColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{ field: 'columnName', title: '字段列名', minWidth: 130 },
|
||||||
|
{
|
||||||
|
field: 'columnComment',
|
||||||
|
title: '字段描述',
|
||||||
|
minWidth: 100,
|
||||||
|
slots: { default: 'columnComment' },
|
||||||
|
},
|
||||||
|
{ field: 'dataType', title: '物理类型', minWidth: 100 },
|
||||||
|
{
|
||||||
|
field: 'javaType',
|
||||||
|
title: 'Java 类型',
|
||||||
|
minWidth: 130,
|
||||||
|
slots: { default: 'javaType' },
|
||||||
|
params: {
|
||||||
|
options: [
|
||||||
|
{ label: 'Long', value: 'Long' },
|
||||||
|
{ label: 'String', value: 'String' },
|
||||||
|
{ label: 'Integer', value: 'Integer' },
|
||||||
|
{ label: 'Double', value: 'Double' },
|
||||||
|
{ label: 'BigDecimal', value: 'BigDecimal' },
|
||||||
|
{ label: 'LocalDateTime', value: 'LocalDateTime' },
|
||||||
|
{ label: 'Boolean', value: 'Boolean' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'javaField',
|
||||||
|
title: 'Java 属性',
|
||||||
|
minWidth: 100,
|
||||||
|
slots: { default: 'javaField' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'createOperation',
|
||||||
|
title: '插入',
|
||||||
|
width: 40,
|
||||||
|
slots: { default: 'createOperation' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'updateOperation',
|
||||||
|
title: '编辑',
|
||||||
|
width: 40,
|
||||||
|
slots: { default: 'updateOperation' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'listOperationResult',
|
||||||
|
title: '列表',
|
||||||
|
width: 40,
|
||||||
|
slots: { default: 'listOperationResult' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'listOperation',
|
||||||
|
title: '查询',
|
||||||
|
width: 40,
|
||||||
|
slots: { default: 'listOperation' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'listOperationCondition',
|
||||||
|
title: '查询方式',
|
||||||
|
minWidth: 100,
|
||||||
|
slots: { default: 'listOperationCondition' },
|
||||||
|
params: {
|
||||||
|
options: [
|
||||||
|
{ label: '=', value: '=' },
|
||||||
|
{ label: '!=', value: '!=' },
|
||||||
|
{ label: '>', value: '>' },
|
||||||
|
{ label: '>=', value: '>=' },
|
||||||
|
{ label: '<', value: '<' },
|
||||||
|
{ label: '<=', value: '<=' },
|
||||||
|
{ label: 'LIKE', value: 'LIKE' },
|
||||||
|
{ label: 'BETWEEN', value: 'BETWEEN' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'nullable',
|
||||||
|
title: '允许空',
|
||||||
|
width: 60,
|
||||||
|
slots: { default: 'nullable' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'htmlType',
|
||||||
|
title: '显示类型',
|
||||||
|
width: 130,
|
||||||
|
slots: { default: 'htmlType' },
|
||||||
|
params: {
|
||||||
|
options: [
|
||||||
|
{ label: '文本框', value: 'input' },
|
||||||
|
{ label: '文本域', value: 'textarea' },
|
||||||
|
{ label: '下拉框', value: 'select' },
|
||||||
|
{ label: '单选框', value: 'radio' },
|
||||||
|
{ label: '复选框', value: 'checkbox' },
|
||||||
|
{ label: '日期控件', value: 'datetime' },
|
||||||
|
{ label: '图片上传', value: 'imageUpload' },
|
||||||
|
{ label: '文件上传', value: 'fileUpload' },
|
||||||
|
{ label: '富文本控件', value: 'editor' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'dictType',
|
||||||
|
title: '字典类型',
|
||||||
|
width: 120,
|
||||||
|
slots: { default: 'dictType' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'example',
|
||||||
|
title: '示例',
|
||||||
|
minWidth: 100,
|
||||||
|
slots: { default: 'example' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
|
||||||
|
import { ref, unref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { useTabs } from '@vben/hooks';
|
||||||
|
|
||||||
|
import { ElButton, ElLoading, ElMessage, ElStep, ElSteps } from 'element-plus';
|
||||||
|
|
||||||
|
import { getCodegenTable, updateCodegenTable } from '#/api/infra/codegen';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import BasicInfo from '../modules/basic-info.vue';
|
||||||
|
import ColumnInfo from '../modules/column-info.vue';
|
||||||
|
import GenerationInfo from '../modules/generation-info.vue';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const loading = ref(false);
|
||||||
|
const currentStep = ref(0);
|
||||||
|
const formData = ref<InfraCodegenApi.CodegenDetail>({
|
||||||
|
table: {} as InfraCodegenApi.CodegenTable,
|
||||||
|
columns: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 表单引用 */
|
||||||
|
const basicInfoRef = ref<InstanceType<typeof BasicInfo>>();
|
||||||
|
const columnInfoRef = ref<InstanceType<typeof ColumnInfo>>();
|
||||||
|
const generateInfoRef = ref<InstanceType<typeof GenerationInfo>>();
|
||||||
|
|
||||||
|
/** 获取详情数据 */
|
||||||
|
const getDetail = async () => {
|
||||||
|
const id = route.query.id as any;
|
||||||
|
if (!id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
formData.value = await getCodegenTable(id);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 表单验证
|
||||||
|
const basicInfoValid = await basicInfoRef.value?.validate();
|
||||||
|
if (!basicInfoValid) {
|
||||||
|
ElMessage.warning('保存失败,原因:基本信息表单校验失败请检查!!!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const generateInfoValid = await generateInfoRef.value?.validate();
|
||||||
|
if (!generateInfoValid) {
|
||||||
|
ElMessage.warning('保存失败,原因:生成信息表单校验失败请检查!!!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const loadingInstance = ElLoading.service({
|
||||||
|
text: $t('ui.actionMessage.updating'),
|
||||||
|
fullscreen: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
// 拼接相关信息
|
||||||
|
const basicInfo = await basicInfoRef.value?.getValues();
|
||||||
|
const columns = columnInfoRef.value?.getData() || unref(formData).columns;
|
||||||
|
const generateInfo = await generateInfoRef.value?.getValues();
|
||||||
|
await updateCodegenTable({
|
||||||
|
table: { ...unref(formData).table, ...basicInfo, ...generateInfo },
|
||||||
|
columns,
|
||||||
|
});
|
||||||
|
// 关闭并提示
|
||||||
|
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||||
|
close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存失败', error);
|
||||||
|
} finally {
|
||||||
|
loadingInstance.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const tabs = useTabs();
|
||||||
|
/** 返回列表 */
|
||||||
|
const close = () => {
|
||||||
|
tabs.closeCurrentTab();
|
||||||
|
router.push('/infra/codegen');
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 下一步 */
|
||||||
|
const nextStep = async () => {
|
||||||
|
currentStep.value += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 上一步 */
|
||||||
|
const prevStep = () => {
|
||||||
|
if (currentStep.value > 0) {
|
||||||
|
currentStep.value -= 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 步骤配置 */
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: '基本信息',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '字段信息',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '生成信息',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
getDetail();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height v-loading="loading">
|
||||||
|
<div
|
||||||
|
class="flex h-[95%] flex-col rounded-md bg-white p-4 dark:bg-[#1f1f1f] dark:text-gray-300"
|
||||||
|
>
|
||||||
|
<ElSteps
|
||||||
|
:active="currentStep"
|
||||||
|
class="mb-8 rounded shadow-sm dark:bg-[#141414]"
|
||||||
|
simple
|
||||||
|
>
|
||||||
|
<ElStep
|
||||||
|
v-for="(step, index) in steps"
|
||||||
|
:key="index"
|
||||||
|
:title="step.title"
|
||||||
|
/>
|
||||||
|
</ElSteps>
|
||||||
|
|
||||||
|
<div class="flex-1 overflow-auto py-4">
|
||||||
|
<!-- 根据当前步骤显示对应的组件 -->
|
||||||
|
<BasicInfo
|
||||||
|
v-show="currentStep === 0"
|
||||||
|
ref="basicInfoRef"
|
||||||
|
:table="formData.table"
|
||||||
|
/>
|
||||||
|
<ColumnInfo
|
||||||
|
v-show="currentStep === 1"
|
||||||
|
ref="columnInfoRef"
|
||||||
|
:columns="formData.columns"
|
||||||
|
/>
|
||||||
|
<GenerationInfo
|
||||||
|
v-show="currentStep === 2"
|
||||||
|
ref="generateInfoRef"
|
||||||
|
:table="formData.table"
|
||||||
|
:columns="formData.columns"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 flex justify-end space-x-2">
|
||||||
|
<ElButton v-show="currentStep > 0" @click="prevStep">上一步</ElButton>
|
||||||
|
<ElButton v-show="currentStep < steps.length - 1" @click="nextStep">
|
||||||
|
下一步
|
||||||
|
</ElButton>
|
||||||
|
<ElButton type="primary" :loading="loading" @click="submitForm">
|
||||||
|
保存
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { ElButton, ElLoading, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
deleteCodegenTable,
|
||||||
|
downloadCodegen,
|
||||||
|
getCodegenTablePage,
|
||||||
|
syncCodegenFromDB,
|
||||||
|
} from '#/api/infra/codegen';
|
||||||
|
import { getDataSourceConfigList } from '#/api/infra/data-source-config';
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
import ImportTable from './modules/import-table.vue';
|
||||||
|
import PreviewCode from './modules/preview-code.vue';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const dataSourceConfigList = ref<InfraDataSourceConfigApi.DataSourceConfig[]>(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 获取数据源名称 */
|
||||||
|
const getDataSourceConfigName = (dataSourceConfigId: number) => {
|
||||||
|
return dataSourceConfigList.value.find(
|
||||||
|
(item) => item.id === dataSourceConfigId,
|
||||||
|
)?.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [ImportModal, importModalApi] = useVbenModal({
|
||||||
|
connectedComponent: ImportTable,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [PreviewModal, previewModalApi] = useVbenModal({
|
||||||
|
connectedComponent: PreviewCode,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 刷新表格 */
|
||||||
|
function onRefresh() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导入表格 */
|
||||||
|
function onImport() {
|
||||||
|
importModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 预览代码 */
|
||||||
|
function onPreview(row: InfraCodegenApi.CodegenTable) {
|
||||||
|
previewModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 编辑表格 */
|
||||||
|
function onEdit(row: InfraCodegenApi.CodegenTable) {
|
||||||
|
router.push({ name: 'InfraCodegenEdit', query: { id: row.id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除代码生成配置 */
|
||||||
|
async function onDelete(row: InfraCodegenApi.CodegenTable) {
|
||||||
|
const loadingInstance = ElLoading.service({
|
||||||
|
text: $t('ui.actionMessage.deleting', [row.tableName]),
|
||||||
|
fullscreen: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteCodegenTable(row.id);
|
||||||
|
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.tableName]));
|
||||||
|
onRefresh();
|
||||||
|
} finally {
|
||||||
|
loadingInstance.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 同步数据库 */
|
||||||
|
async function onSync(row: InfraCodegenApi.CodegenTable) {
|
||||||
|
const loadingInstance = ElLoading.service({
|
||||||
|
text: $t('ui.actionMessage.updating', [row.tableName]),
|
||||||
|
fullscreen: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await syncCodegenFromDB(row.id);
|
||||||
|
ElMessage.success($t('ui.actionMessage.updateSuccess', [row.tableName]));
|
||||||
|
onRefresh();
|
||||||
|
} finally {
|
||||||
|
loadingInstance.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 生成代码 */
|
||||||
|
async function onGenerate(row: InfraCodegenApi.CodegenTable) {
|
||||||
|
const loadingInstance = ElLoading.service({
|
||||||
|
text: '正在生成代码...',
|
||||||
|
fullscreen: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const res = await downloadCodegen(row.id);
|
||||||
|
const blob = new Blob([res], { type: 'application/zip' });
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = `codegen-${row.className}.zip`;
|
||||||
|
link.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
ElMessage.success('代码生成成功');
|
||||||
|
} finally {
|
||||||
|
loadingInstance.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表格操作按钮的回调函数 */
|
||||||
|
function onActionClick({
|
||||||
|
code,
|
||||||
|
row,
|
||||||
|
}: OnActionClickParams<InfraCodegenApi.CodegenTable>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'generate': {
|
||||||
|
onGenerate(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'preview': {
|
||||||
|
onPreview(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'sync': {
|
||||||
|
onSync(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useGridColumns(onActionClick, getDataSourceConfigName),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getCodegenTablePage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<InfraCodegenApi.CodegenTable>,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 获取数据源配置列表 */
|
||||||
|
async function initDataSourceConfig() {
|
||||||
|
try {
|
||||||
|
dataSourceConfigList.value = await getDataSourceConfigList();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据源配置失败', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
initDataSourceConfig();
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<template #doc>
|
||||||
|
<DocAlert
|
||||||
|
title="代码生成(单表)"
|
||||||
|
url="https://doc.iocoder.cn/new-feature/"
|
||||||
|
/>
|
||||||
|
<DocAlert
|
||||||
|
title="代码生成(树表)"
|
||||||
|
url="https://doc.iocoder.cn/new-feature/tree/"
|
||||||
|
/>
|
||||||
|
<DocAlert
|
||||||
|
title="代码生成(主子表)"
|
||||||
|
url="https://doc.iocoder.cn/new-feature/master-sub/"
|
||||||
|
/>
|
||||||
|
<DocAlert title="单元测试" url="https://doc.iocoder.cn/unit-test/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ImportModal @success="onRefresh" />
|
||||||
|
<PreviewModal />
|
||||||
|
<Grid table-title="代码生成列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
@click="onImport"
|
||||||
|
v-access:code="['infra:codegen:create']"
|
||||||
|
>
|
||||||
|
<Plus class="size-5" />
|
||||||
|
导入
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
|
||||||
|
import { watch } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
import type { SystemDictTypeApi } from '#/api/system/dict/type';
|
||||||
|
|
||||||
|
import { nextTick, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { ElCheckbox, ElInput, ElOption, ElSelect } from 'element-plus';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getSimpleDictTypeList } from '#/api/system/dict/type';
|
||||||
|
|
||||||
|
import { useCodegenColumnTableColumns } from '../data';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
columns?: InfraCodegenApi.CodegenColumn[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
/** 表格配置 */
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: useCodegenColumnTableColumns(),
|
||||||
|
border: true,
|
||||||
|
showOverflow: true,
|
||||||
|
autoResize: true,
|
||||||
|
keepSource: true,
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 监听外部传入的列数据 */
|
||||||
|
watch(
|
||||||
|
() => props.columns,
|
||||||
|
async (columns) => {
|
||||||
|
if (!columns) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await nextTick();
|
||||||
|
gridApi.grid?.loadData(columns);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 提供获取表格数据的方法供父组件调用 */
|
||||||
|
defineExpose({
|
||||||
|
getData: (): InfraCodegenApi.CodegenColumn[] => gridApi.grid.getData(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
const dictTypeOptions = ref<SystemDictTypeApi.DictType[]>([]); // 字典类型选项
|
||||||
|
onMounted(async () => {
|
||||||
|
dictTypeOptions.value = await getSimpleDictTypeList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Grid>
|
||||||
|
<!-- 字段描述 -->
|
||||||
|
<template #columnComment="{ row }">
|
||||||
|
<ElInput v-model="row.columnComment" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Java 类型 -->
|
||||||
|
<template #javaType="{ row, column }">
|
||||||
|
<ElSelect v-model="row.javaType" style="width: 100%">
|
||||||
|
<ElOption
|
||||||
|
v-for="option in column.params.options"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</template>
|
||||||
|
<!-- Java 属性 -->
|
||||||
|
<template #javaField="{ row }">
|
||||||
|
<ElInput v-model="row.javaField" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 插入 -->
|
||||||
|
<template #createOperation="{ row }">
|
||||||
|
<ElCheckbox v-model="row.createOperation" />
|
||||||
|
</template>
|
||||||
|
<!-- 编辑 -->
|
||||||
|
<template #updateOperation="{ row }">
|
||||||
|
<ElCheckbox v-model="row.updateOperation" />
|
||||||
|
</template>
|
||||||
|
<!-- 列表 -->
|
||||||
|
<template #listOperationResult="{ row }">
|
||||||
|
<ElCheckbox v-model="row.listOperationResult" />
|
||||||
|
</template>
|
||||||
|
<!-- 查询 -->
|
||||||
|
<template #listOperation="{ row }">
|
||||||
|
<ElCheckbox v-model="row.listOperation" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 查询方式 -->
|
||||||
|
<template #listOperationCondition="{ row, column }">
|
||||||
|
<ElSelect v-model="row.listOperationCondition" class="w-full">
|
||||||
|
<ElOption
|
||||||
|
v-for="option in column.params.options"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 允许空 -->
|
||||||
|
<template #nullable="{ row }">
|
||||||
|
<ElCheckbox v-model="row.nullable" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 显示类型 -->
|
||||||
|
<template #htmlType="{ row, column }">
|
||||||
|
<ElSelect v-model="row.htmlType" class="w-full">
|
||||||
|
<ElOption
|
||||||
|
v-for="option in column.params.options"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 字典类型 -->
|
||||||
|
<template #dictType="{ row }">
|
||||||
|
<ElSelect v-model="row.dictType" class="w-full" clearable filterable>
|
||||||
|
<ElOption
|
||||||
|
v-for="option in dictTypeOptions"
|
||||||
|
:key="option.type"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.type"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 示例 -->
|
||||||
|
<template #example="{ row }">
|
||||||
|
<ElInput v-model="row.example" />
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getCodegenTableList } from '#/api/infra/codegen';
|
||||||
|
import { InfraCodegenTemplateTypeEnum } from '#/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useGenerationInfoBaseFormSchema,
|
||||||
|
useGenerationInfoSubTableFormSchema,
|
||||||
|
useGenerationInfoTreeFormSchema,
|
||||||
|
} from '../data';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
columns?: InfraCodegenApi.CodegenColumn[];
|
||||||
|
table?: InfraCodegenApi.CodegenTable;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const tables = ref<InfraCodegenApi.CodegenTable[]>([]);
|
||||||
|
|
||||||
|
/** 计算当前模板类型 */
|
||||||
|
const currentTemplateType = ref<number>();
|
||||||
|
const isTreeTable = computed(
|
||||||
|
() => currentTemplateType.value === InfraCodegenTemplateTypeEnum.TREE,
|
||||||
|
);
|
||||||
|
const isSubTable = computed(
|
||||||
|
() => currentTemplateType.value === InfraCodegenTemplateTypeEnum.SUB,
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 基础表单实例 */
|
||||||
|
const [BaseForm, baseFormApi] = useVbenForm({
|
||||||
|
wrapperClass: 'grid grid-cols-1 md:grid-cols-2 gap-4', // 配置表单布局为两列
|
||||||
|
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: 'grid grid-cols-1 md:grid-cols-2 gap-4', // 配置表单布局为两列
|
||||||
|
layout: 'horizontal',
|
||||||
|
showDefaultActions: false,
|
||||||
|
schema: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 主子表信息表单实例 */
|
||||||
|
const [SubForm, subFormApi] = useVbenForm({
|
||||||
|
wrapperClass: 'grid grid-cols-1 md:grid-cols-2 gap-4', // 配置表单布局为两列
|
||||||
|
layout: 'horizontal',
|
||||||
|
showDefaultActions: false,
|
||||||
|
schema: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 更新树表信息表单 schema */
|
||||||
|
function updateTreeSchema(): void {
|
||||||
|
treeFormApi.setState({
|
||||||
|
schema: useGenerationInfoTreeFormSchema(props.columns),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新主子表信息表单 schema */
|
||||||
|
function updateSubSchema(): void {
|
||||||
|
subFormApi.setState({
|
||||||
|
schema: useGenerationInfoSubTableFormSchema(props.columns, tables.value),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取合并的表单值 */
|
||||||
|
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() {
|
||||||
|
// 验证基础表单
|
||||||
|
const { valid: baseFormValid } = await baseFormApi.validate();
|
||||||
|
// 根据模板类型验证对应的额外表单
|
||||||
|
let extraValid = true;
|
||||||
|
if (isTreeTable.value) {
|
||||||
|
const { valid: treeFormValid } = await treeFormApi.validate();
|
||||||
|
extraValid = treeFormValid;
|
||||||
|
} else if (isSubTable.value) {
|
||||||
|
const { valid: subFormValid } = await subFormApi.validate();
|
||||||
|
extraValid = subFormValid;
|
||||||
|
}
|
||||||
|
return baseFormValid && extraValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设置表单值 */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = val as InfraCodegenApi.CodegenTable;
|
||||||
|
// 初始化树表的 schema
|
||||||
|
updateTreeSchema();
|
||||||
|
// 设置表单值
|
||||||
|
setAllFormValues(table);
|
||||||
|
// 获取表数据,用于主子表选择
|
||||||
|
const dataSourceConfigId = table.dataSourceConfigId;
|
||||||
|
if (dataSourceConfigId === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tables.value = await getCodegenTableList(dataSourceConfigId);
|
||||||
|
// 初始化子表 schema
|
||||||
|
updateSubSchema();
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 暴露出表单校验方法和表单值获取方法 */
|
||||||
|
defineExpose({
|
||||||
|
validate: validateAllForms,
|
||||||
|
getValues: getAllFormValues,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- 基础表单 -->
|
||||||
|
<BaseForm />
|
||||||
|
<!-- 树表信息表单 -->
|
||||||
|
<TreeForm v-if="isTreeTable" />
|
||||||
|
<!-- 主子表信息表单 -->
|
||||||
|
<SubForm v-if="isSubTable" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { ElLoading, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { createCodegenList, getSchemaTableList } from '#/api/infra/codegen';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import {
|
||||||
|
useImportTableColumns,
|
||||||
|
useImportTableFormSchema,
|
||||||
|
} from '#/views/infra/codegen/data';
|
||||||
|
|
||||||
|
/** 定义组件事件 */
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'success'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const formData = reactive<InfraCodegenApi.CodegenCreateListReqVO>({
|
||||||
|
dataSourceConfigId: 0,
|
||||||
|
tableNames: [], // 已选择的表列表
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 表格实例 */
|
||||||
|
const [Grid] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useImportTableFormSchema(),
|
||||||
|
submitOnChange: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useImportTableColumns(),
|
||||||
|
height: 600,
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
if (formValues.dataSourceConfigId === undefined) {
|
||||||
|
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-1/2',
|
||||||
|
async onConfirm() {
|
||||||
|
modalApi.lock();
|
||||||
|
// 1.1 获取表单值
|
||||||
|
if (formData?.dataSourceConfigId === undefined) {
|
||||||
|
ElMessage.error('请选择数据源');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 1.2 校验是否选择了表
|
||||||
|
if (formData.tableNames.length === 0) {
|
||||||
|
ElMessage.error('请选择需要导入的表');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 提交请求
|
||||||
|
const loadingInstance = ElLoading.service({
|
||||||
|
text: '导入中...',
|
||||||
|
fullscreen: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await createCodegenList(formData);
|
||||||
|
// 关闭并提示
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||||
|
} finally {
|
||||||
|
loadingInstance.close();
|
||||||
|
modalApi.unlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal>
|
||||||
|
<Grid />
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,376 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
// TODO @芋艿:待定,vben2.0 有 CodeEditor,不确定官方后续会不会迁移!!!
|
||||||
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
|
||||||
|
import { h, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Copy } from '@vben/icons';
|
||||||
|
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
import { ElButton, ElMessage, ElTabPane, ElTabs, ElTree } from 'element-plus';
|
||||||
|
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 codeMap = ref<Map<string, string>>(new Map<string, string>());
|
||||||
|
const setCodeMap = (key: string, lang: string, code: string) => {
|
||||||
|
// 处理可能的缩进问题,特别是对Java文件
|
||||||
|
const trimmedCode = code.trimStart();
|
||||||
|
// 如果已有缓存则不重新构建
|
||||||
|
if (codeMap.value.has(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const highlightedCode = hljs.highlight(trimmedCode, {
|
||||||
|
language: lang,
|
||||||
|
}).value;
|
||||||
|
codeMap.value.set(key, highlightedCode);
|
||||||
|
} catch {
|
||||||
|
codeMap.value.set(key, trimmedCode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const removeCodeMapKey = (targetKey: any) => {
|
||||||
|
// 只有一个代码视图时不允许删除
|
||||||
|
if (codeMap.value.size === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (codeMap.value.has(targetKey)) {
|
||||||
|
codeMap.value.delete(targetKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 复制代码 */
|
||||||
|
const copyCode = async () => {
|
||||||
|
const { copy } = useClipboard();
|
||||||
|
const file = previewFiles.value.find(
|
||||||
|
(item) => item.filePath === activeKey.value,
|
||||||
|
);
|
||||||
|
if (file) {
|
||||||
|
await copy(file.code);
|
||||||
|
ElMessage.success('复制成功');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 文件节点点击事件 */
|
||||||
|
const handleNodeClick = (node: FileNode) => {
|
||||||
|
if (!node.isLeaf) return;
|
||||||
|
|
||||||
|
activeKey.value = node.key;
|
||||||
|
const file = previewFiles.value.find((item) => {
|
||||||
|
const list = activeKey.value.split('.');
|
||||||
|
// 特殊处理-包合并
|
||||||
|
if (list.length > 2) {
|
||||||
|
const lang = list.pop();
|
||||||
|
return item.filePath === `${list.join('/')}.${lang}`;
|
||||||
|
}
|
||||||
|
return item.filePath === activeKey.value;
|
||||||
|
});
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const lang = file.filePath.split('.').pop() || '';
|
||||||
|
setCodeMap(activeKey.value, lang, 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 cursor = 0;
|
||||||
|
let fullPath = '';
|
||||||
|
|
||||||
|
while (cursor < paths.length) {
|
||||||
|
const path = paths[cursor] || '';
|
||||||
|
const oldFullPath = fullPath;
|
||||||
|
|
||||||
|
// 处理Java包路径特殊情况
|
||||||
|
if (path === 'java' && cursor + 1 < paths.length) {
|
||||||
|
fullPath = fullPath ? `${fullPath}/${path}` : path;
|
||||||
|
cursor++;
|
||||||
|
|
||||||
|
// 合并包路径
|
||||||
|
let packagePath = '';
|
||||||
|
while (cursor < paths.length) {
|
||||||
|
const nextPath = paths[cursor] || '';
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
'controller',
|
||||||
|
'convert',
|
||||||
|
'dal',
|
||||||
|
'dataobject',
|
||||||
|
'enums',
|
||||||
|
'mysql',
|
||||||
|
'service',
|
||||||
|
'vo',
|
||||||
|
].includes(nextPath)
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
packagePath = packagePath ? `${packagePath}.${nextPath}` : nextPath;
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packagePath) {
|
||||||
|
const newFullPath = `${fullPath}/${packagePath}`;
|
||||||
|
if (!exists[newFullPath]) {
|
||||||
|
exists[newFullPath] = true;
|
||||||
|
files.push({
|
||||||
|
key: newFullPath,
|
||||||
|
title: packagePath,
|
||||||
|
parentKey: oldFullPath || '/',
|
||||||
|
isLeaf: cursor === paths.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fullPath = newFullPath;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理普通路径
|
||||||
|
fullPath = fullPath ? `${fullPath}/${path}` : path;
|
||||||
|
if (!exists[fullPath]) {
|
||||||
|
exists[fullPath] = true;
|
||||||
|
files.push({
|
||||||
|
key: fullPath,
|
||||||
|
title: path,
|
||||||
|
parentKey: oldFullPath || '/',
|
||||||
|
isLeaf: cursor === paths.length - 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构建树形结构 */
|
||||||
|
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,
|
||||||
|
fullscreen: true,
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
// 关闭时清除代码视图缓存
|
||||||
|
codeMap.value.clear();
|
||||||
|
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 || '';
|
||||||
|
setCodeMap(activeKey.value, lang, code);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal title="代码预览">
|
||||||
|
<div class="flex h-full" v-loading="loading">
|
||||||
|
<!-- 文件树 -->
|
||||||
|
<div
|
||||||
|
class="h-full w-1/3 overflow-auto border-r border-gray-200 pr-4 dark:border-gray-700"
|
||||||
|
>
|
||||||
|
<ElTree
|
||||||
|
v-if="fileTree.length > 0"
|
||||||
|
:data="fileTree"
|
||||||
|
:props="{
|
||||||
|
label: 'title',
|
||||||
|
children: 'children',
|
||||||
|
isLeaf: 'isLeaf',
|
||||||
|
}"
|
||||||
|
node-key="key"
|
||||||
|
default-expand-all
|
||||||
|
@node-click="handleNodeClick"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 代码预览 -->
|
||||||
|
<div class="h-full w-2/3 overflow-auto pl-4">
|
||||||
|
<ElTabs
|
||||||
|
v-model="activeKey"
|
||||||
|
type="card"
|
||||||
|
closable
|
||||||
|
@tab-remove="removeCodeMapKey"
|
||||||
|
>
|
||||||
|
<ElTabPane
|
||||||
|
v-for="key in codeMap.keys()"
|
||||||
|
:key="key"
|
||||||
|
:label="key.split('/').pop()"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="h-full rounded-md bg-gray-50 !p-0 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
|
||||||
|
>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
|
<code
|
||||||
|
v-html="codeMap.get(activeKey)"
|
||||||
|
class="code-highlight"
|
||||||
|
></code>
|
||||||
|
</div>
|
||||||
|
</ElTabPane>
|
||||||
|
<template #extra>
|
||||||
|
<ElButton type="primary" @click="copyCode" :icon="h(Copy)">
|
||||||
|
复制代码
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElTabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
|
||||||
|
/* 代码高亮样式 - 支持暗黑模式 */
|
||||||
|
:deep(.code-highlight) {
|
||||||
|
display: block;
|
||||||
|
white-space: pre;
|
||||||
|
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>
|
||||||
Loading…
Reference in New Issue