372 Commits

Author SHA1 Message Date
dvadmin-开发-李强
82a16545b4 Accept Merge Request #5: (develop -> master)
1. 新增: 大数据选择器组件
2. 新增: docker默认一键启动参数
3. 新增: 集成celery和Redis
4. 新增: 封装时间范围的搜索,示例在登录日志页面
5. 优化: 角色授权中的获取授权列表接口
6. 优化: 角色授权中字段权限显示判断
7. 优化: 个人中心修改密码成功后,强制退出登录状态
8. 优化: getBaseURL函数
9. 修复: 页面缓存无效问题
10. 修复: 登录页背景logo设置无效问题
11. 修复: 全局过滤器导致过滤无效问题
12. 修复: 当dept_belong_id为null时,触发接口报错
2024-03-12 22:45:36 +08:00
猿小天
39638e2e6a 功能变化: 优化个人中心修改密码后强制退出登录状态 2024-02-28 15:27:03 +08:00
猿小天
d3e5f258e5 修复BUG: 修复页面缓存问题 2024-02-25 16:30:49 +08:00
猿小天
77a27cba14 Merge remote-tracking branch 'origin/develop' into develop 2024-02-25 15:03:26 +08:00
猿小天
26bbf67da8 新功能: 角色授权字段权限判断 2024-02-25 15:03:07 +08:00
李强
d4f754976e Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	backend/application/settings.py
2024-02-21 17:49:38 +08:00
猿小天
85805e0d4b 功能变化: 优化角色授权中的获取授权列表 2024-02-16 22:41:15 +08:00
猿小天
8a646e5ef7 新功能: 新增大数据选择组件 2024-02-16 20:13:20 +08:00
猿小天
71eec9cfbd 修复BUG:
1.消息中心不加载目标数据问题;
2.地区数据加载问题
2024-01-18 22:12:55 +08:00
猿小天
9bd79b9dc6 修复BUG:
1.角色管理没有分页问题;
2.自定义指令引用问题
2024-01-18 20:25:02 +08:00
李强
e210b7dd17 Merge remote-tracking branch 'origin/develop' into develop 2024-01-12 00:28:56 +08:00
李强
4d00661163 feat: 优化getBaseURL函数 2024-01-12 00:28:52 +08:00
H0nGzA1
6798fca362 fix: 修复登录页背景logo设置 2024-01-04 19:49:40 +08:00
猿小天
6f5bbb045d 修复BUG: 全局过滤器导致的过滤无效 2024-01-04 16:53:12 +08:00
猿小天
bbf3018b20 Merge remote-tracking branch 'origin/develop' into develop 2024-01-03 23:23:24 +08:00
猿小天
5510a18280 修复BUG: 全局过滤器导致的过滤无效 2024-01-03 23:23:13 +08:00
李强
97737c3ef1 feat: 更新docker默认一键启动参数 2024-01-02 23:17:39 +08:00
李强
0b554f3669 feat: 默认集成celery与redis 2024-01-02 22:10:57 +08:00
猿小天
63453450ad 修复BUG: 当dept_belong_id为null时,触发接口报错 2024-01-02 14:39:16 +08:00
猿小天
fea6ebd98e Merge remote-tracking branch 'origin/develop' into develop 2024-01-02 13:50:52 +08:00
猿小天
d4e43e28e8 新功能: 封装时间范围的搜索,示例在登录日志页面 2024-01-02 13:50:43 +08:00
dvadmin-开发-李强
9973688675 Accept Merge Request #4: (develop -> master)
Merge Request: 正式发布v3.0.1版本

Created By: @dvadmin-开发-李强
Accepted By: @dvadmin-开发-李强
URL: https://dvadmin-private.coding.net/p/code/d/dvadmin3/git/merge/4?initial=true
2024-01-01 22:56:43 +08:00
李强
98a5bbd60a feat: 文档更新 2024-01-01 22:41:01 +08:00
李强
ac7e8a7764 feat: CustomModelSerializer序列化器中dept_belong_id 添加默认值 2024-01-01 22:31:43 +08:00
猿小天
26ed59d1ff 功能变化: 修复部分bug 2024-01-01 15:30:48 +08:00
猿小天
369157fa4f 修复BUG: 当部门没有返回顶级部门时,部门接口为空情况 2024-01-01 15:00:17 +08:00
猿小天
8961733025 功能变化: 修复部分bug 2024-01-01 14:47:53 +08:00
猿小天
577b88332f 功能变化: 菜单授权返回包含父级的完整名称 2023-12-28 01:25:58 +08:00
猿小天
411f065bfc 功能变化: 菜单授权返回包含父级的完整名称 2023-12-28 00:41:07 +08:00
猿小天
8d37d929b2 功能变化: 菜单按钮排序 2023-12-27 23:35:28 +08:00
猿小天
c0d6cd54c3 Merge remote-tracking branch 'origin/develop' into develop 2023-12-27 23:29:22 +08:00
猿小天
ae8366f097 功能变化: 部门允许选择父级 2023-12-27 23:29:15 +08:00
猿小天
4baf5da0ff 新功能: 框架外路由 2023-12-27 23:22:25 +08:00
猿小天
c00f5f2beb 新功能: 框架外路由 2023-12-27 23:22:05 +08:00
李强
5143afdd85 feat: 列权限初始化 2023-12-27 21:26:21 +08:00
李强
b4153d1848 feat: 优化commonCrudConfig 2023-12-27 19:14:46 +08:00
猿小天
d2c0c54080 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	web/src/utils/commonCrud.ts
2023-12-27 19:10:53 +08:00
猿小天
7f184b2a9a 新功能:
1.部门显示组件
2023-12-27 19:08:55 +08:00
李强
87f9784445 feat: 优化commonCrudConfig顺序 2023-12-26 15:39:04 +08:00
李强
e900cc2280 feat: 优化所有类型的table居中显示 2023-12-26 15:38:09 +08:00
李强
4a26257a61 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	web/src/settings.ts
2023-12-26 15:37:08 +08:00
李强
06476c182e feat: 优化所有类型的table居中显示 2023-12-26 15:36:26 +08:00
猿小天
6146e6edb6 修复BUG:
1.新增用户时,默认密码
2023-12-26 12:46:19 +08:00
猿小天
85f5ff1935 修复BUG:
1.允许table表头被选中
2023-12-26 11:50:52 +08:00
猿小天
86e33e44e1 功能变化: 将网络字体改为本地字体 2023-12-26 11:33:37 +08:00
猿小天
217f3c8e14 Merge remote-tracking branch 'origin/develop' into develop 2023-12-26 10:49:44 +08:00
猿小天
d0feaa23e5 功能变化: 修改baseUrl函数 2023-12-26 10:49:09 +08:00
李强
fa67a27908 feat: 删除log打印 2023-12-26 09:51:45 +08:00
李强
56145dfe61 feat: 删除log打印 2023-12-26 09:50:49 +08:00
李强
3def205e6e Merge remote-tracking branch 'origin/develop' into develop 2023-12-26 09:48:06 +08:00
李强
873400bcce feat: 新增 placeholder 的默认值 2023-12-26 09:47:59 +08:00
猿小天
0c1cf1218b 新功能: 1. 引入fa 图标,兼容dvadmin2的图标 2023-12-25 20:35:33 +08:00
猿小天
d8e5ad9057 新功能: 加入隐私和条款文件 2023-12-25 20:13:05 +08:00
猿小天
3639cad58c 新功能: 1. 封装所有的更新时间、更新数据、部门等,类似dvadmin2的,一键加入 2023-12-25 20:02:43 +08:00
猿小天
c99eb1a9bc 修复BUG:
1.系统配置没有在刷新页面的时候获取;
2.登录页面网站标题等信息更新名字不刷新;
3.列权限排序,且需可以搜索
2023-12-25 18:48:11 +08:00
猿小天
7ae15022c0 新功能:
1.菜单新增框架外显示字段
2023-12-24 23:05:00 +08:00
猿小天
68b3d479ca 新功能:
1.菜单新增是否固定配置;
2.菜单新增框外显示配置
2023-12-24 12:09:15 +08:00
猿小天
2826cafa75 修复BUG:
1.验证码输入错误不刷新问题
2023-12-24 11:04:28 +08:00
猿小天
ba4a580bb0 修复BUG:
1.修复更新提示,拒绝后仍然弹出问题
2023-12-20 23:56:09 +08:00
猿小天
f322d38ab8 Merge remote-tracking branch 'origin/develop' into develop 2023-12-20 23:45:49 +08:00
猿小天
7d315a7d28 修复BUG:
1.文件上传返回值缺少问题
2023-12-20 23:44:45 +08:00
猿小天
916071814a 修复BUG:
1.接口报错后,继续通过的情况
2023-12-20 23:43:32 +08:00
李强
acf113e5e6 feat: 升级中心弹窗bug 2023-12-19 01:06:18 +08:00
李强
083eef9df1 Merge branch 'master' into develop 2023-12-19 00:54:05 +08:00
李强
e8c64d6d11 feat: 新增存活检测中间件 2023-12-19 00:50:42 +08:00
李强
d1abc58b40 feat: add __init__.py 2023-12-19 00:34:14 +08:00
李强
0c8ce0ce27 feat: 更新celery.py 添加重试机制 2023-12-18 18:14:18 +08:00
猿小天
23d320434e Merge remote-tracking branch 'origin/develop' into develop 2023-12-06 18:28:09 +08:00
猿小天
14907e140d 修复BUG:
1.修复加入列权限后没有分页问题
2023-12-06 18:28:00 +08:00
猿小天
1132e04080 修复BUG:
1.修复加入列权限后没有分页问题
2023-12-06 18:15:18 +08:00
猿小天
39fee0875d 修复BUG:
1.修复加入列权限后没有分页问题
2023-12-06 18:00:21 +08:00
符锦辉
5898ae85de chore: 后端降低channels版本为3 2023-12-05 17:56:43 +08:00
猿小天
172a867bad 修复BUG:
1.部门信息递归获取子部门
2023-12-04 23:57:40 +08:00
猿小天
5a308695fd 修复BUG:
1.个人中心密码修改,未对密码进行md5加密
2023-12-04 23:53:22 +08:00
猿小天
0c5c3c4ecd 修复BUG:
1.修复字段权限不显示序号的问题
2023-12-04 18:19:58 +08:00
猿小天
9a8241023b 修复BUG:
1.个人中心密码修改,未对密码进行md5加密
2023-12-03 23:28:53 +08:00
猿小天
27be35616b 修复BUG:
1.菜单管理中状态在编辑时显示不正确的问题
2023-12-03 23:24:32 +08:00
猿小天
e6f0a5e0ea 修复BUG:
1.修复角色相关代码中存在is_admin引起的错误
2023-12-03 23:07:18 +08:00
猿小天
cbebd8305c 修复BUG:
1.自定义权限bug
2023-12-03 23:04:26 +08:00
猿小天
66b68523f5 功能变化:
1.优化字典配置的表格显示;
2023-12-03 22:51:58 +08:00
猿小天
95b14e5a42 功能变化:
1.优化角色管理中的列权限;
2023-12-03 22:51:42 +08:00
猿小天
583b172a37 功能变化:
1.优化对字段权限的处理
2023-12-03 22:42:37 +08:00
猿小天
2227564c6f 功能变化:
1.优化对字段权限的处理
2023-12-03 15:19:42 +08:00
猿小天
5e3ecfc0a6 功能变化:
1.优化对字段权限的处理
2023-12-03 15:03:18 +08:00
猿小天
d467915f97 Merge remote-tracking branch 'origin/develop' into develop 2023-12-01 17:31:47 +08:00
猿小天
197714e845 修复BUG:
1.修复settings中上传文件的bug;
2.优化字典展示
2023-12-01 17:31:37 +08:00
dvadmin-开发-李强
bc15ed15ca Accept Merge Request #2: (develop -> master)
Merge Request: 正式发布v3.0.0版本

Created By: @dvadmin-开发-李强
Accepted By: @dvadmin-开发-李强
URL: https://dvadmin-private.coding.net/p/code/d/dvadmin3/git/merge/2
2023-11-30 22:13:42 +08:00
李强
349986f14f 正式发布v3.0.0版本 2023-11-30 22:02:49 +08:00
李强
fb4bba9000 feat: 样式优化 2023-11-30 21:37:43 +08:00
李强
e0d46871c4 update README.md 2023-11-30 21:33:32 +08:00
李强
7691481006 feat: 优化页面样式 2023-11-30 21:31:55 +08:00
李强
120c737de7 feat: 优化获取所有项目下的app里的models配置项 2023-11-30 20:23:52 +08:00
猿小天
5729aeb521 refactor: 上传最新的英文README 2023-11-30 16:13:49 +08:00
猿小天
205cfcca2e refactor: 上传最新的README 2023-11-30 15:57:42 +08:00
猿小天
0fdc108ebf Merge remote-tracking branch 'origin/develop' into develop 2023-11-28 10:29:52 +08:00
猿小天
064cbee8a2 1.更新系统配置初始化文件 2023-11-28 10:29:29 +08:00
李强
a2d4d6e07e feat: 初始化菜单问题 2023-11-28 10:29:05 +08:00
猿小天
d721405ee4 1.更新列权限初始化文件 2023-11-26 11:43:43 +08:00
猿小天
0650c95745 1.更新列权限初始化文件 2023-11-26 11:37:21 +08:00
猿小天
f291885d57 1.更新按钮权限初始化文件 2023-11-26 11:03:26 +08:00
猿小天
4b77fb903c refactor: 检测目前所有菜单权限 2023-11-24 15:37:47 +08:00
李强
fca82c093a feat: 优化deep 2023-11-24 15:25:41 +08:00
猿小天
49300fb17e Merge remote-tracking branch 'origin/develop' into develop 2023-11-24 15:13:23 +08:00
猿小天
c763333024 refactor: 检测目前所有菜单权限 2023-11-24 15:12:46 +08:00
李强
42e6c7b600 feat: websocket优化 2023-11-23 18:55:25 +08:00
李强
6172dea399 feat: 优化商业授权标志 2023-11-23 16:33:19 +08:00
李强
2baba8e36f feat: 版本升级弹窗 2023-11-23 16:19:35 +08:00
李强
0131d31808 feat: 优化docker 2023-11-22 21:58:10 +08:00
李强
dd2dcd4ad1 feat: 优化docker 2023-11-22 17:52:39 +08:00
猿小天
fb8b0a5ac6 refactor: 重构权限管理
1.更新字段管理
2023-11-21 21:21:58 +08:00
猿小天
6045312f7e 1.修改菜单初始化文件 2023-11-21 19:22:00 +08:00
猿小天
4641d9c774 Merge remote-tracking branch 'origin/develop' into develop 2023-11-21 17:51:31 +08:00
猿小天
c04b33ed31 refactor: 重构权限管理
1.更新字段管理
2023-11-21 17:51:19 +08:00
李强
0286ca003f feat: 更新.gitignore 2023-11-21 16:01:42 +08:00
猿小天
54e4d23cf7 refactor: 重构权限管理
1.更新字段管理
2023-11-21 14:21:08 +08:00
猿小天
645f43c887 refactor: 重构权限管理
1.更新字段管理
2023-11-20 19:03:14 +08:00
猿小天
4ac8ed7627 refactor: 重构权限管理
1.更新字段管理
2023-11-20 17:19:20 +08:00
猿小天
94ad6b1bae refactor: 重构权限管理
1.更新字段管理
2023-11-20 17:02:37 +08:00
china_ahhui
96748da99d refactor: 优化配置文件的app配置 2023-11-13 18:27:10 +08:00
china_ahhui
b74a196c94 chore: 修改后端的忽略文件 2023-11-13 17:45:49 +08:00
H0nGzA1
dd98b98775 feat: 更新fast-crud版本 2023-10-27 16:05:06 +08:00
李强
8e107d9944 feat: 优化默认头像显示 2023-10-19 16:55:19 +08:00
猿小天
7159253c6b Merge remote-tracking branch 'origin/master' 2023-10-19 16:10:15 +08:00
猿小天
075d457dc7 fix: 修复列管理的问题 2023-10-19 16:10:05 +08:00
李强
564e9a112b feat: 更新requirements.txt 2023-10-19 16:01:59 +08:00
李强
3b84773a7d feat: 更新requirements.txt 2023-10-19 16:01:16 +08:00
李强
996d644d9b 删除 .DS_Store 2023-10-19 15:36:07 +08:00
china_ahhui
97f7bfd3f0 chore(用户管理): 添加了直接重置密码的约束,只允许超管可以操作 2023-10-10 17:15:40 +08:00
china_ahhui
d1fdbd7e51 Merge branches 'master' and 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-10-10 17:12:50 +08:00
china_ahhui
802ee7f50e fix(设置): 修复env的引入bug 2023-10-10 17:12:33 +08:00
sheng
ddf9fcd8e8 feat(部门管理): 查询框多行模式添加 2023-10-10 16:11:49 +08:00
sheng
6f15c2f669 feat(部门管理): 重设密码添加 2023-10-10 15:44:13 +08:00
李强
084d1c0c3e 删除lock文件 2023-10-03 23:26:15 +08:00
李强
6ded198206 Merge branch 'master' of e.coding.net:dvadmin/dvadmin3/dvadmin3
# Conflicts:
#	README.md
#	backend/conf/env.example.py
#	backend/docker_start.sh
#	backend/requirements.txt
#	docker_env/README.md
#	docker_env/mysql/launch.sh
#	docker_env/web/Dockerfile
#	docker_env/web/DockerfileBuild
#	web/.env.development
#	web/package.json
2023-10-03 23:22:43 +08:00
李强
e549d33a48 更新启动方式 2023-10-03 23:11:51 +08:00
china_ahhui
cbba5a60f4 fix(用户管理): 修复在部门显示子级的情况下搜索失效的bug 2023-10-03 23:09:55 +08:00
china_ahhui
773b56a5e6 style: 修改忽略文件 2023-10-03 23:09:55 +08:00
H0nGzA1
fb29929f55 perf: 优化首页登录 2023-10-03 23:09:55 +08:00
H0nGzA1
0772a605bf perf: 登录验证码自动刷新 2023-10-03 23:09:55 +08:00
H0nGzA1
496c453615 fix: transition组件热重载显示页面空白bug 2023-10-03 23:09:55 +08:00
sheng
58b59c2ed0 部门管理隐藏统计图表添加 2023-10-03 23:09:55 +08:00
sheng
94815f4b20 列权限添加 2023-10-03 23:09:55 +08:00
猿小天
ad6ec5ca58 1.修改字段权限判断是否为超级管理员 2023-10-03 23:09:51 +08:00
猿小天
4bcaf8d45f 1.新增字段权限接口 2023-10-03 23:09:51 +08:00
猿小天
be5db33a55 1.修复单例查询时,报404问题; 2023-10-03 23:09:51 +08:00
猿小天
cfc5f8597a 1.字段权限配置完成; 2023-10-03 23:09:51 +08:00
猿小天
209e2ecced 1.菜单和按钮权限范围授权完成; 2023-10-03 23:09:51 +08:00
猿小天
4b948fe5c3 1.菜单和按钮权限范围授权完成; 2023-10-03 23:09:51 +08:00
猿小天
ea335922b2 1.菜单和按钮基本授权完成;
2. todo:
a.按钮数据权限;
b.字段权限
2023-10-03 23:09:51 +08:00
符锦辉
d290836f4f 更新文件 init_menu.json 2023-10-03 23:09:36 +08:00
符锦辉
9a426dbd72 更新文件 routing.py 2023-10-03 23:09:36 +08:00
符锦辉
a0b6e57a3c 更新文件 urls.py 2023-10-03 23:09:36 +08:00
符锦辉
5a89cdf889 删除 device 2023-10-03 23:09:36 +08:00
ahhui
b6b970f7a2 feat: 物联网相关代码 2023-10-03 23:09:36 +08:00
ahhui
2722631276 test: 列权限测试 2023-10-03 23:09:36 +08:00
ahhui
478aea32f1 chore: 更新依赖文件 2023-10-03 23:09:36 +08:00
sheng
cf55897552 chore: 设备管理去除 2023-10-03 23:09:36 +08:00
sheng
8020da03e2 feat( 角色管理 ): 新权限配置页面完成 2023-10-03 23:09:36 +08:00
ahhui
c646563d26 chore: 更新初始化文件 2023-10-03 23:09:36 +08:00
sheng
43a4572a71 feat( 角色管理 ): 权限配置重写 2023-10-03 23:09:36 +08:00
ahhui
1582353279 fix: 修复部门和菜单新增父级下的第一个子级菜单时sort找不到的bug 2023-10-03 23:09:36 +08:00
sheng
1df2101658 feat(设备管理): 文件添加 2023-10-03 23:09:36 +08:00
ahhui
0679198d0f perf: 优化列权限新增代码 2023-10-03 23:09:36 +08:00
sheng
e3c478a263 feat(列管理): 样式修改 2023-10-03 23:09:36 +08:00
ahhui
07cf81c9fe chore: 更改jwt有效期 2023-10-03 23:09:36 +08:00
ahhui
1b98fbc982 feat: 新增列权限配置功能
新增列权限配置,优化代码结构,更新初始化文件
2023-10-03 23:09:36 +08:00
ahhui
85040c808f chore: 更新初始化文件 2023-10-03 23:09:36 +08:00
ahhui
00b2cac7a5 chore: 更新初始化文件 2023-10-03 23:09:36 +08:00
ahhui
7c27f95353 refactor: 重构配置文件
所有项目的app都放在env.py里的CUSTOM_APPS列表里注册,为了支持按照项目app查找model
2023-10-03 23:09:36 +08:00
ahhui
d59df6db7c perf: 优化部分代码结构 2023-10-03 23:08:41 +08:00
ahhui
34c55cfd7e fix(菜单): 修复新增菜单时排序错误的bug 2023-10-03 23:08:41 +08:00
sheng
191e57dcd9 feat(列管理): 列管理页面添加 2023-10-03 23:08:41 +08:00
ahhui
9be50863d9 chore(优化): 优化代码结构 2023-10-03 23:08:41 +08:00
ahhui
9c218a856c feat(部门): 添加部门头信息查询以及递归的头信息查询,优化部分代码 2023-10-03 23:08:41 +08:00
ahhui
b4ebb14843 fix(用户): 重写user视图集的list视图,支持递归查询 2023-10-03 23:08:41 +08:00
sheng
17d80b0b00 revert(角色管理): 回滚角色管理代码 2023-10-03 23:08:41 +08:00
sheng
01394c6904 feat(部门管理): 部门详情与统计的添加 2023-10-03 23:08:41 +08:00
ahhui
59b1b54da1 fix(部门): 修复部门排序bug
修复添加新部门后排序值依旧是1导致前端上移下移部门时不生效的bug,添加部门上移下移的权限校验
2023-10-03 23:08:41 +08:00
sheng
8084ca7b0b feat(部门管理): 部门信息布局完成 2023-10-03 23:08:41 +08:00
sheng
427b6f8478 perf(菜单管理,部门管理): 编辑时父级菜单懒加载的修改 2023-10-03 23:08:37 +08:00
sheng
41a9888441 feat(菜单管理): 新建编辑完成 2023-10-03 23:08:37 +08:00
sheng
5139d37007 feat(菜单管理): 父级菜单改为树形结构 2023-10-03 23:08:37 +08:00
ahhui
44bceb6e22 chore: 更新初始化用户数据 2023-10-03 23:08:37 +08:00
sheng
c2f8c43905 refactor(菜单管理管理): 组件抽离 2023-10-03 23:08:37 +08:00
ahhui
9f479e476d perf: 微调验证码配置 2023-10-03 23:08:37 +08:00
ahhui
f5a7b157f9 build: 增加pillow依赖 2023-10-03 23:08:37 +08:00
sheng
7110d10432 feat(部门管理): 新增编辑实现 2023-10-03 23:08:37 +08:00
sheng
05f4e2970d feat(部门管理): 上移下移实现 2023-10-03 23:08:33 +08:00
H0nGzA1
5353a27187 feat: 后端-部门管理-上移下移排序功能&新增部门人数字段 2023-10-03 23:08:33 +08:00
H0nGzA1
f4ff5d87af fix: 移动菜单子级问题 2023-10-03 23:08:33 +08:00
sheng
ceac44813f fix(菜单管理): 上移下移修改 2023-10-03 23:08:33 +08:00
H0nGzA1
a490ced370 feat: 菜单管理-上下移动功能 2023-10-03 23:07:30 +08:00
sheng
20188261c5 refactor: 菜单管理新增重构 2023-10-03 23:06:26 +08:00
sheng
15ef01aed9 feat: 部门管理新增用户列表 2023-10-03 23:06:26 +08:00
sheng
1dea6ed29c refactor: 部门新增与编辑 2023-10-03 23:06:22 +08:00
sheng
2bad36c982 feat:部门管理重构 2023-10-03 23:05:59 +08:00
sheng
941dbbc783 feat: 菜单管理菜单列表上移下移添加 2023-10-03 23:05:59 +08:00
sheng
14881707e2 页面美化:菜单管理页面优化 2023-10-03 23:05:53 +08:00
sheng
d60c795ea2 页面美化:菜单管理树的美化 2023-10-03 23:05:42 +08:00
sheng
9cf5d13f1c 页面美化:登陆页面优化 2023-10-03 23:05:32 +08:00
china_ahhui
e1618359d7 fix(用户管理): 修复在部门显示子级的情况下搜索失效的bug 2023-09-19 11:45:37 +08:00
china_ahhui
abe7972ab4 style: 修改忽略文件 2023-09-19 10:20:19 +08:00
H0nGzA1
0709e72ae1 perf: 优化首页登录 2023-09-17 22:25:55 +08:00
H0nGzA1
2e676d64bb perf: 登录验证码自动刷新 2023-09-17 19:42:50 +08:00
H0nGzA1
98e974b55e fix: transition组件热重载显示页面空白bug 2023-09-16 02:00:22 +08:00
sheng
e7f78fbae0 部门管理隐藏统计图表添加 2023-09-11 14:58:34 +08:00
sheng
111edb3f4a 列权限添加 2023-09-11 12:02:33 +08:00
猿小天
86f539a193 1.修改字段权限判断是否为超级管理员 2023-09-11 11:33:09 +08:00
李强
3b9dc7c171 更新dockerfile 2023-09-09 22:38:41 +08:00
猿小天
8175c620ed 1.新增字段权限接口 2023-09-09 10:42:55 +08:00
猿小天
de5884d28d 1.修复单例查询时,报404问题; 2023-09-07 23:24:21 +08:00
猿小天
e63a7cab6f 1.字段权限配置完成; 2023-08-19 22:06:58 +08:00
猿小天
cdfdf0a483 1.菜单和按钮权限范围授权完成; 2023-08-18 00:42:26 +08:00
猿小天
515f720a59 1.菜单和按钮权限范围授权完成; 2023-08-18 00:39:14 +08:00
猿小天
5524446dbc 1.菜单和按钮基本授权完成;
2. todo:
a.按钮数据权限;
b.字段权限
2023-08-15 23:55:46 +08:00
sheng
9c32c7f60b Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-11 18:03:05 +08:00
sheng
6eed2814f8 chore: 设备管理去除 2023-08-11 18:02:59 +08:00
符锦辉
f03e61ce63 更新文件 init_menu.json 2023-08-11 18:01:22 +08:00
符锦辉
5450102275 更新文件 routing.py 2023-08-11 17:44:27 +08:00
符锦辉
689ce4b43b 更新文件 urls.py 2023-08-11 17:41:59 +08:00
符锦辉
65af240757 删除 device 2023-08-11 17:41:25 +08:00
ahhui
f61025c6db feat: 物联网相关代码 2023-08-08 17:40:28 +08:00
ahhui
516f0ac675 test: 列权限测试 2023-08-08 16:31:55 +08:00
ahhui
b1b5ae5fcd chore: 更新依赖文件 2023-08-08 16:31:18 +08:00
sheng
9a847d078f feat( 角色管理 ): 新权限配置页面完成 2023-08-08 16:16:32 +08:00
sheng
dcac5c759a Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-07 18:26:14 +08:00
sheng
13999142aa feat( 角色管理 ): 权限配置重写 2023-08-07 18:26:08 +08:00
ahhui
cd6318c6fd chore: 更新初始化文件 2023-08-07 16:31:06 +08:00
sheng
3c58bcf467 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-07 16:22:25 +08:00
sheng
b9bdf878de feat(设备管理): 文件添加 2023-08-07 16:22:17 +08:00
ahhui
db989671c0 Merge remote-tracking branch 'origin/master' 2023-08-07 14:50:40 +08:00
ahhui
f67869d428 fix: 修复部门和菜单新增父级下的第一个子级菜单时sort找不到的bug 2023-08-07 14:50:34 +08:00
sheng
78c693718b Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-07 13:42:13 +08:00
sheng
653483e5de feat(列管理): 样式修改 2023-08-07 13:42:03 +08:00
ahhui
89ba768932 Merge remote-tracking branch 'origin/master' 2023-08-04 18:26:50 +08:00
ahhui
e4326b6d4e perf: 优化列权限新增代码 2023-08-04 18:26:44 +08:00
sheng
97020ba4e8 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-04 18:13:09 +08:00
sheng
945db3344c feat(列管理): 列管理页面添加 2023-08-04 18:13:04 +08:00
ahhui
19df23ce5f chore: 更改jwt有效期 2023-08-04 18:12:58 +08:00
ahhui
97c76b6afe feat: 新增列权限配置功能
新增列权限配置,优化代码结构,更新初始化文件
2023-08-04 18:10:51 +08:00
ahhui
6597826fdd chore: 更新初始化文件 2023-08-04 12:01:12 +08:00
ahhui
ee419e282e chore: 更新初始化文件 2023-08-04 11:55:34 +08:00
ahhui
3d12d7a5c5 Merge remote-tracking branch 'origin/master' 2023-08-03 18:04:43 +08:00
ahhui
b2aa22ce84 refactor: 重构配置文件
所有项目的app都放在env.py里的CUSTOM_APPS列表里注册,为了支持按照项目app查找model
2023-08-03 18:04:34 +08:00
ahhui
06d7d718e1 perf: 优化部分代码结构 2023-08-03 18:02:55 +08:00
ahhui
6067dfac2f fix(菜单): 修复新增菜单时排序错误的bug 2023-08-03 18:01:49 +08:00
sheng
637e45ef83 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-03 17:39:09 +08:00
sheng
202f67460e revert(角色管理): 回滚角色管理代码 2023-08-03 17:39:01 +08:00
ahhui
7da23ab6a6 chore(优化): 优化代码结构 2023-08-03 10:44:57 +08:00
ahhui
be51124bb6 feat(部门): 添加部门头信息查询以及递归的头信息查询,优化部分代码 2023-08-02 18:21:14 +08:00
ahhui
7e7637f10b Merge remote-tracking branch 'origin/master' 2023-08-02 18:19:59 +08:00
ahhui
dbb94fb9ff fix(用户): 重写user视图集的list视图,支持递归查询 2023-08-02 18:19:52 +08:00
sheng
fea61e35ef feat(部门管理): 部门详情与统计的添加 2023-08-02 17:40:57 +08:00
sheng
8ecfc1143b Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-01 18:29:41 +08:00
sheng
7c72cc74df feat(部门管理): 部门信息布局完成 2023-08-01 18:29:31 +08:00
ahhui
12f5125207 Merge remote-tracking branch 'origin/master' 2023-08-01 15:25:48 +08:00
ahhui
770c5f73c0 fix(部门): 修复部门排序bug
修复添加新部门后排序值依旧是1导致前端上移下移部门时不生效的bug,添加部门上移下移的权限校验
2023-08-01 15:25:42 +08:00
sheng
98b81a789d perf(菜单管理,部门管理): 编辑时父级菜单懒加载的修改 2023-08-01 15:13:50 +08:00
sheng
c13a5f0dda feat(菜单管理): 新建编辑完成 2023-08-01 13:50:29 +08:00
sheng
a524137e18 feat(菜单管理): 父级菜单改为树形结构 2023-07-31 17:58:52 +08:00
sheng
2c9d8766e3 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-07-31 17:06:38 +08:00
sheng
8a0f18eab1 refactor(菜单管理管理): 组件抽离 2023-07-31 17:05:15 +08:00
ahhui
d8511eeb7b Merge remote-tracking branch 'origin/master' 2023-07-31 16:43:42 +08:00
ahhui
52a91f1703 chore: 更新初始化用户数据 2023-07-31 16:43:37 +08:00
sheng
14395621c0 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-07-31 14:49:18 +08:00
sheng
c4e2451ac8 feat(部门管理): 新增编辑实现 2023-07-31 14:49:12 +08:00
ahhui
936928e854 perf: 微调验证码配置 2023-07-31 14:45:09 +08:00
ahhui
619ebeacf1 build: 增加pillow依赖 2023-07-31 14:43:59 +08:00
sheng
b95679a986 feat(部门管理): 上移下移实现 2023-07-31 10:33:57 +08:00
sheng
8e930c6a10 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-07-31 10:23:55 +08:00
sheng
d2811a1f87 fix(菜单管理): 上移下移修改 2023-07-31 10:23:46 +08:00
H0nGzA1
7ac5c86ed3 feat: 后端-部门管理-上移下移排序功能&新增部门人数字段 2023-07-30 22:43:30 +08:00
H0nGzA1
eea5cdadfe Merge remote-tracking branch 'origin/master' 2023-07-28 18:12:06 +08:00
H0nGzA1
4e5f9ce946 fix: 移动菜单子级问题 2023-07-28 18:11:57 +08:00
sheng
e790882ab6 Merge remote-tracking branch 'origin/master' 2023-07-28 17:24:06 +08:00
sheng
0f86f5c6fc refactor: 菜单管理新增重构 2023-07-28 17:09:41 +08:00
H0nGzA1
4018113161 Merge remote-tracking branch 'origin/master' 2023-07-28 16:14:13 +08:00
H0nGzA1
252b6a8328 feat: 菜单管理-上下移动功能 2023-07-28 16:13:33 +08:00
sheng
c26abef364 feat: 部门管理新增用户列表 2023-07-28 14:37:42 +08:00
sheng
a40dc9afb1 refactor: 部门新增与编辑 2023-07-28 11:16:07 +08:00
sheng
9114c6bf81 feat:部门管理重构 2023-07-27 18:31:28 +08:00
sheng
7b0a6bc2be feat: 菜单管理菜单列表上移下移添加 2023-07-26 18:12:15 +08:00
sheng
e4ca462153 页面美化:菜单管理页面优化 2023-07-26 16:55:22 +08:00
sheng
840323ff07 页面美化:菜单管理树的美化 2023-07-26 13:35:47 +08:00
H0nGzA1
ed0dca7346 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	web/yarn.lock
2023-07-25 18:33:45 +08:00
H0nGzA1
2b63966ee2 feat: update fast-crud 2023-07-25 18:33:11 +08:00
sheng
55a2f73420 页面美化:登陆页面优化 2023-07-25 16:45:47 +08:00
sheng
7ccbcebe77 init 2023-07-25 15:25:57 +08:00
李一民
8f57a240db Initial Commit 2023-07-25 14:53:35 +08:00
猿小天
1ccfd619f3 fix: 查询时空字符串问题 2023-07-12 21:34:57 +08:00
猿小天
dcdc7bbe3b fix: 权限问题,文件管理页面,查询时TF问题
1.权限编辑提交时,值不变问题;
2.恢复文件管理页面;
3.查询时T/F后台报错问题;
2023-07-12 21:18:49 +08:00
猿小天
90557ea039 fix: 部门懒加载
修复部门懒加载报data_range错误
2023-06-10 22:39:58 +08:00
猿小天
a462e15643 refactor: ♻️ 授权页面重构
重构授权页面
2023-06-09 20:35:06 +08:00
猿小天
6a24683769 refactor: ♻️ 授权页面重构
重构授权页面
2023-06-08 13:20:31 +08:00
猿小天
9fa1775b77 Merge remote-tracking branch 'origin/dev' into dev 2023-06-08 13:17:18 +08:00
猿小天
16605779ed refactor: ♻️ 授权页面重构
重构授权页面
2023-06-08 13:17:10 +08:00
unknown
186658858d Merge branch 'dev' of https://gitee.com/huge-dream/django-vue3-admin into dev 2023-06-07 17:20:38 +08:00
unknown
996cb1447d fix: 🐛 url false拼接bug修复 2023-06-07 17:20:31 +08:00
猿小天
e8b84ad71f refactor: ♻️ 授权页面重构
重构授权页面
2023-06-06 20:57:07 +08:00
猿小天
e7065ab89e Merge remote-tracking branch 'origin/dev' into dev 2023-06-06 20:53:08 +08:00
猿小天
e962ca6559 refactor: ♻️ 授权页面重构
重构授权页面
2023-06-06 20:52:25 +08:00
unknown
4bdc256311 perf: 💄 部门列表和菜单列表字体修改,switch颜色修改 2023-06-06 17:52:47 +08:00
猿小天
f7b94e496c refactor: ♻️ 授权页面重构
重构授权页面
2023-06-05 22:21:19 +08:00
李强
8c0a04ce61 perf: 🐛 用户登录后,每次都会变化密码加密值 2023-05-28 13:28:36 +08:00
李强
31f20a766f perf: 🐛 filter_fields使用exact无效,使用icontains等报错
fix: https://gitee.com/liqianglog/django-vue-admin/issues/I6QZW5
2023-05-28 13:24:38 +08:00
李强
65a34a683c 配置变化: 验证码接口bug 2023-05-26 20:07:36 +08:00
H0nGzA1
66effde1d9 perf: 用户-重设密码 2023-05-18 00:15:39 +08:00
H0nGzA1
f4f958d2e9 perf: 优化细节 2023-05-17 23:48:59 +08:00
H0nGzA1
d10127eafc feat: keepAlive优化 2023-05-17 23:43:05 +08:00
H0nGzA1
60035767ad feat: keepAlive功能 2023-05-17 23:34:41 +08:00
H0nGzA1
adc522c180 fix: 字典管理-配置:无搜索框问题(https://gitee.com/huge-dream/django-vue3-admin/issues/I6ZGNK) 2023-05-17 21:37:17 +08:00
H0nGzA1
17b67e43c9 refactor: 前端布局 菜单,部门:重构 2023-05-17 21:25:59 +08:00
猿小天
3cccfaa7f4 feat(文件管理): 文件管理修改 2023-05-16 00:26:49 +08:00
猿小天
ce4d410e53 feat(文件管理): 文件管理修改 2023-05-16 00:24:22 +08:00
猿小天
7b0341b527 feat(文件管理): 文件管理修改 2023-05-15 22:11:40 +08:00
猿小天
31f7175186 feat(文件管理): 文件管理修改 2023-05-15 22:11:12 +08:00
猿小天
2c22ecbac5 feat: 🎉 导入导出
用户管理加入导入导出
2023-05-13 00:36:16 +08:00
猿小天
ad59bc68aa fix: 🐛 菜单管理
1.修复菜单管理问题;
2023-05-09 00:14:29 +08:00
猿小天
8d7d775164 fix: 🐛 菜单管理和字典管理
1.修复菜单管理问题;
2.修复字典管理问题;
2023-05-09 00:05:41 +08:00
H0nGzA1
9c2dea9db5 ci: 🐳 web env文件线上环境地址配置 2023-04-29 01:03:57 +08:00
H0nGzA1
8c31bda3da ci: 🐳 docker环境 nginx代理地址修改 2023-04-29 01:02:20 +08:00
H0nGzA1
7f7d88fe5b feat: 新增支持新版租户功能 2023-04-29 00:55:23 +08:00
猿小天
19e18a7b6f feat(全局配置): 🔧 字典配置和文件上传配置 2023-04-28 18:19:29 +08:00
猿小天
cd6cd775e7 fix(个人中心): 🐛 性别选项和字典对应问题 2023-04-28 17:48:16 +08:00
猿小天
111ca9554c fix(登录): 🐛 自动登录导致登录错误问题 2023-04-28 17:34:05 +08:00
猿小天
c9fddbd4a7 fix(角色授权): 🐛 权限删除失败问题 2023-04-28 12:02:11 +08:00
猿小天
d3b057f75a fix(系统配置): 🐛 系统配置中文件上传,内容添加,分组添加
1.文件上传;
2.内容添加;
3.添加完成后刷新
2023-04-28 11:54:50 +08:00
猿小天
096f5919af fix: 🐛 系统配置中switch的文件 2023-04-28 11:05:56 +08:00
H0nGzA1
25cc2c83c1 feat: 全局crud的ElNotification提示,ElNotification的封装 2023-04-21 20:49:48 +08:00
H0nGzA1
054bcf3eea build: ⬆️ 更新依赖:fast-crud版本 2023-04-21 15:07:52 +08:00
H0nGzA1
9c24e61123 fix: 🐛 修复菜单管理的添加权限按钮bug 2023-04-21 14:37:02 +08:00
H0nGzA1
cc36ff3d8f Merge branch 'master' into dev 2023-04-21 14:30:12 +08:00
H0nGzA1
93e74dbaa2 Merge remote-tracking branch 'origin/master' 2023-04-21 14:29:53 +08:00
H0nGzA1
5ab3ad4ed9 fix: 🐛 get查询params参数问题,导致分页不成功 2023-04-21 14:27:03 +08:00
李强
9468abd4a2 新功能: 取消loguru日志,使用简单Django日志 2023-04-17 23:24:43 +08:00
H0nGzA1
1080af6413 Merge remote-tracking branch 'origin/dev' into dev 2023-04-14 23:09:15 +08:00
H0nGzA1
c4c9a81ac8 fix: 🐛 部门删除bug 2023-04-13 02:24:31 +08:00
H0nGzA1
d1223dddd3 feat: 用户管理细节优化 2023-04-12 02:13:57 +08:00
H0nGzA1
3dd68e5d21 feat: 账号锁定功能 2023-04-12 01:58:34 +08:00
H0nGzA1
0668cb087f fix: 🐛 修复添加用户bug 2023-04-12 00:25:36 +08:00
H0nGzA1
38494edea5 fix: 🐛 头像更新问题 2023-04-11 22:43:33 +08:00
猿小天
2f04f22904 fix(文件上传): 🐛 文件上传 2023-04-11 19:09:12 +08:00
H0nGzA1
0ccf2e3725 fix: 🐛 头像上传,文件上传问题 2023-04-11 18:40:20 +08:00
H0nGzA1
68cd42246d Merge remote-tracking branch 'origin/dev' into dev 2023-04-10 17:59:42 +08:00
H0nGzA1
dc6ddda4ab refactor: ♻️ 重构部门管理前端 2023-04-10 17:59:30 +08:00
猿小天
08b56ee663 refactor(所有页面): ♻️ 所有页面的按钮权限配置 2023-04-10 16:45:49 +08:00
猿小天
82d7193dcb refactor(所有页面): ♻️ 所有页面的表格列宽优化 2023-04-10 09:45:25 +08:00
H0nGzA1
4b609ed7e4 Merge remote-tracking branch 'origin/dev' into dev 2023-04-10 00:04:00 +08:00
H0nGzA1
61066b8caa refactor: ♻️ 重构菜单管理前端 2023-04-10 00:03:17 +08:00
猿小天
160c376f81 fix(登录): 登录优化 2023-04-09 23:09:35 +08:00
猿小天
b96c220f1d refactor(登录页面): ♻️ 登录优化 2023-04-09 23:08:30 +08:00
猿小天
42884685ff refactor(用户管理): ♻️ 用户管理优化 2023-04-09 22:26:26 +08:00
H0nGzA1
58e611cb22 feat: 前端自动注册插件功能 2023-03-31 00:25:23 +08:00
H0nGzA1
0b2fa1e92c refactor: ♻️ 更新优化精简fast-crud结构 2023-03-30 21:42:45 +08:00
H0nGzA1
125dfd4dd5 feat: 更新fast-crud版本以及依赖 2023-03-30 18:21:38 +08:00
H0nGzA1
af7bfcd215 feat: 所有菜单页面状态改为可编辑开关 2023-03-30 18:12:38 +08:00
H0nGzA1
6917786864 feat: 所有菜单页面样式优化~ 2023-03-30 16:43:57 +08:00
H0nGzA1
cd60b3d9be refactor: ♻️ 菜单管理重构完成 2023-03-30 15:37:27 +08:00
H0nGzA1
cfae363f53 feat: 精简化初始化菜单json文件 2023-03-29 18:29:19 +08:00
H0nGzA1
4f8686b35e feat: 登录页面,自动刷新验证码 2023-03-28 21:21:05 +08:00
H0nGzA1
1407a25b6a feat: 登录页面回车登录功能 2023-03-28 20:35:35 +08:00
H0nGzA1
7c78c7e53f refactor: ♻️ 菜单管理重构完成 2023-03-28 20:09:49 +08:00
H0nGzA1
f1da0507dd Merge remote-tracking branch 'origin/dev' into dev 2023-03-24 18:46:42 +08:00
H0nGzA1
df74c792f6 refactor: ♻️ 用户管理重构 2023-03-24 18:45:58 +08:00
raymond
4c697ef9d4 Merge remote-tracking branch 'origin/dev' into dev 2023-03-24 16:42:12 +08:00
raymond
ab1d1c87e2 feat: 支持通过python manage.py createsuperuser创建管理员用户 2023-03-24 16:41:22 +08:00
H0nGzA1
b6b3b4a39a feat: 🚀 更新nginx配置文件 2023-03-24 15:17:43 +08:00
H0nGzA1
581e4cd92f feat: 🔧 tailwind css 配置 2023-03-23 21:10:07 +08:00
H0nGzA1
fb76d4271b Merge remote-tracking branch 'origin/dev' into dev 2023-03-23 18:13:49 +08:00
victory
4f176cf88a Merge branch 'dev' of gitee.com:huge-dream/django-vue3-admin into dev 2023-03-22 22:39:22 +08:00
victory
af5f3b7137 feat:添加对url中排序参数的解析 2023-03-22 22:39:19 +08:00
victory
b03037b866 fix:优化角色管理用户管理和接口白名单的页面显示 2023-03-22 22:39:08 +08:00
203 changed files with 13869 additions and 10099 deletions

BIN
.DS_Store vendored

Binary file not shown.

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@
.history/
.vscode/
web/package-lock.json

10
CHANGELOG.md Normal file
View File

@@ -0,0 +1,10 @@
# Django-Vue3-Admin 更新日志
## 正式发布v3.0.0版本
### 1.新增:列权限管理与授权;
### 2.新增:代码新版本发布后,进行升级提醒;
### 3.优化:角色管理中按钮权限的操作;
### 4.优化websocket 连接状态显示;
### 5.优化:初始化获取系统配置与字典配置,进行动态渲染登录页面;
### 6.修复:登录页面中系统配置不生效问题;
### 7.其他优化

241
README.md
View File

@@ -1,196 +1,175 @@
# Django-Vue3-Admin
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-3.2-blue)](https://docs.djangoproject.com/zh-hans/3.2/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/liqianglog/django-vue-admin/badge/star.svg?theme=dark)](https://gitee.com/liqianglog/django-vue-admin)
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/huge-dream/django-vue3-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-3.2-blue)](https://docs.djangoproject.com/zh-hans/3.2/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/huge-dream/django-vue3-admin/badge/star.svg?theme=dark)](https://gitee.com/huge-dream/django-vue3-admin)
[预 览](https://demo.dvadmin.com) | [官网文档](https://www.django-vue-admin.com) | [群聊](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [社区](https://bbs.django-vue-admin.com) | [插件市场](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
[preview](https://demo.dvadmin.com) | [Official website document](https://www.django-vue-admin.com) | [qq group](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [community](https://bbs.django-vue-admin.com) | [plugins market](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
💡 **「About」**
It is a completely open-source rapid development platform, provided free for personal use and authorized for group use.
Django-Vue3-Admin is a comprehensive basic development platform based on the RBAC (Role-Based Access Control) model for permission control, with column-level granularity. It follows a frontend-backend separation architecture, with Django and Django Rest Framework used for the backend, and Vue3, Composition API, TypeScript, Vite, and Element Plus used for the frontend.
## framework introduction
💡 **「关于」**
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) Is a set of all open source rapid development platform, no reservation for individuals and enterprises free use.
我们是一群热爱代码的青年在这个炙热的时代下我们希望静下心来通过Code带来一点我们的色彩和颜色
* 🧑🤝🧑Front-end adoption Vue3+TS+pinia+fastcrud
* 👭The backend uses the Python language Django framework as well as the powerful[Django REST Framework](https://pypi.org/project/djangorestframework)。
* 👫Permission authentication use[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt)Supports the multi-terminal authentication system.
* 👬Support loading dynamic permission menu, multi - way easy permission control.
* 👬Enhanced Column Permission Control, with granularity down to each column.
* 💏Special thanks: [vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/).
* 💡Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
因为热爱,所以拥抱未来
## Online experience
## 平台简介
👩‍👧‍👦👩‍👧‍👦 demo address:[https://demo.dvadmin.com](https://demo.dvadmin.com)
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
* demo accountsuperadmin
* demo passwordadmin123456
👩👦👦docs:[https://django-vue-admin.com](https://django-vue-admin.com)
* 🧑‍🤝‍🧑前端采用 Vue3+TS+pinia+fastcrud(感谢[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/))
* 👭后端采用 Python 语言 Django 框架以及强大的 [Django REST Framework](https://pypi.org/project/djangorestframework)。
* 👫权限认证使用[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),支持多终端认证系统。
* 👬支持加载动态权限菜单,多方式轻松权限控制。
* 💏特别鸣谢:[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)。
* 💡 特别感谢[jetbrains](https://www.jetbrains.com/) 为本开源项目提供免费的 IntelliJ IDEA 授权。
## communication
* Communication community:[click here](https://bbs.django-vue-admin.com)👩‍👦‍👦
* plugins market:[click here](https://bbs.django-vue-admin.com/plugMarket.html)👩‍👦‍👦
## 在线体验
## source code url:
👩‍👧‍👦演示地址[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com)
gitee(Main push)[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩‍👦‍👦
- 账号superadmin
github[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩‍👦‍👦
- 密码admin123456
## core function
👩‍👦‍👦文档地址:[https://django-vue-admin.com](https://django-vue-admin.com)
1. 👨Menu Management: Configure system menus, operation permissions, button permission flags, backend interface permissions, etc.
2. 🧑Department Management: Configure system organizational structure (company, department, role).
3. 👩Role Management: Role menu permission assignment, data permission assignment, set role-based data scope permissions by department.
4. 🧑🎓Button Permission Control: Authorize role-specific button permissions and interface permissions, enabling authorization of data scope for each interface.
5. 🧑🎓Field Column Permission Control: Authorize page field display permissions, specifically for the display permissions of a certain column.
6. 👨🎓User Management: Users are system operators, and this function is mainly used for system user configuration.
7. 👬API Whitelist: Configure interfaces that do not require permission verification.
8. 🧑🔧Dictionary Management: Maintain frequently used and relatively fixed data in the system.
9. 🧑🔧Region Management: Manage provinces, cities, counties, and districts.
10. 📁File Management: Unified management of all files, images, etc., on the platform.
11. 🗓Operation Logs: Record and query logs for normal system operations and exceptional system information.
12. 🔌[Plugin Market](https://bbs.django-vue-admin.com/plugMarket.html): Applications and plugins developed based on the Django-Vue-Admin framework.
## plugins market 🔌
Updating...
## 交流
## Repository Branch Explanation 💈
Main Branch: master (stable version)
Development Branch: develop
- 交流社区:[戳我](https://bbs.django-vue-admin.com)👩‍👦‍👦
## before start project you need:
- 插件市场:[戳我](https://bbs.django-vue-admin.com/plugMarket.html)👩‍👦‍👦
- django-vue-admin交流01群(已满)812482043 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=aJVwjDvH-Es4MPJQuoO32N0SucK22TE5&jump_from=webapi)
- django-vue-admin交流02群687252418 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=4jJN4IjWGfxJ8YJXbb_gTsuWjR34WLdc&jump_from=webapi)
- 二维码
<img src='https://images.gitee.com/uploads/images/2022/0530/233203_5fb11883_5074988.jpeg' width='200'>
## 源码地址
gitee地址(主推)[https://gitee.com/liqianglog/django-vue-admin](https://gitee.com/liqianglog/django-vue-admin)👩‍👦‍👦
github地址[https://github.com/liqianglog/django-vue-admin](https://github.com/liqianglog/django-vue-admin)👩‍👦‍👦
## 内置功能
1. 👨‍⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
2. 🧑‍⚕️部门管理:配置系统组织机构(公司、部门、角色)。
3. 👩‍⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
4. 🧑‍🎓权限权限:授权角色的权限范围。
5. 👨‍🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
6. 👬接口白名单:配置不需要进行权限校验的接口。
7. 🧑‍🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
8. 🧑‍🔧地区管理:对省市县区域进行管理。
9. 📁附件管理:对平台上所有文件、图片等进行统一管理。
10. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
11. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html)基于Django-Vue-Admin框架开发的应用和插件。
## 插件市场 🔌
- Celery异步任务[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
- 升级中心后端:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
- 升级中心前端:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
## 准备工作
~~~
Python >= 3.8.0 (推荐3.8+版本)
nodejs >= 14.0 (推荐最新)
Mysql >= 5.7.0 (可选默认数据库sqlite3推荐8.0版本)
Redis(可选,最新版)
Python >= 3.11.0 (Minimum version 3.9+)
Node.js >= 16.0
Mysql >= 8.0 (Optional, default database: SQLite3, supports 5.7+, recommended version: 8.0)
Redis (Optional, latest version)
~~~
## 前端
## frontend
```bash
# 克隆项目
git clone https://gitee.com/liqianglog/django-vue-admin.git
# clone code
git clone https://gitee.com/huge-dream/django-vue3-admin.git
# 进入项目目录
# enter code dir
cd web
# 安装依赖
npm install --registry=https://registry.npm.taobao.org
# install dependence
npm install yarn
yarn install --registry=https://registry.npm.taobao.org
# 启动服务
npm run dev
# 浏览器访问 http://localhost:8080
# .env.development 文件中可配置启动端口等参数
# 构建生产环境
# npm run build
# Start service
yarn run dev
# Visit http://localhost:8080 in your browser
# Parameters such as boot port can be configured in the #.env.development file
# Build the production environment
# yarn run build
```
## 后端💈
## backend💈
~~~bash
1. 进入项目目录 cd backend
2. 在项目根目录中,复制 ./conf/env.example.py 文件为一份新的到 ./conf 文件夹下,并重命名为 env.py
3. env.py 中配置数据库信息
mysql数据库版本建议:8.0
mysql数据库字符集:utf8mb4
4. 安装依赖环境
1. enter code dir cd backend
2. copy ./conf/env.example.py to ./conf dirrename as env.py
3. in env.py configure database information
mysql database recommended version: 8.0
mysql database character set: utf8mb4
4. install pip dependence
pip3 install -r requirements.txt
5. 执行迁移命令:
5. Execute the migration command:
python3 manage.py makemigrations
python3 manage.py migrate
6. 初始化数据
6. Initialization data
python3 manage.py init
7. 初始化省市县数据:
7. Initialize provincial, municipal and county data:
python3 manage.py init_area
8. 启动项目
8. start backend
python3 manage.py runserver 0.0.0.0:8000
或使用 daphne :
daphne -b 0.0.0.0 -p 8000 application.asgi:application
or uvicorn :
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8
~~~
### 访问项目
### visit backend swagger
- 访问地址[http://localhost:8080](http://localhost:8080) (默认为此地址,如有修改请按照配置文件)
- 账号`superadmin` 密码`admin123456`
* visit url[http://localhost:8080](http://localhost:8080) (The default address is this one. If you want to change it, follow the configuration file)
* account`superadmin` password`admin123456`
### docker-compose 运行
### docker-compose
~~~shell
# 先安装docker-compose (自行百度安装),执行此命令等待安装如有使用celery插件请打开docker-compose.yml中celery 部分注释
docker-compose up -d
# 初始化后端数据(第一次执行即可)
docker exec -ti dvadmin-django bash
# Initialize backend data (first execution only)
docker exec -ti dvadmin3-django bash
python manage.py makemigrations
python manage.py migrate
python manage.py init_area
python manage.py init
exit
前端地址http://127.0.0.1:8080
后端地址http://127.0.0.1:8080/api
# 在服务器上请把127.0.0.1 换成自己公网ip
账号superadmin 密码:admin123456
frontend urlhttp://127.0.0.1:8080
backend urlhttp://127.0.0.1:8080/api
# Change 127.0.0.1 to your own public ip address on the server
account`superadmin` password`admin123456`
# docker-compose 停止
# docker-compose stop
docker-compose down
# docker-compose 重启
# docker-compose restart
docker-compose restart
# docker-compose 启动时重新进行 build
# docker-compose on start build
docker-compose up -d --build
~~~
## 演示图✅
![image-01](https://images.gitee.com/uploads/images/2022/0530/234137_b58c8f98_5074988.png)
![image-02](https://images.gitee.com/uploads/images/2022/0530/234240_39834603_5074988.png)
![image-03](https://images.gitee.com/uploads/images/2022/0530/234339_35e728a0_5074988.png)
![image-04](https://images.gitee.com/uploads/images/2022/0530/234426_957036b0_5074988.png)
![image-05](https://images.gitee.com/uploads/images/2022/0530/234458_898be492_5074988.png)
![image-06](https://images.gitee.com/uploads/images/2022/0530/234521_35b40076_5074988.png)
![image-07](https://images.gitee.com/uploads/images/2022/0530/234615_c2325639_5074988.png)
![image-08](https://images.gitee.com/uploads/images/2022/0530/234639_1ed6cc93_5074988.png)
![image-09](https://images.gitee.com/uploads/images/2022/0530/234815_cea2c53f_5074988.png)
![image-10](https://images.gitee.com/uploads/images/2022/0530/234840_5f3e5f53_5074988.png)
## Demo screenshot✅
![image-01](https://foruda.gitee.com/images/1701348994587355489/1bc749e7_5074988.png)
![image-02](https://foruda.gitee.com/images/1701349037811908960/80d361db_5074988.png)
![image-03](https://foruda.gitee.com/images/1701349224478845203/954f0a7b_5074988.png)
![image-04](https://foruda.gitee.com/images/1701349248928658877/64926724_5074988.png)
![image-05](https://foruda.gitee.com/images/1701349259068943299/1306ba40_5074988.png)
![image-06](https://foruda.gitee.com/images/1701349294894429495/e3b3a8cf_5074988.png)
![image-07](https://foruda.gitee.com/images/1701350432536247561/3b26685e_5074988.png)
![image-08](https://foruda.gitee.com/images/1701350455264771992/b364c57f_5074988.png)
![image-09](https://foruda.gitee.com/images/1701350479266000753/e4e4f7c5_5074988.png)
![image-10](https://foruda.gitee.com/images/1701350501421625746/f8dd215e_5074988.png)

214
README.zh.md Normal file
View File

@@ -0,0 +1,214 @@
# Django-Vue3-Admin
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-3.2-blue)](https://docs.djangoproject.com/zh-hans/3.2/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/liqianglog/django-vue-admin/badge/star.svg?theme=dark)](https://gitee.com/liqianglog/django-vue-admin)
[预 览](https://demo.dvadmin.com) | [官网文档](https://www.django-vue-admin.com) | [群聊](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [社区](https://bbs.django-vue-admin.com) | [插件市场](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
💡 **「关于」**
我们是一群热爱代码的青年在这个炙热的时代下我们希望静下心来通过Code带来一点我们的色彩和颜色。
因为热爱,所以拥抱未来!
## 平台简介
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) 是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。
django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台权限粒度达到列级别前后端分离后端采用django + django-rest-framework前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus
* 🧑‍🤝‍🧑前端采用 Vue3+TS+pinia+fastcrud(感谢[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/))
* 👭后端采用 Python 语言 Django 框架以及强大的 [Django REST Framework](https://pypi.org/project/djangorestframework)。
* 👫权限认证使用[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),支持多终端认证系统。
* 👬支持加载动态权限菜单,多方式轻松权限控制。
* 👬全新的列权限管控,粒度细化到每一列。
* 💏特别鸣谢:[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)。
* 💡特别感谢[jetbrains](https://www.jetbrains.com/) 为本开源项目提供免费的 IntelliJ IDEA 授权。
#### 🏭 环境支持
| Edge | Firefox | Chrome | Safari |
| --------- | ------------ | ----------- | ----------- |
| Edge ≥ 79 | Firefox ≥ 78 | Chrome ≥ 64 | Safari ≥ 12 |
> 由于 Vue3 不再支持 IE11故而 ElementPlus 也不支持 IE11 及之前版本。
## 在线体验
👩‍👧‍👦演示地址:[https://demo.dvadmin.com](https://demo.dvadmin.com)
- 账号superadmin
- 密码admin123456
👩‍👦‍👦文档地址:[coding](https://dvadmin-private.coding.net/share/km/cec69f3d-30fe-47d5-bd97-e9e851f0b776/K-2)
## 交流
- 交流社区:[戳我](https://bbs.django-vue-admin.com)👩‍👦‍👦
- 插件市场:[戳我](https://bbs.django-vue-admin.com/plugMarket.html)👩‍👦‍👦
- django-vue-admin交流01群(已满)812482043 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=aJVwjDvH-Es4MPJQuoO32N0SucK22TE5&jump_from=webapi)
- django-vue-admin交流02群(已满)687252418 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=4jJN4IjWGfxJ8YJXbb_gTsuWjR34WLdc&jump_from=webapi)
- django-vue-admin交流03群442108213 [点击链接加入群聊](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=wsm5oSz3K8dElBYUDtLTcQSEPhINFkl8&authKey=M6sbER0z59ZakgBr5erFeZyFZU15CI52bErNZa%2FxSvvGIuVAbY0N5866v89hm%2FK4&noverify=0&group_code=442108213)
- 二维码
<img src='https://images.gitee.com/uploads/images/2022/0530/233203_5fb11883_5074988.jpeg' width='200'>
## 源码地址
gitee地址(主推)[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩‍👦‍👦
github地址[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩‍👦‍👦
## 内置功能
1. 👨‍⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
2. 🧑‍⚕️部门管理:配置系统组织机构(公司、部门、角色)。
3. 👩‍⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
4. 🧑‍🎓按钮权限控制:授权角色的按钮权限和接口权限,可做到每一个接口都能授权数据范围。
5. 🧑‍🎓字段列权限控制:授权页面的字段显示权限,具体到某一列的显示权限。
7. 👨‍🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
8. 👬接口白名单:配置不需要进行权限校验的接口。
9. 🧑‍🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
10. 🧑‍🔧地区管理:对省市县区域进行管理。
11. 📁附件管理:对平台上所有文件、图片等进行统一管理。
12. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
13. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html)基于Django-Vue-Admin框架开发的应用和插件。
## 插件市场 🔌
更新中...
## 仓库分支说明 💈
主分支master稳定版本
开发分支develop
## 准备工作
~~~
Python >= 3.11.0 (最低3.9+版本)
nodejs >= 16.0
Mysql >= 8.0 (可选默认数据库sqlite3支持5.7+推荐8.0版本)
Redis (可选,最新版)
~~~
## 前端♝
```bash
# 克隆项目
git clone https://gitee.com/huge-dream/django-vue3-admin.git
# 进入项目目录
cd web
# 安装依赖
npm install yarn
yarn install --registry=https://registry.npm.taobao.org
# 启动服务
yarn build
# 浏览器访问 http://localhost:8080
# .env.development 文件中可配置启动端口等参数
# 构建生产环境
# yarn run build
```
## 后端💈
~~~bash
1. 进入项目目录 cd backend
2. 在项目根目录中,复制 ./conf/env.example.py 文件为一份新的到 ./conf 文件夹下,并重命名为 env.py
3. 在 env.py 中配置数据库信息
mysql数据库版本建议8.0
mysql数据库字符集utf8mb4
4. 安装依赖环境
pip3 install -r requirements.txt
5. 执行迁移命令:
python3 manage.py makemigrations
python3 manage.py migrate
6. 初始化数据
python3 manage.py init
7. 初始化省市县数据:
python3 manage.py init_area
8. 启动项目
python3 manage.py runserver 0.0.0.0:8000
或使用 uvicorn :
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8
~~~
## 开发建议
前后端backend与web各自单独一个窗口打开进行开发
### 访问项目
- 访问地址:[http://localhost:8080](http://localhost:8080) (默认为此地址,如有修改请按照配置文件)
- 账号:`superadmin` 密码:`admin123456`
### docker-compose 运行
~~~shell
# 先安装docker-compose (自行百度安装),执行此命令等待安装如有使用celery插件请打开docker-compose.yml中celery 部分注释
docker-compose up -d
# 初始化后端数据(第一次执行即可)
docker exec -ti dvadmin3-django bash
python manage.py makemigrations
python manage.py migrate
python manage.py init_area
python manage.py init
exit
前端地址http://127.0.0.1:8080
后端地址http://127.0.0.1:8080/api
# 在服务器上请把127.0.0.1 换成自己公网ip
账号superadmin 密码admin123456
# docker-compose 停止
docker-compose down
# docker-compose 重启
docker-compose restart
# docker-compose 启动时重新进行 build
docker-compose up -d --build
~~~
## 演示图✅
![image-01](https://foruda.gitee.com/images/1701348994587355489/1bc749e7_5074988.png)
![image-02](https://foruda.gitee.com/images/1701349037811908960/80d361db_5074988.png)
![image-03](https://foruda.gitee.com/images/1701349224478845203/954f0a7b_5074988.png)
![image-04](https://foruda.gitee.com/images/1701349248928658877/64926724_5074988.png)
![image-05](https://foruda.gitee.com/images/1701349259068943299/1306ba40_5074988.png)
![image-06](https://foruda.gitee.com/images/1701349294894429495/e3b3a8cf_5074988.png)
![image-07](https://foruda.gitee.com/images/1701350432536247561/3b26685e_5074988.png)
![image-08](https://foruda.gitee.com/images/1701350455264771992/b364c57f_5074988.png)
![image-09](https://foruda.gitee.com/images/1701350479266000753/e4e4f7c5_5074988.png)
![image-10](https://foruda.gitee.com/images/1701350501421625746/f8dd215e_5074988.png)

3
backend/.gitignore vendored
View File

@@ -91,10 +91,11 @@ ENV/
**/migrations/*.py
!**/migrations/__init__.py
*.pyc
conf/
conf/*
!conf/env.example.py
db.sqlite3
media/
__pypackages__/
package-lock.json
gunicorn.pid
!plugins/__init__.py

View File

@@ -1,9 +1,11 @@
import functools
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
from django.conf import settings
from celery import platforms
# 租户模式
if "django_tenants" in settings.INSTALLED_APPS:
from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
@@ -16,3 +18,23 @@ else:
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
platforms.C_FORCE_ROOT = True
def retry_base_task_error():
"""
celery 失败重试装饰器
:return:
"""
def wraps(func):
@app.task(bind=True, retry_delay=180, max_retries=3)
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as exc:
raise self.retry(exc=exc)
return wrapper
return wraps

View File

@@ -3,6 +3,5 @@ from django.urls import path
from application.websocketConfig import MegCenter
websocket_urlpatterns = [
path('ws/<str:service_uid>/', MegCenter.as_asgi()), #consumers.DvadminWebSocket 是该路由的消费者
path('ws/<str:service_uid>/', MegCenter.as_asgi()), # consumers.DvadminWebSocket 是该路由的消费者
]

View File

@@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/4.1/ref/settings/
import os
import sys
from pathlib import Path
from datetime import timedelta
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -42,7 +43,8 @@ sys.path.insert(0, os.path.join(PLUGINS_PATH))
DEBUG = locals().get("DEBUG", True)
ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
# Application definition
# 列权限需要排除的App应用
COLUMN_EXCLUDE_APPS = ['channels', 'captcha'] + locals().get("COLUMN_EXCLUDE_APPS", [])
INSTALLED_APPS = [
"django.contrib.auth",
@@ -54,13 +56,14 @@ INSTALLED_APPS = [
"rest_framework",
"django_filters",
"corsheaders", # 注册跨域app
"dvadmin.system",
"drf_yasg",
"captcha",
'channels',
"channels",
"dvadmin.system",
]
MIDDLEWARE = [
"dvadmin.utils.middleware.HealthCheckMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
@@ -152,6 +155,11 @@ STATICFILES_DIRS = [
MEDIA_ROOT = "media" # 项目下的目录
MEDIA_URL = "/media/" # 跟STATIC_URL类似指定用户可以通过这个url找到文件
#添加以下代码以后就不用写{% load staticfiles %},可以直接引用
STATICFILES_FINDERS = (
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder"
)
# 收集静态文件,必须将 MEDIA_ROOT,STATICFILES_DIRS先注释
# python manage.py collectstatic
# STATIC_ROOT=os.path.join(BASE_DIR,'static')
@@ -165,9 +173,9 @@ CORS_ORIGIN_ALLOW_ALL = True
# 允许cookie
CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中后端是否支持对cookie的操作
# ================================================= #
# ===================================================== #
# ********************* channels配置 ******************* #
# ================================================= #
# ===================================================== #
ASGI_APPLICATION = 'application.asgi.application'
CHANNEL_LAYERS = {
"default": {
@@ -187,69 +195,83 @@ CHANNEL_LAYERS = {
# ================================================= #
# ********************* 日志配置 ******************* #
# ================================================= #
# log 配置部分BEGIN #
# # log 配置部分BEGIN #
SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
LOGS_FILE = os.path.join(BASE_DIR, "logs")
if not os.path.exists(os.path.join(BASE_DIR, "logs")):
os.makedirs(os.path.join(BASE_DIR, "logs"))
# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
# 格式:[日期][模块.函数名称():行号] [级别] 信息
STANDARD_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
CONSOLE_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"servers": {
"format": "%(message)s",
"standard": {"format": STANDARD_LOG_FORMAT},
"console": {
"format": CONSOLE_LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
}
},
"file": {
"format": CONSOLE_LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": {
"servers": {
"logging_levels": ["info", "error", "warning"],
"class": "dvadmin.utils.log.InterceptTimedRotatingFileHandler", # 这个路径看你本地放在哪里(下面的log文件)
"filename": os.path.join(LOGS_FILE, "server.log"),
"when": "D",
"interval": 1,
"file": {
"level": "INFO",
"class": "logging.handlers.RotatingFileHandler",
"filename": SERVER_LOGS_FILE,
"maxBytes": 1024 * 1024 * 100, # 100 MB
"backupCount": 1,
"formatter": "servers",
"backupCount": 5, # 最多备份5个
"formatter": "standard",
"encoding": "utf-8",
}
},
"error": {
"level": "ERROR",
"class": "logging.handlers.RotatingFileHandler",
"filename": ERROR_LOGS_FILE,
"maxBytes": 1024 * 1024 * 100, # 100 MB
"backupCount": 3, # 最多备份3个
"formatter": "standard",
"encoding": "utf-8",
},
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "console",
},
},
"loggers": {
# default日志
'django': {
'handlers': ['servers'],
'propagate': False,
'level': "INFO"
"": {
"handlers": ["console", "error", "file"],
"level": "INFO",
},
'': {
'handlers': ['servers'],
'propagate': False,
'level': "ERROR"
},
'celery': {
'handlers': ['servers'],
'propagate': False,
'level': "INFO"
"django": {
"handlers": ["console", "error", "file"],
"level": "INFO",
"propagate": False,
},
'django.db.backends': {
'handlers': ['servers'],
'handlers': ["console", "error", "file"],
'propagate': False,
'level': "INFO"
},
'django.request': {
'handlers': ['servers'],
'propagate': False,
'level': "DEBUG"
},
"uvicorn.error": {
"level": "INFO",
"handlers": ["servers"],
"handlers": ["console", "error", "file"],
},
"uvicorn.access": {
"handlers": ["servers"],
"level": "INFO",
"propagate": False
"handlers": ["console", "error", "file"],
"level": "INFO"
},
},
}
@@ -289,11 +311,9 @@ AUTHENTICATION_BACKENDS = ["dvadmin.utils.backends.CustomBackend"]
# ================================================= #
# ****************** simplejwt配置 ***************** #
# ================================================= #
from datetime import timedelta
SIMPLE_JWT = {
# token有效时长
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=120),
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=1440),
# token刷新后的有效时间
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
# 设置前缀
@@ -329,11 +349,11 @@ SWAGGER_SETTINGS = {
# ================================================= #
# **************** 验证码配置 ******************* #
# ================================================= #
CAPTCHA_IMAGE_SIZE = (160, 60) # 设置 captcha 图片大小
CAPTCHA_IMAGE_SIZE = (160, 46) # 设置 captcha 图片大小
CAPTCHA_LENGTH = 4 # 字符个数
CAPTCHA_TIMEOUT = 1 # 超时(minutes)
CAPTCHA_OUTPUT_FORMAT = "%(image)s %(text_field)s %(hidden_field)s "
CAPTCHA_FONT_SIZE = 40 # 字体大小
CAPTCHA_FONT_SIZE = 36 # 字体大小
CAPTCHA_FOREGROUND_COLOR = "#64DAAA" # 前景色
CAPTCHA_BACKGROUND_COLOR = "#F5F7F4" # 背景色
CAPTCHA_NOISE_FUNCTIONS = (
@@ -388,5 +408,7 @@ PLUGINS_URL_PATTERNS = []
# from dvadmin_third.settings import * # 第三方用户管理
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
# from dvadmin_tenants.settings import * # 租户管理
#from dvadmin_social_auth.settings import *
#from dvadmin_uniapp.settings import *
# ...
# ********** 一键导入插件配置结束 **********

View File

@@ -30,6 +30,7 @@ from dvadmin.system.views.login import (
CaptchaView,
ApiLogin,
LogoutView,
LoginTokenView
)
from dvadmin.system.views.system_config import InitSettingsViewSet
from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator
@@ -81,6 +82,9 @@ urlpatterns = (
path("api/init/dictionary/", InitDictionaryViewSet.as_view()),
path("api/init/settings/", InitSettingsViewSet.as_view()),
path("apiLogin/", ApiLogin.as_view()),
# 仅用于开发,上线需关闭
path("api/token/", LoginTokenView.as_view()),
]
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL)

View File

@@ -73,7 +73,7 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer):
unread_count = await _get_message_unread(self.user_id)
if unread_count == 0:
# 发送连接成功
await self.send_json(set_message('system', 'SYSTEM', '连接成功'))
await self.send_json(set_message('system', 'SYSTEM', '您已上线'))
else:
await self.send_json(
set_message('system', 'SYSTEM', "请查看您的未读消息~",
@@ -122,7 +122,8 @@ class MessageCreateSerializer(CustomModelSerializer):
fields = "__all__"
read_only_fields = ["id"]
def websocket_push(user_id,message):
def websocket_push(user_id, message):
username = "user_" + str(user_id)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
@@ -133,8 +134,9 @@ def websocket_push(user_id,message):
}
)
def create_message_push(title: str, content: str, target_type: int=0, target_user: list=[], target_dept=None, target_role=None,
message: dict = {'contentType': 'INFO', 'content': '测试~'}, request= Request):
def create_message_push(title: str, content: str, target_type: int = 0, target_user: list = None, target_dept=None,
target_role=None, message: dict = None, request=Request):
if message is None:
message = {"contentType": "INFO", "content": None}
if target_role is None:
@@ -145,11 +147,11 @@ def create_message_push(title: str, content: str, target_type: int=0, target_use
"title": title,
"content": content,
"target_type": target_type,
"target_user":target_user,
"target_dept":target_dept,
"target_role":target_role
"target_user": target_user,
"target_dept": target_dept,
"target_role": target_role
}
message_center_instance = MessageCreateSerializer(data=data,request=request)
message_center_instance = MessageCreateSerializer(data=data, request=request)
message_center_instance.is_valid(raise_exception=True)
message_center_instance.save()
users = target_user or []
@@ -176,6 +178,6 @@ def create_message_push(title: str, content: str, target_type: int=0, target_use
username,
{
"type": "push.message",
"json": {**message,'unread':unread_count}
"json": {**message, 'unread': unread_count}
}
)

View File

@@ -7,30 +7,30 @@ from application.settings import BASE_DIR
# ================================================= #
# 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库
# sqlite3 设置
DATABASE_ENGINE = "django.db.backends.sqlite3"
DATABASE_NAME = os.path.join(BASE_DIR, "db.sqlite3")
# DATABASE_ENGINE = "django.db.backends.sqlite3"
# DATABASE_NAME = os.path.join(BASE_DIR, "db.sqlite3")
# 使用mysql时改为此配置
# DATABASE_ENGINE = "django.db.backends.mysql"
# DATABASE_NAME = 'django-vue-admin' # mysql 时使用
DATABASE_ENGINE = "django.db.backends.mysql"
DATABASE_NAME = 'django-vue3-admin' # mysql 时使用
# 数据库地址 改为自己数据库地址
DATABASE_HOST = "127.0.0.1"
DATABASE_HOST = '127.0.0.1'
# # 数据库端口
DATABASE_PORT = 3306
# # 数据库用户名
DATABASE_USER = "root"
# # 数据库密码
DATABASE_PASSWORD = "123456"
DATABASE_PASSWORD = "DVADMIN3"
# 表前缀
TABLE_PREFIX = "dvadmin_"
# ================================================= #
# ******** redis配置无redis 可不进行配置 ******** #
# ================================================= #
# REDIS_PASSWORD = ''
# REDIS_HOST = '127.0.0.1'
# REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6380'
REDIS_PASSWORD = 'DVADMIN3'
REDIS_HOST = '127.0.0.1'
REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379'
# ================================================= #
# ****************** 功能 启停 ******************* #
# ================================================= #
@@ -44,6 +44,5 @@ LOGIN_NO_CAPTCHA_AUTH = True
# ================================================= #
ALLOWED_HOSTS = ["*"]
# daphne启动命令
#daphne application.asgi:application -b 0.0.0.0 -p 8000
# 列权限中排除App应用
COLUMN_EXCLUDE_APPS = []

View File

@@ -4,7 +4,7 @@ import os
exclude = ["venv"] # 需要排除的文件目录
for root, dirs, files in os.walk('.'):
dirs[:] = [d for d in set(dirs) - set(exclude)]
dirs[:] = list(set(dirs) - set(exclude))
if 'migrations' in dirs:
dir = dirs[dirs.index('migrations')]
for root_j, dirs_j, files_j in os.walk(os.path.join(root, dir)):

View File

@@ -2,4 +2,4 @@
# python manage.py makemigrations
# python manage.py migrate
# python manage.py init -y
gunicorn -c gunicorn_conf.py application.asgi:application
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 4

View File

@@ -5,9 +5,13 @@ from rest_framework import serializers
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
import django
django.setup()
from dvadmin.system.models import Role, Dept, Users, Menu, MenuButton, ApiWhiteList, Dictionary, SystemConfig, \
RoleMenuPermission, RoleMenuButtonPermission
from dvadmin.system.models import (
Role, Dept, Users, Menu, MenuButton,
ApiWhiteList, Dictionary, SystemConfig,
RoleMenuPermission, RoleMenuButtonPermission, MenuField
)
from dvadmin.utils.serializers import CustomModelSerializer
@@ -50,6 +54,16 @@ class MenuButtonInitSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class MenuFieldInitSerializer(CustomModelSerializer):
"""
初始化列权限-序列化器
"""
class Meta:
model = MenuField
fields = ['id', 'menu', 'field_name', 'title', 'model']
read_only_fields = ["id"]
class MenuInitSerializer(CustomModelSerializer):
"""
@@ -58,6 +72,7 @@ class MenuInitSerializer(CustomModelSerializer):
name = serializers.CharField(required=False)
children = serializers.SerializerMethodField()
menu_button = serializers.SerializerMethodField()
menu_field = serializers.SerializerMethodField()
def get_children(self, obj: Menu):
data = []
@@ -74,10 +89,18 @@ class MenuInitSerializer(CustomModelSerializer):
data = list(instance.values('name', 'value', 'api', 'method'))
return data
def get_menu_field(self, obj: Menu):
data = []
instance = obj.menufield_set.order_by('field_name')
if instance:
data = list(instance.values('field_name', 'title', 'model'))
return data
def save(self, **kwargs):
instance = super().save(**kwargs)
children = self.initial_data.get('children')
menu_button = self.initial_data.get('menu_button')
menu_field = self.initial_data.get('menu_field')
# 菜单表
if children:
for menu_data in children:
@@ -106,12 +129,25 @@ class MenuInitSerializer(CustomModelSerializer):
serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
# 列权限
if menu_field:
for field_data in menu_field:
field_data['menu'] = instance.id
filter_data = {
'menu': field_data['menu'],
'field_name': field_data['field_name'],
'model': field_data['model']
}
instance_obj = MenuField.objects.filter(**filter_data).first()
serializer = MenuFieldInitSerializer(instance_obj, data=field_data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
return instance
class Meta:
model = Menu
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
'cache', 'visible', 'parent', 'children', 'menu_button', 'creator', 'dept_belong_id']
'cache', 'visible', 'parent', 'children', 'menu_button', 'menu_field', 'creator', 'dept_belong_id']
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
@@ -126,7 +162,7 @@ class RoleInitSerializer(CustomModelSerializer):
class Meta:
model = Role
fields = ['name', 'key', 'sort', 'status', 'admin',
fields = ['name', 'key', 'sort', 'status',
'creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
@@ -139,8 +175,8 @@ class RoleMenuInitSerializer(CustomModelSerializer):
"""
初始化角色菜单(用于生成初始化json文件)
"""
role_key = serializers.CharField(max_length=100,required=True)
menu_component_name = serializers.CharField(max_length=100,required=True)
role_key = serializers.CharField(max_length=100, required=True)
menu_component_name = serializers.CharField(max_length=100, required=True)
def create(self, validated_data):
init_data = self.initial_data
@@ -154,7 +190,7 @@ class RoleMenuInitSerializer(CustomModelSerializer):
class Meta:
model = RoleMenuPermission
fields = ['role_key','menu_component_name','creator', 'dept_belong_id']
fields = ['role_key', 'menu_component_name', 'creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
'role': {'required': False},
@@ -168,8 +204,8 @@ class RoleMenuButtonInitSerializer(CustomModelSerializer):
"""
初始化角色菜单按钮(用于生成初始化json文件)
"""
role_key = serializers.CharField(max_length=100,required=True)
menu_button_value = serializers.CharField(max_length=100,required=True)
role_key = serializers.CharField(max_length=100, required=True)
menu_button_value = serializers.CharField(max_length=100, required=True)
data_range = serializers.CharField(max_length=100, required=False)
def create(self, validated_data):
@@ -186,7 +222,7 @@ class RoleMenuButtonInitSerializer(CustomModelSerializer):
class Meta:
model = RoleMenuButtonPermission
fields = ['role_key','menu_button_value','data_range','dept','creator', 'dept_belong_id']
fields = ['role_key', 'menu_button_value', 'data_range', 'dept', 'creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
'role': {'required': False},
@@ -196,7 +232,6 @@ class RoleMenuButtonInitSerializer(CustomModelSerializer):
}
class ApiWhiteListInitSerializer(CustomModelSerializer):
"""
初始化获取数信息(用于生成初始化json文件)

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,6 @@
"key": "admin",
"sort": 1,
"status": true,
"admin": true,
"remark": null
},
{
@@ -12,7 +11,6 @@
"key": "public",
"sort": 2,
"status": true,
"admin": true,
"remark": null
}
]

View File

@@ -65,6 +65,20 @@
"placeholder": null,
"setting": null,
"children": [
{
"parent": 1,
"title": "网站标题",
"key": "site_title",
"value": "Dvadmin",
"sort": 1,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": [],
"placeholder": "请输入网站标题",
"setting": null,
"children": []
},
{
"parent": 1,
"title": "网站名称",
@@ -116,7 +130,7 @@
"parent": 1,
"title": "版权信息",
"key": "copyright",
"value": "2021-2022 django-vue-admin.com 版权所有",
"value": "2021-2024 django-vue-admin.com 版权所有",
"sort": 4,
"status": true,
"data_options": null,
@@ -168,7 +182,7 @@
"parent": 1,
"title": "隐私链接",
"key": "privacy_url",
"value": "#",
"value": "/api/system/clause/privacy.html",
"sort": 7,
"status": true,
"data_options": null,
@@ -182,7 +196,7 @@
"parent": 1,
"title": "条款链接",
"key": "clause_url",
"value": "#",
"value": "/api/system/clause/terms_service.html",
"sort": 8,
"status": true,
"data_options": null,

View File

@@ -29,6 +29,7 @@
"gender": 1,
"user_type": 0,
"role": [],
"dept_key": "dvadmin",
"first_name": "",
"last_name": "",
"is_staff": true,

View File

@@ -8,9 +8,11 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "application.settings")
django.setup()
from dvadmin.utils.core_initialize import CoreInitialize
from dvadmin.system.fixtures.initSerializer import UsersInitSerializer, DeptInitSerializer, RoleInitSerializer, \
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer, SystemConfigInitSerializer, \
RoleMenuInitSerializer, RoleMenuButtonInitSerializer
from dvadmin.system.fixtures.initSerializer import (
UsersInitSerializer, DeptInitSerializer, RoleInitSerializer,
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer,
SystemConfigInitSerializer, RoleMenuInitSerializer, RoleMenuButtonInitSerializer
)
class Initialize(CoreInitialize):
@@ -51,7 +53,6 @@ class Initialize(CoreInitialize):
"""
self.init_base(RoleMenuButtonInitSerializer, unique_fields=['role', 'menu_button'])
def init_api_white_list(self):
"""
初始API白名单

View File

@@ -5,12 +5,7 @@ from django.contrib.auth.models import AbstractUser, UserManager
from django.db import models
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from application import dispatch
from dvadmin.utils.models import CoreModel, table_prefix
STATUS_CHOICES = (
(0, "禁用"),
(1, "启用"),
)
from dvadmin.utils.models import CoreModel, table_prefix, get_custom_app_models
class Role(CoreModel):
@@ -18,7 +13,6 @@ class Role(CoreModel):
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
status = models.BooleanField(default=True, verbose_name="角色状态", help_text="角色状态")
admin = models.BooleanField(default=False, verbose_name="是否为admin", help_text="是否为admin")
class Meta:
db_table = table_prefix + "system_role"
@@ -107,8 +101,7 @@ class Post(CoreModel):
class Dept(CoreModel):
name = models.CharField(max_length=64, verbose_name="部门名称", help_text="部门名称")
key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符",
help_text="关联字符")
key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符", help_text="关联字符")
sort = models.IntegerField(default=1, verbose_name="显示排序", help_text="显示排序")
owner = models.CharField(max_length=32, verbose_name="负责人", null=True, blank=True, help_text="负责人")
phone = models.CharField(max_length=32, verbose_name="联系电话", null=True, blank=True, help_text="联系电话")
@@ -169,6 +162,7 @@ class Menu(CoreModel):
(1, ""),
)
is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
link_url = models.CharField(max_length=255, verbose_name="链接地址", null=True, blank=True, help_text="链接地址")
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录")
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
@@ -178,13 +172,59 @@ class Menu(CoreModel):
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示",
help_text="侧边栏中是否显示")
is_iframe = models.BooleanField(default=False, blank=True, verbose_name="框架外显示", help_text="框架外显示")
is_affix = models.BooleanField(default=False, blank=True, verbose_name="是否固定", help_text="是否固定")
@classmethod
def get_all_parent(cls, id: int, all_list=None, nodes=None):
"""
递归获取给定ID的所有层级
:param id: 参数ID
:param all_list: 所有列表
:param nodes: 递归列表
:return: nodes
"""
if not all_list:
all_list = Menu.objects.values("id", "name", "parent")
if nodes is None:
nodes = []
for ele in all_list:
if ele.get("id") == id:
parent_id = ele.get("parent")
if parent_id is not None:
cls.get_all_parent(parent_id, all_list, nodes)
nodes.append(ele)
return nodes
class Meta:
db_table = table_prefix + "system_menu"
verbose_name = "菜单表"
verbose_name_plural = verbose_name
ordering = ("sort",)
class MenuField(CoreModel):
model = models.CharField(max_length=64, verbose_name='表名')
menu = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name='菜单', db_constraint=False)
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
title = models.CharField(max_length=64, verbose_name='字段显示名')
class Meta:
db_table = table_prefix + "system_menu_field"
verbose_name = "菜单字段表"
verbose_name_plural = verbose_name
ordering = ("id",)
class FieldPermission(CoreModel):
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
field = models.ForeignKey(to='MenuField', on_delete=models.CASCADE,related_name='menu_field', verbose_name='字段', db_constraint=False)
is_query = models.BooleanField(default=1, verbose_name='是否可查询')
is_create = models.BooleanField(default=1, verbose_name='是否可创建')
is_update = models.BooleanField(default=1, verbose_name='是否可更新')
class Meta:
db_table = table_prefix + "system_field_permission"
verbose_name = "字段权限表"
verbose_name_plural = verbose_name
ordering = ("id",)
class MenuButton(CoreModel):
menu = models.ForeignKey(
@@ -236,7 +276,7 @@ class RoleMenuPermission(CoreModel):
db_table = table_prefix + "role_menu_permission"
verbose_name = "角色菜单权限表"
verbose_name_plural = verbose_name
ordering = ("-create_datetime",)
# ordering = ("-create_datetime",)
class RoleMenuButtonPermission(CoreModel):
@@ -289,8 +329,7 @@ class Dictionary(CoreModel):
(7, "images"),
)
label = models.CharField(max_length=100, blank=True, null=True, verbose_name="字典名称", help_text="字典名称")
value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号",
help_text="字典编号/实际值")
value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号", help_text="字典编号/实际值")
parent = models.ForeignKey(
to="self",
related_name="sublist",
@@ -359,7 +398,11 @@ def media_file_name(instance, filename):
class FileList(CoreModel):
name = models.CharField(max_length=200, null=True, blank=True, verbose_name="名称", help_text="名称")
url = models.FileField(upload_to=media_file_name)
url = models.FileField(upload_to=media_file_name, null=True, blank=True,)
file_url = models.CharField(max_length=255, blank=True, verbose_name="文件地址", help_text="文件地址")
engine = models.CharField(max_length=100, default='local', blank=True, verbose_name="引擎", help_text="引擎")
mime_type = models.CharField(max_length=100, blank=True, verbose_name="Mime类型", help_text="Mime类型")
size = models.CharField(max_length=36, blank=True, verbose_name="文件大小", help_text="文件大小")
md5sum = models.CharField(max_length=36, blank=True, verbose_name="文件md5", help_text="文件md5")
def save(self, *args, **kwargs):
@@ -368,6 +411,11 @@ class FileList(CoreModel):
for chunk in self.url.chunks():
md5.update(chunk)
self.md5sum = md5.hexdigest()
if not self.size:
self.size = self.url.size
if not self.file_url:
url = media_file_name(self, self.name)
self.file_url = f'media/{url}'
super(FileList, self).save(*args, **kwargs)
class Meta:

View File

@@ -1 +1,56 @@
from functools import wraps
from django.db.models import Func, F, OuterRef, Exists
from django.test import TestCase
import django
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "application.settings")
django.setup()
from dvadmin.system.models import Menu, RoleMenuPermission, RoleMenuButtonPermission, MenuButton
import time
def timing_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
run_time = end_time - start_time
print(f"{func.__name__} ran in {run_time:.6f} seconds")
return result
return wrapper
@timing_decorator
def getMenu():
data = []
queryset = Menu.objects.filter(status=1, is_catalog=False).values('name', 'id')
for item in queryset:
parent_list = Menu.get_all_parent(item['id'])
names = [d["name"] for d in parent_list]
completeName = "/".join(names)
isCheck = RoleMenuPermission.objects.filter(
menu__id=item['id'],
role__id=1,
).exists()
mbCheck = RoleMenuButtonPermission.objects.filter(
menu_button = OuterRef("pk"),
role__id=1,
)
btns = MenuButton.objects.filter(
menu__id=item['id'],
).annotate(isCheck=Exists(mbCheck)).values('id', 'name', 'value', 'isCheck',data_range=F('menu_button_permission__data_range'))
# print(b)
dicts = {
'name': completeName,
'id': item['id'],
'isCheck': isCheck,
'btns':btns
}
print(dicts)
data.append(dicts)
# print(data)
if __name__ == '__main__':
getMenu()

View File

@@ -3,6 +3,7 @@ from rest_framework import routers
from dvadmin.system.views.api_white_list import ApiWhiteListViewSet
from dvadmin.system.views.area import AreaViewSet
from dvadmin.system.views.clause import PrivacyView, TermsServiceView
from dvadmin.system.views.dept import DeptViewSet
from dvadmin.system.views.dictionary import DictionaryViewSet
from dvadmin.system.views.file_list import FileViewSet
@@ -16,6 +17,7 @@ from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
from dvadmin.system.views.system_config import SystemConfigViewSet
from dvadmin.system.views.user import UserViewSet
from dvadmin.system.views.menu_field import MenuFieldViewSet
system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet)
@@ -29,11 +31,10 @@ system_url.register(r'area', AreaViewSet)
system_url.register(r'file', FileViewSet)
system_url.register(r'api_white_list', ApiWhiteListViewSet)
system_url.register(r'system_config', SystemConfigViewSet)
system_url.register(r'message_center',MessageCenterViewSet)
system_url.register(r'message_center', MessageCenterViewSet)
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
system_url.register(r'column', MenuFieldViewSet)
urlpatterns = [
@@ -46,5 +47,7 @@ urlpatterns = [
path('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})),
path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})),
path('clause/privacy.html', PrivacyView.as_view()),
path('clause/terms_service.html', TermsServiceView.as_view()),
]
urlpatterns += system_url.urls

View File

@@ -60,12 +60,9 @@ class AreaViewSet(CustomModelViewSet):
del params['page']
if limit:
del params['limit']
if params:
if pcode:
if params and pcode:
queryset = self.queryset.filter(enable=True, pcode=pcode)
else:
queryset = self.queryset.filter(enable=True)
else:
queryset = self.queryset.filter(enable=True, pcode__isnull=True)
return queryset

View File

@@ -0,0 +1,23 @@
from rest_framework.views import APIView
from django.shortcuts import render
class PrivacyView(APIView):
"""
后台隐私政策
"""
permission_classes = []
def get(self, request, *args, **kwargs):
return render(request, 'privacy.html')
class TermsServiceView(APIView):
"""
后台服务条款
"""
permission_classes = []
def get(self, request, *args, **kwargs):
return render(request, 'terms_service.html')

View File

@@ -9,9 +9,8 @@ from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Dept
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
from dvadmin.utils.permission import AnonymousUserPermission
from dvadmin.system.models import Dept, RoleMenuButtonPermission, Users
from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -25,6 +24,11 @@ class DeptSerializer(CustomModelSerializer):
has_children = serializers.SerializerMethodField()
hasChild = serializers.SerializerMethodField()
dept_user_count = serializers.SerializerMethodField()
def get_dept_user_count(self, obj: Dept):
return Users.objects.filter(dept=obj).count()
def get_hasChild(self, instance):
hasChild = Dept.objects.filter(parent=instance.id)
if hasChild:
@@ -56,18 +60,18 @@ class DeptImportSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class DeptCreateUpdateSerializer(CustomModelSerializer):
"""
部门管理 创建/更新时的列化器
"""
def create(self, validated_data):
value = validated_data.get('parent',None)
value = validated_data.get('parent', None)
if value is None:
validated_data['parent'] = self.request.user.dept
dept_obj = Dept.objects.filter(parent=self.request.user.dept).order_by('-sort').first()
last_sort = dept_obj.sort if dept_obj else 0
validated_data['sort'] = last_sort + 1
instance = super().create(validated_data)
instance.dept_belong_id = instance.id
instance.save()
@@ -111,13 +115,10 @@ class DeptViewSet(CustomModelViewSet):
del params['page']
if limit:
del params['limit']
if params:
if parent:
if params and parent:
queryset = self.queryset.filter(status=True, parent=parent)
else:
queryset = self.queryset.filter(status=True)
else:
queryset = self.queryset.filter(status=True, parent__isnull=True)
queryset = self.filter_queryset(queryset)
serializer = DeptSerializer(queryset, many=True, request=request)
data = serializer.data
@@ -130,27 +131,107 @@ class DeptViewSet(CustomModelViewSet):
if is_superuser:
queryset = Dept.objects.values('id', 'name', 'parent')
else:
data_range = request.user.role.values_list('data_range', flat=True)
role_ids = request.user.role.values_list('id', flat=True)
data_range = RoleMenuButtonPermission.objects.filter(role__in=role_ids).values_list('data_range', flat=True)
user_dept_id = request.user.dept.id
dept_list = [user_dept_id]
data_range_list = list(set(data_range))
for item in data_range_list:
if item in [0,2]:
if item in [0, 2]:
dept_list = [user_dept_id]
elif item == 1:
dept_list = Dept.recursion_dept_info(dept_id=user_dept_id)
dept_list = Dept.recursion_all_dept(dept_id=user_dept_id)
elif item == 3:
dept_list = Dept.objects.values_list('id',flat=True)
dept_list = Dept.objects.values_list('id', flat=True)
elif item == 4:
dept_list = request.user.role.values_list('dept',flat=True)
dept_list = request.user.role.values_list('dept', flat=True)
else:
dept_list = []
queryset = Dept.objects.filter(id__in=dept_list).values('id', 'name', 'parent')
return DetailResponse(data=queryset, msg="获取成功")
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated],extra_filter_class=[])
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated], extra_filter_class=[])
def all_dept(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent')
return DetailResponse(data=data, msg="获取成功")
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
def move_up(self, request):
"""部门上移"""
dept_id = request.data.get('dept_id')
try:
dept = Dept.objects.get(id=dept_id)
except Dept.DoesNotExist:
return ErrorResponse(msg="部门不存在")
previous_menu = Dept.objects.filter(sort__lt=dept.sort, parent=dept.parent).order_by('-sort').first()
if previous_menu:
previous_menu.sort, dept.sort = dept.sort, previous_menu.sort
previous_menu.save()
dept.save()
return SuccessResponse(data=[], msg="上移成功")
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
def move_down(self, request):
"""部门下移"""
dept_id = request.data['dept_id']
try:
dept = Dept.objects.get(id=dept_id)
except Dept.DoesNotExist:
return ErrorResponse(msg="部门不存在")
next_menu = Dept.objects.filter(sort__gt=dept.sort, parent=dept.parent).order_by('sort').first()
if next_menu:
next_menu.sort, dept.sort = dept.sort, next_menu.sort
next_menu.save()
dept.save()
return SuccessResponse(data=[], msg="下移成功")
@action(methods=['GET'], detail=False, permission_classes=[])
def dept_info(self, request):
"""部门信息"""
def inner(did, li):
sub = Dept.objects.filter(parent_id=did)
if not sub.exists():
return li
for i in sub:
li.append(i.pk)
inner(i, li)
return li
dept_id = request.query_params.get('dept_id')
show_all = request.query_params.get('show_all')
if dept_id is None:
return ErrorResponse(msg="部门不存在")
if not show_all:
show_all = 0
if int(show_all): # 递归当前部门下的所有部门,查询用户
all_did = [dept_id]
inner(dept_id, all_did)
users = Users.objects.filter(dept_id__in=all_did)
else:
if dept_id != '':
users = Users.objects.filter(dept_id=dept_id)
else:
users = Users.objects.none()
dept_obj = Dept.objects.get(id=dept_id) if dept_id != '' else None
sub_dept = Dept.objects.filter(parent_id=dept_obj.pk) if dept_id != '' else []
data = {
'dept_name': dept_obj and dept_obj.name,
'dept_user': users.count(),
'owner': dept_obj and dept_obj.owner,
'description': dept_obj and dept_obj.description,
'gender': {
'male': users.filter(gender=1).count(),
'female': users.filter(gender=2).count(),
'unknown': users.filter(gender=0).count(),
},
'sub_dept_map': []
}
for dept in sub_dept:
all_did = [dept.pk]
inner(dept.pk, all_did)
sub_data = {
'name': dept.name,
'count': Users.objects.filter(dept_id__in=all_did).count()
}
data['sub_dept_map'].append(sub_data)
return SuccessResponse(data)

View File

@@ -34,6 +34,19 @@ class DictionaryCreateUpdateSerializer(CustomModelSerializer):
"""
字典管理 创建/更新时的列化器
"""
value = serializers.CharField(max_length=100)
def validate_value(self, value):
"""
在父级的字典编号验证重复性
"""
initial_data = self.initial_data
parent = initial_data.get('parent',None)
if parent is None:
unique = Dictionary.objects.filter(value=value).exists()
if unique:
raise serializers.ValidationError("字典编号不能重复")
return value
class Meta:
model = Dictionary
@@ -51,20 +64,24 @@ class DictionaryViewSet(CustomModelViewSet):
"""
queryset = Dictionary.objects.all()
serializer_class = DictionarySerializer
create_serializer_class = DictionaryCreateUpdateSerializer
extra_filter_class = []
search_fields = ['label']
def get_queryset(self):
if self.action =='list':
params = self.request.query_params
parent = params.get('parent', None)
if params:
if parent:
queryset = self.queryset.filter(status=1, parent=parent)
queryset = self.queryset.filter(parent=parent)
else:
queryset = self.queryset.filter(status=1, parent__isnull=True)
queryset = self.queryset.filter(parent__isnull=True)
else:
queryset = self.queryset.filter(status=1, parent__isnull=True)
queryset = self.queryset.filter(parent__isnull=True)
return queryset
else:
return self.queryset
class InitDictionaryViewSet(APIView):

View File

@@ -1,5 +1,12 @@
import hashlib
import mimetypes
from rest_framework import serializers
from rest_framework.decorators import action
from application import dispatch
from dvadmin.system.models import FileList
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -8,15 +15,52 @@ class FileSerializer(CustomModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
def get_url(self, instance):
return f'media/{str(instance.url)}'
# return 'media/' + str(instance.url)
return instance.file_url or (f'media/{str(instance.url)}')
class Meta:
model = FileList
fields = "__all__"
def create(self, validated_data):
validated_data['name'] = str(self.initial_data.get('file'))
validated_data['url'] = self.initial_data.get('file')
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local'
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup")
file = self.initial_data.get('file')
file_size = file.size
validated_data['name'] = str(file)
validated_data['size'] = file_size
md5 = hashlib.md5()
for chunk in file.chunks():
md5.update(chunk)
validated_data['md5sum'] = md5.hexdigest()
validated_data['engine'] = file_engine
validated_data['mime_type'] = file.content_type
if file_backup:
validated_data['url'] = file
if file_engine == 'oss':
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
file_path = ali_oss_upload(file)
if file_path:
validated_data['file_url'] = file_path
else:
raise ValueError("上传失败")
elif file_engine == 'cos':
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
file_path = tencent_cos_upload(file)
if file_path:
validated_data['file_url'] = file_path
else:
raise ValueError("上传失败")
else:
validated_data['url'] = file
# 审计字段
try:
request_user = self.request.user
validated_data['dept_belong_id'] = request_user.dept.id
validated_data['creator'] = request_user.id
validated_data['modifier'] = request_user.id
except:
pass
return super().create(validated_data)

View File

@@ -9,8 +9,9 @@
from rest_framework import serializers
from rest_framework.decorators import action
from dvadmin.system.models import Menu, MenuButton, RoleMenuPermission
from dvadmin.utils.json_response import SuccessResponse
from dvadmin.system.models import Menu, RoleMenuPermission
from dvadmin.system.views.menu_button import MenuButtonSerializer
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -23,7 +24,8 @@ class MenuSerializer(CustomModelSerializer):
hasChild = serializers.SerializerMethodField()
def get_menuPermission(self, instance):
queryset = instance.menuPermission.order_by('-name').values_list('name', flat=True)
queryset = instance.menuPermission.order_by('-name').values('id', 'name', 'value')
# MenuButtonSerializer(instance.menuPermission.all(), many=True)
if queryset:
return queryset
else:
@@ -47,6 +49,12 @@ class MenuCreateSerializer(CustomModelSerializer):
"""
name = serializers.CharField(required=False)
def create(self, validated_data):
menu_obj = Menu.objects.filter(parent_id=validated_data.get('parent', None)).order_by('-sort').first()
last_sort = menu_obj.sort if menu_obj else 0
validated_data['sort'] = last_sort + 1
return super().create(validated_data)
class Meta:
model = Menu
fields = "__all__"
@@ -63,8 +71,8 @@ class WebRouterSerializer(CustomModelSerializer):
class Meta:
model = Menu
fields = (
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', 'web_path', 'component',
'component_name', 'cache', 'visible', 'status')
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link','link_url', 'is_catalog', 'web_path', 'component',
'component_name', 'cache', 'visible','is_iframe','is_affix', 'status')
read_only_fields = ["id"]
@@ -84,45 +92,6 @@ class MenuViewSet(CustomModelViewSet):
search_fields = ['name', 'status']
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog']
# extra_filter_class = []
@action(methods=['GET'], detail=False, permission_classes=[])
def web_router(self, request):
"""用于前端获取当前角色的路由"""
user = request.user
queryset = self.queryset.filter(status=1)
if not user.is_superuser:
role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id')
queryset = Menu.objects.filter(id__in=menu_list)
serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功")
@action(methods=['GET'], detail=False, permission_classes=[])
def get_all_menu(self, request):
"""用于菜单管理获取所有的菜单"""
user = request.user
queryset = self.queryset.all()
if not user.is_superuser:
role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id')
queryset = Menu.objects.filter(id__in=menu_list)
serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功")
@action(methods=['POST'], detail=False, permission_classes=[])
def drag_menu(self, request):
"""前端拖拽菜单之后重写parent"""
menu_id = request.data['menu_id']
parent_id = request.data['parent_id']
menu = Menu.objects.get(id=menu_id)
menu.parent_id = parent_id
menu.save()
return SuccessResponse(data=[], msg="拖拽菜单成功")
def list(self, request):
"""懒加载"""
request.query_params._mutable = True
@@ -145,3 +114,60 @@ class MenuViewSet(CustomModelViewSet):
serializer = MenuSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data)
@action(methods=['GET'], detail=False, permission_classes=[])
def web_router(self, request):
"""用于前端获取当前角色的路由"""
user = request.user
if user.is_superuser:
queryset = self.queryset.filter(status=1)
else:
role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id', flat=True)
queryset = Menu.objects.filter(id__in=menu_list)
serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功")
@action(methods=['GET'], detail=False, permission_classes=[])
def get_all_menu(self, request):
"""用于菜单管理获取所有的菜单"""
user = request.user
queryset = self.queryset.all()
if not user.is_superuser:
role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id')
queryset = Menu.objects.filter(id__in=menu_list)
serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功")
@action(methods=['POST'], detail=False, permission_classes=[])
def move_up(self, request):
"""菜单上移"""
menu_id = request.data.get('menu_id')
try:
menu = Menu.objects.get(id=menu_id)
except Menu.DoesNotExist:
return ErrorResponse(msg="菜单不存在")
previous_menu = Menu.objects.filter(sort__lt=menu.sort, parent=menu.parent).order_by('-sort').first()
if previous_menu:
previous_menu.sort, menu.sort = menu.sort, previous_menu.sort
previous_menu.save()
menu.save()
return SuccessResponse(data=[], msg="上移成功")
@action(methods=['POST'], detail=False, permission_classes=[])
def move_down(self, request):
"""菜单下移"""
menu_id = request.data['menu_id']
try:
menu = Menu.objects.get(id=menu_id)
except Menu.DoesNotExist:
return ErrorResponse(msg="菜单不存在")
next_menu = Menu.objects.filter(sort__gt=menu.sort, parent=menu.parent).order_by('sort').first()
if next_menu:
next_menu.sort, menu.sort = menu.sort, next_menu.sort
next_menu.save()
menu.save()
return SuccessResponse(data=[], msg="下移成功")

View File

@@ -11,7 +11,7 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import MenuButton, RoleMenuButtonPermission
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -23,7 +23,7 @@ class MenuButtonSerializer(CustomModelSerializer):
class Meta:
model = MenuButton
fields = ['id', 'name', 'value', 'api', 'method']
fields = ['id', 'name', 'value', 'api', 'method','menu']
read_only_fields = ["id"]
@@ -49,12 +49,24 @@ class MenuButtonViewSet(CustomModelViewSet):
retrieve:单例
destroy:删除
"""
queryset = MenuButton.objects.all()
queryset = MenuButton.objects.order_by('create_datetime')
serializer_class = MenuButtonSerializer
create_serializer_class = MenuButtonCreateUpdateSerializer
update_serializer_class = MenuButtonCreateUpdateSerializer
extra_filter_class = []
def list(self, request, *args, **kwargs):
"""
重写list方法
:param request:
:param args:
:param kwargs:
:return:
"""
queryset = self.filter_queryset(self.get_queryset()).order_by('name')
serializer = self.get_serializer(queryset, many=True, request=request)
return SuccessResponse(serializer.data,msg="获取成功")
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
def menu_button_all_permission(self,request):
"""
@@ -63,8 +75,7 @@ class MenuButtonViewSet(CustomModelViewSet):
:return:
"""
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
if is_superuser:
queryset = MenuButton.objects.values_list('value',flat=True)
else:
role_id = request.user.role.values_list('id', flat=True)

View File

@@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
from django.apps import apps
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Role, MenuField
from dvadmin.utils.models import get_custom_app_models
from dvadmin.utils.viewset import CustomModelViewSet
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse
class MenuFieldSerializer(CustomModelSerializer):
"""
列权限序列化器
"""
class Meta:
model = MenuField
fields = '__all__'
read_only_fields = ['id']
class MenuFieldViewSet(CustomModelViewSet):
"""
列权限视图集
"""
queryset = MenuField.objects.order_by('-model')
serializer_class = MenuFieldSerializer
def list(self, request, *args, **kwargs):
menu = request.query_params.get('menu')
if not menu:
return SuccessResponse([])
queryset = self.filter_queryset(self.get_queryset().filter(menu=menu))
serializer = self.get_serializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功")
def create(self, request, *args, **kwargs):
payload = request.data
for model in apps.get_models():
if payload.get('model') == model.__name__:
break
else:
return ErrorResponse(msg='模型表不存在')
if MenuField.objects.filter(menu=payload.get('menu'),model=model.__name__, field_name=payload.get('field_name')).exists():
return ErrorResponse(msg='%s 字段权限已有,不可重复创建' % payload.get('title'))
return super().create(request, *args, **kwargs)
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def get_models(self, request):
"""获取所有项目app下的model"""
res = []
for model in get_custom_app_models():
res.append({
'app': model['app'],
'title': model['verbose'],
'key': model['model']
})
return DetailResponse(res)
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
def auto_match_fields(self, request):
"""自动匹配已有的字段"""
menu_id = request.data.get('menu')
model_name = request.data.get('model')
if not menu_id or not model_name:
return ErrorResponse( msg='参数错误')
for model in get_custom_app_models():
if model['model'] != model_name:
continue
for field in model['fields']:
if MenuField.objects.filter(
menu_id=menu_id, model=model_name, field_name=field['name']
).exists():
continue
data = {
'menu': menu_id,
'model': model_name,
'field_name': field['name'],
'title': str(field['title']),
}
serializer = self.get_serializer(data=data, request=request)
serializer.is_valid(raise_exception=True)
serializer.save()
return SuccessResponse(msg='匹配成功')

View File

@@ -15,6 +15,7 @@ from dvadmin.system.views.dept import DeptSerializer
from dvadmin.system.views.menu import MenuSerializer
from dvadmin.system.views.menu_button import MenuButtonSerializer
from dvadmin.utils.crud_mixin import FastCrudMixin
from dvadmin.utils.field_permission import FieldPermissionMixin
from dvadmin.utils.json_response import SuccessResponse, DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.validator import CustomUniqueValidator
@@ -32,9 +33,6 @@ class RoleSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class RoleCreateUpdateSerializer(CustomModelSerializer):
"""
角色管理 创建/更新时的列化器
@@ -49,19 +47,19 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
def validate(self, attrs: dict):
return super().validate(attrs)
def save(self, **kwargs):
is_superuser = self.request.user.is_superuser
if not is_superuser:
self.validated_data.pop('admin')
data = super().save(**kwargs)
return data
# def save(self, **kwargs):
# is_superuser = self.request.user.is_superuser
# if not is_superuser:
# self.validated_data.pop('admin')
# data = super().save(**kwargs)
# return data
class Meta:
model = Role
fields = '__all__'
class MenuPermissonSerializer(CustomModelSerializer):
class MenuPermissionSerializer(CustomModelSerializer):
"""
菜单的按钮权限
"""
@@ -72,9 +70,9 @@ class MenuPermissonSerializer(CustomModelSerializer):
if is_superuser:
queryset = MenuButton.objects.filter(menu__id=instance.id)
else:
menu_permission_id_list = self.request.user.role.values_list('permission',flat=True)
queryset = MenuButton.objects.filter(id__in=menu_permission_id_list,menu__id=instance.id)
serializer = MenuButtonSerializer(queryset,many=True, read_only=True)
menu_permission_id_list = self.request.user.role.values_list('permission', flat=True)
queryset = MenuButton.objects.filter(id__in=menu_permission_id_list, menu__id=instance.id)
serializer = MenuButtonSerializer(queryset, many=True, read_only=True)
return serializer.data
class Meta:
@@ -82,7 +80,29 @@ class MenuPermissonSerializer(CustomModelSerializer):
fields = ['id', 'parent', 'name', 'menuPermission']
class RoleViewSet(CustomModelViewSet,FastCrudMixin):
class MenuButtonPermissionSerializer(CustomModelSerializer):
"""
菜单和按钮权限
"""
isCheck = serializers.SerializerMethodField()
def get_isCheck(self, instance):
is_superuser = self.request.user.is_superuser
if is_superuser:
return True
else:
return MenuButton.objects.filter(
menu__id=instance.id,
role__id__in=self.request.user.role.values_list('id', flat=True),
).exists()
class Meta:
model = Menu
fields = '__all__'
class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin):
"""
角色管理接口
list:查询

View File

@@ -72,7 +72,14 @@ class RoleMenuPermissionViewSet(CustomModelViewSet):
menu_list = body.get('menu',None)
if menu_list is None:
return ErrorResponse(msg="未获取到菜单参数")
data = [{"role":role_id,"menu":item} for item in menu_list]
obj_list = RoleMenuPermission.objects.filter(role__id=role_id).values_list('menu__id',flat=True)
old_set = set(obj_list)
new_set = set(menu_list)
#need_update = old_set.intersection(new_set) # 需要更新的
need_del = old_set.difference(new_set) # 需要删除的
need_add = new_set.difference(old_set) # 需要新增的
RoleMenuPermission.objects.filter(role__id=role_id,menu__in=list(need_del)).delete()
data = [{"role": role_id, "menu": item} for item in list(need_add)]
serializer = RoleMenuPermissionSerializer(data=data,many=True,request=request)
if serializer.is_valid(raise_exception=True):
serializer.save()

View File

@@ -6,11 +6,14 @@
@Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理
"""
from django.db.models import F
from django.db.models import F, Subquery, OuterRef, Exists
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, FieldPermission, \
MenuField
from dvadmin.system.views.menu import MenuSerializer
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -20,27 +23,19 @@ class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
"""
菜单按钮-序列化器
"""
class Meta:
model = RoleMenuButtonPermission
fields = "__all__"
read_only_fields = ["id"]
class RoleMenuButtonPermissionInitSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化器
"""
class Meta:
model = RoleMenuButtonPermission
fields = "__all__"
read_only_fields = ["id"]
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化器
"""
menu_button__name = serializers.CharField(source='menu_button.name', read_only=True)
menu_button__value = serializers.CharField(source='menu_button.value', read_only=True)
class Meta:
model = RoleMenuButtonPermission
@@ -48,6 +43,107 @@ class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class RoleButtonPermissionSerializer(CustomModelSerializer):
"""
角色按钮权限
"""
isCheck = serializers.SerializerMethodField()
data_range = serializers.SerializerMethodField()
def get_isCheck(self, instance):
params = self.request.query_params
return RoleMenuButtonPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
).exists()
def get_data_range(self, instance):
params = self.request.query_params
obj = RoleMenuButtonPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
).first()
if obj is None:
return None
return obj.data_range
class Meta:
model = MenuButton
fields = ['id','name','value','isCheck','data_range']
class RoleFieldPermissionSerializer(CustomModelSerializer):
class Meta:
model = FieldPermission
fields = "__all__"
class RoleMenuFieldSerializer(CustomModelSerializer):
is_query = serializers.SerializerMethodField()
is_create = serializers.SerializerMethodField()
is_update = serializers.SerializerMethodField()
def get_is_query(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
if queryset:
return queryset.is_query
return False
def get_is_create(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
if queryset:
return queryset.is_create
return False
def get_is_update(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
if queryset:
return queryset.is_update
return False
class Meta:
model = MenuField
fields = ['id','field_name','title','is_query','is_create','is_update']
class RoleMenuPermissionSerializer(CustomModelSerializer):
"""
菜单和按钮权限
"""
name = serializers.SerializerMethodField()
isCheck = serializers.SerializerMethodField()
btns = serializers.SerializerMethodField()
columns = serializers.SerializerMethodField()
def get_name(self, instance):
parent_list = Menu.get_all_parent(instance['id'])
names = [d["name"] for d in parent_list]
return "/".join(names)
def get_isCheck(self, instance):
params = self.request.query_params
return RoleMenuPermission.objects.filter(
menu__id=instance['id'],
role__id=params.get('role'),
).exists()
def get_btns(self, instance):
btn_list = MenuButton.objects.filter(menu__id=instance['id']).values('id', 'name', 'value')
serializer = RoleButtonPermissionSerializer(btn_list,many=True,request=self.request)
return serializer.data
def get_columns(self, instance):
col_list = MenuField.objects.filter(menu=instance['id'])
serializer = RoleMenuFieldSerializer(col_list,many=True,request=self.request)
return serializer.data
class Meta:
model = Menu
fields = ['id','name','isCheck','btns','columns']
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
"""
菜单按钮接口
@@ -64,38 +160,106 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
extra_filter_class = []
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def role_get_menu(self, request):
"""根据当前用户的角色返回角色拥有的菜单"""
def get_role_premission(self, request):
"""
角色授权获取:
:param request: role
:return: menu,btns,columns
"""
params = request.query_params
role = params.get('role',None)
if role is None:
return ErrorResponse(msg="未获取到角色信息")
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
queryset = Menu.objects.filter(status=1).values('id','name','parent','is_catalog')
# if is_superuser:
# queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all()
# else:
# role_id = request.user.role.values_list('id', flat=True)
# menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id',flat=True)
# queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id').all()
# serializer = RoleMenuPermissionSerializer(queryset,many=True,request=request)
# data = serializer.data
# return DetailResponse(data=data)
data = []
if is_superuser:
queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all()
else:
role_id = request.user.role.values_list('id',flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_id).values(id=F('menu__id'),name=F('menu__name'),parent=F('menu__parent'),is_catalog=F('menu__is_catalog'))
return DetailResponse(data=queryset)
role_id = request.user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id',flat=True)
queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id')
for item in queryset:
parent_list = Menu.get_all_parent(item['id'])
names = [d["name"] for d in parent_list]
completeName = "/".join(names)
isCheck = RoleMenuPermission.objects.filter(
menu__id=item['id'],
role__id=role,
).exists()
mbCheck = RoleMenuButtonPermission.objects.filter(
menu_button=OuterRef("pk"),
role__id=role,
)
btns = MenuButton.objects.filter(
menu__id=item['id'],
).annotate(isCheck=Exists(mbCheck)).values('id', 'name', 'value', 'isCheck',
data_range=F('menu_button_permission__data_range'))
dicts = {
'name': completeName,
'id': item['id'],
'isCheck': isCheck,
'btns': btns,
}
data.append(dicts)
return DetailResponse(data=data)
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
def set_role_premission(self,request,pk):
"""
对角色的菜单和按钮及按钮范围授权:
:param request:
:param pk: role
:return:
"""
body = request.data
RoleMenuPermission.objects.filter(role=pk).delete()
RoleMenuButtonPermission.objects.filter(role=pk).delete()
for menu in body:
if menu.get('isCheck'):
menu_parent = Menu.get_all_parent(menu.get('id'))
role_menu_permission_list = []
for d in menu_parent:
role_menu_permission_list.append(RoleMenuPermission(role_id=pk, menu_id=d["id"]))
RoleMenuPermission.objects.bulk_create(role_menu_permission_list)
# RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
for btn in menu.get('btns'):
if btn.get('isCheck'):
data_range = btn.get('data_range',0) or 0
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),data_range=data_range)
instance.dept.set(btn.get('dept',[]))
for col in menu.get('columns'):
FieldPermission.objects.update_or_create(role_id=pk,field_id=col.get('id'),is_query=col.get('is_query'),is_create=col.get('is_create'),is_update=col.get('is_update'))
return DetailResponse(msg="授权成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def role_menu_get_button(self,request):
def role_menu_get_button(self, request):
"""
当前用户角色和菜单获取可下拉选项的按钮:角色授权页面使用
:param request:
:return:
"""
params = request.query_params
if params:
menu_id = params.get('menu',None)
if menu_id:
if params := request.query_params:
if menu_id := params.get('menu', None):
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
if is_superuser:
queryset = MenuButton.objects.filter(menu=menu_id).values('id', 'name')
else:
role_list = request.user.role.values_list('id',flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role_in=role_list,menu_button__menu=menu_id).values(
id=F('menu_button__id'),
name=F('menu_button__name')
)
role_list = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(
role__in=role_list, menu_button__menu=menu_id
).values(btn_id=F('menu_button__id'), name=F('menu_button__name'))
return DetailResponse(data=queryset)
return ErrorResponse(msg="参数错误")
@@ -133,12 +297,12 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
return DetailResponse(data=data)
else:
data = []
role_id = request.user.role.id
params = request.query_params
if params:
menu_button_id = params.get('menu_button', None)
if menu_button_id:
role_queryset = RoleMenuButtonPermission.objects.filter(role=role_id,menu_button=menu_button_id).values_list('data_range',flat=True)
role_list = request.user.role.values_list('id', flat=True)
if params := request.query_params:
if menu_button_id := params.get('menu_button', None):
role_queryset = RoleMenuButtonPermission.objects.filter(
role__in=role_list, menu_button__id=menu_button_id
).values_list('data_range', flat=True)
data_range_list = list(set(role_queryset))
for item in data_range_list:
if item == 0:
@@ -199,46 +363,70 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
"""
params = request.query_params
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
queryset = Dept.objects.values('id','name','parent')
return DetailResponse(data=queryset)
if is_superuser:
queryset = Dept.objects.values('id', 'name', 'parent')
else:
if params:
if not params:
return ErrorResponse(msg="参数错误")
menu_button = params.get('menu_button')
if menu_button is None:
return ErrorResponse(msg="参数错误")
role_list = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role_in=role_list,menu_button=None).values(
id=F('dept__id'),
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list, menu_button=None).values(
dept_id=F('dept__id'),
name=F('dept__name'),
parent=F('dept__parent')
)
return DetailResponse(data=queryset)
else:
return ErrorResponse(msg="参数错误")
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
def menu_to_button(self,request):
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def menu_to_button(self, request):
"""
根据所选择菜单获取已配置的按钮/接口权限:角色授权页面使用
:param request:
:return:
"""
params = request.query_params
if params:
menu_id = params.get('menu',None)
menu_id = params.get('menu', None)
if menu_id is None:
return ErrorResponse(msg="未获取到参数")
is_superuser = request.user.is_superuser
if is_superuser:
queryset = RoleMenuButtonPermission.objects.filter(menu_button__menu=menu_id).values(
'id',
'data_range',
'menu_button',
'menu_button__name',
'menu_button__value'
)
return DetailResponse(data=queryset)
else:
if params:
role_id = params.get('role', None)
if role_id is None:
return ErrorResponse(msg="未获取到参数")
queryset = RoleMenuButtonPermission.objects.filter(role=role_id,menu_button__menu=menu_id).values(
queryset = RoleMenuButtonPermission.objects.filter(role=role_id, menu_button__menu=menu_id).values(
'id',
'data_range',
'menu_button'
'menu_button',
'menu_button__name',
'menu_button__value'
)
return DetailResponse(data=queryset)
return ErrorResponse(msg="未获取到参数")
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def role_to_menu(self, request):
"""
获取角色对应的按钮权限
:param request:
:return:
"""
params = request.query_params
role_id = params.get('role', None)
if role_id is None:
return ErrorResponse(msg="未获取到参数")
queryset = RoleMenuPermission.objects.filter(role_id=role_id).values_list('menu_id', flat=True).distinct()
return DetailResponse(data=queryset)

View File

@@ -1,15 +1,16 @@
import hashlib
from django.contrib.auth.hashers import make_password
from django.contrib.auth.hashers import make_password, check_password
from django_restql.fields import DynamicSerializerMethodField
from rest_framework import serializers
from rest_framework.decorators import action, permission_classes
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from django.db import connection
from django.db.models import Q
from application import dispatch
from dvadmin.system.models import Users, Role, Dept
from dvadmin.system.views.role import RoleSerializer
from dvadmin.utils.json_response import ErrorResponse, DetailResponse
from dvadmin.utils.json_response import ErrorResponse, DetailResponse, SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.validator import CustomUniqueValidator
from dvadmin.utils.viewset import CustomModelViewSet
@@ -23,7 +24,7 @@ def recursion(instance, parent, result):
res.append(data)
if new_instance:
array = recursion(new_instance, parent, result)
res += (array)
res += array
return res
@@ -196,10 +197,12 @@ class ExportUserProfileSerializer(CustomModelSerializer):
class UserProfileImportSerializer(CustomModelSerializer):
password = serializers.CharField(read_only=True, required=False)
def save(self, **kwargs):
data = super().save(**kwargs)
password = hashlib.new(
"md5", str(self.initial_data.get("password", "")).encode(encoding="UTF-8")
"md5", str(self.initial_data.get("password", "admin123456")).encode(encoding="UTF-8")
).hexdigest()
data.set_password(password)
data.save()
@@ -231,7 +234,7 @@ class UserViewSet(CustomModelViewSet):
create_serializer_class = UserCreateSerializer
update_serializer_class = UserUpdateSerializer
filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"]
search_fields = ["username", "name", "gender", "dept__name", "role__name"]
search_fields = ["username", "name", "dept__name", "role__name"]
# 导出
export_field_label = {
"username": "用户账号",
@@ -264,7 +267,6 @@ class UserViewSet(CustomModelViewSet):
"data": {"启用": True, "禁用": False},
}
},
"password": "登录密码",
"dept": {"title": "部门", "choices": {"queryset": Dept.objects.filter(status=True), "values_name": "name"}},
"role": {"title": "角色", "choices": {"queryset": Role.objects.filter(status=True), "values_name": "name"}},
}
@@ -313,7 +315,7 @@ class UserViewSet(CustomModelViewSet):
serializer.save()
return DetailResponse(data=None, msg="修改成功")
@action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
@action(methods=["PUT"], detail=False, permission_classes=[IsAuthenticated])
def change_password(self, request, *args, **kwargs):
"""密码修改"""
data = request.data
@@ -324,12 +326,11 @@ class UserViewSet(CustomModelViewSet):
return ErrorResponse(msg="参数不能为空")
if new_pwd != new_pwd2:
return ErrorResponse(msg="两次密码不匹配")
check_password = request.user.check_password(old_pwd)
if not check_password:
check_password = request.user.check_password(hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest())
if check_password:
new_pwd = hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest()
request.user.password = make_password(new_pwd)
verify_password = check_password(old_pwd, self.request.user.password)
if not verify_password:
verify_password = check_password(hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest(), self.request.user.password)
if verify_password:
request.user.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest())
request.user.save()
return DetailResponse(data=None, msg="修改成功")
else:
@@ -351,6 +352,8 @@ class UserViewSet(CustomModelViewSet):
"""
密码重置
"""
if not self.request.user.is_superuser:
return ErrorResponse(msg="只允许超级管理员对其进行密码重置")
instance = Users.objects.filter(id=pk).first()
data = request.data
new_pwd = data.get("newPassword")
@@ -364,3 +367,43 @@ class UserViewSet(CustomModelViewSet):
return DetailResponse(data=None, msg="修改成功")
else:
return ErrorResponse(msg="未获取到用户")
def list(self, request, *args, **kwargs):
dept_id = request.query_params.get('dept')
show_all = request.query_params.get('show_all')
if not dept_id:
dept_id = ''
if not show_all:
show_all = 0
if int(show_all):
all_did = [dept_id]
def inner(did):
sub = Dept.objects.filter(parent_id=did)
if not sub.exists():
return
for i in sub:
all_did.append(i.pk)
inner(i)
if dept_id != '':
inner(dept_id)
searchs = [
Q(**{f+'__icontains':i})
for f in self.search_fields
] if (i:=request.query_params.get('search')) else []
q_obj = []
if searchs:
q = searchs[0]
for i in searchs[1:]:
q |= i
q_obj.append(Q(q))
queryset = Users.objects.filter(*q_obj, dept_id__in=all_did)
else:
queryset = self.filter_queryset(self.get_queryset())
else:
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True, request=request)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功")

View File

@@ -3,8 +3,11 @@ import logging
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.hashers import check_password
from django.utils import timezone
from dvadmin.utils.validator import CustomValidationError
logger = logging.getLogger(__name__)
UserModel = get_user_model()
@@ -24,10 +27,13 @@ class CustomBackend(ModelBackend):
except UserModel.DoesNotExist:
UserModel().set_password(password)
else:
check_password = user.check_password(password)
if not check_password:
check_password = user.check_password(hashlib.md5(password.encode(encoding='UTF-8')).hexdigest())
if check_password and self.user_can_authenticate(user):
verify_password = check_password(password, user.password)
if not verify_password:
password = hashlib.md5(password.encode(encoding='UTF-8')).hexdigest()
verify_password = check_password(password, user.password)
if verify_password:
if self.user_can_authenticate(user):
user.last_login = timezone.now()
user.save()
return user
raise CustomValidationError("当前用户已被禁用,请联系管理员!")

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from django.db.models import F
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import FieldPermission, MenuField
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.models import get_custom_app_models
class FieldPermissionMixin:
@action(methods=['get'], detail=False,permission_classes=[IsAuthenticated])
def field_permission(self, request):
"""
获取字段权限
"""
finded = False
for model in get_custom_app_models():
if model['object'] is self.serializer_class.Meta.model:
finded = True
break
if finded:
break
if finded is False:
return []
user = request.user
if user.is_superuser==1:
data = MenuField.objects.filter( model=model['model']).values('field_name')
for item in data:
item['is_create'] = True
item['is_query'] = True
item['is_update'] = True
else:
roles = request.user.role.values_list('id', flat=True)
data= FieldPermission.objects.filter(
field__model=model['model'],role__in=roles
).values( 'is_create', 'is_query', 'is_update',field_name=F('field__field_name'))
return DetailResponse(data=data)

View File

@@ -12,16 +12,49 @@ from collections import OrderedDict
from functools import reduce
import six
from django.db import models
from django.db.models import Q, F
from django.db.models.constants import LOOKUP_SEP
from django_filters import utils
from django_filters.filters import CharFilter
from django_filters import utils, FilterSet
from django_filters.constants import ALL_FIELDS
from django_filters.filters import CharFilter, DateTimeFromToRangeFilter
from django_filters.rest_framework import DjangoFilterBackend
from django_filters.utils import get_model_field
from rest_framework.filters import BaseFilterBackend
from django_filters.conf import settings
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission
from dvadmin.utils.models import CoreModel
class CoreModelFilterBankend(BaseFilterBackend):
"""
自定义时间范围过滤器
"""
def filter_queryset(self, request, queryset, view):
create_datetime_after = request.query_params.get('create_datetime_after', None)
create_datetime_before = request.query_params.get('create_datetime_before', None)
update_datetime_after = request.query_params.get('update_datetime_after', None)
update_datetime_before = request.query_params.get('update_datetime_after', None)
if any([create_datetime_after, create_datetime_before, update_datetime_after, update_datetime_before]):
create_filter = Q()
if create_datetime_after and create_datetime_before:
create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=create_datetime_before)
elif create_datetime_after:
create_filter &= Q(create_datetime__gte=create_datetime_after)
elif create_datetime_before:
create_filter &= Q(create_datetime__lte=create_datetime_before)
# 更新时间范围过滤条件
update_filter = Q()
if update_datetime_after and update_datetime_before:
update_filter &= Q(update_datetime__gte=update_datetime_after) & Q(update_datetime__lte=update_datetime_before)
elif update_datetime_after:
update_filter &= Q(update_datetime__gte=update_datetime_after)
elif update_datetime_before:
update_filter &= Q(update_datetime__lte=update_datetime_before)
# 结合两个时间范围过滤条件
queryset = queryset.filter(create_filter & update_filter)
return queryset
return queryset
def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
"""
@@ -75,7 +108,7 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
if item.get("permission__api")
]
for item in api_white_list:
new_api = api + ":" + str(method)
new_api = f"{api}:{method}"
matchObj = re.match(item, new_api, re.M | re.I)
if matchObj is None:
continue
@@ -86,6 +119,12 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
如果不是超级管理员,则进入下一步权限判断
"""
if request.user.is_superuser == 0:
return self._extracted_from_filter_queryset_33(request, queryset, api, method)
else:
return queryset
# TODO Rename this here and in `filter_queryset`
def _extracted_from_filter_queryset_33(self, request, queryset, api, method):
# 0. 获取用户的部门id没有部门则返回空
user_dept_id = getattr(request.user, "dept_id", None)
if not user_dept_id:
@@ -105,21 +144,22 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
# (2, "本部门数据权限"),
# (3, "全部数据权限"),
# (4, "自定数据权限")
replace_str = re.compile('\d')
re_api = replace_str.sub('{id}', api)
re_api = api
_pk = request.parser_context["kwargs"].get('pk')
if _pk: # 判断是否是单例查询
re_api = re.sub(_pk,'{id}', api)
role_id_list = request.user.role.values_list('id', flat=True)
role_permission_list=RoleMenuButtonPermission.objects.filter(
role__in=role_id_list,
role__status=1,
menu_button__api=re_api,
menu_button__method=method).values(
'data_range',
role_admin=F('role__admin')
'data_range'
)
dataScope_list = [] # 权限范围列表
for ele in role_permission_list:
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
if 3 == ele.get("data_range") or ele.get("role_admin") == True:
if ele.get("data_range") == 3:
return queryset
dataScope_list.append(ele.get("data_range"))
dataScope_list = list(set(dataScope_list))
@@ -133,26 +173,28 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
# 5. 自定数据权限 获取部门,根据部门过滤
dept_list = []
for ele in dataScope_list:
if ele == 4:
dept_list.extend(
request.user.role.filter(status=1).values_list(
"dept__id", flat=True
)
)
elif ele == 2:
dept_list.append(user_dept_id)
elif ele == 1:
if ele == 1:
dept_list.append(user_dept_id)
dept_list.extend(
get_dept(
user_dept_id,
)
)
elif ele == 2:
dept_list.append(user_dept_id)
elif ele == 4:
dept_ids = RoleMenuButtonPermission.objects.filter(
role__in=role_id_list,
role__status=1,
data_range=4).values_list(
'dept__id',flat=True
)
dept_list.extend(
dept_ids
)
if queryset.model._meta.model_name == 'dept':
return queryset.filter(id__in=list(set(dept_list)))
return queryset.filter(dept_belong_id__in=list(set(dept_list)))
else:
return queryset
class CustomDjangoFilterBackend(DjangoFilterBackend):
@@ -163,19 +205,24 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
"$": "iregex",
"~": "icontains",
}
filter_fields = "__all__"
def construct_search(self, field_name):
def construct_search(self, field_name, lookup_expr=None):
lookup = self.lookup_prefixes.get(field_name[0])
if lookup:
field_name = field_name[1:]
else:
lookup = "icontains"
lookup = lookup_expr
if lookup:
if field_name.endswith(lookup):
return field_name
return LOOKUP_SEP.join([field_name, lookup])
return field_name
def find_filter_lookups(self, orm_lookups, search_term_key):
for lookup in orm_lookups:
# if lookup.find(search_term_key) >= 0:
new_lookup = lookup.split("__")[0]
new_lookup = LOOKUP_SEP.join(lookup.split(LOOKUP_SEP)[:-1]) if len(lookup.split(LOOKUP_SEP)) > 1 else lookup
# 修复条件搜索错误 bug
if new_lookup == search_term_key:
return lookup
@@ -191,18 +238,22 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
# TODO: remove assertion in 2.1
if filterset_class is None and hasattr(view, "filter_class"):
utils.deprecate(
"`%s.filter_class` attribute should be renamed `filterset_class`."
% view.__class__.__name__
"`%s.filter_class` attribute should be renamed `filterset_class`." % view.__class__.__name__
)
filterset_class = getattr(view, "filter_class", None)
# TODO: remove assertion in 2.1
if filterset_fields is None and hasattr(view, "filter_fields"):
utils.deprecate(
"`%s.filter_fields` attribute should be renamed `filterset_fields`."
% view.__class__.__name__
"`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__
)
filterset_fields = getattr(view, "filter_fields", None)
self.filter_fields = getattr(view, "filter_fields", None)
if isinstance(self.filter_fields, (list, tuple)):
filterset_fields = [
field[1:] if field[0] in self.lookup_prefixes.keys() else field for field in self.filter_fields
]
else:
filterset_fields = self.filter_fields
if filterset_class:
filterset_model = filterset_class._meta.model
@@ -222,6 +273,51 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
MetaBase = getattr(self.filterset_base, "Meta", object)
class AutoFilterSet(self.filterset_base):
@classmethod
def get_all_model_fields(cls, model):
opts = model._meta
return [
f.name
for f in sorted(opts.fields + opts.many_to_many)
if (f.name == "id")
or not isinstance(f, models.AutoField)
and not (getattr(f.remote_field, "parent_link", False))
]
@classmethod
def get_fields(cls):
"""
Resolve the 'fields' argument that should be used for generating filters on the
filterset. This is 'Meta.fields' sans the fields in 'Meta.exclude'.
"""
model = cls._meta.model
fields = cls._meta.fields
exclude = cls._meta.exclude
assert not (fields is None and exclude is None), (
"Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' "
"has been deprecated since 0.15.0 and is now disallowed. Add an explicit "
"'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__
)
# Setting exclude with no fields implies all other fields.
if exclude is not None and fields is None:
fields = ALL_FIELDS
# Resolve ALL_FIELDS into all fields for the filterset's model.
if fields == ALL_FIELDS:
fields = cls.get_all_model_fields(model)
# Remove excluded fields
exclude = exclude or []
if not isinstance(fields, dict):
fields = [(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude]
else:
fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude]
return OrderedDict(fields)
@classmethod
def get_filters(cls):
"""
@@ -249,7 +345,13 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
# warn if the field doesn't exist.
if field is None:
undefined.append(field_name)
# 更新默认字符串搜索为模糊搜索
if (
isinstance(field, (models.CharField))
and filterset_fields == "__all__"
and lookups == ["exact"]
):
lookups = ["icontains"]
for lookup_expr in lookups:
filter_name = cls.get_filter_name(field_name, lookup_expr)
@@ -259,20 +361,15 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
continue
if field is not None:
filters[filter_name] = cls.filter_for_field(
field, field_name, lookup_expr
)
filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr)
# Allow Meta.fields to contain declared filters *only* when a list/tuple
if isinstance(cls._meta.fields, (list, tuple)):
undefined = [
f for f in undefined if f not in cls.declared_filters
]
undefined = [f for f in undefined if f not in cls.declared_filters]
if undefined:
raise TypeError(
"'Meta.fields' must not contain non-model field names: %s"
% ", ".join(undefined)
"'Meta.fields' must not contain non-model field names: %s" % ", ".join(undefined)
)
# Add in declared filters. This is necessary since we don't enforce adding
@@ -294,22 +391,31 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
return queryset
if filterset.__class__.__name__ == "AutoFilterSet":
queryset = filterset.queryset
orm_lookups = []
for search_field in filterset.filters:
if isinstance(filterset.filters[search_field], CharFilter):
orm_lookups.append(
self.construct_search(six.text_type(search_field))
filter_fields = filterset.filters if self.filter_fields == "__all__" else self.filter_fields
orm_lookup_dict = dict(
zip(
[field for field in filter_fields],
[filterset.filters[lookup].lookup_expr for lookup in filterset.filters.keys()],
)
else:
orm_lookups.append(search_field)
)
orm_lookups = [
self.construct_search(lookup, lookup_expr) for lookup, lookup_expr in orm_lookup_dict.items()
]
# print(orm_lookups)
conditions = []
queries = []
for search_term_key in filterset.data.keys():
orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key)
if not orm_lookup:
if not orm_lookup or filterset.data.get(search_term_key) == '':
continue
filterset_data_len = len(filterset.data.getlist(search_term_key))
if filterset_data_len == 1:
query = Q(**{orm_lookup: filterset.data[search_term_key]})
queries.append(query)
elif filterset_data_len == 2:
orm_lookup += '__range'
query = Q(**{orm_lookup: filterset.data.getlist(search_term_key)})
queries.append(query)
if len(queries) > 0:
conditions.append(reduce(operator.and_, queries))
queryset = queryset.filter(reduce(operator.and_, conditions))

View File

@@ -1,125 +0,0 @@
import logging
import os.path
import sys
from django.db import connection
from loguru import logger
from logging.handlers import RotatingFileHandler
# 1.🎖先声明一个类继承logging.Handler(制作一件品如的衣服)
from application.dispatch import is_tenants_mode
class InterceptTimedRotatingFileHandler(RotatingFileHandler, logging.Filter):
"""
自定义反射时间回滚日志记录器
缺少命名空间
"""
def __init__(self, filename, when='d', interval=1, backupCount=5, encoding="utf-8", delay=False, utc=False,
maxBytes=1024 * 1024 * 100, atTime=None, logging_levels="all", format=None):
super(InterceptTimedRotatingFileHandler, self).__init__(filename)
filename = os.path.abspath(filename)
# 定义默认格式
if not format:
format = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <green>{extra[ip]}:{extra[port]}</green> | <level>{level: <8}</level>| <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>"
when = when.lower()
# 2.🎖️需要本地用不同的文件名做为不同日志的筛选器
logger.configure(
handlers=[
dict(sink=sys.stderr, format=format),
],
)
self.logger_ = logger.bind(sime=filename, ip="-", port="-", username="张三")
self.filename = filename
key_map = {
'h': 'hour',
'w': 'week',
's': 'second',
'm': 'minute',
'd': 'day',
}
# 根据输入文件格式及时间回滚设立文件名称
rotation = f"{maxBytes / 1024 / 1024}MB"
retention = "%d %ss" % (backupCount, key_map[when])
time_format = "{time:%Y-%m-%d_%H-%M-%S}"
if when == "s":
time_format = "{time:%Y-%m-%d_%H-%M-%S}"
elif when == "m":
time_format = "{time:%Y-%m-%d_%H-%M}"
elif when == "h":
time_format = "{time:%Y-%m-%d_%H}"
elif when == "d":
time_format = "{time:%Y-%m-%d}"
elif when == "w":
time_format = "{time:%Y-%m-%d}"
level_keys = ["info"]
# 3.🎖️构建一个筛选器
levels = {
"debug": lambda x: "DEBUG" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"error": lambda x: "ERROR" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"info": lambda x: "INFO" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"warning": lambda x: "WARNING" == x['level'].name.upper() and x['extra'].get('sime') == filename
}
# 4. 🎖️根据输出构建筛选器
if isinstance(logging_levels, str):
if logging_levels.lower() == "all":
level_keys = levels.keys()
elif logging_levels.lower() in levels:
level_keys = [logging_levels]
elif isinstance(logging_levels, (list, tuple)):
level_keys = logging_levels
for k, f in {_: levels[_] for _ in level_keys}.items():
# 5.🎖为防止重复添加sink而重复写入日志需要判断是否已经装载了对应sink防止其使用秘技反复横跳。
filename_fmt = filename.replace(".log", "_%s_%s.log" % (time_format, k))
# noinspection PyUnresolvedReferences,PyProtectedMember
file_key = {_._name: han_id for han_id, _ in self.logger_._core.handlers.items()}
filename_fmt_key = "'{}'".format(filename_fmt)
if filename_fmt_key in file_key:
continue
# self.logger_.remove(file_key[filename_fmt_key])
self.logger_.add(
filename_fmt,
format=format,
retention=retention,
encoding=encoding,
level=self.level,
rotation=rotation,
compression="zip", # 日志归档自行压缩文件
delay=delay,
enqueue=True,
backtrace=True,
filter=f
)
def emit(self, record):
try:
level = self.logger_.level(record.levelname).name
except ValueError:
level = record.levelno
frame, depth = logging.currentframe(), 2
# 6.🎖️把当前帧的栈深度回到发生异常的堆栈深度,不然就是当前帧发生异常而无法回溯
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
# 设置自定义属性
port = "-"
ip = "-"
details = frame.f_locals.get('details', None)
msg = self.format(record)
bind = {}
if details and details.get('client'):
ip, port = details.get('client').split(':')
if is_tenants_mode():
bind["schema_name"] = connection.tenant.schema_name
bind["domain_url"] = getattr(connection.tenant, 'domain_url', None)
bind["ip"] = ip
bind["port"] = port
self.logger_ \
.opt(depth=depth, exception=record.exc_info, colors=True) \
.bind(**bind) \
.log(level, msg)

View File

@@ -2,9 +2,11 @@
日志 django中间件
"""
import json
import logging
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.http import HttpResponse, HttpResponseServerError
from django.utils.deprecation import MiddlewareMixin
from dvadmin.system.models import OperationLog
@@ -87,3 +89,58 @@ class ApiLoggingMiddleware(MiddlewareMixin):
if self.methods == 'ALL' or request.method in self.methods:
self.__handle_response(request, response)
return response
logger = logging.getLogger("healthz")
class HealthCheckMiddleware(object):
"""
存活检查中间件
"""
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
if request.method == "GET":
if request.path == "/readiness":
return self.readiness(request)
elif request.path == "/healthz":
return self.healthz(request)
return self.get_response(request)
def healthz(self, request):
"""
Returns that the server is alive.
"""
return HttpResponse("OK")
def readiness(self, request):
# Connect to each database and do a generic standard SQL query
# that doesn't write any data and doesn't depend on any tables
# being present.
try:
from django.db import connections
for name in connections:
cursor = connections[name].cursor()
cursor.execute("SELECT 1;")
row = cursor.fetchone()
if row is None:
return HttpResponseServerError("db: invalid response")
except Exception as e:
logger.exception(e)
return HttpResponseServerError("db: cannot connect to database.")
# Call get_stats() to connect to each memcached instance and get it's stats.
# This can effectively check if each is online.
try:
from django.core.cache import caches
from django.core.cache.backends.memcached import BaseMemcachedCache
for cache in caches.all():
if isinstance(cache, BaseMemcachedCache):
stats = cache._cache.get_stats()
if len(stats) != len(cache._servers):
return HttpResponseServerError("cache: cannot connect to cache.")
except Exception as e:
logger.exception(e)
return HttpResponseServerError("cache: cannot connect to cache.")
return HttpResponse("OK")

View File

@@ -6,22 +6,21 @@
@Created on: 2021/5/31 031 22:08
@Remark: 公共基础model类
"""
import uuid
from importlib import import_module
from django.apps import apps
from django.db import models
from django.db.models import QuerySet
from django.conf import settings
from application import settings
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
class SoftDeleteQuerySet(QuerySet):
class SoftDeleteQuerySet(models.QuerySet):
pass
class SoftDeleteManager(models.Manager):
"""支持软删除"""
@@ -40,7 +39,7 @@ class SoftDeleteManager(models.Manager):
return SoftDeleteQuerySet(self.model, using=self._db).exclude(is_deleted=False)
return SoftDeleteQuerySet(self.model).exclude(is_deleted=True)
def get_by_natural_key(self,name):
def get_by_natural_key(self, name):
return SoftDeleteQuerySet(self.model).get(username=name)
@@ -73,10 +72,13 @@ class CoreModel(models.Model):
id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
description = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False)
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL,
db_constraint=False)
modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人")
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True, verbose_name="数据归属部门")
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True,
verbose_name="数据归属部门")
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间",
verbose_name="修改时间")
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
verbose_name="创建时间")
@@ -86,8 +88,6 @@ class CoreModel(models.Model):
verbose_name_plural = verbose_name
def get_all_models_objects(model_name=None):
"""
获取所有 models 对象
@@ -112,3 +112,51 @@ def get_all_models_objects(model_name=None):
if model_name:
return settings.ALL_MODELS_OBJECTS[model_name] or {}
return settings.ALL_MODELS_OBJECTS or {}
def get_model_from_app(app_name):
"""获取模型里的字段"""
model_module = import_module(app_name + '.models')
filter_model = [
getattr(model_module, item) for item in dir(model_module)
if item != 'CoreModel' and issubclass(getattr(model_module, item).__class__, models.base.ModelBase)
]
model_list = []
for model in filter_model:
if model.__name__ == 'AbstractUser':
continue
fields = [
{'title': field.verbose_name, 'name': field.name, 'object': field}
for field in model._meta.fields
]
model_list.append({
'app': app_name,
'verbose': model._meta.verbose_name,
'model': model.__name__,
'object': model,
'fields': fields
})
return model_list
def get_custom_app_models(app_name=None):
"""
获取所有项目下的app里的models
"""
if app_name:
return get_model_from_app(app_name)
all_apps = apps.get_app_configs()
res = []
for app in all_apps:
if app.name.startswith('django'):
continue
if app.name in settings.COLUMN_EXCLUDE_APPS:
continue
try:
all_models = get_model_from_app(app.name)
if all_models:
for model in all_models:
res.append(model)
except Exception as e:
pass
return res

View File

@@ -40,13 +40,11 @@ class CustomPagination(PageNumberPagination):
try:
self.page = paginator.page(page_number)
except InvalidPage as exc:
# msg = self.invalid_page_message.format(
# page_number=page_number, message=str(exc)
# )
# raise NotFound(msg)
empty = False
pass
if paginator.num_pages > 1 and self.template is not None:
# The browsable API should display pagination controls.
@@ -58,15 +56,15 @@ class CustomPagination(PageNumberPagination):
self.page = []
return list(self.page)
def get_paginated_response(self, data):
code = 2000
msg = 'success'
page =int(self.get_page_number(self.request, paginator)) or 1
total=self.page.paginator.count if self.page else 0
limit= int(self.get_page_size(self.request)) or 10
is_next= self.page.has_next()
is_previous= self.page.has_previous()
data=data
page = int(self.get_page_number(self.request, paginator)) or 1
total = self.page.paginator.count if self.page else 0
limit = int(self.get_page_size(self.request)) or 10
is_next = self.page.has_next() if self.page else False
is_previous = self.page.has_previous() if self.page else False
if not data:
code = 2000
@@ -78,8 +76,8 @@ class CustomPagination(PageNumberPagination):
('msg', msg),
('page', page),
('limit', limit),
('total',total),
('is_next',is_next),
('total', total),
('is_next', is_next),
('is_previous', is_previous),
('data', data)
]))

View File

@@ -26,6 +26,7 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
# 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖
modifier_field_id = "modifier"
modifier_name = serializers.SerializerMethodField(read_only=True)
dept_belong_id = serializers.IntegerField(required=False, allow_null=True)
def get_modifier_name(self, instance):
if not hasattr(instance, "modifier"):

View File

@@ -6,18 +6,20 @@
@Created on: 2021/6/1 001 22:57
@Remark: 自定义视图集
"""
import uuid
from django.db import transaction
from django_filters import DateTimeFromToRangeFilter
from django_filters.rest_framework import FilterSet
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.viewsets import ModelViewSet
from dvadmin.utils.filters import DataLevelPermissionsFilter
from dvadmin.utils.filters import DataLevelPermissionsFilter, CoreModelFilterBankend
from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSerializerMixin
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
from dvadmin.utils.permission import CustomPermission
from dvadmin.utils.models import get_custom_app_models, CoreModel
from dvadmin.system.models import FieldPermission, MenuField
from django_restql.mixins import QueryArgumentsMixin
@@ -37,7 +39,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
update_serializer_class = None
filter_fields = '__all__'
search_fields = ()
extra_filter_class = [DataLevelPermissionsFilter]
extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionsFilter]
permission_classes = [CustomPermission]
import_field_dict = {}
export_field_label = {}
@@ -63,12 +65,34 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs.setdefault('context', self.get_serializer_context())
# 全部以可见字段为准
can_see = self.get_menu_field(serializer_class)
# 排除掉序列化器级的字段
# sub_set = set(serializer_class._declared_fields.keys()) - set(can_see)
# for field in sub_set:
# serializer_class._declared_fields.pop(field)
# if not self.request.user.is_superuser:
# serializer_class.Meta.fields = can_see
# 在分页器中使用
self.request.permission_fields = can_see
if isinstance(self.request.data, list):
with transaction.atomic():
return serializer_class(many=True, *args, **kwargs)
else:
return serializer_class(*args, **kwargs)
def get_menu_field(self, serializer_class):
"""获取字段权限"""
finded = False
for model in get_custom_app_models():
if model['object'] is serializer_class.Meta.model:
finded = True
break
if finded is False:
return []
return MenuField.objects.filter(model=model['model']
).values('field_name', 'title')
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, request=request)
serializer.is_valid(raise_exception=True)

0
backend/logs/__init__.py Normal file
View File

View File

@@ -1,31 +1,31 @@
Django==4.1.5
Django==4.2.7
django-comment-migrate==0.1.7
django-cors-headers==3.13.0
django-filter==21.1
django-cors-headers==4.3.0
django-filter==23.3
django-ranged-response==0.2.0
djangorestframework==3.14.0
django-restql==0.15.3
django-simple-captcha==0.5.17
django-timezone-field==5.0
djangorestframework-simplejwt==5.2.2
drf-yasg==1.21.4
mysqlclient==2.1.1
pypinyin==0.48.0
ua-parser==0.16.1
pyparsing==3.0.9
openpyxl==3.0.10
requests==2.28.2
loguru==0.6.0
typing-extensions==4.4.0
smmap==5.0.0
tzlocal==4.1
channels==4.0.0
channels-redis==4.0.0
websockets==10.4
django-simple-captcha==0.5.20
django-timezone-field==6.0.1
djangorestframework-simplejwt==5.3.0
drf-yasg==1.21.7
mysqlclient==2.2.0
pypinyin==0.49.0
ua-parser==0.18.0
pyparsing==3.1.1
openpyxl==3.1.2
requests==2.31.0
typing-extensions==4.8.0
tzlocal==5.1
channels==3.0.5
channels-redis==4.1.0
websockets==11.0.3
user-agents==2.2.0
six==1.16.0
whitenoise==6.3.0
psycopg2==2.9.5
uvicorn==0.20.0
gunicorn==20.1.0
gevent==22.10.2
whitenoise==6.6.0
psycopg2==2.9.9
uvicorn==0.23.2
gunicorn==21.2.0
gevent==23.9.1
Pillow==10.1.0
dvadmin-celery==1.0.5

View File

@@ -0,0 +1,34 @@
html,body{width:100%;overflow-x: hidden;margin: 0;padding: 0;font-size:1rem;font-family: "PingFang SC","SF Pro SC","SF Pro Text","SF Pro Icons","Helvetica Neue","Helvetica","Arial",sans-serif;
-webkit-font-smoothing: antialiased;}
a{text-decoration:none;}
ul,li{list-style:none;padding:0;margin:0;}
.bg{background-repeat:no-repeat;background-position:center center;background-size:100%;}
.center{margin:0 auto;position:relative;}
.abs_center{position:absolute;left: 50%;transform: translateX(-50%);-webkit-transform:translateX(-50%);}
/*滚动条统一样式*/
.scrollbar{overflow-x:hidden;scrollbar-track-color:none;}
.scrollbar::-webkit-scrollbar{width:8px;background-color:transparent;} /*滚动条整体部分*/
.scrollbar::-webkit-scrollbar-track{border-radius:4px;-webkit-box-shadow: inset 0 0 0 rgba(0,0,0,0);background-color:transparent;display:none;} /* 滚动条的轨道*/
.scrollbar::-webkit-scrollbar-track-piece{background-color:transparent;display:none;}/* 滚动条的内层轨道*/
.scrollbar::-webkit-scrollbar-thumb{border-radius:4px;background: #313131;}/*滚动条里面的小方块,能向上向下移动(或往左往右移动,取决于是垂直滚动条还是水平滚动条)*/
h3,h4,h5{ font-weight: bold;}
h3{ font-size: .4rem; color:#000;}
h4{ font-size: .35rem; color: #000; margin:.2rem 0 0 0;}
p{ line-height: .5rem; margin:.2rem 0;word-break: break-all;text-align:justify;}
.app .main{width:90%;overflow:hidden;font-size:.30rem;box-sizing: border-box; margin: 0 auto; color: #484d57;}
.app ul { padding-left: .1rem; font-size: .26rem;}
.app ul p{ line-height: .4rem;}
.app .mainbg{position:absolute;left:50%;transform:translateX(-50%);background-repeat:no-repeat;background-size:100%;}
.app .poster{width:100%;height:31.85rem;background-image:url(../images/poster_bg.jpg);}
.pc .main{font-size:.16rem;}

View File

@@ -0,0 +1,233 @@
<!DOCTYPE html>
<html data-dpr="1" style="font-size: 50px;">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>隐私政策</title>
<link href="/static/clause/privacy.css" rel="stylesheet">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
</head>
<body class="app" style="font-size: 0.28rem;">
<div class="main">
<h3>隐私政策</h3>
<br>更新日期2024年01月01日
<br>生效日期2024年01月01日
<br>
<br>欢迎您使用DvAdmin产品DvAdmin非常重视用户的个人信息和隐私保护为同时给您提供更准确有个性化的服务和更安全的互联网环境我们依据中华人民共和国网络安全法信息安全技术个人信息安全规范GB/T35273-2017以及其他相关法律法规和技术规范制定了隐私政策阐述了关于您个人信息的相关权利等
<br>
<br>本政策适用于我们的所有DvAdmin系列产品及服务本政策与您所使用的我们的产品与/或服务息息相关您在下载安装启动浏览注册登录使用巨梦科技的产品及/或服务时我们将按照本政策的约定处理和保护您的个人信息我们将尽量以简明扼要的表述向您解释本政策所涉及的技术词汇以便于您理解
<br>
<br>您在使用DvAdmin的产品及/或服务时巨梦科技可能会收集和使用您的相关信息为此希望通过本隐私政策向您说明巨梦科技如何取得使用保存和管理这些信息请在使用/继续使用我们的各项产品与服务前仔细阅读并充分理解本政策特别应重点阅读我们以粗体/粗体下划线标识的条款并在需要时按照本政策的指引作出适当的选择如果您不同意本政策的内容将可能导致我们的产品与/或服务无法正常运行或者无法达到我们拟达到的服务效果您应立即停止访问/使用我们的产品与/或服务您使用或继续使用我们提供的产品与/或服务的行为都表示您充分理解和同意本隐私政策的全部内容
<br>
<br>隐私政策将帮助您了解以下内容
<br><a href="#1_chapter">第一章 如何收集您的个人信息</a>
<br><a href="#2_chapter">第二章 如何使用您的个人信息</a>
<br><a href="#3_chapter">第三章 如何使用Cookie和同类技术</a>
<br><a href="#4_chapter">第四章 如何共享转让公开披露您的个人信息</a>
<br><a href="#5_chapter">第五章 如何保护您的个人信息</a>
<br><a href="#6_chapter">第六章 如何保存您的个人信息</a>
<br><a href="#7_chapter">第七章 管理查看或删除您的个人信息</a>
<br><a href="#8_chapter">第八章 如何处理儿童的个人信息</a>
<br><a href="#9_chapter">第九章 本政策如何更新</a>
<br><a href="#10_chapter">第十章 投诉及建议</a>
<br><a href="#11_chapter">第十一章 其他</a>
<br>
<h1 id="1_chapter">第一章 如何收集您的个人信息</h1>
<br>
<br>在您使用我们的产品/服务时我们需要/可能需要收集和使用的您的个人信息包括如下两种
<br>第一种为实现向您提供我们产品及/或服务的基本功能您须授权我们收集使用的必要的信息如您拒绝提供相应信息您将无法正常使用我们的产品及/或服务
<br>第二种为实现向您提供我们产品及/或服务的附加功能您可选择单独同意或不同意我们收集使用的信息如您拒绝提供您将无法正常使用相关附加功能或无法达到我们拟达到的功能效果不会影响您使用我们的基本功能
<br>
<br>个人敏感信息是指一旦泄露非法提供或滥用可能危害人身和财产安全极易导致个人名誉身心健康受到损害或歧视性待遇等的个人信息如个人财产信息个人健康生理信息个人生物识别信息个人身份信息网络身份标识信息及其他信息
<br>上述信息所包含的内容均出自于GB/T35273信息安全技术 个人信息安全规范
<br>
<h3>1.1 您向巨梦科技提供相关个人信息</h3>
<h4>1.1.1
为使用巨梦科技的产品及/或服务您首先需要注册账号并登录您可以选择通过微信等方式创建巨梦科技产品及/或服务的账号并登录"账号登录"</h4>
<h5>1.1.1.1 通过微信第三方账户登录巨梦科技产品及/或服务时您同意我们从微信获取您授权共享的信息包括手机号位置信息昵称头像等
并且您同意将您的第三方账户与您的巨梦科技产品及/或服务账号绑定使您可以通过第三方账户直接登录并使用巨梦科技的产品及/或服务并借助该账户实现数据在不同设备之间的同步
我们会依据与第三方的约定并在符合相关法律法规的前提下使用您授权共享的个人信息如果您拒绝同意巨梦科技从第三方获取您授权共享的信息您将无法通过微信的方式注册账号并登录也无法借助该第三方账户实现数据的同步</h5>
<h4>1.1.2 在您使用巨梦科技产品及/或服务时您可能会因账号管理产品使用等问题联系巨梦科技客服为此您可能需要向巨梦科技提供姓名手机号码系统操作记录照片银行账号第三方支付平台的账号
与巨梦科技进行联系的通信记录及内容等与您使用巨梦科技产品及/或服务相关的信息以便巨梦科技的客服人员您联系并帮助您解决相关问题
如果您拒绝提供相关信息巨梦科技可能无法帮助您解决相关问题</h4>
<h3>1.2 巨梦科技收集您所使用的相关设备及网络信息</h3>
<h4>1.2.1
当您使用巨梦科技的产品及/或服务时巨梦科技将收集您所使用的相关设备信息以便为您提供持续稳定的服务支持使您在使用巨梦科技的产品及/或服务过程中获得最优体验巨梦科技收集的您所使用的相关设备信息包括浏览器信息网络信息IP地址日志信息如操作日志服务日志等等</h4>
<h3>1.3 巨梦科技向您收集的信息</h3>
<h4>1.3.1
为提升巨梦科技产品及/或服务的使用体验和便利程度巨梦科技可能会通过调用设备权限的方式收集您的如下个人信息如果您不同意提供这些个人信息您可能无法使用与提升用户体验相关的功能您也可能需要在使用巨梦科技产品及/或服务过程中手动重复填写一些信息</h4>
<h4>1.3.2
若您希望向巨梦科技上传您的头像及/或其他图片信息您可以选择授权巨梦科技调用您所使用设备中的摄像头或者相册权限</h4>
<h4>1.3.3
若您希望快速便捷地通过第三方移动运营商完成支付在您的设备支持的情况下您可以选择授权调用您所使用设备中的移动运营相关权限如短信以便您快速便捷地完成付款</h4>
<h4>1.3.4
若您希望实现或体验上述功能您可能需要在您的设备中向巨梦科技开启您的相应访问权限您也可以随时选择关闭相应访问权限以取消相应授权在不同设备中权限显示方式及关闭方式可能有所不同具体请参考设备及系统开发方的说明或指引</h4>
<h3>1.4 第三方向您收集的信息</h3>
<h4>1.4.1 第三方付款服务提供商收集相关个人信息</h4>
<h5>1.4.1.1
您可以通过向巨梦科技付款的方式获取巨梦科技产品及/或服务相关的产品上述付款服务可能由巨梦科技以外的第三方提供巨梦科技并不会获取与您付款相关的密码等信息但可能从您或第三方支付平台获得您的第三方支付账户微信账号若您希望了解第三方服务提供商收集个人信息的具体规则请查阅您选择的第三方付款服务提供商的隐私政策</h5>
<h3>1.5 您充分知晓在以下情形下我们收集使用个人信息无需征得您的授权同意</h3>
<h4>1.5.1 与国家安全国防安全有关的</h4>
<h4>1.5.2 与公共安全公共卫生重大公共利益有关的</h4>
<h4>1.5.3 与犯罪侦查起诉审判和判决执行等有关的</h4>
<h4>1.5.4 出于维护个人信息主体或其他个人的生命财产等重大合法权益但又很难得到本人同意的</h4>
<h4>1.5.5 所收集的个人信息是个人信息主体自行向社会公众公开的</h4>
<h4>1.5.6 从合法公开披露的信息中收集的您的个人信息的如合法的新闻报道政府信息公开等渠道</h4>
<h4>1.5.7 根据您要求签订和履行合同所必需的</h4>
<h4>1.5.8 用于维护所提供的产品及/或服务的安全稳定运行所必需的例如发现处置产品及/或服务的故障</h4>
<h4>1.5.9 法律法规规定的其他情形</h4>
<h3>1.6
根据法律规定向第三方提供经去标识化处理的个人信息且确保数据接收方无法复原并重新识别个人信息主体的不属于个人信息的对外共享转让及公开披露行为对此类数据的保存及处理将无需另行向您通知并征得您的同意</h3>
<br>
<h1 id="2_chapter">第二章 如何使用您的个人信息</h1>
<br>
<h2>在收集您的个人信息后巨梦科技将根据如下规则使用您的个人信息</h2>
<br>
<h3>2.1 会根据本隐私政策的约定并为实现巨梦科技的产品及/或服务功能对所收集的个人信息进行使用</h3>
<h3>2.2
请您注意对于您在使用巨梦科技的产品及/或服务时所提供的所有个人信息除非您删除或通过相关设置拒绝我们收集否则您将在使用产品及/或服务期间账号注销前持续授权我们使用</h3>
<h3>2.3
巨梦科技系列产品或服务对使用情况进行统计并可能会与公众或第三方共享这些统计信息以展示巨梦科技产品及/或服务的整体使用趋势但这些统计信息不会包含您的任何身份识别信息</h3>
<h3>2.4
当巨梦科技要将您的个人信息用于本政策未载明的其它用途时或将基于特定目的收集而来的信息用于其他目的时巨梦科技会主动事先征求您的明示同意</h3>
<br>
<h1 id="3_chapter">第三章 如何使用Cookie和同类技术</h1>
<h2>巨梦科技暂未使用任何信息收集工具</h2>
<br>
<h1 id="4_chapter">第四章 如何共享转让公开披露您的个人信息</h1>
<h2>共享</h2>
<h3>4.1 巨梦科技将严格遵守相关法律法规对您的个人信息予以保密除以下情况外我们不会向其他人共享您的个人信息</h3>
<h4>4.1.1 事先获得您明确的同意或授权</h4>
<h4>4.1.2 根据适用的法律法规规定或基于司法或行政主管部门的强制性要求进行提供</h4>
<h4>4.1.3
在法律法规允许的范围内为维护您或其他巨梦科技用户或其他个人的生命财产等合法权益或是社会公共利益而有必要提供</h4>
<h4>4.1.4 应您的监护人的合法要求而提供您的信息</h4>
<h4>4.1.5 根据与您签署的相关协议包括在线签署的电子协议以及相应的平台规则或其他的法律文件约定而提供</h4>
<h4>4.1.6 根据本隐私政策所述与第三方进行共享</h4>
<h4>4.1.7
巨梦科技可能会基于您的相应授权将您的个人信息与公司的关联方共享但巨梦科技只会共享必要的个人信息且受本隐私政策所述目的之约束 如果巨梦科技的关联方要改变个人信息的处理目的将适时向您征得明示同意</h4>
<h3>4.2
对巨梦科技与之共享个人信息的公司组织和个人我们将尽合理的努力要求其处理您的个人信息时遵守相关法律法规尽力要求其采取相关的保密和安全措施以保障您的个人信息安全</h3>
<h2>转让</h2>
<h3>4.3 巨梦科技不会将您的个人信息转让给任何公司组织和个人但以下情况除外</h3>
<h4>4.3.1 事先获得您明确的同意或授权</h4>
<h4>4.3.2 根据适用的法律法规法律程序的要求强制性的行政或司法要求而必须进行提供</h4>
<h4>4.3.3
涉及收购兼并破产清算重组等重大变更时如涉及到个人信息转让的巨梦科技会要求新的持有您个人信息的公司或组织继续履行本隐私政策项下的责任和义务如变更后的主体需变更个人信息使用目的我们会要求其事先获得您的明示同意</h4>
<h2>公开披露</h2>
<h3>4.4 巨梦科技仅会在以下情况下且在采取符合业界标准的安全防护措施的前提下才会公开披露您的个人信息</h3>
<h4>4.4.1根据您的需求在您明确同意的披露方式下披露您所指定的个人信息</h4>
<h4>4.4.2
根据法律法规的要求行政或司法机关的强制性要求我们可能会公开披露您的个人信息当巨梦科技收到上述披露请求时巨梦科技会依法要求请求方出具相关法律文件如传票或协助调查函等巨梦科技会慎重审查每一个披露请求以确保该等披露请求符合相关法律规定在法律法规许可的前提下巨梦科技会对包含披露信息的文件采取加密保护等措施</h4>
<br>
<h1 id="5_chapter">第五章 如何保护您的个人信息</h1>
<h3>5.1
巨梦科技非常重视个人信息安全并会采取一切合理可行的措施持续保护您的个人信息以防其他人在未经授权的情况下访问篡改或披露巨梦科技收集的您的个人信息</h3>
<h4>5.1.1
巨梦科技已采用符合行业标准的安全防护措施来保护您的个人信息防止数据遭到未经授权的访问公开披露使用修改损坏或丢失我们会采取一切合理可行的措施保护您的个人信息我们会使用受信赖的保护机制防止数据遭到恶意攻击</h4>
<h4>5.1.2
巨梦科技仅允许有必要知晓的人员访问相关个人信息并为此设置了严格的访问权限控制和监控机制巨梦科技同时要求可能接触到您个人信息的所有人员履行相应的保密义务如果未能履行这些义务可能会被追究法律责任或被终止与巨梦科技的相应法律关系</h4>
<h3>5.2 巨梦科技会采取一切合理可行的措施确保未收集无关的信息</h3>
<h3>5.3
您理解由于技术的限制以及可能存在的各种恶意手段在互联网行业即便我们竭尽所能加强安全措施但也不可能始终保证信息百分之百的绝对安全您需要了解和知悉您接入巨梦科技的产品及/或服务所用的系统和通讯网络有可能因我们可控范围外的其他因素而出现问题在此情形下我们会依法尽力协助解决</h3>
<h3>5.4
如不幸发生信息安全事故我们将按照法律法规的要求及时向您告知安全事件的基本情况和可能的影响我们已采取或将要采取的处置措施您可自主防范和降低风险的建议等我们同时将及时将事件相关情况以邮件信函电话推送通知等适合的方式告知您难以逐一告知信息主体时我们会采取合理有效的方式发布公告同时我们还将按照监管部门要求主动上报信息安全事件的处置情况</h3>
<br>
<h1 id="6_chapter">第六章 如何保存您的个人信息</h1>
<h2>保存期限</h2>
<h3>6.1 在用户使用巨梦科技产品及/或服务期间巨梦科技会持续保存用户的个人信息</h3>
<h3>6.2
巨梦科技承诺始终按照法律的规定在合理必要期限内在存储您个人信息对于日志信息记录备份等信息为您注销账号后180天但根据法律规定有最短保存期限要求的则我们会保存法律要求的最短期限交易信息为交易完成日起三年或您注销账号后180天以较长者为准在超出上述期限后我们会对您的相关信息进行删除或匿名化处理</h3>
<h2>保存地域</h2>
<h3>6.3 您的个人信息将全部被存储于中华人民共和国境内</h3>
<h3>6.4 目前我们不存在向境外提供个人信息的场景</h3>
<br>
<h1 id="7_chapter">第七章 管理查看或删除您的个人信息</h1>
<h2>巨梦科技非常尊重您对自己的个人信息所享有的权利我们保障您对个人信息所享有的访问更正删除管理等权利</h2>
<br>
<h2>访问和更正您的个人信息</h2>
<h3>7.1
除法律法规另有规定之外您有权行使数据访问权当您发现我们处理关于您的个人信息有错误或者您有其他修改补充需求时您也有权要求我们或自行予以更正您行使数据访问和更正权的方式包括但不限于</h3>
<h4>7.1.1
如果您希望访问或修改您在巨梦科技产品及/或服务中的账号信息包括头像昵称性别生日等您可以通过访问我们的产品及/或服务进行操作可通过点击我的功能后进行操作</h4>
<h4>7.1.2
如巨梦科技的产品及/或服务中提供发表话题参与讨论留言等功能并使得您有机会通过这些服务公开或提供的个人信息的则巨梦科技在提供前述服务的同时会提供相应的功能确保您可以再次访问或删除您在前述服务过程中公开或提供的个人信息</h4>
<h3>7.2
您有权知悉通过巨梦科技获得您的个人信息的第三方的身份或类型您可以通过本政策第一章和第四章了解第三方的身份或类型</h3>
<br>
<h2>删除您的个人信息及撤回已同意的授权</h2>
<h3>7.3 在以下情形中您可以向巨梦科技提出删除个人信息的请求</h3>
<h4>7.3.1 如果巨梦科技处理个人信息的行为违反相关的法律法规</h4>
<h4>7.3.2 如果巨梦科技收集使用您的个人信息却未征得您的同意</h4>
<h4>7.3.3 如果巨梦科技处理个人信息的行为违反了与您的约定或法律的规定</h4>
<h4>7.3.4 如果您不再使用巨梦科技的产品及/或服务或者您注销了相关账号</h4>
<h4>7.3.5 如果巨梦科技不再为您提供产品及/或服务</h4>
<h3>7.4
您有权向巨梦科技撤回您此前作出的有关同意收集使用您的个人信息的授权当您撤回同意后我们将不再处理您的相关个人信息但您撤回同意的决定不会影响此前基于您的授权而开展的个人信息处理活动</h3>
<h3>7.5 您可以通过删除相关个人信息的方式撤回您此前就特定个人信息而对我们作出的同意授权</h3>
<h2>注销账号</h2>
<h3>7.6
您有权随时提出申请注销您在巨梦科技产品及/或服务中注册的账号为保障账号及财产安全您需要通过客户服务或本隐私条款第十章载明的联系方式向巨梦科技提出您的账号注销请求巨梦科技将在与您核实相关信息后的15个工作日内为您注销账号</h3>
<h3>7.7
请您注意注销巨梦科技相关产品及/或服务账号是不可恢复的操作在注销账号之后我们将停止为您提供产品及/或服务并将删除该账号项下的您的个人信息除非法律法规另有规定</h3>
<h2>约束信息系统自动决策</h2>
<h3>7.8
在巨梦科技仅依据信息系统算法等在内的非人工自动决策机制做出决定并且这些决定显著影响您的合法权益的情况下您有权要求巨梦科技做出解释巨梦科技也将提供适当的救济方式</h3>
<h2>获取个人信息副本</h2>
<h3>7.9
根据您的请求巨梦科技可以向您提供巨梦科技持有的有关您的个人信息副本如个人基本资料您可以通过客户服务或本隐私条款第十章载明的联系方式向我们提出请求</h3>
<h2>响应您的上述请求</h2>
<h3>
7.10如果您对巨梦科技在以上列明的有关访问更正删除您的个人信息以及撤回同意注销账号约束信息系统自动决策方法有任何疑问您可以通过客户服务或本隐私政策第十章载明的联系方式与我们取得联系</h3>
<h3>7.11
对于您合理的请求巨梦科技原则上不收取费用但对多次重复超出合理限度的请求巨梦科技将视情况收取一定成本费用对于那些无端重复需要过多技术手段给他人合法权益带来风险或者非常不切实际的请求巨梦科技可能会予以拒绝</h3>
<h3>7.12 在以下情形下我们可能无法响应您的请求</h3>
<h4>7.12.1 与国家安全国防安全有关的</h4>
<h4>7.12.2 与公共安全公共卫生重大公共利益有关的</h4>
<h4>7.12.3 与犯罪侦查起诉和审判等有关的</h4>
<h4>7.12.4 有证据表明您存在主观恶意或滥用权利的</h4>
<h4>7.12.5 响应您的请求将导致其他个人组织的合法权益受到严重损害的</h4>
<h4>7.12.6 涉及商业秘密的</h4>
<h2>申诉机制</h2>
<h3>7.13
巨梦科技已经建立申诉管理机制包括跟踪流程等为了保障账号及财产安全巨梦科技可能会与您核实相关信息巨梦科技将在收到您的反馈后尽快受理并处理您的请求最长不超过15个工作日若您对答复意见不满意您可以再次通过客户服务进行申诉申诉的联系方式及相关具体信息请详见第十章</h3>
<br>
<br>
<h1 id="8_chapter">第八章 儿童信息的保护</h1>
<h3>8.1
我们的产品及服务主要面向成人巨梦科技非常重视对未成年人信息的保护如果您是未满18周岁的未成年人应在监护人监护指导并获得监护人同意情况下仔细阅读本隐私政策和使用巨梦科技的产品及/或服务</h3>
<h3>8.2 如果您/您的监护人不同意本隐私政策的任何内容您应该立即停止使用我们的产品及/或服务</h3>
<h3>8.3
若您是未成年人的监护人当您对您所监护的未成年人使用我们的产品及/或服务或其向我们提供的用户信息有任何疑问时请您及时与我们联系我们将根据国家相关法律法规及本政策的规定保护未成年人用户信息的保密性及安全性如果我们发现自己在未事先获得可证实的监护人同意的情况下收集了未成年人的个人信息则会设法尽快删除相关数据</h3>
<br>
<h1 id="9_chapter">第九章 本政策如何更新</h1>
<h3>9.1 如巨梦科技产品及/或服务发生以下变化巨梦科技将及时对本隐私政策进行相应的修订</h3>
<h4>9.1.1 巨梦科技产品及/或服务所涉业务功能发生变更导致处理个人信息的目的类型使用方式发生变更</h4>
<h4>9.1.2 您参与个人信息处理方面的权利及其行使方式发生重大变化</h4>
<h4>9.1.3 巨梦科技负责处理您的个人信息安全的部门的联络方式发生变更</h4>
<h4>9.1.4 发生其他可能影响用户个人信息安全或影响用户隐私权利的变更等</h4>
<h3>9.2
隐私政策修订后巨梦科技会在巨梦科技产品及/或服务相关界面发布最新版本并以弹窗推送通知等合理的方式告知用户以便用户及时了解隐私政策的最新版本</h3>
<h3>9.3 未经您的明确同意巨梦科技不会削减您基于本隐私政策所享有的权利</h3>
<h3>9.4 如无特殊说明修订后的隐私政策自公布之日起生效</h3>
<br>
<h1 id="10_chapter">第十章 投诉及建议</h1>
<h3>10.1
您在使用巨梦科技产品及/或服务的过程中如果对本隐私政策有任何的疑义或建议或您认为您的个人信息没有得到本隐私政策规定的保护您可以通过以下方式联系我们我们将真诚地处理您的投诉及建议</h3>
<h4>公司名称北京巨梦科技信息技术有限公司</h4>
<h4>联系邮箱qiangli@django-vue-admin.com</h4>
<h4>微信公众号巨梦科技9:00-22:00</h4>
<br>
<h1 id="11_chapter">第十一章 其他</h1>
<h3>11.1 因本政策以及我们处理您个人信息事宜引起的任何争议您可诉至有管辖权的人民法院</h3>
<h3>11.2 如果您认为我们的个人信息处理行为损害了您的合法权益您也可向有关政府部门进行反映</h3>
<h3>11.3
在巨梦科技发布本隐私政策或向您提供产品及/或服务均视为本隐私政策生效巨梦科技停止运营或永久停止提供产品及/或服务时本隐私政策失效</h3>
<br>
</div>
</body>
</html>

View File

@@ -0,0 +1,96 @@
<!DOCTYPE html>
<html data-dpr="1" style="font-size: 50px;">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用户服务协议</title>
<link href="/static/clause/privacy.css" rel="stylesheet">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
</head>
<body class="app" style="font-size: 0.28rem;">
<div class="main">
<h3>服务使用协议</h3>
<br>更新日期2024年01月01日
<br>生效日期2024年01月01日
<br>DvAdmin服务使用协议是您下称"用户"与北京巨梦科技科技有限公司之间在使用其DvAdmin产品中注册或登录时签署的协议巨梦科技产品包括django-vue-admin产品请仔细阅读以下条款点击同意按钮即表示对相关条款的同意如果您阅读这份协议后有任何疑问或意见请与巨梦科技客服人员取得联系联系邮箱qiangli@django-vue-admin.cn微信公众号巨梦科技9:00-18:00
<br>
<h2>重要须知</h2>
<h3>
1用户应认真阅读未成年人应当在监护人陪同下阅读充分理解本协议中各条款除非用户接受本协议用户应当立即停止注册及使用行为</h3>
<h3>2用户在进行注册程序过程中点击同意按钮即表示用户完全接受本协议项下的全部条款</h3>
<br>
<h2>服务内容</h2>
<h3>1巨梦科技服务的具体内容由巨梦科技根据实际情况提供</h3>
<h3>2用户理解巨梦科技仅提供软件相关服务
除此之外与相关软件服务有关的设备如手机个人电脑及其他与接入互联网或移动网有关的装置及所需的费用如为接入互联网而支付的电话费及上网费为使用移动网而支付的手机费均应由用户自行负担</h3>
<h3>
3用户同意巨梦科技为本协议履行或相关研究的目的收集使用或授权第三方且该等第三方同意承担与巨梦科技同等的个人信息保护义务非商业地使用通过服务获取的用户信息包括但不限于身份信息性别职业兴趣爱好等资料巨梦科技承诺除非另行取得用户的事先同意否则巨梦科技不会将用户信息授权给与履行本协议无关的第三方用于商业目的</h3>
<h3>5根据相关法律法规及国家标准在以下情形中巨梦科技可能会依法收集并使用用户的个人信息并且无需征得用户的同意:</h3>
<h4>1与国家安全国防安全直接相关的</h4>
<h4>2与公共安全公共卫生重大公共利益直接相关的</h4>
<h4>3与犯罪侦查起诉审判和判决执行等直接相关的</h4>
<h4>4出于维护用户或他人的生命财产等重大合法权益但又很难得到用户本人同意的</h4>
<h4>5所收集的个人信息是用户自行向社会公众公开的</h4>
<h4>6从合法公开披露的信息中收集个人信息例如合法的新闻报道政府信息公开等渠道</h4>
<h4>7根据用户的要求签订和履行合同所必需的</h4>
<h4>8用于维护所提供的服务的安全稳定运行所必需的例如发现处置产品或服务的故障</h4>
<h4>9为合法的新闻报道所必需的</h4>
<h4>
10学术研究机构基于公共利益开展统计或学术研究所必要且对外提供学术研究或描述的结果时对结果中所包含的个人信息进行去标识化处理的</h4>
<h4>11法律法规规定的其他情形</h4>
<h3>6在以下情形中巨梦科技向第三方提供用户的个人信息无需事先征得用户的授权同意</h3>
<h4>1与国家安全国防安全有关的</h4>
<h4>2与公共安全公共卫生重大公共利益有关的</h4>
<h4>3与犯罪侦查起诉审判和判决执行等有关的</h4>
<h4>4出于维护用户或其他个人的生命财产等重大合法权益但又很难得到本人同意的</h4>
<h4>5用户自行向社会公众公开的个人信息</h4>
<h4>6从合法公开披露的信息中收集个人信息的如合法的新闻报道政府信息公开等渠道</h4>
<h3>
根据法律规定向第三方提供经去标识化处理的个人信息且确保数据接收方无法复原并重新识别个人信息主体的不属于个人信息的对外共享转让及公开披露行为对此类数据的保存及处理将无需另行向用户通知并征得用户的同意</h3>
<h3>本协议中涉及巨梦科技对于用户提供的个人信息的使用如与隐私政策不符的隐私政策为准</h3>
<br>
<h2>用户使用规则</h2>
<h3>1用户授权微信登录成功后用户有权使用巨梦科技软件功能包括参与活动终端管理经销商业务管理等</h3>
<h3>2用户有权使用巨梦科技提供的功能进行照片上传分享个人信息</h3>
<h3>3用户在使用巨梦科技时须遵守国家相关法律法规内容不得包含有下列内容之一的信息</h3>
<h4>a) 反对宪法所确定的基本原则的</h4>
<h4>b) 危害国家安全泄露国家秘密颠覆国家政权破坏国家统一的</h4>
<h4>c) 损害国家荣誉和利益的</h4>
<h4>d) 煽动民族仇恨民族歧视破坏民族团结的</h4>
<h4>e) 破坏国家宗教政策宣扬邪教和封建迷信的</h4>
<h4>f) 散布谣言扰乱社会秩序破坏社会稳定的</h4>
<h4>g) 散布淫秽色情赌博暴力凶杀恐怖或者教唆犯罪的</h4>
<h4>h) 侮辱或者诽谤他人侵害他人合法权利的</h4>
<h4>i) 含有虚假有害胁迫侵害他人隐私骚扰侵害中伤粗俗猥亵或其它道德上令人反感的内容</h4>
<h4>j) 含有中国法律法规规章条例以及任何具有法律效力之规范所限制或禁止的其它内容的</h4>
<h4>k) 含有巨梦科技认为不利于巨梦科技运营的内容</h4>
<h3>
4用户保证在使用巨梦科技时发布传播的信息的真实性准确性保证不得发布谣言或其他与事实不符引起他人不适的言论信息</h3>
<h3>
5用户保证在使用巨梦科技不得发布传播侵犯知识产权或其他合法权益的信息用户传播内容中涉及第三方拥有知识产权内容的相关授权或其他内容的合法性由用户自行负责巨梦科技对相关内容的知识产权权属或是否侵犯他人民事权益等情况不进行审查或监督但将按相关法规对涉嫌侵权的内容履行删除或断开链接等职责</h3>
<h3>
6若用户发生前述3-6的行为时由用户承担所有的违法侵权责任若因此给巨梦科技造成任何损失巨梦科技有权向责任用户主张相关责任同时巨梦科技有权对违法侵权违规的用户终止提供服务如果政府或者司法机关要求巨梦科技告知进行侵权行为用户的具体信息的巨梦科技有权根据现行法规向其告知用户信息</h3>
<h3>
7用户同意巨梦科技有权在提供服务过程中以各种方式投放各种商业性广告或其他任何类型的商业信息并且用户同意接受巨梦科技通过电子邮件或其他方式向用户发送商品促销或其他相关商业信息</h3>
<h3>
8用户承诺提供的注册信息的真实性合法性有效性承担全部责任用户不得冒充他人不得利用他人的名义发布任何信息或享受巨梦科技供的任何服务不得恶意使用注册帐号导致其他用户误认否则巨梦科技有权立即停止提供服务收回其帐号并由用户独自承担由此而产生的一切法律责任</h3>
<br>
<h2>服务变更中断或终止</h2>
<h3>
1鉴于服务的特殊性用户同意巨梦科技有权随时变更中断或终止部分或全部的服务如变更中断或终止的服务属于免费服务巨梦科技无需通知用户也无需对任何用户或任何第三方承担任何责任</h3>
<br>
<h2>巨梦科技企业客户服务说明</h2>
<h3>
1巨梦科技平台提供给多家企业客户使用企业客户通过巨梦科技平台进行发布用户活动等如果功夫企火星企业用户违反了隐私政策或发生其它侵犯用户权益的行为用户可要求巨梦科技提供必要的配合与巨梦科技企业用户进行沟通和维权维权过程中产生的费用由用户自行承担</h3>
<br>
<h2>其他事宜</h2>
<h3>
1巨梦科技有权根据法律法规的变化网站以及相应的巨梦科技运营的需要不时地对本协议及本站的内容进行修改,并在网站以及巨梦科技醒目位置张贴修改后的协议一旦被张贴在本站上即生效,并代替原来的协议,用户可随时登录查阅最新协议用户有义务及时关注并阅读最新版的协议及网站公告如用户不同意更新后的协议可以立即向巨梦科技进行反馈且应立即停止接受巨梦科技依据本协议提供的服务如用户继续使用本站提供的服务的,即视为同意更新后的协议</h3>
<h3>2本协议自用户点击同意接受后生效</h3>
<h3>3本协议所有条款的标题仅为方便而设,不具法律或契约效果</h3>
<h3>4在法律允许范围内 本协议的解释权归巨梦科技所有</h3>
</div>
</body>
</html>

View File

@@ -13,6 +13,7 @@ services:
- ./docker_env/nginx/my.conf:/etc/nginx/conf.d/my.conf
expose:
- "8080"
restart: always
networks:
network:
ipv4_address: 177.10.0.11
@@ -23,9 +24,8 @@ services:
dockerfile: ./docker_env/django/Dockerfile
container_name: dvadmin3-django
working_dir: /backend
# 打开mysql 时,打开此选项
# depends_on:
# - dvadmin3-mysql
depends_on:
- dvadmin3-mysql
environment:
PYTHONUNBUFFERED: 1
DATABASE_HOST: dvadmin3-mysql
@@ -42,74 +42,70 @@ services:
network:
ipv4_address: 177.10.0.12
# dvadmin3-mysql:
# image: mysql:5.7
# container_name: dvadmin3-mysql
# #使用该参数container内的root拥有真正的root权限否则container内的root只是外部的一个普通用户权限
# #设置为true不然数据卷可能挂载不了启动不起
## privileged: true
# restart: always
# ports:
# - "3306:3306"
# environment:
# MYSQL_ROOT_PASSWORD: "123456"
# MYSQL_DATABASE: "dvadmin3_pro"
# TZ: Asia/Shanghai
# command:
# --wait_timeout=31536000
# --interactive_timeout=31536000
# --max_connections=1000
# --default-authentication-plugin=mysql_native_password
# volumes:
# - "./docker_env/mysql/data:/var/lib/mysql"
# - "./docker_env/mysql/conf.d:/etc/mysql/conf.d"
# - "./docker_env/mysql/logs:/logs"
# networks:
# network:
# ipv4_address: 177.10.0.13
dvadmin3-mysql:
image: mysql:8.0
container_name: dvadmin3-mysql
privileged: true
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: "DVADMIN3"
MYSQL_DATABASE: "django-vue3-admin"
TZ: Asia/Shanghai
command:
--wait_timeout=31536000
--interactive_timeout=31536000
--max_connections=1000
--default-authentication-plugin=mysql_native_password
volumes:
- "./docker_env/mysql/data:/var/lib/mysql"
- "./docker_env/mysql/conf.d:/etc/mysql/conf.d"
- "./docker_env/mysql/logs:/logs"
networks:
network:
ipv4_address: 177.10.0.13
# 如果使用celery 插件,请自行打开此注释
# dvadmin3-celery:
# build:
# context: .
# dockerfile: ./docker_env/celery/Dockerfile
# # image: django:2.2
# container_name: dvadmin3-celery
# working_dir: /backend
# depends_on:
# - dvadmin3-mysql
# environment:
# PYTHONUNBUFFERED: 1
# DATABASE_HOST: dvadmin3-mysql
# TZ: Asia/Shanghai
# volumes:
# - ./backend:/backend
# - ./logs/log:/var/log
# restart: always
# networks:
# network:
# ipv4_address: 177.10.0.14
dvadmin3-celery:
build:
context: .
dockerfile: ./docker_env/celery/Dockerfile
container_name: dvadmin3-celery
working_dir: /backend
depends_on:
- dvadmin3-mysql
environment:
PYTHONUNBUFFERED: 1
DATABASE_HOST: dvadmin3-mysql
TZ: Asia/Shanghai
volumes:
- ./backend:/backend
- ./logs/log:/var/log
restart: always
networks:
network:
ipv4_address: 177.10.0.14
# dvadmin3-redis:
# image: redis:6.2.6-alpine # 指定服务镜像最好是与之前下载的redis配置文件保持一致
# container_name: dvadmin3-redis # 容器名称
# restart: on-failure # 重启方式
# environment:
# - TZ=Asia/Shanghai # 设置时区
# volumes: # 配置数据卷
# - ./docker_env/redis/data:/data
# - ./docker_env/redis/redis.conf:/etc/redis/redis.conf
# ports: # 映射端口
# - "6379:6379"
# sysctls: # 设置容器中的内核参数
# - net.core.somaxconn=1024
# command: /bin/sh -c "echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf && redis-server /etc/redis/redis.conf --appendonly yes" # 指定配置文件并开启持久化
# privileged: true # 使用该参数container内的root拥有真正的root权限。否则container内的root只是外部的一个普通用户权限
# networks:
# network:
# ipv4_address: 177.10.0.15
dvadmin3-redis:
image: redis:6.2.6-alpine # 指定服务镜像最好是与之前下载的redis配置文件保持一致
container_name: dvadmin3-redis # 容器名称
restart: always
environment:
- TZ=Asia/Shanghai # 设置时区
volumes: # 配置数据卷
- ./docker_env/redis/data:/data
- ./docker_env/redis/redis.conf:/etc/redis/redis.conf
ports: # 映射端口
- "6379:6379"
sysctls: # 设置容器中的内核参数
- net.core.somaxconn=1024
command: /bin/sh -c "echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf && redis-server /etc/redis/redis.conf --appendonly yes --requirepass DVADMIN3" # 指定配置文件并开启持久化
privileged: true # 使用该参数container内的root拥有真正的root权限。否则container内的root只是外部的一个普通用户权限
networks:
network:
ipv4_address: 177.10.0.15
networks:

View File

@@ -4,9 +4,9 @@
~~~sh
# 编译打包到本地
docker build -f ./docker_env/web/DockerfileBuild -t registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-web:latest .
docker build -f ./docker_env/web/DockerfileBuild -t registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-web:16.19-alpine .
# 上传到阿里云仓库
docker push registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-web:latest
docker push registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-web:16.19-alpine
~~~

View File

@@ -1,6 +1,9 @@
FROM registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-backend:latest
WORKDIR /backend
COPY ./backend/ .
RUN ls ./conf/
RUN awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }'
RUN sed -i "s|DATABASE_HOST = "127.0.0.1"|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py
RUN sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py
RUN python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt
CMD ["/backend/docker_start.sh"]

View File

@@ -28,6 +28,6 @@ server {
proxy_send_timeout 600s;
real_ip_header X-Forwarded-For;
rewrite ^/api/(.*)$ /$1 break; #重写
proxy_pass http://178.10.0.12:8000/; # 设置代理服务器的协议和地址
proxy_pass http://177.10.0.12:8000/; # 设置代理服务器的协议和地址
}
}

View File

@@ -1,7 +1,7 @@
FROM node:16.19-alpine
FROM registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-web:16.19-alpine
WORKDIR /web/
COPY web/. .
RUN yarn install
RUN yarn install --registry=https://registry.npm.taobao.org
RUN yarn build
FROM nginx:alpine

View File

@@ -1,4 +1,4 @@
FROM node:16.19-alpine
WORKDIR /
COPY ./web/package.json .
RUN npm install -g yarn
RUN yarn install --registry=https://registry.npm.taobao.org

View File

@@ -1,6 +1,6 @@
# port 端口号
VITE_PORT = 8080
VITE_API_URL = 'http://dvadmin3api.django.icu:8001'
# open 运行 npm run dev 时自动打开浏览器
VITE_OPEN = false

View File

@@ -2,7 +2,7 @@
ENV = 'development'
# 本地环境接口地址
VITE_API_URL = 'http://127.0.0.1:8000/'
VITE_API_URL = 'http://127.0.0.1:8000'
# 是否启用按钮权限
VITE_PM_ENABLED = true

View File

@@ -2,7 +2,7 @@
ENV = 'production'
# 线上环境接口地址
VITE_API_URL = ''
VITE_API_URL = '/api' # docker-compose部署不需要修改nginx容器自动代理了这个地址
# 是否启用按钮权限
VITE_PM_ENABLED = true

2
web/.gitignore vendored
View File

@@ -1,7 +1,7 @@
.DS_Store
node_modules
/dist
yarn.lock
# local env files
.env.local

View File

@@ -1,6 +1,6 @@
# Django-Vue3-Admin
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-3.2-blue)](https://docs.djangoproject.com/zh-hans/3.2/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/liqianglog/django-vue-admin/badge/star.svg?theme=dark)](https://gitee.com/liqianglog/django-vue-admin)
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/huge-dream/django-vue3-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-3.2-blue)](https://docs.djangoproject.com/zh-hans/3.2/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/huge-dream/django-vue3-admin/badge/star.svg?theme=dark)](https://gitee.com/huge-dream/django-vue3-admin)
[preview](https://demo.dvadmin.com) | [Official website document](https://www.django-vue-admin.com) | [qq group](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [community](https://bbs.django-vue-admin.com) | [plugins market](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
@@ -23,7 +23,7 @@ Because of love, so embrace the future
## Online experience
👩‍👧‍👦👩‍👧‍👦 demo address:[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com)
👩‍👧‍👦👩‍👧‍👦 demo address:[https://demo.dvadmin.com](https://demo.dvadmin.com)
* demo accountsuperadmin
@@ -39,9 +39,9 @@ Because of love, so embrace the future
## source code url:
gitee(Main push)[https://gitee.com/liqianglog/django-vue-admin](https://gitee.com/liqianglog/django-vue-admin)👩‍👦‍👦
gitee(Main push)[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩‍👦‍👦
github[https://github.com/liqianglog/django-vue-admin](https://github.com/liqianglog/django-vue-admin)👩‍👦‍👦
githubno data
## core function
@@ -55,7 +55,7 @@ github[https://github.com/liqianglog/django-vue-admin](https://github.com/liq
8. 🧑‍🔧 Regional management: to manage provinces, cities, counties and regions.
9. 📁 Attachment management: Unified management of all files and pictures on the platform.
10. 🗓 operation logs: log and query the system normal operation; Log and query system exception information.
11.🔌 [plugins market] (<https://bbs.django-vue-admin.com/plugMarket.html>) : based on the Django framework - Vue - Admin application and plug-in development.
11.🔌 [plugins market] (<https://bbs.django-vue-admin.com/plugMarket.html>) : based on the Django framework - Vue - Admin application and plug-in development.
## plugins market 🔌
@@ -76,7 +76,7 @@ Redis(Optional, the latest edition)
```bash
# clone code
git clone https://gitee.com/liqianglog/django-vue-admin.git
git clone https://gitee.com/huge-dream/django-vue3-admin.git
# enter code dir
cd web

View File

@@ -1,8 +1,32 @@
<div align="center">django-vue3-admin:web </div>
# Django-Vue3-Admin
#### 🌈 介绍
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-3.2-blue)](https://docs.djangoproject.com/zh-hans/3.2/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/liqianglog/django-vue-admin/badge/star.svg?theme=dark)](https://gitee.com/liqianglog/django-vue-admin)
django-vue3-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!
[预 览](https://demo.dvadmin.com) | [官网文档](https://www.django-vue-admin.com) | [群聊](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [社区](https://bbs.django-vue-admin.com) | [插件市场](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
💡 **「关于」**
我们是一群热爱代码的青年在这个炙热的时代下我们希望静下心来通过Code带来一点我们的色彩和颜色。
因为热爱,所以拥抱未来!
## 平台简介
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
django-vue3-admin 基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!
* 🧑‍🤝‍🧑前端采用 Vue3+TS+pinia+fastcrud(感谢[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/))
* 👭后端采用 Python 语言 Django 框架以及强大的 [Django REST Framework](https://pypi.org/project/djangorestframework)。
* 👫权限认证使用[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),支持多终端认证系统。
* 👬支持加载动态权限菜单,多方式轻松权限控制。
* 💏特别鸣谢:[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)。
* 💡 特别感谢[jetbrains](https://www.jetbrains.com/) 为本开源项目提供免费的 IntelliJ IDEA 授权。
#### 🏭 环境支持
@@ -12,21 +36,173 @@ django-vue3-admin基于 vue3 + CompositionAPI + typescript + vite + element p
> 由于 Vue3 不再支持 IE11故而 ElementPlus 也不支持 IE11 及之前版本。
#### ⚡ 使用说明
建议使用 yarnyarn 是一个类似于npm的包管理器 <a href="http://nodejs.cn/" target="_blank">node 版本 > 16</a>
## 在线体验
👩‍👧‍👦演示地址:[https://demo.dvadmin.com](https://demo.dvadmin.com)
- 账号superadmin
- 密码admin123456
👩‍👦‍👦文档地址:[coding](https://dvadmin-private.coding.net/share/km/cec69f3d-30fe-47d5-bd97-e9e851f0b776/K-2)
## 交流
- 交流社区:[戳我](https://bbs.django-vue-admin.com)👩‍👦‍👦
- 插件市场:[戳我](https://bbs.django-vue-admin.com/plugMarket.html)👩‍👦‍👦
- django-vue-admin交流01群(已满)812482043 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=aJVwjDvH-Es4MPJQuoO32N0SucK22TE5&jump_from=webapi)
- django-vue-admin交流02群(已满)687252418 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=4jJN4IjWGfxJ8YJXbb_gTsuWjR34WLdc&jump_from=webapi)
- django-vue-admin交流03群442108213 [点击链接加入群聊](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=wsm5oSz3K8dElBYUDtLTcQSEPhINFkl8&authKey=M6sbER0z59ZakgBr5erFeZyFZU15CI52bErNZa%2FxSvvGIuVAbY0N5866v89hm%2FK4&noverify=0&group_code=442108213)
- 二维码
<img src='https://images.gitee.com/uploads/images/2022/0530/233203_5fb11883_5074988.jpeg' width='200'>
## 源码地址
gitee地址(主推)[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩‍👦‍👦
github地址暂无
## 内置功能
1. 👨‍⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
2. 🧑‍⚕️部门管理:配置系统组织机构(公司、部门、角色)。
3. 👩‍⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
4. 🧑‍🎓按钮权限权限:授权角色的按钮权限和接口权限,可做到每一个接口都能授权数据范围。
5. 🧑‍🎓字段权限权限:授权页面的字段显示权限。
5. 👨‍🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
6. 👬接口白名单:配置不需要进行权限校验的接口。
7. 🧑‍🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
8. 🧑‍🔧地区管理:对省市县区域进行管理。
9. 📁附件管理:对平台上所有文件、图片等进行统一管理。
10. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
11. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html)基于Django-Vue-Admin框架开发的应用和插件。
## 插件市场 🔌
- Celery异步任务[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
- 升级中心后端:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
- 升级中心前端:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
## 准备工作
~~~
Python >= 3.8.0 (推荐3.8+版本)
nodejs >= 14.0 (推荐最新)
Mysql >= 5.7.0 (可选默认数据库sqlite3推荐8.0版本)
Redis(可选,最新版)
~~~
## 前端♝
```bash
# 克隆项目
git clone https://gitee.com/huge-dream/django-vue3-admin.git
# 进入项目
cd django-vue-admin/web
# 进入项目目录
cd web
# 安装依赖
yarn install
npm install --registry=https://registry.npm.taobao.org
# 运行项目
yarn dev
# 打包发布
yarn build
# 启动服务
npm run dev
# 浏览器访问 http://localhost:8080
# .env.development 文件中可配置启动端口等参数
# 构建生产环境
# npm run build
```
## 后端💈
~~~bash
1. 进入项目目录 cd backend
2. 在项目根目录中,复制 ./conf/env.example.py 文件为一份新的到 ./conf 文件夹下,并重命名为 env.py
3. 在 env.py 中配置数据库信息
mysql数据库版本建议8.0
mysql数据库字符集utf8mb4
4. 安装依赖环境
pip3 install -r requirements.txt
5. 执行迁移命令:
python3 manage.py makemigrations
python3 manage.py migrate
6. 初始化数据
python3 manage.py init
7. 初始化省市县数据:
python3 manage.py init_area
8. 启动项目
python3 manage.py runserver 0.0.0.0:8000
或使用 daphne :
daphne -b 0.0.0.0 -p 8000 application.asgi:application
~~~
### 访问项目
- 访问地址:[http://localhost:8080](http://localhost:8080) (默认为此地址,如有修改请按照配置文件)
- 账号:`superadmin` 密码:`admin123456`
### docker-compose 运行
~~~shell
# 先安装docker-compose (自行百度安装),执行此命令等待安装如有使用celery插件请打开docker-compose.yml中celery 部分注释
docker-compose up -d
# 初始化后端数据(第一次执行即可)
docker exec -ti dvadmin-django bash
python manage.py makemigrations
python manage.py migrate
python manage.py init_area
python manage.py init
exit
前端地址http://127.0.0.1:8080
后端地址http://127.0.0.1:8080/api
# 在服务器上请把127.0.0.1 换成自己公网ip
账号superadmin 密码admin123456
# docker-compose 停止
docker-compose down
# docker-compose 重启
docker-compose restart
# docker-compose 启动时重新进行 build
docker-compose up -d --build
~~~
## 演示图✅
![image-01](https://images.gitee.com/uploads/images/2022/0530/234137_b58c8f98_5074988.png)
![image-02](https://images.gitee.com/uploads/images/2022/0530/234240_39834603_5074988.png)
![image-03](https://images.gitee.com/uploads/images/2022/0530/234339_35e728a0_5074988.png)
![image-04](https://images.gitee.com/uploads/images/2022/0530/234426_957036b0_5074988.png)
![image-05](https://images.gitee.com/uploads/images/2022/0530/234458_898be492_5074988.png)
![image-06](https://images.gitee.com/uploads/images/2022/0530/234521_35b40076_5074988.png)
![image-07](https://images.gitee.com/uploads/images/2022/0530/234615_c2325639_5074988.png)
![image-08](https://images.gitee.com/uploads/images/2022/0530/234639_1ed6cc93_5074988.png)
![image-09](https://images.gitee.com/uploads/images/2022/0530/234815_cea2c53f_5074988.png)
![image-10](https://images.gitee.com/uploads/images/2022/0530/234840_5f3e5f53_5074988.png)

View File

@@ -10,23 +10,22 @@
/>
<meta
name="description"
content="django-vue3-admin基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!"
content="django-vue-admin 基于RBAC模型的权限控制的一整套基础开发平台权限粒度达到列级别前后端分离后端采用django + django-rest-framework前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus"
/>
<link rel="icon" href="/favicon.ico" />
<title>django-vue3-admin</title>
<title>django-vue-admin</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript">
// let _hmt = _hmt || [];
(function () {
let hm = document.createElement('script');
hm.src = 'https://hm.baidu.com/hm.js?d9c8b87d10717013641458b300c552e4';
let s = document.getElementsByTagName('script')[0];
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?9ba8fc809b5584167a2fb9b31bb3970c";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="module" src="/src/main.ts"></script>
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script>
</body>
</html>

View File

@@ -1,7 +1,7 @@
{
"name": "django-vue3-admin",
"version": "1.0.0",
"description": "django-vue3-admin基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!",
"version": "3.0.0",
"description": "是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。\n django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台权限粒度达到列级别前后端分离后端采用django + django-rest-framework前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus",
"license": "MIT",
"scripts": {
"dev": "vite --force",
@@ -10,10 +10,11 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@fast-crud/fast-crud": "^1.11.10",
"@fast-crud/fast-extends": "^1.11.10",
"@fast-crud/ui-element": "^1.11.10",
"@fast-crud/ui-interface": "^1.11.9",
"@fast-crud/fast-crud": "^1.19.2",
"@fast-crud/fast-extends": "^1.19.2",
"@fast-crud/ui-element": "^1.19.2",
"@fast-crud/ui-interface": "^1.19.2",
"@types/lodash": "^4.14.202",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
@@ -25,7 +26,8 @@
"echarts": "^5.4.1",
"echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.2.26",
"element-plus": "^2.5.5",
"element-tree-line": "^0.2.1",
"font-awesome": "^4.7.0",
"js-cookie": "^3.0.1",
"js-table2excel": "^1.0.3",
@@ -43,13 +45,14 @@
"splitpanes": "^3.1.5",
"tailwindcss": "^3.2.7",
"ts-md5": "^1.3.1",
"upgrade": "^1.1.0",
"vue": "^3.2.45",
"vue-clipboard3": "^2.0.0",
"vue-cropper": "^1.0.8",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",
"vxe-table": "^4.3.10",
"vxe-table": "^4.4.1",
"xe-utils": "^3.5.7"
},
"devDependencies": {
@@ -60,7 +63,7 @@
"@typescript-eslint/parser": "^5.46.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/compiler-sfc": "^3.2.45",
"eslint": "^8.29.0",
"eslint": "^8.54.0",
"eslint-plugin-vue": "^9.8.0",
"prettier": "^2.8.1",
"sass": "^1.56.2",

View File

@@ -1,5 +1,6 @@
<template>
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<!-- v-show="themeConfig.lockScreenTime > 1" -->
<router-view v-show="themeConfig.lockScreenTime > 1" />
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
@@ -9,7 +10,7 @@
</template>
<script setup lang="ts" name="app">
import { defineAsyncComponent, computed, ref, onBeforeMount, onMounted, onUnmounted, nextTick, watch,onBeforeUnmount } from 'vue';
import { defineAsyncComponent, computed, ref, onBeforeMount, onMounted, onUnmounted, nextTick, watch, onBeforeUnmount } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
@@ -33,8 +34,8 @@ const route = useRoute();
const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
import websocket from "/@/utils/websocket";
import {ElNotification} from "element-plus";
import websocket from '/@/utils/websocket';
import { ElNotification } from 'element-plus';
// 获取版本号
const getVersion = computed(() => {
let isVersion = false;
@@ -58,8 +59,6 @@ onBeforeMount(() => {
setIntroduction.cssCdn();
// 设置批量第三方 js
setIntroduction.jsCdn();
//websockt 模块
websocket.init(wsReceive)
});
// 页面加载时
onMounted(() => {
@@ -88,6 +87,14 @@ watch(
() => route.path,
() => {
other.useTitle();
if (!websocket.websocket) {
//websockt 模块
try {
websocket.init(wsReceive)
} catch (e) {
console.log('websocket错误');
}
}
},
{
deep: true,
@@ -95,11 +102,11 @@ watch(
);
// websocket相关代码
import {messageCenterStore} from "/@/stores/messageCenter";
import { messageCenterStore } from '/@/stores/messageCenter';
const wsReceive = (message: any) => {
const data = JSON.parse(message.data)
const { unread } = data
const messageCenter = messageCenterStore()
const data = JSON.parse(message.data);
const { unread } = data;
const messageCenter = messageCenterStore();
messageCenter.setUnread(unread);
if (data.contentType === 'SYSTEM') {
ElNotification({
@@ -107,14 +114,12 @@ const wsReceive = (message: any) => {
message: data.content,
type: 'success',
position: 'bottom-right',
duration: 5000
})
duration: 5000,
});
}
}
};
onBeforeUnmount(() => {
// 关闭连接
websocket.close()
})
websocket.close();
});
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

View File

@@ -0,0 +1,9 @@
.fs-crud-container {
.el-table thead {
color: #606266;
}
.el-input__inner::placeholder {
color: #dcdfe6;
font-size: 12px;
}
}

View File

@@ -0,0 +1,29 @@
<template>
<div>
{{ data }}
</div>
</template>
<script setup lang="ts">
import {defineProps,ref,watch} from 'vue'
import {useDeptInfoStore} from '/@/stores/modules/dept'
const props = defineProps({
modelValue:{
type: Number || String
}
})
const data = ref()
watch(()=>{
return props.modelValue
},async (newVal)=>{
const deptInfoStore = useDeptInfoStore()
const result = await deptInfoStore.getParentDeptById(newVal)
if(result?.nodes){
let name = ""
console.log(result)
result.nodes.forEach((item:any,index:number)=>{
name += index>0?`/${item.name}`:item.name
})
data.value = name
}
},{immediate: true} )
</script>

View File

@@ -0,0 +1,141 @@
<template>
<!-- 你的自定义受控组件-->
<el-select-v2
v-model="data"
:options="options"
style="width: 100%;"
:clearable="true"
:props="selectProps"
@change="onDataChange"
/>
</template>
<script lang="ts" setup>
import {ref, defineComponent, watch, computed, toRefs, toRaw, onMounted} from 'vue'
import {useUi} from "@fast-crud/fast-crud";
import {request} from "/@/utils/service";
const props = defineProps({
dict: { // 接收来自FastCrud配置中的dict数据
type: Array,
required: true,
},
modelValue: {}
})
const emit = defineEmits(['update:modelValue'])
// 获取数据
const dataList = ref([])
function getData(params) {
request({
url: props.dict.url,
params: params
}).then(res => {
dataList.value = res.data
})
}
// template上使用data
const data = ref()
// const data = computed({
// get: () => {
// console.log("有默认值", props.modelValue)
// //getData({id:props.modelValue})
//
// console.log(11, dataList)
// // const {data} = res
// // console.log("get",data[0][selectProps.value.label])
// if (dataList && dataList.length === 1) {
// return dataList[0][selectProps.value.value]
// } else {
// // console.log("aa",res.data)
// return props.modelValue
// }
// // return props.modelValue
// },
// set: (val) => {
// //data.value = val
// return val
// }
// })
const options = ref([])
const selectProps = ref({
label: 'label',
value: 'value'
})
watch(
() => {
return props.modelValue
}, // 监听modelValue的变化
(value) => {
// data.value = value
request({
url: props.dict.url,
params: {
id: props.modelValue
}
}).then(res => {
const dataList = res.data
console.log(dataList)
if (dataList && dataList.length === 1) {
data.value = dataList[0][selectProps.value.label]
}else{
data.value = null
}
})
}, // 当modelValue值触发后同步修改data.value的值
{immediate: true} // 立即触发一次给data赋值初始值
)
//获取表单校验上下文
const {ui} = useUi()
const formValidator = ui.formItem.injectFormItemContext();
// 当data需要变化时上报给父组件
// 父组件监听到update:modelValue事件后会更新props.modelValue的值
// 然后watch会被触发修改data.value的值。
function onDataChange(value) {
emit('update:modelValue', value)
data.value = value
//触发校验
formValidator.onChange()
formValidator.onBlur()
}
if (props.dict.url instanceof Function) {
request(props.dict.url).then((res) => {
options.value = res.data
})
} else {
selectProps.value.label = props.dict.label
selectProps.value.value = props.dict.value
request({
url: props.dict.url
}).then((res) => {
options.value = res.data
})
}
// onMounted(() => {
// getData({id: props.modelValue})
// })
</script>
<style scoped lang="scss">
.el-select .el-input__wrapper .el-input__inner::placeholder {
//color: #a8abb2;
color: #0d84ff;
}
.el-select-v2 {
.el-select-v2__wrapper {
.el-select-v2__placeholder.is-transparent {
//color: #a8abb2;
color: #0d84ff;
}
}
}
</style>

View File

@@ -0,0 +1,146 @@
<template>
<div style="display: inline-block">
<el-button size="default" type="success" @click="handleImport()">
<slot>导入</slot>
</el-button>
<el-dialog :title="props.upload.title" v-model="uploadShow" width="400px" append-to-body>
<div v-loading="loading">
<el-upload
ref="uploadRef"
:limit="1"
accept=".xlsx, .xls"
:headers="props.upload.headers"
:action="props.upload.url"
:disabled="isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<i class="el-icon-upload"/>
<div class="el-upload__text">
将文件拖到此处
<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip" style="color:red">提示仅允许导入xlsxlsx格式文件</div>
</template>
</el-upload>
<div>
<el-button type="warning" style="font-size:14px;margin-top: 20px" @click="importTemplate">下载导入模板</el-button>
<el-button type="warning" style="font-size:14px;margin-top: 20px" @click="updateTemplate">批量更新模板</el-button>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" :disabled="loading" @click="submitFileForm"> </el-button>
<el-button :disabled="loading" @click="uploadShow = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup name="importExcel">
import { request, downloadFile } from '/@/utils/service';
import {inject,ref} from "vue";
import { getBaseURL } from '/@/utils/baseUrl';
import { Session } from '/@/utils/storage';
import { ElMessageBox } from 'element-plus'
import type { Action } from 'element-plus'
const refreshView = inject('refreshView')
let props = defineProps({
upload: {
type: Object,
default () {
return {
// 是否显示弹出层
open: true,
// 弹出层标题
title: '',
// 是否禁用上传
isUploading: false,
// 是否更新已经存在的用户数据
updateSupport: 0,
// 设置上传的请求头部
headers: { Authorization: 'JWT ' + Session.get('token') },
// 上传的地址
url: getBaseURL() + 'api/system/file/'
}
}
},
api: { // 导入接口地址
type: String,
default () {
return undefined
}
}
})
let loading = ref(false)
const uploadRef = ref()
const uploadShow = ref(false)
const isUploading = ref(false)
/** 导入按钮操作 */
const handleImport = function () {
uploadShow.value = true
}
/** 下载模板操作 */
const importTemplate=function () {
downloadFile({
url: props.api + 'import_data/',
params: {},
method: 'get'
})
}
/***
* 批量更新模板
*/
const updateTemplate=function () {
downloadFile({
url: props.api + 'update_template/',
params: {},
method: 'get'
})
}
// 文件上传中处理
const handleFileUploadProgress=function (event:any, file:any, fileList:any) {
isUploading.value = true
}
// 文件上传成功处理
const handleFileSuccess=function (response:any, file:any, fileList:any) {
isUploading.value = false
loading.value = true
uploadRef.value.clearFiles()
// 是否更新已经存在的用户数据
return request({
url: props.api + 'import_data/',
method: 'post',
data: {
url: response.data.url
}
}).then((response:any) => {
loading.value = false
ElMessageBox.alert('导入成功', '导入完成', {
confirmButtonText: 'OK',
callback: (action: Action) => {
refreshView()
},
})
}).catch(()=>{
loading.value = false
})
}
// 提交上传文件
const submitFileForm=function () {
uploadRef.value.submit()
}
</script>
<style scoped>
</style>

View File

@@ -43,7 +43,7 @@
import {defineProps, onMounted, reactive, ref, toRaw, watch} from 'vue'
import {dict} from '@fast-crud/fast-crud'
import XEUtils from 'xe-utils'
import {request} from '/@/utils/service'
const props = defineProps({
modelValue: {},
tableConfig: {
@@ -71,6 +71,7 @@ watch(multipleSelection, // 监听multipleSelection的变化
if (!tableConfig.isMultiple) {
data.value = value ? value[tableConfig.label] : null
} else {
const result = value ? value.map((item: any) => {
return item[tableConfig.label]
}) : null
@@ -123,12 +124,12 @@ const getDict = async () => {
const params = {
page: pageConfig.page,
limit: pageConfig.limit,
search: search
search: search.value
}
const dicts = dict({url: url, params: params})
await dicts.reloadDict()
const dictData: any = dicts.data
const {data, page, limit, total} = dictData
const {data, page, limit, total} = await request({
url:url,
params:params
})
pageConfig.page = page
pageConfig.limit = limit
pageConfig.total = total

View File

@@ -1,7 +1,6 @@
import type { App } from 'vue';
import { useUserInfo } from '/@/stores/userInfo';
import { judementSameArr } from '/@/utils/arrayOperation';
import {BtnPermissionStore} from "/@/stores/btnPermission";
/**
* 用户权限指令
* @directive 单个权限验证v-auth="xxx"
@@ -12,16 +11,16 @@ export function authDirective(app: App) {
// 单个权限验证v-auth="xxx"
app.directive('auth', {
mounted(el, binding) {
const stores = useUserInfo();
if (!stores.userInfos.authBtnList.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
const stores = BtnPermissionStore();
if (!stores.data.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
},
});
// 多个权限验证满足一个则显示v-auths="[xxx,xxx]"
app.directive('auths', {
mounted(el, binding) {
let flag = false;
const stores = useUserInfo();
stores.userInfos.authBtnList.map((val: string) => {
const stores = BtnPermissionStore();
stores.data.map((val: string) => {
binding.value.map((v: string) => {
if (val === v) flag = true;
});
@@ -32,8 +31,8 @@ export function authDirective(app: App) {
// 多个权限验证全部满足则显示v-auth-all="[xxx,xxx]"
app.directive('auth-all', {
mounted(el, binding) {
const stores = useUserInfo();
const flag = judementSameArr(binding.value, stores.userInfos.authBtnList);
const stores = BtnPermissionStore();
const flag = judementSameArr(binding.value, stores.data);
if (!flag) el.parentNode.removeChild(el);
},
});

View File

@@ -1,7 +1,7 @@
import type { App } from 'vue';
import { authDirective } from '/@/directive/authDirective';
import { wavesDirective, dragDirective } from '/@/directive/customDirective';
import {resizeObDirective} from '/@/directive/sizeDirective'
/**
* 导出指令方法v-xxx
* @methods authDirective 用户权限指令用法v-auth
@@ -15,4 +15,6 @@ export function directive(app: App) {
wavesDirective(app);
// 自定义拖动指令
dragDirective(app);
// 监听窗口大小变化
resizeObDirective(app)
}

View File

@@ -0,0 +1,23 @@
import {App} from "vue/dist/vue";
const map = new WeakMap()
const ob = new ResizeObserver((entries) => {
for(const entry of entries){
const handler = map.get(entry.target);
handler && handler({
width: entry.borderBoxSize[0].inlineSize,
height: entry.borderBoxSize[0].blockSize
});
}
});
export function resizeObDirective(app: App){
app.directive('resizeOb', {
mounted(el,binding) {
map.set(el,binding.value);
ob.observe(el); // 监听目标元素
},
unmounted(el) {
ob.unobserve(el); // 停止监听
},
})
}

View File

@@ -14,9 +14,9 @@ import { useThemeConfig } from '/@/stores/themeConfig';
*/
// element plus 自带国际化
import enLocale from 'element-plus/lib/locale/lang/en';
import zhcnLocale from 'element-plus/lib/locale/lang/zh-cn';
import zhtwLocale from 'element-plus/lib/locale/lang/zh-tw';
import enLocale from 'element-plus/es/locale/lang/en';
import zhcnLocale from 'element-plus/es/locale/lang/zh-cn';
import zhtwLocale from 'element-plus/es/locale/lang/zh-tw';
// 定义变量内容
const messages = {};

View File

@@ -120,4 +120,12 @@ export default {
copyTextSuccess: 'Copy succeeded!',
copyTextError: 'Copy failed!',
},
upgrade: {
title: 'New version upgrade',
msg: 'It\'s a new version. Update it nowDon\'t worry, update quickly oh!',
desc: 'Tip: The update restores the default configuration',
btnOne: 'Cruel refusal',
btnTwo: 'Update now',
btnTwoLoading: 'updating',
},
};

View File

@@ -40,6 +40,8 @@ export default {
title4: '消息',
title5: '开全屏',
title6: '关全屏',
retry: '重试上线',
onlinePrompt: '当前离线状态,是否重试上线?',
dropdownLarge: '大型',
dropdownDefault: '默认',
dropdownSmall: '小型',
@@ -75,7 +77,7 @@ export default {
},
noAccess: {
accessTitle: '您未被授权,没有操作权限~',
accessMsg: '联系方式加QQ群探讨 665452019',
accessMsg: '联系管理员',
accessBtn: '重新授权',
},
layout: {
@@ -89,10 +91,12 @@ export default {
twoIsTopBarColorGradual: '顶栏背景渐变',
twoMenuBar: '菜单背景',
twoMenuBarColor: '菜单默认字体颜色',
twoMenuBarActiveColor: '菜单高亮背景色',
twoIsMenuBarColorGradual: '菜单背景渐变',
twoColumnsMenuBar: '分栏菜单背景',
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
twoIsColumnsMenuHoverPreload: '分栏菜单滑鼠悬停预加载',
threeTitle: '界面设置',
threeIsCollapse: '菜单水平折叠',
threeIsUniqueOpened: '菜单手风琴',
@@ -131,4 +135,12 @@ export default {
copyTextSuccess: '复制成功!',
copyTextError: '复制失败!',
},
upgrade: {
title: '新版本升级',
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
desc: '提示:更新会还原默认配寘',
btnOne: '残忍拒绝',
btnTwo: '马上更新',
btnTwoLoading: '更新中',
},
};

View File

@@ -123,7 +123,7 @@ export default {
},
noAccess: {
accessTitle: '您未被授權,沒有操作許可權~',
accessMsg: '聯繫方式加QQ群探討665452019',
accessMsg: '請聯系管理員',
accessBtn: '重新授權',
},
layout: {

View File

@@ -1,7 +1,7 @@
// 定义内容
export default {
label: {
one1: '用户名登录',
one1: '账号密码登录',
two2: '手机号登录',
},
link: {

View File

@@ -1,11 +1,8 @@
<template>
<el-main class="layout-main" :style="isFixedHeader ? `height: calc(100% - ${setMainHeight})` : `minHeight: calc(100% - ${setMainHeight})`">
<el-scrollbar
ref="layoutMainScrollbarRef"
class="layout-main-scroll layout-backtop-header-fixed"
wrap-class="layout-main-scroll"
view-class="layout-main-scroll"
>
<el-main class="layout-main"
:style="isFixedHeader ? `height: calc(100% - ${setMainHeight})` : `minHeight: calc(100% - ${setMainHeight})`">
<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll layout-backtop-header-fixed"
wrap-class="layout-main-scroll" view-class="layout-main-scroll">
<LayoutParentView />
<LayoutFooter v-if="isFooter" />
</el-scrollbar>

View File

@@ -1,8 +1,7 @@
<template>
<div class="layout-footer pb15">
<div class="layout-footer pb5 pt2">
<div class="layout-footer-warp">
<div> Powered by Django-Vue3-Admin </div>
<div class="mt5">Copyright DVAdmin团队</div>
<div> Powered by Django-Vue3-Admin Copyright © DVAdmin团队 </div>
</div>
</div>
</template>

View File

@@ -1,10 +1,10 @@
<template>
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
<img :src="logoMini" class="layout-logo-medium-img" />
<span style="font-size: x-large">{{ themeConfig.globalTitle }}</span>
<img :src="siteLogo" class="layout-logo-medium-img" />
<span style="font-size: x-large">{{ getSystemConfig['login.site_title'] || themeConfig.globalTitle }}</span>
</div>
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
<img :src="logoMini" class="layout-logo-size-img" />
<img :src="siteLogo" class="layout-logo-size-img" />
</div>
</template>
@@ -13,7 +13,8 @@ import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import logoMini from '/@/assets/logo-mini.svg';
import { SystemConfigStore } from "/@/stores/systemConfig";
import _ from "lodash";
// 定义变量内容
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
@@ -28,6 +29,20 @@ const onThemeConfigChange = () => {
if (themeConfig.value.layout === 'transverse') return false;
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
};
const systemConfigStore = SystemConfigStore()
const { systemConfig } = storeToRefs(systemConfigStore)
const getSystemConfig = computed(() => {
return systemConfig.value
})
const siteLogo = computed(() => {
if (!_.isEmpty(getSystemConfig.value['login.site_logo'])) {
return getSystemConfig.value['login.site_logo']
}
return logoMini
});
</script>
<style scoped lang="scss">
@@ -42,30 +57,36 @@ const onThemeConfigChange = () => {
font-size: 16px;
cursor: pointer;
animation: logoAnimation 0.3s ease-in-out;
span {
white-space: nowrap;
display: inline-block;
}
&:hover {
span {
color: var(--color-primary-light-2);
}
}
&-medium-img {
width: 40px;
margin-right: 5px;
}
}
.layout-logo-size {
width: 100%;
height: 50px;
display: flex;
cursor: pointer;
animation: logoAnimation 0.3s ease-in-out;
&-img {
width: 40px;
margin: auto;
}
&:hover {
img {
animation: logoAnimation 0.3s ease-in-out;

View File

@@ -57,10 +57,34 @@
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
></i>
</div>
<div>
<span v-if="!isSocketOpen">
<el-popconfirm
width="250"
ref="onlinePopoverRef"
:confirm-button-text="$t('message.user.retry')"
:icon="InfoFilled"
trigger="hover"
icon-color="#626AEF"
:title="$t('message.user.onlinePrompt')"
@confirm="onlineConfirmEvent"
>
<template #reference>
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
</el-badge>
</template>
</el-popconfirm>
</span>
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
<span class="layout-navbars-breadcrumb-user-link">
<img :src="userInfos.avatar" class="layout-navbars-breadcrumb-user-link-photo mr5" />
{{ userInfos.userName === '' ? 'common' : userInfos.userName }}
<span v-if="isSocketOpen">
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
</el-badge>
</span>
{{ userInfos.username === '' ? 'common' : userInfos.username }}
<el-icon class="el-icon--right">
<ele-ArrowDown />
</el-icon>
@@ -79,7 +103,7 @@
</template>
<script setup lang="ts" name="layoutBreadcrumbUser">
import { defineAsyncComponent, ref, computed, reactive, onMounted } from 'vue';
import { defineAsyncComponent, ref, computed, reactive, onMounted, unref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessageBox, ElMessage } from 'element-plus';
import screenfull from 'screenfull';
@@ -90,7 +114,9 @@ import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
import mittBus from '/@/utils/mitt';
import { Session, Local } from '/@/utils/storage';
import headerImage from '/@/assets/img/headerImage.png';
import websocket from '/@/utils/websocket';
import { InfoFilled } from '@element-plus/icons-vue'
// 引入组件
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
@@ -118,6 +144,21 @@ const layoutUserFlexNum = computed(() => {
else num = '';
return num;
});
// 定义变量内容
const { isSocketOpen } = storeToRefs(useUserInfo());
// websocket状态
const onlinePopoverRef = ref()
const onlineConfirmEvent = () => {
if (!isSocketOpen.value) {
websocket.is_reonnect = true
websocket.reconnect_current = 1
websocket.reconnect()
}
// 手动隐藏弹出
unref(onlinePopoverRef).popperRef?.delayHide?.()
}
// 全屏点击时
const onScreenfullClick = () => {
if (!screenfull.isEnabled) {
@@ -256,5 +297,29 @@ const messageCenter = messageCenterStore();
:deep(.el-badge__content.is-fixed) {
top: 12px;
}
.online-status{
cursor: pointer;
:deep(.el-badge__content.is-fixed) {
top: 30px;
font-size: 14px;
left: 5px;
height: 12px;
width: 12px;
padding: 0;
background-color: #18bc9c;
}
}
.online-down{
cursor: pointer;
:deep(.el-badge__content.is-fixed) {
top: 30px;
font-size: 14px;
left: 5px;
height: 12px;
width: 12px;
padding: 0;
background-color: #979b9c;
}
}
}
</style>

View File

@@ -8,13 +8,14 @@
</transition>
</router-view>
<transition :name="setTransitionName" mode="out-in">
<Iframes class="w100" v-show="isIframePage" :refreshKey="state.iframeRefreshKey" :name="setTransitionName" :list="state.iframeList" />
<Iframes class="w100" v-show="isIframePage" :refreshKey="state.iframeRefreshKey" :name="setTransitionName"
:list="state.iframeList" />
</transition>
</div>
</template>
<script setup lang="ts" name="layoutParentView">
import { defineAsyncComponent, computed, reactive, onBeforeMount, onUnmounted, nextTick, watch, onMounted,ref,provide } from 'vue';
import { defineAsyncComponent, computed, reactive, onBeforeMount, onUnmounted, nextTick, watch, onMounted, ref, provide } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
@@ -44,13 +45,13 @@ const showView = ref(true)
/**
* 刷新页面
*/
const refreshView=function () {
const refreshView = function () {
showView.value = false // 通过v-if移除router-view节点
nextTick(() => {
showView.value = true // DOM更新后再通过v-if添加router-view节点
})
}
provide('refreshView',refreshView)
provide('refreshView', refreshView)
// 设置主界面切换动画
const setTransitionName = computed(() => {
@@ -58,6 +59,7 @@ const setTransitionName = computed(() => {
});
// 获取组件缓存列表(name值)
const getKeepAliveNames = computed(() => {
console.log(cachedViews.value)
return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
});
// 设置 iframe 显示/隐藏
@@ -105,7 +107,7 @@ onMounted(() => {
});
// 页面卸载时
onUnmounted(() => {
mittBus.off('onTagsViewRefreshRouterView', () => {});
mittBus.off('onTagsViewRefreshRouterView', () => { });
});
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
// https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files

View File

@@ -17,15 +17,15 @@
<div class="upgrade-content">
{{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
<div class="mt5">
<el-link type="primary" class="font12" href="https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md" target="_black">
<el-link type="primary" class="font12" href="https://gitee.com/huge-dream/django-vue3-admin/blob/master/CHANGELOG.md" target="_black">
CHANGELOG.md
</el-link>
</div>
<div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
</div>
<div class="upgrade-btn">
<el-button round size="default" type="info" text @click="onCancel">{{ $t('message.upgrade.btnOne') }}</el-button>
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading">{{ state.btnTxt }}</el-button>
<el-button round size="default" type="info" text @click="onCancel" >{{ $t('message.upgrade.btnOne') }}</el-button>
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading" >{{ state.btnTxt }}</el-button>
</div>
</el-dialog>
</div>
@@ -36,7 +36,7 @@ import { reactive, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Local } from '/@/utils/storage';
import { Local,Session } from '/@/utils/storage';
// 定义变量内容
const { t } = useI18n();
@@ -57,6 +57,7 @@ const getThemeConfig = computed(() => {
// 残忍拒绝
const onCancel = () => {
state.isUpgrade = false;
Session.set('isUpgrade', false)
};
// 马上更新
const onUpgrade = () => {
@@ -66,20 +67,24 @@ const onUpgrade = () => {
Local.clear();
window.location.reload();
Local.set('version', state.version);
Session.set('isUpgrade', false)
}, 2000);
};
// 延迟显示,防止刷新时界面显示太快
const delayShow = () => {
const isUpgrade = Session.get('isUpgrade')===false?Session.get('isUpgrade'):true
if(isUpgrade){
setTimeout(() => {
state.isUpgrade = true;
}, 2000);
}
};
// 页面加载时
onMounted(() => {
// delayShow();
// setTimeout(() => {
// state.btnTxt = t('message.upgrade.btnTwo');
// }, 200);
delayShow();
setTimeout(() => {
state.btnTxt = t('message.upgrade.btnTwo');
}, 200);
});
</script>

View File

@@ -1,8 +1,8 @@
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { directive } from '/@/utils/directive';
import { i18n } from '/@/i18n/index';
import { directive } from '/@/directive/index';
import { i18n } from '/@/i18n';
import other from '/@/utils/other';
import '/@/assets/style/tailwind.css'; // 先引入tailwind css, 以免element-plus冲突
import ElementPlus from 'element-plus';
@@ -14,7 +14,7 @@ import piniaPersist from 'pinia-plugin-persist';
// @ts-ignore
import fastCrud from './settings.ts';
import pinia from './stores';
import permission from '/@/plugin/permission/index';
import {RegisterPermission} from '/@/plugin/permission/index';
// @ts-ignore
import eIconPicker, { iconList, analyzingIconForIconfont } from 'e-icon-picker';
import 'e-icon-picker/icon/default-icon/symbol.js'; //基本彩色图标库
@@ -27,6 +27,11 @@ import iconfont from '/@/assets/iconfont/iconfont.json'; //引入json文件
import '/@/assets/iconfont/iconfont.css'; //引入css
// 自动注册插件
import { scanAndInstallPlugins } from '/@/views/plugins/index';
import VXETable from 'vxe-table'
import 'vxe-table/lib/style.css'
import '/@/assets/style/reset.scss';
import 'element-tree-line/dist/style.css'
let forIconfont = analyzingIconForIconfont(iconfont); //解析class
iconList.addIcon(forIconfont.list); // 添加iconfont dvadmin3的icon
@@ -47,7 +52,8 @@ pinia.use(piniaPersist);
directive(app);
other.elSvg(app);
app.use(permission);
app.use(VXETable)
app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).use(fastCrud).mount('#app');
app.config.globalProperties.mittBus = mitt();

View File

@@ -9,7 +9,7 @@ export default {
return XEUtils.includeArrays(BtnPermission, value)
}else if(typeof value === 'string'){
const index = XEUtils.arrayIndexOf(BtnPermission, value)
return index>0?true:false
return index>-1?true:false
}
}
return true

View File

@@ -1,10 +1,6 @@
import permissionDirective from './directive.permission'
import permissionFunc from './func.permission'
const install = function (app:any) {
export const RegisterPermission = function (app:any) {
app.directive('permission', permissionDirective)
app.provide('$hasPermissions',permissionFunc.hasPermissions)
}
export default {
install
}

View File

@@ -12,14 +12,16 @@ import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useMenuApi } from '/@/api/menu/index';
import { handleMenu } from '../utils/menu';
import { BtnPermissionStore } from '/@/plugin/permission/store.permission';
import {SystemConfigStore} from "/@/stores/systemConfig";
import {useDeptInfoStore} from "/@/stores/modules/dept";
import {DictionaryStore} from "/@/stores/dictionary";
import {useFrontendMenuStore} from "/@/stores/frontendMenu";
import {toRaw} from "vue";
const menuApi = useMenuApi();
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
// 后端控制路由
/**
* 获取目录下的 .vue、.tsx 全部文件
* @method import.meta.glob
@@ -45,15 +47,36 @@ export async function initBackEndControlRoutes() {
await useUserInfo().setUserInfos();
// 获取路由菜单数据
const res = await getBackEndControlRoutes();
// 无登录权限时,添加判断
// https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO
if (res.data.length <= 0) return Promise.resolve(true);
// 处理路由component替换 dynamicRoutes/@/router/route第一个顶级 children 的路由
dynamicRoutes[0].children = await backEndComponent(handleMenu(res.data));
const {frameIn,frameOut} = handleMenu(res.data)
dynamicRoutes[0].children = await backEndComponent(frameIn);
// 添加动态路由
await setAddRoute();
// 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
await setFilterMenuAndCacheTagsViewRoutes();
}
export async function setRouters(){
const {frameInRoutes,frameOutRoutes} = await useFrontendMenuStore().getRouter()
const frameInRouter = toRaw(frameInRoutes)
const frameOutRouter = toRaw(frameOutRoutes)
dynamicRoutes[0].children = frameInRouter
dynamicRoutes.forEach((item:any)=>{
router.addRoute(item)
})
frameOutRouter.forEach((item:any)=>{
router.addRoute(item)
})
const storesRoutesList = useRoutesList(pinia);
storesRoutesList.setRoutesList([...dynamicRoutes[0].children,...frameOutRouter]);
const storesTagsView = useTagsViewRoutes(pinia);
storesTagsView.setTagsViewRoutes([...dynamicRoutes[0].children,...frameOutRouter])
}
/**
* 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
* @description 用于左侧菜单、横向菜单的显示
@@ -81,6 +104,8 @@ export function setCacheTagsViewRoutes() {
*/
export function setFilterRouteEnd() {
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
// notFoundAndNoPower 防止 404、401 不在 layout 布局中不设置的话404、401 界面将全屏显示
// 关联问题 No match found for location with path 'xxx'
filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower];
return filterRouteEnd;
}
@@ -105,6 +130,12 @@ export async function setAddRoute() {
export function getBackEndControlRoutes() {
//获取所有的按钮权限
BtnPermissionStore().getBtnPermissionStore();
// 获取系统配置
SystemConfigStore().getSystemConfigs()
// 获取所有部门信息
useDeptInfoStore().requestDeptInfo()
// 获取字典信息
DictionaryStore().getSystemDictionarys()
return menuApi.getSystemMenu();
}
@@ -126,6 +157,32 @@ export function backEndComponent(routes: any) {
if (!routes) return;
return routes.map((item: any) => {
if (item.component) item.component = dynamicImport(dynamicViewsModules, item.component as string);
if(item.is_catalog){
// 对目录的处理
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/parent')
}
if(item.is_link){
// 对外链接的处理
if(item.is_iframe){
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/iframes')
}else {
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link')
}
}else{
if(item.is_iframe){
// const iframeRoute:RouteRecordRaw = {
// ...item
// }
// router.addRoute(iframeRoute)
item.meta.isLink = item.link_url
// item.path = `${item.path}Link`
// item.name = `${item.name}Link`
// item.meta.isIframe = item.is_iframe
// item.meta.isKeepAlive = false
// item.meta.isIframeOpen = true
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link.vue')
}
}
item.children && backEndComponent(item.children);
return item;
});

View File

@@ -1,15 +1,18 @@
import { createRouter, createWebHashHistory } from 'vue-router';
import {createRouter, createWebHashHistory} from 'vue-router';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import pinia from '/@/stores/index';
import { storeToRefs } from 'pinia';
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Session } from '/@/utils/storage';
import { staticRoutes } from '/@/router/route';
import { initFrontEndControlRoutes } from '/@/router/frontEnd';
import { initBackEndControlRoutes } from '/@/router/backEnd';
import {storeToRefs} from 'pinia';
import {useKeepALiveNames} from '/@/stores/keepAliveNames';
import {useRoutesList} from '/@/stores/routesList';
import {useThemeConfig} from '/@/stores/themeConfig';
import {Session} from '/@/utils/storage';
import {dynamicRoutes, notFoundAndNoPower, staticRoutes} from '/@/router/route';
import {initFrontEndControlRoutes} from '/@/router/frontEnd';
import {initBackEndControlRoutes, setRouters} from '/@/router/backEnd';
import {useFrontendMenuStore} from "/@/stores/frontendMenu";
import {useTagsViewRoutes} from "/@/stores/tagsViewRoutes";
import {toRaw} from "vue";
/**
* 1、前端控制路由时isRequestRoutes 为 false需要写 roles需要走 setFilterRoute 方法。
@@ -22,8 +25,8 @@ import { initBackEndControlRoutes } from '/@/router/backEnd';
// 读取 `/src/stores/themeConfig.ts` 是否开启后端控制路由配置
const storesThemeConfig = useThemeConfig(pinia);
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isRequestRoutes } = themeConfig.value;
const {themeConfig} = storeToRefs(storesThemeConfig);
const {isRequestRoutes} = themeConfig.value;
/**
* 创建一个可以被 Vue 应用程序使用的路由实例
@@ -32,7 +35,13 @@ const { isRequestRoutes } = themeConfig.value;
*/
export const router = createRouter({
history: createWebHashHistory(),
routes: staticRoutes,
/**
* 说明:
* 1、notFoundAndNoPower 默认添加 404、401 界面,防止一直提示 No match found for location with path 'xxx'
* 2、backEnd.ts(后端控制路由)、frontEnd.ts(前端控制路由) 中也需要加 notFoundAndNoPower 404、401 界面。
* 防止 404、401 不在 layout 布局中不设置的话404、401 界面将全屏显示
*/
routes: [...notFoundAndNoPower, ...staticRoutes]
});
/**
@@ -63,7 +72,7 @@ export function formatTwoStageRoutes(arr: any) {
const cacheList: Array<string> = [];
arr.forEach((v: any) => {
if (v.path === '/') {
newArr.push({ component: v.component, name: v.name, path: v.path, redirect: v.redirect, meta: v.meta, children: [] });
newArr.push({component: v.component,name: v.name,path: v.path,redirect: v.redirect,meta: v.meta,children: []});
} else {
// 判断是否是动态路由xx/:id/:name用于 tagsView 等中使用
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
@@ -71,10 +80,10 @@ export function formatTwoStageRoutes(arr: any) {
v.meta['isDynamic'] = true;
v.meta['isDynamicPath'] = v.path;
}
newArr[0].children.push({ ...v });
newArr[0].children.push({...v});
// 存 name 值keep-alive 中 include 使用,实现路由的缓存
// 路径:/@/layout/routerView/parent.vue
if (newArr[0].meta.isKeepAlive && v.meta.isKeepAlive) {
if (newArr[0].meta.isKeepAlive && v.meta.isKeepAlive && v.component_name != "") {
cacheList.push(v.name);
const stores = useKeepALiveNames(pinia);
stores.setCacheKeepAlive(cacheList);
@@ -86,7 +95,7 @@ export function formatTwoStageRoutes(arr: any) {
// 路由加载前
router.beforeEach(async (to, from, next) => {
NProgress.configure({ showSpinner: false });
NProgress.configure({showSpinner: false});
if (to.meta.title) NProgress.start();
const token = Session.get('token');
if (to.path === '/login' && !token) {
@@ -101,19 +110,21 @@ router.beforeEach(async (to, from, next) => {
next('/home');
NProgress.done();
} else {
const storesRoutesList = useRoutesList(pinia);
const { routesList } = storeToRefs(storesRoutesList);
const {routesList} = storeToRefs(storesRoutesList);
if (routesList.value.length === 0) {
if (isRequestRoutes) {
// 后端控制路由:路由数据初始化,防止刷新时丢失
await initBackEndControlRoutes();
// 动态添加路由:防止非首页刷新时跳转回首页的问题
// 确保 addRoute() 时动态添加的路由已经被完全加载上去
next({ ...to, replace: true });
// 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx'
// to.query 防止页面刷新时普通路由带参数时参数丢失。动态路由xxx/:id/:name"isDynamic 无需处理
next({ path: to.path, query: to.query });
} else {
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
await initFrontEndControlRoutes();
next({ ...to, replace: true });
next({ path: to.path, query: to.query });
}
} else {
next();

View File

@@ -88,4 +88,12 @@ export const staticRoutes: Array<RouteRecordRaw> = [
title: '登录',
},
},
{
path: '/demo',
name: 'demo',
component: () => import('/@/views/system/demo/index.vue'),
meta: {
title: 'message.router.personal'
},
}
];

View File

@@ -1,14 +1,18 @@
// 引入fast-crud
import { FastCrud, useTypes } from '@fast-crud/fast-crud';
const { getType } = useTypes();
import {FastCrud, useTypes} from '@fast-crud/fast-crud';
const {getType} = useTypes();
import '@fast-crud/fast-crud/dist/style.css';
import { setLogger } from '@fast-crud/fast-crud';
import {setLogger} from '@fast-crud/fast-crud';
import {getBaseURL} from '/@/utils/baseUrl';
// element
import ui from '@fast-crud/ui-element';
import { request } from '/@/utils/service';
import {request} from '/@/utils/service';
//扩展包
import { FsExtendsEditor } from '@fast-crud/fast-extends';
import {FsExtendsEditor, FsExtendsUploader,FsCropperUploader} from '@fast-crud/fast-extends';
import '@fast-crud/fast-extends/dist/style.css';
import {successNotification} from '/@/utils/message';
import XEUtils from "xe-utils";
export default {
async install(app: any, options: any) {
// 先安装ui
@@ -17,8 +21,15 @@ export default {
app.use(FastCrud, {
//i18n, //i18n配置可选默认使用中文具体用法请看demo里的 src/i18n/index.js 文件
// 此处配置公共的dictRequest字典请求
async dictRequest({ dict }: any) {
return await request({ url: dict.url, params: dict.params || {} }); //根据dict的url异步返回一个字典数组
async dictRequest({dict}: any) {
const {isTree} = dict
//根据dict的url异步返回一个字典数组
return await request({url: dict.url, params: dict.params || {}}).then((res: any) => {
if (isTree) {
return XEUtils.toArrayTree(res.data, {parentKey: 'parent'})
}
return res.data
});
},
//公共crud配置
commonOptions() {
@@ -27,17 +38,25 @@ export default {
//接口请求配置
//你项目后台接口大概率与fast-crud所需要的返回结构不一致所以需要配置此项
//请参考文档http://fast-crud.docmirror.cn/api/crud-options/request.html
transformQuery: ({ page, form, sort }: any) => {
transformQuery: ({page, form, sort}: any) => {
if (sort.asc !== undefined) {
form['ordering'] = `${sort.asc ? '' : '-'}${sort.prop}`;
}
//转换为你pageRequest所需要的请求参数结构
return { page: page.currentPage, limit: page.pageSize, ...form };
return {page: page.currentPage, limit: page.pageSize, ...form};
},
transformRes: ({ res }: any) => {
transformRes: ({res}: any) => {
//将pageRequest的返回数据转换为fast-crud所需要的格式
//return {records,currentPage,pageSize,total};
return { records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total };
return {records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total};
},
},
form: {
afterSubmit(ctx: any) {
// 增加crud提示
if (ctx.res.code == 2000) {
successNotification(ctx.res.msg);
}
},
},
/* search: {
@@ -63,11 +82,77 @@ export default {
width: 300,
},
});
setLogger({ level: 'error' });
// 文件上传
app.use(FsExtendsUploader, {
defaultType: "form",
form: {
action: `/api/system/file/`,
name: "file",
withCredentials: false,
uploadRequest: async ({action, file, onProgress}) => {
// @ts-ignore
const data = new FormData();
data.append("file", file);
return await request({
url: action,
method: "post",
timeout: 60000,
headers: {
"Content-Type": "multipart/form-data"
},
data,
onUploadProgress: (p) => {
onProgress({percent: Math.round((p.loaded / p.total) * 100)});
}
});
},
successHandle(ret) {
// 上传完成后的结果处理, 此处应返回格式为{url:xxx,key:xxx}
return {
url: getBaseURL(ret.data.url),
key: ret.data.id,
...ret.data
};
}
},
valueBuilder(context){
const {row,key} = context
return getBaseURL(row[key])
}
})
setLogger({level: 'error'});
// 设置自动染色
const dictComponentList = ['dict-cascader', 'dict-checkbox', 'dict-radio', 'dict-select', 'dict-switch', 'dict-tree'];
dictComponentList.forEach((val) => {
getType(val).column.component.color = 'auto';
getType(val).column.align = 'center';
});
// 设置placeholder 的默认值
const placeholderComponentList = [
{key: 'text', placeholder: "请输入"},
{key: 'textarea', placeholder: "请输入"},
{key: 'input', placeholder: "请输入"},
{key: 'password', placeholder: "请输入"}
]
placeholderComponentList.forEach((val) => {
if (getType(val.key)?.search?.component) {
getType(val.key).search.component.placeholder = val.placeholder;
} else if (getType(val.key)?.search) {
getType(val.key).search.component = {placeholder: val.placeholder};
}
if (getType(val.key)?.form?.component) {
getType(val.key).form.component.placeholder = val.placeholder;
} else if (getType(val.key)?.form) {
getType(val.key).form.component = {placeholder: val.placeholder};
}
if (getType(val.key)?.column?.align) {
getType(val.key).column.align = 'center'
} else if (getType(val.key)?.column) {
getType(val.key).column = {align: 'center'};
} else if (getType(val.key)) {
getType(val.key).column = {align: 'center'};
}
});
},
};

View File

@@ -0,0 +1,26 @@
import {defineStore} from "pinia";
import {DictionaryStates} from "/@/stores/interface";
import {request} from "/@/utils/service";
export const BtnPermissionStore = defineStore('BtnPermission', {
state: (): DictionaryStates => ({
data: []
}),
actions: {
async getBtnPermissionStore() {
request({
url: '/api/system/menu_button/menu_button_all_permission/',
method: 'get',
}).then((ret: {
data: []
}) => {
// 转换数据格式并保存到pinia
let dataList = ret.data
this.data=dataList
})
},
},
persist: {
enabled: true,
},
});

View File

@@ -0,0 +1,20 @@
import { defineStore } from 'pinia';
export interface DataItemType {
field_name: string;
is_create: boolean;
is_query: boolean;
is_update: boolean;
}
export const useColumnPermission = defineStore('columnPermission', {
state: () => ({
permission: [] as DataItemType[],
}),
actions: {
setPermissionData(data: DataItemType[]) {
this.permission = data;
},
},
});

View File

@@ -0,0 +1,169 @@
import {defineStore} from 'pinia';
import {FrontendMenu} from './interface';
import {Session} from '/@/utils/storage';
import {request} from '../utils/service';
import XEUtils from "xe-utils";
import {RouteRecordRaw} from "vue-router";
import {useKeepALiveNames} from "/@/stores/keepAliveNames";
import pinia from "/@/stores/index";
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
/**
* 获取目录下的 .vue、.tsx 全部文件
* @method import.meta.glob
* @link 参考https://cn.vitejs.dev/guide/features.html#json
*/
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...layouModules }, { ...viewsModules });
/**
* 后端路由 component 转换函数
* @param dynamicViewsModules 获取目录下的 .vue、.tsx 全部文件
* @param component 当前要处理项 component
* @returns 返回处理成函数后的 component
*/
export function dynamicImport(dynamicViewsModules: Record<string, Function>, component: string) {
const keys = Object.keys(dynamicViewsModules);
const matchKeys = keys.filter((key) => {
const k = key.replace(/..\/views|../, '');
return k.startsWith(`${component}`) || k.startsWith(`/${component}`);
});
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0];
return dynamicViewsModules[matchKey];
}
if (matchKeys?.length > 1) {
return false;
}
}
/**
* @description: 处理后端菜单数据格式
* @param {Array} menuData
* @return {*}
*/
export const handleMenu = (menuData: Array<any>) => {
// 框架内路由
const frameInRoutes:Array<any> = []
// 框架外路由
const frameOutRoutes:Array<any> = []
// 需要缓存的路由
const cacheList:Array<any> = []
// 先处理menu meta数据转换
const handleMeta = (item: any) => {
item.path = item.web_path
item.meta = {
title: item.title,
isLink: item.link_url,
isHide: !item.visible,
isKeepAlive: item.cache,
isAffix: item.is_affix,
isIframe: item.is_iframe,
roles: ['admin'],
icon: item.icon
}
item.component = dynamicImport(dynamicViewsModules, item.component as string)
if(item.is_catalog){
// 对目录的处理
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/parent')
}
if(item.is_link){
// 对外链接的处理
item.meta.isIframe = !item.is_iframe
if(item.is_iframe){
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link')
}else {
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/iframes')
}
}else{
if(item.is_iframe){
const route = JSON.parse(JSON.stringify(item))
route.meta.isLink = ''
route.path = `${item.web_path}`
route.name = `${item.name}`
route.meta.isIframe = true
route.meta.isKeepAlive = false
route.meta.isIframeOpen = true
route.component = item.component
frameOutRoutes.push(route)
item.path = `${item.web_path}FrameOut`
item.name = `${item.name}FrameOut`
item.meta.isLink = item.web_path
item.meta.isIframe = !item.is_iframe
//item.meta.isIframeOpen = true
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link.vue')
}
}
item.children && handleMeta(item.children);
if (item.meta.isKeepAlive && item.meta.isKeepAlive && item.component_name != "") {
cacheList.push(item.name);
}
return item
}
menuData.forEach((val) => {
frameInRoutes.push(handleMeta(val))
})
const stores = useKeepALiveNames(pinia);
stores.setCacheKeepAlive(cacheList);
const data = XEUtils.toArrayTree(frameInRoutes, {
parentKey: 'parent',
strict: true,
})
const dynamicRoutes = [
{
path: '/home', name: 'home',
component: dynamicImport(dynamicViewsModules, '/system/home/index'),
meta: {
title: 'message.router.home',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: true,
isIframe: false,
roles: ['admin'],
icon: 'iconfont icon-shouye'
}
},
...data
]
return {frameIn:dynamicRoutes,frameOut:frameOutRoutes}
}
export const useFrontendMenuStore = defineStore('frontendMenu',{
state: (): FrontendMenu => ({
arrayRouter: [],
treeRouter: [],
frameInRoutes:[],
frameOutRoutes:[]
}),
actions:{
async requestMenu(){
return request({
url: '/api/system/menu/web_router/',
method: 'get',
params:{},
}).then((res:any)=>{
return res.data
});
},
async handleRouter(){
const menuData = await this.requestMenu();
this.arrayRouter = menuData
const {frameIn,frameOut} = handleMenu(menuData);
this.treeRouter = [...frameIn,...frameOut]
this.frameInRoutes=frameIn
this.frameOutRoutes=frameOut
},
async getRouter(){
await this.handleRouter()
return {
frameInRoutes:this.frameInRoutes,
frameOutRoutes:this.frameOutRoutes,
treeRouter:this.treeRouter
}
}
}
})

View File

@@ -2,11 +2,12 @@
* 定义接口来定义对象的类型
* `stores` 全部类型定义在这里
*/
import {useFrontendMenuStore} from "/@/stores/frontendMenu";
// 用户信息
export interface UserInfosState {
avatar: string;
userName: string;
username: string;
name: string;
email: string;
mobile: string;
@@ -19,6 +20,7 @@ export interface UserInfosState {
}
export interface UserInfosStates {
userInfos: UserInfosState;
isSocketOpen: boolean
}
// 路由缓存列表
@@ -101,3 +103,12 @@ export interface DictionaryStates {
export interface ConfigStates {
systemConfig: any;
}
export interface FrontendMenu {
arrayRouter: Array<any>;
treeRouter:Array<any>;
frameOutRoutes:Array<any>;
frameInRoutes:Array<any>;
}

View File

@@ -0,0 +1,30 @@
import {defineStore} from "pinia";
import {request} from "/@/utils/service";
import XEUtils from "xe-utils";
import {toRaw} from 'vue'
export const useDeptInfoStore = defineStore('deptInfo', {
state:()=>(
{
list:[],
tree:[],
}
),
actions:{
async requestDeptInfo() {
// 请求部门信息
const ret = await request({
url: '/api/system/dept/all_dept/'
})
this.list = ret.data
this.tree = XEUtils.toArrayTree(ret.data,{parentKey:'parent',strict:true})
},
async getDeptById(id:any){
},
async getParentDeptById(id: any){
const tree = toRaw(this.tree)
const obj = XEUtils.findTree(tree, item => item.id == id)
return obj
}
}
})

View File

@@ -22,5 +22,8 @@ export const useRoutesList = defineStore('routesList', {
async setColumnsNavHover(bool: Boolean) {
this.isColumnsNavHover = bool;
},
async addRoutesList(data: Array<string>) {
this.routesList.push(data);
}
},
});

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