Pre Merge pull request !69 from puhui999/v-next-dev

pull/69/MERGE
puhui999 2025-04-09 10:13:31 +00:00 committed by Gitee
commit 53d1dc6c65
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
16 changed files with 2149 additions and 259 deletions

View File

@ -43,6 +43,7 @@
"@vueuse/core": "catalog:",
"ant-design-vue": "catalog:",
"dayjs": "catalog:",
"highlight.js": "catalog:",
"pinia": "catalog:",
"vue": "catalog:",
"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

@ -11,8 +11,30 @@ const routes: RouteRecordRaw[] = [
activePath: '/infra/job',
keepAlive: false,
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;

View File

@ -9,14 +9,14 @@
// 全局通用状态枚举
export const CommonStatusEnum = {
ENABLE: 0, // 开启
DISABLE: 1 // 禁用
}
DISABLE: 1, // 禁用
};
// 全局用户类型枚举
export const UserTypeEnum = {
MEMBER: 1, // 会员
ADMIN: 2 // 管理员
}
ADMIN: 2, // 管理员
};
// ========== SYSTEM 模块 ==========
/**
@ -25,16 +25,16 @@ export const UserTypeEnum = {
export const SystemMenuTypeEnum = {
DIR: 1, // 目录
MENU: 2, // 菜单
BUTTON: 3 // 按钮
}
BUTTON: 3, // 按钮
};
/**
*
*/
export const SystemRoleTypeEnum = {
SYSTEM: 1, // 内置角色
CUSTOM: 2 // 自定义角色
}
CUSTOM: 2, // 自定义角色
};
/**
*
@ -44,8 +44,8 @@ export const SystemDataScopeEnum = {
DEPT_CUSTOM: 2, // 指定部门数据权限
DEPT_ONLY: 3, // 部门数据权限
DEPT_AND_CHILD: 4, // 部门及以下数据权限
DEPT_SELF: 5 // 仅本人数据权限
}
DEPT_SELF: 5, // 仅本人数据权限
};
/**
*
@ -55,15 +55,15 @@ export const SystemUserSocialTypeEnum = {
title: '钉钉',
type: 20,
source: 'dingtalk',
img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png'
img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png',
},
WECHAT_ENTERPRISE: {
title: '企业微信',
type: 30,
source: 'wechat_enterprise',
img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png'
}
}
img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png',
},
};
// ========== INFRA 模块 ==========
/**
@ -72,8 +72,8 @@ export const SystemUserSocialTypeEnum = {
export const InfraCodegenTemplateTypeEnum = {
CRUD: 1, // 基础 CRUD
TREE: 2, // 树形 CRUD
SUB: 3 // 主子表 CRUD
}
SUB: 15, // 主子表 CRUD
};
/**
*
@ -81,8 +81,8 @@ export const InfraCodegenTemplateTypeEnum = {
export const InfraJobStatusEnum = {
INIT: 0, // 初始化中
NORMAL: 1, // 运行中
STOP: 2 // 暂停运行
}
STOP: 2, // 暂停运行
};
/**
* API
@ -90,8 +90,8 @@ export const InfraJobStatusEnum = {
export const InfraApiErrorLogProcessStatusEnum = {
INIT: 0, // 未处理
DONE: 1, // 已处理
IGNORE: 2 // 已忽略
}
IGNORE: 2, // 已忽略
};
// ========== PAY 模块 ==========
/**
@ -100,78 +100,78 @@ export const InfraApiErrorLogProcessStatusEnum = {
export const PayChannelEnum = {
WX_PUB: {
code: 'wx_pub',
name: '微信 JSAPI 支付'
name: '微信 JSAPI 支付',
},
WX_LITE: {
code: 'wx_lite',
name: '微信小程序支付'
name: '微信小程序支付',
},
WX_APP: {
code: 'wx_app',
name: '微信 APP 支付'
name: '微信 APP 支付',
},
WX_NATIVE: {
code: 'wx_native',
name: '微信 Native 支付'
name: '微信 Native 支付',
},
WX_WAP: {
code: 'wx_wap',
name: '微信 WAP 网站支付'
name: '微信 WAP 网站支付',
},
WX_BAR: {
code: 'wx_bar',
name: '微信条码支付'
name: '微信条码支付',
},
ALIPAY_PC: {
code: 'alipay_pc',
name: '支付宝 PC 网站支付'
name: '支付宝 PC 网站支付',
},
ALIPAY_WAP: {
code: 'alipay_wap',
name: '支付宝 WAP 网站支付'
name: '支付宝 WAP 网站支付',
},
ALIPAY_APP: {
code: 'alipay_app',
name: '支付宝 APP 支付'
name: '支付宝 APP 支付',
},
ALIPAY_QR: {
code: 'alipay_qr',
name: '支付宝扫码支付'
name: '支付宝扫码支付',
},
ALIPAY_BAR: {
code: 'alipay_bar',
name: '支付宝条码支付'
name: '支付宝条码支付',
},
WALLET: {
code: 'wallet',
name: '钱包支付'
name: '钱包支付',
},
MOCK: {
code: 'mock',
name: '模拟支付'
}
}
name: '模拟支付',
},
};
/**
*
*/
export const PayDisplayModeEnum = {
URL: {
mode: 'url'
mode: 'url',
},
IFRAME: {
mode: 'iframe'
mode: 'iframe',
},
FORM: {
mode: 'form'
mode: 'form',
},
QR_CODE: {
mode: 'qr_code'
mode: 'qr_code',
},
APP: {
mode: 'app'
}
}
mode: 'app',
},
};
/**
*
@ -179,8 +179,8 @@ export const PayDisplayModeEnum = {
export const PayType = {
WECHAT: 'WECHAT',
ALIPAY: 'ALIPAY',
MOCK: 'MOCK'
}
MOCK: 'MOCK',
};
/**
*
@ -188,17 +188,17 @@ export const PayType = {
export const PayOrderStatusEnum = {
WAITING: {
status: 0,
name: '未支付'
name: '未支付',
},
SUCCESS: {
status: 10,
name: '已支付'
name: '已支付',
},
CLOSED: {
status: 20,
name: '未支付'
}
}
name: '未支付',
},
};
// ========== MALL - 商品模块 ==========
/**
@ -207,17 +207,17 @@ export const PayOrderStatusEnum = {
export const ProductSpuStatusEnum = {
RECYCLE: {
status: -1,
name: '回收站'
name: '回收站',
},
DISABLE: {
status: 0,
name: '下架'
name: '下架',
},
ENABLE: {
status: 1,
name: '上架'
}
}
name: '上架',
},
};
// ========== MALL - 营销模块 ==========
/**
@ -226,13 +226,13 @@ export const ProductSpuStatusEnum = {
export const CouponTemplateValidityTypeEnum = {
DATE: {
type: 1,
name: '固定日期可用'
name: '固定日期可用',
},
TERM: {
type: 2,
name: '领取之后可用'
}
}
name: '领取之后可用',
},
};
/**
*
@ -240,17 +240,17 @@ export const CouponTemplateValidityTypeEnum = {
export const CouponTemplateTakeTypeEnum = {
USER: {
type: 1,
name: '直接领取'
name: '直接领取',
},
ADMIN: {
type: 2,
name: '指定发放'
name: '指定发放',
},
REGISTER: {
type: 3,
name: '新人券'
}
}
name: '新人券',
},
};
/**
*
@ -258,17 +258,17 @@ export const CouponTemplateTakeTypeEnum = {
export const PromotionProductScopeEnum = {
ALL: {
scope: 1,
name: '通用劵'
name: '通用劵',
},
SPU: {
scope: 2,
name: '商品劵'
name: '商品劵',
},
CATEGORY: {
scope: 3,
name: '品类劵'
}
}
name: '品类劵',
},
};
/**
*
@ -276,13 +276,13 @@ export const PromotionProductScopeEnum = {
export const PromotionConditionTypeEnum = {
PRICE: {
type: 10,
name: '满 N 元'
name: '满 N 元',
},
COUNT: {
type: 20,
name: '满 N 件'
}
}
name: '满 N 件',
},
};
/**
*
@ -290,13 +290,13 @@ export const PromotionConditionTypeEnum = {
export const PromotionDiscountTypeEnum = {
PRICE: {
type: 1,
name: '满减'
name: '满减',
},
PERCENT: {
type: 2,
name: '折扣'
}
}
name: '折扣',
},
};
// ========== MALL - 交易模块 ==========
/**
@ -305,89 +305,89 @@ export const PromotionDiscountTypeEnum = {
export const BrokerageBindModeEnum = {
ANYTIME: {
mode: 1,
name: '首次绑定'
name: '首次绑定',
},
REGISTER: {
mode: 2,
name: '注册绑定'
name: '注册绑定',
},
OVERRIDE: {
mode: 3,
name: '覆盖绑定'
}
}
name: '覆盖绑定',
},
};
/**
*
*/
export const BrokerageEnabledConditionEnum = {
ALL: {
condition: 1,
name: '人人分销'
name: '人人分销',
},
ADMIN: {
condition: 2,
name: '指定分销'
}
}
name: '指定分销',
},
};
/**
*
*/
export const BrokerageRecordBizTypeEnum = {
ORDER: {
type: 1,
name: '获得推广佣金'
name: '获得推广佣金',
},
WITHDRAW: {
type: 2,
name: '提现申请'
}
}
name: '提现申请',
},
};
/**
*
*/
export const BrokerageWithdrawStatusEnum = {
AUDITING: {
status: 0,
name: '审核中'
name: '审核中',
},
AUDIT_SUCCESS: {
status: 10,
name: '审核通过'
name: '审核通过',
},
AUDIT_FAIL: {
status: 20,
name: '审核不通过'
name: '审核不通过',
},
WITHDRAW_SUCCESS: {
status: 11,
name: '提现成功'
name: '提现成功',
},
WITHDRAW_FAIL: {
status: 21,
name: '提现失败'
}
}
name: '提现失败',
},
};
/**
*
*/
export const BrokerageWithdrawTypeEnum = {
WALLET: {
type: 1,
name: '钱包'
name: '钱包',
},
BANK: {
type: 2,
name: '银行卡'
name: '银行卡',
},
WECHAT: {
type: 3,
name: '微信'
name: '微信',
},
ALIPAY: {
type: 4,
name: '支付宝'
}
}
name: '支付宝',
},
};
/**
*
@ -395,38 +395,38 @@ export const BrokerageWithdrawTypeEnum = {
export const DeliveryTypeEnum = {
EXPRESS: {
type: 1,
name: '快递发货'
name: '快递发货',
},
PICK_UP: {
type: 2,
name: '到店自提'
}
}
name: '到店自提',
},
};
/**
* -
*/
export const TradeOrderStatusEnum = {
UNPAID: {
status: 0,
name: '待支付'
name: '待支付',
},
UNDELIVERED: {
status: 10,
name: '待发货'
name: '待发货',
},
DELIVERED: {
status: 20,
name: '已发货'
name: '已发货',
},
COMPLETED: {
status: 30,
name: '已完成'
name: '已完成',
},
CANCELED: {
status: 40,
name: '已取消'
}
}
name: '已取消',
},
};
// ========== ERP - 企业资源计划 ==========
@ -436,31 +436,31 @@ export const ErpBizType = {
PURCHASE_RETURN: 12,
SALE_ORDER: 20,
SALE_OUT: 21,
SALE_RETURN: 22
}
SALE_RETURN: 22,
};
// ========== BPM 模块 ==========
export const BpmModelType = {
BPMN: 10, // BPMN 设计器
SIMPLE: 20 // 简易设计器
}
SIMPLE: 20, // 简易设计器
};
export const BpmModelFormType = {
NORMAL: 10, // 流程表单
CUSTOM: 20 // 业务表单
}
CUSTOM: 20, // 业务表单
};
export const BpmProcessInstanceStatus = {
NOT_START: -1, // 未开始
RUNNING: 1, // 审批中
APPROVE: 2, // 审批通过
REJECT: 3, // 审批不通过
CANCEL: 4 // 已取消
}
CANCEL: 4, // 已取消
};
export const BpmAutoApproveType = {
NONE: 0, // 不自动通过
APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过
}
};

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,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' },
},
];
}

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,45 @@
<script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen';
import { useVbenForm } from '#/adapter/form';
import { watch } from 'vue';
import { useBasicInfoFormSchema } from '../data';
const props = defineProps<{
table: InfraCodegenApi.CodegenTable;
}>();
/** 表单实例 */
const [Form, formApi] = useVbenForm({
//
wrapperClass: 'grid grid-cols-1 md:grid-cols-2 gap-4',
schema: useBasicInfoFormSchema(),
layout: 'horizontal',
showDefaultActions: false,
});
/** 动态更新表单值 */
watch(
() => props.table,
(val: any) => {
if (!val) {
return;
}
formApi.setValues(val);
},
{ immediate: true },
);
/** 暴露出表单校验方法和表单值获取方法 */
defineExpose({
validate: async () => {
const { valid } = await formApi.validate();
return valid;
},
getValues: formApi.getValues,
});
</script>
<template>
<Form />
</template>

View File

@ -0,0 +1,152 @@
<script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen';
import type { SystemDictTypeApi } from '#/api/system/dict/type';
import { Checkbox, Input, Select } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getSimpleDictTypeList } from '#/api/system/dict/type';
import { ref, watch } from 'vue';
import { useCodegenColumnTableColumns } from '../data';
defineOptions({ name: 'InfraCodegenColumInfoForm' });
const props = defineProps<{
columns?: InfraCodegenApi.CodegenColumn[];
}>();
//
const [Grid, extendedApi] = useVbenVxeGrid({
gridOptions: {
columns: useCodegenColumnTableColumns(),
border: true,
showOverflow: true,
height: 'auto',
autoResize: true,
keepSource: true,
rowConfig: {
keyField: 'id',
},
pagerConfig: {
enabled: false,
},
toolbarConfig: {
enabled: false,
},
},
});
//
watch(
() => props.columns,
(columns) => {
if (!columns) {
return;
}
setTimeout(() => {
extendedApi.grid?.loadData(columns);
}, 100);
},
{
immediate: true,
},
);
//
defineExpose({
getData: (): InfraCodegenApi.CodegenColumn[] => extendedApi.grid.getData(),
});
//
const dictTypeOptions = ref<{ label: string; value: string }[]>([]);
const loadDictTypeOptions = async () => {
const dictTypes = await getSimpleDictTypeList();
dictTypeOptions.value = dictTypes.map((dict: SystemDictTypeApi.SystemDictType) => ({
label: dict.name,
value: dict.type,
}));
};
loadDictTypeOptions();
</script>
<template>
<Grid>
<!-- 字段描述 -->
<template #columnComment="{ row }">
<Input v-model:value="row.columnComment" />
</template>
<!-- Java类型 -->
<template #javaType="{ row, column }">
<Select v-model:value="row.javaType" style="width: 100%">
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
{{ option.label }}
</Select.Option>
</Select>
</template>
<!-- Java属性 -->
<template #javaField="{ row }">
<Input v-model:value="row.javaField" />
</template>
<!-- 插入 -->
<template #createOperation="{ row }">
<Checkbox v-model:checked="row.createOperation" />
</template>
<!-- 编辑 -->
<template #updateOperation="{ row }">
<Checkbox v-model:checked="row.updateOperation" />
</template>
<!-- 列表 -->
<template #listOperationResult="{ row }">
<Checkbox v-model:checked="row.listOperationResult" />
</template>
<!-- 查询 -->
<template #listOperation="{ row }">
<Checkbox v-model:checked="row.listOperation" />
</template>
<!-- 查询方式 -->
<template #listOperationCondition="{ row, column }">
<Select v-model:value="row.listOperationCondition" style="width: 100%">
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
{{ option.label }}
</Select.Option>
</Select>
</template>
<!-- 允许空 -->
<template #nullable="{ row }">
<Checkbox v-model:checked="row.nullable" />
</template>
<!-- 显示类型 -->
<template #htmlType="{ row, column }">
<Select v-model:value="row.htmlType" style="width: 100%">
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
{{ option.label }}
</Select.Option>
</Select>
</template>
<!-- 字典类型 -->
<template #dictType="{ row }">
<Select v-model:value="row.dictType" style="width: 100%" allow-clear show-search>
<Select.Option v-for="option in dictTypeOptions" :key="option.value" :value="option.value">
{{ option.label }}
</Select.Option>
</Select>
</template>
<!-- 示例 -->
<template #example="{ row }">
<Input v-model:value="row.example" />
</template>
</Grid>
</template>

View File

@ -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>

View File

@ -0,0 +1,141 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraCodegenApi } from '#/api/infra/codegen';
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { createCodegenList, getSchemaTableList } from '#/api/infra/codegen';
import { getDataSourceConfigList } from '#/api/infra/data-source-config';
import { reactive, ref, unref } from 'vue';
import { $t } from '@vben/locales';
import { useImportTableFormSchema } from '#/views/infra/codegen/data';
/** 定义组件事件 */
const emit = defineEmits<{
(e: 'success'): void;
}>();
const dataSourceConfigList = ref<InfraDataSourceConfigApi.InfraDataSourceConfig[]>([]);
const formData = reactive<InfraCodegenApi.CodegenCreateListReq>({
dataSourceConfigId: undefined,
tableNames: [], //
});
/** 表格实例 */
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useImportTableFormSchema([]),
},
gridOptions: {
columns: [
{ type: 'checkbox', width: 40 },
{ field: 'name', title: '表名称', minWidth: 200 },
{ field: 'comment', title: '表描述', minWidth: 200 },
],
height: '600px',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
if (formValues.dataSourceConfigId === undefined) {
if (unref(dataSourceConfigList).length > 0) {
formValues.dataSourceConfigId = unref(dataSourceConfigList)[0]?.id;
} else {
return [];
}
}
formData.dataSourceConfigId = formValues.dataSourceConfigId;
return await getSchemaTableList({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'name',
},
toolbarConfig: {
enabled: false,
},
checkboxConfig: {
highlight: true,
range: true,
},
pagerConfig: {
enabled: false,
},
} as VxeTableGridOptions<InfraCodegenApi.DatabaseTable>,
gridEvents: {
checkboxChange: ({ records }: { records: InfraCodegenApi.DatabaseTable[] }) => {
formData.tableNames = records.map((item) => item.name);
},
},
});
/** 模态框实例 */
const [Modal, modalApi] = useVbenModal({
title: '导入表',
class: 'w-2/3',
async onConfirm() {
modalApi.lock();
// 1.
if (formData?.dataSourceConfigId === undefined) {
message.error('请选择数据源');
return;
}
// 2.
if (formData.tableNames.length === 0) {
message.error('请选择需要导入的表');
return;
}
// 3.
const hideLoading = message.loading({
content: '导入中...',
duration: 0,
key: 'import_loading',
});
try {
await createCodegenList(formData);
//
await modalApi.close();
emit('success');
message.success({
content: $t('ui.actionMessage.operationSuccess'),
key: 'action_process_msg',
});
} finally {
hideLoading();
modalApi.lock(false);
}
},
});
//
async function initDataSourceConfig() {
try {
dataSourceConfigList.value = await getDataSourceConfigList();
gridApi.setState({
formOptions: {
schema: useImportTableFormSchema(dataSourceConfigList.value),
},
});
} catch (error) {
console.error('获取数据源配置失败', error);
}
}
//
initDataSourceConfig();
</script>
<template>
<Modal>
<Grid />
</Modal>
</template>

View File

@ -0,0 +1,305 @@
<script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen';
import { useVbenModal } from '@vben/common-ui';
import { Copy } from '@vben/icons';
import { Button, message, Tree } from 'ant-design-vue';
import { previewCodegen } from '#/api/infra/codegen';
import { computed, h, 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';
/** 注册代码高亮语言 */
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">
<code v-html="highlightedCode" class="code-highlight"></code></pre>
</div>
</div>
</div>
</Modal>
</template>
<style scoped>
/* 代码高亮样式 - 支持暗黑模式 */
: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;
}
</style>

View File

@ -33,7 +33,7 @@ function isBoolean(value: unknown): value is boolean {
* @param {T} value
* @returns {boolean} truefalse
*/
function isEmpty<T = unknown>(value?: T): value is T {
function isEmpty<T = unknown>(value?: T): boolean {
if (value === null || value === undefined) {
return true;
}
@ -75,9 +75,7 @@ function isHttpUrl(url?: string): boolean {
* @returns {boolean} windowtruefalse
*/
function isWindow(value: any): value is Window {
return (
typeof window !== 'undefined' && value !== null && value === value.window
);
return typeof window !== 'undefined' && value !== null && value === value.window;
}
/**
@ -137,9 +135,7 @@ function isNumber(value: any): value is number {
* // Returns undefined because all values are either null or undefined.
* getFirstNonNullOrUndefined(undefined, null); // undefined
*/
function getFirstNonNullOrUndefined<T>(
...values: (null | T | undefined)[]
): T | undefined {
function getFirstNonNullOrUndefined<T>(...values: (null | T | undefined)[]): T | undefined {
for (const value of values) {
if (value !== undefined && value !== null) {
return value;

View File

@ -191,3 +191,4 @@ catalog:
watermark-js-plus: ^1.5.8
zod: ^3.24.2
zod-defaults: ^0.1.3
highlight.js: ^11.11.1