81 Commits

Author SHA1 Message Date
insistence
00a7af2d5b !30 RuoYi-Vue3-FastAPI 1.6.2
Merge pull request !30 from insistence/develop
2025-03-19 07:59:40 +00:00
insistence
e34a493b3e docs: 更新README文档 2025-03-19 15:55:10 +08:00
insistence
a9df9ea56a chore: 升级版本至1.6.2 2025-03-19 15:54:12 +08:00
insistence
d89c0df425 perf: 优化代码生成vue模板 #23 2025-03-19 15:46:28 +08:00
insistence
d0730e7993 fix: 修复用户导出缺失部门名称的问题 2025-03-18 16:55:16 +08:00
insistence
5fe376f978 perf: pagination更换成flex布局 2025-03-18 08:28:46 +08:00
insistence
dd5b1c55c9 feat: 文件上传组件新增类型 2025-03-18 08:12:13 +08:00
insistence
7645c6d9fd feat: 文件上传组件新增disabled属性 2025-03-18 08:11:18 +08:00
insistence
61073970fa fix: 修复代码生成模板时间查询问题 #28 2025-03-17 16:32:46 +08:00
insistence
9ae2ac02eb perf: 优化代码生成新增和编辑字段显示和渲染 2025-03-17 09:51:05 +08:00
insistence
6622c329fc fix: 修复修改字典类型时字典数据更新时间异常的问题 2025-03-17 08:27:39 +08:00
insistence
b66d545985 fix: 修复修改字典类型时获取dict_code异常的问题 2025-03-17 08:25:39 +08:00
insistence
1789cb5a9b fix: 修复定时任务状态暂停时执行单次任务会触发cron表达式的问题 #31 2025-03-15 21:59:28 +08:00
insistence
ee376c477d fix: 修复日志管理时间查询报错 #27 2025-03-09 18:43:57 +08:00
jiangbaihe
2b6f0905a9 chore: 调整.gitignore策略 2025-03-04 14:58:04 +08:00
insistence
c953754bed chore: pg依赖文件新增sqlglot依赖 2025-03-03 17:13:37 +08:00
insistence
cea4a10baa !29 RuoYi-Vue3-FastAPI v1.6.1
Merge pull request !29 from insistence/develop
2025-03-03 08:50:47 +00:00
insistence
b708d86eff docs: 更新README文档 2025-03-03 16:44:31 +08:00
insistence
28aab8c7d4 chore: 升级版本至1.6.1 2025-03-03 16:43:47 +08:00
insistence
9412e3f344 fix: 修复代码生成主子表vo模板可能缺失NotBlank的问题 2025-02-28 10:46:45 +08:00
insistence
b4d5619b1b fix: 引入泛型修复as_query和as_form装饰模型文档丢失的问题 2025-02-28 10:36:29 +08:00
insistence
8ce598ad54 fix: 修复代码生成字段唯一性校验dao层模板判断异常的问题 2025-02-26 09:00:35 +08:00
insistence
ca641055e0 fix: 引入sqlglot修复sql语句解析异常的问题 2025-02-21 15:40:47 +08:00
insistence
a5a4099374 !28 RuoYi-Vue3-FastAPI v1.6.0
Merge pull request !28 from insistence/develop
2025-02-19 04:29:42 +00:00
insistence
729dc23a16 docs: 更新README文档 2025-02-19 12:25:48 +08:00
insistence
3e898083ac chore: 升级版本至1.6.0 2025-02-19 12:22:40 +08:00
insistence
492dd7b052 perf: 优化TopNav内链菜单点击没有高亮 2025-02-19 12:20:54 +08:00
insistence
3e5890b8c2 perf: 用户管理过滤掉已禁用部门 2025-02-19 12:18:35 +08:00
insistence
e21bde54be chore: 更新后端依赖至最新版本 2025-02-19 11:51:04 +08:00
insistence
fdf8e600a0 perf: 代码生成默认不允许生成文件覆盖到本地 2025-02-19 10:59:44 +08:00
insistence
1b5b89e282 perf: ResponseUtil补充完整参数 2025-02-19 10:54:56 +08:00
insistence
4cc9037f25 fix: 修复个人中心特殊字符密码修改失败问题 2025-02-19 10:32:02 +08:00
insistence
e8b3a6c49b fix: 修复vue2模板主子表异常的问题 2025-02-19 08:43:47 +08:00
insistence
6bae2d2d0c fix: 修复vue2模版异常问题 2025-02-19 00:16:23 +08:00
insistence
f42a0b530b fix: 修复代码生成树表功能异常的问题 2025-02-18 17:59:57 +08:00
insistence
0fdf45c73f fix: 修复代码生成主子表功能异常的问题 2025-02-18 17:46:37 +08:00
insistence
fd07ad088c feat: 代码生成新增字段唯一性配置 2025-02-18 16:10:57 +08:00
insistence
2a486dc01e feat: 代码生成功能适配pg数据库 2025-02-18 11:36:33 +08:00
insistence
15686b44ad feat: 代码生成初步适配pg数据库 2025-02-17 23:06:22 +08:00
insistence
2b474ddb35 perf: 优化代码生成功能 2025-02-17 17:43:29 +08:00
insistence
25e2a1e931 feat: postgresql数据库sql语句新增视图 2025-02-16 23:30:28 +08:00
insistence
59f6e0091f feat: 新增代码生成vue2前端模板 2025-02-16 23:29:44 +08:00
insistence
fe41a207fa perf: 优化代码生成功能 2025-02-16 23:29:15 +08:00
insistence
1d36c0c56e feat: 新增代码生成功能 2025-02-14 17:42:36 +08:00
insistence
7cca5c88f3 perf: 优化日志装饰器获取核心参数的方式 2025-01-26 16:34:27 +08:00
py1ren
00011f8419 !27 feat: 新增trace中间件强化日志链路追踪和响应头
* refactor: trace_log重命名为trace_middleware
* refactor: 日志处理器重构为类式写法
* perf: 移除无用文件
* perf: 优化trace中间件部分写法
* style: 格式化代码
* Merge branch 'master' into develop
* feature: 1.日志添加traceId链路追踪 2.response-header默认添加request-id与traceId对应
2025-01-24 01:34:33 +00:00
insistence
1cfd85f9de fix: 修复执行单次任务时会覆盖已启用任务的问题 #IBEKD2 2025-01-08 15:32:56 +08:00
insistence
3e05f1b8b3 fix: 修复定时任务目标字符串规则校验不全的问题 2024-12-27 15:02:53 +08:00
insistence
a67c629fa6 perf: 优化日志中操作方法显示 2024-12-26 08:48:47 +08:00
insistence
691076bb14 fix: 修复删除当前登录用户拦截失效的问题 2024-12-26 08:48:22 +08:00
insistence
1d408e6a09 perf: 优化导出方法 2024-12-20 10:26:00 +08:00
insistence
3a3bbcc32a feat: 新增表单构建功能 2024-12-11 10:08:07 +08:00
insistence
a2a0e4ff1c perf: 优化代码 2024-12-11 10:01:45 +08:00
insistence
1453402ac3 feat: 支持开启暗黑模式 2024-12-11 09:59:50 +08:00
insistence
7ce127a9e6 feat: 白名单支持对通配符路径匹配 2024-12-11 09:22:48 +08:00
insistence
c9ecfd213c perf: 优化代码 2024-12-11 09:16:41 +08:00
insistence
1d08ba9905 feat: 菜单面包屑导航支持多层级显示 2024-12-11 09:04:55 +08:00
insistence
6ffcf4dca7 feat: 用户管理支持分栏拖动 2024-12-11 08:47:14 +08:00
insistence
8f851f5e51 fix: 修复默认关闭Tags-Views时,内链页面打不开 2024-12-10 15:53:06 +08:00
insistence
29b672cdab perf: 参数键值更换为多行文本 2024-12-10 15:16:40 +08:00
insistence
27fe0e04f6 feat: 用户头像新增支持http(s)链接 2024-12-10 15:02:16 +08:00
insistence
591dbe06a2 !22 RuoYi-Vue3-FastAPI v1.5.1
Merge pull request !22 from insistence/develop
2024-11-12 07:35:06 +00:00
insistence
43b7931591 docs: 更新README文档 2024-11-12 15:31:01 +08:00
insistence
51257df07c chore: 升级版本至1.5.1 2024-11-12 15:30:54 +08:00
insistence
c282f362f4 perf: 移除已弃用的log_decorator装饰器 2024-11-12 15:10:17 +08:00
insistence
425d6e00eb perf: 校检文件名是否包含特殊字符 2024-11-08 17:10:54 +08:00
insistence
564d470240 perf: 优化字典数组条件判断 2024-11-08 17:06:21 +08:00
insistence
a1937448d6 refactor: 定时任务改用AsyncIOScheduler和AsyncIOExecutor 2024-11-07 10:01:49 +08:00
insistence
1ddb1bd5b5 feat: 定时任务新增支持调用异步函数 2024-11-06 22:28:09 +08:00
insistence
4f4ee34e8d style: 修复书写错误 2024-10-31 15:38:58 +08:00
insistence
8202e8c9db !21 RuoYi-Vue3-FastAPI v1.5.0
Merge pull request !21 from insistence/develop
2024-10-22 12:05:05 +00:00
insistence
b797ffb5af docs: 更新README文档 2024-10-22 20:02:09 +08:00
insistence
c2795c49e4 chore: 升级版本至1.5.0 2024-10-22 20:01:59 +08:00
insistence
545cb6606c revert: 因fastapi查询参数模型底层存在bug,回滚查询参数模型声明方式为as_query 2024-10-22 19:41:28 +08:00
insistence
d0211cfd11 perf: 优化上传图片带域名时不增加前缀 2024-10-22 19:36:27 +08:00
insistence
a3c9ffdad5 fix: 修复登录日志导出文件名称错误的问题 2024-10-22 19:34:13 +08:00
insistence
5ce1ffd557 perf: 优化参数设置页面 2024-10-22 17:04:34 +08:00
insistence
850d8d7890 fix: 修复DictTag组件控制台抛异常的问题 #IAYSVZ 2024-10-22 17:04:08 +08:00
insistence
53cc6eb8ce perf: 优化列表查询排序 2024-10-22 16:28:08 +08:00
insistence
c7af974d3f feat: 新增对PostgreSQL数据库的支持 2024-10-22 16:27:47 +08:00
insistence
bb143ac15a perf: 优化CamelCaseUtil和SnakeCaseUtil以兼容更多转换场景 2024-10-21 11:11:05 +08:00
150 changed files with 13617 additions and 2803 deletions

7
.gitignore vendored
View File

@@ -136,3 +136,10 @@ dmypy.json
# Cython debug symbols
cython_debug/
# PyCharm
.idea/
# VSCode
.vscode/

View File

@@ -1,12 +1,12 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI v1.4.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI v1.6.2</h1>
<h4 align="center">基于RuoYi-Vue3+FastAPI前后端分离的快速开发框架</h4>
<p align="center">
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/stargazers"><img src="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/badge/star.svg?theme=dark"></a>
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/github/stars/insistence/RuoYi-Vue3-FastAPI?style=social"></a>
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/badge/RuoYiVue3FastAPI-v1.4.0-brightgreen.svg"></a>
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/badge/RuoYiVue3FastAPI-v1.6.2-brightgreen.svg"></a>
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
<img src="https://img.shields.io/badge/python-≥3.9-blue">
<img src="https://img.shields.io/badge/MySQL-≥5.7-blue">
@@ -17,7 +17,7 @@
RuoYi-Vue3-FastAPI是一套全部开源的快速开发平台毫无保留给个人及企业免费使用。
* 前端采用Vue3、Element Plus基于<u>[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3)</u>前端项目修改。
* 后端采用FastAPI、sqlalchemy、MySQL、Redis、OAuth2 & Jwt。
* 后端采用FastAPI、sqlalchemy、MySQLPostgreSQL、Redis、OAuth2 & Jwt。
* 权限认证使用OAuth2 & Jwt支持多终端认证系统。
* 支持加载动态权限菜单,多方式轻松权限控制。
* Vue2版本
@@ -44,7 +44,9 @@ RuoYi-Vue3-FastAPI是一套全部开源的快速开发平台毫无保留给
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
13. 服务监控监视当前系统CPU、内存、磁盘、堆栈等相关信息。
14. 缓存监控:对系统的缓存信息查询,命令统计等。
15. 系统接口根据业务代码自动生成相关的api接口文档
15. 在线构建器拖动表单元素生成相应的HTML代码
16. 系统接口根据业务代码自动生成相关的api接口文档。
17. 代码生成配置数据库表信息一键生成前后端代码python、sql、vue、js支持下载。
## 演示图
@@ -83,7 +85,11 @@ RuoYi-Vue3-FastAPI是一套全部开源的快速开发平台毫无保留给
</tr>
<tr>
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/cacheList.png"></td>
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/form.png"></td>
</tr>
<tr>
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/api.png"></td>
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/gen.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/profile.png"/></td>
@@ -127,15 +133,17 @@ npm run dev 或 yarn dev
# 进入后端目录
cd ruoyi-fastapi-backend
# 安装项目依赖环境
# 如果使用的是MySQL数据库请执行以下命令安装项目依赖环境
pip3 install -r requirements.txt
# 如果使用的是PostgreSQL数据库请执行以下命令安装项目依赖环境
pip3 install -r requirements-pg.txt
# 配置环境
在.env.dev文件中配置开发环境的数据库和redis
# 运行sql文件
1.新建数据库ruoyi-fastapi(默认,可修改)
2.使用命令或数据库连接工具运行sql文件夹下的ruoyi-fastapi.sql
2.如果使用的是MySQL数据库使用命令或数据库连接工具运行sql文件夹下的ruoyi-fastapi.sql如果使用的是PostgreSQL数据库使用命令或数据库连接工具运行sql文件夹下的ruoyi-fastapi-pg.sql
# 运行后端
python3 app.py --env=dev

View File

@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
# 应用端口
APP_PORT = 9099
# 应用版本
APP_VERSION= '1.4.0'
APP_VERSION= '1.6.2'
# 应用是否开启热重载
APP_RELOAD = true
# 应用是否开启IP归属区域查询
@@ -30,6 +30,8 @@ JWT_REDIS_EXPIRE_MINUTES = 30
# -------- 数据库配置 --------
# 数据库类型,可选的有'mysql'、'postgresql',默认为'mysql'
DB_TYPE = 'mysql'
# 数据库主机
DB_HOST = '127.0.0.1'
# 数据库端口

View File

@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
# 应用端口
APP_PORT = 9099
# 应用版本
APP_VERSION= '1.4.0'
APP_VERSION= '1.6.2'
# 应用是否开启热重载
APP_RELOAD = false
# 应用是否开启IP归属区域查询
@@ -30,6 +30,8 @@ JWT_REDIS_EXPIRE_MINUTES = 30
# -------- 数据库配置 --------
# 数据库类型,可选的有'mysql'、'postgresql',默认为'mysql'
DB_TYPE = 'mysql'
# 数据库主机
DB_HOST = '127.0.0.1'
# 数据库端口

View File

@@ -1,3 +1,6 @@
from config.env import DataBaseConfig
class CommonConstant:
"""
常用常量
@@ -150,3 +153,331 @@ class MenuConstant:
LAYOUT = 'Layout'
PARENT_VIEW = 'ParentView'
INNER_LINK = 'InnerLink'
class GenConstant:
"""
代码生成常量
TPL_CRUD: 单表(增删改查
TPL_TREE: 树表(增删改查)
TPL_SUB: 主子表(增删改查)
TREE_CODE: 树编码字段
TREE_PARENT_CODE: 树父编码字段
TREE_NAME: 树名称字段
PARENT_MENU_ID: 上级菜单ID字段
PARENT_MENU_NAME: 上级菜单名称字段
COLUMNTYPE_STR: 数据库字符串类型
COLUMNTYPE_TEXT: 数据库文本类型
COLUMNTYPE_TIME: 数据库时间类型
COLUMNTYPE_GEOMETRY: 数据库字空间类型
COLUMNTYPE_NUMBER: 数据库数字类型
COLUMNNAME_NOT_EDIT: 页面不需要编辑字段
COLUMNNAME_NOT_LIST: 页面不需要显示的列表字段
COLUMNNAME_NOT_QUERY: 页面不需要查询字段
BASE_ENTITY: Entity基类字段
TREE_ENTITY: Tree基类字段
HTML_INPUT: 文本框
HTML_TEXTAREA: 文本域
HTML_SELECT: 下拉框
HTML_RADIO: 单选框
HTML_CHECKBOX: 复选框
HTML_DATETIME: 日期控件
HTML_IMAGE_UPLOAD: 图片上传控件
HTML_FILE_UPLOAD: 文件上传控件
HTML_EDITOR: 富文本控件
TYPE_DECIMAL: 高精度计算类型
TYPE_DATE: 时间类型
QUERY_LIKE: 模糊查询
QUERY_EQ: 相等查询
REQUIRE: 需要
DB_TO_SQLALCHEMY_TYPE_MAPPING: 数据库类型与sqlalchemy类型映射
DB_TO_PYTHON_TYPE_MAPPING: 数据库类型与python类型映射
"""
TPL_CRUD = 'crud'
TPL_TREE = 'tree'
TPL_SUB = 'sub'
TREE_CODE = 'treeCode'
TREE_PARENT_CODE = 'treeParentCode'
TREE_NAME = 'treeName'
PARENT_MENU_ID = 'parentMenuId'
PARENT_MENU_NAME = 'parentMenuName'
COLUMNTYPE_STR = (
['character varying', 'varchar', 'character', 'char']
if DataBaseConfig.db_type == 'postgresql'
else ['char', 'varchar', 'nvarchar', 'varchar2']
)
COLUMNTYPE_TEXT = (
['text', 'citext'] if DataBaseConfig.db_type == 'postgresql' else ['tinytext', 'text', 'mediumtext', 'longtext']
)
COLUMNTYPE_TIME = (
[
'date',
'time',
'time with time zone',
'time without time zone',
'timestamp',
'timestamp with time zone',
'timestamp without time zone',
'interval',
]
if DataBaseConfig.db_type == 'postgresql'
else ['datetime', 'time', 'date', 'timestamp']
)
COLUMNTYPE_GEOMETRY = (
['point', 'line', 'lseg', 'box', 'path', 'polygon', 'circle']
if DataBaseConfig.db_type == 'postgresql'
else [
'geometry',
'point',
'linestring',
'polygon',
'multipoint',
'multilinestring',
'multipolygon',
'geometrycollection',
]
)
COLUMNTYPE_NUMBER = [
'tinyint',
'smallint',
'mediumint',
'int',
'number',
'integer',
'bit',
'bigint',
'float',
'double',
'decimal',
]
COLUMNNAME_NOT_ADD_SHOW = ['create_by', 'create_time']
COLUMNNAME_NOT_EDIT_SHOW = ['update_by', 'update_time']
COLUMNNAME_NOT_EDIT = ['id', 'create_by', 'create_time', 'del_flag']
COLUMNNAME_NOT_LIST = ['id', 'create_by', 'create_time', 'del_flag', 'update_by', 'update_time']
COLUMNNAME_NOT_QUERY = ['id', 'create_by', 'create_time', 'del_flag', 'update_by', 'update_time', 'remark']
BASE_ENTITY = ['createBy', 'createTime', 'updateBy', 'updateTime', 'remark']
TREE_ENTITY = ['parentName', 'parentId', 'orderNum', 'ancestors', 'children']
HTML_INPUT = 'input'
HTML_TEXTAREA = 'textarea'
HTML_SELECT = 'select'
HTML_RADIO = 'radio'
HTML_CHECKBOX = 'checkbox'
HTML_DATETIME = 'datetime'
HTML_IMAGE_UPLOAD = 'imageUpload'
HTML_FILE_UPLOAD = 'fileUpload'
HTML_EDITOR = 'editor'
TYPE_DECIMAL = 'Decimal'
TYPE_DATE = ['date', 'time', 'datetime']
QUERY_LIKE = 'LIKE'
QUERY_EQ = 'EQ'
REQUIRE = '1'
DB_TO_SQLALCHEMY_TYPE_MAPPING = (
{
'boolean': 'Boolean',
'smallint': 'SmallInteger',
'integer': 'Integer',
'bigint': 'BigInteger',
'real': 'Float',
'double precision': 'Float',
'numeric': 'Numeric',
'character varying': 'String',
'character': 'String',
'text': 'Text',
'bytea': 'LargeBinary',
'date': 'Date',
'time': 'Time',
'time with time zone': 'Time',
'time without time zone': 'Time',
'timestamp': 'DateTime',
'timestamp with time zone': 'DateTime',
'timestamp without time zone': 'DateTime',
'interval': 'Interval',
'json': 'JSON',
'jsonb': 'JSONB',
'uuid': 'Uuid',
'inet': 'INET',
'cidr': 'CIDR',
'macaddr': 'MACADDR',
'point': 'Geometry',
'line': 'Geometry',
'lseg': 'Geometry',
'box': 'Geometry',
'path': 'Geometry',
'polygon': 'Geometry',
'circle': 'Geometry',
'bit': 'Bit',
'bit varying': 'Bit',
'tsvector': 'TSVECTOR',
'tsquery': 'TSQUERY',
'xml': 'String',
'array': 'ARRAY',
'composite': 'JSON',
'enum': 'Enum',
'range': 'Range',
'money': 'Numeric',
'pg_lsn': 'BigInteger',
'txid_snapshot': 'String',
'oid': 'BigInteger',
'regproc': 'String',
'regclass': 'String',
'regtype': 'String',
'regrole': 'String',
'regnamespace': 'String',
'int2vector': 'ARRAY',
'oidvector': 'ARRAY',
'pg_node_tree': 'Text',
}
if DataBaseConfig.db_type == 'postgresql'
else {
# 数值类型
'TINYINT': 'SmallInteger',
'SMALLINT': 'SmallInteger',
'MEDIUMINT': 'Integer',
'INT': 'Integer',
'INTEGER': 'Integer',
'BIGINT': 'BigInteger',
'FLOAT': 'Float',
'DOUBLE': 'Float',
'DECIMAL': 'DECIMAL',
'BIT': 'Integer',
# 日期和时间类型
'DATE': 'Date',
'TIME': 'Time',
'DATETIME': 'DateTime',
'TIMESTAMP': 'TIMESTAMP',
'YEAR': 'Integer',
# 字符串类型
'CHAR': 'CHAR',
'VARCHAR': 'String',
'TINYTEXT': 'Text',
'TEXT': 'Text',
'MEDIUMTEXT': 'Text',
'LONGTEXT': 'Text',
'BINARY': 'BINARY',
'VARBINARY': 'VARBINARY',
'TINYBLOB': 'LargeBinary',
'BLOB': 'LargeBinary',
'MEDIUMBLOB': 'LargeBinary',
'LONGBLOB': 'LargeBinary',
# 枚举和集合类型
'ENUM': 'Enum',
'SET': 'String',
# JSON 类型
'JSON': 'JSON',
# 空间数据类型(需要扩展支持,如 GeoAlchemy2
'GEOMETRY': 'Geometry', # 需要安装 geoalchemy2
'POINT': 'Geometry',
'LINESTRING': 'Geometry',
'POLYGON': 'Geometry',
'MULTIPOINT': 'Geometry',
'MULTILINESTRING': 'Geometry',
'MULTIPOLYGON': 'Geometry',
'GEOMETRYCOLLECTION': 'Geometry',
}
)
DB_TO_PYTHON_TYPE_MAPPING = (
{
'boolean': 'bool',
'smallint': 'int',
'integer': 'int',
'bigint': 'int',
'real': 'float',
'double precision': 'float',
'numeric': 'Decimal',
'character varying': 'str',
'character': 'str',
'text': 'str',
'bytea': 'bytes',
'date': 'date',
'time': 'time',
'time with time zone': 'time',
'time without time zone': 'time',
'timestamp': 'datetime',
'timestamp with time zone': 'datetime',
'timestamp without time zone': 'datetime',
'interval': 'timedelta',
'json': 'dict',
'jsonb': 'dict',
'uuid': 'str',
'inet': 'str',
'cidr': 'str',
'macaddr': 'str',
'point': 'list',
'line': 'list',
'lseg': 'list',
'box': 'list',
'path': 'list',
'polygon': 'list',
'circle': 'list',
'bit': 'int',
'bit varying': 'int',
'tsvector': 'str',
'tsquery': 'str',
'xml': 'str',
'array': 'list',
'composite': 'dict',
'enum': 'str',
'range': 'list',
'money': 'Decimal',
'pg_lsn': 'int',
'txid_snapshot': 'str',
'oid': 'int',
'regproc': 'str',
'regclass': 'str',
'regtype': 'str',
'regrole': 'str',
'regnamespace': 'str',
'int2vector': 'list',
'oidvector': 'list',
'pg_node_tree': 'str',
}
if DataBaseConfig.db_type == 'postgresql'
else {
# 数值类型
'TINYINT': 'int',
'SMALLINT': 'int',
'MEDIUMINT': 'int',
'INT': 'int',
'INTEGER': 'int',
'BIGINT': 'int',
'FLOAT': 'float',
'DOUBLE': 'float',
'DECIMAL': 'Decimal',
'BIT': 'int',
# 日期和时间类型
'DATE': 'date',
'TIME': 'time',
'DATETIME': 'datetime',
'TIMESTAMP': 'datetime',
'YEAR': 'int',
# 字符串类型
'CHAR': 'str',
'VARCHAR': 'str',
'TINYTEXT': 'str',
'TEXT': 'str',
'MEDIUMTEXT': 'str',
'LONGTEXT': 'str',
'BINARY': 'bytes',
'VARBINARY': 'bytes',
'TINYBLOB': 'bytes',
'BLOB': 'bytes',
'MEDIUMBLOB': 'bytes',
'LONGBLOB': 'bytes',
# 枚举和集合类型
'ENUM': 'str',
'SET': 'str',
# JSON 类型
'JSON': 'dict',
# 空间数据类型(通常需要特殊处理)
'GEOMETRY': 'bytes',
'POINT': 'bytes',
'LINESTRING': 'bytes',
'POLYGON': 'bytes',
'MULTIPOINT': 'bytes',
'MULTILINESTRING': 'bytes',
'MULTIPOLYGON': 'bytes',
'GEOMETRYCOLLECTION': 'bytes',
}
)

View File

@@ -9,6 +9,11 @@ ASYNC_SQLALCHEMY_DATABASE_URL = (
f'mysql+asyncmy://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@'
f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}'
)
if DataBaseConfig.db_type == 'postgresql':
ASYNC_SQLALCHEMY_DATABASE_URL = (
f'postgresql+asyncpg://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@'
f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}'
)
async_engine = create_async_engine(
ASYNC_SQLALCHEMY_DATABASE_URL,

View File

@@ -3,7 +3,9 @@ import os
import sys
from dotenv import load_dotenv
from functools import lru_cache
from pydantic import computed_field
from pydantic_settings import BaseSettings
from typing import Literal
class AppSettings(BaseSettings):
@@ -38,6 +40,7 @@ class DataBaseSettings(BaseSettings):
数据库配置
"""
db_type: Literal['mysql', 'postgresql'] = 'mysql'
db_host: str = '127.0.0.1'
db_port: int = 3306
db_username: str = 'root'
@@ -49,6 +52,13 @@ class DataBaseSettings(BaseSettings):
db_pool_recycle: int = 3600
db_pool_timeout: int = 30
@computed_field
@property
def sqlglot_parse_dialect(self) -> str:
if self.db_type == 'postgresql':
return 'postgres'
return self.db_type
class RedisSettings(BaseSettings):
"""
@@ -62,6 +72,24 @@ class RedisSettings(BaseSettings):
redis_database: int = 2
class GenSettings:
"""
代码生成配置
"""
author = 'insistence'
package_name = 'module_admin.system'
auto_remove_pre = False
table_prefix = 'sys_'
allow_overwrite = False
GEN_PATH = 'vf_admin/gen_path'
def __init__(self):
if not os.path.exists(self.GEN_PATH):
os.makedirs(self.GEN_PATH)
class UploadSettings:
"""
上传配置
@@ -157,6 +185,14 @@ class GetConfig:
# 实例化Redis配置模型
return RedisSettings()
@lru_cache()
def get_gen_config(self):
"""
获取代码生成配置
"""
# 实例化代码生成配置
return GenSettings()
@lru_cache()
def get_upload_config(self):
"""
@@ -202,5 +238,7 @@ JwtConfig = get_config.get_jwt_config()
DataBaseConfig = get_config.get_database_config()
# Redis配置
RedisConfig = get_config.get_redis_config()
# 代码生成配置
GenConfig = get_config.get_gen_config()
# 上传配置
UploadConfig = get_config.get_upload_config()

View File

@@ -1,11 +1,15 @@
import json
from apscheduler.events import EVENT_ALL
from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.executors.asyncio import AsyncIOExecutor
from apscheduler.executors.pool import ProcessPoolExecutor
from apscheduler.jobstores.memory import MemoryJobStore
from apscheduler.jobstores.redis import RedisJobStore
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.combining import OrTrigger
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.date import DateTrigger
from asyncio import iscoroutinefunction
from datetime import datetime, timedelta
from sqlalchemy.engine import create_engine
from sqlalchemy.orm import sessionmaker
@@ -82,6 +86,11 @@ SQLALCHEMY_DATABASE_URL = (
f'mysql+pymysql://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@'
f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}'
)
if DataBaseConfig.db_type == 'postgresql':
SQLALCHEMY_DATABASE_URL = (
f'postgresql+psycopg2://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@'
f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}'
)
engine = create_engine(
SQLALCHEMY_DATABASE_URL,
echo=DataBaseConfig.db_echo,
@@ -104,9 +113,9 @@ job_stores = {
)
),
}
executors = {'default': ThreadPoolExecutor(20), 'processpool': ProcessPoolExecutor(5)}
executors = {'default': AsyncIOExecutor(), 'processpool': ProcessPoolExecutor(5)}
job_defaults = {'coalesce': False, 'max_instance': 1}
scheduler = BackgroundScheduler()
scheduler = AsyncIOScheduler()
scheduler.configure(jobstores=job_stores, executors=executors, job_defaults=job_defaults)
@@ -127,9 +136,7 @@ class SchedulerUtil:
async with AsyncSessionLocal() as session:
job_list = await JobDao.get_job_list_for_scheduler(session)
for item in job_list:
query_job = cls.get_scheduler_job(job_id=str(item.job_id))
if query_job:
cls.remove_scheduler_job(job_id=str(item.job_id))
cls.remove_scheduler_job(job_id=str(item.job_id))
cls.add_scheduler_job(item)
scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL)
logger.info('系统初始定时任务加载成功')
@@ -164,6 +171,10 @@ class SchedulerUtil:
:param job_info: 任务对象信息
:return:
"""
job_func = eval(job_info.invoke_target)
job_executor = job_info.job_executor
if iscoroutinefunction(job_func):
job_executor = 'default'
scheduler.add_job(
func=eval(job_info.invoke_target),
trigger=MyCronTrigger.from_crontab(job_info.cron_expression),
@@ -175,7 +186,7 @@ class SchedulerUtil:
coalesce=True if job_info.misfire_policy == '2' else False,
max_instances=3 if job_info.concurrent == '0' else 1,
jobstore=job_info.job_group,
executor=job_info.job_executor,
executor=job_executor,
)
@classmethod
@@ -186,10 +197,16 @@ class SchedulerUtil:
:param job_info: 任务对象信息
:return:
"""
job_func = eval(job_info.invoke_target)
job_executor = job_info.job_executor
if iscoroutinefunction(job_func):
job_executor = 'default'
job_trigger = DateTrigger()
if job_info.status == '0':
job_trigger = OrTrigger(triggers=[DateTrigger(), MyCronTrigger.from_crontab(job_info.cron_expression)])
scheduler.add_job(
func=eval(job_info.invoke_target),
trigger='date',
run_date=datetime.now() + timedelta(seconds=1),
trigger=job_trigger,
args=job_info.job_args.split(',') if job_info.job_args else None,
kwargs=json.loads(job_info.job_kwargs) if job_info.job_kwargs else None,
id=str(job_info.job_id),
@@ -198,7 +215,7 @@ class SchedulerUtil:
coalesce=True if job_info.misfire_policy == '2' else False,
max_instances=3 if job_info.concurrent == '0' else 1,
jobstore=job_info.job_group,
executor=job_info.job_executor,
executor=job_executor,
)
@classmethod
@@ -209,7 +226,9 @@ class SchedulerUtil:
:param job_id: 任务id
:return:
"""
scheduler.remove_job(job_id=str(job_id))
query_job = cls.get_scheduler_job(job_id=job_id)
if query_job:
scheduler.remove_job(job_id=str(job_id))
@classmethod
def scheduler_event_listener(cls, event):

View File

@@ -1,6 +1,7 @@
from fastapi import FastAPI
from middlewares.cors_middleware import add_cors_middleware
from middlewares.gzip_middleware import add_gzip_middleware
from middlewares.trace_middleware import add_trace_middleware
def handle_middleware(app: FastAPI):
@@ -11,3 +12,5 @@ def handle_middleware(app: FastAPI):
add_cors_middleware(app)
# 加载gzip压缩中间件
add_gzip_middleware(app)
# 加载trace中间件
add_trace_middleware(app)

View File

@@ -0,0 +1,17 @@
from fastapi import FastAPI
from .ctx import TraceCtx
from .middle import TraceASGIMiddleware
__all__ = ('TraceASGIMiddleware', 'TraceCtx')
__version__ = '0.1.0'
def add_trace_middleware(app: FastAPI):
"""
添加trace中间件
:param app: FastAPI对象
:return:
"""
app.add_middleware(TraceASGIMiddleware)

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
"""
@author: peng
@file: ctx.py
@time: 2025/1/17 16:57
"""
import contextvars
from uuid import uuid4
CTX_REQUEST_ID: contextvars.ContextVar[str] = contextvars.ContextVar('request-id', default='')
class TraceCtx:
@staticmethod
def set_id():
_id = uuid4().hex
CTX_REQUEST_ID.set(_id)
return _id
@staticmethod
def get_id():
return CTX_REQUEST_ID.get()

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
"""
@author: peng
@file: middle.py
@time: 2025/1/17 16:57
"""
from functools import wraps
from starlette.types import ASGIApp, Message, Receive, Scope, Send
from .span import get_current_span, Span
class TraceASGIMiddleware:
"""
fastapi-example:
app = FastAPI()
app.add_middleware(TraceASGIMiddleware)
"""
def __init__(self, app: ASGIApp) -> None:
self.app = app
@staticmethod
async def my_receive(receive: Receive, span: Span):
await span.request_before()
@wraps(receive)
async def my_receive():
message = await receive()
await span.request_after(message)
return message
return my_receive
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope['type'] != 'http':
await self.app(scope, receive, send)
return
async with get_current_span(scope) as span:
handle_outgoing_receive = await self.my_receive(receive, span)
async def handle_outgoing_request(message: 'Message') -> None:
await span.response(message)
await send(message)
await self.app(scope, handle_outgoing_receive, handle_outgoing_request)

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
"""
@author: peng
@file: span.py
@time: 2025/1/17 16:57
"""
from contextlib import asynccontextmanager
from starlette.types import Scope, Message
from .ctx import TraceCtx
class Span:
"""
整个http生命周期
request(before) --> request(after) --> response(before) --> response(after)
"""
def __init__(self, scope: Scope):
self.scope = scope
async def request_before(self):
"""
request_before: 处理header信息等, 如记录请求体信息
"""
TraceCtx.set_id()
async def request_after(self, message: Message):
"""
request_after: 处理请求bytes 如记录请求参数
example:
message: {'type': 'http.request', 'body': b'{\r\n "name": "\xe8\x8b\x8f\xe8\x8b\x8f\xe8\x8b\x8f"\r\n}', 'more_body': False}
"""
return message
async def response(self, message: Message):
"""
if message['type'] == "http.response.start": -----> request-before
pass
if message['type'] == "http.response.body": -----> request-after
message.get('body', b'')
pass
"""
if message['type'] == 'http.response.start':
message['headers'].append((b'request-id', TraceCtx.get_id().encode()))
return message
@asynccontextmanager
async def get_current_span(scope: Scope):
yield Span(scope)

View File

@@ -3,19 +3,19 @@ import json
import os
import requests
import time
import warnings
from datetime import datetime
from fastapi import Request
from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse
from functools import lru_cache, wraps
from typing import Literal, Optional, Union
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Any, Callable, Literal, Optional
from user_agents import parse
from module_admin.entity.vo.log_vo import LogininforModel, OperLogModel
from module_admin.service.log_service import LoginLogService, OperationLogService
from module_admin.service.login_service import LoginService
from config.enums import BusinessType
from config.env import AppConfig
from exceptions.exception import LoginException, ServiceException, ServiceWarning
from module_admin.entity.vo.log_vo import LogininforModel, OperLogModel
from module_admin.service.log_service import LoginLogService, OperationLogService
from module_admin.service.login_service import LoginService
from utils.log_util import logger
from utils.response_util import ResponseUtil
@@ -52,13 +52,15 @@ class Log:
# 获取项目根路径
project_root = os.getcwd()
# 处理文件路径,去除项目根路径部分
relative_path = os.path.relpath(file_path, start=project_root)[0:-2].replace('\\', '.')
relative_path = os.path.relpath(file_path, start=project_root)[0:-2].replace('\\', '.').replace('/', '.')
# 获取当前被装饰函数所在路径
func_path = f'{relative_path}{func.__name__}()'
# 获取上下文信息
request: Request = kwargs.get('request')
request_name_list = get_function_parameters_name_by_type(func, Request)
request = get_function_parameters_value_by_name(func, request_name_list[0], *args, **kwargs)
token = request.headers.get('Authorization')
query_db = kwargs.get('query_db')
session_name_list = get_function_parameters_name_by_type(func, AsyncSession)
query_db = get_function_parameters_value_by_name(func, session_name_list[0], *args, **kwargs)
request_method = request.method
operator_type = 0
user_agent = request.headers.get('User-Agent')
@@ -201,188 +203,6 @@ class Log:
return wrapper
def log_decorator(
title: str,
business_type: Union[Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], BusinessType],
log_type: Optional[Literal['login', 'operation']] = 'operation',
):
"""
日志装饰器
:param title: 当前日志装饰器装饰的模块标题
:param business_type: 业务类型0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8生成代码 9清空数据
:param log_type: 日志类型login表示登录日志operation表示为操作日志
:return:
"""
warnings.simplefilter('always', category=DeprecationWarning)
if isinstance(business_type, BusinessType):
business_type = business_type.value
warnings.warn(
'未来版本将会移除@log_decorator装饰器请使用@Log装饰器',
category=DeprecationWarning,
stacklevel=2,
)
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start_time = time.time()
# 获取被装饰函数的文件路径
file_path = inspect.getfile(func)
# 获取项目根路径
project_root = os.getcwd()
# 处理文件路径,去除项目根路径部分
relative_path = os.path.relpath(file_path, start=project_root)[0:-2].replace('\\', '.')
# 获取当前被装饰函数所在路径
func_path = f'{relative_path}{func.__name__}()'
# 获取上下文信息
request: Request = kwargs.get('request')
token = request.headers.get('Authorization')
query_db = kwargs.get('query_db')
request_method = request.method
operator_type = 0
user_agent = request.headers.get('User-Agent')
if 'Windows' in user_agent or 'Macintosh' in user_agent or 'Linux' in user_agent:
operator_type = 1
if 'Mobile' in user_agent or 'Android' in user_agent or 'iPhone' in user_agent:
operator_type = 2
# 获取请求的url
oper_url = request.url.path
# 获取请求的ip及ip归属区域
oper_ip = request.headers.get('X-Forwarded-For')
oper_location = '内网IP'
if AppConfig.app_ip_location_query:
oper_location = get_ip_location(oper_ip)
# 根据不同的请求类型使用不同的方法获取请求参数
content_type = request.headers.get('Content-Type')
if content_type and (
'multipart/form-data' in content_type or 'application/x-www-form-urlencoded' in content_type
):
payload = await request.form()
oper_param = '\n'.join([f'{key}: {value}' for key, value in payload.items()])
else:
payload = await request.body()
# 通过 request.path_params 直接访问路径参数
path_params = request.path_params
oper_param = {}
if payload:
oper_param.update(json.loads(str(payload, 'utf-8')))
if path_params:
oper_param.update(path_params)
oper_param = json.dumps(oper_param, ensure_ascii=False)
# 日志表请求参数字段长度最大为2000因此在此处判断长度
if len(oper_param) > 2000:
oper_param = '请求参数过长'
# 获取操作时间
oper_time = datetime.now()
# 此处在登录之前向原始函数传递一些登录信息,用于监测在线用户的相关信息
login_log = {}
if log_type == 'login':
user_agent_info = parse(user_agent)
browser = f'{user_agent_info.browser.family}'
system_os = f'{user_agent_info.os.family}'
if user_agent_info.browser.version != ():
browser += f' {user_agent_info.browser.version[0]}'
if user_agent_info.os.version != ():
system_os += f' {user_agent_info.os.version[0]}'
login_log = dict(
ipaddr=oper_ip,
loginLocation=oper_location,
browser=browser,
os=system_os,
loginTime=oper_time.strftime('%Y-%m-%d %H:%M:%S'),
)
kwargs['form_data'].login_info = login_log
try:
# 调用原始函数
result = await func(*args, **kwargs)
except (LoginException, ServiceWarning) as e:
logger.warning(e.message)
result = ResponseUtil.failure(data=e.data, msg=e.message)
except ServiceException as e:
logger.error(e.message)
result = ResponseUtil.error(data=e.data, msg=e.message)
except Exception as e:
logger.exception(e)
result = ResponseUtil.error(msg=str(e))
# 获取请求耗时
cost_time = float(time.time() - start_time) * 100
# 判断请求是否来自api文档
request_from_swagger = (
request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False
)
request_from_redoc = (
request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False
)
# 根据响应结果的类型使用不同的方法获取响应结果参数
if (
isinstance(result, JSONResponse)
or isinstance(result, ORJSONResponse)
or isinstance(result, UJSONResponse)
):
result_dict = json.loads(str(result.body, 'utf-8'))
else:
if request_from_swagger or request_from_redoc:
result_dict = {}
else:
if result.status_code == 200:
result_dict = {'code': result.status_code, 'message': '获取成功'}
else:
result_dict = {'code': result.status_code, 'message': '获取失败'}
json_result = json.dumps(result_dict, ensure_ascii=False)
# 根据响应结果获取响应状态及异常信息
status = 1
error_msg = ''
if result_dict.get('code') == 200:
status = 0
else:
error_msg = result_dict.get('msg')
# 根据日志类型向对应的日志表插入数据
if log_type == 'login':
# 登录请求来自于api文档时不记录登录日志其余情况则记录
if request_from_swagger or request_from_redoc:
pass
else:
user = kwargs.get('form_data')
user_name = user.username
login_log['loginTime'] = oper_time
login_log['userName'] = user_name
login_log['status'] = str(status)
login_log['msg'] = result_dict.get('msg')
await LoginLogService.add_login_log_services(query_db, LogininforModel(**login_log))
else:
current_user = await LoginService.get_current_user(request, token, query_db)
oper_name = current_user.user.user_name
dept_name = current_user.user.dept.dept_name if current_user.user.dept else None
operation_log = OperLogModel(
title=title,
businessType=business_type,
method=func_path,
requestMethod=request_method,
operatorType=operator_type,
operName=oper_name,
deptName=dept_name,
operUrl=oper_url,
operIp=oper_ip,
operLocation=oper_location,
operParam=oper_param,
jsonResult=json_result,
status=status,
errorMsg=error_msg,
operTime=oper_time,
costTime=int(cost_time),
)
await OperationLogService.add_operation_log_services(query_db, operation_log)
return result
return wrapper
return decorator
@lru_cache()
def get_ip_location(oper_ip: str):
"""
@@ -405,3 +225,37 @@ def get_ip_location(oper_ip: str):
oper_location = '未知'
print(e)
return oper_location
def get_function_parameters_name_by_type(func: Callable, param_type: Any):
"""
获取函数指定类型的参数名称
:param func: 函数
:param arg_type: 参数类型
:return: 函数指定类型的参数名称
"""
# 获取函数的参数信息
parameters = inspect.signature(func).parameters
# 找到指定类型的参数名称
parameters_name_list = []
for name, param in parameters.items():
if param.annotation == param_type:
parameters_name_list.append(name)
return parameters_name_list
def get_function_parameters_value_by_name(func: Callable, name: str, *args, **kwargs):
"""
获取函数指定参数的值
:param func: 函数
:param name: 参数名
:return: 参数值
"""
# 获取参数值
bound_parameters = inspect.signature(func).bind(*args, **kwargs)
bound_parameters.apply_defaults()
parameters_value = bound_parameters.arguments.get(name)
return parameters_value

View File

@@ -2,10 +2,13 @@ import inspect
from fastapi import Form, Query
from pydantic import BaseModel
from pydantic.fields import FieldInfo
from typing import Type
from typing import Type, TypeVar
def as_query(cls: Type[BaseModel]):
BaseModelVar = TypeVar('BaseModelVar', bound=BaseModel)
def as_query(cls: Type[BaseModelVar]) -> Type[BaseModelVar]:
"""
pydantic模型查询参数装饰器将pydantic模型用于接收查询参数
"""
@@ -43,7 +46,7 @@ def as_query(cls: Type[BaseModel]):
return cls
def as_form(cls: Type[BaseModel]):
def as_form(cls: Type[BaseModelVar]) -> Type[BaseModelVar]:
"""
pydantic模型表单参数装饰器将pydantic模型用于接收表单参数
"""

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from fastapi import APIRouter, Depends, Form, Query, Request
from fastapi import APIRouter, Depends, Form, Request
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from config.enums import BusinessType
@@ -24,7 +24,7 @@ configController = APIRouter(prefix='/system/config', dependencies=[Depends(Logi
)
async def get_system_config_list(
request: Request,
config_page_query: ConfigPageQueryModel = Query(),
config_page_query: ConfigPageQueryModel = Depends(ConfigPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from fastapi import APIRouter, Depends, Query, Request
from fastapi import APIRouter, Depends, Request
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
@@ -42,7 +42,7 @@ async def get_system_dept_tree_for_edit_option(
)
async def get_system_dept_list(
request: Request,
dept_query: DeptQueryModel = Query(),
dept_query: DeptQueryModel = Depends(DeptQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
data_scope_sql: str = Depends(GetDataScope('SysDept')),
):

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from fastapi import APIRouter, Depends, Form, Query, Request
from fastapi import APIRouter, Depends, Form, Request
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
@@ -32,7 +32,7 @@ dictController = APIRouter(prefix='/system/dict', dependencies=[Depends(LoginSer
)
async def get_system_dict_type_list(
request: Request,
dict_type_page_query: DictTypePageQueryModel = Query(),
dict_type_page_query: DictTypePageQueryModel = Depends(DictTypePageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据
@@ -152,7 +152,7 @@ async def query_system_dict_type_data(request: Request, dict_type: str, query_db
)
async def get_system_dict_data_list(
request: Request,
dict_data_page_query: DictDataPageQueryModel = Query(),
dict_data_page_query: DictDataPageQueryModel = Depends(DictDataPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from fastapi import APIRouter, Depends, Form, Query, Request
from fastapi import APIRouter, Depends, Form, Request
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from config.enums import BusinessType
@@ -32,7 +32,7 @@ jobController = APIRouter(prefix='/monitor', dependencies=[Depends(LoginService.
)
async def get_system_job_list(
request: Request,
job_page_query: JobPageQueryModel = Query(),
job_page_query: JobPageQueryModel = Depends(JobPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据
@@ -148,7 +148,7 @@ async def export_system_job_list(
)
async def get_system_job_log_list(
request: Request,
job_log_page_query: JobLogPageQueryModel = Query(),
job_log_page_query: JobLogPageQueryModel = Depends(JobLogPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据

View File

@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, Form, Query, Request
from fastapi import APIRouter, Depends, Form, Request
from sqlalchemy.ext.asyncio import AsyncSession
from config.enums import BusinessType
from config.get_db import get_db
@@ -29,7 +29,7 @@ logController = APIRouter(prefix='/monitor', dependencies=[Depends(LoginService.
)
async def get_system_operation_log_list(
request: Request,
operation_log_page_query: OperLogPageQueryModel = Query(),
operation_log_page_query: OperLogPageQueryModel = Depends(OperLogPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据
@@ -88,7 +88,7 @@ async def export_system_operation_log_list(
)
async def get_system_login_log_list(
request: Request,
login_log_page_query: LoginLogPageQueryModel = Query(),
login_log_page_query: LoginLogPageQueryModel = Depends(LoginLogPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from fastapi import APIRouter, Depends, Query, Request
from fastapi import APIRouter, Depends, Request
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
@@ -48,7 +48,7 @@ async def get_system_role_menu_tree(
)
async def get_system_menu_list(
request: Request,
menu_query: MenuQueryModel = Query(),
menu_query: MenuQueryModel = Depends(MenuQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from fastapi import APIRouter, Depends, Query, Request
from fastapi import APIRouter, Depends, Request
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from config.enums import BusinessType
@@ -23,7 +23,7 @@ noticeController = APIRouter(prefix='/system/notice', dependencies=[Depends(Logi
)
async def get_system_notice_list(
request: Request,
notice_page_query: NoticePageQueryModel = Query(),
notice_page_query: NoticePageQueryModel = Depends(NoticePageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据

View File

@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, Query, Request
from fastapi import APIRouter, Depends, Request
from sqlalchemy.ext.asyncio import AsyncSession
from config.enums import BusinessType
from config.get_db import get_db
@@ -18,7 +18,9 @@ onlineController = APIRouter(prefix='/monitor/online', dependencies=[Depends(Log
@onlineController.get(
'/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:list'))]
)
async def get_monitor_online_list(request: Request, online_page_query: OnlineQueryModel = Query()):
async def get_monitor_online_list(
request: Request, online_page_query: OnlineQueryModel = Depends(OnlineQueryModel.as_query)
):
# 获取全量数据
online_query_result = await OnlineService.get_online_list_services(request, online_page_query)
logger.info('获取成功')

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from fastapi import APIRouter, Depends, Form, Query, Request
from fastapi import APIRouter, Depends, Form, Request
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from config.enums import BusinessType
@@ -24,7 +24,7 @@ postController = APIRouter(prefix='/system/post', dependencies=[Depends(LoginSer
)
async def get_system_post_list(
request: Request,
post_page_query: PostPageQueryModel = Query(),
post_page_query: PostPageQueryModel = Depends(PostPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from fastapi import APIRouter, Depends, Form, Query, Request
from fastapi import APIRouter, Depends, Form, Request
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from config.enums import BusinessType
@@ -43,7 +43,7 @@ async def get_system_role_dept_tree(
)
async def get_system_role_list(
request: Request,
role_page_query: RolePageQueryModel = Query(),
role_page_query: RolePageQueryModel = Depends(RolePageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
data_scope_sql: str = Depends(GetDataScope('SysDept')),
):
@@ -211,7 +211,7 @@ async def reset_system_role_status(
)
async def get_system_allocated_user_list(
request: Request,
user_role: UserRolePageQueryModel = Query(),
user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
data_scope_sql: str = Depends(GetDataScope('SysUser')),
):
@@ -230,7 +230,7 @@ async def get_system_allocated_user_list(
)
async def get_system_unallocated_user_list(
request: Request,
user_role: UserRolePageQueryModel = Query(),
user_role: UserRolePageQueryModel = Depends(UserRolePageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
data_scope_sql: str = Depends(GetDataScope('SysUser')),
):
@@ -246,7 +246,7 @@ async def get_system_unallocated_user_list(
@Log(title='角色管理', business_type=BusinessType.GRANT)
async def add_system_role_user(
request: Request,
add_role_user: CrudUserRoleModel = Query(),
add_role_user: CrudUserRoleModel = Depends(CrudUserRoleModel.as_query),
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
data_scope_sql: str = Depends(GetDataScope('SysDept')),
@@ -274,7 +274,7 @@ async def cancel_system_role_user(
@Log(title='角色管理', business_type=BusinessType.GRANT)
async def batch_cancel_system_role_user(
request: Request,
batch_cancel_user_role: CrudUserRoleModel = Query(),
batch_cancel_user_role: CrudUserRoleModel = Depends(CrudUserRoleModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
batch_cancel_user_role_result = await UserService.delete_user_role_services(query_db, batch_cancel_user_role)

View File

@@ -2,9 +2,10 @@ import os
from datetime import datetime
from fastapi import APIRouter, Depends, File, Form, Query, Request, UploadFile
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Optional, Union
from typing import Literal, Optional, Union
from pydantic_validation_decorator import ValidateFields
from config.get_db import get_db
from config.enums import BusinessType
from config.env import UploadConfig
from module_admin.annotation.log_annotation import Log
from module_admin.aspect.data_scope import GetDataScope
@@ -30,7 +31,6 @@ from module_admin.service.login_service import LoginService
from module_admin.service.user_service import UserService
from module_admin.service.role_service import RoleService
from module_admin.service.dept_service import DeptService
from config.enums import BusinessType
from utils.common_util import bytes2file_response
from utils.log_util import logger
from utils.page_util import PageResponseModel
@@ -57,7 +57,7 @@ async def get_system_dept_tree(
)
async def get_system_user_list(
request: Request,
user_page_query: UserPageQueryModel = Query(),
user_page_query: UserPageQueryModel = Depends(UserPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
data_scope_sql: str = Depends(GetDataScope('SysUser')),
):
@@ -135,7 +135,7 @@ async def delete_system_user(
):
user_id_list = user_ids.split(',') if user_ids else []
if user_id_list:
if current_user.user.user_id in user_id_list:
if current_user.user.user_id in list(map(int, user_id_list)):
logger.warning('当前登录用户不能删除')
return ResponseUtil.failure(msg='当前登录用户不能删除')
@@ -220,7 +220,7 @@ async def query_detail_system_user_profile(
)
async def query_detail_system_user(
request: Request,
user_id: Optional[Union[int, str]] = '',
user_id: Optional[Union[int, Literal['']]] = '',
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
data_scope_sql: str = Depends(GetDataScope('SysUser')),
@@ -296,7 +296,7 @@ async def change_system_user_profile_info(
@Log(title='个人信息', business_type=BusinessType.UPDATE)
async def reset_system_user_password(
request: Request,
reset_password: ResetPasswordModel = Query(),
reset_password: ResetPasswordModel,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):

View File

@@ -71,6 +71,7 @@ class ConfigDao:
if query_object.begin_time and query_object.end_time
else True,
)
.order_by(SysConfig.config_id)
.distinct()
)
config_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)

View File

@@ -84,6 +84,7 @@ class DictTypeDao:
if query_object.begin_time and query_object.end_time
else True,
)
.order_by(SysDictType.dict_id)
.distinct()
)
dict_type_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)

View File

@@ -69,6 +69,7 @@ class JobDao:
SysJob.job_group == query_object.job_group if query_object.job_group else True,
SysJob.status == query_object.status if query_object.status else True,
)
.order_by(SysJob.job_id)
.distinct()
)
job_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)

View File

@@ -1,5 +1,5 @@
from datetime import datetime, time
from sqlalchemy import delete, select
from sqlalchemy import delete, desc, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from module_admin.entity.do.job_do import SysJobLog
@@ -35,6 +35,7 @@ class JobLogDao:
if query_object.begin_time and query_object.end_time
else True,
)
.order_by(desc(SysJobLog.create_time))
.distinct()
)
job_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)

View File

@@ -5,6 +5,7 @@ from module_admin.entity.do.log_do import SysLogininfor, SysOperLog
from module_admin.entity.vo.log_vo import LogininforModel, LoginLogPageQueryModel, OperLogModel, OperLogPageQueryModel
from utils.common_util import SnakeCaseUtil
from utils.page_util import PageUtil
from utils.time_format_util import TimeFormatUtil
class OperationLogDao:
@@ -38,8 +39,8 @@ class OperationLogDao:
SysOperLog.business_type == query_object.business_type if query_object.business_type else True,
SysOperLog.status == query_object.status if query_object.status else True,
SysOperLog.oper_time.between(
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
datetime.combine(TimeFormatUtil.parse_date(query_object.begin_time), time(00, 00, 00)),
datetime.combine(TimeFormatUtil.parse_date(query_object.end_time), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,
@@ -120,8 +121,8 @@ class LoginLogDao:
SysLogininfor.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True,
SysLogininfor.status == query_object.status if query_object.status else True,
SysLogininfor.login_time.between(
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
datetime.combine(TimeFormatUtil.parse_date(query_object.begin_time), time(00, 00, 00)),
datetime.combine(TimeFormatUtil.parse_date(query_object.end_time), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,

View File

@@ -72,6 +72,7 @@ class NoticeDao:
if query_object.begin_time and query_object.end_time
else True,
)
.order_by(SysNotice.notice_id)
.distinct()
)
notice_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)

View File

@@ -318,6 +318,7 @@ class UserDao:
and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'),
isouter=True,
)
.order_by(SysUser.user_id)
.distinct()
)
user_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)

View File

@@ -19,4 +19,4 @@ class SysConfig(Base):
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, default='', comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(String(500), nullable=True, default='', comment='备注')
remark = Column(String(500), nullable=True, default=None, comment='备注')

View File

@@ -18,8 +18,8 @@ class SysDept(Base):
leader = Column(String(20), nullable=True, default=None, comment='负责人')
phone = Column(String(11), nullable=True, default=None, comment='联系电话')
email = Column(String(50), nullable=True, default=None, comment='邮箱')
status = Column(String(1), nullable=True, default=0, comment='部门状态0正常 1停用')
del_flag = Column(String(1), nullable=True, default=0, comment='删除标志0代表存在 2代表删除')
status = Column(String(1), nullable=True, default='0', comment='部门状态0正常 1停用')
del_flag = Column(String(1), nullable=True, default='0', comment='删除标志0代表存在 2代表删除')
create_by = Column(String(64), nullable=True, default='', comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, default='', comment='更新者')

View File

@@ -18,7 +18,7 @@ class SysDictType(Base):
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, default='', comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(String(500), nullable=True, default='', comment='备注')
remark = Column(String(500), nullable=True, default=None, comment='备注')
__table_args__ = (UniqueConstraint('dict_type', name='uq_sys_dict_type_dict_type'),)
@@ -35,12 +35,12 @@ class SysDictData(Base):
dict_label = Column(String(100), nullable=True, default='', comment='字典标签')
dict_value = Column(String(100), nullable=True, default='', comment='字典键值')
dict_type = Column(String(100), nullable=True, default='', comment='字典类型')
css_class = Column(String(100), nullable=True, default='', comment='样式属性(其他样式扩展)')
list_class = Column(String(100), nullable=True, default='', comment='表格回显样式')
css_class = Column(String(100), nullable=True, default=None, comment='样式属性(其他样式扩展)')
list_class = Column(String(100), nullable=True, default=None, comment='表格回显样式')
is_default = Column(String(1), nullable=True, default='N', comment='是否默认Y是 N否')
status = Column(String(1), nullable=True, default='0', comment='状态0正常 1停用')
create_by = Column(String(64), nullable=True, default='', comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), nullable=True, default='', comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(String(500), nullable=True, default='', comment='备注')
remark = Column(String(500), nullable=True, default=None, comment='备注')

View File

@@ -11,32 +11,26 @@ class SysJob(Base):
__tablename__ = 'sys_job'
job_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务ID')
job_name = Column(String(64, collation='utf8_general_ci'), nullable=False, comment='任务名称')
job_group = Column(String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务组名')
job_executor = Column(
String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务执行器'
)
invoke_target = Column(String(500, collation='utf8_general_ci'), nullable=False, comment='调用目标字符串')
job_args = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='位置参数')
job_kwargs = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='关键字参数')
cron_expression = Column(
String(255, collation='utf8_general_ci'), nullable=True, default='', comment='cron执行表达式'
)
job_name = Column(String(64), nullable=True, default='', comment='任务名称')
job_group = Column(String(64), nullable=True, default='default', comment='任务组名')
job_executor = Column(String(64), nullable=True, default='default', comment='任务执行器')
invoke_target = Column(String(500), nullable=False, comment='调用目标字符串')
job_args = Column(String(255), nullable=True, default='', comment='位置参数')
job_kwargs = Column(String(255), nullable=True, default='', comment='关键字参数')
cron_expression = Column(String(255), nullable=True, default='', comment='cron执行表达式')
misfire_policy = Column(
String(20, collation='utf8_general_ci'),
String(20),
nullable=True,
default='3',
comment='计划执行错误策略1立即执行 2执行一次 3放弃执行',
)
concurrent = Column(
String(1, collation='utf8_general_ci'), nullable=True, default='1', comment='是否并发执行0允许 1禁止'
)
status = Column(String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='状态0正常 1暂停')
create_by = Column(String(64, collation='utf8_general_ci'), nullable=True, default='', comment='创建者')
concurrent = Column(String(1), nullable=True, default='1', comment='是否并发执行0允许 1禁止')
status = Column(String(1), nullable=True, default='0', comment='状态0正常 1暂停')
create_by = Column(String(64), nullable=True, default='', comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64, collation='utf8_general_ci'), nullable=True, default='', comment='更新者')
update_by = Column(String(64), nullable=True, default='', comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(String(500, collation='utf8_general_ci'), nullable=True, default='', comment='备注信息')
remark = Column(String(500), nullable=True, default='', comment='备注信息')
class SysJobLog(Base):
@@ -47,18 +41,14 @@ class SysJobLog(Base):
__tablename__ = 'sys_job_log'
job_log_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务日志ID')
job_name = Column(String(64, collation='utf8_general_ci'), nullable=False, comment='任务名称')
job_group = Column(String(64, collation='utf8_general_ci'), nullable=False, comment='任务组名')
job_executor = Column(
String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务执行器'
)
invoke_target = Column(String(500, collation='utf8_general_ci'), nullable=False, comment='调用目标字符串')
job_args = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='位置参数')
job_kwargs = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='关键字参数')
job_trigger = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='任务触发器')
job_message = Column(String(500, collation='utf8_general_ci'), nullable=True, default='', comment='日志信息')
status = Column(
String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='执行状态0正常 1失败'
)
exception_info = Column(String(2000, collation='utf8_general_ci'), nullable=True, default='', comment='异常信息')
job_name = Column(String(64), nullable=False, comment='任务名称')
job_group = Column(String(64), nullable=False, comment='任务组名')
job_executor = Column(String(64), nullable=False, comment='任务执行器')
invoke_target = Column(String(500), nullable=False, comment='调用目标字符串')
job_args = Column(String(255), nullable=True, default='', comment='位置参数')
job_kwargs = Column(String(255), nullable=True, default='', comment='关键字参数')
job_trigger = Column(String(255), nullable=True, default='', comment='任务触发器')
job_message = Column(String(500), nullable=True, default='', comment='日志信息')
status = Column(String(1), nullable=True, default='0', comment='执行状态0正常 1失败')
exception_info = Column(String(2000), nullable=True, default='', comment='异常信息')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')

View File

@@ -11,15 +11,13 @@ class SysLogininfor(Base):
__tablename__ = 'sys_logininfor'
info_id = Column(Integer, primary_key=True, autoincrement=True, comment='访问ID')
user_name = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='用户账号')
ipaddr = Column(String(128, collation='utf8_general_ci'), nullable=True, default='', comment='登录IP地址')
login_location = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='登录地点')
browser = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='浏览器类型')
os = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='操作系统')
status = Column(
String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='登录状态0成功 1失败'
)
msg = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='提示消息')
user_name = Column(String(50), nullable=True, default='', comment='用户账号')
ipaddr = Column(String(128), nullable=True, default='', comment='登录IP地址')
login_location = Column(String(255), nullable=True, default='', comment='登录地点')
browser = Column(String(50), nullable=True, default='', comment='浏览器类型')
os = Column(String(50), nullable=True, default='', comment='操作系统')
status = Column(String(1), nullable=True, default='0', comment='登录状态0成功 1失败')
msg = Column(String(255), nullable=True, default='', comment='提示消息')
login_time = Column(DateTime, nullable=True, default=datetime.now(), comment='访问时间')
idx_sys_logininfor_s = Index('idx_sys_logininfor_s', status)
@@ -34,20 +32,20 @@ class SysOperLog(Base):
__tablename__ = 'sys_oper_log'
oper_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='日志主键')
title = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='模块标题')
title = Column(String(50), nullable=True, default='', comment='模块标题')
business_type = Column(Integer, default=0, comment='业务类型0其它 1新增 2修改 3删除')
method = Column(String(100, collation='utf8_general_ci'), nullable=True, default='', comment='方法名称')
request_method = Column(String(10, collation='utf8_general_ci'), nullable=True, default='', comment='请求方式')
method = Column(String(100), nullable=True, default='', comment='方法名称')
request_method = Column(String(10), nullable=True, default='', comment='请求方式')
operator_type = Column(Integer, default=0, comment='操作类别0其它 1后台用户 2手机端用户')
oper_name = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='操作人员')
dept_name = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='部门名称')
oper_url = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='请求URL')
oper_ip = Column(String(128, collation='utf8_general_ci'), nullable=True, default='', comment='主机地址')
oper_location = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='操作地点')
oper_param = Column(String(2000, collation='utf8_general_ci'), nullable=True, default='', comment='请求参数')
json_result = Column(String(2000, collation='utf8_general_ci'), nullable=True, default='', comment='返回参数')
oper_name = Column(String(50), nullable=True, default='', comment='操作人员')
dept_name = Column(String(50), nullable=True, default='', comment='部门名称')
oper_url = Column(String(255), nullable=True, default='', comment='请求URL')
oper_ip = Column(String(128), nullable=True, default='', comment='主机地址')
oper_location = Column(String(255), nullable=True, default='', comment='操作地点')
oper_param = Column(String(2000), nullable=True, default='', comment='请求参数')
json_result = Column(String(2000), nullable=True, default='', comment='返回参数')
status = Column(Integer, default=0, comment='操作状态0正常 1异常')
error_msg = Column(String(2000, collation='utf8_general_ci'), nullable=True, default='', comment='错误消息')
error_msg = Column(String(2000), nullable=True, default='', comment='错误消息')
oper_time = Column(DateTime, nullable=True, default=datetime.now(), comment='操作时间')
cost_time = Column(BigInteger, default=0, comment='消耗时间')

View File

@@ -11,12 +11,12 @@ class SysNotice(Base):
__tablename__ = 'sys_notice'
notice_id = Column(Integer, primary_key=True, autoincrement=True, comment='公告ID')
notice_title = Column(String(50, collation='utf8_general_ci'), nullable=False, comment='公告标题')
notice_type = Column(String(1, collation='utf8_general_ci'), nullable=False, comment='公告类型1通知 2公告')
notice_title = Column(String(50), nullable=False, comment='公告标题')
notice_type = Column(String(1), nullable=False, comment='公告类型1通知 2公告')
notice_content = Column(LargeBinary, comment='公告内容')
status = Column(String(1, collation='utf8_general_ci'), default='0', comment='公告状态0正常 1关闭')
create_by = Column(String(64, collation='utf8_general_ci'), default='', comment='创建者')
status = Column(String(1), default='0', comment='公告状态0正常 1关闭')
create_by = Column(String(64), default='', comment='创建者')
create_time = Column(DateTime, comment='创建时间', default=datetime.now())
update_by = Column(String(64, collation='utf8_general_ci'), default='', comment='更新者')
update_by = Column(String(64), default='', comment='更新者')
update_time = Column(DateTime, comment='更新时间', default=datetime.now())
remark = Column(String(255, collation='utf8_general_ci'), comment='备注')
remark = Column(String(255), comment='备注')

View File

@@ -19,4 +19,4 @@ class SysPost(Base):
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), default='', comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(String(500), nullable=True, default='', comment='备注')
remark = Column(String(500), nullable=True, default=None, comment='备注')

View File

@@ -11,23 +11,23 @@ class SysRole(Base):
__tablename__ = 'sys_role'
role_id = Column(Integer, primary_key=True, autoincrement=True, comment='角色ID')
role_name = Column(String(30, collation='utf8_general_ci'), nullable=False, comment='角色名称')
role_key = Column(String(100, collation='utf8_general_ci'), nullable=False, comment='角色权限字符串')
role_name = Column(String(30), nullable=False, comment='角色名称')
role_key = Column(String(100), nullable=False, comment='角色权限字符串')
role_sort = Column(Integer, nullable=False, comment='显示顺序')
data_scope = Column(
String(1, collation='utf8_general_ci'),
String(1),
default='1',
comment='数据范围1全部数据权限 2自定数据权限 3本部门数据权限 4本部门及以下数据权限',
)
menu_check_strictly = Column(Integer, default=1, comment='菜单树选择项是否关联显示')
dept_check_strictly = Column(Integer, default=1, comment='部门树选择项是否关联显示')
status = Column(String(1, collation='utf8_general_ci'), nullable=False, comment='角色状态0正常 1停用')
del_flag = Column(String(1, collation='utf8_general_ci'), default='0', comment='删除标志0代表存在 2代表删除')
create_by = Column(String(64, collation='utf8_general_ci'), default='', comment='创建者')
status = Column(String(1), nullable=False, default='0', comment='角色状态0正常 1停用')
del_flag = Column(String(1), default='0', comment='删除标志0代表存在 2代表删除')
create_by = Column(String(64), default='', comment='创建者')
create_time = Column(DateTime, default=datetime.now(), comment='创建时间')
update_by = Column(String(64, collation='utf8_general_ci'), default='', comment='更新者')
update_by = Column(String(64), default='', comment='更新者')
update_time = Column(DateTime, default=datetime.now(), comment='更新时间')
remark = Column(String(500, collation='utf8_general_ci'), comment='备注')
remark = Column(String(500), default=None, comment='备注')
class SysRoleDept(Base):

View File

@@ -11,24 +11,24 @@ class SysUser(Base):
__tablename__ = 'sys_user'
user_id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID')
dept_id = Column(Integer, comment='部门ID')
user_name = Column(String(30, collation='utf8_general_ci'), nullable=False, comment='用户账号')
nick_name = Column(String(30, collation='utf8_general_ci'), nullable=False, comment='用户昵称')
user_type = Column(String(2, collation='utf8_general_ci'), default='00', comment='用户类型00系统用户')
email = Column(String(50, collation='utf8_general_ci'), default='', comment='用户邮箱')
phonenumber = Column(String(11, collation='utf8_general_ci'), default='', comment='手机号码')
sex = Column(String(1, collation='utf8_general_ci'), default='0', comment='用户性别0男 1女 2未知')
avatar = Column(String(100, collation='utf8_general_ci'), default='', comment='头像地址')
password = Column(String(100, collation='utf8_general_ci'), default='', comment='密码')
status = Column(String(1, collation='utf8_general_ci'), default='0', comment='帐号状态0正常 1停用')
del_flag = Column(String(1, collation='utf8_general_ci'), default='0', comment='删除标志0代表存在 2代表删除')
login_ip = Column(String(128, collation='utf8_general_ci'), default='', comment='最后登录IP')
dept_id = Column(Integer, default=None, comment='部门ID')
user_name = Column(String(30), nullable=False, comment='用户账号')
nick_name = Column(String(30), nullable=False, comment='用户昵称')
user_type = Column(String(2), default='00', comment='用户类型00系统用户')
email = Column(String(50), default='', comment='用户邮箱')
phonenumber = Column(String(11), default='', comment='手机号码')
sex = Column(String(1), default='0', comment='用户性别0男 1女 2未知')
avatar = Column(String(100), default='', comment='头像地址')
password = Column(String(100), default='', comment='密码')
status = Column(String(1), default='0', comment='帐号状态0正常 1停用')
del_flag = Column(String(1), default='0', comment='删除标志0代表存在 2代表删除')
login_ip = Column(String(128), default='', comment='最后登录IP')
login_date = Column(DateTime, comment='最后登录时间')
create_by = Column(String(64, collation='utf8_general_ci'), default='', comment='创建者')
create_by = Column(String(64), default='', comment='创建者')
create_time = Column(DateTime, comment='创建时间', default=datetime.now())
update_by = Column(String(64, collation='utf8_general_ci'), default='', comment='更新者')
update_by = Column(String(64), default='', comment='更新者')
update_time = Column(DateTime, comment='更新时间', default=datetime.now())
remark = Column(String(500, collation='utf8_general_ci'), comment='备注')
remark = Column(String(500), default=None, comment='备注')
class SysUserRole(Base):

View File

@@ -3,6 +3,7 @@ from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
from typing import Literal, Optional
from module_admin.annotation.pydantic_annotation import as_query
class ConfigModel(BaseModel):
@@ -53,6 +54,7 @@ class ConfigQueryModel(ConfigModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class ConfigPageQueryModel(ConfigQueryModel):
"""
参数配置管理分页查询模型

View File

@@ -3,6 +3,7 @@ from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import Network, NotBlank, Size
from typing import Literal, Optional
from module_admin.annotation.pydantic_annotation import as_query
class DeptModel(BaseModel):
@@ -52,6 +53,7 @@ class DeptModel(BaseModel):
self.get_email()
@as_query
class DeptQueryModel(DeptModel):
"""
部门管理不分页查询模型

View File

@@ -3,6 +3,7 @@ from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Pattern, Size
from typing import Literal, Optional
from module_admin.annotation.pydantic_annotation import as_query
class DictTypeModel(BaseModel):
@@ -99,6 +100,7 @@ class DictTypeQueryModel(DictTypeModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class DictTypePageQueryModel(DictTypeQueryModel):
"""
字典类型管理分页查询模型
@@ -127,6 +129,7 @@ class DictDataQueryModel(DictDataModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class DictDataPageQueryModel(DictDataQueryModel):
"""
字典数据管理分页查询模型

View File

@@ -3,6 +3,7 @@ from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
from typing import Literal, Optional
from module_admin.annotation.pydantic_annotation import as_query
class JobModel(BaseModel):
@@ -76,6 +77,7 @@ class JobQueryModel(JobModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class JobPageQueryModel(JobQueryModel):
"""
定时任务管理分页查询模型
@@ -112,6 +114,7 @@ class JobLogQueryModel(JobLogModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class JobLogPageQueryModel(JobLogQueryModel):
"""
定时任务日志管理分页查询模型

View File

@@ -2,6 +2,7 @@ from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from typing import Literal, Optional
from module_admin.annotation.pydantic_annotation import as_query
class OperLogModel(BaseModel):
@@ -67,6 +68,7 @@ class OperLogQueryModel(OperLogModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class OperLogPageQueryModel(OperLogQueryModel):
"""
操作日志管理分页查询模型
@@ -99,6 +101,7 @@ class LoginLogQueryModel(LogininforModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class LoginLogPageQueryModel(LoginLogQueryModel):
"""
登录日志管理分页查询模型

View File

@@ -3,6 +3,7 @@ from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
from typing import Literal, Optional
from module_admin.annotation.pydantic_annotation import as_query
class MenuModel(BaseModel):
@@ -67,6 +68,7 @@ class MenuModel(BaseModel):
self.get_perms()
@as_query
class MenuQueryModel(MenuModel):
"""
菜单管理不分页查询模型

View File

@@ -3,6 +3,7 @@ from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size, Xss
from typing import Literal, Optional
from module_admin.annotation.pydantic_annotation import as_query
class NoticeModel(BaseModel):
@@ -42,6 +43,7 @@ class NoticeQueryModel(NoticeModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class NoticePageQueryModel(NoticeQueryModel):
"""
通知公告管理分页查询模型

View File

@@ -2,6 +2,7 @@ from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from typing import Optional
from module_admin.annotation.pydantic_annotation import as_query
class OnlineModel(BaseModel):
@@ -21,6 +22,7 @@ class OnlineModel(BaseModel):
login_time: Optional[datetime] = Field(default=None, description='登录时间')
@as_query
class OnlineQueryModel(OnlineModel):
"""
岗位管理不分页查询模型

View File

@@ -3,6 +3,7 @@ from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
from typing import Literal, Optional
from module_admin.annotation.pydantic_annotation import as_query
class PostModel(BaseModel):
@@ -52,6 +53,7 @@ class PostQueryModel(PostModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class PostPageQueryModel(PostQueryModel):
"""
岗位管理分页查询模型

View File

@@ -3,6 +3,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_valida
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
from typing import List, Literal, Optional, Union
from module_admin.annotation.pydantic_annotation import as_query
class RoleModel(BaseModel):
@@ -103,6 +104,7 @@ class RoleQueryModel(RoleModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class RolePageQueryModel(RoleQueryModel):
"""
角色管理分页查询模型

View File

@@ -5,6 +5,7 @@ from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import Network, NotBlank, Size, Xss
from typing import List, Literal, Optional, Union
from exceptions.exception import ModelValidatorException
from module_admin.annotation.pydantic_annotation import as_query
from module_admin.entity.vo.dept_vo import DeptModel
from module_admin.entity.vo.post_vo import PostModel
from module_admin.entity.vo.role_vo import RoleModel
@@ -161,6 +162,7 @@ class UserQueryModel(UserModel):
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class UserPageQueryModel(UserQueryModel):
"""
用户管理分页查询模型
@@ -237,6 +239,7 @@ class UserRoleQueryModel(UserModel):
role_id: Optional[int] = Field(default=None, description='角色ID')
@as_query
class UserRolePageQueryModel(UserRoleQueryModel):
"""
用户角色关联管理分页查询模型
@@ -265,6 +268,7 @@ class UserRoleResponseModel(BaseModel):
user: UserInfoModel = Field(description='用户信息')
@as_query
class CrudUserRoleModel(BaseModel):
"""
新增、删除用户关联角色及角色关联用户模型

View File

@@ -7,7 +7,8 @@ from exceptions.exception import ServiceException
from module_admin.dao.config_dao import ConfigDao
from module_admin.entity.vo.common_vo import CrudResponseModel
from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel
from utils.common_util import CamelCaseUtil, export_list2excel
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
class ConfigService:
@@ -207,17 +208,12 @@ class ConfigService:
'remark': '备注',
}
data = config_list
for item in data:
for item in config_list:
if item.get('configType') == 'Y':
item['configType'] = ''
else:
item['configType'] = ''
new_data = [
{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data
]
binary_data = export_list2excel(new_data)
binary_data = ExcelUtil.export_list2excel(config_list, mapping_dict)
return binary_data

View File

@@ -253,7 +253,7 @@ class DeptService:
:return:
"""
dept_id_list = dept.ancestors.split(',')
await DeptDao.update_dept_status_normal_dao(query_db, dept_id_list)
await DeptDao.update_dept_status_normal_dao(query_db, list(map(int, dept_id_list)))
@classmethod
async def update_dept_children(cls, query_db: AsyncSession, dept_id: int, new_ancestors: str, old_ancestors: str):

View File

@@ -15,7 +15,8 @@ from module_admin.entity.vo.dict_vo import (
DictTypeModel,
DictTypePageQueryModel,
)
from utils.common_util import CamelCaseUtil, export_list2excel
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
class DictTypeService:
@@ -102,9 +103,10 @@ class DictTypeService:
if dict_type_info.dict_type != page_object.dict_type:
for dict_data in dict_data_list:
edit_dict_data = DictDataModel(
dictCode=dict_data.dict_code,
dictCode=dict_data.get('dict_code'),
dictType=page_object.dict_type,
updateBy=page_object.update_by,
updateTime=page_object.update_time,
).model_dump(exclude_unset=True)
await DictDataDao.edit_dict_data_dao(query_db, edit_dict_data)
await DictTypeDao.edit_dict_type_dao(query_db, edit_dict_type)
@@ -192,17 +194,12 @@ class DictTypeService:
'remark': '备注',
}
data = dict_type_list
for item in data:
for item in dict_type_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '停用'
new_data = [
{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data
]
binary_data = export_list2excel(new_data)
binary_data = ExcelUtil.export_list2excel(dict_type_list, mapping_dict)
return binary_data
@@ -448,9 +445,7 @@ class DictDataService:
'remark': '备注',
}
data = dict_data_list
for item in data:
for item in dict_data_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
@@ -459,9 +454,6 @@ class DictDataService:
item['isDefault'] = ''
else:
item['isDefault'] = ''
new_data = [
{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data
]
binary_data = export_list2excel(new_data)
binary_data = ExcelUtil.export_list2excel(dict_data_list, mapping_dict)
return binary_data

View File

@@ -6,7 +6,7 @@ from module_admin.dao.job_log_dao import JobLogDao
from module_admin.entity.vo.common_vo import CrudResponseModel
from module_admin.entity.vo.job_vo import DeleteJobLogModel, JobLogModel, JobLogPageQueryModel
from module_admin.service.dict_service import DictDataService
from utils.common_util import export_list2excel
from utils.excel_util import ExcelUtil
class JobLogService:
@@ -115,7 +115,6 @@ class JobLogService:
'createTime': '创建时间',
}
data = job_log_list
job_group_list = await DictDataService.query_dict_data_list_from_cache_services(
request.app.state.redis, dict_type='sys_job_group'
)
@@ -129,7 +128,7 @@ class JobLogService:
]
job_executor_option_dict = {item.get('value'): item for item in job_executor_option}
for item in data:
for item in job_log_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
@@ -138,9 +137,6 @@ class JobLogService:
item['jobGroup'] = job_group_option_dict.get(str(item.get('jobGroup'))).get('label')
if str(item.get('jobExecutor')) in job_executor_option_dict.keys():
item['jobExecutor'] = job_executor_option_dict.get(str(item.get('jobExecutor'))).get('label')
new_data = [
{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data
]
binary_data = export_list2excel(new_data)
binary_data = ExcelUtil.export_list2excel(job_log_list, mapping_dict)
return binary_data

View File

@@ -8,8 +8,9 @@ from module_admin.dao.job_dao import JobDao
from module_admin.entity.vo.common_vo import CrudResponseModel
from module_admin.entity.vo.job_vo import DeleteJobModel, EditJobModel, JobModel, JobPageQueryModel
from module_admin.service.dict_service import DictDataService
from utils.common_util import CamelCaseUtil, export_list2excel
from utils.common_util import CamelCaseUtil
from utils.cron_util import CronUtil
from utils.excel_util import ExcelUtil
from utils.string_util import StringUtil
@@ -129,9 +130,7 @@ class JobService:
raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,定时任务已存在')
try:
await JobDao.edit_job_dao(query_db, edit_job)
query_job = SchedulerUtil.get_scheduler_job(job_id=edit_job.get('job_id'))
if query_job:
SchedulerUtil.remove_scheduler_job(job_id=edit_job.get('job_id'))
SchedulerUtil.remove_scheduler_job(job_id=edit_job.get('job_id'))
if edit_job.get('status') == '0':
job_info = await cls.job_detail_services(query_db, edit_job.get('job_id'))
SchedulerUtil.add_scheduler_job(job_info=job_info)
@@ -152,9 +151,7 @@ class JobService:
:param page_object: 定时任务对象
:return: 执行一次定时任务结果
"""
query_job = SchedulerUtil.get_scheduler_job(job_id=page_object.job_id)
if query_job:
SchedulerUtil.remove_scheduler_job(job_id=page_object.job_id)
SchedulerUtil.remove_scheduler_job(job_id=page_object.job_id)
job_info = await cls.job_detail_services(query_db, page_object.job_id)
if job_info:
SchedulerUtil.execute_scheduler_job_once(job_info=job_info)
@@ -176,9 +173,7 @@ class JobService:
try:
for job_id in job_id_list:
await JobDao.delete_job_dao(query_db, JobModel(jobId=job_id))
query_job = SchedulerUtil.get_scheduler_job(job_id=job_id)
if query_job:
SchedulerUtil.remove_scheduler_job(job_id=job_id)
SchedulerUtil.remove_scheduler_job(job_id=job_id)
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
@@ -233,7 +228,6 @@ class JobService:
'remark': '备注',
}
data = job_list
job_group_list = await DictDataService.query_dict_data_list_from_cache_services(
request.app.state.redis, dict_type='sys_job_group'
)
@@ -247,7 +241,7 @@ class JobService:
]
job_executor_option_dict = {item.get('value'): item for item in job_executor_option}
for item in data:
for item in job_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
@@ -266,9 +260,6 @@ class JobService:
item['concurrent'] = '允许'
else:
item['concurrent'] = '禁止'
new_data = [
{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data
]
binary_data = export_list2excel(new_data)
binary_data = ExcelUtil.export_list2excel(job_list, mapping_dict)
return binary_data

View File

@@ -14,7 +14,7 @@ from module_admin.entity.vo.log_vo import (
UnlockUser,
)
from module_admin.service.dict_service import DictDataService
from utils.common_util import export_list2excel
from utils.excel_util import ExcelUtil
class OperationLogService:
@@ -122,7 +122,6 @@ class OperationLogService:
'costTime': '消耗时间(毫秒)',
}
data = operation_log_list
operation_type_list = await DictDataService.query_dict_data_list_from_cache_services(
request.app.state.redis, dict_type='sys_oper_type'
)
@@ -131,18 +130,14 @@ class OperationLogService:
]
operation_type_option_dict = {item.get('value'): item for item in operation_type_option}
for item in data:
for item in operation_log_list:
if item.get('status') == 0:
item['status'] = '成功'
else:
item['status'] = '失败'
if str(item.get('businessType')) in operation_type_option_dict.keys():
item['businessType'] = operation_type_option_dict.get(str(item.get('businessType'))).get('label')
new_data = [
{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data
]
binary_data = export_list2excel(new_data)
binary_data = ExcelUtil.export_list2excel(operation_log_list, mapping_dict)
return binary_data
@@ -253,16 +248,11 @@ class LoginLogService:
'loginTime': '登录日期',
}
data = login_log_list
for item in data:
for item in login_log_list:
if item.get('status') == '0':
item['status'] = '成功'
else:
item['status'] = '失败'
new_data = [
{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data
]
binary_data = export_list2excel(new_data)
binary_data = ExcelUtil.export_list2excel(login_log_list, mapping_dict)
return binary_data

View File

@@ -99,9 +99,9 @@ class MenuService:
:return: 新增菜单校验结果
"""
if not await cls.check_menu_name_unique_services(query_db, page_object):
raise ServiceException(message=f'新增菜单{page_object.post_name}失败,菜单名称已存在')
raise ServiceException(message=f'新增菜单{page_object.menu_name}失败,菜单名称已存在')
elif page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path):
raise ServiceException(message=f'新增菜单{page_object.post_name}失败地址必须以http(s)://开头')
raise ServiceException(message=f'新增菜单{page_object.menu_name}失败地址必须以http(s)://开头')
else:
try:
await MenuDao.add_menu_dao(query_db, page_object)
@@ -124,11 +124,11 @@ class MenuService:
menu_info = await cls.menu_detail_services(query_db, page_object.menu_id)
if menu_info.menu_id:
if not await cls.check_menu_name_unique_services(query_db, page_object):
raise ServiceException(message=f'修改菜单{page_object.post_name}失败,菜单名称已存在')
raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,菜单名称已存在')
elif page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path):
raise ServiceException(message=f'修改菜单{page_object.post_name}失败地址必须以http(s)://开头')
raise ServiceException(message=f'修改菜单{page_object.menu_name}失败地址必须以http(s)://开头')
elif page_object.menu_id == page_object.parent_id:
raise ServiceException(message=f'修改菜单{page_object.post_name}失败,上级菜单不能选择自己')
raise ServiceException(message=f'修改菜单{page_object.menu_name}失败,上级菜单不能选择自己')
else:
try:
await MenuDao.edit_menu_dao(query_db, edit_menu)

View File

@@ -5,7 +5,8 @@ from exceptions.exception import ServiceException
from module_admin.dao.post_dao import PostDao
from module_admin.entity.vo.common_vo import CrudResponseModel
from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel
from utils.common_util import CamelCaseUtil, export_list2excel
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
class PostService:
@@ -172,16 +173,11 @@ class PostService:
'remark': '备注',
}
data = post_list
for item in data:
for item in post_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '停用'
new_data = [
{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data
]
binary_data = export_list2excel(new_data)
binary_data = ExcelUtil.export_list2excel(post_list, mapping_dict)
return binary_data

View File

@@ -15,7 +15,8 @@ from module_admin.entity.vo.role_vo import (
from module_admin.entity.vo.user_vo import UserInfoModel, UserRolePageQueryModel
from module_admin.dao.role_dao import RoleDao
from module_admin.dao.user_dao import UserDao
from utils.common_util import CamelCaseUtil, export_list2excel
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
from utils.page_util import PageResponseModel
@@ -295,17 +296,12 @@ class RoleService:
'remark': '备注',
}
data = role_list
for item in data:
for item in role_list:
if item.get('status') == '0':
item['status'] = '正常'
else:
item['status'] = '停用'
new_data = [
{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data
]
binary_data = export_list2excel(new_data)
binary_data = ExcelUtil.export_list2excel(role_list, mapping_dict)
return binary_data

View File

@@ -31,7 +31,8 @@ from module_admin.service.config_service import ConfigService
from module_admin.service.dept_service import DeptService
from module_admin.service.post_service import PostService
from module_admin.service.role_service import RoleService
from utils.common_util import CamelCaseUtil, export_list2excel, get_excel_template
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
from utils.page_util import PageResponseModel
from utils.pwd_util import PwdUtil
@@ -461,7 +462,7 @@ class UserService:
header_list = ['部门编号', '登录名称', '用户名称', '用户邮箱', '手机号码', '用户性别', '帐号状态']
selector_header_list = ['用户性别', '帐号状态']
option_list = [{'用户性别': ['', '', '未知']}, {'帐号状态': ['正常', '停用']}]
binary_data = get_excel_template(
binary_data = ExcelUtil.get_excel_template(
header_list=header_list, selector_header_list=selector_header_list, option_list=option_list
)
@@ -492,9 +493,8 @@ class UserService:
'remark': '备注',
}
data = user_list
for item in data:
for item in user_list:
item['deptName'] = item.get('dept').get('deptName')
if item.get('status') == '0':
item['status'] = '正常'
else:
@@ -505,10 +505,7 @@ class UserService:
item['sex'] = ''
else:
item['sex'] = '未知'
new_data = [
{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data
]
binary_data = export_list2excel(new_data)
binary_data = ExcelUtil.export_list2excel(user_list, mapping_dict)
return binary_data

View File

@@ -0,0 +1,158 @@
from datetime import datetime
from fastapi import APIRouter, Depends, Query, Request
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from config.enums import BusinessType
from config.env import GenConfig
from config.get_db import get_db
from module_admin.annotation.log_annotation import Log
from module_admin.aspect.interface_auth import CheckRoleInterfaceAuth, CheckUserInterfaceAuth
from module_admin.service.login_service import LoginService
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_generator.entity.vo.gen_vo import DeleteGenTableModel, EditGenTableModel, GenTablePageQueryModel
from module_generator.service.gen_service import GenTableColumnService, GenTableService
from utils.common_util import bytes2file_response
from utils.log_util import logger
from utils.page_util import PageResponseModel
from utils.response_util import ResponseUtil
genController = APIRouter(prefix='/tool/gen', dependencies=[Depends(LoginService.get_current_user)])
@genController.get(
'/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))]
)
async def get_gen_table_list(
request: Request,
gen_page_query: GenTablePageQueryModel = Depends(GenTablePageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据
gen_page_query_result = await GenTableService.get_gen_table_list_services(query_db, gen_page_query, is_page=True)
logger.info('获取成功')
return ResponseUtil.success(model_content=gen_page_query_result)
@genController.get(
'/db/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))]
)
async def get_gen_db_table_list(
request: Request,
gen_page_query: GenTablePageQueryModel = Depends(GenTablePageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
# 获取分页数据
gen_page_query_result = await GenTableService.get_gen_db_table_list_services(query_db, gen_page_query, is_page=True)
logger.info('获取成功')
return ResponseUtil.success(model_content=gen_page_query_result)
@genController.post('/importTable', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:import'))])
@Log(title='代码生成', business_type=BusinessType.IMPORT)
async def import_gen_table(
request: Request,
tables: str = Query(),
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
table_names = tables.split(',') if tables else []
add_gen_table_list = await GenTableService.get_gen_db_table_list_by_name_services(query_db, table_names)
add_gen_table_result = await GenTableService.import_gen_table_services(query_db, add_gen_table_list, current_user)
logger.info(add_gen_table_result.message)
return ResponseUtil.success(msg=add_gen_table_result.message)
@genController.put('', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))])
@ValidateFields(validate_model='edit_gen_table')
@Log(title='代码生成', business_type=BusinessType.UPDATE)
async def edit_gen_table(
request: Request,
edit_gen_table: EditGenTableModel,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
edit_gen_table.update_by = current_user.user.user_name
edit_gen_table.update_time = datetime.now()
await GenTableService.validate_edit(edit_gen_table)
edit_gen_result = await GenTableService.edit_gen_table_services(query_db, edit_gen_table)
logger.info(edit_gen_result.message)
return ResponseUtil.success(msg=edit_gen_result.message)
@genController.delete('/{table_ids}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:remove'))])
@Log(title='代码生成', business_type=BusinessType.DELETE)
async def delete_gen_table(request: Request, table_ids: str, query_db: AsyncSession = Depends(get_db)):
delete_gen_table = DeleteGenTableModel(tableIds=table_ids)
delete_gen_table_result = await GenTableService.delete_gen_table_services(query_db, delete_gen_table)
logger.info(delete_gen_table_result.message)
return ResponseUtil.success(msg=delete_gen_table_result.message)
@genController.post('/createTable', dependencies=[Depends(CheckRoleInterfaceAuth('admin'))])
@Log(title='创建表', business_type=BusinessType.OTHER)
async def create_table(
request: Request,
sql: str = Query(),
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
create_table_result = await GenTableService.create_table_services(query_db, sql, current_user)
logger.info(create_table_result.message)
return ResponseUtil.success(msg=create_table_result.message)
@genController.get('/batchGenCode', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))])
@Log(title='代码生成', business_type=BusinessType.GENCODE)
async def batch_gen_code(request: Request, tables: str = Query(), query_db: AsyncSession = Depends(get_db)):
table_names = tables.split(',') if tables else []
batch_gen_code_result = await GenTableService.batch_gen_code_services(query_db, table_names)
logger.info('生成代码成功')
return ResponseUtil.streaming(data=bytes2file_response(batch_gen_code_result))
@genController.get('/genCode/{table_name}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))])
@Log(title='代码生成', business_type=BusinessType.GENCODE)
async def gen_code_local(request: Request, table_name: str, query_db: AsyncSession = Depends(get_db)):
if not GenConfig.allow_overwrite:
logger.error('【系统预设】不允许生成文件覆盖到本地')
return ResponseUtil.error('【系统预设】不允许生成文件覆盖到本地')
gen_code_local_result = await GenTableService.generate_code_services(query_db, table_name)
logger.info(gen_code_local_result.message)
return ResponseUtil.success(msg=gen_code_local_result.message)
@genController.get('/{table_id}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:query'))])
async def query_detail_gen_table(request: Request, table_id: int, query_db: AsyncSession = Depends(get_db)):
gen_table = await GenTableService.get_gen_table_by_id_services(query_db, table_id)
gen_tables = await GenTableService.get_gen_table_all_services(query_db)
gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_services(query_db, table_id)
gen_table_detail_result = dict(info=gen_table, rows=gen_columns, tables=gen_tables)
logger.info(f'获取table_id为{table_id}的信息成功')
return ResponseUtil.success(data=gen_table_detail_result)
@genController.get('/preview/{table_id}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:preview'))])
async def preview_code(request: Request, table_id: int, query_db: AsyncSession = Depends(get_db)):
preview_code_result = await GenTableService.preview_code_services(query_db, table_id)
logger.info('获取预览代码成功')
return ResponseUtil.success(data=preview_code_result)
@genController.get('/synchDb/{table_name}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))])
@Log(title='代码生成', business_type=BusinessType.UPDATE)
async def sync_db(request: Request, table_name: str, query_db: AsyncSession = Depends(get_db)):
sync_db_result = await GenTableService.sync_db_services(query_db, table_name)
logger.info(sync_db_result.message)
return ResponseUtil.success(data=sync_db_result.message)

View File

@@ -0,0 +1,393 @@
from datetime import datetime, time
from sqlalchemy import delete, func, select, text, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from sqlglot.expressions import Expression
from typing import List
from config.env import DataBaseConfig
from module_generator.entity.do.gen_do import GenTable, GenTableColumn
from module_generator.entity.vo.gen_vo import (
GenTableBaseModel,
GenTableColumnBaseModel,
GenTableColumnModel,
GenTableModel,
GenTablePageQueryModel,
)
from utils.page_util import PageUtil
class GenTableDao:
"""
代码生成业务表模块数据库操作层
"""
@classmethod
async def get_gen_table_by_id(cls, db: AsyncSession, table_id: int):
"""
根据业务表id获取需要生成的业务表信息
:param db: orm对象
:param table_id: 业务表id
:return: 需要生成的业务表信息对象
"""
gen_table_info = (
(
await db.execute(
select(GenTable).options(selectinload(GenTable.columns)).where(GenTable.table_id == table_id)
)
)
.scalars()
.first()
)
return gen_table_info
@classmethod
async def get_gen_table_by_name(cls, db: AsyncSession, table_name: str):
"""
根据业务表名称获取需要生成的业务表信息
:param db: orm对象
:param table_name: 业务表名称
:return: 需要生成的业务表信息对象
"""
gen_table_info = (
(
await db.execute(
select(GenTable).options(selectinload(GenTable.columns)).where(GenTable.table_name == table_name)
)
)
.scalars()
.first()
)
return gen_table_info
@classmethod
async def get_gen_table_all(cls, db: AsyncSession):
"""
获取所有业务表信息
:param db: orm对象
:return: 所有业务表信息
"""
gen_table_all = (await db.execute(select(GenTable).options(selectinload(GenTable.columns)))).scalars().all()
return gen_table_all
@classmethod
async def create_table_by_sql_dao(cls, db: AsyncSession, sql_statements: List[Expression]):
"""
根据sql语句创建表结构
:param db: orm对象
:param sql_statements: sql语句的ast列表
:return:
"""
for sql_statement in sql_statements:
sql = sql_statement.sql(dialect=DataBaseConfig.sqlglot_parse_dialect)
await db.execute(text(sql))
@classmethod
async def get_gen_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False):
"""
根据查询参数获取代码生成业务表列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 代码生成业务表列表信息对象
"""
query = (
select(GenTable)
.options(selectinload(GenTable.columns))
.where(
func.lower(GenTable.table_name).like(f'%{query_object.table_name.lower()}%')
if query_object.table_name
else True,
func.lower(GenTable.table_comment).like(f'%{query_object.table_comment.lower()}%')
if query_object.table_comment
else True,
GenTable.create_time.between(
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
)
if query_object.begin_time and query_object.end_time
else True,
)
.distinct()
)
gen_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
return gen_table_list
@classmethod
async def get_gen_db_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False):
"""
根据查询参数获取数据库列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 数据库列表信息对象
"""
if DataBaseConfig.db_type == 'postgresql':
query_sql = """
table_name as table_name,
table_comment as table_comment,
create_time as create_time,
update_time as update_time
from
list_table
where
table_name not like 'apscheduler_%'
and table_name not like 'gen_%'
and table_name not in (select table_name from gen_table)
"""
else:
query_sql = """
table_name as table_name,
table_comment as table_comment,
create_time as create_time,
update_time as update_time
from
information_schema.tables
where
table_schema = (select database())
and table_name not like 'apscheduler\_%'
and table_name not like 'gen\_%'
and table_name not in (select table_name from gen_table)
"""
if query_object.table_name:
query_sql += """and lower(table_name) like lower(concat('%', :table_name, '%'))"""
if query_object.table_comment:
query_sql += """and lower(table_comment) like lower(concat('%', :table_comment, '%'))"""
if query_object.begin_time:
if DataBaseConfig.db_type == 'postgresql':
query_sql += """and create_time::date >= to_date(:begin_time, 'yyyy-MM-dd')"""
else:
query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:begin_time, '%Y%m%d')"""
if query_object.end_time:
if DataBaseConfig.db_type == 'postgresql':
query_sql += """and create_time::date <= to_date(:end_time, 'yyyy-MM-dd')"""
else:
query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:end_time, '%Y%m%d')"""
query_sql += """order by create_time desc"""
query = select(
text(query_sql).bindparams(
**{
k: v
for k, v in query_object.model_dump(exclude_none=True, exclude={'page_num', 'page_size'}).items()
}
)
)
gen_db_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
return gen_db_table_list
@classmethod
async def get_gen_db_table_list_by_names(cls, db: AsyncSession, table_names: List[str]):
"""
根据业务表名称组获取数据库列表信息
:param db: orm对象
:param table_names: 业务表名称组
:return: 数据库列表信息对象
"""
if DataBaseConfig.db_type == 'postgresql':
query_sql = """
select
table_name as table_name,
table_comment as table_comment,
create_time as create_time,
update_time as update_time
from
list_table
where
table_name not like 'qrtz_%'
and table_name not like 'gen_%'
and table_name = any(:table_names)
"""
else:
query_sql = """
select
table_name as table_name,
table_comment as table_comment,
create_time as create_time,
update_time as update_time
from
information_schema.tables
where
table_name not like 'qrtz\_%'
and table_name not like 'gen\_%'
and table_schema = (select database())
and table_name in :table_names
"""
query = text(query_sql).bindparams(table_names=tuple(table_names))
gen_db_table_list = (await db.execute(query)).fetchall()
return gen_db_table_list
@classmethod
async def add_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel):
"""
新增业务表数据库操作
:param db: orm对象
:param gen_table: 业务表对象
:return:
"""
db_gen_table = GenTable(**GenTableBaseModel(**gen_table.model_dump(by_alias=True)).model_dump())
db.add(db_gen_table)
await db.flush()
return db_gen_table
@classmethod
async def edit_gen_table_dao(cls, db: AsyncSession, gen_table: dict):
"""
编辑业务表数据库操作
:param db: orm对象
:param gen_table: 需要更新的业务表字典
:return:
"""
await db.execute(update(GenTable), [GenTableBaseModel(**gen_table).model_dump()])
@classmethod
async def delete_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel):
"""
删除业务表数据库操作
:param db: orm对象
:param gen_table: 业务表对象
:return:
"""
await db.execute(delete(GenTable).where(GenTable.table_id.in_([gen_table.table_id])))
class GenTableColumnDao:
"""
代码生成业务表字段模块数据库操作层
"""
@classmethod
async def get_gen_table_column_list_by_table_id(cls, db: AsyncSession, table_id: int):
"""
根据业务表id获取需要生成的业务表字段列表信息
:param db: orm对象
:param table_id: 业务表id
:return: 需要生成的业务表字段列表信息对象
"""
gen_table_column_list = (
(
await db.execute(
select(GenTableColumn).where(GenTableColumn.table_id == table_id).order_by(GenTableColumn.sort)
)
)
.scalars()
.all()
)
return gen_table_column_list
@classmethod
async def get_gen_db_table_columns_by_name(cls, db: AsyncSession, table_name: str):
"""
根据业务表名称获取业务表字段列表信息
:param db: orm对象
:param table_name: 业务表名称
:return: 业务表字段列表信息对象
"""
if DataBaseConfig.db_type == 'postgresql':
query_sql = """
select
column_name, is_required, is_pk, sort, column_comment, is_increment, column_type
from
list_column
where
table_name = :table_name
"""
else:
query_sql = """
select
column_name as column_name,
case
when is_nullable = 'no' and column_key != 'PRI' then '1'
else '0'
end as is_required,
case
when column_key = 'PRI' then '1'
else '0'
end as is_pk,
ordinal_position as sort,
column_comment as column_comment,
case
when extra = 'auto_increment' then '1'
else '0'
end as is_increment,
column_type as column_type
from
information_schema.columns
where
table_schema = (select database())
and table_name = :table_name
order by
ordinal_position
"""
query = text(query_sql).bindparams(table_name=table_name)
gen_db_table_columns = (await db.execute(query)).fetchall()
return gen_db_table_columns
@classmethod
async def add_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel):
"""
新增业务表字段数据库操作
:param db: orm对象
:param gen_table_column: 岗位对象
:return:
"""
db_gen_table_column = GenTableColumn(
**GenTableColumnBaseModel(**gen_table_column.model_dump(by_alias=True)).model_dump()
)
db.add(db_gen_table_column)
await db.flush()
return db_gen_table_column
@classmethod
async def edit_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: dict):
"""
编辑业务表字段数据库操作
:param db: orm对象
:param gen_table_column: 需要更新的业务表字段字典
:return:
"""
await db.execute(update(GenTableColumn), [GenTableColumnBaseModel(**gen_table_column).model_dump()])
@classmethod
async def delete_gen_table_column_by_table_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel):
"""
通过业务表id删除业务表字段数据库操作
:param db: orm对象
:param gen_table_column: 业务表字段对象
:return:
"""
await db.execute(delete(GenTableColumn).where(GenTableColumn.table_id.in_([gen_table_column.table_id])))
@classmethod
async def delete_gen_table_column_by_column_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel):
"""
通过业务字段id删除业务表字段数据库操作
:param db: orm对象
:param post: 业务表字段对象
:return:
"""
await db.execute(delete(GenTableColumn).where(GenTableColumn.column_id.in_([gen_table_column.column_id])))

View File

@@ -0,0 +1,74 @@
from datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from config.database import Base
class GenTable(Base):
"""
代码生成业务表
"""
__tablename__ = 'gen_table'
table_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号')
table_name = Column(String(200), nullable=True, default='', comment='表名称')
table_comment = Column(String(500), nullable=True, default='', comment='表描述')
sub_table_name = Column(String(64), nullable=True, comment='关联子表的表名')
sub_table_fk_name = Column(String(64), nullable=True, comment='子表关联的外键名')
class_name = Column(String(100), nullable=True, default='', comment='实体类名称')
tpl_category = Column(String(200), nullable=True, default='crud', comment='使用的模板crud单表操作 tree树表操作')
tpl_web_type = Column(
String(30), nullable=True, default='', comment='前端模板类型element-ui模版 element-plus模版'
)
package_name = Column(String(100), nullable=True, comment='生成包路径')
module_name = Column(String(30), nullable=True, comment='生成模块名')
business_name = Column(String(30), nullable=True, comment='生成业务名')
function_name = Column(String(100), nullable=True, comment='生成功能名')
function_author = Column(String(100), nullable=True, comment='生成功能作者')
gen_type = Column(String(1), nullable=True, default='0', comment='生成代码方式0zip压缩包 1自定义路径')
gen_path = Column(String(200), nullable=True, default='/', comment='生成路径(不填默认项目路径)')
options = Column(String(1000), nullable=True, comment='其它生成选项')
create_by = Column(String(64), default='', comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), default='', comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
remark = Column(String(500), nullable=True, default=None, comment='备注')
columns = relationship('GenTableColumn', order_by='GenTableColumn.sort', back_populates='tables')
class GenTableColumn(Base):
"""
代码生成业务表字段
"""
__tablename__ = 'gen_table_column'
column_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号')
table_id = Column(Integer, ForeignKey('gen_table.table_id'), nullable=True, comment='归属表编号')
column_name = Column(String(200), nullable=True, comment='列名称')
column_comment = Column(String(500), nullable=True, comment='列描述')
column_type = Column(String(100), nullable=True, comment='列类型')
python_type = Column(String(500), nullable=True, comment='PYTHON类型')
python_field = Column(String(200), nullable=True, comment='PYTHON字段名')
is_pk = Column(String(1), nullable=True, comment='是否主键1是')
is_increment = Column(String(1), nullable=True, comment='是否自增1是')
is_required = Column(String(1), nullable=True, comment='是否必填1是')
is_unique = Column(String(1), nullable=True, comment='是否唯一1是')
is_insert = Column(String(1), nullable=True, comment='是否为插入字段1是')
is_edit = Column(String(1), nullable=True, comment='是否编辑字段1是')
is_list = Column(String(1), nullable=True, comment='是否列表字段1是')
is_query = Column(String(1), nullable=True, comment='是否查询字段1是')
query_type = Column(String(200), nullable=True, default='EQ', comment='查询方式(等于、不等于、大于、小于、范围)')
html_type = Column(
String(200), nullable=True, comment='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)'
)
dict_type = Column(String(200), nullable=True, default='', comment='字典类型')
sort = Column(Integer, nullable=True, comment='排序')
create_by = Column(String(64), default='', comment='创建者')
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
update_by = Column(String(64), default='', comment='更新者')
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
tables = relationship('GenTable', back_populates='columns')

View File

@@ -0,0 +1,264 @@
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field, model_validator
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank
from typing import List, Literal, Optional
from config.constant import GenConstant
from module_admin.annotation.pydantic_annotation import as_query
from utils.string_util import StringUtil
class GenTableBaseModel(BaseModel):
"""
代码生成业务表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
table_id: Optional[int] = Field(default=None, description='编号')
table_name: Optional[str] = Field(default=None, description='表名称')
table_comment: Optional[str] = Field(default=None, description='表描述')
sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名')
sub_table_fk_name: Optional[str] = Field(default=None, description='子表关联的外键名')
class_name: Optional[str] = Field(default=None, description='实体类名称')
tpl_category: Optional[str] = Field(default=None, description='使用的模板crud单表操作 tree树表操作')
tpl_web_type: Optional[str] = Field(default=None, description='前端模板类型element-ui模版 element-plus模版')
package_name: Optional[str] = Field(default=None, description='生成包路径')
module_name: Optional[str] = Field(default=None, description='生成模块名')
business_name: Optional[str] = Field(default=None, description='生成业务名')
function_name: Optional[str] = Field(default=None, description='生成功能名')
function_author: Optional[str] = Field(default=None, description='生成功能作者')
gen_type: Optional[Literal['0', '1']] = Field(default=None, description='生成代码方式0zip压缩包 1自定义路径')
gen_path: Optional[str] = Field(default=None, description='生成路径(不填默认项目路径)')
options: Optional[str] = Field(default=None, description='其它生成选项')
create_by: Optional[str] = Field(default=None, description='创建者')
create_time: Optional[datetime] = Field(default=None, description='创建时间')
update_by: Optional[str] = Field(default=None, description='更新者')
update_time: Optional[datetime] = Field(default=None, description='更新时间')
remark: Optional[str] = Field(default=None, description='备注')
@NotBlank(field_name='table_name', message='表名称不能为空')
def get_table_name(self):
return self.table_name
@NotBlank(field_name='table_comment', message='表描述不能为空')
def get_table_comment(self):
return self.table_comment
@NotBlank(field_name='class_name', message='实体类名称不能为空')
def get_class_name(self):
return self.class_name
@NotBlank(field_name='package_name', message='生成包路径不能为空')
def get_package_name(self):
return self.package_name
@NotBlank(field_name='module_name', message='生成模块名不能为空')
def get_module_name(self):
return self.module_name
@NotBlank(field_name='business_name', message='生成业务名不能为空')
def get_business_name(self):
return self.business_name
@NotBlank(field_name='function_name', message='生成功能名不能为空')
def get_function_name(self):
return self.function_name
@NotBlank(field_name='function_author', message='生成功能作者不能为空')
def get_function_author(self):
return self.function_author
def validate_fields(self):
self.get_table_name()
self.get_table_comment()
self.get_class_name()
self.get_package_name()
self.get_module_name()
self.get_business_name()
self.get_function_name()
self.get_function_author()
class GenTableModel(GenTableBaseModel):
"""
代码生成业务表模型
"""
pk_column: Optional['GenTableColumnModel'] = Field(default=None, description='主键信息')
sub_table: Optional['GenTableModel'] = Field(default=None, description='子表信息')
columns: Optional[List['GenTableColumnModel']] = Field(default=None, description='表列信息')
tree_code: Optional[str] = Field(default=None, description='树编码字段')
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
tree_name: Optional[str] = Field(default=None, description='树名称字段')
parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段')
parent_menu_name: Optional[str] = Field(default=None, description='上级菜单名称字段')
sub: Optional[bool] = Field(default=None, description='是否为子表')
tree: Optional[bool] = Field(default=None, description='是否为树表')
crud: Optional[bool] = Field(default=None, description='是否为单表')
@model_validator(mode='after')
def check_some_is(self) -> 'GenTableModel':
self.sub = True if self.tpl_category and self.tpl_category == GenConstant.TPL_SUB else False
self.tree = True if self.tpl_category and self.tpl_category == GenConstant.TPL_TREE else False
self.crud = True if self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD else False
return self
class EditGenTableModel(GenTableModel):
"""
修改代码生成业务表模型
"""
params: Optional['GenTableParamsModel'] = Field(default=None, description='业务表参数')
class GenTableParamsModel(BaseModel):
"""
代码生成业务表参数模型
"""
model_config = ConfigDict(alias_generator=to_camel)
tree_code: Optional[str] = Field(default=None, description='树编码字段')
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
tree_name: Optional[str] = Field(default=None, description='树名称字段')
parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段')
class GenTableQueryModel(GenTableBaseModel):
"""
代码生成业务表不分页查询模型
"""
begin_time: Optional[str] = Field(default=None, description='开始时间')
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class GenTablePageQueryModel(GenTableQueryModel):
"""
代码生成业务表分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class DeleteGenTableModel(BaseModel):
"""
删除代码生成业务表模型
"""
model_config = ConfigDict(alias_generator=to_camel)
table_ids: str = Field(description='需要删除的代码生成业务表ID')
class GenTableColumnBaseModel(BaseModel):
"""
代码生成业务表字段对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
column_id: Optional[int] = Field(default=None, description='编号')
table_id: Optional[int] = Field(default=None, description='归属表编号')
column_name: Optional[str] = Field(default=None, description='列名称')
column_comment: Optional[str] = Field(default=None, description='列描述')
column_type: Optional[str] = Field(default=None, description='列类型')
python_type: Optional[str] = Field(default=None, description='PYTHON类型')
python_field: Optional[str] = Field(default=None, description='PYTHON字段名')
is_pk: Optional[str] = Field(default=None, description='是否主键1是')
is_increment: Optional[str] = Field(default=None, description='是否自增1是')
is_required: Optional[str] = Field(default=None, description='是否必填1是')
is_unique: Optional[str] = Field(default=None, description='是否唯一1是')
is_insert: Optional[str] = Field(default=None, description='是否为插入字段1是')
is_edit: Optional[str] = Field(default=None, description='是否编辑字段1是')
is_list: Optional[str] = Field(default=None, description='是否列表字段1是')
is_query: Optional[str] = Field(default=None, description='是否查询字段1是')
query_type: Optional[str] = Field(default=None, description='查询方式(等于、不等于、大于、小于、范围)')
html_type: Optional[str] = Field(
default=None, description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)'
)
dict_type: Optional[str] = Field(default=None, description='字典类型')
sort: Optional[int] = Field(default=None, description='排序')
create_by: Optional[str] = Field(default=None, description='创建者')
create_time: Optional[datetime] = Field(default=None, description='创建时间')
update_by: Optional[str] = Field(default=None, description='更新者')
update_time: Optional[datetime] = Field(default=None, description='更新时间')
@NotBlank(field_name='python_field', message='Python属性不能为空')
def get_python_field(self):
return self.python_field
def validate_fields(self):
self.get_python_field()
class GenTableColumnModel(GenTableColumnBaseModel):
"""
代码生成业务表字段模型
"""
cap_python_field: Optional[str] = Field(default=None, description='字段大写形式')
pk: Optional[bool] = Field(default=None, description='是否主键')
increment: Optional[bool] = Field(default=None, description='是否自增')
required: Optional[bool] = Field(default=None, description='是否必填')
unique: Optional[bool] = Field(default=None, description='是否唯一')
insert: Optional[bool] = Field(default=None, description='是否为插入字段')
edit: Optional[bool] = Field(default=None, description='是否编辑字段')
list: Optional[bool] = Field(default=None, description='是否列表字段')
query: Optional[bool] = Field(default=None, description='是否查询字段')
super_column: Optional[bool] = Field(default=None, description='是否为基类字段')
usable_column: Optional[bool] = Field(default=None, description='是否为基类字段白名单')
@model_validator(mode='after')
def check_some_is(self) -> 'GenTableModel':
self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None
self.pk = True if self.is_pk and self.is_pk == '1' else False
self.increment = True if self.is_increment and self.is_increment == '1' else False
self.required = True if self.is_required and self.is_required == '1' else False
self.unique = True if self.is_unique and self.is_unique == '1' else False
self.insert = True if self.is_insert and self.is_insert == '1' else False
self.edit = True if self.is_edit and self.is_edit == '1' else False
self.list = True if self.is_list and self.is_list == '1' else False
self.query = True if self.is_query and self.is_query == '1' else False
self.super_column = (
True
if StringUtil.equals_any_ignore_case(self.python_field, GenConstant.TREE_ENTITY + GenConstant.BASE_ENTITY)
else False
)
self.usable_column = (
True if StringUtil.equals_any_ignore_case(self.python_field, ['parentId', 'orderNum', 'remark']) else False
)
return self
class GenTableColumnQueryModel(GenTableColumnBaseModel):
"""
代码生成业务表字段不分页查询模型
"""
begin_time: Optional[str] = Field(default=None, description='开始时间')
end_time: Optional[str] = Field(default=None, description='结束时间')
@as_query
class GenTableColumnPageQueryModel(GenTableColumnQueryModel):
"""
代码生成业务表字段分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class DeleteGenTableColumnModel(BaseModel):
"""
删除代码生成业务表字段模型
"""
model_config = ConfigDict(alias_generator=to_camel)
column_ids: str = Field(description='需要删除的代码生成业务表字段ID')

View File

@@ -0,0 +1,499 @@
import io
import json
import os
import zipfile
from datetime import datetime
from sqlalchemy.ext.asyncio import AsyncSession
from sqlglot import parse as sqlglot_parse
from sqlglot.expressions import Add, Alter, Create, Delete, Drop, Expression, Insert, Table, TruncateTable, Update
from typing import List
from config.constant import GenConstant
from config.env import DataBaseConfig, GenConfig
from exceptions.exception import ServiceException
from module_admin.entity.vo.common_vo import CrudResponseModel
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_generator.entity.vo.gen_vo import (
DeleteGenTableModel,
EditGenTableModel,
GenTableColumnModel,
GenTableModel,
GenTablePageQueryModel,
)
from module_generator.dao.gen_dao import GenTableColumnDao, GenTableDao
from utils.common_util import CamelCaseUtil
from utils.gen_util import GenUtils
from utils.template_util import TemplateInitializer, TemplateUtils
class GenTableService:
"""
代码生成业务表服务层
"""
@classmethod
async def get_gen_table_list_services(
cls, query_db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False
):
"""
获取代码生成业务表列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 代码生成业务列表信息对象
"""
gen_table_list_result = await GenTableDao.get_gen_table_list(query_db, query_object, is_page)
return gen_table_list_result
@classmethod
async def get_gen_db_table_list_services(
cls, query_db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False
):
"""
获取数据库列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 数据库列表信息对象
"""
gen_db_table_list_result = await GenTableDao.get_gen_db_table_list(query_db, query_object, is_page)
return gen_db_table_list_result
@classmethod
async def get_gen_db_table_list_by_name_services(cls, query_db: AsyncSession, table_names: List[str]):
"""
根据表名称组获取数据库列表信息service
:param query_db: orm对象
:param table_names: 表名称组
:return: 数据库列表信息对象
"""
gen_db_table_list_result = await GenTableDao.get_gen_db_table_list_by_names(query_db, table_names)
return [GenTableModel(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_db_table_list_result)]
@classmethod
async def import_gen_table_services(
cls, query_db: AsyncSession, gen_table_list: List[GenTableModel], current_user: CurrentUserModel
):
"""
导入表结构service
:param query_db: orm对象
:param gen_table_list: 导入表列表
:param current_user: 当前用户信息对象
:return: 导入结果
"""
try:
for table in gen_table_list:
table_name = table.table_name
GenUtils.init_table(table, current_user.user.user_name)
add_gen_table = await GenTableDao.add_gen_table_dao(query_db, table)
if add_gen_table:
table.table_id = add_gen_table.table_id
gen_table_columns = await GenTableColumnDao.get_gen_db_table_columns_by_name(query_db, table_name)
for column in [
GenTableColumnModel(**gen_table_column)
for gen_table_column in CamelCaseUtil.transform_result(gen_table_columns)
]:
GenUtils.init_column_field(column, table)
await GenTableColumnDao.add_gen_table_column_dao(query_db, column)
await query_db.commit()
return CrudResponseModel(is_success=True, message='导入成功')
except Exception as e:
await query_db.rollback()
raise ServiceException(message=f'导入失败, {str(e)}')
@classmethod
async def edit_gen_table_services(cls, query_db: AsyncSession, page_object: EditGenTableModel):
"""
编辑业务表信息service
:param query_db: orm对象
:param page_object: 编辑业务表对象
:return: 编辑业务表校验结果
"""
edit_gen_table = page_object.model_dump(exclude_unset=True, by_alias=True)
gen_table_info = await cls.get_gen_table_by_id_services(query_db, page_object.table_id)
if gen_table_info.table_id:
try:
edit_gen_table['options'] = json.dumps(edit_gen_table.get('params'))
await GenTableDao.edit_gen_table_dao(query_db, edit_gen_table)
for gen_table_column in page_object.columns:
gen_table_column.update_by = page_object.update_by
gen_table_column.update_time = datetime.now()
await GenTableColumnDao.edit_gen_table_column_dao(
query_db, gen_table_column.model_dump(by_alias=True)
)
await query_db.commit()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='业务表不存在')
@classmethod
async def delete_gen_table_services(cls, query_db: AsyncSession, page_object: DeleteGenTableModel):
"""
删除业务表信息service
:param query_db: orm对象
:param page_object: 删除业务表对象
:return: 删除业务表校验结果
"""
if page_object.table_ids:
table_id_list = page_object.table_ids.split(',')
try:
for table_id in table_id_list:
await GenTableDao.delete_gen_table_dao(query_db, GenTableModel(tableId=table_id))
await GenTableColumnDao.delete_gen_table_column_by_table_id_dao(
query_db, GenTableColumnModel(tableId=table_id)
)
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入业务表id为空')
@classmethod
async def get_gen_table_by_id_services(cls, query_db: AsyncSession, table_id: int):
"""
获取需要生成的业务表详细信息service
:param query_db: orm对象
:param table_id: 需要生成的业务表id
:return: 需要生成的业务表id对应的信息
"""
gen_table = await GenTableDao.get_gen_table_by_id(query_db, table_id)
result = await cls.set_table_from_options(GenTableModel(**CamelCaseUtil.transform_result(gen_table)))
return result
@classmethod
async def get_gen_table_all_services(cls, query_db: AsyncSession):
"""
获取所有业务表信息service
:param query_db: orm对象
:return: 所有业务表信息
"""
gen_table_all = await GenTableDao.get_gen_table_all(query_db)
result = [GenTableModel(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_table_all)]
return result
@classmethod
async def create_table_services(cls, query_db: AsyncSession, sql: str, current_user: CurrentUserModel):
"""
创建表结构service
:param query_db: orm对象
:param sql: 建表语句
:param current_user: 当前用户信息对象
:return: 创建表结构结果
"""
sql_statements = sqlglot_parse(sql, dialect=DataBaseConfig.sqlglot_parse_dialect)
if cls.__is_valid_create_table(sql_statements):
try:
table_names = cls.__get_table_names(sql_statements)
await GenTableDao.create_table_by_sql_dao(query_db, sql_statements)
gen_table_list = await cls.get_gen_db_table_list_by_name_services(query_db, table_names)
await cls.import_gen_table_services(query_db, gen_table_list, current_user)
return CrudResponseModel(is_success=True, message='创建表结构成功')
except Exception as e:
raise ServiceException(message=f'创建表结构异常,详细错误信息:{str(e)}')
else:
raise ServiceException(message='建表语句不合法')
@classmethod
def __is_valid_create_table(cls, sql_statements: List[Expression]):
"""
校验sql语句是否为合法的建表语句
:param sql_statements: sql语句的ast列表
:return: 校验结果
"""
validate_create = [isinstance(sql_statement, Create) for sql_statement in sql_statements]
validate_forbidden_keywords = [
isinstance(
sql_statement,
(Add, Alter, Delete, Drop, Insert, TruncateTable, Update),
)
for sql_statement in sql_statements
]
if not any(validate_create) or any(validate_forbidden_keywords):
return False
return True
@classmethod
def __get_table_names(cls, sql_statements: List[Expression]):
"""
获取sql语句中所有的建表表名
:param sql_statements: sql语句的ast列表
:return: 建表表名列表
"""
table_names = []
for sql_statement in sql_statements:
if isinstance(sql_statement, Create):
table_names.append(sql_statement.find(Table).name)
return table_names
@classmethod
async def preview_code_services(cls, query_db: AsyncSession, table_id: int):
"""
预览代码service
:param query_db: orm对象
:param table_id: 业务表id
:return: 预览数据列表
"""
gen_table = GenTableModel(
**CamelCaseUtil.transform_result(await GenTableDao.get_gen_table_by_id(query_db, table_id))
)
await cls.set_sub_table(query_db, gen_table)
await cls.set_pk_column(gen_table)
env = TemplateInitializer.init_jinja2()
context = TemplateUtils.prepare_context(gen_table)
template_list = TemplateUtils.get_template_list(gen_table.tpl_category, gen_table.tpl_web_type)
preview_code_result = {}
for template in template_list:
render_content = env.get_template(template).render(**context)
preview_code_result[template] = render_content
return preview_code_result
@classmethod
async def generate_code_services(cls, query_db: AsyncSession, table_name: str):
"""
生成代码至指定路径service
:param query_db: orm对象
:param table_name: 业务表名称
:return: 生成代码结果
"""
env = TemplateInitializer.init_jinja2()
render_info = await cls.__get_gen_render_info(query_db, table_name)
for template in render_info[0]:
try:
render_content = env.get_template(template).render(**render_info[2])
gen_path = cls.__get_gen_path(render_info[3], template)
os.makedirs(os.path.dirname(gen_path), exist_ok=True)
with open(gen_path, 'w', encoding='utf-8') as f:
f.write(render_content)
except Exception as e:
raise ServiceException(
message=f'渲染模板失败,表名:{render_info[3].table_name},详细错误信息:{str(e)}'
)
return CrudResponseModel(is_success=True, message='生成代码成功')
@classmethod
async def batch_gen_code_services(cls, query_db: AsyncSession, table_names: List[str]):
"""
批量生成代码service
:param query_db: orm对象
:param table_names: 业务表名称组
:return: 下载代码结果
"""
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for table_name in table_names:
env = TemplateInitializer.init_jinja2()
render_info = await cls.__get_gen_render_info(query_db, table_name)
for template_file, output_file in zip(render_info[0], render_info[1]):
render_content = env.get_template(template_file).render(**render_info[2])
zip_file.writestr(output_file, render_content)
zip_data = zip_buffer.getvalue()
zip_buffer.close()
return zip_data
@classmethod
async def __get_gen_render_info(cls, query_db: AsyncSession, table_name: str):
"""
获取生成代码渲染模板相关信息
:param query_db: orm对象
:param table_name: 业务表名称
:return: 生成代码渲染模板相关信息
"""
gen_table = GenTableModel(
**CamelCaseUtil.transform_result(await GenTableDao.get_gen_table_by_name(query_db, table_name))
)
await cls.set_sub_table(query_db, gen_table)
await cls.set_pk_column(gen_table)
context = TemplateUtils.prepare_context(gen_table)
template_list = TemplateUtils.get_template_list(gen_table.tpl_category, gen_table.tpl_web_type)
output_files = [TemplateUtils.get_file_name(template, gen_table) for template in template_list]
return [template_list, output_files, context, gen_table]
@classmethod
def __get_gen_path(cls, gen_table: GenTableModel, template: str):
"""
根据GenTableModel对象和模板名称生成路径
:param gen_table: GenTableModel对象
:param template: 模板名称
:return: 生成的路径
"""
gen_path = gen_table.gen_path
if gen_path == '/':
return os.path.join(os.getcwd(), GenConfig.GEN_PATH, TemplateUtils.get_file_name(template, gen_table))
else:
return os.path.join(gen_path, TemplateUtils.get_file_name(template, gen_table))
@classmethod
async def sync_db_services(cls, query_db: AsyncSession, table_name: str):
"""
同步数据库service
:param query_db: orm对象
:param table_name: 业务表名称
:return: 同步数据库结果
"""
gen_table = await GenTableDao.get_gen_table_by_name(query_db, table_name)
table = GenTableModel(**CamelCaseUtil.transform_result(gen_table))
table_columns = table.columns
table_column_map = {column.column_name: column for column in table_columns}
query_db_table_columns = await GenTableColumnDao.get_gen_db_table_columns_by_name(query_db, table_name)
db_table_columns = [
GenTableColumnModel(**column) for column in CamelCaseUtil.transform_result(query_db_table_columns)
]
if not db_table_columns:
raise ServiceException('同步数据失败,原表结构不存在')
db_table_column_names = [column.column_name for column in db_table_columns]
try:
for column in db_table_columns:
GenUtils.init_column_field(column, table)
if column.column_name in table_column_map:
prev_column = table_column_map[column.column_name]
column.column_id = prev_column.column_id
if column.list:
column.dict_type = prev_column.dict_type
column.query_type = prev_column.query_type
if (
prev_column.is_required != ''
and not column.pk
and (column.insert or column.edit)
and (column.usable_column or column.super_column)
):
column.is_required = prev_column.is_required
column.html_type = prev_column.html_type
await GenTableColumnDao.edit_gen_table_column_dao(query_db, column.model_dump(by_alias=True))
else:
await GenTableColumnDao.add_gen_table_column_dao(query_db, column)
del_columns = [column for column in table_columns if column.column_name not in db_table_column_names]
if del_columns:
for column in del_columns:
await GenTableColumnDao.delete_gen_table_column_by_column_id_dao(query_db, column)
await query_db.commit()
return CrudResponseModel(is_success=True, message='同步成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def set_sub_table(cls, query_db: AsyncSession, gen_table: GenTableModel):
"""
设置主子表信息
:param query_db: orm对象
:param gen_table: 业务表信息
:return:
"""
if gen_table.sub_table_name:
sub_table = await GenTableDao.get_gen_table_by_name(query_db, gen_table.sub_table_name)
gen_table.sub_table = GenTableModel(**CamelCaseUtil.transform_result(sub_table))
@classmethod
async def set_pk_column(cls, gen_table: GenTableModel):
"""
设置主键列信息
:param gen_table: 业务表信息
:return:
"""
for column in gen_table.columns:
if column.pk:
gen_table.pk_column = column
break
if gen_table.pk_column is None:
gen_table.pk_column = gen_table.columns[0]
if gen_table.tpl_category == GenConstant.TPL_SUB:
for column in gen_table.sub_table.columns:
if column.pk:
gen_table.sub_table.pk_column = column
break
if gen_table.sub_table.columns is None:
gen_table.sub_table.pk_column = gen_table.sub_table.columns[0]
@classmethod
async def set_table_from_options(cls, gen_table: GenTableModel):
"""
设置代码生成其他选项值
:param gen_table: 生成对象
:return: 设置后的生成对象
"""
params_obj = json.loads(gen_table.options) if gen_table.options else None
if params_obj:
gen_table.tree_code = params_obj.get(GenConstant.TREE_CODE)
gen_table.tree_parent_code = params_obj.get(GenConstant.TREE_PARENT_CODE)
gen_table.tree_name = params_obj.get(GenConstant.TREE_NAME)
gen_table.parent_menu_id = params_obj.get(GenConstant.PARENT_MENU_ID)
gen_table.parent_menu_name = params_obj.get(GenConstant.PARENT_MENU_NAME)
return gen_table
@classmethod
async def validate_edit(cls, edit_gen_table: EditGenTableModel):
"""
编辑保存参数校验
:param edit_gen_table: 编辑业务表对象
"""
if edit_gen_table.tpl_category == GenConstant.TPL_TREE:
params_obj = edit_gen_table.params.model_dump(by_alias=True)
if GenConstant.TREE_CODE not in params_obj:
raise ServiceException(message='树编码字段不能为空')
elif GenConstant.TREE_PARENT_CODE not in params_obj:
raise ServiceException(message='树父编码字段不能为空')
elif GenConstant.TREE_NAME not in params_obj:
raise ServiceException(message='树名称字段不能为空')
elif edit_gen_table.tpl_category == GenConstant.TPL_SUB:
if not edit_gen_table.sub_table_name:
raise ServiceException(message='关联子表的表名不能为空')
elif not edit_gen_table.sub_table_fk_name:
raise ServiceException(message='子表关联的外键名不能为空')
class GenTableColumnService:
"""
代码生成业务表字段服务层
"""
@classmethod
async def get_gen_table_column_list_by_table_id_services(cls, query_db: AsyncSession, table_id: int):
"""
获取业务表字段列表信息service
:param query_db: orm对象
:param table_id: 业务表格id
:return: 业务表字段列表信息对象
"""
gen_table_column_list_result = await GenTableColumnDao.get_gen_table_column_list_by_table_id(query_db, table_id)
return [
GenTableColumnModel(**gen_table_column)
for gen_table_column in CamelCaseUtil.transform_result(gen_table_column_list_result)
]

View File

@@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询{{ functionName }}列表
export function list{{ BusinessName }}(query) {
return request({
url: '/{{ moduleName }}/{{ businessName }}/list',
method: 'get',
params: query
})
}
// 查询{{ functionName }}详细
export function get{{ BusinessName }}({{ pkColumn.python_field }}) {
return request({
url: '/{{ moduleName }}/{{ businessName }}/' + {{ pkColumn.python_field }},
method: 'get'
})
}
// 新增{{ functionName }}
export function add{{ BusinessName }}(data) {
return request({
url: '/{{ moduleName }}/{{ businessName }}',
method: 'post',
data: data
})
}
// 修改{{ functionName }}
export function update{{ BusinessName }}(data) {
return request({
url: '/{{ moduleName }}/{{ businessName }}',
method: 'put',
data: data
})
}
// 删除{{ functionName }}
export function del{{ BusinessName }}({{ pkColumn.python_field }}) {
return request({
url: '/{{ moduleName }}/{{ businessName }}/' + {{ pkColumn.python_field }},
method: 'delete'
})
}

View File

@@ -0,0 +1,125 @@
{% set pkField = pkColumn.python_field %}
{% set pk_field = pkColumn.python_field | camel_to_snake %}
{% for column in columns %}
{% if column.python_field == "createTime" %}
from datetime import datetime
{% endif %}
{% endfor %}
from fastapi import APIRouter, Depends, Form, Request
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from config.enums import BusinessType
from config.get_db import get_db
from module_admin.annotation.log_annotation import Log
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.service.login_service import LoginService
from {{ packageName }}.service.{{ businessName }}_service import {{ BusinessName }}Service
from {{ packageName }}.entity.vo.{{ businessName }}_vo import Delete{{ BusinessName }}Model, {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel
from utils.common_util import bytes2file_response
from utils.log_util import logger
from utils.page_util import PageResponseModel
from utils.response_util import ResponseUtil
{{ businessName }}Controller = APIRouter(prefix='/{{ moduleName }}/{{ businessName }}', dependencies=[Depends(LoginService.get_current_user)])
@{{ businessName }}Controller.get(
'/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:list'))]
)
async def get_{{ moduleName }}_{{ businessName }}_list(
request: Request,
{% if table.crud or table.sub %}{{ businessName }}_page_query{% elif table.tree %}{{ businessName }}_query{% endif %}: {{ BusinessName }}PageQueryModel = Depends({{ BusinessName }}PageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
{% if table.crud or table.sub %}
# 获取分页数据
{{ businessName }}_page_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_page_query, is_page=True)
logger.info('获取成功')
return ResponseUtil.success(model_content={{ businessName }}_page_query_result)
{% elif table.tree %}
{{ businessName }}_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_query)
logger.info('获取成功')
return ResponseUtil.success(data={{ businessName }}_query_result)
{% endif %}
@{{ businessName }}Controller.post('', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:add'))])
@ValidateFields(validate_model='add_{{ businessName }}')
@Log(title='{{ functionName }}', business_type=BusinessType.INSERT)
async def add_{{ moduleName }}_{{ businessName }}(
request: Request,
add_{{ businessName }}: {{ BusinessName }}Model,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
{% for column in columns %}
{% if column.python_field == "createBy" %}
add_{{ businessName }}.create_by = current_user.user.user_name
{% elif column.python_field == "createTime" %}
add_{{ businessName }}.create_time = datetime.now()
{% elif column.python_field == "updateBy" %}
add_{{ businessName }}.update_by = current_user.user.user_name
{% elif column.python_field == "updateTime" %}
add_{{ businessName }}.update_time = datetime.now()
{% endif %}
{% endfor %}
add_{{ businessName }}_result = await {{ BusinessName }}Service.add_{{ businessName }}_services(query_db, add_{{ businessName }})
logger.info(add_{{ businessName }}_result.message)
return ResponseUtil.success(msg=add_{{ businessName }}_result.message)
@{{ businessName }}Controller.put('', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:edit'))])
@ValidateFields(validate_model='edit_{{ businessName }}')
@Log(title='{{ functionName }}', business_type=BusinessType.UPDATE)
async def edit_{{ moduleName }}_{{ businessName }}(
request: Request,
edit_{{ businessName }}: {{ BusinessName }}Model,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
edit_{{ businessName }}.update_by = current_user.user.user_name
edit_{{ businessName }}.update_time = datetime.now()
edit_{{ businessName }}_result = await {{ BusinessName }}Service.edit_{{ businessName }}_services(query_db, edit_{{ businessName }})
logger.info(edit_{{ businessName }}_result.message)
return ResponseUtil.success(msg=edit_{{ businessName }}_result.message)
@{{ businessName }}Controller.delete('/{% raw %}{{% endraw %}{{ pk_field }}s{% raw %}}{% endraw %}', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:remove'))])
@Log(title='{{ functionName }}', business_type=BusinessType.DELETE)
async def delete_{{ moduleName }}_{{ businessName }}(request: Request, {{ pk_field }}s: str, query_db: AsyncSession = Depends(get_db)):
delete_{{ businessName }} = Delete{{ BusinessName }}Model({{ pkField }}s={{ pk_field }}s)
delete_{{ businessName }}_result = await {{ BusinessName }}Service.delete_{{ businessName }}_services(query_db, delete_{{ businessName }})
logger.info(delete_{{ businessName }}_result.message)
return ResponseUtil.success(msg=delete_{{ businessName }}_result.message)
@{{ businessName }}Controller.get(
'/{% raw %}{{% endraw %}{{ pk_field }}{% raw %}}{% endraw %}', response_model={{ BusinessName }}Model, dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:query'))]
)
async def query_detail_{{ moduleName }}_{{ businessName }}(request: Request, {{ pk_field }}: int, query_db: AsyncSession = Depends(get_db)):
{{ businessName }}_detail_result = await {{ BusinessName }}Service.{{ businessName }}_detail_services(query_db, {{ pk_field }})
logger.info(f'获取{{ pk_field }}为{% raw %}{{% endraw %}{{ pk_field }}{% raw %}}{% endraw %}的信息成功')
return ResponseUtil.success(data={{ businessName }}_detail_result)
@{{ businessName }}Controller.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:export'))])
@Log(title='{{ functionName }}', business_type=BusinessType.EXPORT)
async def export_{{ moduleName }}_{{ businessName }}_list(
request: Request,
{{ businessName }}_page_query: {{ BusinessName }}PageQueryModel = Form(),
query_db: AsyncSession = Depends(get_db),
):
# 获取全量数据
{{ businessName }}_query_result = await {{ BusinessName }}Service.get_{{ businessName }}_list_services(query_db, {{ businessName }}_page_query, is_page=False)
{{ businessName }}_export_result = await {{ BusinessName }}Service.export_{{ businessName }}_list_services({% if dicts %}request, {% endif %}{{ businessName }}_query_result)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response({{ businessName }}_export_result))

View File

@@ -0,0 +1,213 @@
{% set pkField = pkColumn.python_field %}
{% set pk_field = pkColumn.python_field | camel_to_snake %}
{% set pkParentheseIndex = pkColumn.column_comment.find("") %}
{% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %}
{% for column in columns %}
{% if column.query and column.query_type == 'BETWEEN' and column.python_field == "createTime" %}
from datetime import datetime, time
{% endif %}
{% endfor %}
from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession
{% if table.sub %}
from sqlalchemy.orm import selectinload
{% endif %}
{% if table.sub %}
from {{ packageName }}.entity.do.{{ businessName }}_do import {{ ClassName }}, {{ subClassName }}
from {{ packageName }}.entity.vo.{{ businessName }}_vo import {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel, {{ subTable.business_name | capitalize }}Model
{% else %}
from {{ packageName }}.entity.do.{{ businessName }}_do import {{ ClassName }}
from {{ packageName }}.entity.vo.{{ businessName }}_vo import {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel
{% endif %}
from utils.page_util import PageUtil
class {{ BusinessName }}Dao:
"""
{{ functionName }}模块数据库操作层
"""
@classmethod
async def get_{{ businessName }}_detail_by_id(cls, db: AsyncSession, {{ pk_field }}: int):
"""
根据{{ pk_field_comment }}获取{{ functionName }}详细信息
:param db: orm对象
:param {{ pk_field }}: {{ pk_field_comment }}
:return: {{ functionName }}信息对象
"""
{{ businessName }}_info = (
(
await db.execute(
{% if table.sub %}
select({{ ClassName }})
.options(selectinload({{ ClassName }}.{{ subclassName }}_list))
{% else %}
select({{ ClassName }})
{% endif %}
.where(
{{ ClassName }}.{{ pk_field }} == {{ pk_field }}
)
)
)
.scalars()
.first()
)
return {{ businessName }}_info
@classmethod
async def get_{{ businessName }}_detail_by_info(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model):
"""
根据{{ functionName }}参数获取{{ functionName }}信息
:param db: orm对象
:param {{ businessName }}: {{ functionName }}参数对象
:return: {{ functionName }}信息对象
"""
{{ businessName }}_info = (
(
await db.execute(
select({{ ClassName }}).where(
{% for column in columns %}
{% if column.unique %}
{{ ClassName }}.{{ column.python_field | camel_to_snake }} == {{ businessName }}.{{ column.python_field | camel_to_snake }} if {{ businessName }}.{{ column.python_field | camel_to_snake }} else True,
{% endif %}
{% endfor %}
)
)
)
.scalars()
.first()
)
return {{ businessName }}_info
@classmethod
async def get_{{ businessName }}_list(cls, db: AsyncSession, query_object: {{ BusinessName }}PageQueryModel, is_page: bool = False):
"""
根据查询参数获取{{ functionName }}列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: {{ functionName }}列表信息对象
"""
query = (
{% if table.sub %}
select({{ ClassName }})
.options(selectinload({{ ClassName }}.{{ subclassName }}_list))
{% else %}
select({{ ClassName }})
{% endif %}
.where(
{% for column in columns %}
{% set field = column.python_field | camel_to_snake %}
{% if column.query %}
{% if column.query_type == "EQ" %}
{{ ClassName }}.{{ field }} == query_object.{{ field }} if query_object.{{ field }} else True,
{% elif column.query_type == "NE" %}
{{ ClassName }}.{{ field }} != query_object.{{ field }} if query_object.{{ field }} else True,
{% elif column.query_type == "GT" %}
{{ ClassName }}.{{ field }} > query_object.{{ field }} if query_object.{{ field }} else True,
{% elif column.query_type == "GTE" %}
{{ ClassName }}.{{ field }} >= query_object.{{ field }} if query_object.{{ field }} else True,
{% elif column.query_type == "LT" %}
{{ ClassName }}.{{ field }} < query_object.{{ field }} if query_object.{{ field }} else True,
{% elif column.query_type == "LTE" %}
{{ ClassName }}.{{ field }} <= query_object.{{ field }} if query_object.{{ field }} else True,
{% elif column.query_type == "LIKE" %}
{{ ClassName }}.{{ field }}.like(f'%{% raw %}{{% endraw %}query_object.{{ field }}{% raw %}}{% endraw %}%') if query_object.{{ field }} else True,
{% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{{ ClassName }}.{{ field }}.between(
datetime.combine(datetime.strptime(query_object.begin_{{ column.column_name }}, '%Y-%m-%d'), time(00, 00, 00)),
datetime.combine(datetime.strptime(query_object.end_{{ column.column_name }}, '%Y-%m-%d'), time(23, 59, 59)),
)
if query_object.begin_{{ column.column_name }} and query_object.end_{{ column.column_name }}
else True,
{% endif %}
{% endif %}
{% endfor %}
)
.order_by({{ ClassName }}.{{ pk_field }})
.distinct()
)
{{ businessName }}_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
return {{ businessName }}_list
@classmethod
async def add_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model):
"""
新增{{ functionName }}数据库操作
:param db: orm对象
:param {{ businessName }}: {{ functionName }}对象
:return:
"""
db_{{ businessName }} = {{ ClassName }}(**{{ businessName }}.model_dump(exclude={% raw %}{{% endraw %}{% if table.sub %}'{{ subclassName }}_list', {% endif %}{% for column in columns %}{% if not column.insert and column.column_name not in column_not_add_show + column_not_edit_show %}'{{ column.python_field | camel_to_snake }}'{% if not loop.last %}, {% endif %}{% endif %}{% endfor %}{% raw %}}{% endraw %}))
db.add(db_{{ businessName }})
await db.flush()
return db_{{ businessName }}
@classmethod
async def edit_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: dict):
"""
编辑{{ functionName }}数据库操作
:param db: orm对象
:param {{ businessName }}: 需要更新的{{ functionName }}字典
:return:
"""
await db.execute(update({{ ClassName }}), [{{ businessName }}])
@classmethod
async def delete_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model):
"""
删除{{ functionName }}数据库操作
:param db: orm对象
:param {{ businessName }}: {{ functionName }}对象
:return:
"""
await db.execute(delete({{ ClassName }}).where({{ ClassName }}.{{ pk_field }}.in_([{{ businessName }}.{{ pk_field }}])))
{% if table.sub %}
@classmethod
async def add_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model):
"""
新增{{ subTable.function_name }}数据库操作
:param db: orm对象
:param {{ subTable.business_name }}: {{ subTable.function_name }}对象
:return:
"""
db_{{ subTable.business_name }} = {{ subClassName }}(**{{ subTable.business_name }}.model_dump())
db.add(db_{{ subTable.business_name }})
await db.flush()
return db_{{ subTable.business_name }}
@classmethod
async def edit_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: dict):
"""
编辑{{ subTable.function_name }}数据库操作
:param db: orm对象
:param {{ subTable.business_name }}: 需要更新的{{ subTable.function_name }}字典
:return:
"""
await db.execute(update({{ subClassName }}), [{{ subTable.business_name }}])
@classmethod
async def delete_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model):
"""
删除{{ subTable.function_name }}数据库操作
:param db: orm对象
:param {{ subTable.business_name }}: {{ subTable.function_name }}对象
:return:
"""
await db.execute(delete({{ subClassName }}).where({{ subClassName }}.{{ subTable.pk_column.python_field | camel_to_snake }}.in_([{{ subTable.business_name }}.{{ subTable.pk_column.python_field | camel_to_snake }}])))
{% endif %}

View File

@@ -0,0 +1,41 @@
{% for do_import in doImportList %}
{{ do_import }}
{% endfor %}
{% if table.sub %}
from sqlalchemy.orm import relationship
{% endif %}
from config.database import Base
class {{ ClassName }}(Base):
"""
{{ functionName }}表
"""
__tablename__ = '{{ tableName }}'
{% for column in columns %}
{{ column.column_name }} = Column({{ column.column_type | get_sqlalchemy_type }}, {% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required or column.pk %}nullable=False{% else %}nullable=True{% endif %}, comment='{{ column.column_comment }}')
{% endfor %}
{% if table.sub %}
{{ subclassName }}_list = relationship('{{ subClassName }}', back_populates='{{ businessName }}')
{% endif %}
{% if table.sub %}
class {{ subClassName }}(Base):
"""
{{ subTable.function_name }}表
"""
__tablename__ = '{{ subTableName }}'
{% for column in subTable.columns %}
{{ column.column_name }} = Column({{ column.column_type | get_sqlalchemy_type }}, {% if column.column_name == subTableFkName %}ForeignKey('{{ tableName }}.{{ subTableFkName }}'), {% endif %}{% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required %}nullable=True{% else %}nullable=False{% endif %}, comment='{{ column.column_comment }}')
{% endfor %}
{% if table.sub %}
{{ businessName }} = relationship('{{ ClassName }}', back_populates='{{ subclassName }}_list')
{% endif %}
{% endif %}

View File

@@ -0,0 +1,211 @@
{% set pkField = pkColumn.python_field %}
{% set pk_field = pkColumn.python_field | camel_to_snake %}
{% set pkParentheseIndex = pkColumn.column_comment.find("") %}
{% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %}
{% if dicts %}
from fastapi import Request
{% endif %}
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
from config.constant import CommonConstant
from exceptions.exception import ServiceException
from module_admin.entity.vo.common_vo import CrudResponseModel
{% if dicts %}
from module_admin.service.dict_service import DictDataService
{% endif %}
from {{ packageName }}.dao.{{ businessName }}_dao import {{ BusinessName }}Dao
from {{ packageName }}.entity.vo.{{ businessName }}_vo import Delete{{ BusinessName }}Model, {{ BusinessName }}Model, {{ BusinessName }}PageQueryModel
from utils.common_util import CamelCaseUtil
from utils.excel_util import ExcelUtil
class {{ BusinessName }}Service:
"""
{{ functionName }}模块服务层
"""
@classmethod
async def get_{{ businessName }}_list_services(
cls, query_db: AsyncSession, query_object: {{ BusinessName }}PageQueryModel, is_page: bool = False
):
"""
获取{{ functionName }}列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: {{ functionName }}列表信息对象
"""
{{ businessName }}_list_result = await {{ BusinessName }}Dao.get_{{ businessName }}_list(query_db, query_object, is_page)
return {{ businessName }}_list_result
{% for column in columns %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.unique %}
@classmethod
async def check_{{ column.python_field | camel_to_snake }}_unique_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model):
"""
检查{{ comment }}是否唯一service
:param query_db: orm对象
:param page_object: {{ functionName }}对象
:return: 校验结果
"""
{{ pk_field }} = -1 if page_object.{{ pk_field }} is None else page_object.{{ pk_field }}
{{ businessName }} = await {{ BusinessName }}Dao.get_{{ businessName }}_detail_by_info(query_db, {{ BusinessName }}Model({{ column.python_field }}=page_object.{{ column.python_field | camel_to_snake }}))
if {{ businessName }} and {{ businessName }}.{{ pk_field }} != {{ pk_field }}:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
{% if not loop.last %}{{ "\n" }}{% endif %}
{% endif %}
{% endfor %}
@classmethod
async def add_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model):
"""
新增{{ functionName }}信息service
:param query_db: orm对象
:param page_object: 新增{{ functionName }}对象
:return: 新增{{ functionName }}校验结果
"""
{% for column in columns %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.unique %}
if not await cls.check_{{ column.python_field | camel_to_snake }}_unique_services(query_db, page_object):
raise ServiceException(message=f'新增{{ functionName }}{page_object.{{ column.python_field | camel_to_snake }}}失败,{{ comment }}已存在')
{% endif %}
{% endfor %}
try:
{% if table.sub %}
add_{{ businessName }} = await {{ BusinessName }}Dao.add_{{ businessName }}_dao(query_db, page_object)
if add_{{ businessName }}:
for sub_table in page_object.{{ subclassName }}_list:
await {{ BusinessName }}Dao.add_{{ subTable.business_name }}_dao(query_db, sub_table)
{% else %}
await {{ BusinessName }}Dao.add_{{ businessName }}_dao(query_db, page_object)
{% endif %}
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功')
except Exception as e:
await query_db.rollback()
raise e
@classmethod
async def edit_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: {{ BusinessName }}Model):
"""
编辑{{ functionName }}信息service
:param query_db: orm对象
:param page_object: 编辑{{ functionName }}对象
:return: 编辑{{ functionName }}校验结果
"""
edit_{{ businessName }} = page_object.model_dump(exclude_unset=True, exclude={% raw %}{{% endraw %}{% if table.sub %}'{{ subclassName }}_list', {% endif %}{% for column in columns %}{% if not column.edit and not column.pk and column.column_name not in column_not_edit_show %}'{{ column.python_field | camel_to_snake }}'{% if not loop.last %}, {% endif %}{% endif %}{% endfor %}{% raw %}}{% endraw %})
{{ businessName }}_info = await cls.{{ businessName }}_detail_services(query_db, page_object.{{ pk_field }})
if {{ businessName }}_info.{{ pk_field }}:
{% for column in columns %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.unique %}
if not await cls.check_{{ column.python_field | camel_to_snake }}_unique_services(query_db, page_object):
raise ServiceException(message=f'修改{{ functionName }}{page_object.{{ column.python_field | camel_to_snake }}}失败,{{ comment }}已存在')
{% endif %}
{% endfor %}
try:
await {{ BusinessName }}Dao.edit_{{ businessName }}_dao(query_db, edit_{{ businessName }})
{% if table.sub %}
for sub_table in {{ businessName }}_info.{{ subclassName }}_list:
await {{ BusinessName }}Dao.delete_{{ subTable.business_name }}_dao(query_db, sub_table)
for sub_table in page_object.{{ subclassName }}_list:
await {{ BusinessName }}Dao.add_{{ subTable.business_name }}_dao(query_db, sub_table)
{% endif %}
await query_db.commit()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='{{ functionName }}不存在')
@classmethod
async def delete_{{ businessName }}_services(cls, query_db: AsyncSession, page_object: Delete{{ BusinessName }}Model):
"""
删除{{ functionName }}信息service
:param query_db: orm对象
:param page_object: 删除{{ functionName }}对象
:return: 删除{{ functionName }}校验结果
"""
if page_object.{{ pk_field }}s:
{{ pk_field }}_list = page_object.{{ pk_field }}s.split(',')
try:
for {{ pk_field }} in {{ pk_field }}_list:
{% if table.sub %}
{{ businessName }} = await cls.{{ businessName }}_detail_services(query_db, int({{ pk_field }}))
for sub_table in {{ businessName }}.{{ subclassName }}_list:
await {{ BusinessName }}Dao.delete_{{ subTable.business_name }}_dao(query_db, sub_table)
{% endif %}
await {{ BusinessName }}Dao.delete_{{ businessName }}_dao(query_db, {{ BusinessName }}Model({{ pkField }}={{ pk_field }}))
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入{{ pk_field_comment }}为空')
@classmethod
async def {{ businessName }}_detail_services(cls, query_db: AsyncSession, {{ pk_field }}: int):
"""
获取{{ functionName }}详细信息service
:param query_db: orm对象
:param {{ pk_field }}: {{ pk_field_comment }}
:return: {{ pk_field_comment }}对应的信息
"""
{{ businessName }} = await {{ BusinessName }}Dao.get_{{ businessName }}_detail_by_id(query_db, {{ pk_field }}={{ pk_field }})
if {{ businessName }}:
result = {{ BusinessName }}Model(**CamelCaseUtil.transform_result({{ businessName }}))
else:
result = {{ BusinessName }}Model(**dict())
return result
@staticmethod
async def export_{{ businessName }}_list_services({% if dicts %}request: Request, {% endif %}{{ businessName }}_list: List):
"""
导出{{ functionName }}信息service
:param {{ businessName }}_list: {{ functionName }}信息列表
:return: {{ functionName }}信息对应excel的二进制数据
"""
# 创建一个映射字典,将英文键映射到中文键
mapping_dict = {
{% for column in columns %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
'{{ column.python_field }}': '{{ comment }}',
{% endfor %}
}
{% if dicts %}
{% for dict_type in dicts.split(", ") %}
{{ dict_type[1:-1] }}_list = await DictDataService.query_dict_data_list_from_cache_services(
request.app.state.redis, dict_type={{ dict_type }}
)
{{ dict_type[1:-1] }}_option = [dict(label=item.get('dictLabel'), value=item.get('dictValue')) for item in {{ dict_type[1:-1] }}_list]
{{ dict_type[1:-1] }}_option_dict = {item.get('value'): item for item in {{ dict_type[1:-1] }}_option}
{% endfor %}
for item in {{ businessName }}_list:
{% for column in columns %}
{% if column.dict_type %}
if str(item.get('{{ column.python_field }}')) in {{ column.dict_type }}_option_dict.keys():
item['{{ column.python_field }}'] = {{ column.dict_type }}_option_dict.get(str(item.get('{{ column.python_field }}'))).get('label')
{% endif %}
{% endfor %}
{% endif %}
binary_data = ExcelUtil.export_list2excel({{ businessName }}_list, mapping_dict)
return binary_data

View File

@@ -0,0 +1,178 @@
{% set pkField = pkColumn.python_field %}
{% set pk_field = pkColumn.python_field | camel_to_snake %}
{% set pkParentheseIndex = pkColumn.column_comment.find("") %}
{% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %}
{% set vo_field_required = namespace(has_required=False) %}
{% set vo_field_daterange = namespace(has_daterange=False) %}
{% for column in columns %}
{% if column.required %}
{% set vo_field_required.has_required = True %}
{% endif %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set vo_field_daterange.has_daterange = True %}
{% endif %}
{% endfor %}
{% set sub_vo_field_required = namespace(has_required=False) %}
{% if table.sub %}
{% for sub_column in subTable.columns %}
{% if sub_column.required %}
{% set sub_vo_field_required.has_required = True %}
{% endif %}
{% endfor %}
{% endif %}
{% for vo_import in voImportList %}
{{ vo_import }}
{% endfor %}
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
{% if vo_field_required.has_required or sub_vo_field_required.has_required %}
from pydantic_validation_decorator import NotBlank
{% endif %}
{% if table.sub %}
from typing import List, Optional
{% else %}
from typing import Optional
{% endif %}
from module_admin.annotation.pydantic_annotation import as_query
{% if table.sub %}
class {{ BusinessName }}BaseModel(BaseModel):
"""
{{ functionName }}表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
{% for column in columns %}
{{ column.column_name }}: Optional[{{ column.python_type }}] = Field(default=None, description='{{ column.column_comment }}')
{% endfor %}
{% for column in columns %}
{% if column.required %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
@NotBlank(field_name='{{ column.column_name }}', message='{{ comment }}不能为空')
def get_{{ column.column_name }}(self):
return self.{{ column.column_name }}
{% if not loop.last %}{{ "\n" }}{% endif %}
{% endif %}
{% endfor %}
{% if vo_field_required.has_required %}
def validate_fields(self):
{% for column in columns %}
{% if column.required %}
self.get_{{ column.column_name }}()
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
class {{ BusinessName }}Model({% if table.sub %}{{ BusinessName }}BaseModel{% else %}BaseModel{% endif %}):
"""
{{ functionName }}表对应pydantic模型
"""
{% if not table.sub %}
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
{% for column in columns %}
{{ column.column_name }}: Optional[{{ column.python_type }}] = Field(default=None, description='{{ column.column_comment }}')
{% endfor %}
{% endif %}
{% if table.sub %}
{{ subclassName }}_list: Optional[List['{{ subTable.business_name | capitalize }}Model']] = Field(default=None, description='子表列信息')
{% endif %}
{% if not table.sub %}
{% for column in columns %}
{% if column.required %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
@NotBlank(field_name='{{ column.column_name }}', message='{{ comment }}不能为空')
def get_{{ column.column_name }}(self):
return self.{{ column.column_name }}
{% if not loop.last %}{{ "\n" }}{% endif %}
{% endif %}
{% endfor %}
{% if vo_field_required.has_required %}
def validate_fields(self):
{% for column in columns %}
{% if column.required %}
self.get_{{ column.column_name }}()
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% if table.sub %}
class {{ subTable.business_name | capitalize }}Model(BaseModel):
"""
{{ subTable.function_name }}表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
{% for sub_column in subTable.columns %}
{{ sub_column.column_name }}: Optional[{{ sub_column.python_type }}] = Field(default=None, description='{{ sub_column.column_comment}}')
{% endfor %}
{% for sub_column in subTable.columns %}
{% if sub_column.required %}
{% set parentheseIndex = sub_column.column_comment.find("") %}
{% set comment = sub_column.column_comment[:parentheseIndex] if parentheseIndex != -1 else sub_column.column_comment %}
@NotBlank(field_name='{{ sub_column.column_name }}', message='{{ comment }}不能为空')
def get_{{ sub_column.column_name }}(self):
return self.{{ sub_column.column_name }}
{% if not loop.last %}{{ "\n" }}{% endif %}
{% endif %}
{% endfor %}
{% if sub_vo_field_required.has_required %}
def validate_fields(self):
{% for sub_column in subTable.columns %}
{% if sub_column.required %}
self.get_{{ sub_column.column_name }}()
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
class {{ BusinessName }}QueryModel({% if table.sub %}{{ BusinessName }}BaseModel{% else %}{{ BusinessName }}Model{% endif %}):
"""
{{ functionName }}不分页查询模型
"""
{% if vo_field_daterange.has_daterange %}
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
begin_{{ column.column_name }}: Optional[str] = Field(default=None, description='开始{{ column.column_comment }}')
end_{{ column.column_name }}: Optional[str] = Field(default=None, description='结束{{ column.column_comment }}')
{% endif %}
{% endfor %}
{% else %}
pass
{% endif %}
@as_query
class {{ BusinessName }}PageQueryModel({{ BusinessName }}QueryModel):
"""
{{ functionName }}分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
class Delete{{ BusinessName }}Model(BaseModel):
"""
删除{{ functionName }}模型
"""
model_config = ConfigDict(alias_generator=to_camel)
{{ pk_field }}s: str = Field(description='需要删除的{{ pk_field_comment }}')

View File

@@ -0,0 +1,47 @@
{% if dbType == 'postgresql' %}
-- 菜单 SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}', '{{ parentMenuId }}', '1', '{{ businessName }}', '{{ moduleName }}/{{ businessName }}/index', 1, 0, 'C', '0', '0', '{{ permissionPrefix }}:list', '#', 'admin', current_timestamp, '', null, '{{ functionName }}菜单');
-- 按钮父菜单ID
select max(menu_id) from sys_menu;
-- 按钮 SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}查询', max(menu_id), '1', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:query', '#', 'admin', current_timestamp, '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}新增', max(menu_id), '2', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:add', '#', 'admin', current_timestamp, '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}修改', max(menu_id), '3', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:edit', '#', 'admin', current_timestamp, '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}删除', max(menu_id), '4', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:remove', '#', 'admin', current_timestamp, '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}导出', max(menu_id), '5', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:export', '#', 'admin', current_timestamp, '', null, '');
{% else %}
-- 菜单 SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}', '{{ parentMenuId }}', '1', '{{ businessName }}', '{{ moduleName }}/{{ businessName }}/index', 1, 0, 'C', '0', '0', '{{ permissionPrefix }}:list', '#', 'admin', sysdate(), '', null, '{{ functionName }}菜单');
-- 按钮父菜单ID
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{ functionName }}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '{{ permissionPrefix }}:export', '#', 'admin', sysdate(), '', null, '');
{% endif %}

View File

@@ -0,0 +1,496 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
{% for column in columns %}
{% if column.query %}
{% set dictType = column.dict_type %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.html_type == "input" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-input
v-model="queryParams.{{ column.python_field }}"
placeholder="请输入{{ comment }}"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dictType %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable>
<el-option
v-for="dict in dict.type.{{ dictType }}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and not dictType %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-date-picker
v-model="queryParams.{{ column.python_field }}"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择{{ comment }}"
clearable
/>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %}
<el-form-item label="{{ comment }}">
<el-date-picker
v-model="daterange{{ AttrName }}"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
{% endif %}
{% endif %}
{% endfor %}
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-sort"
size="mini"
@click="toggleExpandAll"
>展开/折叠</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table
v-if="refreshTable"
v-loading="loading"
:data="{{ businessName }}List"
row-key="{{ treeCode }}"
:default-expand-all="isExpandAll"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
{% for column in columns %}
{% set pythonField = column.python_field %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.pk %}
{% elif column.list and column.html_type == "datetime" %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" width="180">
<template slot-scope="scope">
<span>{% raw %}{{{% endraw %} parseTime(scope.row.{{ pythonField }}, '{y}-{m}-{d}') {% raw %}}}{% endraw %}</span>
</template>
</el-table-column>
{% elif column.list and column.html_type == "imageUpload" %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" width="100">
<template slot-scope="scope">
<image-preview :src="scope.row.{{ pythonField }}" :width="50" :height="50"/>
</template>
</el-table-column>
{% elif column.list and column.dict_type %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}">
<template slot-scope="scope">
{% if column.html_type == "checkbox" %}
<dict-tag :options="{{ column.dict_type }}" :value="scope.row.{{ pythonField }} ? scope.row.{{ pythonField }}.split(',') : []"/>
{% else %}
<dict-tag :options="{{ column.dict_type }}" :value="scope.row.{{ pythonField }}"/>
{% endif %}
</template>
</el-table-column>
{% elif column.list and pythonField %}
{% if loop.index == 1 %}
<el-table-column label="{{ comment }}" prop="{{ pythonField }}" />
{% else %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" />
{% endif %}
{% endif %}
{% endfor %}
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAdd(scope.row)"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:add']"
>新增</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改{{ functionName }}对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
{% for column in columns %}
{% set field = column.python_field %}
{% if (column.insert or column.edit) and not column.pk and column.column_name not in column_not_add_show + column_not_edit_show %}
{% if column.usable_column or not column.super_column %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% set dictType = column.dict_type %}
{% if treeParentCode and column.python_field == treeParentCode %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ treeParentCode }}">
<treeselect v-model="form.{{ treeParentCode }}" :options="{{ businessName }}Options" :normalizer="normalizer" placeholder="请选择{{ comment }}" />
</el-form-item>
{% elif column.html_type == "input" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-input v-model="form.{{ field }}" placeholder="请输入{{ comment }}" />
</el-form-item>
{% elif column.html_type == "imageUpload" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<image-upload v-model="form.{{ field }}"/>
</el-form-item>
{% elif column.html_type == "fileUpload" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<file-upload v-model="form.{{ field }}"/>
</el-form-item>
{% elif column.html_type == "editor" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<editor v-model="form.{{ field }}" :min-height="192"/>
</el-form-item>
{% elif column.html_type == "select" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
<el-option
v-for="dict in dict.type.{{ dictType }}"
:key="dict.value"
:label="dict.label"
{% if column.python_type == 'int' %}
:value="parseInt(dict.value)"
{% else %}
:value="dict.value"
{% endif %}
></el-option>
</el-select>
</el-form-item>
{% elif column.html_type == "select" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "checkbox" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-checkbox-group v-model="form.{{ field }}">
<el-checkbox
v-for="dict in dict.type.{{ dictType }}"
:key="dict.value"
:label="dict.value">
{% raw %}{{{% endraw %} dict.label {% raw %}}}{% endraw %}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
{% elif column.html_type == "checkbox" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-checkbox-group v-model="form.{{ field }}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
{% elif column.html_type == "radio" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-radio-group v-model="form.{{ field }}">
<el-radio
v-for="dict in dict.type.{{ dictType }}"
:key="dict.value"
{% if column.python_type == 'int' %}
:label="parseInt(dict.value)"
{% else %}
:label="dict.value"
{% endif %}>
{% raw %}{{{% endraw %} dict.label {% raw %}}}{% endraw %}
</el-radio>
</el-radio-group>
</el-form-item>
{% elif column.html_type == "radio" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-radio-group v-model="form.{{ field }}">
<el-radio label="请选择字典生成" value="" />
</el-radio-group>
</el-form-item>
{% elif column.html_type == "datetime" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-date-picker clearable
v-model="form.{{ field }}"
type="date"
value-format="yyyy-MM-d"
placeholder="请选择{{ comment }}">
</el-date-picker>
</el-form-item>
{% elif column.html_type == "textarea" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-input v-model="form.{{ field }}" type="textarea" placeholder="请输入内容" />
</el-form-item>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script>
import { list{{ BusinessName }}, get{{ BusinessName }}, del{{ BusinessName }}, add{{ BusinessName }}, update{{ BusinessName }} } from "@/api/{{ moduleName }}/{{ businessName }}";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "{{ BusinessName }}",
{% if dicts %}
dicts: [{{ dicts }}],
{% endif %}
components: {
Treeselect
},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// {{ functionName }}表格数据
{{ businessName }}List: [],
// {{ functionName }}树选项
{{ businessName }}Options: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 是否展开,默认全部展开
isExpandAll: true,
// 重新渲染表格状态
refreshTable: true,
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
// {{ comment }}时间范围
daterange{{ AttrName }}: [],
{% endif %}
{% endfor %}
// 查询参数
queryParams: {
{% for column in columns %}
{% if column.query %}
{{ column.python_field }}: null,
{% endif %}
{% endfor %}
},
// 表单参数
form: {},
// 表单校验
rules: {
{% for column in columns %}
{% if column.required %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{{ column.python_field }}: [
{ required: true, message: "{{ comment }}不能为空", trigger: "{% if column.html_type == 'select' or column.html_type == 'radio' %}change{% else %}blur{% endif %}" }
],
{% endif %}
{% endfor %}
}
};
},
created() {
this.getList();
},
methods: {
/** 查询{{ functionName }}列表 */
getList() {
this.loading = true;
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
this.queryParams.params = {};
{% endif %}
{% endfor %}
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
if (null != this.daterange{{ AttrName }} && '' != this.daterange{{ AttrName }}) {
this.queryParams.begin{{ AttrName }} = this.daterange{{ AttrName }}[0];
this.queryParams.end{{ AttrName }} = this.daterange{{ AttrName }}[1];
}
{% endif %}
{% endfor %}
list{{ BusinessName }}(this.queryParams).then(response => {
this.{{ businessName }}List = this.handleTree(response.data, "{{ treeCode }}", "{{ treeParentCode }}");
this.loading = false;
});
},
/** 转换{{ functionName }}数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children;
}
return {
id: node.{{ treeCode }},
label: node.{{ treeName }},
children: node.children
};
},
/** 查询{{ functionName }}下拉树结构 */
getTreeselect() {
list{{ BusinessName }}().then(response => {
this.{{ businessName }}Options = [];
const data = { {{ treeCode }}: 0, {{ treeName }}: '顶级节点', children: [] };
data.children = this.handleTree(response.data, "{{ treeCode }}", "{{ treeParentCode }}");
this.{{ businessName }}Options.push(data);
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
{% for column in columns %}
{% if column.html_type == "checkbox" %}
{{ column.python_field }}: [],
{% else %}
{{ column.python_field }}: null,
{% endif %}
{% endfor %}
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
this.daterange{{ AttrName }} = [];
{% endif %}
{% endfor %}
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd(row) {
this.reset();
this.getTreeselect();
if (row != null && row.{{ treeCode }}) {
this.form.{{ treeParentCode }} = row.{{ treeCode }};
} else {
this.form.{{ treeParentCode }} = 0;
}
this.open = true;
this.title = "添加{{ functionName }}";
},
/** 展开/折叠操作 */
toggleExpandAll() {
this.refreshTable = false;
this.isExpandAll = !isExpandAll;
nextTick(() => {
this.refreshTable = true;
});
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
this.getTreeselect();
if (row != null) {
this.form.{{ treeParentCode }} = row.{{ treeParentCode }};
}
get{{ BusinessName }}(row.{{ pkColumn.python_field }}).then(response => {
this.form = response.data;
{% for column in columns %}
{% if column.html_type == "checkbox" %}
this.form.{{ column.python_field }} = this.form.{{ column.python_field }}.split(",");
{% endif %}
{% endfor %}
this.open = true;
this.title = "修改{{ functionName }}";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
{% for column in columns %}
{% if column.html_type == "checkbox" %}
this.form.{{ column.python_field }} = this.form.{{ column.python_field }}.join(",");
{% endif %}
{% endfor %}
if (this.form.{{ pkColumn.python_field }} != null) {
update{{ BusinessName }}(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
add{{ BusinessName }}(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
this.$modal.confirm('是否确认删除{{ functionName }}编号为"' + row.{{ pkColumn.python_field }} + '"的数据项?').then(function() {
return del{{ BusinessName }}(row.{{ pkColumn.python_field }});
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 是否渲染字段 */
renderField(insert, edit) {
return this.form.{{ pkColumn.python_field }} == null ? insert : edit;
}
},
};
</script>

View File

@@ -0,0 +1,591 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
{% for column in columns %}
{% if column.query %}
{% set dictType = column.dict_type %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.html_type == "input" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-input
v-model="queryParams.{{ column.python_field }}"
placeholder="请输入{{ comment }}"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dictType %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable>
<el-option
v-for="dict in dict.type.{{ dictType }}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and not dictType %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-date-picker
v-model="queryParams.{{ column.python_field }}"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择{{ comment }}"
clearable
/>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %}
<el-form-item label="{{ comment }}">
<el-date-picker
v-model="daterange{{ AttrName }}"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
{% endif %}
{% endif %}
{% endfor %}
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
@click="handleExport"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="{{ businessName }}List" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
{% for column in columns %}
{% set pythonField = column.python_field %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.pk %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" />
{% elif column.list and column.html_type == "datetime" %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" width="180">
<template slot-scope="scope">
<span>{% raw %}{{{% endraw %} parseTime(scope.row.{{ pythonField }}, '{y}-{m}-{d}') {% raw %}}}{% endraw %}</span>
</template>
</el-table-column>
{% elif column.list and column.html_type == "imageUpload" %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" width="100">
<template slot-scope="scope">
<image-preview :src="scope.row.{{ pythonField }}" :width="50" :height="50"/>
</template>
</el-table-column>
{% elif column.list and column.dict_type %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}">
<template slot-scope="scope">
{% if column.html_type == "checkbox" %}
<dict-tag :options="{{ column.dict_type }}" :value="scope.row.{{ pythonField }} ? scope.row.{{ pythonField }}.split(',') : []"/>
{% else %}
<dict-tag :options="{{ column.dict_type }}" :value="scope.row.{{ pythonField }}"/>
{% endif %}
</template>
</el-table-column>
{% elif column.list and pythonField %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" />
{% endif %}
{% endfor %}
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改{{ functionName }}对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
{% for column in columns %}
{% set field = column.python_field %}
{% if (column.insert or column.edit) and not column.pk and column.column_name not in column_not_add_show + column_not_edit_show %}
{% if column.usable_column or not column.super_column %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% set dictType = column.dict_type %}
{% if column.html_type == "input" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-input v-model="form.{{ field }}" placeholder="请输入{{ comment }}" />
</el-form-item>
{% elif column.html_type == "imageUpload" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<image-upload v-model="form.{{ field }}"/>
</el-form-item>
{% elif column.html_type == "fileUpload" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<file-upload v-model="form.{{ field }}"/>
</el-form-item>
{% elif column.html_type == "editor" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<editor v-model="form.{{ field }}" :min-height="192"/>
</el-form-item>
{% elif column.html_type == "select" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
<el-option
v-for="dict in dict.type.{{ dictType }}"
:key="dict.value"
:label="dict.label"
{% if column.python_type == 'int' %}
:value="parseInt(dict.value)"
{% else %}
:value="dict.value"
{% endif %}
></el-option>
</el-select>
</el-form-item>
{% elif column.html_type == "select" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "checkbox" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-checkbox-group v-model="form.{{ field }}">
<el-checkbox
v-for="dict in dict.type.{{ dictType }}"
:key="dict.value"
:label="dict.value">
{{ dict.label }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
{% elif column.html_type == "checkbox" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-checkbox-group v-model="form.{{ field }}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
{% elif column.html_type == "radio" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-radio-group v-model="form.{{ field }}">
<el-radio
v-for="dict in dict.type.{{ dictType }}"
:key="dict.value"
{% if column.python_type == 'int' %}
:label="parseInt(dict.value)"
{% else %}
:label="dict.value"
{% endif %}>
{% raw %}{{{% endraw %} dict.label {% raw %}}}{% endraw %}
</el-radio>
</el-radio-group>
</el-form-item>
{% elif column.html_type == "radio" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-radio-group v-model="form.{{ field }}">
<el-radio label="请选择字典生成" value="" />
</el-radio-group>
</el-form-item>
{% elif column.html_type == "datetime" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-date-picker clearable
v-model="form.{{ field }}"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择{{ comment }}">
</el-date-picker>
</el-form-item>
{% elif column.html_type == "textarea" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-input v-model="form.{{ field }}" type="textarea" placeholder="请输入内容" />
</el-form-item>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% if table.sub %}
<el-divider content-position="center">{{ subTable.functionName }}信息</el-divider>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd{{ subClassName }}">添加</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" icon="el-icon-delete" size="mini" @click="handleDelete{{ subClassName }}">删除</el-button>
</el-col>
</el-row>
<el-table :data="{{ subclassName }}List" :row-class-name="row{{ subClassName }}Index" @selection-change="handle{{ subClassName }}SelectionChange" ref="{{ subclassName }}">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" align="center" prop="index" width="50"/>
{% for column in subTable.columns %}
{% set pythonField = column.python_field %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.pk or pythonField == subTableFkclassName %}
{% elif column.list and column.html_type == 'input' %}
<el-table-column label="{{ comment }}" prop="{{ pythonField }}" width="150">
<template slot-scope="scope">
<el-input v-model="scope.row.{{ pythonField }}" placeholder="请输入{{ comment }}" />
</template>
</el-table-column>
{% elif column.list and column.html_type == 'datetime' %}
<el-table-column label="{{ comment }}" prop="{{ pythonField }}" width="240">
<template slot-scope="scope">
<el-date-picker clearable
v-model="scope.row.{{ pythonField }}"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择{{ comment }}">
</el-date-picker>
</template>
</el-table-column>
{% elif column.list and (column.html_type == 'select' or column.html_type == 'radio') and column.dict_type %}
<el-table-column label="{{ comment }}" prop="{{ pythonField }}" width="150">
<template slot-scope="scope">
<el-select v-model="scope.row.{{ pythonField }}" placeholder="请选择{{ comment }}">
<el-option
v-for="dict in dict.type.{{ column.dict_type }}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</template>
</el-table-column>
{% elif column.list and (column.html_type == 'select' or column.html_type == 'radio') and not column.dict_type %}
<el-table-column label="{{ comment }}" prop="{{ pythonField }}" width="150">
<template slot-scope="scope">
<el-select v-model="scope.row.{{ pythonField }}" placeholder="请选择{{ comment }}">
<el-option label="请选择字典生成" value="" />
</el-select>
</template>
</el-table-column>
{% endif %}
{% endfor %}
</el-table>
{% endif %}
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script>
import { list{{ BusinessName }}, get{{ BusinessName }}, del{{ BusinessName }}, add{{ BusinessName }}, update{{ BusinessName }} } from "@/api/{{ moduleName }}/{{ businessName }}";
export default {
name: "{{ BusinessName }}",
{% if dicts %}
dicts: [{{ dicts }}],
{% endif %}
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
{% if table.sub %}
checked{{ subClassName }}: [],
{% endif %}
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// {{ functionName }}表格数据
{{ businessName }}List: [],
{% if table.sub %}
// {{ subTable.functionName }}表格数据
{{ subclassName }}List: [],
{% endif %}
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
// {{ comment }}时间范围
daterange{{ AttrName }}: [],
{% endif %}
{% endfor %}
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
{% for column in columns %}
{% if column.query %}
{{ column.python_field }}: null,
{% endif %}
{% endfor %}
},
// 表单参数
form: {},
// 表单校验
rules: {
{% for column in columns %}
{% if column.required %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{{ column.python_field }}: [
{ required: true, message: "{{ comment }}不能为空", trigger: "{% if column.html_type == 'select' or column.html_type == 'radio' %}change{% else %}blur{% endif %}" }
],
{% endif %}
{% endfor %}
}
};
},
created() {
this.getList();
},
methods: {
/** 查询{{ functionName }}列表 */
getList() {
this.loading = true;
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
this.queryParams.params = {};
{% endif %}
{% endfor %}
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
if (null != this.daterange{{ AttrName }} && '' != this.daterange{{ AttrName }}) {
this.queryParams.begin{{ AttrName }} = this.daterange{{ AttrName }}[0];
this.queryParams.end{{ AttrName }} = this.daterange{{ AttrName }}[1];
}
{% endif %}
{% endfor %}
list{{ BusinessName }}(this.queryParams).then(response => {
this.{{ businessName }}List = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
{% for column in columns %}
{% if column.html_type == "checkbox" %}
{{ column.python_field }}: [],
{% else %}
{{ column.python_field }}: null,
{% endif %}
{% endfor %}
};
{% if table.sub %}
this.{{ subclassName }}List = [];
{% endif %}
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
this.daterange{{ AttrName }} = [];
{% endif %}
{% endfor %}
this.resetForm("queryForm");
this.handleQuery();
},
/** 多选框选中数据 */
handleSelectionChange(selection) {
this.ids = selection.map(item => item.{{ pkColumn.python_field }});
this.single = selection.length != 1;
this.multiple = !selection.length;
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加{{ functionName }}";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const {{ pkColumn.python_field }} = row.{{ pkColumn.python_field }} || this.ids;
get{{ BusinessName }}({{ pkColumn.python_field }}).then(response => {
this.form = response.data;
{% for column in columns %}
{% if column.html_type == "checkbox" %}
this.form.{{ column.python_field }} = this.form.{{ column.python_field }}.split(",");
{% endif %}
{% endfor %}
{% if table.sub %}
this.{{ subclassName }}List = response.data.{{ subclassName }}List;
{% endif %}
this.open = true;
this.title = "修改{{ functionName }}";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
{% for column in columns %}
{% if column.html_type == "checkbox" %}
this.form.{{ column.python_field }} = this.form.{{ column.python_field }}.join(",");
{% endif %}
{% endfor %}
{% if table.sub %}
this.form.{{ subclassName }}List = this.{{ subclassName }}List;
{% endif %}
if (this.form.{{ pkColumn.python_field }} != null) {
update{{ BusinessName }}(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
add{{ BusinessName }}(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const {{ pkColumn.python_field }}s = row.{{ pkColumn.python_field }} || this.ids;
this.$modal.confirm('是否确认删除{{ functionName }}编号为"' + {{ pkColumn.python_field }}s + '"的数据项?').then(function() {
return del{{ BusinessName }}({{ pkColumn.python_field }}s);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
{% if table.sub %}
/** {{ subTable.functionName }}序号 */
row{{ subClassName }}Index({ row, rowIndex }) {
row.index = rowIndex + 1;
},
/** {{ subTable.functionName }}添加按钮操作 */
handleAdd{{ subClassName }}() {
let obj = {};
{% for column in subTable.columns %}
{% if column.pk or column.python_field == subTableFkclassName %}
{% elif column.list and column.python_field %}
obj.{{ column.python_field }} = "";
{% endif %}
{% endfor %}
this.{{ subclassName }}List.push(obj);
},
/** {{ subTable.functionName }}删除按钮操作 */
handleDelete{{ subClassName }}() {
if (this.checked{{ subClassName }}.length == 0) {
this.$modal.msgError("请先选择要删除的{{ subTable.functionName }}数据");
} else {
const {{ subclassName }}List = this.{{ subclassName }}List;
const checked{{ subClassName }} = this.checked{{ subClassName }};
this.{{ subclassName }}List = {{ subclassName }}List.filter(function(item) {
return checked{{ subClassName }}.indexOf(item.index) == -1
});
}
},
/** 复选框选中数据 */
handle{{ subClassName }}SelectionChange(selection) {
this.checked{{ subClassName }} = selection.map(item => item.index)
},
{% endif %}
/** 导出按钮操作 */
handleExport() {
this.download('{{ moduleName }}/{{ businessName }}/export', {
...this.queryParams
}, `{{ businessName }}_${new Date().getTime()}.xlsx`);
},
/** 是否渲染字段 */
renderField(insert, edit) {
return this.form.{{ pkColumn.python_field }} == null ? insert : edit;
}
},
};
</script>

View File

@@ -0,0 +1,463 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
{% for column in columns %}
{% if column.query %}
{% set dictType = column.dict_type %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.html_type == "input" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-input
v-model="queryParams.{{ column.python_field }}"
placeholder="请输入{{ comment }}"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dictType %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable style="width: 240px">
<el-option
v-for="dict in {{ dictType }}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and not dictType %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable style="width: 240px">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-date-picker
v-model="queryParams.{{ column.python_field }}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择{{ comment }}"
clearable
style="width: 240px"
/>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %}
<el-form-item label="{{ comment }}" style="width: 308px">
<el-date-picker
v-model="daterange{{ AttrName }}"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 240px"
/>
</el-form-item>
{% endif %}
{% endif %}
{% endfor %}
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Sort"
@click="toggleExpandAll"
>展开/折叠</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table
v-if="refreshTable"
v-loading="loading"
:data="{{ businessName }}List"
row-key="{{ treeCode }}"
:default-expand-all="isExpandAll"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
{% for column in columns %}
{% set pythonField = column.python_field %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.pk %}
{% elif column.list and column.html_type == "datetime" %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" width="180">
<template #default="scope">
<span>{% raw %}{{{% endraw %} parseTime(scope.row.{{ pythonField }}, '{y}-{m}-{d}') {% raw %}}}{% endraw %}</span>
</template>
</el-table-column>
{% elif column.list and column.html_type == "imageUpload" %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" width="100">
<template #default="scope">
<image-preview :src="scope.row.{{ pythonField }}" :width="50" :height="50"/>
</template>
</el-table-column>
{% elif column.list and column.dict_type %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}">
<template #default="scope">
{% if column.html_type == "checkbox" %}
<dict-tag :options="{{ column.dict_type }}" :value="scope.row.{{ pythonField }} ? scope.row.{{ pythonField }}.split(',') : []"/>
{% else %}
<dict-tag :options="{{ column.dict_type }}" :value="scope.row.{{ pythonField }}"/>
{% endif %}
</template>
</el-table-column>
{% elif column.list and pythonField %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" />
{% endif %}
{% endfor %}
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['{{ moduleName }}:{{ businessName }}:edit']">修改</el-button>
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['{{ moduleName }}:{{ businessName }}:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改{{ functionName }}对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="{{ businessName }}Ref" :model="form" :rules="rules" label-width="80px">
{% for column in columns %}
{% set field = column.python_field %}
{% if (column.insert or column.edit) and not column.pk and column.column_name not in column_not_add_show + column_not_edit_show %}
{% if column.usable_column or not column.super_column %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% set dictType = column.dict_type %}
{% if treeParentCode and column.python_field == treeParentCode %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ treeParentCode }}">
<el-tree-select
v-model="form.{{ treeParentCode }}"
:data="{{ businessName }}Options"
:props="{ value: '{{ treeCode }}', label: '{{ treeName }}', children: 'children' }"
value-key="{{ treeCode }}"
placeholder="请选择{{ comment }}"
check-strictly
/>
</el-form-item>
{% elif column.html_type == "input" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-input v-model="form.{{ field }}" placeholder="请输入{{ comment }}" />
</el-form-item>
{% elif column.html_type == "imageUpload" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<image-upload v-model="form.{{ field }}"/>
</el-form-item>
{% elif column.html_type == "fileUpload" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<file-upload v-model="form.{{ field }}"/>
</el-form-item>
{% elif column.html_type == "editor" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<editor v-model="form.{{ field }}" :min-height="192"/>
</el-form-item>
{% elif column.html_type == "select" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
<el-option
v-for="dict in {{ dictType }}"
:key="dict.value"
:label="dict.label"
{% if column.python_type == 'int' %}
:value="parseInt(dict.value)"
{% else %}
:value="dict.value"
{% endif %}
></el-option>
</el-select>
</el-form-item>
{% elif column.html_type == "select" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "checkbox" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-checkbox-group v-model="form.{{ field }}">
<el-checkbox
v-for="dict in {{ dictType }}"
:key="dict.value"
:label="dict.value">
{% raw %}{{{% endraw %} dict.label {% raw %}}}{% endraw %}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
{% elif column.html_type == "checkbox" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-checkbox-group v-model="form.{{ field }}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
{% elif column.html_type == "radio" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-radio-group v-model="form.{{ field }}">
<el-radio
v-for="dict in {{ dictType }}"
:key="dict.value"
{% if column.python_type == 'int' %}
:label="parseInt(dict.value)"
{% else %}
:label="dict.value"
{% endif %}>
{% raw %}{{{% endraw %} dict.label {% raw %}}}{% endraw %}
</el-radio>
</el-radio-group>
</el-form-item>
{% elif column.html_type == "radio" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-radio-group v-model="form.{{ field }}">
<el-radio label="请选择字典生成" value="" />
</el-radio-group>
</el-form-item>
{% elif column.html_type == "datetime" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-date-picker clearable
v-model="form.{{ field }}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择{{ comment }}">
</el-date-picker>
</el-form-item>
{% elif column.html_type == "textarea" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-input v-model="form.{{ field }}" type="textarea" placeholder="请输入内容" />
</el-form-item>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="{{ BusinessName }}">
import { list{{ BusinessName }}, get{{ BusinessName }}, del{{ BusinessName }}, add{{ BusinessName }}, update{{ BusinessName }} } from "@/api/{{ moduleName }}/{{ businessName }}";
const { proxy } = getCurrentInstance();
{% if dicts %}
{% set dictsNoSymbol = dicts.replace("'", "") %}
const { {{ dictsNoSymbol }} } = proxy.useDict({{ dicts }});
{% endif %}
const {{ businessName }}List = ref([]);
const {{ businessName }}Options = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const title = ref("");
const isExpandAll = ref(true);
const refreshTable = ref(true);
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
const daterange{{ AttrName }} = ref([]);
{% endif %}
{% endfor %}
const data = reactive({
form: {},
queryParams: {
{% for column in columns %}
{% if column.query %}
{{ column.python_field }}: null,
{% endif %}
{% endfor %}
},
rules: {
{% for column in columns %}
{% if column.required %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{{ column.python_field }}: [
{ required: true, message: "{{ comment }}不能为空", trigger: "{% if column.html_type == 'select' or column.html_type == 'radio' %}change{% else %}blur{% endif %}" }
],
{% endif %}
{% endfor %}
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询{{ functionName }}列表 */
function getList() {
loading.value = true;
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
queryParams.value.params = {};
{% endif %}
{% endfor %}
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
if (null != daterange{{ AttrName }} && '' != daterange{{ AttrName }}) {
queryParams.value.begin{{ AttrName }} = daterange{{ AttrName }}.value[0];
queryParams.value.end{{ AttrName }} = daterange{{ AttrName }}.value[1];
}
{% endif %}
{% endfor %}
list{{ BusinessName }}(queryParams.value).then(response => {
{{ businessName }}List.value = proxy.handleTree(response.data, "{{ treeCode }}", "{{ treeParentCode }}");
loading.value = false;
});
}
/** 查询{{ functionName }}下拉树结构 */
function getTreeselect() {
list{{ BusinessName }}().then(response => {
{{ businessName }}Options.value = [];
const data = { {{ treeCode }}: 0, {{ treeName }}: '顶级节点', children: [] };
data.children = proxy.handleTree(response.data, "{{ treeCode }}", "{{ treeParentCode }}");
{{ businessName }}Options.value.push(data);
});
}
/** 取消按钮 */
function cancel() {
open.value = false;
reset();
}
/** 表单重置 */
function reset() {
form.value = {
{% for column in columns %}
{% if column.html_type == "checkbox" %}
{{ column.python_field }}: [],
{% else %}
{{ column.python_field }}: null,
{% endif %}
{% endfor %}
};
proxy.resetForm("{{ businessName }}Ref");
}
/** 搜索按钮操作 */
function handleQuery() {
getList();
}
/** 重置按钮操作 */
function resetQuery() {
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
daterange{{ AttrName }}.value = [];
{% endif %}
{% endfor %}
proxy.resetForm("queryRef");
handleQuery();
}
/** 新增按钮操作 */
function handleAdd(row) {
reset();
getTreeselect();
if (row != null && row.{{ treeCode }}) {
form.value.{{ treeParentCode }} = row.{{ treeCode }};
} else {
form.value.{{ treeParentCode }} = 0;
}
open.value = true;
title.value = "添加{{ functionName }}";
}
/** 展开/折叠操作 */
function toggleExpandAll() {
refreshTable.value = false;
isExpandAll.value = !isExpandAll.value;
nextTick(() => {
refreshTable.value = true;
});
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
getTreeselect();
if (row != null) {
form.value.{{ treeParentCode }} = row.{{ treeParentCode }};
}
get{{ BusinessName }}(row.{{ pkColumn.python_field }}).then(response => {
form.value = response.data;
{% for column in columns %}
{% if column.html_type == "checkbox" %}
form.value.{{ column.python_field }} = form.value.{{ column.python_field }}.split(",");
{% endif %}
{% endfor %}
open.value = true;
title.value = "修改{{ functionName }}";
});
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["{{ businessName }}Ref"].validate(valid => {
if (valid) {
{% for column in columns %}
{% if column.html_type == "checkbox" %}
form.value.{{ column.python_field }} = form.value.{{ column.python_field }}.join(",");
{% endif %}
{% endfor %}
if (form.value.{{ pkColumn.python_field }} != null) {
update{{ BusinessName }}(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
add{{ BusinessName }}(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$modal.confirm('是否确认删除{{ functionName }}编号为"' + row.{{ pkColumn.python_field }} + '"的数据项?').then(function() {
return del{{ BusinessName }}(row.{{ pkColumn.python_field }});
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
/** 是否渲染字段 */
function renderField(insert, edit) {
return form.value.{{ pkColumn.python_field }} == null ? insert : edit;
}
getList();
</script>

View File

@@ -0,0 +1,580 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
{% for column in columns %}
{% if column.query %}
{% set dictType = column.dict_type %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.html_type == "input" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-input
v-model="queryParams.{{ column.python_field }}"
placeholder="请输入{{ comment }}"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dictType %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable style="width: 240px">
<el-option
v-for="dict in {{ dictType }}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and not dictType %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-select v-model="queryParams.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable style="width: 240px">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-date-picker
v-model="queryParams.{{ column.python_field }}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择{{ comment }}"
clearable
style="width: 240px"
/>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type == "BETWEEN" %}
<el-form-item label="{{ comment }}" style="width: 308px">
<el-date-picker
v-model="daterange{{ AttrName }}"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 240px"
/>
</el-form-item>
{% endif %}
{% endif %}
{% endfor %}
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:export']"
>导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="{{ businessName }}List" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
{% for column in columns %}
{% set pythonField = column.python_field %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.pk %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" />
{% elif column.list and column.html_type == "datetime" %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" width="180">
<template #default="scope">
<span>{% raw %}{{{% endraw %} parseTime(scope.row.{{ pythonField }}, '{y}-{m}-{d}') {% raw %}}}{% endraw %}</span>
</template>
</el-table-column>
{% elif column.list and column.html_type == "imageUpload" %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" width="100">
<template #default="scope">
<image-preview :src="scope.row.{{ pythonField }}" :width="50" :height="50"/>
</template>
</el-table-column>
{% elif column.list and column.dict_type %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}">
<template #default="scope">
{% if column.html_type == "checkbox" %}
<dict-tag :options="{{ column.dict_type }}" :value="scope.row.{{ pythonField }} ? scope.row.{{ pythonField }}.split(',') : []"/>
{% else %}
<dict-tag :options="{{ column.dict_type }}" :value="scope.row.{{ pythonField }}"/>
{% endif %}
</template>
</el-table-column>
{% elif column.list and pythonField %}
<el-table-column label="{{ comment }}" align="center" prop="{{ pythonField }}" />
{% endif %}
{% endfor %}
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['{{ moduleName }}:{{ businessName }}:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['{{ moduleName }}:{{ businessName }}:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改{{ functionName }}对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="{{ businessName }}Ref" :model="form" :rules="rules" label-width="80px">
{% for column in columns %}
{% set field = column.python_field %}
{% if (column.insert or column.edit) and not column.pk and column.column_name not in column_not_add_show + column_not_edit_show %}
{% if column.usable_column or not column.super_column %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% set dictType = column.dict_type %}
{% if column.html_type == "input" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-input v-model="form.{{ field }}" placeholder="请输入{{ comment }}" />
</el-form-item>
{% elif column.html_type == "imageUpload" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<image-upload v-model="form.{{ field }}"/>
</el-form-item>
{% elif column.html_type == "fileUpload" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<file-upload v-model="form.{{ field }}"/>
</el-form-item>
{% elif column.html_type == "editor" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<editor v-model="form.{{ field }}" :min-height="192"/>
</el-form-item>
{% elif column.html_type == "select" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
<el-option
v-for="dict in {{ dictType }}"
:key="dict.value"
:label="dict.label"
{% if column.python_type == 'int' %}
:value="parseInt(dict.value)"
{% else %}
:value="dict.value"
{% endif %}
></el-option>
</el-select>
</el-form-item>
{% elif column.html_type == "select" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "checkbox" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-checkbox-group v-model="form.{{ field }}">
<el-checkbox
v-for="dict in {{ dictType }}"
:key="dict.value"
:label="dict.value">
{{ dict.label }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
{% elif column.html_type == "checkbox" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-checkbox-group v-model="form.{{ field }}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
{% elif column.html_type == "radio" and dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-radio-group v-model="form.{{ field }}">
<el-radio
v-for="dict in {{ dictType }}"
:key="dict.value"
{% if column.python_type == 'int' %}
:label="parseInt(dict.value)"
{% else %}
:label="dict.value"
{% endif %}>
{% raw %}{{{% endraw %} dict.label {% raw %}}}{% endraw %}
</el-radio>
</el-radio-group>
</el-form-item>
{% elif column.html_type == "radio" and not dictType %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-radio-group v-model="form.{{ field }}">
<el-radio label="请选择字典生成" value="" />
</el-radio-group>
</el-form-item>
{% elif column.html_type == "datetime" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-date-picker clearable
v-model="form.{{ field }}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择{{ comment }}">
</el-date-picker>
</el-form-item>
{% elif column.html_type == "textarea" %}
<el-form-item v-if="renderField({{ column.insert | lower }}, {{ column.edit | lower }})" label="{{ comment }}" prop="{{ field }}">
<el-input v-model="form.{{ field }}" type="textarea" placeholder="请输入内容" />
</el-form-item>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% if table.sub %}
<el-divider content-position="center">{{ subTable.functionName }}信息</el-divider>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" icon="Plus" @click="handleAdd{{ subClassName }}">添加</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" icon="Delete" @click="handleDelete{{ subClassName }}">删除</el-button>
</el-col>
</el-row>
<el-table :data="{{ subclassName }}List" :row-class-name="row{{ subClassName }}Index" @selection-change="handle{{ subClassName }}SelectionChange" ref="{{ subclassName }}">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" align="center" prop="index" width="50"/>
{% for column in subTable.columns %}
{% set pythonField = column.python_field %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{% if column.pk or pythonField == subTableFkclassName %}
{% elif column.list and column.html_type == 'input' %}
<el-table-column label="{{ comment }}" prop="{{ pythonField }}" width="150">
<template #default="scope">
<el-input v-model="scope.row.{{ pythonField }}" placeholder="请输入{{ comment }}" />
</template>
</el-table-column>
{% elif column.list and column.html_type == 'datetime' %}
<el-table-column label="{{ comment }}" prop="{{ pythonField }}" width="240">
<template #default="scope">
<el-date-picker clearable
v-model="scope.row.{{ pythonField }}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择{{ comment }}">
</el-date-picker>
</template>
</el-table-column>
{% elif column.list and (column.html_type == 'select' or column.html_type == 'radio') and column.dict_type %}
<el-table-column label="{{ comment }}" prop="{{ pythonField }}" width="150">
<template #default="scope">
<el-select v-model="scope.row.{{ pythonField }}" placeholder="请选择{{ comment }}">
<el-option
v-for="dict in {{ column.dict_type }}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</template>
</el-table-column>
{% elif column.list and (column.html_type == 'select' or column.html_type == 'radio') and not column.dict_type %}
<el-table-column label="{{ comment }}" prop="{{ pythonField }}" width="150">
<template #default="scope">
<el-select v-model="scope.row.{{ pythonField }}" placeholder="请选择{{ comment }}">
<el-option label="请选择字典生成" value="" />
</el-select>
</template>
</el-table-column>
{% endif %}
{% endfor %}
</el-table>
{% endif %}
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="{{ BusinessName }}">
import { list{{ BusinessName }}, get{{ BusinessName }}, del{{ BusinessName }}, add{{ BusinessName }}, update{{ BusinessName }} } from "@/api/{{ moduleName }}/{{ businessName }}";
const { proxy } = getCurrentInstance();
{% if dicts %}
{% set dictsNoSymbol = dicts.replace("'", "") %}
const { {{ dictsNoSymbol }} } = proxy.useDict({{ dicts }});
{% endif %}
const {{ businessName }}List = ref([]);
{% if table.sub %}
const {{ subclassName }}List = ref([]);
{% endif %}
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
{% if table.sub %}
const checked{{ subClassName }} = ref([]);
{% endif %}
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
const daterange{{ AttrName }} = ref([]);
{% endif %}
{% endfor %}
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
{% for column in columns %}
{% if column.query %}
{{ column.python_field }}: null,
{% endif %}
{% endfor %}
},
rules: {
{% for column in columns %}
{% if column.required %}
{% set parentheseIndex = column.column_comment.find("") %}
{% set comment = column.column_comment[:parentheseIndex] if parentheseIndex != -1 else column.column_comment %}
{{ column.python_field }}: [
{ required: true, message: "{{ comment }}不能为空", trigger: "{% if column.html_type == 'select' or column.html_type == 'radio' %}change{% else %}blur{% endif %}" }
],
{% endif %}
{% endfor %}
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询{{ functionName }}列表 */
function getList() {
loading.value = true;
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
queryParams.value.params = {};
{% endif %}
{% endfor %}
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
if (null != daterange{{ AttrName }} && '' != daterange{{ AttrName }}) {
queryParams.value.begin{{ AttrName }} = daterange{{ AttrName }}.value[0];
queryParams.value.end{{ AttrName }} = daterange{{ AttrName }}.value[1];
}
{% endif %}
{% endfor %}
list{{ BusinessName }}(queryParams.value).then(response => {
{{ businessName }}List.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
/** 取消按钮 */
function cancel() {
open.value = false;
reset();
}
/** 表单重置 */
function reset() {
form.value = {
{% for column in columns %}
{% if column.html_type == "checkbox" %}
{{ column.python_field }}: [],
{% else %}
{{ column.python_field }}: null,
{% endif %}
{% endfor %}
};
{% if table.sub %}
{{ subclassName }}List.value = [];
{% endif %}
proxy.resetForm("{{ businessName }}Ref");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
{% for column in columns %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set AttrName = column.python_field[0] | upper + column.python_field[1:] %}
daterange{{ AttrName }}.value = [];
{% endif %}
{% endfor %}
proxy.resetForm("queryRef");
handleQuery();
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.{{ pkColumn.python_field }});
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加{{ functionName }}";
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _{{ pkColumn.python_field }} = row.{{ pkColumn.python_field }} || ids.value;
get{{ BusinessName }}(_{{ pkColumn.python_field }}).then(response => {
form.value = response.data;
{% for column in columns %}
{% if column.html_type == "checkbox" %}
form.value.{{ column.python_field }} = form.value.{{ column.python_field }}.split(",");
{% endif %}
{% endfor %}
{% if table.sub %}
{{ subclassName }}List.value = response.data.{{ subclassName }}List;
{% endif %}
open.value = true;
title.value = "修改{{ functionName }}";
});
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["{{ businessName }}Ref"].validate(valid => {
if (valid) {
{% for column in columns %}
{% if column.html_type == "checkbox" %}
form.value.{{ column.python_field }} = form.value.{{ column.python_field }}.join(",");
{% endif %}
{% endfor %}
{% if table.sub %}
form.value.{{ subclassName }}List = {{ subclassName }}List.value;
{% endif %}
if (form.value.{{ pkColumn.python_field }} != null) {
update{{ BusinessName }}(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
add{{ BusinessName }}(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
const _{{ pkColumn.python_field }}s = row.{{ pkColumn.python_field }} || ids.value;
proxy.$modal.confirm('是否确认删除{{ functionName }}编号为"' + _{{ pkColumn.python_field }}s + '"的数据项?').then(function() {
return del{{ BusinessName }}(_{{ pkColumn.python_field }}s);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
{% if table.sub %}
/** {{ subTable.functionName }}序号 */
function row{{ subClassName }}Index({ row, rowIndex }) {
row.index = rowIndex + 1;
}
/** {{ subTable.functionName }}添加按钮操作 */
function handleAdd{{ subClassName }}() {
let obj = {};
{% for column in subTable.columns %}
{% if column.pk or column.python_field == subTableFkclassName %}
{% elif column.list and column.python_field %}
obj.{{ column.python_field }} = "";
{% endif %}
{% endfor %}
{{ subclassName }}List.value.push(obj);
}
/** {{ subTable.functionName }}删除按钮操作 */
function handleDelete{{ subClassName }}() {
if (checked{{ subClassName }}.value.length == 0) {
proxy.$modal.msgError("请先选择要删除的{{ subTable.functionName }}数据");
} else {
const {{ subclassName }}s = {{ subclassName }}List.value;
const checked{{ subClassName }}s = checked{{ subClassName }}.value;
{{ subclassName }}List.value = {{ subclassName }}s.filter(function(item) {
return checked{{ subClassName }}s.indexOf(item.index) == -1
});
}
}
/** 复选框选中数据 */
function handle{{ subClassName }}SelectionChange(selection) {
checked{{ subClassName }}.value = selection.map(item => item.index)
}
{% endif %}
/** 导出按钮操作 */
function handleExport() {
proxy.download('{{ moduleName }}/{{ businessName }}/export', {
...queryParams.value
}, `{{ businessName }}_${new Date().getTime()}.xlsx`);
}
/** 是否渲染字段 */
function renderField(insert, edit) {
return form.value.{{ pkColumn.python_field }} == null ? insert : edit;
}
getList();
</script>

View File

@@ -2,6 +2,18 @@ from datetime import datetime
def job(*args, **kwargs):
"""
定时任务执行同步函数示例
"""
print(args)
print(kwargs)
print(f'{datetime.now()}执行了')
print(f'{datetime.now()}同步函数执行了')
async def async_job(*args, **kwargs):
"""
定时任务执行异步函数示例
"""
print(args)
print(kwargs)
print(f'{datetime.now()}异步函数执行了')

View File

@@ -0,0 +1,18 @@
APScheduler==3.11.0
asyncpg==0.30.0
DateTime==5.5
fastapi[all]==0.115.8
loguru==0.7.3
openpyxl==3.1.5
pandas==2.2.3
passlib[bcrypt]==1.7.4
Pillow==11.1.0
psutil==7.0.0
pydantic-validation-decorator==0.1.4
PyJWT[crypto]==2.10.1
psycopg2==2.9.10
redis==5.2.1
requests==2.32.3
SQLAlchemy[asyncio]==2.0.38
sqlglot[rs]==26.6.0
user-agents==2.2.0

View File

@@ -1,17 +1,18 @@
APScheduler==3.10.4
asyncmy==0.2.9
APScheduler==3.11.0
asyncmy==0.2.10
DateTime==5.5
fastapi[all]==0.115.0
loguru==0.7.2
fastapi[all]==0.115.8
loguru==0.7.3
openpyxl==3.1.5
pandas==2.2.2
pandas==2.2.3
passlib[bcrypt]==1.7.4
Pillow==10.4.0
psutil==6.0.0
Pillow==11.1.0
psutil==7.0.0
pydantic-validation-decorator==0.1.4
PyJWT[crypto]==2.8.0
PyJWT[crypto]==2.10.1
PyMySQL==1.1.1
redis==5.0.7
redis==5.2.1
requests==2.32.3
SQLAlchemy[asyncio]==2.0.31
SQLAlchemy[asyncio]==2.0.38
sqlglot[rs]==26.6.0
user-agents==2.2.0

View File

@@ -22,6 +22,7 @@ from module_admin.controller.post_controler import postController
from module_admin.controller.role_controller import roleController
from module_admin.controller.server_controller import serverController
from module_admin.controller.user_controller import userController
from module_generator.controller.gen_controller import genController
from sub_applications.handle import handle_sub_applications
from utils.common_util import worship
from utils.log_util import logger
@@ -77,6 +78,7 @@ controller_list = [
{'router': serverController, 'tags': ['系统监控-菜单管理']},
{'router': cacheController, 'tags': ['系统监控-缓存监控']},
{'router': commonController, 'tags': ['通用模块']},
{'router': genController, 'tags': ['代码生成']},
]
for controller in controller_list:

File diff suppressed because it is too large Load Diff

View File

@@ -691,11 +691,12 @@ create table gen_table_column (
column_name varchar(200) comment '列名称',
column_comment varchar(500) comment '列描述',
column_type varchar(100) comment '列类型',
java_type varchar(500) comment 'JAVA类型',
java_field varchar(200) comment 'JAVA字段名',
python_type varchar(500) comment 'PYTHON类型',
python_field varchar(200) comment 'PYTHON字段名',
is_pk char(1) comment '是否主键1是',
is_increment char(1) comment '是否自增1是',
is_required char(1) comment '是否必填1是',
is_unique char(1) comment '是否唯一1是',
is_insert char(1) comment '是否为插入字段1是',
is_edit char(1) comment '是否编辑字段1是',
is_list char(1) comment '是否列表字段1是',

View File

@@ -7,7 +7,9 @@ from openpyxl.styles import Alignment, PatternFill
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.datavalidation import DataValidation
from sqlalchemy.engine.row import Row
from typing import List
from sqlalchemy.orm.collections import InstrumentedList
from typing import Any, Dict, List, Literal, Union
from config.database import Base
from config.env import CachePathConfig
@@ -38,13 +40,74 @@ def worship():
""")
class SqlalchemyUtil:
"""
sqlalchemy工具类
"""
@classmethod
def base_to_dict(
cls, obj: Union[Base, Dict], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case'
):
"""
将sqlalchemy模型对象转换为字典
:param obj: sqlalchemy模型对象或普通字典
:param transform_case: 转换得到的结果形式,可选的有'no_case'(不转换)、'snake_to_camel'(下划线转小驼峰)、'camel_to_snake'(小驼峰转下划线),默认为'no_case'
:return: 字典结果
"""
if isinstance(obj, Base):
base_dict = obj.__dict__.copy()
base_dict.pop('_sa_instance_state', None)
for name, value in base_dict.items():
if isinstance(value, InstrumentedList):
base_dict[name] = cls.serialize_result(value, 'snake_to_camel')
elif isinstance(obj, dict):
base_dict = obj.copy()
if transform_case == 'snake_to_camel':
return {CamelCaseUtil.snake_to_camel(k): v for k, v in base_dict.items()}
elif transform_case == 'camel_to_snake':
return {SnakeCaseUtil.camel_to_snake(k): v for k, v in base_dict.items()}
return base_dict
@classmethod
def serialize_result(
cls, result: Any, transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case'
):
"""
将sqlalchemy查询结果序列化
:param result: sqlalchemy查询结果
:param transform_case: 转换得到的结果形式,可选的有'no_case'(不转换)、'snake_to_camel'(下划线转小驼峰)、'camel_to_snake'(小驼峰转下划线),默认为'no_case'
:return: 序列化结果
"""
if isinstance(result, (Base, dict)):
return cls.base_to_dict(result, transform_case)
elif isinstance(result, list):
return [cls.serialize_result(row, transform_case) for row in result]
elif isinstance(result, Row):
if all([isinstance(row, Base) for row in result]):
return [cls.base_to_dict(row, transform_case) for row in result]
elif any([isinstance(row, Base) for row in result]):
return [cls.serialize_result(row, transform_case) for row in result]
else:
result_dict = result._asdict()
if transform_case == 'snake_to_camel':
return {CamelCaseUtil.snake_to_camel(k): v for k, v in result_dict.items()}
elif transform_case == 'camel_to_snake':
return {SnakeCaseUtil.camel_to_snake(k): v for k, v in result_dict.items()}
return result_dict
return result
class CamelCaseUtil:
"""
下划线形式(snake_case)转小驼峰形式(camelCase)工具方法
"""
@classmethod
def snake_to_camel(cls, snake_str):
def snake_to_camel(cls, snake_str: str):
"""
下划线形式字符串(snake_case)转换为小驼峰形式字符串(camelCase)
@@ -57,41 +120,14 @@ class CamelCaseUtil:
return words[0] + ''.join(word.capitalize() for word in words[1:])
@classmethod
def transform_result(cls, result):
def transform_result(cls, result: Any):
"""
针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法
:param result: 输入数据
:return: 小驼峰形式结果
"""
if result is None:
return result
# 如果是字典,直接转换键
elif isinstance(result, dict):
return {cls.snake_to_camel(k): v for k, v in result.items()}
# 如果是一组字典或其他类型的列表,遍历列表进行转换
elif isinstance(result, list):
return [
cls.transform_result(row)
if isinstance(row, (dict, Row))
else (
cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row
)
for row in result
]
# 如果是sqlalchemy的Row实例遍历Row进行转换
elif isinstance(result, Row):
return [
cls.transform_result(row)
if isinstance(row, dict)
else (
cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row
)
for row in result
]
# 如果是其他类型,如模型实例,先转换为字典
else:
return cls.transform_result({c.name: getattr(result, c.name) for c in result.__table__.columns})
return SqlalchemyUtil.serialize_result(result=result, transform_case='snake_to_camel')
class SnakeCaseUtil:
@@ -100,7 +136,7 @@ class SnakeCaseUtil:
"""
@classmethod
def camel_to_snake(cls, camel_str):
def camel_to_snake(cls, camel_str: str):
"""
小驼峰形式字符串(camelCase)转换为下划线形式字符串(snake_case)
@@ -112,41 +148,14 @@ class SnakeCaseUtil:
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', words).lower()
@classmethod
def transform_result(cls, result):
def transform_result(cls, result: Any):
"""
针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法
:param result: 输入数据
:return: 小驼峰形式结果
"""
if result is None:
return result
# 如果是字典,直接转换键
elif isinstance(result, dict):
return {cls.camel_to_snake(k): v for k, v in result.items()}
# 如果是一组字典或其他类型的列表,遍历列表进行转换
elif isinstance(result, list):
return [
cls.transform_result(row)
if isinstance(row, (dict, Row))
else (
cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row
)
for row in result
]
# 如果是sqlalchemy的Row实例遍历Row进行转换
elif isinstance(result, Row):
return [
cls.transform_result(row)
if isinstance(row, dict)
else (
cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row
)
for row in result
]
# 如果是其他类型,如模型实例,先转换为字典
else:
return cls.transform_result({c.name: getattr(result, c.name) for c in result.__table__.columns})
return SqlalchemyUtil.serialize_result(result=result, transform_case='camel_to_snake')
def bytes2human(n, format_str='%(value).1f%(symbol)s'):

View File

@@ -0,0 +1,104 @@
import io
import pandas as pd
from openpyxl import Workbook
from openpyxl.styles import Alignment, PatternFill
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.datavalidation import DataValidation
from typing import Dict, List
class ExcelUtil:
"""
Excel操作类
"""
@classmethod
def __mapping_list(cls, list_data: List, mapping_dict: Dict):
"""
工具方法将list数据中的字段名映射为对应的中文字段名
:param list_data: 数据列表
:param mapping_dict: 映射字典
:return: 映射后的数据列表
"""
mapping_data = [{mapping_dict.get(key): item.get(key) for key in mapping_dict} for item in list_data]
return mapping_data
@classmethod
def export_list2excel(cls, list_data: List, mapping_dict: Dict):
"""
工具方法将需要导出的list数据转化为对应excel的二进制数据
:param list_data: 数据列表
:param mapping_dict: 映射字典
:return: list数据对应excel的二进制数据
"""
mapping_data = cls.__mapping_list(list_data, mapping_dict)
df = pd.DataFrame(mapping_data)
binary_data = io.BytesIO()
df.to_excel(binary_data, index=False, engine='openpyxl')
binary_data = binary_data.getvalue()
return binary_data
@classmethod
def get_excel_template(cls, header_list: List, selector_header_list: List, option_list: List[Dict]):
"""
工具方法将需要导出的list数据转化为对应excel的二进制数据
:param header_list: 表头数据列表
:param selector_header_list: 需要设置为选择器格式的表头数据列表
:param option_list: 选择器格式的表头预设的选项列表
:return: 模板excel的二进制数据
"""
# 创建Excel工作簿
wb = Workbook()
# 选择默认的活动工作表
ws = wb.active
# 设置表头文字
headers = header_list
# 设置表头背景样式为灰色,前景色为白色
header_fill = PatternFill(start_color='ababab', end_color='ababab', fill_type='solid')
# 将表头写入第一行
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col_num)
cell.value = header
cell.fill = header_fill
# 设置列宽度为16
ws.column_dimensions[chr(64 + col_num)].width = 12
# 设置水平居中对齐
cell.alignment = Alignment(horizontal='center')
# 设置选择器的预设选项
options = option_list
# 获取selector_header的字母索引
for selector_header in selector_header_list:
column_selector_header_index = headers.index(selector_header) + 1
# 创建数据有效性规则
header_option = []
for option in options:
if option.get(selector_header):
header_option = option.get(selector_header)
dv = DataValidation(type='list', formula1=f'"{",".join(header_option)}"')
# 设置数据有效性规则的起始单元格和结束单元格
dv.add(
f'{get_column_letter(column_selector_header_index)}2:{get_column_letter(column_selector_header_index)}1048576'
)
# 添加数据有效性规则到工作表
ws.add_data_validation(dv)
# 保存Excel文件为字节类型的数据
file = io.BytesIO()
wb.save(file)
file.seek(0)
# 读取字节数据
excel_data = file.getvalue()
return excel_data

View File

@@ -0,0 +1,223 @@
import re
from datetime import datetime
from typing import List
from config.constant import GenConstant
from config.env import GenConfig
from module_generator.entity.vo.gen_vo import GenTableColumnModel, GenTableModel
from utils.string_util import StringUtil
class GenUtils:
"""代码生成器工具类"""
@classmethod
def init_table(cls, gen_table: GenTableModel, oper_name: str) -> None:
"""
初始化表信息
param gen_table: 业务表对象
param oper_name: 操作人
:return:
"""
gen_table.class_name = cls.convert_class_name(gen_table.table_name)
gen_table.package_name = GenConfig.package_name
gen_table.module_name = cls.get_module_name(GenConfig.package_name)
gen_table.business_name = cls.get_business_name(gen_table.table_name)
gen_table.function_name = cls.replace_text(gen_table.table_comment)
gen_table.function_author = GenConfig.author
gen_table.create_by = oper_name
gen_table.create_time = datetime.now()
gen_table.update_by = oper_name
gen_table.update_time = datetime.now()
@classmethod
def init_column_field(cls, column: GenTableColumnModel, table: GenTableModel) -> None:
"""
初始化列属性字段
param column: 业务表字段对象
param table: 业务表对象
:return:
"""
data_type = cls.get_db_type(column.column_type)
column_name = column.column_name
column.table_id = table.table_id
column.create_by = table.create_by
# 设置Python字段名
column.python_field = cls.to_camel_case(column_name)
# 设置默认类型
column.python_type = StringUtil.get_mapping_value_by_key_ignore_case(
GenConstant.DB_TO_PYTHON_TYPE_MAPPING, data_type
)
column.query_type = GenConstant.QUERY_EQ
if cls.arrays_contains(GenConstant.COLUMNTYPE_STR, data_type) or cls.arrays_contains(
GenConstant.COLUMNTYPE_TEXT, data_type
):
# 字符串长度超过500设置为文本域
column_length = cls.get_column_length(column.column_type)
html_type = (
GenConstant.HTML_TEXTAREA
if column_length >= 500 or cls.arrays_contains(GenConstant.COLUMNTYPE_TEXT, data_type)
else GenConstant.HTML_INPUT
)
column.html_type = html_type
elif cls.arrays_contains(GenConstant.COLUMNTYPE_TIME, data_type):
column.html_type = GenConstant.HTML_DATETIME
elif cls.arrays_contains(GenConstant.COLUMNTYPE_NUMBER, data_type):
column.html_type = GenConstant.HTML_INPUT
# 插入字段(默认所有字段都需要插入)
column.is_insert = GenConstant.REQUIRE
# 编辑字段
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_EDIT, column_name) and not column.pk:
column.is_edit = GenConstant.REQUIRE
# 列表字段
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_LIST, column_name) and not column.pk:
column.is_list = GenConstant.REQUIRE
# 查询字段
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_QUERY, column_name) and not column.pk:
column.is_query = GenConstant.REQUIRE
# 查询字段类型
if column_name.lower().endswith('name'):
column.query_type = GenConstant.QUERY_LIKE
# 状态字段设置单选框
if column_name.lower().endswith('status'):
column.html_type = GenConstant.HTML_RADIO
# 类型&性别字段设置下拉框
elif column_name.lower().endswith('type') or column_name.lower().endswith('sex'):
column.html_type = GenConstant.HTML_SELECT
# 图片字段设置图片上传控件
elif column_name.lower().endswith('image'):
column.html_type = GenConstant.HTML_IMAGE_UPLOAD
# 文件字段设置文件上传控件
elif column_name.lower().endswith('file'):
column.html_type = GenConstant.HTML_FILE_UPLOAD
# 内容字段设置富文本控件
elif column_name.lower().endswith('content'):
column.html_type = GenConstant.HTML_EDITOR
column.create_by = table.create_by
column.create_time = datetime.now()
column.update_by = table.update_by
column.update_time = datetime.now()
@classmethod
def arrays_contains(cls, arr: List[str], target_value: str) -> bool:
"""
校验数组是否包含指定值
param arr: 数组
param target_value: 需要校验的值
:return: 校验结果
"""
return target_value in arr
@classmethod
def get_module_name(cls, package_name: str) -> str:
"""
获取模块名
param package_name: 包名
:return: 模块名
"""
return package_name.split('.')[-1]
@classmethod
def get_business_name(cls, table_name: str) -> str:
"""
获取业务名
param table_name: 业务表名
:return: 业务名
"""
return table_name.split('_')[-1]
@classmethod
def convert_class_name(cls, table_name: str) -> str:
"""
表名转换成Python类名
param table_name: 业务表名
:return: Python类名
"""
auto_remove_pre = GenConfig.auto_remove_pre
table_prefix = GenConfig.table_prefix
if auto_remove_pre and table_prefix:
search_list = table_prefix.split(',')
table_name = cls.replace_first(table_name, search_list)
return StringUtil.convert_to_camel_case(table_name)
@classmethod
def replace_first(cls, replacement: str, search_list: List[str]) -> str:
"""
批量替换前缀
param replacement: 需要被替换的字符串
param search_list: 可替换的字符串列表
:return: 替换后的字符串
"""
for search_string in search_list:
if replacement.startswith(search_string):
return replacement.replace(search_string, '', 1)
return replacement
@classmethod
def replace_text(cls, text: str) -> str:
"""
关键字替换
param text: 需要被替换的字符串
:return: 替换后的字符串
"""
return re.sub(r'(?:表|若依)', '', text)
@classmethod
def get_db_type(cls, column_type: str) -> str:
"""
获取数据库类型字段
param column_type: 字段类型
:return: 数据库类型
"""
if '(' in column_type:
return column_type.split('(')[0]
return column_type
@classmethod
def get_column_length(cls, column_type: str) -> int:
"""
获取字段长度
param column_type: 字段类型
:return: 字段长度
"""
if '(' in column_type:
length = len(column_type.split('(')[1].split(')')[0])
return length
return 0
@classmethod
def split_column_type(cls, column_type: str) -> List[str]:
"""
拆分列类型
param column_type: 字段类型
:return: 拆分结果
"""
if '(' in column_type and ')' in column_type:
return column_type.split('(')[1].split(')')[0].split(',')
return []
@classmethod
def to_camel_case(cls, text: str) -> str:
"""
将字符串转换为驼峰命名
param text: 需要转换的字符串
:return: 驼峰命名
"""
parts = text.split('_')
return parts[0] + ''.join(word.capitalize() for word in parts[1:])

View File

@@ -1,11 +1,60 @@
import os
import sys
import time
from loguru import logger
from loguru import logger as _logger
from typing import Dict
from middlewares.trace_middleware import TraceCtx
log_path = os.path.join(os.getcwd(), 'logs')
if not os.path.exists(log_path):
os.mkdir(log_path)
log_path_error = os.path.join(log_path, f'{time.strftime("%Y-%m-%d")}_error.log')
class LoggerInitializer:
def __init__(self):
self.log_path = os.path.join(os.getcwd(), 'logs')
self.__ensure_log_directory_exists()
self.log_path_error = os.path.join(self.log_path, f'{time.strftime("%Y-%m-%d")}_error.log')
logger.add(log_path_error, rotation='50MB', encoding='utf-8', enqueue=True, compression='zip')
def __ensure_log_directory_exists(self):
"""
确保日志目录存在,如果不存在则创建
"""
if not os.path.exists(self.log_path):
os.mkdir(self.log_path)
@staticmethod
def __filter(log: Dict):
"""
自定义日志过滤器添加trace_id
"""
log['trace_id'] = TraceCtx.get_id()
return log
def init_log(self):
"""
初始化日志配置
"""
# 自定义日志格式
format_str = (
'<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | '
'<cyan>{trace_id}</cyan> | '
'<level>{level: <8}</level> | '
'<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - '
'<level>{message}</level>'
)
_logger.remove()
# 移除后重新添加sys.stderr, 目的: 控制台输出与文件日志内容和结构一致
_logger.add(sys.stderr, filter=self.__filter, format=format_str, enqueue=True)
_logger.add(
self.log_path_error,
filter=self.__filter,
format=format_str,
rotation='50MB',
encoding='utf-8',
enqueue=True,
compression='zip',
)
return _logger
# 初始化日志处理器
log_initializer = LoggerInitializer()
logger = log_initializer.init_log()

View File

@@ -3,7 +3,8 @@ from fastapi import status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse, Response, StreamingResponse
from pydantic import BaseModel
from typing import Any, Dict, Optional
from starlette.background import BackgroundTask
from typing import Any, Dict, Mapping, Optional
from config.constant import HttpStatusConstant
@@ -20,6 +21,9 @@ class ResponseUtil:
rows: Optional[Any] = None,
dict_content: Optional[Dict] = None,
model_content: Optional[BaseModel] = None,
headers: Optional[Mapping[str, str]] = None,
media_type: Optional[str] = None,
background: Optional[BackgroundTask] = None,
) -> Response:
"""
成功响应方法
@@ -29,6 +33,9 @@ class ResponseUtil:
:param rows: 可选成功响应结果中属性为rows的值
:param dict_content: 可选dict类型成功响应结果中自定义属性的值
:param model_content: 可选BaseModel类型成功响应结果中自定义属性的值
:param headers: 可选,响应头信息
:param media_type: 可选,响应结果媒体类型
:param background: 可选,响应返回后执行的后台任务
:return: 成功响应结果
"""
result = {'code': HttpStatusConstant.SUCCESS, 'msg': msg}
@@ -44,7 +51,13 @@ class ResponseUtil:
result.update({'success': True, 'time': datetime.now()})
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result))
return JSONResponse(
status_code=status.HTTP_200_OK,
content=jsonable_encoder(result),
headers=headers,
media_type=media_type,
background=background,
)
@classmethod
def failure(
@@ -54,6 +67,9 @@ class ResponseUtil:
rows: Optional[Any] = None,
dict_content: Optional[Dict] = None,
model_content: Optional[BaseModel] = None,
headers: Optional[Mapping[str, str]] = None,
media_type: Optional[str] = None,
background: Optional[BackgroundTask] = None,
) -> Response:
"""
失败响应方法
@@ -63,6 +79,9 @@ class ResponseUtil:
:param rows: 可选失败响应结果中属性为rows的值
:param dict_content: 可选dict类型失败响应结果中自定义属性的值
:param model_content: 可选BaseModel类型失败响应结果中自定义属性的值
:param headers: 可选,响应头信息
:param media_type: 可选,响应结果媒体类型
:param background: 可选,响应返回后执行的后台任务
:return: 失败响应结果
"""
result = {'code': HttpStatusConstant.WARN, 'msg': msg}
@@ -78,7 +97,13 @@ class ResponseUtil:
result.update({'success': False, 'time': datetime.now()})
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result))
return JSONResponse(
status_code=status.HTTP_200_OK,
content=jsonable_encoder(result),
headers=headers,
media_type=media_type,
background=background,
)
@classmethod
def unauthorized(
@@ -88,6 +113,9 @@ class ResponseUtil:
rows: Optional[Any] = None,
dict_content: Optional[Dict] = None,
model_content: Optional[BaseModel] = None,
headers: Optional[Mapping[str, str]] = None,
media_type: Optional[str] = None,
background: Optional[BackgroundTask] = None,
) -> Response:
"""
未认证响应方法
@@ -97,6 +125,9 @@ class ResponseUtil:
:param rows: 可选未认证响应结果中属性为rows的值
:param dict_content: 可选dict类型未认证响应结果中自定义属性的值
:param model_content: 可选BaseModel类型未认证响应结果中自定义属性的值
:param headers: 可选,响应头信息
:param media_type: 可选,响应结果媒体类型
:param background: 可选,响应返回后执行的后台任务
:return: 未认证响应结果
"""
result = {'code': HttpStatusConstant.UNAUTHORIZED, 'msg': msg}
@@ -112,7 +143,13 @@ class ResponseUtil:
result.update({'success': False, 'time': datetime.now()})
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result))
return JSONResponse(
status_code=status.HTTP_200_OK,
content=jsonable_encoder(result),
headers=headers,
media_type=media_type,
background=background,
)
@classmethod
def forbidden(
@@ -122,6 +159,9 @@ class ResponseUtil:
rows: Optional[Any] = None,
dict_content: Optional[Dict] = None,
model_content: Optional[BaseModel] = None,
headers: Optional[Mapping[str, str]] = None,
media_type: Optional[str] = None,
background: Optional[BackgroundTask] = None,
) -> Response:
"""
未授权响应方法
@@ -131,6 +171,9 @@ class ResponseUtil:
:param rows: 可选未授权响应结果中属性为rows的值
:param dict_content: 可选dict类型未授权响应结果中自定义属性的值
:param model_content: 可选BaseModel类型未授权响应结果中自定义属性的值
:param headers: 可选,响应头信息
:param media_type: 可选,响应结果媒体类型
:param background: 可选,响应返回后执行的后台任务
:return: 未授权响应结果
"""
result = {'code': HttpStatusConstant.FORBIDDEN, 'msg': msg}
@@ -146,7 +189,13 @@ class ResponseUtil:
result.update({'success': False, 'time': datetime.now()})
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result))
return JSONResponse(
status_code=status.HTTP_200_OK,
content=jsonable_encoder(result),
headers=headers,
media_type=media_type,
background=background,
)
@classmethod
def error(
@@ -156,6 +205,9 @@ class ResponseUtil:
rows: Optional[Any] = None,
dict_content: Optional[Dict] = None,
model_content: Optional[BaseModel] = None,
headers: Optional[Mapping[str, str]] = None,
media_type: Optional[str] = None,
background: Optional[BackgroundTask] = None,
) -> Response:
"""
错误响应方法
@@ -165,6 +217,9 @@ class ResponseUtil:
:param rows: 可选错误响应结果中属性为rows的值
:param dict_content: 可选dict类型错误响应结果中自定义属性的值
:param model_content: 可选BaseModel类型错误响应结果中自定义属性的值
:param headers: 可选,响应头信息
:param media_type: 可选,响应结果媒体类型
:param background: 可选,响应返回后执行的后台任务
:return: 错误响应结果
"""
result = {'code': HttpStatusConstant.ERROR, 'msg': msg}
@@ -180,14 +235,32 @@ class ResponseUtil:
result.update({'success': False, 'time': datetime.now()})
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result))
return JSONResponse(
status_code=status.HTTP_200_OK,
content=jsonable_encoder(result),
headers=headers,
media_type=media_type,
background=background,
)
@classmethod
def streaming(cls, *, data: Any = None):
def streaming(
cls,
*,
data: Any = None,
headers: Optional[Mapping[str, str]] = None,
media_type: Optional[str] = None,
background: Optional[BackgroundTask] = None,
) -> Response:
"""
流式响应方法
:param data: 流式传输的内容
:param headers: 可选,响应头信息
:param media_type: 可选,响应结果媒体类型
:param background: 可选,响应返回后执行的后台任务
:return: 流式响应结果
"""
return StreamingResponse(status_code=status.HTTP_200_OK, content=data)
return StreamingResponse(
status_code=status.HTTP_200_OK, content=data, headers=headers, media_type=media_type, background=background
)

View File

@@ -1,4 +1,4 @@
from typing import List
from typing import Dict, List
from config.constant import CommonConstant
@@ -36,6 +36,16 @@ class StringUtil:
"""
return string is None or len(string) == 0
@classmethod
def is_not_empty(cls, string: str) -> bool:
"""
校验字符串是否不是''和None
:param string: 需要校验的字符串
:return: 校验结果
"""
return not cls.is_empty(string)
@classmethod
def is_http(cls, link: str):
"""
@@ -49,7 +59,7 @@ class StringUtil:
@classmethod
def contains_ignore_case(cls, search_str: str, compare_str: str):
"""
查找指定字符串是否包含指定字符串同时忽略大小写
查找指定字符串是否包含指定字符串同时忽略大小写
:param search_str: 查找的字符串
:param compare_str: 比对的字符串
@@ -62,15 +72,40 @@ class StringUtil:
@classmethod
def contains_any_ignore_case(cls, search_str: str, compare_str_list: List[str]):
"""
查找指定字符串是否包含指定字符串列表中的任意一个字符串同时忽略大小写
查找指定字符串是否包含指定字符串列表中的任意一个字符串同时忽略大小写
:param search_str: 查找的字符串
:param compare_str_list: 比对的字符串列表
:return: 查找结果
"""
if search_str and compare_str_list:
for compare_str in compare_str_list:
return cls.contains_ignore_case(search_str, compare_str)
return any([cls.contains_ignore_case(search_str, compare_str) for compare_str in compare_str_list])
return False
@classmethod
def equals_ignore_case(cls, search_str: str, compare_str: str):
"""
比较两个字符串是否相等同时忽略大小写
:param search_str: 查找的字符串
:param compare_str: 比对的字符串
:return: 比较结果
"""
if search_str and compare_str:
return search_str.lower() == compare_str.lower()
return False
@classmethod
def equals_any_ignore_case(cls, search_str: str, compare_str_list: List[str]):
"""
比较指定字符串是否与指定字符串列表中的任意一个字符串相等同时忽略大小写
:param search_str: 查找的字符串
:param compare_str_list: 比对的字符串列表
:return: 比较结果
"""
if search_str and compare_str_list:
return any([cls.equals_ignore_case(search_str, compare_str) for compare_str in compare_str_list])
return False
@classmethod
@@ -96,6 +131,40 @@ class StringUtil:
:return: 查找结果
"""
if search_str and compare_str_list:
for compare_str in compare_str_list:
return cls.startswith_case(search_str, compare_str)
return any([cls.startswith_case(search_str, compare_str) for compare_str in compare_str_list])
return False
@classmethod
def convert_to_camel_case(cls, name: str) -> str:
"""
将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串
:param name: 转换前的下划线大写方式命名的字符串
:return: 转换后的驼峰式命名的字符串
"""
if not name:
return ''
if '_' not in name:
return name[0].upper() + name[1:]
parts = name.split('_')
result = []
for part in parts:
if not part:
continue
result.append(part[0].upper() + part[1:].lower())
return ''.join(result)
@classmethod
def get_mapping_value_by_key_ignore_case(cls, mapping: Dict[str, str], key: str) -> str:
"""
根据忽略大小写的键获取字典中的对应的值
param mapping: 字典
param key: 字典的键
:return: 字典键对应的值
"""
for k, v in mapping.items():
if key.lower() == k.lower():
return v
return ''

View File

@@ -0,0 +1,470 @@
import json
import os
from datetime import datetime
from jinja2 import Environment, FileSystemLoader
from typing import Dict, List, Set
from config.constant import GenConstant
from config.env import DataBaseConfig
from exceptions.exception import ServiceWarning
from module_generator.entity.vo.gen_vo import GenTableModel, GenTableColumnModel
from utils.common_util import CamelCaseUtil, SnakeCaseUtil
from utils.string_util import StringUtil
class TemplateInitializer:
"""
模板引擎初始化类
"""
@classmethod
def init_jinja2(cls):
"""
初始化 Jinja2 模板引擎
:return: Jinja2 环境对象
"""
try:
template_dir = os.path.join(os.getcwd(), 'module_generator', 'templates')
env = Environment(
loader=FileSystemLoader(template_dir),
keep_trailing_newline=True,
trim_blocks=True,
lstrip_blocks=True,
)
env.filters.update(
{
'camel_to_snake': SnakeCaseUtil.camel_to_snake,
'snake_to_camel': CamelCaseUtil.snake_to_camel,
'get_sqlalchemy_type': TemplateUtils.get_sqlalchemy_type,
}
)
return env
except Exception as e:
raise RuntimeError(f'初始化Jinja2模板引擎失败: {e}')
class TemplateUtils:
"""
模板工具类
"""
# 项目路径
FRONTEND_PROJECT_PATH = 'frontend'
BACKEND_PROJECT_PATH = 'backend'
DEFAULT_PARENT_MENU_ID = '3'
@classmethod
def prepare_context(cls, gen_table: GenTableModel):
"""
准备模板变量
:param gen_table: 生成表的配置信息
:return: 模板上下文字典
"""
if not gen_table.options:
raise ServiceWarning(message='请先完善生成配置信息')
class_name = gen_table.class_name
module_name = gen_table.module_name
business_name = gen_table.business_name
package_name = gen_table.package_name
tpl_category = gen_table.tpl_category
function_name = gen_table.function_name
context = {
'tplCategory': tpl_category,
'tableName': gen_table.table_name,
'functionName': function_name if StringUtil.is_not_empty(function_name) else '【请填写功能名称】',
'ClassName': class_name,
'className': class_name.lower(),
'moduleName': module_name,
'BusinessName': business_name.capitalize(),
'businessName': business_name,
'basePackage': cls.get_package_prefix(package_name),
'packageName': package_name,
'author': gen_table.function_author,
'datetime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'pkColumn': gen_table.pk_column,
'doImportList': cls.get_do_import_list(gen_table),
'voImportList': cls.get_vo_import_list(gen_table),
'permissionPrefix': cls.get_permission_prefix(module_name, business_name),
'columns': gen_table.columns,
'table': gen_table,
'dicts': cls.get_dicts(gen_table),
'dbType': DataBaseConfig.db_type,
'column_not_add_show': GenConstant.COLUMNNAME_NOT_ADD_SHOW,
'column_not_edit_show': GenConstant.COLUMNNAME_NOT_EDIT_SHOW,
}
# 设置菜单、树形结构、子表的上下文
cls.set_menu_context(context, gen_table)
if tpl_category == GenConstant.TPL_TREE:
cls.set_tree_context(context, gen_table)
if tpl_category == GenConstant.TPL_SUB:
cls.set_sub_context(context, gen_table)
return context
@classmethod
def set_menu_context(cls, context: Dict, gen_table: GenTableModel):
"""
设置菜单上下文
:param context: 模板上下文字典
:param gen_table: 生成表的配置信息
:return: 新的模板上下文字典
"""
options = gen_table.options
params_obj = json.loads(options)
context['parentMenuId'] = cls.get_parent_menu_id(params_obj)
@classmethod
def set_tree_context(cls, context: Dict, gen_table: GenTableModel):
"""
设置树形结构上下文
:param context: 模板上下文字典
:param gen_table: 生成表的配置信息
:return: 新的模板上下文字典
"""
options = gen_table.options
params_obj = json.loads(options)
context['treeCode'] = cls.get_tree_code(params_obj)
context['treeParentCode'] = cls.get_tree_parent_code(params_obj)
context['treeName'] = cls.get_tree_name(params_obj)
context['expandColumn'] = cls.get_expand_column(gen_table)
@classmethod
def set_sub_context(cls, context: Dict, gen_table: GenTableModel):
"""
设置子表上下文
:param context: 模板上下文字典
:param gen_table: 生成表的配置信息
:return: 新的模板上下文字典
"""
sub_table = gen_table.sub_table
sub_table_name = gen_table.sub_table_name
sub_table_fk_name = gen_table.sub_table_fk_name
sub_class_name = sub_table.class_name
sub_table_fk_class_name = StringUtil.convert_to_camel_case(sub_table_fk_name)
context['subTable'] = sub_table
context['subTableName'] = sub_table_name
context['subTableFkName'] = sub_table_fk_name
context['subTableFkClassName'] = sub_table_fk_class_name
context['subTableFkclassName'] = sub_table_fk_class_name.lower()
context['subClassName'] = sub_class_name
context['subclassName'] = sub_class_name.lower()
@classmethod
def get_template_list(cls, tpl_category: str, tpl_web_type: str):
"""
获取模板列表
:param tpl_category: 生成模板类型
:param tpl_web_type: 前端类型
:return: 模板列表
"""
use_web_type = 'vue'
if tpl_web_type == 'element-plus':
use_web_type = 'vue/v3'
templates = [
'python/controller.py.jinja2',
'python/dao.py.jinja2',
'python/do.py.jinja2',
'python/service.py.jinja2',
'python/vo.py.jinja2',
'sql/sql.jinja2',
'js/api.js.jinja2',
]
if tpl_category == GenConstant.TPL_CRUD:
templates.append(f'{use_web_type}/index.vue.jinja2')
elif tpl_category == GenConstant.TPL_TREE:
templates.append(f'{use_web_type}/index-tree.vue.jinja2')
elif tpl_category == GenConstant.TPL_SUB:
templates.append(f'{use_web_type}/index.vue.jinja2')
# templates.append('python/sub-domain.python.jinja2')
return templates
@classmethod
def get_file_name(cls, template: List[str], gen_table: GenTableModel):
"""
根据模板生成文件名
:param template: 模板列表
:param gen_table: 生成表的配置信息
:return: 模板生成文件名
"""
package_name = gen_table.package_name
module_name = gen_table.module_name
business_name = gen_table.business_name
vue_path = cls.FRONTEND_PROJECT_PATH
python_path = f'{cls.BACKEND_PROJECT_PATH}/{package_name.replace(".", "/")}'
if 'controller.py.jinja2' in template:
return f'{python_path}/controller/{business_name}_controller.py'
elif 'dao.py.jinja2' in template:
return f'{python_path}/dao/{business_name}_dao.py'
elif 'do.py.jinja2' in template:
return f'{python_path}/entity/do/{business_name}_do.py'
elif 'service.py.jinja2' in template:
return f'{python_path}/service/{business_name}_service.py'
elif 'vo.py.jinja2' in template:
return f'{python_path}/entity/vo/{business_name}_vo.py'
elif 'sql.jinja2' in template:
return f'{cls.BACKEND_PROJECT_PATH}/sql/{business_name}_menu.sql'
elif 'api.js.jinja2' in template:
return f'{vue_path}/api/{module_name}/{business_name}.js'
elif 'index.vue.jinja2' in template or 'index-tree.vue.jinja2' in template:
return f'{vue_path}/views/{module_name}/{business_name}/index.vue'
return ''
@classmethod
def get_package_prefix(cls, package_name: str):
"""
获取包前缀
:param package_name: 包名
:return: 包前缀
"""
return package_name[: package_name.rfind('.')]
@classmethod
def get_vo_import_list(cls, gen_table: GenTableModel):
"""
获取vo模板导入包列表
:param gen_table: 生成表的配置信息
:return: 导入包列表
"""
columns = gen_table.columns or []
import_list = set()
for column in columns:
if column.python_type in GenConstant.TYPE_DATE:
import_list.add(f'from datetime import {column.python_type}')
elif column.python_type == GenConstant.TYPE_DECIMAL:
import_list.add('from decimal import Decimal')
if gen_table.sub:
sub_columns = gen_table.sub_table.columns or []
for sub_column in sub_columns:
if sub_column.python_type in GenConstant.TYPE_DATE:
import_list.add(f'from datetime import {sub_column.python_type}')
elif sub_column.python_type == GenConstant.TYPE_DECIMAL:
import_list.add('from decimal import Decimal')
return cls.merge_same_imports(list(import_list), 'from datetime import')
@classmethod
def get_do_import_list(cls, gen_table: GenTableModel):
"""
获取do模板导入包列表
:param gen_table: 生成表的配置信息
:return: 导入包列表
"""
columns = gen_table.columns or []
import_list = set()
import_list.add('from sqlalchemy import Column')
for column in columns:
data_type = cls.get_db_type(column.column_type)
if data_type in GenConstant.COLUMNTYPE_GEOMETRY:
import_list.add('from geoalchemy2 import Geometry')
import_list.add(
f'from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, data_type)}'
)
if gen_table.sub:
import_list.add('from sqlalchemy import ForeignKey')
sub_columns = gen_table.sub_table.columns or []
for sub_column in sub_columns:
data_type = cls.get_db_type(sub_column.column_type)
import_list.add(
f'from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, data_type)}'
)
return cls.merge_same_imports(list(import_list), 'from sqlalchemy import')
@classmethod
def get_db_type(cls, column_type: str) -> str:
"""
获取数据库类型字段
param column_type: 字段类型
:return: 数据库类型
"""
if '(' in column_type:
return column_type.split('(')[0]
return column_type
@classmethod
def merge_same_imports(cls, imports: List[str], import_start: str) -> List[str]:
"""
合并相同的导入语句
:param imports: 导入语句列表
:param import_start: 导入语句的起始字符串
:return: 合并后的导入语句列表
"""
merged_imports = []
_imports = []
for import_stmt in imports:
if import_stmt.startswith(import_start):
imported_items = import_stmt.split('import')[1].strip()
_imports.extend(imported_items.split(', '))
else:
merged_imports.append(import_stmt)
if _imports:
merged_datetime_import = f'{import_start} {", ".join(_imports)}'
merged_imports.append(merged_datetime_import)
return merged_imports
@classmethod
def get_dicts(cls, gen_table: GenTableModel):
"""
获取字典列表
:param gen_table: 生成表的配置信息
:return: 字典列表
"""
columns = gen_table.columns or []
dicts = set()
cls.add_dicts(dicts, columns)
if gen_table.sub_table is not None:
cls.add_dicts(dicts, gen_table.sub_table.columns)
return ', '.join(dicts)
@classmethod
def add_dicts(cls, dicts: Set[str], columns: List[GenTableColumnModel]):
"""
添加字典列表
:param dicts: 字典列表
:param columns: 字段列表
:return: 新的字典列表
"""
for column in columns:
if (
not column.super_column
and StringUtil.is_not_empty(column.dict_type)
and StringUtil.equals_any_ignore_case(
column.html_type, [GenConstant.HTML_SELECT, GenConstant.HTML_RADIO, GenConstant.HTML_CHECKBOX]
)
):
dicts.add(f"'{column.dict_type}'")
@classmethod
def get_permission_prefix(cls, module_name: str, business_name: str):
"""
获取权限前缀
:param module_name: 模块名
:param business_name: 业务名
:return: 权限前缀
"""
return f'{module_name}:{business_name}'
@classmethod
def get_parent_menu_id(cls, params_obj: Dict):
"""
获取上级菜单ID
:param params_obj: 菜单参数字典
:return: 上级菜单ID
"""
if params_obj and params_obj.get(GenConstant.PARENT_MENU_ID):
return params_obj.get(GenConstant.PARENT_MENU_ID)
return cls.DEFAULT_PARENT_MENU_ID
@classmethod
def get_tree_code(cls, params_obj: Dict):
"""
获取树编码
:param params_obj: 菜单参数字典
:return: 树编码
"""
if GenConstant.TREE_CODE in params_obj:
return cls.to_camel_case(params_obj.get(GenConstant.TREE_CODE))
return ''
@classmethod
def get_tree_parent_code(cls, params_obj: Dict):
"""
获取树父编码
:param params_obj: 菜单参数字典
:return: 树父编码
"""
if GenConstant.TREE_PARENT_CODE in params_obj:
return cls.to_camel_case(params_obj.get(GenConstant.TREE_PARENT_CODE))
return ''
@classmethod
def get_tree_name(cls, params_obj: Dict):
"""
获取树名称
:param params_obj: 菜单参数字典
:return: 树名称
"""
if GenConstant.TREE_NAME in params_obj:
return cls.to_camel_case(params_obj.get(GenConstant.TREE_NAME))
return ''
@classmethod
def get_expand_column(cls, gen_table: GenTableModel):
"""
获取展开列
:param gen_table: 生成表的配置信息
:return: 展开列
"""
options = gen_table.options
params_obj = json.loads(options)
tree_name = params_obj.get(GenConstant.TREE_NAME)
num = 0
for column in gen_table.columns or []:
if column.list:
num += 1
if column.column_name == tree_name:
break
return num
@classmethod
def to_camel_case(cls, text: str) -> str:
"""
将字符串转换为驼峰命名
:param text: 待转换的字符串
:return: 转换后的驼峰命名字符串
"""
parts = text.split('_')
return parts[0] + ''.join(word.capitalize() for word in parts[1:])
@classmethod
def get_sqlalchemy_type(cls, column_type: str):
"""
获取SQLAlchemy类型
:param column_type: 列类型
:return: SQLAlchemy类型
"""
if '(' in column_type:
column_type_list = column_type.split('(')
if column_type_list[0] in GenConstant.COLUMNTYPE_STR:
sqlalchemy_type = (
StringUtil.get_mapping_value_by_key_ignore_case(
GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, column_type_list[0]
)
+ '('
+ column_type_list[1]
)
else:
sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case(
GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, column_type_list[0]
)
else:
sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case(
GenConstant.DB_TO_SQLALCHEMY_TYPE_MAPPING, column_type
)
return sqlalchemy_type

View File

@@ -1,4 +1,7 @@
import datetime
from copy import deepcopy
from datetime import datetime
from dateutil.parser import parse
from typing import Dict, List, Union
def object_format_datetime(obj):
@@ -8,7 +11,7 @@ def object_format_datetime(obj):
"""
for attr in dir(obj):
value = getattr(obj, attr)
if isinstance(value, datetime.datetime):
if isinstance(value, datetime):
setattr(obj, attr, value.strftime('%Y-%m-%d %H:%M:%S'))
return obj
@@ -21,7 +24,7 @@ def list_format_datetime(lst):
for obj in lst:
for attr in dir(obj):
value = getattr(obj, attr)
if isinstance(value, datetime.datetime):
if isinstance(value, datetime):
setattr(obj, attr, value.strftime('%Y-%m-%d %H:%M:%S'))
return lst
@@ -41,7 +44,7 @@ def format_datetime_dict_list(dicts):
if isinstance(v, dict):
# 递归遍历子字典
new_item[k] = format_datetime_dict_list([v])[0]
elif isinstance(v, datetime.datetime):
elif isinstance(v, datetime):
# 如果值是 datetime 类型,则格式化为字符串
new_item[k] = v.strftime('%Y-%m-%d %H:%M:%S')
else:
@@ -50,3 +53,89 @@ def format_datetime_dict_list(dicts):
result.append(new_item)
return result
class TimeFormatUtil:
"""
时间格式化工具类
"""
@classmethod
def format_time(cls, time_info: Union[str, datetime], format: str = '%Y-%m-%d %H:%M:%S'):
"""
格式化时间字符串或datetime对象为指定格式
:param time_info: 时间字符串或datetime对象
:param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S'
:return: 格式化后的时间字符串
"""
if isinstance(time_info, datetime):
format_date = time_info.strftime(format)
else:
try:
date = parse(time_info)
format_date = date.strftime(format)
except Exception:
format_date = time_info
return format_date
@classmethod
def parse_date(cls, time_str: str):
"""
解析时间字符串提取日期部分
:param time_str: 时间字符串
:return: 日期部分
"""
try:
dt = parse(time_str)
return dt.date()
except Exception:
return time_str
@classmethod
def format_time_dict(cls, time_dict: Dict, format: str = '%Y-%m-%d %H:%M:%S'):
"""
格式化时间字典
:param time_dict: 时间字典
:param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S'
:return: 格式化后的时间字典
"""
copy_time_dict = deepcopy(time_dict)
for k, v in copy_time_dict.items():
if isinstance(v, (str, datetime)):
copy_time_dict[k] = cls.format_time(v, format)
elif isinstance(v, dict):
copy_time_dict[k] = cls.format_time_dict(v, format)
elif isinstance(v, list):
copy_time_dict[k] = cls.format_time_list(v, format)
else:
copy_time_dict[k] = v
return copy_time_dict
@classmethod
def format_time_list(cls, time_list: List, format: str = '%Y-%m-%d %H:%M:%S'):
"""
格式化时间列表
:param time_list: 时间列表
:param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S'
:return: 格式化后的时间列表
"""
format_time_list = []
for item in time_list:
if isinstance(item, (str, datetime)):
format_item = cls.format_time(item, format)
elif isinstance(item, dict):
format_item = cls.format_time_dict(item, format)
elif isinstance(item, list):
format_item = cls.format_time_list(item, format)
else:
format_item = item
format_time_list.append(format_item)
return format_time_list

View File

@@ -1,6 +1,6 @@
{
"name": "vfadmin",
"version": "1.4.0",
"version": "1.6.2",
"description": "vfadmin管理系统",
"author": "insistence",
"license": "MIT",
@@ -23,17 +23,21 @@
"@vueuse/core": "10.11.0",
"ant-design-vue": "^4.1.1",
"axios": "0.28.1",
"clipboard": "2.0.11",
"echarts": "5.5.1",
"element-plus": "2.7.6",
"file-saver": "2.0.5",
"fuse.js": "6.6.2",
"js-beautify": "1.15.1",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"nprogress": "0.2.0",
"pinia": "2.1.7",
"splitpanes": "3.1.5",
"vue": "3.4.15",
"vue-cropper": "1.1.1",
"vue-router": "4.4.0"
"vue-router": "4.4.0",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "5.0.5",

View File

@@ -96,7 +96,7 @@ export function updateUserPwd(oldPassword, newPassword) {
return request({
url: '/system/user/profile/updatePwd',
method: 'put',
params: data
data: data
})
}

View File

@@ -43,6 +43,15 @@ export function importTable(data) {
})
}
// 创建表
export function createTable(data) {
return request({
url: '/tool/gen/createTable',
method: 'post',
params: data
})
}
// 预览生成代码
export function previewTable(tableId) {
return request({

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733303018722" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1447" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M368.832 67.2c51.328-16.384 89.216 34.112 75.712 76.416a346.816 346.816 0 0 0 435.84 435.84c42.304-13.44 92.8 24.384 76.48 75.712A467.968 467.968 0 1 1 368.832 67.2z m-35.776 122.688a368.832 368.832 0 1 0 501.056 501.056 445.952 445.952 0 0 1-501.056-501.056z" p-id="1448"></path></svg>

After

Width:  |  Height:  |  Size: 619 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733303115132" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12397" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 890.432c18.432 0 33.408 14.976 33.408 33.408v66.752a33.408 33.408 0 0 1-66.816 0v-66.752c0-18.432 14.976-33.408 33.408-33.408z m-267.52-110.848a33.408 33.408 0 0 1 0 47.232l-47.296 47.232a33.408 33.408 0 0 1-47.232-47.232l47.232-47.232a33.408 33.408 0 0 1 47.232 0z m582.336 0l47.232 47.232a33.408 33.408 0 0 1-47.232 47.232l-47.232-47.232a33.408 33.408 0 1 1 47.232-47.232zM512 200.32a311.68 311.68 0 1 1 0 623.296 311.68 311.68 0 0 1 0-623.36z m0 66.752a244.864 244.864 0 1 0 0 489.728 244.864 244.864 0 0 0 0-489.728zM100.16 478.592a33.408 33.408 0 1 1 0 66.816H33.408a33.408 33.408 0 0 1 0-66.816h66.752z m890.432 0a33.408 33.408 0 0 1 0 66.816h-66.752a33.408 33.408 0 1 1 0-66.816h66.752zM197.184 149.952l47.232 47.232a33.408 33.408 0 1 1-47.232 47.232l-47.232-47.232a33.408 33.408 0 0 1 47.232-47.232z m676.864 0a33.408 33.408 0 0 1 0 47.232l-47.232 47.232a33.408 33.408 0 1 1-47.232-47.232l47.232-47.232a33.408 33.408 0 0 1 47.232 0zM512 0c18.432 0 33.408 14.976 33.408 33.408v66.752a33.408 33.408 0 1 1-66.816 0V33.408C478.592 14.976 493.568 0 512 0z" p-id="12398"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -131,10 +131,6 @@ aside {
position: relative;
}
.pagination-container {
margin-top: 30px;
}
.text-center {
text-align: center
}

57
ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss Normal file → Executable file
View File

@@ -1,4 +1,4 @@
/**
/**
* 通用css样式布局处理
* Copyright (c) 2019 ruoyi
*/
@@ -102,40 +102,46 @@
/** 表格布局 **/
.pagination-container {
position: relative;
height: 25px;
margin-bottom: 10px;
margin-top: 15px;
padding: 10px 20px !important;
display: flex;
justify-content: flex-end;
margin-top: 20px;
background-color: transparent !important;
}
/* 弹窗中的分页器 */
.el-dialog .pagination-container {
position: static !important;
position: static !important;
margin: 10px 0 0 0;
padding: 0 !important;
.el-pagination {
position: static;
}
}
/* 移动端适配 */
@media (max-width: 768px) {
.pagination-container {
.el-pagination {
> .el-pagination__jump {
display: none !important;
}
> .el-pagination__sizes {
display: none !important;
}
}
}
}
/* tree border */
.tree-border {
margin-top: 5px;
border: 1px solid #e5e6e7;
background: #FFFFFF none;
border: 1px solid var(--el-border-color-light, #e5e6e7);
background: var(--el-bg-color, #FFFFFF) none;
border-radius:4px;
width: 100%;
}
.pagination-container .el-pagination {
right: 0;
position: absolute;
}
@media ( max-width : 768px) {
.pagination-container .el-pagination > .el-pagination__jump {
display: none !important;
}
.pagination-container .el-pagination > .el-pagination__sizes {
display: none !important;
}
}
.el-table .fixed-width .el-button--small {
padding-left: 0;
padding-right: 0;
@@ -279,3 +285,8 @@
.top-right-btn {
margin-left: auto;
}
/* 分割面板样式 */
.splitpanes.default-theme .splitpanes__pane {
background-color: var(--splitpanes-default-bg) !important;
}

Some files were not shown because too many files have changed in this diff Show More