Compare commits
95 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
00a7af2d5b | ||
![]() |
e34a493b3e | ||
![]() |
a9df9ea56a | ||
![]() |
d89c0df425 | ||
![]() |
d0730e7993 | ||
![]() |
5fe376f978 | ||
![]() |
dd5b1c55c9 | ||
![]() |
7645c6d9fd | ||
![]() |
61073970fa | ||
![]() |
9ae2ac02eb | ||
![]() |
6622c329fc | ||
![]() |
b66d545985 | ||
![]() |
1789cb5a9b | ||
![]() |
ee376c477d | ||
![]() |
2b6f0905a9 | ||
![]() |
c953754bed | ||
![]() |
cea4a10baa | ||
![]() |
b708d86eff | ||
![]() |
28aab8c7d4 | ||
![]() |
9412e3f344 | ||
![]() |
b4d5619b1b | ||
![]() |
8ce598ad54 | ||
![]() |
ca641055e0 | ||
![]() |
a5a4099374 | ||
![]() |
729dc23a16 | ||
![]() |
3e898083ac | ||
![]() |
492dd7b052 | ||
![]() |
3e5890b8c2 | ||
![]() |
e21bde54be | ||
![]() |
fdf8e600a0 | ||
![]() |
1b5b89e282 | ||
![]() |
4cc9037f25 | ||
![]() |
e8b3a6c49b | ||
![]() |
6bae2d2d0c | ||
![]() |
f42a0b530b | ||
![]() |
0fdf45c73f | ||
![]() |
fd07ad088c | ||
![]() |
2a486dc01e | ||
![]() |
15686b44ad | ||
![]() |
2b474ddb35 | ||
![]() |
25e2a1e931 | ||
![]() |
59f6e0091f | ||
![]() |
fe41a207fa | ||
![]() |
1d36c0c56e | ||
![]() |
7cca5c88f3 | ||
![]() |
00011f8419 | ||
![]() |
1cfd85f9de | ||
![]() |
3e05f1b8b3 | ||
![]() |
a67c629fa6 | ||
![]() |
691076bb14 | ||
![]() |
1d408e6a09 | ||
![]() |
3a3bbcc32a | ||
![]() |
a2a0e4ff1c | ||
![]() |
1453402ac3 | ||
![]() |
7ce127a9e6 | ||
![]() |
c9ecfd213c | ||
![]() |
1d08ba9905 | ||
![]() |
6ffcf4dca7 | ||
![]() |
8f851f5e51 | ||
![]() |
29b672cdab | ||
![]() |
27fe0e04f6 | ||
![]() |
591dbe06a2 | ||
![]() |
43b7931591 | ||
![]() |
51257df07c | ||
![]() |
c282f362f4 | ||
![]() |
425d6e00eb | ||
![]() |
564d470240 | ||
![]() |
a1937448d6 | ||
![]() |
1ddb1bd5b5 | ||
![]() |
4f4ee34e8d | ||
![]() |
8202e8c9db | ||
![]() |
b797ffb5af | ||
![]() |
c2795c49e4 | ||
![]() |
545cb6606c | ||
![]() |
d0211cfd11 | ||
![]() |
a3c9ffdad5 | ||
![]() |
5ce1ffd557 | ||
![]() |
850d8d7890 | ||
![]() |
53cc6eb8ce | ||
![]() |
c7af974d3f | ||
![]() |
bb143ac15a | ||
![]() |
c6c9020d89 | ||
![]() |
df1929ab24 | ||
![]() |
3d15c37182 | ||
![]() |
d9810bfb67 | ||
![]() |
5a04889f84 | ||
![]() |
8a964bf3f5 | ||
![]() |
f573e3e93b | ||
![]() |
84a02a42aa | ||
![]() |
c750908c04 | ||
![]() |
af89388294 | ||
![]() |
09380dc94f | ||
![]() |
82c84b2afe | ||
![]() |
f404aba689 | ||
![]() |
9013b1917e |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -136,3 +136,10 @@ dmypy.json
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
|
20
README.md
20
README.md
@@ -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.3.2</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.3.2-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、MySQL(PostgreSQL)、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
|
||||
|
@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
|
||||
# 应用端口
|
||||
APP_PORT = 9099
|
||||
# 应用版本
|
||||
APP_VERSION= '1.3.2'
|
||||
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'
|
||||
# 数据库端口
|
||||
|
@@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0'
|
||||
# 应用端口
|
||||
APP_PORT = 9099
|
||||
# 应用版本
|
||||
APP_VERSION= '1.3.2'
|
||||
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'
|
||||
# 数据库端口
|
||||
|
@@ -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',
|
||||
}
|
||||
)
|
||||
|
@@ -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,
|
||||
|
@@ -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()
|
||||
|
@@ -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,8 +136,6 @@ 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.add_scheduler_job(item)
|
||||
scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL)
|
||||
@@ -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,6 +226,8 @@ class SchedulerUtil:
|
||||
:param job_id: 任务id
|
||||
:return:
|
||||
"""
|
||||
query_job = cls.get_scheduler_job(job_id=job_id)
|
||||
if query_job:
|
||||
scheduler.remove_job(job_id=str(job_id))
|
||||
|
||||
@classmethod
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
23
ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py
Normal file
23
ruoyi-fastapi-backend/middlewares/trace_middleware/ctx.py
Normal 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()
|
47
ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py
Normal file
47
ruoyi-fastapi-backend/middlewares/trace_middleware/middle.py
Normal 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)
|
52
ruoyi-fastapi-backend/middlewares/trace_middleware/span.py
Normal file
52
ruoyi-fastapi-backend/middlewares/trace_middleware/span.py
Normal 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)
|
@@ -3,19 +3,20 @@ 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
|
||||
|
||||
|
||||
@@ -51,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')
|
||||
@@ -116,13 +119,14 @@ class Log:
|
||||
try:
|
||||
# 调用原始函数
|
||||
result = await func(*args, **kwargs)
|
||||
except LoginException as e:
|
||||
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 ServiceWarning as e:
|
||||
result = ResponseUtil.failure(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
|
||||
@@ -199,187 +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 as e:
|
||||
result = ResponseUtil.failure(data=e.data, msg=e.message)
|
||||
except ServiceException as e:
|
||||
result = ResponseUtil.error(data=e.data, msg=e.message)
|
||||
except ServiceWarning as e:
|
||||
result = ResponseUtil.failure(data=e.data, msg=e.message)
|
||||
except Exception as 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):
|
||||
"""
|
||||
@@ -402,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
|
||||
|
@@ -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模型用于接收表单参数
|
||||
"""
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, 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
|
||||
@@ -112,7 +112,7 @@ async def query_system_config(request: Request, config_key: str):
|
||||
@Log(title='参数管理', business_type=BusinessType.EXPORT)
|
||||
async def export_system_config_list(
|
||||
request: Request,
|
||||
config_page_query: ConfigPageQueryModel = Depends(ConfigPageQueryModel.as_form),
|
||||
config_page_query: ConfigPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取全量数据
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import List
|
||||
@@ -123,7 +123,7 @@ async def query_detail_system_dict_type(request: Request, dict_id: int, query_db
|
||||
@Log(title='字典类型', business_type=BusinessType.EXPORT)
|
||||
async def export_system_dict_type_list(
|
||||
request: Request,
|
||||
dict_type_page_query: DictTypePageQueryModel = Depends(DictTypePageQueryModel.as_form),
|
||||
dict_type_page_query: DictTypePageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取全量数据
|
||||
@@ -226,7 +226,7 @@ async def query_detail_system_dict_data(request: Request, dict_code: int, query_
|
||||
@Log(title='字典数据', business_type=BusinessType.EXPORT)
|
||||
async def export_system_dict_data_list(
|
||||
request: Request,
|
||||
dict_data_page_query: DictDataPageQueryModel = Depends(DictDataPageQueryModel.as_form),
|
||||
dict_data_page_query: DictDataPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取全量数据
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, 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
|
||||
@@ -132,7 +132,7 @@ async def query_detail_system_job(request: Request, job_id: int, query_db: Async
|
||||
@Log(title='定时任务', business_type=BusinessType.EXPORT)
|
||||
async def export_system_job_list(
|
||||
request: Request,
|
||||
job_page_query: JobPageQueryModel = Depends(JobPageQueryModel.as_form),
|
||||
job_page_query: JobPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取全量数据
|
||||
@@ -183,7 +183,7 @@ async def delete_system_job_log(request: Request, job_log_ids: str, query_db: As
|
||||
@Log(title='定时任务调度日志', business_type=BusinessType.EXPORT)
|
||||
async def export_system_job_log_list(
|
||||
request: Request,
|
||||
job_log_page_query: JobLogPageQueryModel = Depends(JobLogPageQueryModel.as_form),
|
||||
job_log_page_query: JobLogPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取全量数据
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Depends, 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
|
||||
@@ -66,7 +66,7 @@ async def delete_system_operation_log(request: Request, oper_ids: str, query_db:
|
||||
@Log(title='操作日志', business_type=BusinessType.EXPORT)
|
||||
async def export_system_operation_log_list(
|
||||
request: Request,
|
||||
operation_log_page_query: OperLogPageQueryModel = Depends(OperLogPageQueryModel.as_form),
|
||||
operation_log_page_query: OperLogPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取全量数据
|
||||
@@ -137,7 +137,7 @@ async def unlock_system_user(request: Request, user_name: str, query_db: AsyncSe
|
||||
@Log(title='登录日志', business_type=BusinessType.EXPORT)
|
||||
async def export_system_login_log_list(
|
||||
request: Request,
|
||||
login_log_page_query: LoginLogPageQueryModel = Depends(LoginLogPageQueryModel.as_form),
|
||||
login_log_page_query: LoginLogPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取全量数据
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, 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
|
||||
@@ -94,7 +94,7 @@ async def query_detail_system_post(request: Request, post_id: int, query_db: Asy
|
||||
@Log(title='岗位管理', business_type=BusinessType.EXPORT)
|
||||
async def export_system_post_list(
|
||||
request: Request,
|
||||
post_page_query: PostPageQueryModel = Depends(PostPageQueryModel.as_form),
|
||||
post_page_query: PostPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取全量数据
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, 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
|
||||
@@ -165,7 +165,7 @@ async def query_detail_system_role(
|
||||
@Log(title='角色管理', business_type=BusinessType.EXPORT)
|
||||
async def export_system_role_list(
|
||||
request: Request,
|
||||
role_page_query: RolePageQueryModel = Depends(RolePageQueryModel.as_form),
|
||||
role_page_query: RolePageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept')),
|
||||
):
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, File, Query, Request, UploadFile
|
||||
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
|
||||
@@ -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 = Depends(ResetPasswordModel.as_query),
|
||||
reset_password: ResetPasswordModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
@@ -344,7 +344,7 @@ async def export_system_user_template(request: Request, query_db: AsyncSession =
|
||||
@Log(title='用户管理', business_type=BusinessType.EXPORT)
|
||||
async def export_system_user_list(
|
||||
request: Request,
|
||||
user_page_query: UserPageQueryModel = Depends(UserPageQueryModel.as_form),
|
||||
user_page_query: UserPageQueryModel = Form(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysUser')),
|
||||
):
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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,
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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='备注')
|
||||
|
@@ -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='更新者')
|
||||
|
@@ -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='备注')
|
||||
|
@@ -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='创建时间')
|
||||
|
@@ -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='消耗时间')
|
||||
|
||||
|
@@ -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='备注')
|
||||
|
@@ -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='备注')
|
||||
|
@@ -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):
|
||||
|
@@ -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):
|
||||
|
@@ -3,7 +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_form, as_query
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class ConfigModel(BaseModel):
|
||||
@@ -55,7 +55,6 @@ class ConfigQueryModel(ConfigModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class ConfigPageQueryModel(ConfigQueryModel):
|
||||
"""
|
||||
参数配置管理分页查询模型
|
||||
|
@@ -3,7 +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_form, as_query
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class DictTypeModel(BaseModel):
|
||||
@@ -101,7 +101,6 @@ class DictTypeQueryModel(DictTypeModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class DictTypePageQueryModel(DictTypeQueryModel):
|
||||
"""
|
||||
字典类型管理分页查询模型
|
||||
@@ -131,7 +130,6 @@ class DictDataQueryModel(DictDataModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class DictDataPageQueryModel(DictDataQueryModel):
|
||||
"""
|
||||
字典数据管理分页查询模型
|
||||
|
@@ -3,7 +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_form, as_query
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class JobModel(BaseModel):
|
||||
@@ -78,7 +78,6 @@ class JobQueryModel(JobModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class JobPageQueryModel(JobQueryModel):
|
||||
"""
|
||||
定时任务管理分页查询模型
|
||||
@@ -116,7 +115,6 @@ class JobLogQueryModel(JobLogModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class JobLogPageQueryModel(JobLogQueryModel):
|
||||
"""
|
||||
定时任务日志管理分页查询模型
|
||||
|
@@ -2,7 +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_form, as_query
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class OperLogModel(BaseModel):
|
||||
@@ -69,7 +69,6 @@ class OperLogQueryModel(OperLogModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class OperLogPageQueryModel(OperLogQueryModel):
|
||||
"""
|
||||
操作日志管理分页查询模型
|
||||
@@ -103,7 +102,6 @@ class LoginLogQueryModel(LogininforModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class LoginLogPageQueryModel(LoginLogQueryModel):
|
||||
"""
|
||||
登录日志管理分页查询模型
|
||||
|
@@ -3,7 +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_form, as_query
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class NoticeModel(BaseModel):
|
||||
@@ -44,7 +44,6 @@ class NoticeQueryModel(NoticeModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class NoticePageQueryModel(NoticeQueryModel):
|
||||
"""
|
||||
通知公告管理分页查询模型
|
||||
|
@@ -13,10 +13,10 @@ class OnlineModel(BaseModel):
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
token_id: Optional[str] = Field(default=None, description='会话编号')
|
||||
user_name: Optional[str] = Field(default=None, description='部门名称')
|
||||
dept_name: Optional[str] = Field(default=None, description='用户名称')
|
||||
ipaddr: Optional[str] = Field(default=None, description='登录IP地址')
|
||||
login_location: Optional[str] = Field(default=None, description='登录地址')
|
||||
user_name: Optional[str] = Field(default=None, description='登录名称')
|
||||
dept_name: Optional[str] = Field(default=None, description='所属部门')
|
||||
ipaddr: Optional[str] = Field(default=None, description='主机')
|
||||
login_location: Optional[str] = Field(default=None, description='登录地点')
|
||||
browser: Optional[str] = Field(default=None, description='浏览器类型')
|
||||
os: Optional[str] = Field(default=None, description='操作系统')
|
||||
login_time: Optional[datetime] = Field(default=None, description='登录时间')
|
||||
|
@@ -3,7 +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_form, as_query
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class PostModel(BaseModel):
|
||||
@@ -54,7 +54,6 @@ class PostQueryModel(PostModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class PostPageQueryModel(PostQueryModel):
|
||||
"""
|
||||
岗位管理分页查询模型
|
||||
|
@@ -3,7 +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_form, as_query
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class RoleModel(BaseModel):
|
||||
@@ -105,7 +105,6 @@ class RoleQueryModel(RoleModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class RolePageQueryModel(RoleQueryModel):
|
||||
"""
|
||||
角色管理分页查询模型
|
||||
|
@@ -5,7 +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_form, as_query
|
||||
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
|
||||
@@ -163,7 +163,6 @@ class UserQueryModel(UserModel):
|
||||
|
||||
|
||||
@as_query
|
||||
@as_form
|
||||
class UserPageQueryModel(UserQueryModel):
|
||||
"""
|
||||
用户管理分页查询模型
|
||||
@@ -191,7 +190,6 @@ class EditUserModel(AddUserModel):
|
||||
role: Optional[List] = Field(default=[], description='角色信息')
|
||||
|
||||
|
||||
@as_query
|
||||
class ResetPasswordModel(BaseModel):
|
||||
"""
|
||||
重置密码模型
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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):
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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,8 +130,6 @@ 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'))
|
||||
if edit_job.get('status') == '0':
|
||||
job_info = await cls.job_detail_services(query_db, edit_job.get('job_id'))
|
||||
@@ -152,8 +151,6 @@ 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)
|
||||
job_info = await cls.job_detail_services(query_db, page_object.job_id)
|
||||
if job_info:
|
||||
@@ -176,8 +173,6 @@ 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)
|
||||
await query_db.commit()
|
||||
return CrudResponseModel(is_success=True, message='删除成功')
|
||||
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -22,7 +22,7 @@ class OnlineService:
|
||||
:param query_object: 查询参数对象
|
||||
:return: 在线用户列表信息
|
||||
"""
|
||||
access_token_keys = await request.app.state.redis.keys(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}*")
|
||||
access_token_keys = await request.app.state.redis.keys(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}*')
|
||||
if not access_token_keys:
|
||||
access_token_keys = []
|
||||
access_token_values_list = [await request.app.state.redis.get(key) for key in access_token_keys]
|
||||
@@ -40,11 +40,11 @@ class OnlineService:
|
||||
login_time=payload.get('login_info').get('loginTime'),
|
||||
)
|
||||
if query_object.user_name and not query_object.ipaddr:
|
||||
if query_object.user_name == payload.get('login_info').get('ipaddr'):
|
||||
if query_object.user_name == payload.get('user_name'):
|
||||
online_info_list = [online_dict]
|
||||
break
|
||||
elif not query_object.user_name and query_object.ipaddr:
|
||||
if query_object.ipaddr == payload.get('ipaddr'):
|
||||
if query_object.ipaddr == payload.get('login_info').get('ipaddr'):
|
||||
online_info_list = [online_dict]
|
||||
break
|
||||
elif query_object.user_name and query_object.ipaddr:
|
||||
@@ -70,7 +70,7 @@ class OnlineService:
|
||||
if page_object.token_ids:
|
||||
token_id_list = page_object.token_ids.split(',')
|
||||
for token_id in token_id_list:
|
||||
await request.app.state.redis.delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}")
|
||||
await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}')
|
||||
return CrudResponseModel(is_success=True, message='强退成功')
|
||||
else:
|
||||
raise ServiceException(message='传入session_id为空')
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
||||
@@ -144,9 +145,9 @@ class RoleService:
|
||||
"""
|
||||
add_role = RoleModel(**page_object.model_dump(by_alias=True))
|
||||
if not await cls.check_role_name_unique_services(query_db, page_object):
|
||||
raise ServiceException(message=f'新增角色{page_object.post_name}失败,角色名称已存在')
|
||||
raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色名称已存在')
|
||||
elif not await cls.check_role_key_unique_services(query_db, page_object):
|
||||
raise ServiceException(message=f'新增角色{page_object.post_name}失败,角色权限已存在')
|
||||
raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色权限已存在')
|
||||
else:
|
||||
try:
|
||||
add_result = await RoleDao.add_role_dao(query_db, add_role)
|
||||
@@ -178,9 +179,9 @@ class RoleService:
|
||||
if role_info:
|
||||
if page_object.type != 'status':
|
||||
if not await cls.check_role_name_unique_services(query_db, page_object):
|
||||
raise ServiceException(message=f'修改角色{page_object.post_name}失败,角色名称已存在')
|
||||
raise ServiceException(message=f'修改角色{page_object.role_name}失败,角色名称已存在')
|
||||
elif not await cls.check_role_key_unique_services(query_db, page_object):
|
||||
raise ServiceException(message=f'修改角色{page_object.post_name}失败,角色权限已存在')
|
||||
raise ServiceException(message=f'修改角色{page_object.role_name}失败,角色权限已存在')
|
||||
try:
|
||||
await RoleDao.edit_role_dao(query_db, edit_role)
|
||||
if page_object.type != 'status':
|
||||
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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)
|
393
ruoyi-fastapi-backend/module_generator/dao/gen_dao.py
Normal file
393
ruoyi-fastapi-backend/module_generator/dao/gen_dao.py
Normal 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])))
|
74
ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py
Normal file
74
ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py
Normal 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')
|
264
ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py
Normal file
264
ruoyi-fastapi-backend/module_generator/entity/vo/gen_vo.py
Normal 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')
|
499
ruoyi-fastapi-backend/module_generator/service/gen_service.py
Normal file
499
ruoyi-fastapi-backend/module_generator/service/gen_service.py
Normal 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)
|
||||
]
|
@@ -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'
|
||||
})
|
||||
}
|
@@ -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))
|
@@ -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 %}
|
@@ -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 %}
|
@@ -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
|
@@ -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 }}')
|
@@ -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 %}
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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()}异步函数执行了')
|
||||
|
18
ruoyi-fastapi-backend/requirements-pg.txt
Normal file
18
ruoyi-fastapi-backend/requirements-pg.txt
Normal 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
|
@@ -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.111.1
|
||||
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
|
||||
pydantic-validation-decorator==0.1.2
|
||||
PyJWT[crypto]==2.8.0
|
||||
Pillow==11.1.0
|
||||
psutil==7.0.0
|
||||
pydantic-validation-decorator==0.1.4
|
||||
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
|
||||
|
@@ -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:
|
||||
|
1047
ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql
Normal file
1047
ruoyi-fastapi-backend/sql/ruoyi-fastapi-pg.sql
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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是)',
|
||||
|
@@ -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'):
|
||||
|
104
ruoyi-fastapi-backend/utils/excel_util.py
Normal file
104
ruoyi-fastapi-backend/utils/excel_util.py
Normal 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
|
223
ruoyi-fastapi-backend/utils/gen_util.py
Normal file
223
ruoyi-fastapi-backend/utils/gen_util.py
Normal 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:])
|
@@ -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()
|
||||
|
@@ -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
|
||||
)
|
||||
|
@@ -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 ''
|
||||
|
470
ruoyi-fastapi-backend/utils/template_util.py
Normal file
470
ruoyi-fastapi-backend/utils/template_util.py
Normal 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
|
@@ -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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vfadmin",
|
||||
"version": "1.3.2",
|
||||
"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",
|
||||
|
@@ -2,11 +2,12 @@ import request from '@/utils/request'
|
||||
|
||||
// 登录方法
|
||||
export function login(username, password, code, uuid) {
|
||||
const data = new FormData();
|
||||
data.append("username", username);
|
||||
data.append("password", password);
|
||||
data.append("code", code);
|
||||
data.append("uuid", uuid);
|
||||
const data = {
|
||||
username,
|
||||
password,
|
||||
code,
|
||||
uuid
|
||||
}
|
||||
return request({
|
||||
url: '/login',
|
||||
headers: {
|
||||
|
@@ -96,7 +96,7 @@ export function updateUserPwd(oldPassword, newPassword) {
|
||||
return request({
|
||||
url: '/system/user/profile/updatePwd',
|
||||
method: 'put',
|
||||
params: data
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -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({
|
||||
|
1
ruoyi-fastapi-frontend/src/assets/icons/svg/moon.svg
Executable file
1
ruoyi-fastapi-frontend/src/assets/icons/svg/moon.svg
Executable 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 |
1
ruoyi-fastapi-frontend/src/assets/icons/svg/sunny.svg
Executable file
1
ruoyi-fastapi-frontend/src/assets/icons/svg/sunny.svg
Executable 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 |
@@ -131,10 +131,6 @@ aside {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center
|
||||
}
|
||||
|
53
ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss
Normal file → Executable file
53
ruoyi-fastapi-frontend/src/assets/styles/ruoyi.scss
Normal file → Executable file
@@ -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;
|
||||
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;
|
||||
}
|
||||
|
6
ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss
Normal file → Executable file
6
ruoyi-fastapi-frontend/src/assets/styles/sidebar.scss
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
#app {
|
||||
|
||||
.main-container {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
transition: margin-left .28s;
|
||||
margin-left: $base-sidebar-width;
|
||||
position: relative;
|
||||
@@ -12,10 +12,8 @@
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
-webkit-transition: width .28s;
|
||||
transition: width 0.28s;
|
||||
width: $base-sidebar-width !important;
|
||||
background-color: $base-menu-background;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
font-size: 0px;
|
||||
@@ -103,7 +101,7 @@
|
||||
|
||||
& .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
|
||||
& .theme-dark .el-sub-menu .el-menu-item {
|
||||
background-color: $base-sub-menu-background !important;
|
||||
background-color: $base-sub-menu-background;
|
||||
|
||||
&:hover {
|
||||
background-color: $base-sub-menu-hover !important;
|
||||
|
240
ruoyi-fastapi-frontend/src/assets/styles/variables.module.scss
Normal file → Executable file
240
ruoyi-fastapi-frontend/src/assets/styles/variables.module.scss
Normal file → Executable file
@@ -1,6 +1,6 @@
|
||||
// base color
|
||||
$blue: #324157;
|
||||
$light-blue: #3A71A8;
|
||||
$light-blue: #333c46;
|
||||
$red: #C03639;
|
||||
$pink: #E65D6E;
|
||||
$green: #30B08F;
|
||||
@@ -8,58 +8,214 @@ $tiffany: #4AB7BD;
|
||||
$yellow: #FEC171;
|
||||
$panGreen: #30B08F;
|
||||
|
||||
// 默认菜单主题风格
|
||||
// 默认主题变量
|
||||
$menuText: #bfcbd9;
|
||||
$menuActiveText: #409eff;
|
||||
$menuBg: #304156;
|
||||
$menuHover: #263445;
|
||||
|
||||
// 浅色主题theme-light
|
||||
$menuLightBg: #ffffff;
|
||||
$menuLightHover: #f0f1f5;
|
||||
$menuLightText: #303133;
|
||||
$menuLightActiveText: #409EFF;
|
||||
|
||||
// 基础变量
|
||||
$base-sidebar-width: 200px;
|
||||
$sideBarWidth: 200px;
|
||||
|
||||
// 菜单暗色变量
|
||||
$base-menu-color: #bfcbd9;
|
||||
$base-menu-color-active: #f4f4f5;
|
||||
$base-menu-background: #304156;
|
||||
$base-logo-title-color: #ffffff;
|
||||
|
||||
$base-menu-light-color: rgba(0, 0, 0, 0.7);
|
||||
$base-menu-light-background: #ffffff;
|
||||
$base-logo-light-title-color: #001529;
|
||||
|
||||
$base-sub-menu-background: #1f2d3d;
|
||||
$base-sub-menu-hover: #001528;
|
||||
|
||||
// 自定义暗色菜单风格
|
||||
/**
|
||||
$base-menu-color:hsla(0,0%,100%,.65);
|
||||
$base-menu-color-active:#fff;
|
||||
$base-menu-background:#001529;
|
||||
$base-logo-title-color: #ffffff;
|
||||
|
||||
$base-menu-light-color:rgba(0,0,0,.70);
|
||||
$base-menu-light-background:#ffffff;
|
||||
$base-logo-light-title-color: #001529;
|
||||
|
||||
$base-sub-menu-background:#000c17;
|
||||
$base-sub-menu-hover:#001528;
|
||||
*/
|
||||
|
||||
// 组件变量
|
||||
$--color-primary: #409EFF;
|
||||
$--color-success: #67C23A;
|
||||
$--color-warning: #E6A23C;
|
||||
$--color-danger: #F56C6C;
|
||||
$--color-info: #909399;
|
||||
|
||||
$base-sidebar-width: 200px;
|
||||
|
||||
// the :export directive is the magic sauce for webpack
|
||||
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
||||
:export {
|
||||
menuColor: $base-menu-color;
|
||||
menuLightColor: $base-menu-light-color;
|
||||
menuColorActive: $base-menu-color-active;
|
||||
menuBackground: $base-menu-background;
|
||||
menuLightBackground: $base-menu-light-background;
|
||||
subMenuBackground: $base-sub-menu-background;
|
||||
subMenuHover: $base-sub-menu-hover;
|
||||
sideBarWidth: $base-sidebar-width;
|
||||
logoTitleColor: $base-logo-title-color;
|
||||
logoLightTitleColor: $base-logo-light-title-color;
|
||||
primaryColor: $--color-primary;
|
||||
successColor: $--color-success;
|
||||
dangerColor: $--color-danger;
|
||||
infoColor: $--color-info;
|
||||
warningColor: $--color-warning;
|
||||
menuText: $menuText;
|
||||
menuActiveText: $menuActiveText;
|
||||
menuBg: $menuBg;
|
||||
menuHover: $menuHover;
|
||||
menuLightBg: $menuLightBg;
|
||||
menuLightHover: $menuLightHover;
|
||||
menuLightText: $menuLightText;
|
||||
menuLightActiveText: $menuLightActiveText;
|
||||
sideBarWidth: $sideBarWidth;
|
||||
// 导出基础颜色
|
||||
blue: $blue;
|
||||
lightBlue: $light-blue;
|
||||
red: $red;
|
||||
pink: $pink;
|
||||
green: $green;
|
||||
tiffany: $tiffany;
|
||||
yellow: $yellow;
|
||||
panGreen: $panGreen;
|
||||
// 导出组件颜色
|
||||
colorPrimary: $--color-primary;
|
||||
colorSuccess: $--color-success;
|
||||
colorWarning: $--color-warning;
|
||||
colorDanger: $--color-danger;
|
||||
colorInfo: $--color-info;
|
||||
}
|
||||
|
||||
// CSS变量定义
|
||||
:root {
|
||||
/* 亮色模式变量 */
|
||||
--sidebar-bg: #{$menuBg};
|
||||
--sidebar-text: #{$menuText};
|
||||
--menu-hover: #{$menuHover};
|
||||
|
||||
--navbar-bg: #ffffff;
|
||||
--navbar-text: #303133;
|
||||
|
||||
/* splitpanes default-theme 变量 */
|
||||
--splitpanes-default-bg: #ffffff;
|
||||
|
||||
}
|
||||
|
||||
// 暗黑模式变量
|
||||
html.dark {
|
||||
/* 默认通用 */
|
||||
--el-bg-color: #141414;
|
||||
--el-bg-color-overlay: #1d1e1f;
|
||||
--el-text-color-primary: #ffffff;
|
||||
--el-text-color-regular: #d0d0d0;
|
||||
--el-border-color: #434343;
|
||||
--el-border-color-light: #434343;
|
||||
|
||||
/* 侧边栏 */
|
||||
--sidebar-bg: #141414;
|
||||
--sidebar-text: #ffffff;
|
||||
--menu-hover: #2d2d2d;
|
||||
--menu-active-text: #{$menuActiveText};
|
||||
|
||||
/* 顶部导航栏 */
|
||||
--navbar-bg: #141414;
|
||||
--navbar-text: #ffffff;
|
||||
--navbar-hover: #141414;
|
||||
|
||||
/* 标签栏 */
|
||||
--tags-bg: #141414;
|
||||
--tags-item-bg: #1d1e1f;
|
||||
--tags-item-border: #303030;
|
||||
--tags-item-text: #d0d0d0;
|
||||
--tags-item-hover: #2d2d2d;
|
||||
--tags-close-hover: #64666a;
|
||||
|
||||
/* splitpanes 组件暗黑模式变量 */
|
||||
--splitpanes-bg: #141414;
|
||||
--splitpanes-border: #303030;
|
||||
--splitpanes-splitter-bg: #1d1e1f;
|
||||
--splitpanes-splitter-hover-bg: #2d2d2d;
|
||||
|
||||
/* blockquote 暗黑模式变量 */
|
||||
--blockquote-bg: #1d1e1f;
|
||||
--blockquote-border: #303030;
|
||||
--blockquote-text: #d0d0d0;
|
||||
|
||||
/* Cron 时间表达式 模式变量 */
|
||||
--cron-border: #303030;
|
||||
|
||||
/* splitpanes default-theme 暗黑模式变量 */
|
||||
--splitpanes-default-bg: #141414;
|
||||
|
||||
/* 侧边栏菜单覆盖 */
|
||||
.sidebar-container {
|
||||
.el-menu-item, .menu-title {
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
& .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
|
||||
& .theme-dark .el-sub-menu .el-menu-item {
|
||||
background-color: var(--el-bg-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 顶部栏栏菜单覆盖 */
|
||||
.el-menu--horizontal {
|
||||
.el-menu-item {
|
||||
&:not(.is-disabled) {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--navbar-hover) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 分割窗格覆盖 */
|
||||
.splitpanes {
|
||||
background-color: var(--splitpanes-bg);
|
||||
|
||||
.splitpanes__pane {
|
||||
background-color: var(--splitpanes-bg);
|
||||
border-color: var(--splitpanes-border);
|
||||
}
|
||||
|
||||
.splitpanes__splitter {
|
||||
background-color: var(--splitpanes-splitter-bg);
|
||||
border-color: var(--splitpanes-border);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--splitpanes-splitter-hover-bg);
|
||||
}
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: var(--splitpanes-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式覆盖 */
|
||||
.el-table {
|
||||
--el-table-header-bg-color: var(--el-bg-color-overlay) !important;
|
||||
--el-table-header-text-color: var(--el-text-color-regular) !important;
|
||||
--el-table-border-color: var(--el-border-color-light) !important;
|
||||
--el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important;
|
||||
|
||||
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
|
||||
th {
|
||||
background-color: var(--el-bg-color-overlay, #f8f8f9) !important;
|
||||
color: var(--el-text-color-regular, #515a6e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 树组件高亮样式覆盖 */
|
||||
.el-tree {
|
||||
.el-tree-node.is-current > .el-tree-node__content {
|
||||
background-color: var(--el-bg-color-overlay) !important;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.el-tree-node__content:hover {
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
}
|
||||
}
|
||||
|
||||
/* 下拉菜单样式覆盖 */
|
||||
.el-dropdown-menu__item:not(.is-disabled):focus, .el-dropdown-menu__item:not(.is-disabled):hover{
|
||||
background-color: var(--navbar-hover) !important;
|
||||
}
|
||||
|
||||
/* blockquote样式覆盖 */
|
||||
blockquote {
|
||||
background-color: var(--blockquote-bg) !important;
|
||||
border-left-color: var(--blockquote-border) !important;
|
||||
color: var(--blockquote-text) !important;
|
||||
}
|
||||
|
||||
/* 时间表达式标题样式覆盖 */
|
||||
.popup-result .title {
|
||||
background: var(--cron-border);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -10,21 +10,53 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
const levelList = ref([])
|
||||
|
||||
function getBreadcrumb() {
|
||||
// only show routes with meta.title
|
||||
let matched = route.matched.filter(item => item.meta && item.meta.title);
|
||||
const first = matched[0]
|
||||
// 判断是否为首页
|
||||
if (!isDashboard(first)) {
|
||||
matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched)
|
||||
let matched = []
|
||||
const pathNum = findPathNum(route.path)
|
||||
// multi-level menu
|
||||
if (pathNum > 2) {
|
||||
const reg = /\/\w+/gi
|
||||
const pathList = route.path.match(reg).map((item, index) => {
|
||||
if (index !== 0) item = item.slice(1)
|
||||
return item
|
||||
})
|
||||
getMatched(pathList, permissionStore.defaultRoutes, matched)
|
||||
} else {
|
||||
matched = route.matched.filter((item) => item.meta && item.meta.title)
|
||||
}
|
||||
// 判断是否为首页
|
||||
if (!isDashboard(matched[0])) {
|
||||
matched = [{ path: "/index", meta: { title: "首页" } }].concat(matched)
|
||||
}
|
||||
|
||||
levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||
}
|
||||
function findPathNum(str, char = "/") {
|
||||
let index = str.indexOf(char)
|
||||
let num = 0
|
||||
while (index !== -1) {
|
||||
num++
|
||||
index = str.indexOf(char, index + 1)
|
||||
}
|
||||
return num
|
||||
}
|
||||
function getMatched(pathList, routeList, matched) {
|
||||
let data = routeList.find(item => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0])
|
||||
if (data) {
|
||||
matched.push(data)
|
||||
if (data.children && pathList.length) {
|
||||
pathList.shift()
|
||||
getMatched(pathList, data.children, matched)
|
||||
}
|
||||
}
|
||||
}
|
||||
function isDashboard(route) {
|
||||
const name = route && route.name
|
||||
if (!name) {
|
||||
@@ -48,7 +80,7 @@ watchEffect(() => {
|
||||
}
|
||||
getBreadcrumb()
|
||||
})
|
||||
getBreadcrumb();
|
||||
getBreadcrumb()
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
@@ -251,7 +251,6 @@ onMounted(() => {
|
||||
.popup-main {
|
||||
position: relative;
|
||||
margin: 10px auto;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user