commit
17734ab040
|
@ -43,6 +43,7 @@
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
"ant-design-vue": "catalog:",
|
"ant-design-vue": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
|
"highlight.js": "catalog:",
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-router": "catalog:"
|
"vue-router": "catalog:"
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace InfraCodegenApi {
|
||||||
|
/** 代码生成表定义 */
|
||||||
|
export interface CodegenTable {
|
||||||
|
id: number;
|
||||||
|
tableId: number;
|
||||||
|
isParentMenuIdValid: boolean;
|
||||||
|
dataSourceConfigId: number;
|
||||||
|
scene: number;
|
||||||
|
tableName: string;
|
||||||
|
tableComment: string;
|
||||||
|
remark: string;
|
||||||
|
moduleName: string;
|
||||||
|
businessName: string;
|
||||||
|
className: string;
|
||||||
|
classComment: string;
|
||||||
|
author: string;
|
||||||
|
createTime: Date;
|
||||||
|
updateTime: Date;
|
||||||
|
templateType: number;
|
||||||
|
parentMenuId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 代码生成字段定义 */
|
||||||
|
export interface CodegenColumn {
|
||||||
|
id: number;
|
||||||
|
tableId: number;
|
||||||
|
columnName: string;
|
||||||
|
dataType: string;
|
||||||
|
columnComment: string;
|
||||||
|
nullable: number;
|
||||||
|
primaryKey: number;
|
||||||
|
ordinalPosition: number;
|
||||||
|
javaType: string;
|
||||||
|
javaField: string;
|
||||||
|
dictType: string;
|
||||||
|
example: string;
|
||||||
|
createOperation: number;
|
||||||
|
updateOperation: number;
|
||||||
|
listOperation: number;
|
||||||
|
listOperationCondition: string;
|
||||||
|
listOperationResult: number;
|
||||||
|
htmlType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 数据库表定义 */
|
||||||
|
export interface DatabaseTable {
|
||||||
|
name: string;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 代码生成详情 */
|
||||||
|
export interface CodegenDetail {
|
||||||
|
table: CodegenTable;
|
||||||
|
columns: CodegenColumn[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 代码预览 */
|
||||||
|
export interface CodegenPreview {
|
||||||
|
filePath: string;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新代码生成请求 */
|
||||||
|
export interface CodegenUpdateReq {
|
||||||
|
table: any | CodegenTable;
|
||||||
|
columns: CodegenColumn[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建代码生成请求 */
|
||||||
|
export interface CodegenCreateListReq {
|
||||||
|
dataSourceConfigId?: number;
|
||||||
|
tableNames: string[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询列表代码生成表定义 */
|
||||||
|
export function getCodegenTableList(dataSourceConfigId: number) {
|
||||||
|
return requestClient.get<InfraCodegenApi.CodegenTable[]>('/infra/codegen/table/list', {
|
||||||
|
params: { dataSourceConfigId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询列表代码生成表定义 */
|
||||||
|
export function getCodegenTablePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<InfraCodegenApi.CodegenTable>>('/infra/codegen/table/page', { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询详情代码生成表定义 */
|
||||||
|
export function getCodegenTable(id: number) {
|
||||||
|
return requestClient.get<InfraCodegenApi.CodegenDetail>('/infra/codegen/detail', {
|
||||||
|
params: { tableId: id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增代码生成表定义 */
|
||||||
|
export function createCodegenTable(data: InfraCodegenApi.CodegenCreateListReq) {
|
||||||
|
return requestClient.post('/infra/codegen/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 修改代码生成表定义 */
|
||||||
|
export function updateCodegenTable(data: InfraCodegenApi.CodegenUpdateReq) {
|
||||||
|
return requestClient.put('/infra/codegen/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 基于数据库的表结构,同步数据库的表和字段定义 */
|
||||||
|
export function syncCodegenFromDB(id: number) {
|
||||||
|
return requestClient.put('/infra/codegen/sync-from-db', {
|
||||||
|
params: { tableId: id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 预览生成代码 */
|
||||||
|
export function previewCodegen(id: number) {
|
||||||
|
return requestClient.get<InfraCodegenApi.CodegenPreview[]>('/infra/codegen/preview', {
|
||||||
|
params: { tableId: id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 下载生成代码 */
|
||||||
|
export function downloadCodegen(id: number) {
|
||||||
|
return requestClient.download('/infra/codegen/download', {
|
||||||
|
params: { tableId: id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获得表定义 */
|
||||||
|
export function getSchemaTableList(params: any) {
|
||||||
|
return requestClient.get<InfraCodegenApi.DatabaseTable[]>('/infra/codegen/db/table/list', { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 基于数据库的表结构,创建代码生成器的表定义 */
|
||||||
|
export function createCodegenList(data: InfraCodegenApi.CodegenCreateListReq) {
|
||||||
|
return requestClient.post('/infra/codegen/create-list', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除代码生成表定义 */
|
||||||
|
export function deleteCodegenTable(id: number) {
|
||||||
|
return requestClient.delete('/infra/codegen/delete', {
|
||||||
|
params: { tableId: id },
|
||||||
|
});
|
||||||
|
}
|
|
@ -18,36 +18,36 @@ export namespace SystemMailAccountApi {
|
||||||
remark: string;
|
remark: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO @puhui999:改成 function 风格;不用 await
|
|
||||||
/** 查询邮箱账号列表 */
|
/** 查询邮箱账号列表 */
|
||||||
export const getMailAccountPage = async (params: PageParam) => {
|
export function getMailAccountPage(params: PageParam) {
|
||||||
return await requestClient.get<PageResult<SystemMailAccountApi.SystemMailAccount>>(
|
return requestClient.get<PageResult<SystemMailAccountApi.SystemMailAccount>>(
|
||||||
'/system/mail-account/page',
|
'/system/mail-account/page',
|
||||||
{ params },
|
{ params }
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 查询邮箱账号详情 */
|
/** 查询邮箱账号详情 */
|
||||||
export const getMailAccount = async (id: number) => {
|
export function getMailAccount(id: number) {
|
||||||
return await requestClient.get<SystemMailAccountApi.SystemMailAccount>(`/system/mail-account/get?id=${id}`);
|
return requestClient.get<SystemMailAccountApi.SystemMailAccount>(`/system/mail-account/get?id=${id}`);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 新增邮箱账号 */
|
/** 新增邮箱账号 */
|
||||||
export const createMailAccount = async (data: SystemMailAccountApi.SystemMailAccount) => {
|
export function createMailAccount(data: SystemMailAccountApi.SystemMailAccount) {
|
||||||
return await requestClient.post<SystemMailAccountApi.SystemMailAccount>('/system/mail-account/create', data);
|
return requestClient.post('/system/mail-account/create', data);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 修改邮箱账号 */
|
/** 修改邮箱账号 */
|
||||||
export const updateMailAccount = async (data: SystemMailAccountApi.SystemMailAccount) => {
|
export function updateMailAccount(data: SystemMailAccountApi.SystemMailAccount) {
|
||||||
return await requestClient.put<SystemMailAccountApi.SystemMailAccount>('/system/mail-account/update', data);
|
return requestClient.put('/system/mail-account/update', data);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 删除邮箱账号 */
|
/** 删除邮箱账号 */
|
||||||
export const deleteMailAccount = async (id: number) => {
|
export function deleteMailAccount(id: number) {
|
||||||
return await requestClient.delete<boolean>(`/system/mail-account/delete?id=${id}`);
|
return requestClient.delete(`/system/mail-account/delete?id=${id}`);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 获得邮箱账号精简列表 */
|
/** 获得邮箱账号精简列表 */
|
||||||
export const getSimpleMailAccountList = async () => {
|
export function getSimpleMailAccountList() {
|
||||||
return await requestClient.get<SystemMailAccountApi.SystemMailAccount[]>('/system/mail-account/simple-list');
|
return requestClient.get<SystemMailAccountApi.SystemMailAccount[]>('/system/mail-account/simple-list');
|
||||||
};
|
}
|
||||||
|
|
|
@ -24,21 +24,21 @@ export namespace SystemMailLogApi {
|
||||||
createTime: string;
|
createTime: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO @puhui999:改成 function 风格;不用 await
|
|
||||||
/** 查询邮件日志列表 */
|
/** 查询邮件日志列表 */
|
||||||
export const getMailLogPage = async (params: PageParam) => {
|
export function getMailLogPage(params: PageParam) {
|
||||||
return await requestClient.get<PageResult<SystemMailLogApi.SystemMailLog>>(
|
return requestClient.get<PageResult<SystemMailLogApi.SystemMailLog>>(
|
||||||
'/system/mail-log/page',
|
'/system/mail-log/page',
|
||||||
{ params }
|
{ params }
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 查询邮件日志详情 */
|
/** 查询邮件日志详情 */
|
||||||
export const getMailLog = async (id: number) => {
|
export function getMailLog(id: number) {
|
||||||
return await requestClient.get<SystemMailLogApi.SystemMailLog>(`/system/mail-log/get?${id}`);
|
return requestClient.get<SystemMailLogApi.SystemMailLog>(`/system/mail-log/get?id=${id}`);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 重新发送邮件 */
|
/** 重新发送邮件 */
|
||||||
export const resendMail = async (id: number) => {
|
export function resendMail(id: number) {
|
||||||
return await requestClient.put<boolean>(`/system/mail-log/resend?id=${id}`);
|
return requestClient.put(`/system/mail-log/resend?id=${id}`);
|
||||||
};
|
}
|
||||||
|
|
|
@ -25,36 +25,36 @@ export namespace SystemMailTemplateApi {
|
||||||
templateParams: Record<string, any>;
|
templateParams: Record<string, any>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO @puhui999:改成 function 风格;不用 await
|
|
||||||
/** 查询邮件模版列表 */
|
/** 查询邮件模版列表 */
|
||||||
export const getMailTemplatePage = async (params: PageParam) => {
|
export function getMailTemplatePage(params: PageParam) {
|
||||||
return await requestClient.get<PageResult<SystemMailTemplateApi.SystemMailTemplate>>(
|
return requestClient.get<PageResult<SystemMailTemplateApi.SystemMailTemplate>>(
|
||||||
'/system/mail-template/page',
|
'/system/mail-template/page',
|
||||||
{ params }
|
{ params }
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 查询邮件模版详情 */
|
/** 查询邮件模版详情 */
|
||||||
export const getMailTemplate = async (id: number) => {
|
export function getMailTemplate(id: number) {
|
||||||
return await requestClient.get<SystemMailTemplateApi.SystemMailTemplate>(`/system/mail-template/get?id=${id}`);
|
return requestClient.get<SystemMailTemplateApi.SystemMailTemplate>(`/system/mail-template/get?id=${id}`);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 新增邮件模版 */
|
/** 新增邮件模版 */
|
||||||
export const createMailTemplate = async (data: SystemMailTemplateApi.SystemMailTemplate) => {
|
export function createMailTemplate(data: SystemMailTemplateApi.SystemMailTemplate) {
|
||||||
return await requestClient.post<SystemMailTemplateApi.SystemMailTemplate>('/system/mail-template/create', data);
|
return requestClient.post('/system/mail-template/create', data);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 修改邮件模版 */
|
/** 修改邮件模版 */
|
||||||
export const updateMailTemplate = async (data: SystemMailTemplateApi.SystemMailTemplate) => {
|
export function updateMailTemplate(data: SystemMailTemplateApi.SystemMailTemplate) {
|
||||||
return await requestClient.put<SystemMailTemplateApi.SystemMailTemplate>('/system/mail-template/update', data);
|
return requestClient.put('/system/mail-template/update', data);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 删除邮件模版 */
|
/** 删除邮件模版 */
|
||||||
export const deleteMailTemplate = async (id: number) => {
|
export function deleteMailTemplate(id: number) {
|
||||||
return await requestClient.delete<boolean>(`/system/mail-template/delete?id=${id}`);
|
return requestClient.delete(`/system/mail-template/delete?id=${id}`);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 发送邮件 */
|
/** 发送邮件 */
|
||||||
export const sendMail = async (data: SystemMailTemplateApi.MailSendReqVO) => {
|
export function sendMail(data: SystemMailTemplateApi.MailSendReqVO) {
|
||||||
return await requestClient.post<boolean>('/system/mail-template/send-mail', data);
|
return requestClient.post('/system/mail-template/send-mail', data);
|
||||||
};
|
}
|
||||||
|
|
|
@ -11,8 +11,30 @@ const routes: RouteRecordRaw[] = [
|
||||||
activePath: '/infra/job',
|
activePath: '/infra/job',
|
||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
path: '/codegen',
|
||||||
|
name: 'CodegenEdit',
|
||||||
|
meta: {
|
||||||
|
icon: 'ic:baseline-view-in-ar',
|
||||||
|
keepAlive: true,
|
||||||
|
order: 1000,
|
||||||
|
title: '代码生成',
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/codegen/edit',
|
||||||
|
name: 'InfraCodegenEdit',
|
||||||
|
component: () => import('#/views/infra/codegen/edit.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '修改生成配置',
|
||||||
|
activeMenu: '/infra/codegen',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
|
|
@ -72,7 +72,7 @@ export const SystemUserSocialTypeEnum = {
|
||||||
export const InfraCodegenTemplateTypeEnum = {
|
export const InfraCodegenTemplateTypeEnum = {
|
||||||
CRUD: 1, // 基础 CRUD
|
CRUD: 1, // 基础 CRUD
|
||||||
TREE: 2, // 树形 CRUD
|
TREE: 2, // 树形 CRUD
|
||||||
SUB: 3 // 主子表 CRUD
|
SUB: 15 // 主子表 CRUD
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,9 +2,8 @@ import dayjs from 'dayjs';
|
||||||
|
|
||||||
// TODO @芋艿:后续整理下
|
// TODO @芋艿:后续整理下
|
||||||
|
|
||||||
// TODO @puhui999:转成 function 方式哈
|
|
||||||
/** 时间段选择器拓展 */
|
/** 时间段选择器拓展 */
|
||||||
export const getRangePickerDefaultProps = () => {
|
export function getRangePickerDefaultProps() {
|
||||||
return {
|
return {
|
||||||
showTime: {
|
showTime: {
|
||||||
format: 'HH:mm:ss',
|
format: 'HH:mm:ss',
|
||||||
|
@ -16,23 +15,16 @@ export const getRangePickerDefaultProps = () => {
|
||||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||||
format: 'YYYY-MM-DD HH:mm:ss',
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
placeholder: ['开始时间', '结束时间'],
|
placeholder: ['开始时间', '结束时间'],
|
||||||
|
// prettier-ignore
|
||||||
ranges: {
|
ranges: {
|
||||||
'今天': [dayjs().startOf('day'), dayjs().endOf('day')],
|
'今天': [dayjs().startOf('day'), dayjs().endOf('day')],
|
||||||
'昨天': [
|
'昨天': [dayjs().subtract(1, 'day').startOf('day'),
|
||||||
dayjs().subtract(1, 'day').startOf('day'),
|
dayjs().subtract(1, 'day').endOf('day')],
|
||||||
dayjs().subtract(1, 'day').endOf('day'),
|
'本周': [dayjs().startOf('week'), dayjs().endOf('day')],
|
||||||
],
|
'本月': [dayjs().startOf('month'), dayjs().endOf('day')],
|
||||||
'本周': [dayjs().startOf('week'), dayjs().endOf('day')],
|
'最近 7 天': [dayjs().subtract(7, 'day').startOf('day'), dayjs().endOf('day')],
|
||||||
'本月': [dayjs().startOf('month'), dayjs().endOf('day')],
|
'最近 30 天': [dayjs().subtract(30, 'day').startOf('day'), dayjs().endOf('day')],
|
||||||
'最近 7 天': [
|
},
|
||||||
dayjs().subtract(7, 'day').startOf('day'),
|
|
||||||
dayjs().endOf('day'),
|
|
||||||
],
|
|
||||||
'最近 30 天': [
|
|
||||||
dayjs().subtract(30, 'day').startOf('day'),
|
|
||||||
dayjs().endOf('day'),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
transformDateFunc: (dates: any) => {
|
transformDateFunc: (dates: any) => {
|
||||||
if (dates && dates.length === 2) {
|
if (dates && dates.length === 2) {
|
||||||
return [dates.createTime[0], dates.createTime[1]].join(','); // 格式化为后台支持的时间格式
|
return [dates.createTime[0], dates.createTime[1]].join(','); // 格式化为后台支持的时间格式
|
||||||
|
@ -40,4 +32,4 @@ export const getRangePickerDefaultProps = () => {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
|
@ -0,0 +1,580 @@
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
|
||||||
|
import type { SystemMenuApi } from '#/api/system/menu';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { getMenuList } from '#/api/system/menu';
|
||||||
|
import { getRangePickerDefaultProps } from '#/utils/date';
|
||||||
|
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
||||||
|
import { handleTree } from '#/utils/tree';
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { useAccess } from '@vben/access';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
const { hasAccessByCodes } = useAccess();
|
||||||
|
|
||||||
|
/** 导入数据库表的表单 */
|
||||||
|
export function useImportTableFormSchema(
|
||||||
|
dataSourceConfigList: InfraDataSourceConfigApi.InfraDataSourceConfig[],
|
||||||
|
): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'dataSourceConfigId',
|
||||||
|
label: '数据源',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: dataSourceConfigList.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
})),
|
||||||
|
placeholder: '请选择数据源',
|
||||||
|
},
|
||||||
|
defaultValue: dataSourceConfigList[0]?.id,
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '表名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入表名称',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'comment',
|
||||||
|
label: '表描述',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入表描述',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 基本信息表单的 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,
|
||||||
|
},
|
||||||
|
// 使用 Tailwind 的 col-span-2 让元素跨越两列
|
||||||
|
formItemClass: 'md:col-span-2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 生成信息表单基础 schema */
|
||||||
|
export function useGenerationInfoBaseFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'templateType',
|
||||||
|
label: '生成模板',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE, 'number'),
|
||||||
|
},
|
||||||
|
rules: z.number().min(1, { message: '生成模板不能为空' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'frontType',
|
||||||
|
label: '前端类型',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE, 'number'),
|
||||||
|
},
|
||||||
|
rules: z.number().min(1, { message: '前端类型不能为空' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'scene',
|
||||||
|
label: '生成场景',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE, 'number'),
|
||||||
|
},
|
||||||
|
rules: z.number().min(1, { message: '生成场景不能为空' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'parentMenuId',
|
||||||
|
label: '上级菜单',
|
||||||
|
help: '分配到指定菜单下,例如 系统管理',
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
api: async () => {
|
||||||
|
const data = await getMenuList();
|
||||||
|
data.unshift({
|
||||||
|
id: 0,
|
||||||
|
name: '顶级菜单',
|
||||||
|
} as SystemMenuApi.SystemMenu);
|
||||||
|
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: z.string().min(1, { message: '模块名不能为空' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'businessName',
|
||||||
|
label: '业务名',
|
||||||
|
help: '业务名,即二级目录,例如 user、permission、dict 等等',
|
||||||
|
rules: z.string().min(1, { message: '业务名不能为空' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'className',
|
||||||
|
label: '类名称',
|
||||||
|
help: '类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等',
|
||||||
|
rules: z.string().min(1, { message: '类名称不能为空' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'classComment',
|
||||||
|
label: '类描述',
|
||||||
|
help: '用作类描述,例如 用户',
|
||||||
|
rules: z.string().min(1, { message: '类描述不能为空' }),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 树表信息 schema */
|
||||||
|
export function useTreeTableFormSchema(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 useSubTableFormSchema(
|
||||||
|
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>,
|
||||||
|
dataSourceConfigList: InfraDataSourceConfigApi.InfraDataSourceConfig[],
|
||||||
|
): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'dataSourceConfigId',
|
||||||
|
title: '数据源',
|
||||||
|
minWidth: 120,
|
||||||
|
formatter: ({ cellValue }) => {
|
||||||
|
const config = dataSourceConfigList.find((item) => item.id === cellValue);
|
||||||
|
return config ? config.name : '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: 100,
|
||||||
|
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: 50,
|
||||||
|
slots: { default: 'nullable' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'htmlType',
|
||||||
|
title: '显示类型',
|
||||||
|
width: 120,
|
||||||
|
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,148 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
|
||||||
|
import BasicInfo from './modules/basic-info.vue';
|
||||||
|
import ColumnInfo from './modules/column-info.vue';
|
||||||
|
import GenerationInfo from './modules/generation-info.vue';
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { ChevronsLeft } from '@vben/icons';
|
||||||
|
import { Button, message, Steps } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getCodegenTable, updateCodegenTable } from '#/api/infra/codegen';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { ref, unref } from 'vue';
|
||||||
|
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
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) {
|
||||||
|
message.warn('保存失败,原因:基本信息表单校验失败请检查!!!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const generateInfoValid = await generateInfoRef.value?.validate();
|
||||||
|
if (!generateInfoValid) {
|
||||||
|
message.warn('保存失败,原因:生成信息表单校验失败请检查!!!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.updating'),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
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 });
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.operationSuccess'),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存失败', error);
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 返回列表 */
|
||||||
|
const close = () => {
|
||||||
|
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">
|
||||||
|
<Steps type="navigation" :current="currentStep" class="mb-8 rounded shadow-sm dark:bg-[#141414]">
|
||||||
|
<Steps.Step v-for="(step, index) in steps" :key="index" :title="step.title" />
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<Button v-show="currentStep > 0" @click="prevStep">上一步</Button>
|
||||||
|
<Button v-show="currentStep < steps.length - 1" type="primary" @click="nextStep">下一步</Button>
|
||||||
|
<Button v-show="currentStep === steps.length - 1" type="primary" :loading="loading" @click="submitForm">
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
<Button @click="close">
|
||||||
|
<ChevronsLeft class="mr-1" />
|
||||||
|
返回
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
|
@ -0,0 +1,207 @@
|
||||||
|
<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 { DocAlert } from '#/components/doc-alert';
|
||||||
|
import ImportTable from './modules/import-table.vue';
|
||||||
|
import PreviewCode from './modules/preview-code.vue';
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { deleteCodegenTable, downloadCodegen, getCodegenTablePage, syncCodegenFromDB } from '#/api/infra/codegen';
|
||||||
|
import { getDataSourceConfigList } from '#/api/infra/data-source-config';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const dataSourceConfigList = ref<InfraDataSourceConfigApi.InfraDataSourceConfig[]>([]);
|
||||||
|
|
||||||
|
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(`/codegen/edit?id=${row.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除代码生成配置 */
|
||||||
|
async function onDelete(row: InfraCodegenApi.CodegenTable) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.tableName]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteCodegenTable(row.id);
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.tableName]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
onRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 同步数据库 */
|
||||||
|
async function onSync(row: InfraCodegenApi.CodegenTable) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.updating', [row.tableName]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await syncCodegenFromDB(row.id);
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.updateSuccess', [row.tableName]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
onRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 生成代码 */
|
||||||
|
async function onGenerate(row: InfraCodegenApi.CodegenTable) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: '正在生成代码...',
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
message.success({
|
||||||
|
content: '代码生成成功',
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表格操作按钮的回调函数 */
|
||||||
|
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, dataSourceConfigList.value),
|
||||||
|
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();
|
||||||
|
gridApi.setState({
|
||||||
|
gridOptions: { columns: useGridColumns(onActionClick, dataSourceConfigList.value) },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据源配置失败', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
initDataSourceConfig();
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<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/" />
|
||||||
|
|
||||||
|
<ImportModal @success="onRefresh" />
|
||||||
|
<PreviewModal />
|
||||||
|
<Grid table-title="代码生成列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button type="primary" @click="onImport" v-access:code="['infra:codegen:create']">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', ['导入']) }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -33,7 +33,7 @@ function isBoolean(value: unknown): value is boolean {
|
||||||
* @param {T} value 要检查的值。
|
* @param {T} value 要检查的值。
|
||||||
* @returns {boolean} 如果值为空,返回true,否则返回false。
|
* @returns {boolean} 如果值为空,返回true,否则返回false。
|
||||||
*/
|
*/
|
||||||
function isEmpty<T = unknown>(value?: T): value is T {
|
function isEmpty<T = unknown>(value?: T): boolean {
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
3481
pnpm-lock.yaml
3481
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -191,3 +191,4 @@ catalog:
|
||||||
watermark-js-plus: ^1.5.8
|
watermark-js-plus: ^1.5.8
|
||||||
zod: ^3.24.2
|
zod: ^3.24.2
|
||||||
zod-defaults: ^0.1.3
|
zod-defaults: ^0.1.3
|
||||||
|
highlight.js: ^11.11.1
|
||||||
|
|
Loading…
Reference in New Issue