301 Commits
Author SHA1 Message Date
猿小天 ff7333d882 1.删除旧代码 2023-10-29 21:02:53 +08:00
猿小天 52de562fac Merge remote-tracking branch 'origin/master' 2023-10-29 20:28:59 +08:00
猿小天 9cb3570f5a 1.完成初始化的配置 2023-10-29 20:28:45 +08:00
猿小天 75670f9a5e 1.完成初始化的配置 2023-10-29 20:09:56 +08:00
猿小天 212af88409 1.完成新版列字段授权 2023-10-29 11:16:34 +08:00
猿小天 1b71ba156b 1.完成新版接口授权 2023-10-29 00:43:44 +08:00
猿小天 10b159aa15 1.完成新版接口授权 2023-10-29 00:33:07 +08:00
猿小天 b7b589176a 1.完成新版菜单授权 2023-10-28 19:00:35 +08:00
猿小天 bd8fef9a04 1.完成新版菜单管理 2023-10-27 16:59:25 +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 967e4e76c2 Merge remote-tracking branch 'origin/master' 2023-04-14 23:08:56 +08:00
H0nGzA1 f7b93c349e fix: 🐛 部门删除bug 2023-04-14 23:04:30 +08:00
H0nGzA1 72f1d2eae7 feat: 用户管理细节优化 2023-04-14 23:04:30 +08:00
H0nGzA1 592bc947a0 feat: 账号锁定功能 2023-04-14 23:04:30 +08:00
H0nGzA1 953fcc3437 fix: 🐛 修复添加用户bug 2023-04-14 23:04:30 +08:00
H0nGzA1 13aa6dbb99 fix: 🐛 头像更新问题 2023-04-14 23:04:30 +08:00
猿小天 460ae171dd fix(文件上传): 🐛 文件上传 2023-04-14 23:04:30 +08:00
H0nGzA1 0d556bfb2b fix: 🐛 头像上传,文件上传问题 2023-04-14 23:04:29 +08:00
猿小天 61ae6e8eb3 refactor(所有页面): ♻️ 所有页面的按钮权限配置 2023-04-14 23:04:29 +08:00
猿小天 175b151f7f refactor(所有页面): ♻️ 所有页面的表格列宽优化 2023-04-14 23:04:29 +08:00
H0nGzA1 a50f73d466 refactor: ♻️ 重构部门管理前端 2023-04-14 23:04:29 +08:00
猿小天 eea9d320af fix(登录): 登录优化 2023-04-14 23:04:29 +08:00
猿小天 15c4808bbb refactor(登录页面): ♻️ 登录优化 2023-04-14 23:04:29 +08:00
H0nGzA1 d2d6ba3460 refactor: ♻️ 重构菜单管理前端 2023-04-14 23:04:29 +08:00
猿小天 4f0295acb1 refactor(用户管理): ♻️ 用户管理优化 2023-04-14 23:04:29 +08:00
H0nGzA1 255c405e59 feat: 前端自动注册插件功能 2023-04-14 23:04:29 +08:00
H0nGzA1 96ad956efd refactor: ♻️ 更新优化精简fast-crud结构 2023-04-14 23:04:29 +08:00
H0nGzA1 b586b46016 feat: 更新fast-crud版本以及依赖 2023-04-14 23:04:29 +08:00
H0nGzA1 875146e588 feat: 所有菜单页面状态改为可编辑开关 2023-04-14 23:04:29 +08:00
H0nGzA1 1ee709b9eb feat: 所有菜单页面样式优化~ 2023-04-14 23:04:29 +08:00
H0nGzA1 f89a5228cd refactor: ♻️ 菜单管理重构完成 2023-04-14 23:04:29 +08:00
H0nGzA1 795f621637 feat: 精简化初始化菜单json文件 2023-04-14 23:04:29 +08:00
H0nGzA1 c5d7a70f46 feat: 登录页面,自动刷新验证码 2023-04-14 23:04:29 +08:00
H0nGzA1 8710b047b1 feat: 登录页面回车登录功能 2023-04-14 23:04:28 +08:00
H0nGzA1 300b6c6bb8 refactor: ♻️ 菜单管理重构完成 2023-04-14 23:04:28 +08:00
raymond 296640cb2a feat: 支持通过python manage.py createsuperuser创建管理员用户 2023-04-14 23:04:28 +08:00
H0nGzA1 24861fda42 refactor: ♻️ 用户管理重构 2023-04-14 23:04:28 +08:00
H0nGzA1 0e3fac37e9 feat: 🚀 更新nginx配置文件 2023-04-14 23:04:28 +08:00
H0nGzA1 93d8d94049 feat: 🔧 tailwind css 配置 2023-04-14 23:04:28 +08:00
victory 9d4f007d48 feat:添加对url中排序参数的解析 2023-04-14 23:04:28 +08:00
victory 4d17a7d9df fix:优化角色管理用户管理和接口白名单的页面显示 2023-04-14 23:04:28 +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
李强 c126c704a4 新功能: 删除gunicorn.pid 2023-03-31 10:31:38 +08:00
李强 662c314518 Merge remote-tracking branch 'origin/master' 2023-03-31 10:31:03 +08:00
李强 1715fcb4d8 新功能: 完善日志信息 2023-03-31 10:30:39 +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
H0nGzA1 a304a49107 feat: 引入tailwind css样式 2023-03-23 18:08:41 +08:00
H0nGzA1 fc1ab98b2b build: ⬆️ 更新fast-crud版本 2023-03-23 12:44:09 +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
H0nGzA1 92f57f8608 build: ⬆️ 更新fast-crud版本 2023-03-22 21:08:43 +08:00
H0nGzA1 5ab2aaa066 fix(修复登录界面验证码关闭issue): 🐛 https://gitee.com/huge-dream/django-vue3-admin/issues/I6OS75 2023-03-22 19:56:54 +08:00
H0nGzA1 b5f50bdf30 fix(修复登录界面验证码关闭issue): 🐛 https://gitee.com/huge-dream/django-vue3-admin/issues/I6OS75 2023-03-21 18:01:42 +08:00
H0nGzA1 256c6e4ab9 fix(修复issue): 🐛 https://gitee.com/huge-dream/django-vue3-admin/issues/I6OS1E 2023-03-21 13:43:24 +08:00
177 changed files with 9833 additions and 16251 deletions
Vendored
BIN
View File
Binary file not shown.
+1
View File
@@ -4,3 +4,4 @@
.history/
.vscode/
web/package-lock.json
+1 -1
View File
@@ -2,7 +2,7 @@
[![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)
[preview](https://demo.django-vue-admin.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)
[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」**
+1 -1
View File
@@ -2,7 +2,7 @@
[![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.django-vue-admin.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)
[预 览](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)
+2 -1
View File
@@ -91,9 +91,10 @@ ENV/
**/migrations/*.py
!**/migrations/__init__.py
*.pyc
conf/
conf/*
!conf/env.example.py
db.sqlite3
media/
__pypackages__/
package-lock.json
gunicorn.pid
+1 -2
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 是该路由的消费者
]
+66 -47
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
@@ -43,6 +44,9 @@ DEBUG = locals().get("DEBUG", True)
ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
# Application definition
CUSTOM_APPS = [
"dvadmin.system",
]
INSTALLED_APPS = [
"django.contrib.auth",
@@ -54,10 +58,10 @@ INSTALLED_APPS = [
"rest_framework",
"django_filters",
"corsheaders", # 注册跨域app
"dvadmin.system",
"drf_yasg",
"captcha",
'channels',
*locals().get("CUSTOM_APPS", []), # 所有项目里写的app需要在env.py文件里的CUSTOM_APPS中
]
MIDDLEWARE = [
@@ -165,15 +169,16 @@ CORS_ORIGIN_ALLOW_ALL = True
# 允许cookie
CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中,后端是否支持对cookie的操作
# ================================================= #
# ===================================================== #
# ********************* channels配置 ******************* #
# ================================================= #
# ===================================================== #
ASGI_APPLICATION = 'application.asgi.application'
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}
REDIS_URL = locals().get('REDIS_URL', "")
# CHANNEL_LAYERS = {
# 'default': {
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
@@ -187,69 +192,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"
},
},
}
@@ -261,6 +280,7 @@ LOGGING = {
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.MultiPartParser',
),
"DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S", # 日期时间格式配置
"DATE_FORMAT": "%Y-%m-%d",
@@ -288,11 +308,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),
# 设置前缀
@@ -328,11 +346,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 = (
@@ -382,10 +400,11 @@ TENANT_SHARED_APPS = []
PLUGINS_URL_PATTERNS = []
# ********** 一键导入插件配置开始 **********
# 例如:
# from dvadmin_upgrade_center.settings import * # 升级中心
#from dvadmin3_upgrade_center.settings import * # 升级中心
# from dvadmin_celery.settings import * # celery 异步任务
# from dvadmin_third.settings import * # 第三方用户管理
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
# from dvadmin_tenants.settings import * # 租户管理
# from dvadmin_uniapp.settings import *
# ...
# ********** 一键导入插件配置结束 **********
+4
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)
+10 -8
View File
@@ -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}
}
)
+3 -3
View File
@@ -44,6 +44,6 @@ LOGIN_NO_CAPTCHA_AUTH = True
# ================================================= #
ALLOWED_HOSTS = ["*"]
# daphne启动命令
#daphne application.asgi:application -b 0.0.0.0 -p 8000
CUSTOM_APPS = [
"dvadmin.system",
]
+1 -1
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)):
+1 -1
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
@@ -6,8 +6,11 @@ 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, RoleApiPermission, Columns
)
from dvadmin.utils.serializers import CustomModelSerializer
@@ -39,25 +42,13 @@ class UsersInitSerializer(CustomModelSerializer):
}
class MenuButtonInitSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化器
"""
class Meta:
model = MenuButton
fields = ['id', 'name', 'value', 'api', 'method', 'menu']
read_only_fields = ["id"]
class MenuInitSerializer(CustomModelSerializer):
"""
递归深度获取数信息(用于生成初始化json文件)
"""
name = serializers.CharField(required=False)
name = serializers.CharField(required=True)
menu_type = serializers.IntegerField(required=True)
children = serializers.SerializerMethodField()
menu_button = serializers.SerializerMethodField()
def get_children(self, obj: Menu):
data = []
@@ -67,26 +58,18 @@ class MenuInitSerializer(CustomModelSerializer):
data = serializer.data
return data
def get_menu_button(self, obj: Menu):
data = []
instance = obj.menuPermission.order_by('method')
if instance:
data = list(instance.values('name', 'value', 'api', 'method'))
return data
def save(self, **kwargs):
instance = super().save(**kwargs)
children = self.initial_data.get('children')
menu_button = self.initial_data.get('menu_button')
# 菜单表
if children:
for menu_data in children:
menu_data['parent'] = instance.id
filter_data = {
"name": menu_data['name'],
"web_path": menu_data['web_path'],
"component": menu_data['component'],
"component_name": menu_data['component_name'],
"menu_type": menu_data['menu_type'],
}
instance_obj = Menu.objects.filter(**filter_data).first()
if instance_obj and not self.initial_data.get('reset'):
@@ -94,24 +77,12 @@ class MenuInitSerializer(CustomModelSerializer):
serializer = MenuInitSerializer(instance_obj, data=menu_data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
# 菜单按钮
if menu_button:
for menu_button_data in menu_button:
menu_button_data['menu'] = instance.id
filter_data = {
"menu": menu_button_data['menu'],
"value": menu_button_data['value']
}
instance_obj = MenuButton.objects.filter(**filter_data).first()
serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_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']
fields = ['name', 'icon', 'sort', 'is_link', 'menu_type', 'web_path', 'component', 'component_name', 'status',
'cache', 'visible', 'parent', 'children', 'creator', 'dept_belong_id']
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
@@ -126,7 +97,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 +110,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 +125,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},
@@ -164,38 +135,62 @@ class RoleMenuInitSerializer(CustomModelSerializer):
}
class RoleMenuButtonInitSerializer(CustomModelSerializer):
class RoleApiPermissionInitSerializer(CustomModelSerializer):
"""
初始化角色菜单按钮(用于生成初始化json文件)
初始化角色接口权限(用于生成初始化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)
name = serializers.CharField(max_length=255, required=True)
api = serializers.CharField(max_length=255, required=True)
method = serializers.IntegerField(required=True)
data_range = serializers.CharField(max_length=100, required=False)
def create(self, validated_data):
init_data = self.initial_data
validated_data.pop('menu_button_value')
validated_data.pop('role_key')
role_id = Role.objects.filter(key=init_data['role_key']).first()
menu_button_id = MenuButton.objects.filter(value=init_data['menu_button_value']).first()
validated_data['role'] = role_id
validated_data['menu_button'] = menu_button_id
instance = super().create(validated_data)
instance.dept.set([])
return instance
class Meta:
model = RoleMenuButtonPermission
fields = ['role_key','menu_button_value','data_range','dept','creator', 'dept_belong_id']
model = RoleApiPermission
fields = ['role_key', 'name','api','method', 'data_range', 'dept', 'creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
'role': {'required': False},
'menu': {'required': False},
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
}
class RoleColumnInitSerializer(CustomModelSerializer):
"""
初始化角色字段权限(用于生成初始化json文件)
"""
role_key = serializers.CharField(max_length=100, required=True)
app = serializers.CharField(max_length=255, required=True)
model = serializers.CharField(max_length=255, required=True)
field_name = serializers.CharField(max_length=255, required=True)
title = serializers.CharField(max_length=255, required=True)
def create(self, validated_data):
init_data = self.initial_data
validated_data.pop('role_key')
role_id = Role.objects.filter(key=init_data['role_key']).first()
validated_data['role'] = role_id
instance = super().create(validated_data)
return instance
class Meta:
model = Columns
fields = ['role_key', 'app','model','field_name', 'title']
read_only_fields = ["id"]
extra_kwargs = {
'role': {'required': False},
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
}
class ApiWhiteListInitSerializer(CustomModelSerializer):
"""
@@ -0,0 +1,123 @@
[
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "id",
"title": "Id",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "description",
"title": "描述",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "creator",
"title": "创建人",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "modifier",
"title": "修改人",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "dept_belong_id",
"title": "数据归属部门",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "update_datetime",
"title": "修改时间",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "create_datetime",
"title": "创建时间",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "name",
"title": "角色名称",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "key",
"title": "权限字符",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "sort",
"title": "角色顺序",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "status",
"title": "角色状态",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
}
]
File diff suppressed because it is too large Load Diff
@@ -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
}
]
@@ -0,0 +1,14 @@
[
{
"role_key": "admin",
"name": "菜单列表查询",
"api": "/api/system/menu/",
"method": 0
},
{
"role_key": "public",
"name": "菜单列表查询",
"api": "/api/system/menu/",
"method": 0
}
]
@@ -1,12 +0,0 @@
[
{
"role_key": "admin",
"menu_button_value": "menu:Search",
"data_range": 0
},
{
"role_key": "public",
"menu_button_value":"menu:Search",
"data_range": 0
}
]
@@ -29,6 +29,7 @@
"gender": 1,
"user_type": 0,
"role": [],
"dept_key": "dvadmin",
"first_name": "",
"last_name": "",
"is_staff": true,
+15 -7
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, RoleApiPermissionInitSerializer, RoleColumnInitSerializer
)
class Initialize(CoreInitialize):
@@ -37,7 +39,7 @@ class Initialize(CoreInitialize):
"""
初始化菜单信息
"""
self.init_base(MenuInitSerializer, unique_fields=['name', 'web_path', 'component', 'component_name'])
self.init_base(MenuInitSerializer, unique_fields=['name'])
def init_role_menu(self):
"""
@@ -45,12 +47,17 @@ class Initialize(CoreInitialize):
"""
self.init_base(RoleMenuInitSerializer, unique_fields=['role', 'menu'])
def init_role_menu_button(self):
def init_role_api_permission(self):
"""
初始化角色菜单按钮信息
"""
self.init_base(RoleMenuButtonInitSerializer, unique_fields=['role', 'menu_button'])
self.init_base(RoleApiPermissionInitSerializer, unique_fields=['role', 'api','name'])
def init_role_column(self):
"""
初始化角色字段权限
"""
self.init_base(RoleColumnInitSerializer, unique_fields=['app','model','field_name'])
def init_api_white_list(self):
"""
@@ -76,7 +83,8 @@ class Initialize(CoreInitialize):
self.init_users()
self.init_menu()
self.init_role_menu()
self.init_role_menu_button()
self.init_role_api_permission()
self.init_role_column()
self.init_api_white_list()
self.init_dictionary()
self.init_system_config()
+85 -50
View File
@@ -1,16 +1,38 @@
import hashlib
import os
from django.contrib.auth.models import AbstractUser
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
from dvadmin.utils.models import CoreModel, table_prefix, get_custom_app_models
STATUS_CHOICES = (
(0, "禁用"),
(1, "启用"),
)
class Role(CoreModel):
name = models.CharField(max_length=64, verbose_name="角色名称", help_text="角色名称")
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="角色状态")
class Meta:
db_table = table_prefix + "system_role"
verbose_name = "角色表"
verbose_name_plural = verbose_name
ordering = ("sort",)
class CustomUserManager(UserManager):
def create_superuser(self, username, email=None, password=None, **extra_fields):
user = super(CustomUserManager, self).create_superuser(username, email, password, **extra_fields)
user.set_password(password)
try:
user.role.add(Role.objects.get(name="管理员"))
user.save(using=self._db)
return user
except ObjectDoesNotExist:
user.delete()
raise ValidationError("角色`管理员`不存在, 创建失败, 请先执行python manage.py init")
class Users(CoreModel, AbstractUser):
@@ -48,6 +70,7 @@ class Users(CoreModel, AbstractUser):
blank=True,
help_text="关联部门",
)
objects = CustomUserManager()
def set_password(self, raw_password):
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
@@ -76,24 +99,9 @@ class Post(CoreModel):
ordering = ("sort",)
class Role(CoreModel):
name = models.CharField(max_length=64, verbose_name="角色名称", help_text="角色名称")
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"
verbose_name = "角色表"
verbose_name_plural = verbose_name
ordering = ("sort",)
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="联系电话")
@@ -147,22 +155,24 @@ class Menu(CoreModel):
help_text="上级菜单",
)
icon = models.CharField(max_length=64, verbose_name="菜单图标", null=True, blank=True, help_text="菜单图标")
name = models.CharField(max_length=64, verbose_name="菜单名称", help_text="菜单名称")
name = models.CharField(max_length=64, verbose_name="目录名称/菜单名称/按钮名称", help_text="目录名称/菜单名称/按钮名称")
sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序")
ISLINK_CHOICES = (
(0, ""),
(1, ""),
)
is_link = models.BooleanField(default=False, verbose_name="是否外链", 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="组件地址")
MENU_TYPE_CHOICES =(
(0, "目录"),
(1, "菜单"),
(2, "按钮"),
)
menu_type = models.IntegerField(default=0, verbose_name="菜单类型", help_text="菜单类型")
web_path = models.CharField(max_length=128,default="/", verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
component = models.CharField(max_length=200, verbose_name="组件地址/按钮权限值", null=True, blank=True, help_text="组件地址/按钮权限值")
component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True,
help_text="组件名称")
status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态")
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示",
help_text="侧边栏中是否显示")
frame_out = models.BooleanField(default=False, blank=True, verbose_name="是否主框架外", help_text="是否主框架外")
class Meta:
db_table = table_prefix + "system_menu"
@@ -171,6 +181,23 @@ class Menu(CoreModel):
ordering = ("sort",)
class Columns(CoreModel):
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
app = models.CharField(max_length=64, verbose_name='应用名')
model = models.CharField(max_length=64, verbose_name='表名')
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
title = models.CharField(max_length=64, verbose_name='字段显示名')
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_columns"
verbose_name = "列权限表"
verbose_name_plural = verbose_name
ordering = ("id",)
class MenuButton(CoreModel):
menu = models.ForeignKey(
to="Menu",
@@ -221,28 +248,28 @@ 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):
class RoleApiPermission(CoreModel):
role = models.ForeignKey(
to="Role",
db_constraint=False,
related_name="role_menu_button",
related_name="role_api",
on_delete=models.CASCADE,
verbose_name="关联角色",
help_text="关联角色",
)
menu_button = models.ForeignKey(
to="MenuButton",
db_constraint=False,
related_name="menu_button_permission",
on_delete=models.CASCADE,
verbose_name="关联菜单按钮",
help_text="关联菜单按钮",
null=True,
blank=True
name = models.CharField(max_length=64, verbose_name="名称", help_text="名称")
api = models.CharField(max_length=200, verbose_name="接口地址", help_text="接口地址")
METHOD_CHOICES = (
(0, "GET"),
(1, "POST"),
(2, "PUT"),
(3, "DELETE"),
)
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
help_text="接口请求方法")
DATASCOPE_CHOICES = (
(0, "仅本人数据权限"),
(1, "本部门及以下数据权限"),
@@ -256,8 +283,8 @@ class RoleMenuButtonPermission(CoreModel):
help_text="数据权限-关联部门")
class Meta:
db_table = table_prefix + "role_menu_button_permission"
verbose_name = "角色按钮权限表"
db_table = table_prefix + "role_api_permission"
verbose_name = "角色接口权限表"
verbose_name_plural = verbose_name
ordering = ("-create_datetime",)
@@ -274,8 +301,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",
@@ -339,12 +365,16 @@ class OperationLog(CoreModel):
def media_file_name(instance, filename):
h = instance.md5sum
basename, ext = os.path.splitext(filename)
return os.path.join("files", h[0:1], h[1:2], h + ext.lower())
return os.path.join("files", h[:1], h[1:2], h + ext.lower())
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):
@@ -353,6 +383,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:
@@ -422,7 +457,7 @@ class SystemConfig(CoreModel):
help_text="父级",
)
title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")
key = models.CharField(max_length=20, verbose_name="", help_text="", db_index=True)
key = models.CharField(max_length=50, verbose_name="", help_text="", db_index=True)
value = models.JSONField(max_length=100, verbose_name="", help_text="", null=True, blank=True)
sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True)
status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态")
+5 -7
View File
@@ -8,18 +8,17 @@ from dvadmin.system.views.dictionary import DictionaryViewSet
from dvadmin.system.views.file_list import FileViewSet
from dvadmin.system.views.login_log import LoginLogViewSet
from dvadmin.system.views.menu import MenuViewSet
from dvadmin.system.views.menu_button import MenuButtonViewSet
from dvadmin.system.views.message_center import MessageCenterViewSet
from dvadmin.system.views.operation_log import OperationLogViewSet
from dvadmin.system.views.role import RoleViewSet
from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
from dvadmin.system.views.role_api_permission import RoleApiPermissionViewSet
from dvadmin.system.views.system_config import SystemConfigViewSet
from dvadmin.system.views.user import UserViewSet
from dvadmin.system.views.column import ColumnViewSet
system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet)
system_url.register(r'menu_button', MenuButtonViewSet)
system_url.register(r'role', RoleViewSet)
system_url.register(r'dept', DeptViewSet)
system_url.register(r'user', UserViewSet)
@@ -29,11 +28,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'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
system_url.register(r'message_center', MessageCenterViewSet)
system_url.register(r'role_api_permission', RoleApiPermissionViewSet)
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
system_url.register(r'column', ColumnViewSet)
urlpatterns = [
+93
View File
@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Columns, Role
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 ColumnSerializer(CustomModelSerializer):
"""
列权限序列化器
"""
class Meta:
model = Columns
fields = '__all__'
read_only_fields = ['id']
class ColumnViewSet(CustomModelViewSet):
"""
列权限视图集
"""
queryset = Columns.objects.all()
serializer_class = ColumnSerializer
def list(self, request, *args, **kwargs):
role_id = request.query_params.get('role')
app_name = request.query_params.get('app')
model_name = request.query_params.get('model')
if not role_id or not model_name or not app_name:
return ErrorResponse(msg="参数错误")
queryset = Columns.objects.filter(role_id=role_id, model=model_name, app=app_name)
serializer = ColumnSerializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功")
def create(self, request, *args, **kwargs):
payload = request.data
for model in get_custom_app_models(payload.get('app')):
if payload.get('model') == model['model']:
break
else:
return ErrorResponse(msg='模型表不存在')
if Columns.objects.filter(app=model['app'], model=model['model'], 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 app in get_custom_app_models():
for model in app:
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):
"""自动匹配已有的字段"""
role_id = request.data.get('role')
app_name = request.data.get('app')
model_name = request.data.get('model')
if not role_id or not model_name or not app_name:
return DetailResponse([], msg='无操作')
for model in get_custom_app_models(app_name):
if model['model'] != model_name:
continue
for field in model['fields']:
if Columns.objects.filter(
role_id=role_id, app=app_name, model=model_name, field_name=field['name']
).exists():
continue
data = {
'role': role_id,
'app': app_name,
'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 DetailResponse(msg='匹配成功')
+98 -13
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, RoleApiPermission, 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()
@@ -123,33 +127,114 @@ class DeptViewSet(CustomModelViewSet):
data = serializer.data
return SuccessResponse(data=data)
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated], extra_filter_class=[])
def dept_lazy_tree(self, request, *args, **kwargs):
parent = self.request.query_params.get('parent')
is_superuser = request.user.is_superuser
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 = RoleApiPermission.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)
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)
+25 -8
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):
params = self.request.query_params
parent = params.get('parent', None)
if params:
if parent:
queryset = self.queryset.filter(status=1, parent=parent)
if self.action =='list':
params = self.request.query_params
parent = params.get('parent', None)
if params:
if parent:
queryset = self.queryset.filter(parent=parent)
else:
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:
queryset = self.queryset.filter(status=1, parent__isnull=True)
return queryset
return self.queryset
class InitDictionaryViewSet(APIView):
+47 -4
View File
@@ -1,6 +1,12 @@
from rest_framework import serializers
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
@@ -9,15 +15,52 @@ class FileSerializer(CustomModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
def get_url(self, instance):
return '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)
+9 -6
View File
@@ -1,11 +1,9 @@
import base64
import hashlib
from datetime import datetime, timedelta
from captcha.views import CaptchaStore, captcha_image
from django.contrib import auth
from django.contrib.auth import login
from django.contrib.auth.hashers import make_password, check_password
from django.shortcuts import redirect
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
@@ -14,9 +12,7 @@ from rest_framework import serializers
from rest_framework.views import APIView
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
from django.conf import settings
from application import dispatch
from dvadmin.system.models import Users
from dvadmin.utils.json_response import ErrorResponse, DetailResponse
@@ -58,6 +54,7 @@ class LoginSerializer(TokenObtainPairSerializer):
captcha = serializers.CharField(
max_length=6, required=False, allow_null=True, allow_blank=True
)
class Meta:
model = Users
fields = "__all__"
@@ -79,13 +76,18 @@ class LoginSerializer(TokenObtainPairSerializer):
raise CustomValidationError("验证码过期")
else:
if self.image_code and (
self.image_code.response == captcha
or self.image_code.challenge == captcha
self.image_code.response == captcha
or self.image_code.challenge == captcha
):
self.image_code and self.image_code.delete()
else:
self.image_code and self.image_code.delete()
raise CustomValidationError("图片验证码错误")
user = Users.objects.get(username=attrs['username'])
if not user.is_active:
raise CustomValidationError("账号被锁定")
data = super().validate(attrs)
data["name"] = self.user.name
data["userId"] = self.user.id
@@ -107,6 +109,7 @@ class LoginSerializer(TokenObtainPairSerializer):
save_login_log(request=request)
return {"code": 2000, "msg": "请求成功", "data": data}
class LoginView(TokenObtainPairView):
"""
登录接口
+96 -36
View File
@@ -8,9 +8,10 @@
"""
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Menu, MenuButton, RoleMenuPermission
from dvadmin.utils.json_response import SuccessResponse
from dvadmin.system.models import Menu, RoleMenuPermission
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -20,16 +21,17 @@ class MenuSerializer(CustomModelSerializer):
菜单表的简单序列化器
"""
menuPermission = serializers.SerializerMethodField(read_only=True)
hasChild = serializers.SerializerMethodField()
hasChildren = 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:
return None
def get_hasChild(self, instance):
def get_hasChildren(self, instance):
hasChild = Menu.objects.filter(parent=instance.id)
if hasChild:
return True
@@ -47,15 +49,18 @@ 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__"
read_only_fields = ["id"]
class WebRouterSerializer(CustomModelSerializer):
"""
前端菜单路由的简单序列化器
@@ -63,11 +68,11 @@ class WebRouterSerializer(CustomModelSerializer):
path = serializers.CharField(source="web_path")
title = serializers.CharField(source="name")
class Meta:
model = Menu
fields = ('id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', 'web_path', 'component',
'component_name', 'cache', 'visible')
fields = (
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'menu_type', 'web_path', 'component',
'component_name', 'cache', 'visible', 'status')
read_only_fields = ["id"]
@@ -85,14 +90,62 @@ class MenuViewSet(CustomModelViewSet):
create_serializer_class = MenuCreateSerializer
update_serializer_class = MenuCreateSerializer
search_fields = ['name', 'status']
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog']
# extra_filter_class = []
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'menu_type']
@action(methods=['get'], detail=False)
def tree(self, request):
"""懒加载"""
params = request.query_params
menu_type = params.get('menu_type', 0)
queryset = Menu.objects.filter(menu_type=menu_type).order_by('sort')
serializer = MenuSerializer(queryset, many=True, request=request)
data = serializer.data
return DetailResponse(data=data)
@action(methods=['get'], detail=True)
def getChildren(self,request,pk):
"""
获取子菜单,用于菜单页面的懒加载
"""
queryset = Menu.objects.filter(parent=pk)
serializer = MenuSerializer(queryset, many=True, request=request)
data = serializer.data
return DetailResponse(data=data)
@action(methods=['GET'], detail=False, permission_classes=[])
def web_router(self, request):
"""用于前端获取当前角色的路由"""
user = request.user
queryset = self.queryset.filter(status=1)
if user.is_superuser:
queryset = self.queryset.filter(status=1,menu_type__in=[0,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,menu_type__in=[0,1])
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=[IsAuthenticated])
def menu_button_all_permission(self, request):
"""
获取所有的按钮权限
:param request:
:return:
"""
is_superuser = request.user.is_superuser
if is_superuser:
queryset = Menu.objects.filter(menu_type=2).values_list('component', flat=True)
else:
role_id = request.user.role.values_list('id', flat=True)
queryset = Menu.objects.filter(role__in=role_id,menu_type=2).values_list('component',flat=True).distinct()
return DetailResponse(data=queryset)
@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')
@@ -101,25 +154,32 @@ class MenuViewSet(CustomModelViewSet):
data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功")
def list(self,request):
"""懒加载"""
request.query_params._mutable = True
params = request.query_params
parent = params.get('parent', None)
page = params.get('page',None)
limit = params.get('limit', None)
if page:
del params['page']
if limit:
del params['limit']
if params:
if parent:
queryset = self.queryset.filter(status=1, parent=parent)
else:
queryset = self.queryset.filter(status=1)
else:
queryset = self.queryset.filter(status=1, parent__isnull=True)
queryset = self.filter_queryset(queryset)
serializer = MenuSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data)
@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="下移成功")
@@ -1,72 +0,0 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理
"""
from django.db.models import F
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.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
class MenuButtonSerializer(CustomModelSerializer):
"""
菜单按钮-序列化器
"""
class Meta:
model = MenuButton
fields = ['id', 'name', 'value', 'api', 'method']
read_only_fields = ["id"]
class MenuButtonCreateUpdateSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化器
"""
class Meta:
model = MenuButton
fields = "__all__"
read_only_fields = ["id"]
class MenuButtonViewSet(CustomModelViewSet):
"""
菜单按钮接口
list:查询
create:新增
update:修改
retrieve:单例
destroy:删除
"""
queryset = MenuButton.objects.all()
serializer_class = MenuButtonSerializer
create_serializer_class = MenuButtonCreateUpdateSerializer
update_serializer_class = MenuButtonCreateUpdateSerializer
extra_filter_class = []
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
def menu_button_all_permission(self,request):
"""
获取所有的按钮权限
:param request:
: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:
queryset = MenuButton.objects.values_list('value',flat=True)
else:
role_id = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_id).values_list('menu_button__value',flat=True).distinct()
return DetailResponse(data=queryset)
+13 -13
View File
@@ -85,7 +85,7 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
def get_is_read(self, instance):
user_id = self.request.user.id
message_center_id = instance.id
queryset = MessageCenterTargetUser.objects.filter(messagecenter__id=message_center_id,users_id=user_id).first()
queryset = MessageCenterTargetUser.objects.filter(messagecenter__id=message_center_id, users_id=user_id).first()
if queryset:
return queryset.is_read
return False
@@ -95,21 +95,22 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
fields = "__all__"
read_only_fields = ["id"]
def websocket_push(user_id, message):
"""
主动推送消息
"""
username = "user_"+str(user_id)
print(103,message)
username = "user_" + str(user_id)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
username,
{
"type": "push.message",
"json": message
}
username,
{
"type": "push.message",
"json": message
}
)
class MessageCenterCreateSerializer(CustomModelSerializer):
"""
消息中心-新增-序列化器
@@ -122,10 +123,10 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
# 在保存之前,根据目标类型,把目标用户查询出来并保存
users = initial_data.get('target_user', [])
if target_type in [1]: # 按角色
target_role = initial_data.get('target_role',[])
target_role = initial_data.get('target_role', [])
users = Users.objects.filter(role__id__in=target_role).values_list('id', flat=True)
if target_type in [2]: # 按部门
target_dept = initial_data.get('target_dept',[])
target_dept = initial_data.get('target_dept', [])
users = Users.objects.filter(dept__id__in=target_dept).values_list('id', flat=True)
if target_type in [3]: # 系统通知
users = Users.objects.values_list('id', flat=True)
@@ -141,7 +142,7 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
for user in users:
unread_count = MessageCenterTargetUser.objects.filter(users__id=user, is_read=False).count()
websocket_push(user, message={"sender": 'system', "contentType": 'SYSTEM',
"content": '您有一条新消息~', "unread": unread_count})
"content": '您有一条新消息~', "unread": unread_count})
return data
class Meta:
@@ -184,7 +185,7 @@ class MessageCenterViewSet(CustomModelViewSet):
# 主动推送消息
unread_count = MessageCenterTargetUser.objects.filter(users__id=user_id, is_read=False).count()
websocket_push(user_id, message={"sender": 'system', "contentType": 'TEXT',
"content": '您查看了一条消息~', "unread": unread_count})
"content": '您查看了一条消息~', "unread": unread_count})
return DetailResponse(data=serializer.data, msg="获取成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
@@ -195,7 +196,6 @@ class MessageCenterViewSet(CustomModelViewSet):
self_user_id = self.request.user.id
# queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('-create_datetime')
queryset = MessageCenter.objects.filter(target_user__id=self_user_id)
print(queryset)
# queryset = self.filter_queryset(queryset)
page = self.paginate_queryset(queryset)
if page is not None:
+26 -18
View File
@@ -13,8 +13,8 @@ from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Role, Menu, MenuButton, Dept
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,16 +32,12 @@ class RoleSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class RoleCreateUpdateSerializer(CustomModelSerializer):
"""
角色管理 创建/更新时的列化器
"""
menu = MenuSerializer(many=True, read_only=True)
dept = DeptSerializer(many=True, read_only=True)
permission = MenuButtonSerializer(many=True, read_only=True)
key = serializers.CharField(max_length=50,
validators=[CustomUniqueValidator(queryset=Role.objects.all(), message="权限字符必须唯一")])
name = serializers.CharField(max_length=50, validators=[CustomUniqueValidator(queryset=Role.objects.all())])
@@ -61,28 +57,40 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
fields = '__all__'
class MenuPermissonSerializer(CustomModelSerializer):
class MenuPermissionSerializer(CustomModelSerializer):
"""
菜单的按钮权限
"""
menuPermission = serializers.SerializerMethodField()
def get_menuPermission(self, instance):
is_superuser = self.request.user.is_superuser
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)
return serializer.data
class Meta:
model = Menu
fields = ['id', 'parent', 'name', 'menuPermission']
fields = ['id', 'parent', 'name']
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:查询
@@ -0,0 +1,245 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/3 003 0:30
@Remark: 接口权限管理
"""
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 RoleApiPermission, Menu, MenuButton, Dept, RoleMenuPermission, Columns
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
class RoleApiPermissionSerializer(CustomModelSerializer):
"""
接口权限-序列化器
"""
dept_name = serializers.SerializerMethodField(help_text="部门名称")
def get_dept_name(self, instance):
dept_name_list = instance.dept.values_list("name",flat=True)
return ",".join(dept_name_list)
class Meta:
model = RoleApiPermission
fields = "__all__"
read_only_fields = ["id"]
class RoleApiPermissionCreateUpdateSerializer(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 = RoleApiPermission
fields = "__all__"
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 RoleApiPermission.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 = RoleApiPermission.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 RoleColumnsSerializer(CustomModelSerializer):
class Meta:
model = Columns
fields = "__all__"
class RoleMenuPermissionSerializer(CustomModelSerializer):
"""
菜单和按钮权限
"""
isCheck = serializers.SerializerMethodField()
btns = serializers.SerializerMethodField()
columns = serializers.SerializerMethodField()
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):
params = self.request.query_params
col_list = Columns.objects.filter(role__id=params.get('role'))
serializer = RoleColumnsSerializer(col_list,many=True,request=self.request)
return serializer.data
class Meta:
model = Menu
fields = ['id','name','isCheck','btns','columns']
class RoleApiPermissionViewSet(CustomModelViewSet):
"""
接口权限接口
list:查询
create:新增
update:修改
retrieve:单例
destroy:删除
"""
queryset = RoleApiPermission.objects.all()
serializer_class = RoleApiPermissionSerializer
create_serializer_class = RoleApiPermissionCreateUpdateSerializer
update_serializer_class = RoleApiPermissionCreateUpdateSerializer
extra_filter_class = []
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def data_scope(self, request):
"""
获取数据权限范围:角色授权页面使用
:param request:
:return:
"""
is_superuser = request.user.is_superuser
if is_superuser:
data = [
{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 1,
"label": '本部门及以下数据权限'
},
{
"value": 2,
"label": '本部门数据权限'
},
{
"value": 3,
"label": '全部数据权限'
},
{
"value": 4,
"label": '自定义数据权限'
}
]
return DetailResponse(data=data)
else:
data = []
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 = RoleApiPermission.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:
data = [{
"value": 0,
"label": '仅本人数据权限'
}]
elif item == 1:
data = [{
"value": 0,
"label": '仅本人数据权限'
}, {
"value": 1,
"label": '本部门及以下数据权限'
},
{
"value": 2,
"label": '本部门数据权限'
}]
elif item == 2:
data = [{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 2,
"label": '本部门数据权限'
}]
elif item == 3:
data = [{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 3,
"label": '全部数据权限'
}, ]
elif item == 4:
data = [{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 4,
"label": '自定义数据权限'
}]
else:
data = []
return DetailResponse(data=data)
return ErrorResponse(msg="参数错误")
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def role_to_dept_all(self, request):
"""
当前用户角色下所能授权的部门:角色授权页面使用
:param request:
:return:
"""
params = request.query_params
is_superuser = request.user.is_superuser
if is_superuser:
queryset = Dept.objects.values('id', 'name', 'parent')
else:
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)
dept_ids = RoleApiPermission.objects.filter(role__in=role_list).values_list('dept__id',flat=True)
queryset = Dept.objects.filter(id__in=dept_ids).values('id', 'name', 'parent')
return DetailResponse(data=queryset)
+39 -2
View File
@@ -58,8 +58,38 @@ class RoleMenuPermissionViewSet(CustomModelViewSet):
update_serializer_class = RoleMenuPermissionCreateUpdateSerializer
extra_filter_class = []
@action(methods=['get'],detail=False)
def menu_permission_tree(self,request):
"""
获取菜单按钮树
"""
# params = request.query_params
# role_id = params.get('role',None)
# if role_id is None:
# return ErrorResponse(msg="未获取到角色")
if request.user.is_superuser:
queryset = Menu.objects.filter(status=1).values("id", "name", "parent_id")
else:
role_id = request.user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('menu_id', flat=True)
queryset = Menu.objects.filter(status=1, id__in=menu_list).values('id','name', "parent_id").all()
return DetailResponse(data=queryset)
@action(methods=['get'],detail=False)
def get_menu_permission_checked(self,request):
"""
获取已授权的菜单ID
"""
params = request.query_params
role_id = params.get('role',None)
if role_id is None:
return ErrorResponse(msg="未获取到角色")
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('menu_id', flat=True)
queryset = Menu.objects.filter(status=1, id__in=menu_list).values_list('id',flat=True)
return DetailResponse(data=queryset)
@action(methods=['post'],detail=False)
def save_auth(self,request):
def save_menu_permission(self,request):
"""
保存页面菜单授权
:param request:
@@ -72,7 +102,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()
@@ -1,244 +0,0 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理
"""
from django.db.models import F
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
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):
"""
初始化菜单按钮-序列化器
"""
class Meta:
model = RoleMenuButtonPermission
fields = "__all__"
read_only_fields = ["id"]
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
"""
菜单按钮接口
list:查询
create:新增
update:修改
retrieve:单例
destroy:删除
"""
queryset = RoleMenuButtonPermission.objects.all()
serializer_class = RoleMenuButtonPermissionSerializer
create_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
extra_filter_class = []
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def role_get_menu(self, request):
"""根据当前用户的角色返回角色拥有的菜单"""
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')
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)
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
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:
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 = 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')
)
return DetailResponse(data=queryset)
return ErrorResponse(msg="参数错误")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def data_scope(self, request):
"""
获取数据权限范围:角色授权页面使用
:param request:
:return:
"""
is_superuser = request.user.is_superuser
if is_superuser:
data = [
{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 1,
"label": '本部门及以下数据权限'
},
{
"value": 2,
"label": '本部门数据权限'
},
{
"value": 3,
"label": '全部数据权限'
},
{
"value": 4,
"label": '自定义数据权限'
}
]
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)
data_range_list = list(set(role_queryset))
for item in data_range_list:
if item == 0:
data = [{
"value": 0,
"label": '仅本人数据权限'
}]
elif item == 1:
data = [{
"value": 0,
"label": '仅本人数据权限'
}, {
"value": 1,
"label": '本部门及以下数据权限'
},
{
"value": 2,
"label": '本部门数据权限'
}]
elif item == 2:
data = [{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 2,
"label": '本部门数据权限'
}]
elif item == 3:
data = [{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 3,
"label": '全部数据权限'
}, ]
elif item == 4:
data = [{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 4,
"label": '自定义数据权限'
}]
else:
data = []
return DetailResponse(data=data)
return ErrorResponse(msg="参数错误")
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def role_to_dept_all(self, request):
"""
当前用户角色下所能授权的部门:角色授权页面使用
:param request:
:return:
"""
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)
else:
if params:
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'),
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):
"""
根据所选择菜单获取已配置的按钮/接口权限:角色授权页面使用
:param request:
:return:
"""
params = request.query_params
if params:
menu_id = params.get('menu',None)
if menu_id is None:
return ErrorResponse(msg="未获取到参数")
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(
'id',
'data_range',
'menu_button'
)
return DetailResponse(data=queryset)
return ErrorResponse(msg="未获取到参数")
@@ -61,10 +61,10 @@ class SystemConfigChinldernSerializer(CustomModelSerializer):
"""
系统配置子级-序列化器
"""
chinldern = serializers.SerializerMethodField()
children = serializers.SerializerMethodField()
form_item_type_label = serializers.CharField(source='get_form_item_type_display', read_only=True)
def get_chinldern(self, instance):
def get_children(self, instance):
queryset = SystemConfig.objects.filter(parent=instance)
serializer = SystemConfigSerializer(queryset, many=True)
return serializer.data
+76 -26
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
@@ -41,6 +42,7 @@ class UserSerializer(CustomModelSerializer):
exclude = ["password"]
extra_kwargs = {
"post": {"required": False},
"mobile": {"required": False},
}
def get_dept_name_all(self, instance):
@@ -60,9 +62,6 @@ class UserSerializer(CustomModelSerializer):
return serializer.data
class UserCreateSerializer(CustomModelSerializer):
"""
用户新增-序列化器
@@ -82,10 +81,10 @@ class UserCreateSerializer(CustomModelSerializer):
"""
对密码进行验证
"""
password = self.initial_data.get("password")
if password:
return make_password(value)
return value
md5 = hashlib.md5()
md5.update(value.encode('utf-8'))
md5_password = md5.hexdigest()
return make_password(md5_password)
def save(self, **kwargs):
data = super().save(**kwargs)
@@ -100,6 +99,7 @@ class UserCreateSerializer(CustomModelSerializer):
read_only_fields = ["id"]
extra_kwargs = {
"post": {"required": False},
"mobile": {"required": False},
}
@@ -114,14 +114,15 @@ class UserUpdateSerializer(CustomModelSerializer):
CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")
],
)
# password = serializers.CharField(required=False, allow_blank=True)
mobile = serializers.CharField(
max_length=50,
validators=[
CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一")
],
allow_blank=True
)
# mobile = serializers.CharField(
# max_length=50,
# validators=[
# CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一")
# ],
# allow_blank=True
# )
def save(self, **kwargs):
data = super().save(**kwargs)
@@ -136,6 +137,7 @@ class UserUpdateSerializer(CustomModelSerializer):
fields = "__all__"
extra_kwargs = {
"post": {"required": False, "read_only": True},
"mobile": {"required": False},
}
@@ -159,6 +161,7 @@ class UserInfoUpdateSerializer(CustomModelSerializer):
fields = ['email', 'mobile', 'avatar', 'name', 'gender']
extra_kwargs = {
"post": {"required": False, "read_only": True},
"mobile": {"required": False},
}
@@ -194,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()
@@ -229,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": "用户账号",
@@ -262,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"}},
}
@@ -293,6 +297,11 @@ class UserViewSet(CustomModelViewSet):
'dept_id': dept.id,
'dept_name': dept.name
}
else:
result['dept_info'] = {
'dept_id': None,
'dept_name': "暂无部门"
}
role = getattr(user, 'role', None)
if role:
result['role_info'] = role.values('id', 'name', 'key')
@@ -317,11 +326,10 @@ 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()
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(new_pwd)
request.user.save()
return DetailResponse(data=None, msg="修改成功")
@@ -344,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")
@@ -357,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="获取成功")
+13 -7
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):
user.last_login = timezone.now()
user.save()
return 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("当前用户已被禁用,请联系管理员!")
+9 -5
View File
@@ -38,12 +38,16 @@ def CustomExceptionHandler(ex, context):
# 调用默认的异常处理函数
response = exception_handler(ex, context)
if isinstance(ex, AuthenticationFailed):
code = 401
code_type = response.data.get('detail').code
if code_type == 'no_active_account':
code=400
# 如果是身份验证错误
if response and response.data.get('detail') == "Given token not valid for any token type":
code = 401
msg = ex.detail
elif response and response.data.get('detail') == "Token is blacklisted":
# token在黑名单
return ErrorResponse(status=HTTP_401_UNAUTHORIZED)
msg = ex.detail
else:
code = 401
msg = ex.detail
elif isinstance(ex,Http404):
code = 400
msg = "接口地址不正确"
+38
View File
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Columns
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 app in get_custom_app_models():
for model in app:
if model['object'] is self.serializer_class.Meta.model:
finded = True
break
if finded:
break
if finded is False:
return []
roles = request.user.role.values_list('id', flat=True)
user = request.user
if user.is_superuser==1:
data = Columns.objects.filter(app=model['app'], model=model['model']).values('field_name', 'is_create', 'is_query', 'is_update')
for item in data:
item['is_create'] = True
item['is_query'] = True
item['is_update'] = True
else:
data= Columns.objects.filter(
app=model['app'], model=model['model'],role__in=roles
).values('field_name', 'is_create', 'is_query', 'is_update')
return DetailResponse(data=data)
+81 -71
View File
@@ -20,7 +20,7 @@ from django_filters.rest_framework import DjangoFilterBackend
from django_filters.utils import get_model_field
from rest_framework.filters import BaseFilterBackend
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission
from dvadmin.system.models import Dept, ApiWhiteList, RoleApiPermission
def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
@@ -75,7 +75,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,74 +86,79 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
如果不是超级管理员,则进入下一步权限判断
"""
if request.user.is_superuser == 0:
# 0. 获取用户的部门id,没有部门则返回空
user_dept_id = getattr(request.user, "dept_id", None)
if not user_dept_id:
return queryset.none()
# 1. 判断过滤的数据是否有创建人所在部门 "dept_belong_id" 字段
if not getattr(queryset.model, "dept_belong_id", None):
return queryset
# 2. 如果用户没有关联角色则返回本部门数据
if not hasattr(request.user, "role"):
return queryset.filter(dept_belong_id=user_dept_id)
# 3. 根据所有角色 获取所有权限范围
# (0, "仅本人数据权限"),
# (1, "本部门及以下数据权限"),
# (2, "本部门数据权限"),
# (3, "全部数据权限"),
# (4, "自定数据权限")
replace_str = re.compile('\d')
re_api = replace_str.sub('{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')
)
dataScope_list = [] # 权限范围列表
for ele in role_permission_list:
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
if 3 == ele.get("data_range") or ele.get("role_admin") == True:
return queryset
dataScope_list.append(ele.get("data_range"))
dataScope_list = list(set(dataScope_list))
# 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据)
if 0 in dataScope_list:
return queryset.filter(
creator=request.user, dept_belong_id=user_dept_id
)
# 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:
dept_list.append(user_dept_id)
dept_list.extend(
get_dept(
user_dept_id,
)
)
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)))
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:
return queryset.none()
# 1. 判断过滤的数据是否有创建人所在部门 "dept_belong_id" 字段
if not getattr(queryset.model, "dept_belong_id", None):
return queryset
# 2. 如果用户没有关联角色则返回本部门数据
if not hasattr(request.user, "role"):
return queryset.filter(dept_belong_id=user_dept_id)
# 3. 根据所有角色 获取所有权限范围
# (0, "仅本人数据权限"),
# (1, "本部门及以下数据权限"),
# (2, "本部门数据权限"),
# (3, "全部数据权限"),
# (4, "自定数据权限")
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=RoleApiPermission.objects.filter(
role__in=role_id_list,
role__status=1,
api=re_api,
method=method).values(
'data_range',
)
dataScope_list = [] # 权限范围列表
for ele in role_permission_list:
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
if ele.get("data_range") == 3:
return queryset
dataScope_list.append(ele.get("data_range"))
dataScope_list = list(set(dataScope_list))
# 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据)
if 0 in dataScope_list:
return queryset.filter(
creator=request.user, dept_belong_id=user_dept_id
)
# 5. 自定数据权限 获取部门,根据部门过滤
dept_list = []
for ele in dataScope_list:
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_list.extend(
request.user.role.filter(status=1).values_list(
"dept__id", flat=True
)
)
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)))
class CustomDjangoFilterBackend(DjangoFilterBackend):
lookup_prefixes = {
@@ -164,12 +169,14 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
"~": "icontains",
}
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 field_name.endswith(lookup):
return field_name
return LOOKUP_SEP.join([field_name, lookup])
def find_filter_lookups(self, orm_lookups, search_term_key):
@@ -249,7 +256,10 @@ 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)
@@ -298,7 +308,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
for search_field in filterset.filters:
if isinstance(filterset.filters[search_field], CharFilter):
orm_lookups.append(
self.construct_search(six.text_type(search_field))
self.construct_search(six.text_type(search_field), filterset.filters[search_field].lookup_expr)
)
else:
orm_lookups.append(search_field)
-112
View File
@@ -1,112 +0,0 @@
import logging
import os.path
from logging import LogRecord
from django.core.servers.basehttp import WSGIRequestHandler
from loguru import logger
from logging.handlers import RotatingFileHandler
# 1.🎖️先声明一个类继承logging.Handler(制作一件品如的衣服)
from loguru._defaults import LOGURU_FORMAT
class InterceptTimedRotatingFileHandler(RotatingFileHandler):
"""
自定义反射时间回滚日志记录器
缺少命名空间
"""
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"):
super(InterceptTimedRotatingFileHandler, self).__init__(filename)
filename = os.path.abspath(filename)
when = when.lower()
# 2.🎖️需要本地用不同的文件名做为不同日志的筛选器
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="<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>",
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 = "-"
locals_self = frame.f_locals.get('self', None)
msg = self.format(record)
if locals_self and hasattr(locals_self, 'client_address'):
ip, port = locals_self.client_address
# - 127.0.0.1:56525 -
msg = f"{ip}:{port} - {msg}"
self.logger_ \
.opt(depth=depth, exception=record.exc_info, colors=True) \
.bind(ip=ip, port=port) \
.log(level, msg)
+39 -8
View File
@@ -6,22 +6,20 @@
@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 +38,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)
@@ -86,8 +84,6 @@ class CoreModel(models.Model):
verbose_name_plural = verbose_name
def get_all_models_objects(model_name=None):
"""
获取所有 models 对象
@@ -112,3 +108,38 @@ 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)
res = []
for app in settings.CUSTOM_APPS:
res.append(get_model_from_app(app))
return res
+8 -10
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)
]))
+7 -7
View File
@@ -12,7 +12,7 @@ from django.contrib.auth.models import AnonymousUser
from django.db.models import F
from rest_framework.permissions import BasePermission
from dvadmin.system.models import ApiWhiteList, RoleMenuButtonPermission
from dvadmin.system.models import ApiWhiteList, RoleApiPermission
def ValidationApi(reqApi, validApi):
@@ -74,18 +74,18 @@ class CustomPermission(BasePermission):
methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH']
method = methodList.index(method)
# ***接口白名单***
api_white_list = ApiWhiteList.objects.values(permission__api=F('url'), permission__method=F('method'))
api_white_list = ApiWhiteList.objects.values('method',api=F('url'))
api_white_list = [
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('permission__method')) + '$' for item in api_white_list if item.get('permission__api')]
str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('method')) + '$' for item in api_white_list if item.get('api')]
# ********#
if not hasattr(request.user, "role"):
return False
role_id_list = request.user.role.values_list('id',flat=True)
userApiList = RoleMenuButtonPermission.objects.filter(role__in=role_id_list).values(permission__api=F('menu_button__api'), permission__method=F('menu_button__method')) # 获取当前用户的角色拥有的所有接口
userApiList = RoleApiPermission.objects.filter(role__in=role_id_list).values('api','method') # 获取当前用户的角色拥有的所有接口
ApiList = [
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('permission__method')) + '$' for item in userApiList if item.get('permission__api')]
str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('method')) + '$' for item in userApiList if item.get('api')]
new_api_ist = api_white_list + ApiList
new_api = api + ":" + str(method)
for item in new_api_ist:
+28 -2
View File
@@ -6,8 +6,6 @@
@Created on: 2021/6/1 001 22:57
@Remark: 自定义视图集
"""
import uuid
from django.db import transaction
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
@@ -18,6 +16,8 @@ from dvadmin.utils.filters import DataLevelPermissionsFilter
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
from dvadmin.system.models import Columns
from django_restql.mixins import QueryArgumentsMixin
@@ -63,12 +63,38 @@ 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_column_permission(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_column_permission(self, serializer_class):
"""获取列权限"""
finded = False
for app in get_custom_app_models():
for model in app:
if model['object'] is serializer_class.Meta.model:
finded = True
break
if finded:
break
if finded is False:
return []
return Columns.objects.filter(
app=model['app'], model=model['model']
).values('field_name', 'is_create', 'is_query', 'is_update')
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, request=request)
serializer.is_valid(raise_exception=True)
-1
View File
@@ -1 +0,0 @@
7
+23 -24
View File
@@ -1,31 +1,30 @@
Django==4.1.5
Django==4.2.6
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
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==4.0.0
channels-redis==4.0.0
websockets==10.4
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
+2 -2
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
~~~
+1 -1
View File
@@ -1,4 +1,4 @@
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
+1 -1
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
+1 -1
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
+1 -1
View File
@@ -2,7 +2,7 @@
ENV = 'production'
# 线上环境接口地址
VITE_API_URL = ''
VITE_API_URL = '/api' # docker-compose部署不需要修改,nginx容器自动代理了这个地址
# 是否启用按钮权限
VITE_PM_ENABLED = true
+1 -1
View File
@@ -1,7 +1,7 @@
.DS_Store
node_modules
/dist
yarn.lock
# local env files
.env.local
-6818
View File
File diff suppressed because it is too large Load Diff
+12 -5
View File
@@ -10,12 +10,14 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@fast-crud/fast-crud": "^1.9.0",
"@fast-crud/fast-extends": "^1.9.0",
"@fast-crud/ui-element": "^1.9.0",
"@fast-crud/fast-crud": "^1.18.3",
"@fast-crud/fast-extends": "^1.18.3",
"@fast-crud/ui-element": "^1.18.3",
"@fast-crud/ui-interface": "^1.18.3",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"autoprefixer": "^10.4.14",
"axios": "^1.2.1",
"countup.js": "^2.3.2",
"cropperjs": "^1.5.13",
@@ -23,7 +25,8 @@
"echarts": "^5.4.1",
"echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.2.26",
"element-plus": "^2.3.9",
"element-tree-line": "^0.2.1",
"font-awesome": "^4.7.0",
"js-cookie": "^3.0.1",
"js-table2excel": "^1.0.3",
@@ -32,19 +35,23 @@
"nprogress": "^0.2.0",
"pinia": "^2.0.28",
"pinia-plugin-persist": "^1.0.0",
"postcss": "^8.4.21",
"print-js": "^1.6.0",
"qrcodejs2-fixes": "^0.0.2",
"qs": "^6.11.0",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.0",
"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": {
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
+28 -25
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,12 @@ onBeforeMount(() => {
setIntroduction.cssCdn();
// js
setIntroduction.jsCdn();
//websockt
websocket.init(wsReceive)
//websockt
try {
//websocket.init(wsReceive)
} catch (e) {
console.log('websocket错误');
}
});
//
onMounted(() => {
@@ -95,26 +100,24 @@ 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()
messageCenter.setUnread(unread);
if (data.contentType === 'SYSTEM') {
ElNotification({
title: '系统消息',
message: data.content,
type: 'success',
position: 'bottom-right',
duration: 5000
})
}
}
const data = JSON.parse(message.data);
const { unread } = data;
const messageCenter = messageCenterStore();
messageCenter.setUnread(unread);
if (data.contentType === 'SYSTEM') {
ElNotification({
title: '系统消息',
message: data.content,
type: 'success',
position: 'bottom-right',
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

+9
View File
@@ -0,0 +1,9 @@
.fs-crud-container {
.el-table thead {
color: #606266;
}
.el-input__inner::placeholder {
color: #dcdfe6;
font-size: 12px;
}
}
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+196
View File
@@ -0,0 +1,196 @@
<template>
<div class="user-info-head" @click="editCropper()">
<el-avatar :size="100" :src="options.img" />
<el-dialog :title="title" v-model="dialogVisiable" width="600px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-row>
<el-col class="flex justify-center">
<vue-cropper
ref="cropper"
:img="options.img"
:info="true"
:autoCrop="options.autoCrop"
:autoCropWidth="options.autoCropWidth"
:autoCropHeight="options.autoCropHeight"
:fixedBox="options.fixedBox"
:outputType="options.outputType"
@realTime="realTime"
:centerBox="true"
v-if="visible"
class="cropper"
/>
</el-col>
</el-row>
<br />
<el-row class="flex justify-center">
<el-col :lg="2" :md="2">
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
<el-button type="success">
选择
<el-icon class="el-icon--right"><Plus /></el-icon>
</el-button>
</el-upload>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button icon="RefreshRight" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{ span: 2, offset: 2 }" :md="2">
<el-button type="primary" @click="uploadImg()">更新头像</el-button>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script setup>
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
import { useUserInfo } from '/@/stores/userInfo';
import { getCurrentInstance, nextTick, reactive, ref, computed, onMounted, defineExpose } from 'vue';
import { base64ToFile } from '/@/utils/tools';
const userStore = useUserInfo();
const { proxy } = getCurrentInstance();
const open = ref(false);
const visible = ref(false);
const title = ref('修改头像');
const emit = defineEmits(['uploadImg']);
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
required: true,
},
});
const dialogVisiable = computed({
get() {
return props.modelValue;
},
set(newVal) {
emit('update:modelValue', newVal);
},
});
//
const options = reactive({
img: userStore.userInfos.avatar, //
fileName: '',
autoCrop: true, //
autoCropWidth: 200, //
autoCropHeight: 200, //
fixedBox: true, //
outputType: 'png', // PNG
});
/** 编辑头像 */
function editCropper() {
dialogVisiable.value = true;
}
/** 打开弹出层结束时的回调 */
function modalOpened() {
nextTick(() => {
visible.value = true;
});
}
/** 覆盖默认上传行为 */
function requestUpload() {}
/** 向左旋转 */
function rotateLeft() {
proxy.$refs.cropper.rotateLeft();
}
/** 向右旋转 */
function rotateRight() {
proxy.$refs.cropper.rotateRight();
}
/** 图片缩放 */
function changeScale(num) {
num = num || 1;
proxy.$refs.cropper.changeScale(num);
}
/** 上传预处理 */
function beforeUpload(file) {
if (file.type.indexOf('image/') == -1) {
proxy.$modal.msgError('文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。');
} else {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
options.img = reader.result;
options.fileName = file.name;
};
}
}
/** 上传图片 */
function uploadImg() {
// base64
proxy.$refs.cropper.getCropData((data) => {
let img = new Image();
img.src = data;
img.onload = async () => {
let _data = compress(img);
const imgFile = base64ToFile(_data, options.fileName);
emit('uploadImg', imgFile);
};
});
}
//
function compress(img) {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
// let initSize = img.src.length;
let width = img.width;
let height = img.height;
canvas.width = width;
canvas.height = height;
//
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, width, height);
//
let ndata = canvas.toDataURL('image/jpeg', 0.8);
return ndata;
}
/** 关闭窗口 */
function closeDialog() {
options.visible = false;
options.img = userStore.userInfos.avatar;
}
const updateAvatar = (img) => {
options.img = img;
};
defineExpose({
updateAvatar,
});
</script>
<style lang="scss" scoped>
.user-info-head {
position: relative;
display: inline-block;
height: 120px;
}
.user-info-head:hover:after {
content: '修改头像';
position: absolute;
text-align: center;
left: 0;
right: 0;
top: 0;
bottom: 0;
color: #000000;
font-size: 20px;
font-style: normal;
cursor: pointer;
line-height: 110px;
}
.cropper {
height: 400px;
width: 400px;
}
</style>
+8 -5
View File
@@ -159,11 +159,14 @@ const initModeValueEcho = () => {
// icon tab
const initFontIconName = () => {
let name = 'ali';
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
// tab
state.fontIconTabActive = name;
if(props.modelValue){
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
// tab
state.fontIconTabActive = name;
}
return name;
};
//
+146
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>
+3 -3
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 = {};
+2 -2
View File
@@ -9,8 +9,8 @@ export default {
two4: 'Links',
},
account: {
accountPlaceholder1: 'The user name admin or not is common',
accountPlaceholder2: 'Password: 123456',
accountPlaceholder1: 'Please enter your login account',
accountPlaceholder2: 'Please enter your login password',
accountPlaceholder3: 'Please enter the verification code',
accountBtnText: 'Sign in',
},
+2 -2
View File
@@ -9,8 +9,8 @@ export default {
two4: '友情链接',
},
account: {
accountPlaceholder1: '用户名 admin 或不输均为 common',
accountPlaceholder2: '密码123456',
accountPlaceholder1: '请输入登录账号',
accountPlaceholder2: '请输入登录密码',
accountPlaceholder3: '请输入验证码',
accountBtnText: '登 录',
},
+2 -2
View File
@@ -9,8 +9,8 @@ export default {
two4: '友情連結',
},
account: {
accountPlaceholder1: '用戶名admin或不輸均為common',
accountPlaceholder2: '密碼123456',
accountPlaceholder1: '請輸入登入賬號',
accountPlaceholder2: '請輸入登入密碼',
accountPlaceholder3: '請輸入驗證碼',
accountBtnText: '登入',
},
+5 -8
View File
@@ -1,13 +1,10 @@
<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" />
<!-- <LayoutFooter v-if="isFooter" /> -->
</el-scrollbar>
<el-backtop :target="setBacktopClass" />
</el-main>
+7 -7
View File
@@ -39,7 +39,7 @@
<div class="layout-navbars-breadcrumb-user-icon">
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
<template #reference>
<el-badge :value="messageCenter.unread" :hidden="messageCenter.unread===0">
<el-badge :value="messageCenter.unread" :hidden="messageCenter.unread === 0">
<el-icon :title="$t('message.user.title4')">
<ele-Bell />
</el-icon>
@@ -59,8 +59,8 @@
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
<span class="layout-navbars-breadcrumb-user-link">
<img :src="userInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
{{ userInfos.userName === '' ? 'common' : userInfos.userName }}
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
{{ userInfos.username === '' ? 'common' : userInfos.username }}
<el-icon class="el-icon--right">
<ele-ArrowDown />
</el-icon>
@@ -68,7 +68,7 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
<el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item>
<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
</el-dropdown-menu>
@@ -90,7 +90,7 @@ 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';
//
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
@@ -208,8 +208,8 @@ onMounted(() => {
});
//
import {messageCenterStore} from "/@/stores/messageCenter";
const messageCenter = messageCenterStore()
import { messageCenterStore } from '/@/stores/messageCenter';
const messageCenter = messageCenterStore();
</script>
<style scoped lang="scss">
+13 -10
View File
@@ -3,18 +3,21 @@
<router-view v-slot="{ Component }">
<transition :name="setTransitionName" mode="out-in">
<keep-alive :include="getKeepAliveNames" v-if="showView">
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" v-show="!isIframePage" />
<div>
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" v-show="!isIframePage" />
</div>
</keep-alive>
</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 +47,13 @@ const showView = ref(true)
/**
* 刷新页面
*/
const refreshView=function () {
showView.value = false // v-ifrouter-view
nextTick(() => {
showView.value = true // DOMv-ifrouter-view
})
const refreshView = function () {
showView.value = false // v-ifrouter-view
nextTick(() => {
showView.value = true // DOMv-ifrouter-view
})
}
provide('refreshView',refreshView)
provide('refreshView', refreshView)
//
const setTransitionName = computed(() => {
@@ -105,7 +108,7 @@ onMounted(() => {
});
//
onUnmounted(() => {
mittBus.off('onTagsViewRefreshRouterView', () => {});
mittBus.off('onTagsViewRefreshRouterView', () => { });
});
// tagsView
// https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files
+21 -8
View File
@@ -1,9 +1,10 @@
import {createApp} from 'vue';
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 '/@/utils/directive';
import { i18n } from '/@/i18n';
import other from '/@/utils/other';
import '/@/assets/style/tailwind.css'; // 先引入tailwind css, 以免element-plus冲突
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import '/@/theme/index.scss';
@@ -15,7 +16,7 @@ import fastCrud from './settings.ts';
import pinia from './stores';
import permission from '/@/plugin/permission/index';
// @ts-ignore
import eIconPicker, { iconList,analyzingIconForIconfont } from 'e-icon-picker';
import eIconPicker, { iconList, analyzingIconForIconfont } from 'e-icon-picker';
import 'e-icon-picker/icon/default-icon/symbol.js'; //基本彩色图标库
import 'e-icon-picker/index.css'; // 基本样式,包含基本图标
import 'font-awesome/css/font-awesome.min.css';
@@ -24,6 +25,14 @@ import fontAwesome470 from 'e-icon-picker/icon/fontawesome/font-awesome.v4.7.0.j
import eIconList from 'e-icon-picker/icon/default-icon/eIconList.js';
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
iconList.addIcon(elementPlus); // 添加element plus的图标
@@ -31,17 +40,21 @@ iconList.addIcon(fontAwesome470); // 添加fontAwesome 470版本的图标
let app = createApp(App);
scanAndInstallPlugins(app);
app.use(eIconPicker, {
addIconList: eIconList, //全局添加图标
removeIconList: [], //全局删除图标
zIndex: 3100, //选择器弹层的最低层,全局配置
addIconList: eIconList, //全局添加图标
removeIconList: [], //全局删除图标
zIndex: 3100, //选择器弹层的最低层,全局配置
});
pinia.use(piniaPersist);
directive(app);
other.elSvg(app);
app.use(VXETable)
app.use(permission);
app.use(pinia).use(router).use(ElementPlus, {i18n: i18n.global.t}).use(i18n).use(VueGridLayout).use(fastCrud).mount('#app');
app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).use(fastCrud).mount('#app');
app.config.globalProperties.mittBus = mitt();
+1 -1
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
@@ -9,7 +9,7 @@ export const BtnPermissionStore = defineStore('BtnPermission', {
actions: {
async getBtnPermissionStore() {
request({
url: '/api/system/menu_button/menu_button_all_permission/',
url: '/api/system/menu/menu_button_all_permission/',
method: 'get',
}).then((ret: {
data: []
+1 -1
View File
@@ -11,7 +11,7 @@ import { useRoutesList } from '/@/stores/routesList';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useMenuApi } from '/@/api/menu/index';
import { handleMenu } from '../utils/menu';
import {BtnPermissionStore} from "/@/plugin/permission/store.permission";
import { BtnPermissionStore } from '/@/plugin/permission/store.permission';
const menuApi = useMenuApi();
+86 -79
View File
@@ -1,15 +1,15 @@
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 {staticRoutes} from '/@/router/route';
import {initFrontEndControlRoutes} from '/@/router/frontEnd';
import {initBackEndControlRoutes} from '/@/router/backEnd';
/**
* 1isRequestRoutes false roles setFilterRoute
@@ -22,8 +22,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 使
@@ -31,8 +31,8 @@ const { isRequestRoutes } = themeConfig.value;
* @link https://next.router.vuejs.org/zh/api/#createrouter
*/
export const router = createRouter({
history: createWebHashHistory(),
routes: staticRoutes,
history: createWebHashHistory(),
routes: staticRoutes,
});
/**
@@ -41,13 +41,13 @@ export const router = createRouter({
* @returns
*/
export function formatFlatteningRoutes(arr: any) {
if (arr.length <= 0) return false;
for (let i = 0; i < arr.length; i++) {
if (arr[i].children) {
arr = arr.slice(0, i + 1).concat(arr[i].children, arr.slice(i + 1));
}
}
return arr;
if (arr.length <= 0) return false;
for (let i = 0; i < arr.length; i++) {
if (arr[i].children) {
arr = arr.slice(0, i + 1).concat(arr[i].children, arr.slice(i + 1));
}
}
return arr;
}
/**
@@ -58,73 +58,80 @@ export function formatFlatteningRoutes(arr: any) {
* @returns `定义动态路由(dynamicRoutes`
*/
export function formatTwoStageRoutes(arr: any) {
if (arr.length <= 0) return false;
const newArr: 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: [] });
} else {
// 判断是否是动态路由(xx/:id/:name),用于 tagsView 等中使用
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
if (v.path.indexOf('/:') > -1) {
v.meta['isDynamic'] = true;
v.meta['isDynamicPath'] = v.path;
}
newArr[0].children.push({ ...v });
// 存 name 值,keep-alive 中 include 使用,实现路由的缓存
// 路径:/@/layout/routerView/parent.vue
if (newArr[0].meta.isKeepAlive && v.meta.isKeepAlive) {
cacheList.push(v.name);
const stores = useKeepALiveNames(pinia);
stores.setCacheKeepAlive(cacheList);
}
}
});
return newArr;
if (arr.length <= 0) return false;
const newArr: 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: []
});
} else {
// 判断是否是动态路由(xx/:id/:name),用于 tagsView 等中使用
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
if (v.path.indexOf('/:') > -1) {
v.meta['isDynamic'] = true;
v.meta['isDynamicPath'] = v.path;
}
newArr[0].children.push({...v});
// 存 name 值,keep-alive 中 include 使用,实现路由的缓存
// 路径:/@/layout/routerView/parent.vue
if (newArr[0].meta.isKeepAlive && v.meta.isKeepAlive && v.component_name != "") {
cacheList.push(v.name);
const stores = useKeepALiveNames(pinia);
stores.setCacheKeepAlive(cacheList);
}
}
});
return newArr;
}
// 路由加载前
router.beforeEach(async (to, from, next) => {
NProgress.configure({ showSpinner: false });
if (to.meta.title) NProgress.start();
const token = Session.get('token');
if (to.path === '/login' && !token) {
next();
NProgress.done();
} else {
if (!token) {
next(`/login?redirect=${to.path}&params=${JSON.stringify(to.query ? to.query : to.params)}`);
Session.clear();
NProgress.done();
} else if (token && to.path === '/login') {
next('/home');
NProgress.done();
} else {
const storesRoutesList = useRoutesList(pinia);
const { routesList } = storeToRefs(storesRoutesList);
if (routesList.value.length === 0) {
if (isRequestRoutes) {
// 后端控制路由:路由数据初始化,防止刷新时丢失
await initBackEndControlRoutes();
// 动态添加路由:防止非首页刷新时跳转回首页的问题
// 确保 addRoute() 时动态添加的路由已经被完全加载上去
next({ ...to, replace: true });
} else {
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
await initFrontEndControlRoutes();
next({ ...to, replace: true });
}
} else {
next();
}
}
}
NProgress.configure({showSpinner: false});
if (to.meta.title) NProgress.start();
const token = Session.get('token');
if (to.path === '/login' && !token) {
next();
NProgress.done();
} else {
if (!token) {
next(`/login?redirect=${to.path}&params=${JSON.stringify(to.query ? to.query : to.params)}`);
Session.clear();
NProgress.done();
} else if (token && to.path === '/login') {
next('/home');
NProgress.done();
} else {
const storesRoutesList = useRoutesList(pinia);
const {routesList} = storeToRefs(storesRoutesList);
if (routesList.value.length === 0) {
if (isRequestRoutes) {
// 后端控制路由:路由数据初始化,防止刷新时丢失
await initBackEndControlRoutes();
// 动态添加路由:防止非首页刷新时跳转回首页的问题
// 确保 addRoute() 时动态添加的路由已经被完全加载上去
next({...to, replace: true});
} else {
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
await initFrontEndControlRoutes();
next({...to, replace: true});
}
} else {
next();
}
}
}
});
// 路由加载后
router.afterEach(() => {
NProgress.done();
NProgress.done();
});
// 导出路由
+65 -8
View File
@@ -7,8 +7,9 @@ import { setLogger } from '@fast-crud/fast-crud';
import ui from '@fast-crud/ui-element';
import { request } from '/@/utils/service';
//扩展包
import {FsExtendsEditor} from "@fast-crud/fast-extends";
import "@fast-crud/fast-extends/dist/style.css";
import { FsExtendsEditor,FsExtendsUploader } from '@fast-crud/fast-extends';
import '@fast-crud/fast-extends/dist/style.css';
import { ElMessage } from "element-plus";
export default {
async install(app: any, options: any) {
// 先安装ui
@@ -17,8 +18,11 @@ 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({ url }: any) {
//根据dict的url,异步返回一个字典数组
return await request({ url: url, }).then((res:any)=>{
return res.data
});
},
//公共crud配置
commonOptions() {
@@ -28,13 +32,31 @@ export default {
//你项目后台接口大概率与fast-crud所需要的返回结构不一致,所以需要配置此项
//请参考文档http://fast-crud.docmirror.cn/api/crud-options/request.html
transformQuery: ({ page, form, sort }: any) => {
if (sort.asc !== undefined) {
form['ordering'] = `${sort.asc ? '' : '-'}${sort.prop}`;
}
//转换为你pageRequest所需要的请求参数结构
return { page: page.currentPage, limit: page.pageSize, ...form };
},
transformRes: ({ res }: any) => {
//将pageRequest的返回数据,转换为fast-crud所需要的格式
//return {records,currentPage,pageSize,total};
return { records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total };
if(res.page){
return { records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total };
}else{
return { records: res.data,currentPage: 1, pageSize: res.data.length, total: res.data.length };
}
},
},
form: {
afterSubmit(ctx:any ) {
const {mode} = ctx
if (mode === "add") {
ElMessage.success({ message: "添加成功" });
} else if (mode === "edit") {
ElMessage.success({ message: "保存成功" });
}
},
},
/* search: {
@@ -55,9 +77,44 @@ export default {
},
});
//富文本
app.use(FsExtendsEditor,{
wangEditor:{
width:300,
app.use(FsExtendsEditor, {
wangEditor: {
width: 300,
},
});
// 文件上传
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) {
console.log(111,ret)
// 上传完成后的结果处理, 此处应返回格式为{url:xxx,key:xxx}
return {
url: ret.data.url,
key: ret.data.id,
...ret.data
};
}
}
})
setLogger({ level: 'error' });
+20
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;
},
},
});
+14 -5
View File
@@ -5,11 +5,17 @@
// 用户信息
export interface UserInfosState {
authBtnList: string[];
photo: string;
roles: string[];
time: number;
userName: string;
avatar: string;
username: string;
name: string;
email: string;
mobile: string;
gender: string;
dept_info: {
dept_id: number;
dept_name: string;
};
role_info: any[];
}
export interface UserInfosStates {
userInfos: UserInfosState;
@@ -92,3 +98,6 @@ export interface ThemeConfigStates {
export interface DictionaryStates {
data: any;
}
export interface ConfigStates {
systemConfig: any;
}
+28
View File
@@ -0,0 +1,28 @@
import { defineStore } from 'pinia';
import { ConfigStates } from './interface';
import { request } from '../utils/service';
export const urlPrefix = '/api/init/settings/';
/**
*
* @methods getSystemConfig
*/
export const SystemConfigStore = defineStore('SystemConfig', {
state: (): ConfigStates => ({
systemConfig: {},
}),
actions: {
async getSystemConfigs() {
request({
url: urlPrefix,
method: 'get',
}).then((ret: { data: [] }) => {
// 转换数据格式并保存到pinia
this.systemConfig = JSON.parse(JSON.stringify(ret.data));
});
},
},
persist: {
enabled: true,
},
});
+39 -14
View File
@@ -1,9 +1,7 @@
import { defineStore } from 'pinia';
import Cookies from 'js-cookie';
import { UserInfosStates } from './interface';
import { Session } from '/@/utils/storage';
import { request } from '../utils/service';
/**
*
* @methods setUserInfos
@@ -11,32 +9,59 @@ import { request } from '../utils/service';
export const useUserInfo = defineStore('userInfo', {
state: (): UserInfosStates => ({
userInfos: {
userName: '',
photo: '',
time: 0,
roles: [],
authBtnList: [],
avatar: '',
username: '',
name: '',
email: '',
mobile: '',
gender: '',
dept_info: {
dept_id: 0,
dept_name: '',
},
role_info: [
{
id: 0,
name: '',
},
],
},
}),
actions: {
async updateUserInfos() {
let userInfos: any = await this.getApiUserInfo();
this.userInfos.username = userInfos.data.name;
this.userInfos.avatar = userInfos.data.avatar;
this.userInfos.name = userInfos.data.name;
this.userInfos.email = userInfos.data.email;
this.userInfos.mobile = userInfos.data.mobile;
this.userInfos.gender = userInfos.data.gender;
this.userInfos.dept_info = userInfos.data.dept_info;
this.userInfos.role_info = userInfos.data.role_info;
Session.set('userInfo', this.userInfos);
},
async setUserInfos() {
// 存储用户信息到浏览器缓存
if (Session.get('userInfo')) {
this.userInfos = Session.get('userInfo');
} else {
let userInfos: any = await this.getApiUserInfo();
this.userInfos.userName = userInfos.data.name;
this.userInfos.photo = userInfos.data.avatar || 'https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500'
this.userInfos.time = new Date().getTime()
this.userInfos.roles = ['admin']
Session.set('userInfo', this.userInfos)
this.userInfos.username = userInfos.data.name;
this.userInfos.avatar = userInfos.data.avatar;
this.userInfos.name = userInfos.data.name;
this.userInfos.email = userInfos.data.email;
this.userInfos.mobile = userInfos.data.mobile;
this.userInfos.gender = userInfos.data.gender;
this.userInfos.dept_info = userInfos.data.dept_info;
this.userInfos.role_info = userInfos.data.role_info;
Session.set('userInfo', this.userInfos);
}
},
async getApiUserInfo() {
return request({
url: '/api/system/user/user_info/',
method: 'get',
})
}
});
},
},
});
+1 -1
View File
@@ -9,7 +9,7 @@ declare interface UserInfosState<T = any> {
photo: string;
roles: string[];
time: number;
userName: string;
username: string;
[key: string]: T;
};
}
+1 -1
View File
@@ -90,7 +90,7 @@ declare type TreeType = {
// user
declare type RowUserType<T = any> = {
userName: string;
username: string;
userNickname: string;
roleSign: string;
department: string[];
+61 -55
View File
@@ -1,61 +1,67 @@
import { pluginsAll } from '/@/views/plugins/index';
/**
* @description
*/
export const getBaseURL = function () {
var baseURL = import.meta.env.VITE_API_URL as any
var param = baseURL.split('/')[3] || ''
if (window.pluginsAll && window.pluginsAll.indexOf('dvadmin-tenants-web') !== -1 && (!param || baseURL.startsWith('/'))) {
// 1.把127.0.0.1 替换成和前端一样域名
// 2.把 ip 地址替换成和前端一样域名
// 3.把 /api 或其他类似的替换成和前端一样域名
// document.domain
var host = baseURL.split('/')[2]
if (host) {
var prot = baseURL.split(':')[2] || 80
if (prot === 80 || prot === 443) {
host = document.domain
} else {
host = document.domain + ':' + prot
}
baseURL = baseURL.split('/')[0] + '//' + baseURL.split('/')[1] + host + '/' + param
} else {
baseURL = location.protocol + '//' + location.hostname + (location.port ? ':' : '') + location.port + baseURL
}
}
if (!baseURL.endsWith('/')) {
baseURL += '/'
}
return baseURL
}
var baseURL = import.meta.env.VITE_API_URL as any;
var param = baseURL.split('/')[3] || '';
// @ts-ignore
if (pluginsAll && pluginsAll.indexOf('dvadmin3-tenants-web') !== -1 && (!param || baseURL.startsWith('/'))) {
// 1.把127.0.0.1 替换成和前端一样域名
// 2.把 ip 地址替换成和前端一样域名
// 3.把 /api 或其他类似的替换成和前端一样域名
// document.domain
var host = baseURL.split('/')[2];
if (host) {
var port = baseURL.split(':')[2] || 80;
if (port === 80 || port === 443) {
host = document.domain;
} else {
host = document.domain + ':' + port;
}
baseURL = baseURL.split('/')[0] + '//' + baseURL.split('/')[1] + host + '/' + param;
} else {
baseURL = location.protocol + '//' + location.hostname + (location.port ? ':' : '') + location.port + baseURL;
}
}
if (!baseURL.endsWith('/')) {
baseURL += '/';
}
return baseURL;
};
export const getWsBaseURL = function () {
let baseURL = import.meta.env.VITE_API_URL as any
let param = baseURL.split('/')[3] || ''
if (window.pluginsAll && window.pluginsAll.indexOf('dvadmin-tenants-web') !== -1 && (!param || baseURL.startsWith('/'))) {
// 1.把127.0.0.1 替换成和前端一样域名
// 2.把 ip 地址替换成和前端一样域名
// 3.把 /api 或其他类似的替换成和前端一样域名
// document.domain
var host = baseURL.split('/')[2]
if (host) {
var prot = baseURL.split(':')[2] || 80
if (prot === 80 || prot === 443) {
host = document.domain
} else {
host = document.domain + ':' + prot
}
baseURL = baseURL.split('/')[0] + '//' + baseURL.split('/')[1] + host + '/' + param
} else {
baseURL = location.protocol + '//' + location.hostname + (location.port ? ':' : '') + location.port + baseURL
}
} else if (param !== '' || baseURL.startsWith('/')) {
baseURL = (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.hostname + (location.port ? ':' : '') + location.port + baseURL
}
if (!baseURL.endsWith('/')) {
baseURL += '/'
}
if (baseURL.startsWith('http')) { // https 也默认会被替换成 wss
baseURL = baseURL.replace('http', 'ws')
}
return baseURL
}
let baseURL = import.meta.env.VITE_API_URL as any;
let param = baseURL.split('/')[3] || '';
// @ts-ignore
if (pluginsAll && pluginsAll.indexOf('dvadmin3-tenants-web') !== -1 && (!param || baseURL.startsWith('/'))) {
// 1.把127.0.0.1 替换成和前端一样域名
// 2.把 ip 地址替换成和前端一样域名
// 3.把 /api 或其他类似的替换成和前端一样域名
// document.domain
var host = baseURL.split('/')[2];
if (host) {
var port = baseURL.split(':')[2] || 80;
if (port === 80 || port === 443) {
host = document.domain;
} else {
host = document.domain + ':' + port;
}
baseURL = baseURL.split('/')[0] + '//' + baseURL.split('/')[1] + host + '/' + param;
} else {
baseURL = location.protocol + '//' + location.hostname + (location.port ? ':' : '') + location.port + baseURL;
}
} else if (param !== '' || baseURL.startsWith('/')) {
baseURL = (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.hostname + (location.port ? ':' : '') + location.port + baseURL;
}
if (!baseURL.endsWith('/')) {
baseURL += '/';
}
if (baseURL.startsWith('http')) {
// https 也默认会被替换成 wss
baseURL = baseURL.replace('http', 'ws');
}
return baseURL;
};
+9
View File
@@ -0,0 +1,9 @@
import { useColumnPermission } from '/@/stores/columnPermission';
type permissionType = 'is_create' | 'is_query' | 'is_update';
export const columnPermission = (key: string, type: permissionType): boolean => {
const permissions = useColumnPermission().permission || [];
return !!permissions.some((i) => i.field_name === key && i[type]);
};
+10 -2
View File
@@ -4,8 +4,16 @@ import { DictionaryStore } from '/@/stores/dictionary';
/**
* @method name字典
*/
export const dictionary = (name: string) => {
export const dictionary = (key: string,value:string|number|null=null) => {
const dict = DictionaryStore()
const dictionary = toRaw(dict.data)
return dictionary[name]
if(value!==null){
for (let item of dictionary[key]) {
if (item.value === value) {
return item.label
}
}
return []
}
return dictionary[key]
}
+2 -2
View File
@@ -12,13 +12,13 @@ export const handleMenu = (menuData: Array<any>) => {
title: item.title,
isLink: item.is_link,
isHide: !item.visible,
isKeepAlive: true,
isKeepAlive: item.cache,
isAffix: false,
isIframe: false,
roles: ['admin'],
icon: item.icon
}
item.name = item.component_name
return item
}
menuData.forEach((val) => {
+18 -2
View File
@@ -1,10 +1,10 @@
import { ElMessage, MessageOptions } from 'element-plus';
import { ElMessage, ElNotification, MessageOptions } from 'element-plus';
export function message(message: string, option?: MessageOptions) {
ElMessage({ message, ...option });
}
export function successMessage(message: string, option?: MessageOptions) {
ElMessage({ message, ...option, type: 'success' });
ElMessage({ message, type: 'success' });
}
export function warningMessage(message: string, option?: MessageOptions) {
ElMessage({ message, ...option, type: 'warning' });
@@ -15,3 +15,19 @@ export function errorMessage(message: string, option?: MessageOptions) {
export function infoMessage(message: string, option?: MessageOptions) {
ElMessage({ message, ...option, type: 'info' });
}
export function notification(message: string) {
ElNotification({ message });
}
export function successNotification(message: string) {
ElNotification({ message, type: 'success' });
}
export function warningNotification(message: string) {
ElNotification({ message, type: 'warning' });
}
export function errorNotification(message: string) {
ElNotification({ message, type: 'error' });
}
export function infoNotification(message: string) {
ElNotification({ message, type: 'info' });
}
+66 -16
View File
@@ -1,7 +1,7 @@
import axios from 'axios';
import { get } from 'lodash-es';
import { ElMessage, ElMessageBox } from 'element-plus'
import type { Action } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus';
import type { Action } from 'element-plus';
// @ts-ignore
import { errorLog, errorCreate } from './tools.ts';
@@ -9,6 +9,7 @@ import { errorLog, errorCreate } from './tools.ts';
// import { useUserStore } from "../store/modules/user";
import { Local, Session } from '/@/utils/storage';
import qs from 'qs';
import { getBaseURL } from './baseUrl';
/**
* @description
*/
@@ -17,16 +18,23 @@ function createService() {
const service = axios.create({
timeout: 20000,
headers: {
'Content-Type': 'application/json;charset=utf-8'
'Content-Type': 'application/json;charset=utf-8',
},
paramsSerializer: {
serialize(params) {
return qs.stringify(params, { indices: false,encoder: (val:string) => {
if (typeof val === 'boolean') {
return val ? 1 : 0;
}
return val;
} });
interface paramsObj {
[key: string]: any;
}
let result: paramsObj = {};
for (const [key, value] of Object.entries(params)) {
if (value !== '') {
result[key] = value;
}
if (typeof value === 'boolean') {
result[key] = value ? 'True' : 'False';
}
}
return qs.stringify(result);
},
},
});
@@ -62,21 +70,21 @@ function createService() {
// 有 code 代表这是一个后端接口 可以进行进一步的判断
switch (code) {
case 400:
Local.clear();
Session.clear();
// Local.clear();
// Session.clear();
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
window.location.reload();
// window.location.reload();
break;
case 401:
Local.clear();
Session.clear();
dataAxios.msg = '登录授权过期,请重新登录';
dataAxios.msg = '登录认证失败,请重新登录';
ElMessageBox.alert(dataAxios.msg, '提示', {
confirmButtonText: 'OK',
callback: (action: Action) => {
window.location.reload();
},
})
});
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
break;
case 2000:
@@ -86,6 +94,9 @@ function createService() {
return dataAxios;
}
return dataAxios;
case 4000:
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
return dataAxios;
default:
// 不是正确的 code
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
@@ -100,7 +111,15 @@ function createService() {
error.message = '请求错误';
break;
case 401:
error.message = '未授权,请登录';
Local.clear();
Session.clear();
error.message = '登录授权过期,请重新登录';
ElMessageBox.alert(error.message, '提示', {
confirmButtonText: 'OK',
callback: (action: Action) => {
window.location.reload();
},
});
break;
case 403:
error.message = '拒绝访问';
@@ -154,7 +173,7 @@ function createRequestFunction(service: any) {
'Content-Type': get(config, 'headers.Content-Type', 'application/json'),
},
timeout: 5000,
baseURL: import.meta.env.VITE_API_URL as any,
baseURL: getBaseURL(),
data: {},
};
@@ -175,3 +194,34 @@ export const request = createRequestFunction(service);
// 用于模拟网络请求的实例和请求方法
export const serviceForMock = createService();
export const requestForMock = createRequestFunction(serviceForMock);
/**
*
* @param url
* @param params
* @param method
* @param filename
*/
export const downloadFile = function ({ url, params, method, filename = '文件导出' }: any) {
request({
url: url,
method: method,
params: params,
responseType: 'blob'
// headers: {Accept: 'application/vnd.openxmlformats-officedocument'}
}).then((res: any) => {
const xlsxName = window.decodeURI(res.headers['content-disposition'].split('=')[1])
const fileName = xlsxName || `${filename}.xlsx`
if (res) {
const blob = new Blob([res.data], { type: 'charset=utf-8' })
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL 对象0
document.body.removeChild(elink)
}
})
}
-1
View File
@@ -2,7 +2,6 @@
const cssCdnUrlList: Array<string> = [
'//at.alicdn.com/t/font_2298093_y6u00apwst.css',
'//at.alicdn.com/t/c/font_3882322_9ah7y8m9175.css', //dvadmin3项目用icon
'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
];
// 第三方 js url
const jsCdnUrlList: Array<string> = [];
+59 -27
View File
@@ -3,16 +3,16 @@
* @param {String} jsonString json
* @param {String} defaultValue
*/
import { uiContext } from "@fast-crud/fast-crud";
import { uiContext } from '@fast-crud/fast-crud';
export function parse(jsonString = "{}", defaultValue = {}) {
let result = defaultValue;
try {
result = JSON.parse(jsonString);
} catch (error) {
console.log(error);
}
return result;
export function parse(jsonString = '{}', defaultValue = {}) {
let result = defaultValue;
try {
result = JSON.parse(jsonString);
} catch (error) {
console.log(error);
}
return result;
}
/**
@@ -21,8 +21,8 @@ export function parse(jsonString = "{}", defaultValue = {}) {
* @param {String} msg
* @param {Number} code
*/
export function response(data = {}, msg = "", code = 0) {
return [200, { code, msg, data }];
export function response(data = {}, msg = '', code = 0) {
return [200, { code, msg, data }];
}
/**
@@ -30,8 +30,8 @@ export function response(data = {}, msg = "", code = 0) {
* @param {Any} data
* @param {String} msg
*/
export function responseSuccess(data = {}, msg = "成功") {
return response(data, msg);
export function responseSuccess(data = {}, msg = '成功') {
return response(data, msg);
}
/**
@@ -40,30 +40,62 @@ export function responseSuccess(data = {}, msg = "成功") {
* @param {String} msg
* @param {Number} code
*/
export function responseError(data = {}, msg = "请求失败", code = 500) {
return response(data, msg, code);
export function responseError(data = {}, msg = '请求失败', code = 500) {
return response(data, msg, code);
}
/**
* @description
* @param {Error} error
*/
export function errorLog(error:any,notification=true) {
// 打印到控制台
console.error(error);
// 显示提示
if(notification){
uiContext.get().notification.error({ message: error.message });
}
export function errorLog(error: any, notification = true) {
// 打印到控制台
console.error(error);
// 显示提示
if (notification) {
uiContext.get().notification.error({ message: error.message });
}
}
/**
* @description
* @param {String} msg
*/
export function errorCreate(msg:any,notification=true) {
const error = new Error(msg);
errorLog(error,notification);
// throw error;
export function errorCreate(msg: any, notification = true) {
const error = new Error(msg);
errorLog(error, notification);
// throw error;
}
/**
* @description base64转file
* @param {String} base64 base64字符串
* @param {String} fileName
*/
export function base64ToFile(base64: any, fileName: string) {
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
let data = base64.split(',');
// 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等)
let type = data[0].match(/:(.*?);/)[1];
// 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp
let suffix = type.split('/')[1];
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
const bstr = window.atob(data[1]);
// 获取解码结果字符串的长度
let n = bstr.length;
// 根据解码结果字符串的长度创建一个等长的整形数字数组
// 但在创建时 所有元素初始值都为 0
const u8arr = new Uint8Array(n);
// 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
while (n--) {
// charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
u8arr[n] = bstr.charCodeAt(n);
}
// 利用构造函数创建File文件对象
// new File(bits, name, options)
const file = new File([u8arr], `${fileName}.${suffix}`, {
type: type,
});
// 将File文件对象返回给方法的调用者
return file;
}
+1 -1
View File
@@ -30,7 +30,7 @@ const websocket: socket = {
}
const token = Session.get('token')
if(!token){
message.warning('websocket认证失败')
// message.warning('websocket认证失败')
return null
}
const wsUrl = `${getWsBaseURL()}ws/${token}/`
+9
View File
@@ -0,0 +1,9 @@
/*
*
* API接口响应数据
*/
interface APIResponseData {
code?: number;
data: [];
msg?: string;
}
+16
View File
@@ -0,0 +1,16 @@
import { defineAsyncComponent, AsyncComponentLoader } from 'vue';
export let pluginsAll: any = [];
// 扫描插件目录并注册插件
export const scanAndInstallPlugins = (app: any) => {
const components = import.meta.glob('./**/*.vue');
const pluginNames = new Set();
// 遍历对象并注册异步组件
for (const [key, value] of Object.entries(components)) {
const name = key.slice(key.lastIndexOf('/') + 1, key.lastIndexOf('.'));
app.component(name, defineAsyncComponent(value as AsyncComponentLoader));
const pluginsName = key.match(/\/([^\/]*)\//)?.[1];
pluginNames.add(pluginsName);
}
pluginsAll = Array.from(pluginNames);
console.table('已注册插件:', pluginsAll);
};
+26 -26
View File
@@ -1,41 +1,41 @@
import { request } from '/@/utils/service';
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
export const apiPrefix = '/api/system/area/';
export function GetList(query: PageQuery) {
return request({
url: apiPrefix,
method: 'get',
params: query,
});
export function GetList(query: UserPageQuery) {
return request({
url: apiPrefix,
method: 'get',
params: query,
});
}
export function GetObj(id: InfoReq) {
return request({
url: apiPrefix + id,
method: 'get',
});
return request({
url: apiPrefix + id,
method: 'get',
});
}
export function AddObj(obj: AddReq) {
return request({
url: apiPrefix,
method: 'post',
data: obj,
});
return request({
url: apiPrefix,
method: 'post',
data: obj,
});
}
export function UpdateObj(obj: EditReq) {
return request({
url: apiPrefix + obj.id + '/',
method: 'put',
data: obj,
});
return request({
url: apiPrefix + obj.id + '/',
method: 'put',
data: obj,
});
}
export function DelObj(id: DelReq) {
return request({
url: apiPrefix + id + '/',
method: 'delete',
data: { id },
});
return request({
url: apiPrefix + id + '/',
method: 'delete',
data: { id },
});
}
+55 -14
View File
@@ -1,13 +1,10 @@
import * as api from './api';
import { dict, PageQuery, AddReq, DelReq, EditReq, CrudExpose, CrudOptions } from '@fast-crud/fast-crud';
import { request } from '/@/utils/service';
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
import { dictionary } from '/@/utils/dictionary';
interface CreateCrudOptionsTypes {
crudOptions: CrudOptions;
}
import { successMessage } from '/@/utils/message';
export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExpose }): CreateCrudOptionsTypes {
const pageRequest = async (query: PageQuery) => {
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
@@ -26,8 +23,8 @@ export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExp
* @param row
* @returns {Promise<unknown>}
*/
const loadContentMethod = (tree: any, treeNode: any, resolve: any) => {
api.GetList({ pcode: tree.code }).then((res: any) => {
const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
resolve(res.data);
});
};
@@ -40,6 +37,24 @@ export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExp
editRequest,
delRequest,
},
rowHandle: {
//固定右侧
fixed: 'right',
width: 200,
buttons: {
view: {
show: false,
},
edit: {
iconRight: 'Edit',
type: 'text',
},
remove: {
iconRight: 'Delete',
type: 'text',
},
},
},
pagination: {
show: false,
},
@@ -90,8 +105,10 @@ export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExp
show: true,
},
treeNode: true,
width: 160,
type: 'input',
column:{
minWidth: 120,
},
form: {
rules: [
// 表单校验规则
@@ -108,6 +125,9 @@ export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExp
show: true,
},
type: 'input',
column:{
minWidth: 90,
},
form: {
rules: [
// 表单校验规则
@@ -124,6 +144,9 @@ export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExp
disabled: true,
},
type: 'input',
column:{
minWidth: 120,
},
form: {
rules: [
// 表单校验规则
@@ -140,6 +163,9 @@ export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExp
disabled: true,
},
type: 'input',
column:{
minWidth: 100,
},
form: {
disabled: false,
rules: [
@@ -153,6 +179,9 @@ export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExp
},
initials: {
title: '首字母',
column:{
minWidth: 100,
},
form: {
rules: [
// 表单校验规则
@@ -169,14 +198,26 @@ export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExp
search: {
show: true,
},
width: 90,
type: 'dict-radio',
column: {
minWidth:90,
component: {
name: 'fs-dict-switch',
activeText: '',
inactiveText: '',
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
onChange: compute((context) => {
return () => {
api.UpdateObj(context.row).then((res: APIResponseData) => {
successMessage(res.msg as string);
});
};
}),
},
},
dict: dict({
data: dictionary('button_status_bool'),
}),
form: {
value: true,
},
},
},
},
+4 -12
View File
@@ -4,20 +4,12 @@
</fs-page>
</template>
<script lang="ts" setup>
<script lang="ts" setup name="areas">
import { ref, onMounted } from 'vue';
import { useExpose, useCrud } from '@fast-crud/fast-crud';
import { useFs } from '@fast-crud/fast-crud';
import { createCrudOptions } from './crud';
// crudref
const crudRef = ref();
// crud ref
const crudBinding = ref();
//
const { crudExpose } = useExpose({ crudRef, crudBinding });
// crud
const { crudOptions } = createCrudOptions({ crudExpose });
// crud
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
//
onMounted(() => {
+25
View File
@@ -0,0 +1,25 @@
import { request } from '/@/utils/service';
import { PageQuery } from './types'
export function getRoleList(query: PageQuery) {
return request({
url: '/api/system/role/',
method: 'get',
params: query,
});
}
export function getMenuList(query: PageQuery) {
return request({
url: '/api/system/menu/',
method: 'get',
params: {is_catalog:0,...query},
});
}
export function getModelList() {
return request({
url: '/api/system/column/get_models/',
method: 'get',
});
}

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