feat: 代码生成器

pull/69/head
puhui999 2025-04-07 18:05:43 +08:00
parent cd0c7a86e5
commit b376524980
14 changed files with 1738 additions and 137 deletions

View File

@ -45,6 +45,7 @@
"dayjs": "catalog:",
"pinia": "catalog:",
"vue": "catalog:",
"vue-codemirror": "^6.1.1",
"vue-router": "catalog:"
}
}

View File

@ -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 },
});
}

View File

@ -23,11 +23,7 @@ const externalRoutes: RouteRecordRaw[] = [];
/** 404
* */
const routes: RouteRecordRaw[] = [
...coreRoutes,
...externalRoutes,
fallbackNotFoundRoute,
];
const routes: RouteRecordRaw[] = [...coreRoutes, ...externalRoutes, fallbackNotFoundRoute];
/** 基本路由列表,这些路由不需要进入权限拦截 */
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
@ -36,12 +32,10 @@ const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
// add by 芋艿from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/router/routes/index.ts#L38-L45
const componentKeys: string[] = Object.keys(
import.meta.glob('../../views/**/*.vue'),
)
const componentKeys: string[] = Object.keys(import.meta.glob('../../views/**/*.vue'))
.filter((item) => !item.includes('/modules/'))
.map((v) => {
const path = v.replace('../../views/', '/');
return path.endsWith('.vue') ? path.slice(0, -4) : path;
});
export { accessRoutes, coreRouteNames, routes, componentKeys };
export { accessRoutes, componentKeys, coreRouteNames, routes };

View File

@ -0,0 +1,49 @@
import type { RouteRecordRaw } from 'vue-router';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
order: 1000,
title: $t('demos.title'),
},
name: 'Demos',
path: '/demos',
children: [
{
meta: {
title: $t('demos.antd'),
},
name: 'AntDesignDemos',
path: '/demos/ant-design',
component: () => import('#/views/demos/antd/index.vue'),
},
],
},
{
path: '/codegen',
name: 'CodegenEdit',
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
order: 1000,
title: $t('demos.title'),
},
children: [
// {
// path: 'codegen/edit',
// name: 'InfraCodegenEdit',
// component: () => import('#/views/infra/codegen/edit.vue'),
// meta: {
// title: '修改生成配置',
// activeMenu: '/infra/codegen',
// },
// },
],
},
];
export default routes;

View File

@ -2,36 +2,24 @@ import dayjs from 'dayjs';
// TODO @芋艿:后续整理下
// TODO @puhui999转成 function 方式哈
/** 时间段选择器拓展 */
export const getRangePickerDefaultProps = () => {
export function getRangePickerDefaultProps() {
return {
showTime: {
format: 'HH:mm:ss',
defaultValue: [
dayjs('00:00:00', 'HH:mm:ss'),
dayjs('23:59:59', 'HH:mm:ss'),
],
defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('23:59:59', 'HH:mm:ss')],
},
valueFormat: 'YYYY-MM-DD HH:mm:ss',
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: ['开始时间', '结束时间'],
// prettier-ignore
ranges: {
'今天': [dayjs().startOf('day'), dayjs().endOf('day')],
'昨天': [
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
'昨天': [dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')],
'本周': [dayjs().startOf('week'), dayjs().endOf('day')],
'本月': [dayjs().startOf('month'), dayjs().endOf('day')],
'最近 7 天': [
dayjs().subtract(7, 'day').startOf('day'),
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) => {
if (dates && dates.length === 2) {
@ -40,4 +28,4 @@ export const getRangePickerDefaultProps = () => {
return {};
},
};
};
}

View File

@ -17,7 +17,7 @@ const dictStore = useDictStore();
*/
function getDictLabel(dictType: string, value: any) {
const dictObj = dictStore.getDictData(dictType, value);
return isObject(dictObj)? dictObj.label : '';
return isObject(dictObj) ? dictObj.label : '';
}
/**
@ -38,10 +38,7 @@ function getDictObj(dictType: string, value: any) {
* @param dictType
* @returns
*/
function getDictOptions(
dictType: string,
valueType: 'boolean' | 'number' | 'string' = 'string',
) {
function getDictOptions(dictType: string, valueType: 'boolean' | 'number' | 'string' = 'string') {
const dictOpts = dictStore.getDictOptions(dictType);
const dictOptions: DefaultOptionType = [];
if (dictOpts.length > 0) {
@ -72,137 +69,138 @@ function getDictOptions(
}
enum DICT_TYPE {
AI_GENERATE_MODE = 'ai_generate_mode', // AI 生成模式
AI_IMAGE_STATUS = 'ai_image_status', // AI 图片状态
AI_MUSIC_STATUS = 'ai_music_status', // AI 音乐状态
// ========== AI - 人工智能模块 ==========
AI_PLATFORM = 'ai_platform', // AI 平台
USER_TYPE = 'user_type',
COMMON_STATUS = 'common_status',
TERMINAL = 'terminal', // 终端
DATE_INTERVAL = 'date_interval', // 数据间隔
// ========== SYSTEM 模块 ==========
SYSTEM_USER_SEX = 'system_user_sex',
SYSTEM_MENU_TYPE = 'system_menu_type',
SYSTEM_ROLE_TYPE = 'system_role_type',
SYSTEM_DATA_SCOPE = 'system_data_scope',
SYSTEM_NOTICE_TYPE = 'system_notice_type',
SYSTEM_LOGIN_TYPE = 'system_login_type',
SYSTEM_LOGIN_RESULT = 'system_login_result',
SYSTEM_SMS_CHANNEL_CODE = 'system_sms_channel_code',
SYSTEM_SMS_TEMPLATE_TYPE = 'system_sms_template_type',
SYSTEM_SMS_SEND_STATUS = 'system_sms_send_status',
SYSTEM_SMS_RECEIVE_STATUS = 'system_sms_receive_status',
SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',
SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',
SYSTEM_SOCIAL_TYPE = 'system_social_type',
// ========== INFRA 模块 ==========
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
INFRA_JOB_STATUS = 'infra_job_status',
INFRA_JOB_LOG_STATUS = 'infra_job_log_status',
INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',
INFRA_CONFIG_TYPE = 'infra_config_type',
INFRA_CODEGEN_TEMPLATE_TYPE = 'infra_codegen_template_type',
INFRA_CODEGEN_FRONT_TYPE = 'infra_codegen_front_type',
INFRA_CODEGEN_SCENE = 'infra_codegen_scene',
INFRA_FILE_STORAGE = 'infra_file_storage',
INFRA_OPERATE_TYPE = 'infra_operate_type',
AI_WRITE_FORMAT = 'ai_write_format', // AI 写作格式
AI_WRITE_LANGUAGE = 'ai_write_language', // AI 写作语言
AI_WRITE_LENGTH = 'ai_write_length', // AI 写作长度
AI_WRITE_TONE = 'ai_write_tone', // AI 写作语气
AI_WRITE_TYPE = 'ai_write_type', // AI 写作类型
BPM_MODEL_FORM_TYPE = 'bpm_model_form_type',
// ========== BPM 模块 ==========
BPM_MODEL_TYPE = 'bpm_model_type',
BPM_OA_LEAVE_TYPE = 'bpm_oa_leave_type',
BPM_MODEL_FORM_TYPE = 'bpm_model_form_type',
BPM_TASK_CANDIDATE_STRATEGY = 'bpm_task_candidate_strategy',
BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status',
BPM_TASK_STATUS = 'bpm_task_status',
BPM_OA_LEAVE_TYPE = 'bpm_oa_leave_type',
BPM_PROCESS_LISTENER_TYPE = 'bpm_process_listener_type',
BPM_PROCESS_LISTENER_VALUE_TYPE = 'bpm_process_listener_value_type',
BPM_TASK_CANDIDATE_STRATEGY = 'bpm_task_candidate_strategy',
BPM_TASK_STATUS = 'bpm_task_status',
BROKERAGE_BANK_NAME = 'brokerage_bank_name', // 佣金提现银行
BROKERAGE_BIND_MODE = 'brokerage_bind_mode', // 分销关系绑定模式
// ========== PAY 模块 ==========
PAY_CHANNEL_CODE = 'pay_channel_code', // 支付渠道编码类型
PAY_ORDER_STATUS = 'pay_order_status', // 商户支付订单状态
PAY_REFUND_STATUS = 'pay_refund_status', // 退款订单状态
PAY_NOTIFY_STATUS = 'pay_notify_status', // 商户支付回调状态
PAY_NOTIFY_TYPE = 'pay_notify_type', // 商户支付回调状态
PAY_TRANSFER_STATUS = 'pay_transfer_status', // 转账订单状态
PAY_TRANSFER_TYPE = 'pay_transfer_type', // 转账订单状态
// ========== MP 模块 ==========
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型
// ========== Member 会员模块 ==========
MEMBER_POINT_BIZ_TYPE = 'member_point_biz_type', // 积分的业务类型
MEMBER_EXPERIENCE_BIZ_TYPE = 'member_experience_biz_type', // 会员经验业务类型
// ========== MALL - 商品模块 ==========
PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态
// ========== MALL - 交易模块 ==========
EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', //快递的计费方式
TRADE_AFTER_SALE_STATUS = 'trade_after_sale_status', // 售后 - 状态
TRADE_AFTER_SALE_WAY = 'trade_after_sale_way', // 售后 - 方式
TRADE_AFTER_SALE_TYPE = 'trade_after_sale_type', // 售后 - 类型
TRADE_ORDER_TYPE = 'trade_order_type', // 订单 - 类型
TRADE_ORDER_STATUS = 'trade_order_status', // 订单 - 状态
TRADE_ORDER_ITEM_AFTER_SALE_STATUS = 'trade_order_item_after_sale_status', // 订单项 - 售后状态
TRADE_DELIVERY_TYPE = 'trade_delivery_type', // 配送方式
BROKERAGE_ENABLED_CONDITION = 'brokerage_enabled_condition', // 分佣模式
BROKERAGE_BIND_MODE = 'brokerage_bind_mode', // 分销关系绑定模式
BROKERAGE_BANK_NAME = 'brokerage_bank_name', // 佣金提现银行
BROKERAGE_WITHDRAW_TYPE = 'brokerage_withdraw_type', // 佣金提现类型
BROKERAGE_RECORD_BIZ_TYPE = 'brokerage_record_biz_type', // 佣金业务类型
BROKERAGE_RECORD_STATUS = 'brokerage_record_status', // 佣金状态
BROKERAGE_WITHDRAW_STATUS = 'brokerage_withdraw_status', // 佣金提现状态
BROKERAGE_WITHDRAW_TYPE = 'brokerage_withdraw_type', // 佣金提现类型
COMMON_STATUS = 'common_status',
// ========== MALL - 营销模块 ==========
PROMOTION_DISCOUNT_TYPE = 'promotion_discount_type', // 优惠类型
PROMOTION_PRODUCT_SCOPE = 'promotion_product_scope', // 营销的商品范围
PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_type', // 优惠劵模板的有限期类型
PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态
PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式
PROMOTION_CONDITION_TYPE = 'promotion_condition_type', // 营销的条件类型枚举
PROMOTION_BARGAIN_RECORD_STATUS = 'promotion_bargain_record_status', // 砍价记录的状态
PROMOTION_COMBINATION_RECORD_STATUS = 'promotion_combination_record_status', // 拼团记录的状态
PROMOTION_BANNER_POSITION = 'promotion_banner_position', // banner 定位
// ========== CRM - 客户管理模块 ==========
CRM_AUDIT_STATUS = 'crm_audit_status', // CRM 审批状态
CRM_BIZ_TYPE = 'crm_biz_type', // CRM 业务类型
CRM_BUSINESS_END_STATUS_TYPE = 'crm_business_end_status_type', // CRM 商机结束状态类型
CRM_RECEIVABLE_RETURN_TYPE = 'crm_receivable_return_type', // CRM 回款的还款方式
CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry', // CRM 客户所属行业
CRM_CUSTOMER_LEVEL = 'crm_customer_level', // CRM 客户级别
CRM_CUSTOMER_SOURCE = 'crm_customer_source', // CRM 客户来源
CRM_FOLLOW_UP_TYPE = 'crm_follow_up_type', // CRM 跟进方式
CRM_PERMISSION_LEVEL = 'crm_permission_level', // CRM 数据权限的级别
CRM_PRODUCT_STATUS = 'crm_product_status', // CRM 商品状态
CRM_PERMISSION_LEVEL = 'crm_permission_level', // CRM 数据权限的级别
CRM_PRODUCT_UNIT = 'crm_product_unit', // CRM 产品单位
CRM_RECEIVABLE_RETURN_TYPE = 'crm_receivable_return_type', // CRM 回款的还款方式
DATE_INTERVAL = 'date_interval', // 数据间隔
CRM_FOLLOW_UP_TYPE = 'crm_follow_up_type', // CRM 跟进方式
// ========== ERP - 企业资源计划模块 ==========
ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态
ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type', // 库存明细的业务类型
// ========== MALL - 交易模块 ==========
EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', // 快递的计费方式
INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',
// ========== INFRA 模块 ==========
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
INFRA_CODEGEN_FRONT_TYPE = 'infra_codegen_front_type',
INFRA_CODEGEN_SCENE = 'infra_codegen_scene',
INFRA_CODEGEN_TEMPLATE_TYPE = 'infra_codegen_template_type',
INFRA_CONFIG_TYPE = 'infra_config_type',
// ========== AI - 人工智能模块 ==========
AI_PLATFORM = 'ai_platform', // AI 平台
AI_MODEL_TYPE = 'ai_model_type', // AI 模型类型
AI_IMAGE_STATUS = 'ai_image_status', // AI 图片状态
AI_MUSIC_STATUS = 'ai_music_status', // AI 音乐状态
AI_GENERATE_MODE = 'ai_generate_mode', // AI 生成模式
AI_WRITE_TYPE = 'ai_write_type', // AI 写作类型
AI_WRITE_LENGTH = 'ai_write_length', // AI 写作长度
AI_WRITE_FORMAT = 'ai_write_format', // AI 写作格式
AI_WRITE_TONE = 'ai_write_tone', // AI 写作语气
AI_WRITE_LANGUAGE = 'ai_write_language', // AI 写作语言
INFRA_FILE_STORAGE = 'infra_file_storage',
INFRA_JOB_LOG_STATUS = 'infra_job_log_status',
INFRA_JOB_STATUS = 'infra_job_status',
INFRA_OPERATE_TYPE = 'infra_operate_type',
IOT_DATA_FORMAT = 'iot_data_format', // IOT 数据格式
IOT_DATA_TYPE = 'iot_data_type', // IOT 数据类型
IOT_DEVICE_STATUS = 'iot_device_status', // IOT 设备状态
// ========== IOT - 物联网模块 ==========
IOT_NET_TYPE = 'iot_net_type', // IOT 联网方式
IOT_PRODUCT_DEVICE_TYPE = 'iot_product_device_type', // IOT 产品设备类型
IOT_PRODUCT_FUNCTION_TYPE = 'iot_product_function_type', // IOT 产品功能类型
IOT_PRODUCT_STATUS = 'iot_product_status', // IOT 产品状态
IOT_PROTOCOL_TYPE = 'iot_protocol_type', // IOT 接入网关协议
IOT_RW_TYPE = 'iot_rw_type', // IOT 读写类型
IOT_UNIT_TYPE = 'iot_unit_type', // IOT 单位类型
IOT_VALIDATE_TYPE = 'iot_validate_type', // IOT 数据校验级别
MEMBER_EXPERIENCE_BIZ_TYPE = 'member_experience_biz_type', // 会员经验业务类型
// ========== Member 会员模块 ==========
MEMBER_POINT_BIZ_TYPE = 'member_point_biz_type', // 积分的业务类型
// ========== MP 模块 ==========
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型
// ========== PAY 模块 ==========
PAY_CHANNEL_CODE = 'pay_channel_code', // 支付渠道编码类型
PAY_NOTIFY_STATUS = 'pay_notify_status', // 商户支付回调状态
PAY_NOTIFY_TYPE = 'pay_notify_type', // 商户支付回调状态
PAY_ORDER_STATUS = 'pay_order_status', // 商户支付订单状态
PAY_REFUND_STATUS = 'pay_refund_status', // 退款订单状态
PAY_TRANSFER_STATUS = 'pay_transfer_status', // 转账订单状态
PAY_TRANSFER_TYPE = 'pay_transfer_type', // 转账订单状态
// ========== MALL - 商品模块 ==========
PRODUCT_SPU_STATUS = 'product_spu_status', // 商品状态
PROMOTION_BANNER_POSITION = 'promotion_banner_position', // banner 定位
PROMOTION_BARGAIN_RECORD_STATUS = 'promotion_bargain_record_status', // 砍价记录的状态
PROMOTION_COMBINATION_RECORD_STATUS = 'promotion_combination_record_status', // 拼团记录的状态
PROMOTION_CONDITION_TYPE = 'promotion_condition_type', // 营销的条件类型枚举
PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态
PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式
PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_type', // 优惠劵模板的有限期类型
// ========== MALL - 营销模块 ==========
PROMOTION_DISCOUNT_TYPE = 'promotion_discount_type', // 优惠类型
PROMOTION_PRODUCT_SCOPE = 'promotion_product_scope', // 营销的商品范围
SYSTEM_DATA_SCOPE = 'system_data_scope',
SYSTEM_LOGIN_RESULT = 'system_login_result',
SYSTEM_LOGIN_TYPE = 'system_login_type',
SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
SYSTEM_MENU_TYPE = 'system_menu_type',
SYSTEM_NOTICE_TYPE = 'system_notice_type',
SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',
SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',
SYSTEM_ROLE_TYPE = 'system_role_type',
SYSTEM_SMS_CHANNEL_CODE = 'system_sms_channel_code',
SYSTEM_SMS_RECEIVE_STATUS = 'system_sms_receive_status',
SYSTEM_SMS_SEND_STATUS = 'system_sms_send_status',
SYSTEM_SMS_TEMPLATE_TYPE = 'system_sms_template_type',
SYSTEM_SOCIAL_TYPE = 'system_social_type',
// ========== SYSTEM 模块 ==========
SYSTEM_USER_SEX = 'system_user_sex',
TERMINAL = 'terminal', // 终端
TRADE_AFTER_SALE_STATUS = 'trade_after_sale_status', // 售后 - 状态
TRADE_AFTER_SALE_TYPE = 'trade_after_sale_type', // 售后 - 类型
TRADE_AFTER_SALE_WAY = 'trade_after_sale_way', // 售后 - 方式
TRADE_DELIVERY_TYPE = 'trade_delivery_type', // 配送方式
TRADE_ORDER_ITEM_AFTER_SALE_STATUS = 'trade_order_item_after_sale_status', // 订单项 - 售后状态
TRADE_ORDER_STATUS = 'trade_order_status', // 订单 - 状态
TRADE_ORDER_TYPE = 'trade_order_type', // 订单 - 类型
USER_TYPE = 'user_type',
IOT_PRODUCT_STATUS = 'iot_product_status', // IOT 产品状态
IOT_PRODUCT_DEVICE_TYPE = 'iot_product_device_type', // IOT 产品设备类型
IOT_DATA_FORMAT = 'iot_data_format', // IOT 数据格式
IOT_PROTOCOL_TYPE = 'iot_protocol_type', // IOT 接入网关协议
IOT_DEVICE_STATUS = 'iot_device_status', // IOT 设备状态
IOT_PRODUCT_FUNCTION_TYPE = 'iot_product_function_type', // IOT 产品功能类型
IOT_DATA_TYPE = 'iot_data_type', // IOT 数据类型
IOT_UNIT_TYPE = 'iot_unit_type', // IOT 单位类型
IOT_RW_TYPE = 'iot_rw_type', // IOT 读写类型
}
export { DICT_TYPE, getDictObj, getDictLabel, getDictOptions };
export { DICT_TYPE, getDictLabel, getDictObj, getDictOptions };

View File

@ -0,0 +1,95 @@
<script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen';
import { QuestionCircleFilled } from '@vben/icons';
import { ref, unref, watch } from 'vue';
defineOptions({ name: 'InfraCodegenBasicInfoForm' });
const props = defineProps<{
table?: InfraCodegenApi.CodegenTable;
}>();
const formRef = ref();
const formData = ref({
tableName: '',
tableComment: '',
className: '',
author: '',
remark: '',
});
const rules = {
tableName: [{ required: true, message: '请输入表名称', trigger: 'blur' }],
tableComment: [{ required: true, message: '请输入表描述', trigger: 'blur' }],
className: [{ required: true, message: '请输入实体类名称', trigger: 'blur' }],
author: [{ required: true, message: '请输入作者', trigger: 'blur' }],
};
/** 监听 table 属性,复制给 formData 属性 */
watch(
() => props.table,
(table) => {
if (!table) return;
formData.value = { ...table };
},
{
deep: true,
immediate: true,
},
);
defineExpose({
validate: async () => {
try {
await unref(formRef).validate();
return true;
} catch {
return false;
}
},
});
</script>
<template>
<a-form ref="formRef" :model="formData" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="表名称" name="tableName">
<a-input v-model:value="formData.tableName" placeholder="请输入仓库名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="表描述" name="tableComment">
<a-input v-model:value="formData.tableComment" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item name="className">
<template #label>
<span>
实体类名称
<a-tooltip
title="默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。"
placement="top"
>
<QuestionCircleFilled />
</a-tooltip>
</span>
</template>
<a-input v-model:value="formData.className" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="作者" name="author">
<a-input v-model:value="formData.author" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-textarea v-model:value="formData.remark" :rows="3" />
</a-form-item>
</a-col>
</a-row>
</a-form>
</template>

View File

@ -0,0 +1,150 @@
<script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen';
import type { SystemDictTypeApi } from '#/api/system/dict/type';
import { getSimpleDictTypeList } from '#/api/system/dict/type';
import { computed, onMounted, ref, watch } from 'vue';
defineOptions({ name: 'InfraCodegenColumInfoForm' });
interface Props {
columns?: InfraCodegenApi.CodegenColumn[];
}
const props = defineProps<Props>();
const formData = ref<InfraCodegenApi.CodegenColumn[]>([]);
const tableHeight = computed(() => document.documentElement.scrollHeight - 350);
const dictOptions = ref<SystemDictTypeApi.SystemDictType[]>([]);
//
const columns = [
{ title: '字段列名', dataIndex: 'columnName', width: 100 },
{ title: '字段描述', dataIndex: 'columnComment', width: 100 },
{ title: '物理类型', dataIndex: 'dataType', width: 100 },
{ title: 'Java类型', dataIndex: 'javaType', width: 110 },
{ title: 'java属性', dataIndex: 'javaField', width: 100 },
{ title: '插入', dataIndex: 'createOperation', width: 40 },
{ title: '编辑', dataIndex: 'updateOperation', width: 40 },
{ title: '列表', dataIndex: 'listOperationResult', width: 40 },
{ title: '查询', dataIndex: 'listOperation', width: 40 },
{ title: '查询方式', dataIndex: 'listOperationCondition', width: 100 },
{ title: '允许空', dataIndex: 'nullable', width: 50 },
{ title: '显示类型', dataIndex: 'htmlType', width: 120 },
{ title: '字典类型', dataIndex: 'dictType', width: 120 },
{ title: '示例', dataIndex: 'example', width: 100 },
];
//
const filterOption = (input: string, option: any) => {
return option.children[0].toLowerCase().includes(input.toLowerCase());
};
/** 查询字典下拉列表 */
const getDictOptions = async () => {
dictOptions.value = await getSimpleDictTypeList();
};
watch(
() => props.columns,
(columns) => {
if (!columns) return;
formData.value = [...columns];
},
{
deep: true,
immediate: true,
},
);
onMounted(async () => {
await getDictOptions();
});
</script>
<template>
<a-table
ref="tableRef"
:data-source="formData"
:columns="columns"
:scroll="{ y: tableHeight }"
:pagination="false"
row-key="columnId"
>
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'columnComment'">
<a-input v-model:value="record.columnComment" />
</template>
<template v-else-if="column.dataIndex === 'javaType'">
<a-select v-model:value="record.javaType" style="width: 100%">
<a-select-option value="Long">Long</a-select-option>
<a-select-option value="String">String</a-select-option>
<a-select-option value="Integer">Integer</a-select-option>
<a-select-option value="Double">Double</a-select-option>
<a-select-option value="BigDecimal">BigDecimal</a-select-option>
<a-select-option value="LocalDateTime">LocalDateTime</a-select-option>
<a-select-option value="Boolean">Boolean</a-select-option>
</a-select>
</template>
<template v-else-if="column.dataIndex === 'javaField'">
<a-input v-model:value="record.javaField" />
</template>
<template v-else-if="column.dataIndex === 'createOperation'">
<a-checkbox v-model:checked="record.createOperation" />
</template>
<template v-else-if="column.dataIndex === 'updateOperation'">
<a-checkbox v-model:checked="record.updateOperation" />
</template>
<template v-else-if="column.dataIndex === 'listOperationResult'">
<a-checkbox v-model:checked="record.listOperationResult" />
</template>
<template v-else-if="column.dataIndex === 'listOperation'">
<a-checkbox v-model:checked="record.listOperation" />
</template>
<template v-else-if="column.dataIndex === 'listOperationCondition'">
<a-select v-model:value="record.listOperationCondition" style="width: 100%">
<a-select-option value="=">=</a-select-option>
<a-select-option value="!=">!=</a-select-option>
<a-select-option value=">">></a-select-option>
<a-select-option value=">=">>=</a-select-option>
<a-select-option value="<"><</a-select-option>
<a-select-option value="<="><=</a-select-option>
<a-select-option value="LIKE">LIKE</a-select-option>
<a-select-option value="BETWEEN">BETWEEN</a-select-option>
</a-select>
</template>
<template v-else-if="column.dataIndex === 'nullable'">
<a-checkbox v-model:checked="record.nullable" />
</template>
<template v-else-if="column.dataIndex === 'htmlType'">
<a-select v-model:value="record.htmlType" style="width: 100%">
<a-select-option value="input">文本框</a-select-option>
<a-select-option value="textarea">文本域</a-select-option>
<a-select-option value="select">下拉框</a-select-option>
<a-select-option value="radio">单选框</a-select-option>
<a-select-option value="checkbox">复选框</a-select-option>
<a-select-option value="datetime">日期控件</a-select-option>
<a-select-option value="imageUpload">图片上传</a-select-option>
<a-select-option value="fileUpload">文件上传</a-select-option>
<a-select-option value="editor">富文本控件</a-select-option>
</a-select>
</template>
<template v-else-if="column.dataIndex === 'dictType'">
<a-select
v-model:value="record.dictType"
allow-clear
show-search
placeholder="请选择"
style="width: 100%"
:filter-option="filterOption"
>
<a-select-option v-for="dict in dictOptions" :key="dict.id" :value="dict.type">
{{ dict.name }}
</a-select-option>
</a-select>
</template>
<template v-else-if="column.dataIndex === 'example'">
<a-input v-model:value="record.example" />
</template>
</template>
</a-table>
</template>

View File

@ -0,0 +1,355 @@
<script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen';
import type { SystemMenuApi } from '#/api/system/menu';
import { QuestionCircleFilled } from '@vben/icons';
import { getCodegenTableList } from '#/api/infra/codegen';
import { getSimpleMenusList } from '#/api/system/menu';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { onMounted, ref, unref, watch } from 'vue';
defineOptions({ name: 'InfraCodegenGenerateInfoForm' });
interface Props {
columns?: InfraCodegenApi.CodegenColumn[];
table?: InfraCodegenApi.CodegenTable;
}
const props = defineProps<Props>();
const formRef = ref();
const formData = ref<InfraCodegenApi.CodegenTable>({} as InfraCodegenApi.CodegenTable);
const subColumns = ref<InfraCodegenApi.CodegenColumn[]>([]);
const tables = ref<InfraCodegenApi.CodegenTable[]>([]);
const menus = ref<SystemMenuApi.SystemMenu[]>([]);
/** 菜单树选项 */
const menuTreeProps = {
value: 'id',
title: 'name',
children: 'children',
};
const rules = {
templateType: [{ required: true, message: '生成模板不能为空', trigger: 'blur' }],
frontType: [{ required: true, message: '前端类型不能为空', trigger: 'blur' }],
scene: [{ required: true, message: '生成场景不能为空', trigger: 'blur' }],
moduleName: [{ required: true, message: '模块名不能为空', trigger: 'blur' }],
businessName: [{ required: true, message: '业务名不能为空', trigger: 'blur' }],
className: [{ required: true, message: '类名称不能为空', trigger: 'blur' }],
classComment: [{ required: true, message: '类描述不能为空', trigger: 'blur' }],
};
/** 监听 table 属性,复制给 formData 属性 */
watch(
() => props.table,
(table) => {
if (!table) return;
formData.value = { ...table };
},
{
deep: true,
immediate: true,
},
);
/** 子表名称选中 */
async function subTableNameSelected(value) {
// 使ID
const subTableColumns = await getSubCodegenColumns(formData.value.dataSourceConfigId, value);
subColumns.value = subTableColumns;
//
formData.value.subTableFkColumnId = undefined;
formData.value.mainTableFKColumnId = undefined;
}
/** 获取子表的列 */
async function getSubCodegenColumns(dataSourceConfigId: number, tableName: string) {
const tableList = await getCodegenTableList(dataSourceConfigId);
const subTable = tableList.find((item) => item.tableName === tableName);
if (subTable) {
// API
//
return [];
}
return [];
}
/** 初始化 */
onMounted(async () => {
//
menus.value = await getSimpleMenusList();
//
tables.value = await getCodegenTableList(formData.value.dataSourceConfigId);
});
/** 暴露方法 */
defineExpose({
validate: async () => {
try {
await unref(formRef).validate();
return true;
} catch {
return false;
}
},
});
</script>
<template>
<a-form ref="formRef" :model="formData" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="生成模板" name="templateType">
<a-select v-model:value="formData.templateType">
<a-select-option
v-for="dict in getDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE, 'number')"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="前端类型" name="frontType">
<a-select v-model:value="formData.frontType">
<a-select-option
v-for="dict in getDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE, 'number')"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="生成场景" name="scene">
<a-select v-model:value="formData.scene">
<a-select-option
v-for="dict in getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE, 'number')"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="上级菜单">
<a-tooltip placement="top">
<template #title>分配到指定菜单下例如 系统管理</template>
<template #default>
<a-tree-select
v-model:value="formData.parentMenuId"
:tree-data="menus"
:field-names="menuTreeProps"
show-search
tree-node-filter-prop="title"
:tree-checkable="false"
:tree-default-expand-all="false"
placeholder="请选择系统菜单"
style="width: 100%"
/>
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="模块名" name="moduleName">
<a-tooltip placement="top">
<template #title>模块名即一级目录例如 systeminfratool 等等</template>
<template #default>
<a-input v-model:value="formData.moduleName" />
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="业务名" name="businessName">
<a-tooltip placement="top">
<template #title>业务名即二级目录例如 userpermissiondict 等等</template>
<template #default>
<a-input v-model:value="formData.businessName" />
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="类名称" name="className">
<a-tooltip placement="top">
<template #title>类名称首字母大写例如SysUserSysMenuSysDictData 等等</template>
<template #default>
<a-input v-model:value="formData.className" />
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="类描述" name="classComment">
<a-tooltip placement="top">
<template #title>用作类描述例如 用户</template>
<template #default>
<a-input v-model:value="formData.classComment" />
</template>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
<!-- 树表信息 -->
<template v-if="formData.templateType == 2">
<a-divider>树表信息</a-divider>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="父编号字段" name="treeParentColumnId">
<a-tooltip placement="top">
<template #title>树显示的父编码字段名 parent_Id</template>
<template #icon><QuestionCircleFilled /></template>
<template #default>
<a-select v-model:value="formData.treeParentColumnId" placeholder="请选择" allow-clear>
<a-select-option v-for="column in columns" :key="column.id" :value="column.id">
{{ column.columnName }}
</a-select-option>
</a-select>
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="名称字段" name="treeNameColumnId">
<a-tooltip placement="top">
<template #title>树节点显示的名称字段一般是name</template>
<template #icon><QuestionCircleFilled /></template>
<template #default>
<a-select v-model:value="formData.treeNameColumnId" placeholder="请选择" allow-clear>
<a-select-option v-for="column in columns" :key="column.id" :value="column.id">
{{ column.columnName }}
</a-select-option>
</a-select>
</template>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
</template>
<!-- 主子表信息 -->
<template v-if="formData.templateType == 3">
<a-divider>主子表信息</a-divider>
<a-row :gutter="16">
<a-col :span="24">
<a-form-item label="关联子表的成员变量名" name="subJoinColumnId">
<a-tooltip placement="top">
<template #title>子表的成员变量名默认为子表的名称</template>
<template #icon><QuestionCircleFilled /></template>
<template #default>
<a-input v-model:value="formData.subJoinColumnId" placeholder="请输入" />
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="子表的表名" name="subTableName">
<a-tooltip placement="top">
<template #title>关联的子表的表名 sys_user_post</template>
<template #icon><QuestionCircleFilled /></template>
<template #default>
<a-select
v-model:value="formData.subTableName"
placeholder="请选择"
allow-clear
@change="subTableNameSelected"
>
<a-select-option v-for="table in tables" :key="table.tableName" :value="table.tableName">
{{ `${table.tableName}${table.tableComment}` }}
</a-select-option>
</a-select>
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="子表的类名" name="subTableClassName">
<a-tooltip placement="top">
<template #title>子表的类名 SysUserPost</template>
<template #icon><QuestionCircleFilled /></template>
<template #default>
<a-input v-model:value="formData.subTableClassName" placeholder="请输入" />
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="子表的外键字段" name="subTableFkColumnId">
<a-tooltip placement="top">
<template #title>子表的外键字段的编号对应主表的主键</template>
<template #icon><QuestionCircleFilled /></template>
<template #default>
<a-select v-model:value="formData.subTableFkColumnId" placeholder="请选择" allow-clear>
<a-select-option v-for="column in subColumns" :key="column.id" :value="column.id">
{{ column.columnName }}
</a-select-option>
</a-select>
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="主表的类名" name="mainTableClassName">
<a-tooltip placement="top">
<template #title>主表的类名 SysUser</template>
<template #icon><QuestionCircleFilled /></template>
<template #default>
<a-input v-model:value="formData.mainTableClassName" placeholder="请输入" />
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="主表的外键字段" name="mainTableFKColumnId">
<a-tooltip placement="top">
<template #title>子表的外键关联到主表的主键字段编号</template>
<template #icon><QuestionCircleFilled /></template>
<template #default>
<a-select v-model:value="formData.mainTableFKColumnId" placeholder="请选择" allow-clear>
<a-select-option v-for="column in columns" :key="column.id" :value="column.id">
{{ column.columnName }}
</a-select-option>
</a-select>
</template>
</a-tooltip>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="子表的类描述" name="subTableClassComment">
<a-tooltip placement="top">
<template #title>子表的描述例如 用户岗位</template>
<template #icon><QuestionCircleFilled /></template>
<template #default>
<a-input v-model:value="formData.subTableClassComment" placeholder="请输入" />
</template>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
</template>
</a-form>
</template>
<style scoped>
.form-header {
padding-left: 8px;
margin-bottom: 16px;
font-size: 16px;
border-left: 3px solid #1890ff;
}
</style>

View File

@ -0,0 +1,178 @@
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 { getDataSourceConfigList } from '#/api/infra/data-source-config';
import { getRangePickerDefaultProps } from '#/utils/date';
import { useAccess } from '@vben/access';
const { hasAccessByCodes } = useAccess();
/** 导入数据库表的表单 */
export function useImportTableFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'dataSourceConfigId',
label: '数据源',
component: 'ApiSelect',
componentProps: {
api: async () => await getDataSourceConfigList(),
labelField: 'name',
valueField: 'id',
placeholder: '请选择数据源',
},
rules: 'required',
},
{
fieldName: 'name',
label: '表名称',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入表名称',
},
},
{
fieldName: 'comment',
label: '表描述',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入表描述',
},
},
];
}
/** 预览代码模态框 */
export function usePreviewFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'content',
component: 'Textarea',
componentProps: {
readonly: true,
},
},
];
}
/** 列表的搜索表单 */
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']),
},
],
},
},
];
}

View File

@ -0,0 +1,104 @@
<script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen';
import { Page } from '@vben/common-ui';
import { Button, message, Tabs } from 'ant-design-vue';
import { ArrowLeftOutlined } from '@vben/icons';
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { $t } from '#/locales';
import { getCodegenTable, updateCodegenTable } from '#/api/infra/codegen';
import BasicInfo from './components/BasicInfoForm.vue';
import ColumnInfo from './components/ColumInfoForm.vue';
import GenerateInfo from './components/GenerateInfoForm.vue';
const route = useRoute();
const router = useRouter();
const loading = ref(false);
const activeKey = ref('colum');
const formData = ref<InfraCodegenApi.CodegenDetail>({
table: {},
columns: [],
});
//
const basicInfoRef = ref();
const columnInfoRef = ref();
const generateInfoRef = ref();
//
const getDetail = async () => {
const id = Number(route.query.id);
if (!id) return;
loading.value = true;
try {
formData.value = await getCodegenTable(id);
} finally {
loading.value = false;
}
};
//
const submitForm = async () => {
if (!formData.value) return;
//
await basicInfoRef.value?.validate();
await generateInfoRef.value?.validate();
const hideLoading = message.loading({
content: $t('ui.actionMessage.saving'),
duration: 0,
key: 'action_process_msg',
});
try {
await updateCodegenTable(formData.value);
message.success({
content: $t('ui.actionMessage.saveSuccess'),
key: 'action_process_msg',
});
close();
} catch (error) {
console.error('保存失败', error);
} finally {
hideLoading();
}
};
//
const close = () => {
router.push('/infra/codegen');
};
//
getDetail();
</script>
<template>
<Page auto-content-height>
<div v-loading="loading">
<Tabs v-model:activeKey="activeKey">
<Tabs.TabPane key="basicInfo" tab="基本信息">
<BasicInfo ref="basicInfoRef" :table="formData.table" />
</Tabs.TabPane>
<Tabs.TabPane key="colum" tab="字段信息">
<ColumnInfo ref="columnInfoRef" :columns="formData.columns" />
</Tabs.TabPane>
<Tabs.TabPane key="generateInfo" tab="生成信息">
<GenerateInfo ref="generateInfoRef" :table="formData.table" :columns="formData.columns" />
</Tabs.TabPane>
</Tabs>
<div class="flex justify-end mt-4">
<Button type="primary" :loading="loading" @click="submitForm"></Button>
<Button class="ml-2" @click="close">
<ArrowLeftOutlined class="mr-1" />
返回
</Button>
</div>
</div>
</Page>
</template>

View File

@ -0,0 +1,204 @@
<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 } 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.id).open();
}
/** 编辑表格 */
function onEdit(row: InfraCodegenApi.CodegenTable) {
router.push(`/infra/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();
} 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>

View File

@ -0,0 +1,118 @@
<script lang="ts" setup>
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { createCodegenList, getSchemaTableList } from '#/api/infra/codegen';
import { ref } from 'vue';
import { useImportTableFormSchema } from '../data';
const emit = defineEmits<{
(e: 'success'): void;
}>();
const tableList = ref<string[]>([]);
//
const [Form, formApi] = useVbenForm({
layout: 'horizontal',
schema: useImportTableFormSchema(),
showDefaultActions: false,
});
//
const [Table, tableApi] = useVbenVxeGrid({
columns: [
{ field: 'name', title: '表名称', minWidth: 200 },
{ field: 'comment', title: '表描述', minWidth: 200 },
],
toolbarConfig: false,
rowConfig: {
keyField: 'name',
},
rowSelection: {
multiple: true,
onChange: (records) => {
tableList.value = records.map((item) => item.name);
},
},
});
//
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
if (tableList.value.length === 0) {
message.warning('请选择至少一个表');
return;
}
const values = await formApi.getValues();
modalApi.lock();
try {
await createCodegenList({
dataSourceConfigId: values.dataSourceConfigId,
tableNames: tableList.value,
});
message.success('导入成功');
await modalApi.close();
emit('success');
} finally {
modalApi.lock(false);
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
tableList.value = [];
tableApi.clearSelection();
return;
}
formApi.reset();
},
});
//
async function getList() {
const values = await formApi.getValues();
if (!values.dataSourceConfigId) {
message.warning('请选择数据源');
return;
}
tableApi.loading(true);
try {
const data = await getSchemaTableList(values);
tableApi.loadData(data);
} finally {
tableApi.loading(false);
}
}
//
async function resetQuery() {
formApi.reset();
tableApi.clearData();
}
//
formApi.on('fieldValueChange', (field) => {
if (field.name === 'dataSourceConfigId') {
getList();
}
});
</script>
<template>
<Modal title="导入表">
<div class="px-4">
<Form class="mb-3" @submit="getList" @reset="resetQuery">
<template #actions>
<a-space>
<a-button html-type="submit">搜索</a-button>
<a-button html-type="reset">重置</a-button>
</a-space>
</template>
</Form>
<Table height="300px" />
</div>
</Modal>
</template>

View File

@ -0,0 +1,222 @@
<script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen';
import { useVbenModal } from '@vben/common-ui';
import { CopyOutlined } from '@vben/icons';
import { message, Tree } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { previewCodegen } from '#/api/infra/codegen';
import { ref } from 'vue';
import { useClipboard } from '@vueuse/core';
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 { usePreviewFormSchema } from '../data';
import 'highlight.js/styles/github.css';
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 [Form, formApi] = useVbenForm({
schema: usePreviewFormSchema(),
showActionButtonGroup: false,
});
//
const copyCode = async () => {
const { copy } = useClipboard();
await copy(highlightedCode.value);
message.success('复制成功');
};
//
const handleNodeClick = (selectedKeys: string[], 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;
}
formApi.setFieldValue('content', 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,
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
previewFiles.value = [];
fileTree.value = [];
activeKey.value = '';
highlightedCode.value = '';
return;
}
// ID
const id = modalApi.getData<number>();
if (!id) return;
//
loading.value = true;
try {
const data = await previewCodegen(id);
previewFiles.value = data;
fileTree.value = handleFiles(data);
//
if (data.length > 0) {
activeKey.value = data[0].filePath;
const lang = data[0].filePath.split('.').pop() || '';
try {
highlightedCode.value = hljs.highlight(data[0].code, { language: lang }).value;
} catch {
highlightedCode.value = data[0].code;
}
formApi.setFieldValue('content', data[0].code);
}
} finally {
loading.value = false;
}
},
});
</script>
<template>
<Modal title="代码预览">
<div class="flex h-[70vh]" v-loading="loading">
<!-- 文件树 -->
<div class="w-1/3 border-r pr-4">
<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">{{ activeKey.split('/').pop() }}</div>
<a-button type="primary" @click="copyCode">
<CopyOutlined class="mr-1" />
复制代码
</a-button>
</div>
<div class="h-[calc(100%-40px)] overflow-auto">
<pre><code v-html="highlightedCode" class="hljs"></code></pre>
</div>
</div>
</div>
</Modal>
</template>
<style scoped>
.hljs {
padding: 16px;
overflow: auto;
background-color: #f8f8f8;
border-radius: 4px;
}
</style>