Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc15ed15ca | ||
|
|
349986f14f | ||
|
|
fb4bba9000 | ||
|
|
e0d46871c4 | ||
|
|
7691481006 | ||
|
|
120c737de7 | ||
|
|
5729aeb521 | ||
|
|
205cfcca2e | ||
|
|
0fdc108ebf | ||
|
|
064cbee8a2 | ||
|
|
a2d4d6e07e | ||
|
|
d721405ee4 | ||
|
|
0650c95745 | ||
|
|
f291885d57 | ||
|
|
4b77fb903c | ||
|
|
fca82c093a | ||
|
|
49300fb17e | ||
|
|
c763333024 | ||
|
|
42e6c7b600 | ||
|
|
6172dea399 | ||
|
|
2baba8e36f | ||
|
|
0131d31808 | ||
|
|
dd2dcd4ad1 | ||
|
|
fb8b0a5ac6 | ||
|
|
6045312f7e | ||
|
|
4641d9c774 | ||
|
|
c04b33ed31 | ||
|
|
0286ca003f | ||
|
|
54e4d23cf7 | ||
|
|
645f43c887 | ||
|
|
4ac8ed7627 | ||
|
|
94ad6b1bae | ||
|
|
96748da99d | ||
|
|
b74a196c94 |
10
CHANGELOG.md
Normal file
10
CHANGELOG.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Django-Vue3-Admin 更新日志
|
||||
|
||||
## 正式发布v3.0.0版本
|
||||
### 1.新增:列权限管理与授权;
|
||||
### 2.新增:代码新版本发布后,进行升级提醒;
|
||||
### 3.优化:角色管理中按钮权限的操作;
|
||||
### 4.优化:websocket 连接状态显示;
|
||||
### 5.优化:初始化获取系统配置与字典配置,进行动态渲染登录页面;
|
||||
### 6.修复:登录页面中系统配置不生效问题;
|
||||
### 7.其他优化
|
||||
95
README.en.md
95
README.en.md
@@ -1,14 +1,14 @@
|
||||
# Django-Vue3-Admin
|
||||
|
||||
[](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/liqianglog/django-vue-admin)
|
||||
[](https://gitee.com/huge-dream/django-vue3-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/huge-dream/django-vue3-admin)
|
||||
|
||||
[preview](https://demo.dvadmin.com) | [Official website document](https://www.django-vue-admin.com) | [qq group](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [community](https://bbs.django-vue-admin.com) | [plugins market](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
|
||||
|
||||
💡 **「About」**
|
||||
|
||||
We are a group of young people who love Code. In this hot era, we hope to calm down and bring some of our colors and colors through code.
|
||||
It is a completely open-source rapid development platform, provided free for personal use and authorized for group use.
|
||||
Django-Vue3-Admin is a comprehensive basic development platform based on the RBAC (Role-Based Access Control) model for permission control, with column-level granularity. It follows a frontend-backend separation architecture, with Django and Django Rest Framework used for the backend, and Vue3, Composition API, TypeScript, Vite, and Element Plus used for the frontend.
|
||||
|
||||
Because of love, so embrace the future
|
||||
|
||||
## framework introduction
|
||||
|
||||
@@ -18,12 +18,13 @@ Because of love, so embrace the future
|
||||
* 👭The backend uses the Python language Django framework as well as the powerful[Django REST Framework](https://pypi.org/project/djangorestframework)。
|
||||
* 👫Permission authentication use[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),Supports the multi-terminal authentication system.
|
||||
* 👬Support loading dynamic permission menu, multi - way easy permission control.
|
||||
* 💏 Special thanks: [vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/).
|
||||
* 💡 💏 Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
|
||||
* 👬Enhanced Column Permission Control, with granularity down to each column.
|
||||
* 💏Special thanks: [vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/).
|
||||
* 💡Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
|
||||
|
||||
## Online experience
|
||||
|
||||
👩👧👦👩👧👦 demo address:[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com)
|
||||
👩👧👦👩👧👦 demo address:[https://demo.dvadmin.com](https://demo.dvadmin.com)
|
||||
|
||||
* demo account:superadmin
|
||||
|
||||
@@ -39,57 +40,61 @@ Because of love, so embrace the future
|
||||
|
||||
## source code url:
|
||||
|
||||
gitee(Main push):[https://gitee.com/liqianglog/django-vue-admin](https://gitee.com/liqianglog/django-vue-admin)👩👦👦
|
||||
gitee(Main push):[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
github:[https://github.com/liqianglog/django-vue-admin](https://github.com/liqianglog/django-vue-admin)👩👦👦
|
||||
github:[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
## core function
|
||||
|
||||
1. 👨⚕️ Menu management: Configure the system menu, operation permissions, button permissions, back-end interface permissions, etc.
|
||||
2. 🧑⚕️ Department management: Configure the system organization (company, department, role).
|
||||
3. 👩⚕️ Role management: role menu permission allocation, data permission allocation, set roles according to the department for data range permission division.
|
||||
4. 🧑🎓 Rights Specifies the rights of the authorization role.
|
||||
5. 👨🎓 User management: The user is the system operator, this function mainly completes the system user configuration.
|
||||
6. 👬 Interface whitelist: specifies the interface that does not need permission verification.
|
||||
7. 🧑🔧 Dictionary management: Maintenance of some fixed data frequently used in the system.
|
||||
8. 🧑🔧 Regional management: to manage provinces, cities, counties and regions.
|
||||
9. 📁 Attachment management: Unified management of all files and pictures on the platform.
|
||||
10. 🗓 ️operation logs: log and query the system normal operation; Log and query system exception information.
|
||||
11.🔌 [plugins market] (<https://bbs.django-vue-admin.com/plugMarket.html>) : based on the Django framework - Vue - Admin application and plug-in development.
|
||||
1. 👨⚕️Menu Management: Configure system menus, operation permissions, button permission flags, backend interface permissions, etc.
|
||||
2. 🧑⚕️Department Management: Configure system organizational structure (company, department, role).
|
||||
3. 👩⚕️Role Management: Role menu permission assignment, data permission assignment, set role-based data scope permissions by department.
|
||||
4. 🧑🎓Button Permission Control: Authorize role-specific button permissions and interface permissions, enabling authorization of data scope for each interface.
|
||||
5. 🧑🎓Field Column Permission Control: Authorize page field display permissions, specifically for the display permissions of a certain column.
|
||||
6. 👨🎓User Management: Users are system operators, and this function is mainly used for system user configuration.
|
||||
7. 👬API Whitelist: Configure interfaces that do not require permission verification.
|
||||
8. 🧑🔧Dictionary Management: Maintain frequently used and relatively fixed data in the system.
|
||||
9. 🧑🔧Region Management: Manage provinces, cities, counties, and districts.
|
||||
10. 📁File Management: Unified management of all files, images, etc., on the platform.
|
||||
11. 🗓️Operation Logs: Record and query logs for normal system operations and exceptional system information.
|
||||
12. 🔌[Plugin Market](https://bbs.django-vue-admin.com/plugMarket.html): Applications and plugins developed based on the Django-Vue-Admin framework.
|
||||
|
||||
## plugins market 🔌
|
||||
|
||||
* Celery Asynchronous task:[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
|
||||
* Upgrade center backend:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
|
||||
* Upgrade center front:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
|
||||
Updating...
|
||||
|
||||
## Repository Branch Explanation 💈
|
||||
Main Branch: master (stable version)
|
||||
Development Branch: develop
|
||||
|
||||
## before start project you need:
|
||||
|
||||
~~~
|
||||
Python >= 3.8.0
|
||||
nodejs >= 14.0
|
||||
Mysql >= 5.7.0 (Optional. The default database is sqlite3. 8.0 is recommended)
|
||||
Redis(Optional, the latest edition)
|
||||
Python >= 3.11.0 (Minimum version 3.9+)
|
||||
Node.js >= 16.0
|
||||
Mysql >= 8.0 (Optional, default database: SQLite3, supports 5.7+, recommended version: 8.0)
|
||||
Redis (Optional, latest version)
|
||||
~~~
|
||||
|
||||
## frontend♝
|
||||
|
||||
```bash
|
||||
# clone code
|
||||
git clone https://gitee.com/liqianglog/django-vue-admin.git
|
||||
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||
|
||||
# enter code dir
|
||||
cd web
|
||||
|
||||
# install dependence
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
npm install yarn
|
||||
yarn install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# Start service
|
||||
npm run dev
|
||||
yarn run dev
|
||||
# Visit http://localhost:8080 in your browser
|
||||
# Parameters such as boot port can be configured in the #.env.development file
|
||||
# Build the production environment
|
||||
# npm run build
|
||||
# yarn run build
|
||||
```
|
||||
|
||||
## backend💈
|
||||
@@ -111,8 +116,8 @@ npm run dev
|
||||
python3 manage.py init_area
|
||||
8. start backend
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
or daphne :
|
||||
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
||||
or uvicorn :
|
||||
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8
|
||||
~~~
|
||||
|
||||
### visit backend swagger
|
||||
@@ -120,12 +125,12 @@ or daphne :
|
||||
* visit url:[http://localhost:8080](http://localhost:8080) (The default address is this one. If you want to change it, follow the configuration file)
|
||||
* account:`superadmin` password:`admin123456`
|
||||
|
||||
### docker-compose
|
||||
### docker-compose
|
||||
|
||||
~~~shell
|
||||
docker-compose up -d
|
||||
# Initialize backend data (first execution only)
|
||||
docker exec -ti dvadmin-django bash
|
||||
docker exec -ti dvadmin3-django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init_area
|
||||
@@ -147,22 +152,24 @@ docker-compose up -d --build
|
||||
|
||||
## Demo screenshot✅
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
104
README.md
104
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/liqianglog/django-vue-admin)
|
||||
|
||||
[预 览](https://demo.dvadmin.com) | [官网文档](https://www.django-vue-admin.com) | [群聊](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [社区](https://bbs.django-vue-admin.com) | [插件市场](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
|
||||
[预 览](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)
|
||||
|
||||
|
||||
|
||||
@@ -10,11 +10,14 @@
|
||||
|
||||
我们是一群热爱代码的青年,在这个炙热的时代下,我们希望静下心来通过Code带来一点我们的色彩和颜色。
|
||||
|
||||
因为热爱,所以拥抱未来
|
||||
因为热爱,所以拥抱未来!
|
||||
|
||||
|
||||
## 平台简介
|
||||
|
||||
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
||||
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) 是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。
|
||||
django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,20 +25,29 @@
|
||||
* 👭后端采用 Python 语言 Django 框架以及强大的 [Django REST Framework](https://pypi.org/project/djangorestframework)。
|
||||
* 👫权限认证使用[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),支持多终端认证系统。
|
||||
* 👬支持加载动态权限菜单,多方式轻松权限控制。
|
||||
* 👬全新的列权限管控,粒度细化到每一列。
|
||||
* 💏特别鸣谢:[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)。
|
||||
* 💡 特别感谢[jetbrains](https://www.jetbrains.com/) 为本开源项目提供免费的 IntelliJ IDEA 授权。
|
||||
* 💡特别感谢[jetbrains](https://www.jetbrains.com/) 为本开源项目提供免费的 IntelliJ IDEA 授权。
|
||||
|
||||
#### 🏭 环境支持
|
||||
|
||||
| Edge | Firefox | Chrome | Safari |
|
||||
| --------- | ------------ | ----------- | ----------- |
|
||||
| Edge ≥ 79 | Firefox ≥ 78 | Chrome ≥ 64 | Safari ≥ 12 |
|
||||
|
||||
> 由于 Vue3 不再支持 IE11,故而 ElementPlus 也不支持 IE11 及之前版本。
|
||||
|
||||
|
||||
|
||||
## 在线体验
|
||||
|
||||
👩👧👦演示地址:[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com)
|
||||
👩👧👦演示地址:[https://demo.dvadmin.com](https://demo.dvadmin.com)
|
||||
|
||||
- 账号:superadmin
|
||||
- 账号:superadmin
|
||||
|
||||
- 密码:admin123456
|
||||
|
||||
👩👦👦文档地址:[https://django-vue-admin.com](https://django-vue-admin.com)
|
||||
👩👦👦文档地址:[coding](https://dvadmin-private.coding.net/share/km/cec69f3d-30fe-47d5-bd97-e9e851f0b776/K-2)
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +58,8 @@
|
||||
- 插件市场:[戳我](https://bbs.django-vue-admin.com/plugMarket.html)👩👦👦
|
||||
|
||||
- django-vue-admin交流01群(已满):812482043 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=aJVwjDvH-Es4MPJQuoO32N0SucK22TE5&jump_from=webapi)
|
||||
- django-vue-admin交流02群:687252418 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=4jJN4IjWGfxJ8YJXbb_gTsuWjR34WLdc&jump_from=webapi)
|
||||
- django-vue-admin交流02群(已满):687252418 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=4jJN4IjWGfxJ8YJXbb_gTsuWjR34WLdc&jump_from=webapi)
|
||||
- django-vue-admin交流03群:442108213 [点击链接加入群聊](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=wsm5oSz3K8dElBYUDtLTcQSEPhINFkl8&authKey=M6sbER0z59ZakgBr5erFeZyFZU15CI52bErNZa%2FxSvvGIuVAbY0N5866v89hm%2FK4&noverify=0&group_code=442108213)
|
||||
|
||||
- 二维码
|
||||
|
||||
@@ -54,10 +67,9 @@
|
||||
|
||||
## 源码地址
|
||||
|
||||
gitee地址(主推):[https://gitee.com/liqianglog/django-vue-admin](https://gitee.com/liqianglog/django-vue-admin)👩👦👦
|
||||
|
||||
github地址:[https://github.com/liqianglog/django-vue-admin](https://github.com/liqianglog/django-vue-admin)👩👦👦
|
||||
gitee地址(主推):[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
github地址:[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
|
||||
## 内置功能
|
||||
@@ -65,47 +77,51 @@ github地址:[https://github.com/liqianglog/django-vue-admin](https://github.c
|
||||
1. 👨⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
||||
2. 🧑⚕️部门管理:配置系统组织机构(公司、部门、角色)。
|
||||
3. 👩⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
|
||||
4. 🧑🎓权限权限:授权角色的权限范围。
|
||||
5. 👨🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
6. 👬接口白名单:配置不需要进行权限校验的接口。
|
||||
7. 🧑🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
8. 🧑🔧地区管理:对省市县区域进行管理。
|
||||
9. 📁附件管理:对平台上所有文件、图片等进行统一管理。
|
||||
10. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
11. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html):基于Django-Vue-Admin框架开发的应用和插件。
|
||||
4. 🧑🎓按钮权限控制:授权角色的按钮权限和接口权限,可做到每一个接口都能授权数据范围。
|
||||
5. 🧑🎓字段列权限控制:授权页面的字段显示权限,具体到某一列的显示权限。
|
||||
7. 👨🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
8. 👬接口白名单:配置不需要进行权限校验的接口。
|
||||
9. 🧑🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
10. 🧑🔧地区管理:对省市县区域进行管理。
|
||||
11. 📁附件管理:对平台上所有文件、图片等进行统一管理。
|
||||
12. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
13. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html):基于Django-Vue-Admin框架开发的应用和插件。
|
||||
|
||||
## 插件市场 🔌
|
||||
更新中...
|
||||
|
||||
## 仓库分支说明 💈
|
||||
主分支:master(稳定版本)
|
||||
开发分支:develop
|
||||
|
||||
- Celery异步任务:[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
|
||||
- 升级中心后端:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
|
||||
- 升级中心前端:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
|
||||
|
||||
## 准备工作
|
||||
~~~
|
||||
Python >= 3.8.0 (推荐3.8+版本)
|
||||
nodejs >= 14.0 (推荐最新)
|
||||
Mysql >= 5.7.0 (可选,默认数据库sqlite3,推荐8.0版本)
|
||||
Redis(可选,最新版)
|
||||
Python >= 3.11.0 (最低3.9+版本)
|
||||
nodejs >= 16.0
|
||||
Mysql >= 8.0 (可选,默认数据库sqlite3,支持5.7+,推荐8.0版本)
|
||||
Redis (可选,最新版)
|
||||
~~~
|
||||
|
||||
## 前端♝
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://gitee.com/liqianglog/django-vue-admin.git
|
||||
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||
|
||||
# 进入项目目录
|
||||
cd web
|
||||
|
||||
# 安装依赖
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
npm install yarn
|
||||
yarn install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# 启动服务
|
||||
npm run dev
|
||||
yarn build
|
||||
# 浏览器访问 http://localhost:8080
|
||||
# .env.development 文件中可配置启动端口等参数
|
||||
# 构建生产环境
|
||||
# npm run build
|
||||
# yarn run build
|
||||
```
|
||||
|
||||
|
||||
@@ -129,9 +145,11 @@ npm run dev
|
||||
python3 manage.py init_area
|
||||
8. 启动项目
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
或使用 daphne :
|
||||
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
||||
或使用 uvicorn :
|
||||
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8
|
||||
~~~
|
||||
## 开发建议
|
||||
前后端backend与web各自单独一个窗口打开进行开发
|
||||
|
||||
### 访问项目
|
||||
|
||||
@@ -148,7 +166,7 @@ npm run dev
|
||||
# 先安装docker-compose (自行百度安装),执行此命令等待安装,如有使用celery插件请打开docker-compose.yml中celery 部分注释
|
||||
docker-compose up -d
|
||||
# 初始化后端数据(第一次执行即可)
|
||||
docker exec -ti dvadmin-django bash
|
||||
docker exec -ti dvadmin3-django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init_area
|
||||
@@ -172,25 +190,25 @@ docker-compose up -d --build
|
||||
|
||||
## 演示图✅
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -98,3 +98,4 @@ media/
|
||||
__pypackages__/
|
||||
package-lock.json
|
||||
gunicorn.pid
|
||||
!plugins/__init__.py
|
||||
|
||||
@@ -43,10 +43,8 @@ sys.path.insert(0, os.path.join(PLUGINS_PATH))
|
||||
DEBUG = locals().get("DEBUG", True)
|
||||
ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
|
||||
|
||||
# Application definition
|
||||
CUSTOM_APPS = [
|
||||
"dvadmin.system",
|
||||
]
|
||||
# 列权限需要排除的App应用
|
||||
COLUMN_EXCLUDE_APPS = ['channels', 'captcha'] + locals().get("COLUMN_EXCLUDE_APPS", [])
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.auth",
|
||||
@@ -60,8 +58,8 @@ INSTALLED_APPS = [
|
||||
"corsheaders", # 注册跨域app
|
||||
"drf_yasg",
|
||||
"captcha",
|
||||
'channels',
|
||||
*locals().get("CUSTOM_APPS", []), # 所有项目里写的app需要在env.py文件里的CUSTOM_APPS中
|
||||
"channels",
|
||||
"dvadmin.system",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@@ -178,7 +176,6 @@ CHANNEL_LAYERS = {
|
||||
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||
}
|
||||
}
|
||||
REDIS_URL = locals().get('REDIS_URL', "")
|
||||
# CHANNEL_LAYERS = {
|
||||
# 'default': {
|
||||
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
|
||||
@@ -400,11 +397,10 @@ TENANT_SHARED_APPS = []
|
||||
PLUGINS_URL_PATTERNS = []
|
||||
# ********** 一键导入插件配置开始 **********
|
||||
# 例如:
|
||||
#from dvadmin3_upgrade_center.settings import * # 升级中心
|
||||
# from dvadmin_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 *
|
||||
# ...
|
||||
# ********** 一键导入插件配置结束 **********
|
||||
|
||||
@@ -73,7 +73,7 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer):
|
||||
unread_count = await _get_message_unread(self.user_id)
|
||||
if unread_count == 0:
|
||||
# 发送连接成功
|
||||
await self.send_json(set_message('system', 'SYSTEM', '连接成功'))
|
||||
await self.send_json(set_message('system', 'SYSTEM', '您已上线'))
|
||||
else:
|
||||
await self.send_json(
|
||||
set_message('system', 'SYSTEM', "请查看您的未读消息~",
|
||||
|
||||
@@ -44,6 +44,5 @@ LOGIN_NO_CAPTCHA_AUTH = True
|
||||
# ================================================= #
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
CUSTOM_APPS = [
|
||||
"dvadmin.system",
|
||||
]
|
||||
# 列权限中排除App应用
|
||||
COLUMN_EXCLUDE_APPS = []
|
||||
|
||||
@@ -9,7 +9,7 @@ django.setup()
|
||||
from dvadmin.system.models import (
|
||||
Role, Dept, Users, Menu, MenuButton,
|
||||
ApiWhiteList, Dictionary, SystemConfig,
|
||||
RoleMenuPermission, RoleApiPermission, Columns
|
||||
RoleMenuPermission, RoleMenuButtonPermission, MenuField
|
||||
)
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
|
||||
@@ -42,14 +42,35 @@ class UsersInitSerializer(CustomModelSerializer):
|
||||
}
|
||||
|
||||
|
||||
class MenuButtonInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id', 'name', 'value', 'api', 'method', 'menu']
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MenuFieldInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化列权限-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuField
|
||||
fields = ['id', 'menu','field_name','title','model']
|
||||
read_only_fields = ["id"]
|
||||
|
||||
class MenuInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
递归深度获取数信息(用于生成初始化json文件)
|
||||
"""
|
||||
name = serializers.CharField(required=True)
|
||||
menu_type = serializers.IntegerField(required=True)
|
||||
name = serializers.CharField(required=False)
|
||||
children = serializers.SerializerMethodField()
|
||||
|
||||
menu_button = serializers.SerializerMethodField()
|
||||
menu_field = serializers.SerializerMethodField()
|
||||
def get_children(self, obj: Menu):
|
||||
data = []
|
||||
instance = Menu.objects.filter(parent_id=obj.id)
|
||||
@@ -58,18 +79,34 @@ 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 get_menu_field(self, obj: Menu):
|
||||
data = []
|
||||
instance = obj.menufield_set.order_by('field_name')
|
||||
if instance:
|
||||
data = list(instance.values('field_name', 'title','model'))
|
||||
return data
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
children = self.initial_data.get('children')
|
||||
menu_button = self.initial_data.get('menu_button')
|
||||
menu_field = self.initial_data.get('menu_field')
|
||||
# 菜单表
|
||||
if children:
|
||||
for menu_data in children:
|
||||
menu_data['parent'] = instance.id
|
||||
filter_data = {
|
||||
"name": menu_data['name'],
|
||||
"web_path": menu_data['web_path'],
|
||||
"component": menu_data['component'],
|
||||
"menu_type": menu_data['menu_type'],
|
||||
"component_name": menu_data['component_name'],
|
||||
}
|
||||
instance_obj = Menu.objects.filter(**filter_data).first()
|
||||
if instance_obj and not self.initial_data.get('reset'):
|
||||
@@ -77,12 +114,36 @@ 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()
|
||||
# 列权限
|
||||
if menu_field:
|
||||
for field_data in menu_field:
|
||||
field_data['menu'] = instance.id
|
||||
filter_data = {
|
||||
'menu':field_data['menu'],
|
||||
'field_name':field_data['field_name']
|
||||
}
|
||||
instance_obj = MenuField.objects.filter(**filter_data).first()
|
||||
serializer = MenuFieldInitSerializer(instance_obj, data=field_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['name', 'icon', 'sort', 'is_link', 'menu_type', 'web_path', 'component', 'component_name', 'status',
|
||||
'cache', 'visible', 'parent', 'children', 'creator', 'dept_belong_id']
|
||||
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
|
||||
'cache', 'visible', 'parent', 'children', 'menu_button','menu_field', 'creator', 'dept_belong_id']
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
@@ -135,62 +196,37 @@ class RoleMenuInitSerializer(CustomModelSerializer):
|
||||
}
|
||||
|
||||
|
||||
class RoleApiPermissionInitSerializer(CustomModelSerializer):
|
||||
class RoleMenuButtonInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化角色接口权限(用于生成初始化json文件)
|
||||
初始化角色菜单按钮(用于生成初始化json文件)
|
||||
"""
|
||||
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)
|
||||
menu_button_value = serializers.CharField(max_length=100, 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 = RoleApiPermission
|
||||
fields = ['role_key', 'name','api','method', 'data_range', 'dept', 'creator', 'dept_belong_id']
|
||||
model = RoleMenuButtonPermission
|
||||
fields = ['role_key', 'menu_button_value', '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):
|
||||
"""
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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
@@ -1,14 +0,0 @@
|
||||
[
|
||||
{
|
||||
"role_key": "admin",
|
||||
"name": "菜单列表查询",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"role_key": "public",
|
||||
"name": "菜单列表查询",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 0
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"role_key": "admin",
|
||||
"menu_button_value": "menu:Search",
|
||||
"data_range": 0
|
||||
},
|
||||
{
|
||||
"role_key": "public",
|
||||
"menu_button_value":"menu:Search",
|
||||
"data_range": 0
|
||||
}
|
||||
]
|
||||
@@ -65,6 +65,20 @@
|
||||
"placeholder": null,
|
||||
"setting": null,
|
||||
"children": [
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "网站标题",
|
||||
"key": "site_title",
|
||||
"value": "Dvadmin",
|
||||
"sort": 1,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [],
|
||||
"placeholder": "请输入网站标题",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "网站名称",
|
||||
@@ -116,7 +130,7 @@
|
||||
"parent": 1,
|
||||
"title": "版权信息",
|
||||
"key": "copyright",
|
||||
"value": "2021-2022 django-vue-admin.com 版权所有",
|
||||
"value": "2021-2024 django-vue-admin.com 版权所有",
|
||||
"sort": 4,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
|
||||
@@ -11,7 +11,7 @@ from dvadmin.utils.core_initialize import CoreInitialize
|
||||
from dvadmin.system.fixtures.initSerializer import (
|
||||
UsersInitSerializer, DeptInitSerializer, RoleInitSerializer,
|
||||
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer,
|
||||
SystemConfigInitSerializer, RoleMenuInitSerializer, RoleApiPermissionInitSerializer, RoleColumnInitSerializer
|
||||
SystemConfigInitSerializer, RoleMenuInitSerializer, RoleMenuButtonInitSerializer
|
||||
)
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class Initialize(CoreInitialize):
|
||||
"""
|
||||
初始化菜单信息
|
||||
"""
|
||||
self.init_base(MenuInitSerializer, unique_fields=['name'])
|
||||
self.init_base(MenuInitSerializer, unique_fields=['name', 'web_path', 'component', 'component_name'])
|
||||
|
||||
def init_role_menu(self):
|
||||
"""
|
||||
@@ -47,17 +47,11 @@ class Initialize(CoreInitialize):
|
||||
"""
|
||||
self.init_base(RoleMenuInitSerializer, unique_fields=['role', 'menu'])
|
||||
|
||||
def init_role_api_permission(self):
|
||||
def init_role_menu_button(self):
|
||||
"""
|
||||
初始化角色菜单按钮信息
|
||||
"""
|
||||
self.init_base(RoleApiPermissionInitSerializer, unique_fields=['role', 'api','name'])
|
||||
|
||||
def init_role_column(self):
|
||||
"""
|
||||
初始化角色字段权限
|
||||
"""
|
||||
self.init_base(RoleColumnInitSerializer, unique_fields=['app','model','field_name'])
|
||||
self.init_base(RoleMenuButtonInitSerializer, unique_fields=['role', 'menu_button'])
|
||||
|
||||
def init_api_white_list(self):
|
||||
"""
|
||||
@@ -83,8 +77,7 @@ class Initialize(CoreInitialize):
|
||||
self.init_users()
|
||||
self.init_menu()
|
||||
self.init_role_menu()
|
||||
self.init_role_api_permission()
|
||||
self.init_role_column()
|
||||
self.init_role_menu_button()
|
||||
self.init_api_white_list()
|
||||
self.init_dictionary()
|
||||
self.init_system_config()
|
||||
|
||||
@@ -155,24 +155,22 @@ 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="显示排序")
|
||||
is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
|
||||
MENU_TYPE_CHOICES =(
|
||||
(0, "目录"),
|
||||
(1, "菜单"),
|
||||
(2, "按钮"),
|
||||
ISLINK_CHOICES = (
|
||||
(0, "否"),
|
||||
(1, "是"),
|
||||
)
|
||||
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="组件地址/按钮权限值")
|
||||
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="组件地址")
|
||||
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"
|
||||
@@ -180,20 +178,27 @@ class Menu(CoreModel):
|
||||
verbose_name_plural = verbose_name
|
||||
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='应用名')
|
||||
class MenuField(CoreModel):
|
||||
model = models.CharField(max_length=64, verbose_name='表名')
|
||||
menu = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name='菜单', db_constraint=False)
|
||||
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
|
||||
title = models.CharField(max_length=64, verbose_name='字段显示名')
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_menu_field"
|
||||
verbose_name = "菜单字段表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("id",)
|
||||
|
||||
class FieldPermission(CoreModel):
|
||||
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
|
||||
field = models.ForeignKey(to='MenuField', on_delete=models.CASCADE,related_name='menu_field', verbose_name='字段', db_constraint=False)
|
||||
is_query = models.BooleanField(default=1, verbose_name='是否可查询')
|
||||
is_create = models.BooleanField(default=1, verbose_name='是否可创建')
|
||||
is_update = models.BooleanField(default=1, verbose_name='是否可更新')
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_columns"
|
||||
verbose_name = "列权限表"
|
||||
db_table = table_prefix + "system_field_permission"
|
||||
verbose_name = "字段权限表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("id",)
|
||||
|
||||
@@ -251,25 +256,25 @@ class RoleMenuPermission(CoreModel):
|
||||
# ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
class RoleApiPermission(CoreModel):
|
||||
class RoleMenuButtonPermission(CoreModel):
|
||||
role = models.ForeignKey(
|
||||
to="Role",
|
||||
db_constraint=False,
|
||||
related_name="role_api",
|
||||
related_name="role_menu_button",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="关联角色",
|
||||
help_text="关联角色",
|
||||
)
|
||||
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"),
|
||||
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
|
||||
)
|
||||
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
|
||||
help_text="接口请求方法")
|
||||
DATASCOPE_CHOICES = (
|
||||
(0, "仅本人数据权限"),
|
||||
(1, "本部门及以下数据权限"),
|
||||
@@ -283,8 +288,8 @@ class RoleApiPermission(CoreModel):
|
||||
help_text="数据权限-关联部门")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "role_api_permission"
|
||||
verbose_name = "角色接口权限表"
|
||||
db_table = table_prefix + "role_menu_button_permission"
|
||||
verbose_name = "角色按钮权限表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
@@ -457,7 +462,7 @@ class SystemConfig(CoreModel):
|
||||
help_text="父级",
|
||||
)
|
||||
title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")
|
||||
key = models.CharField(max_length=50, verbose_name="键", help_text="键", db_index=True)
|
||||
key = models.CharField(max_length=20, 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="启用状态")
|
||||
|
||||
@@ -8,17 +8,19 @@ 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_api_permission import RoleApiPermissionViewSet
|
||||
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
|
||||
from dvadmin.system.views.system_config import SystemConfigViewSet
|
||||
from dvadmin.system.views.user import UserViewSet
|
||||
from dvadmin.system.views.column import ColumnViewSet
|
||||
from dvadmin.system.views.menu_field import MenuFieldViewSet
|
||||
|
||||
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,9 +31,9 @@ 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_api_permission', RoleApiPermissionViewSet)
|
||||
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
|
||||
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
||||
system_url.register(r'column', ColumnViewSet)
|
||||
system_url.register(r'column', MenuFieldViewSet)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
@@ -9,7 +9,7 @@ from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Dept, RoleApiPermission, Users
|
||||
from dvadmin.system.models import Dept, RoleMenuButtonPermission, Users
|
||||
from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
@@ -135,7 +135,7 @@ class DeptViewSet(CustomModelViewSet):
|
||||
queryset = Dept.objects.values('id', 'name', 'parent')
|
||||
else:
|
||||
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)
|
||||
data_range = RoleMenuButtonPermission.objects.filter(role__in=role_ids).values_list('data_range', flat=True)
|
||||
user_dept_id = request.user.dept.id
|
||||
dept_list = [user_dept_id]
|
||||
data_range_list = list(set(data_range))
|
||||
|
||||
@@ -8,10 +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, RoleMenuPermission
|
||||
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
|
||||
from dvadmin.system.views.menu_button import MenuButtonSerializer
|
||||
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
@@ -21,7 +21,7 @@ class MenuSerializer(CustomModelSerializer):
|
||||
菜单表的简单序列化器
|
||||
"""
|
||||
menuPermission = serializers.SerializerMethodField(read_only=True)
|
||||
hasChildren = serializers.SerializerMethodField()
|
||||
hasChild = serializers.SerializerMethodField()
|
||||
|
||||
def get_menuPermission(self, instance):
|
||||
queryset = instance.menuPermission.order_by('-name').values('id', 'name', 'value')
|
||||
@@ -31,7 +31,7 @@ class MenuSerializer(CustomModelSerializer):
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_hasChildren(self, instance):
|
||||
def get_hasChild(self, instance):
|
||||
hasChild = Menu.objects.filter(parent=instance.id)
|
||||
if hasChild:
|
||||
return True
|
||||
@@ -71,7 +71,7 @@ class WebRouterSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = (
|
||||
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'menu_type', 'web_path', 'component',
|
||||
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', 'web_path', 'component',
|
||||
'component_name', 'cache', 'visible', 'status')
|
||||
read_only_fields = ["id"]
|
||||
|
||||
@@ -90,57 +90,45 @@ class MenuViewSet(CustomModelViewSet):
|
||||
create_serializer_class = MenuCreateSerializer
|
||||
update_serializer_class = MenuCreateSerializer
|
||||
search_fields = ['name', 'status']
|
||||
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'menu_type']
|
||||
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog']
|
||||
|
||||
@action(methods=['get'], detail=False)
|
||||
def tree(self, request):
|
||||
def list(self, request):
|
||||
"""懒加载"""
|
||||
request.query_params._mutable = True
|
||||
params = request.query_params
|
||||
menu_type = params.get('menu_type', 0)
|
||||
queryset = Menu.objects.filter(menu_type=menu_type).order_by('sort')
|
||||
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(parent=parent)
|
||||
else:
|
||||
queryset = self.queryset.filter()
|
||||
else:
|
||||
queryset = self.queryset.filter(parent__isnull=True)
|
||||
queryset = self.filter_queryset(queryset)
|
||||
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)
|
||||
return SuccessResponse(data=data)
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[])
|
||||
def web_router(self, request):
|
||||
"""用于前端获取当前角色的路由"""
|
||||
user = request.user
|
||||
if user.is_superuser:
|
||||
queryset = self.queryset.filter(status=1,menu_type__in=[0,1])
|
||||
queryset = self.queryset.filter(status=1)
|
||||
else:
|
||||
role_list = user.role.values_list('id', flat=True)
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id', flat=True)
|
||||
queryset = Menu.objects.filter(id__in=menu_list,menu_type__in=[0,1])
|
||||
queryset = Menu.objects.filter(id__in=menu_list)
|
||||
serializer = WebRouterSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return SuccessResponse(data=data, total=len(data), msg="获取成功")
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[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):
|
||||
"""用于菜单管理获取所有的菜单"""
|
||||
|
||||
83
backend/dvadmin/system/views/menu_button.py
Normal file
83
backend/dvadmin/system/views/menu_button.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# -*- 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, SuccessResponse
|
||||
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','menu']
|
||||
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.order_by('create_datetime')
|
||||
serializer_class = MenuButtonSerializer
|
||||
create_serializer_class = MenuButtonCreateUpdateSerializer
|
||||
update_serializer_class = MenuButtonCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""
|
||||
重写list方法
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(serializer.data,msg="获取成功")
|
||||
|
||||
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
|
||||
def menu_button_all_permission(self,request):
|
||||
"""
|
||||
获取所有的按钮权限
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
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)
|
||||
@@ -1,52 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.apps import apps
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Columns, Role
|
||||
from dvadmin.system.models import Role, MenuField
|
||||
from dvadmin.utils.models import get_custom_app_models
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse
|
||||
|
||||
|
||||
class ColumnSerializer(CustomModelSerializer):
|
||||
class MenuFieldSerializer(CustomModelSerializer):
|
||||
"""
|
||||
列权限序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Columns
|
||||
model = MenuField
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id']
|
||||
|
||||
|
||||
class ColumnViewSet(CustomModelViewSet):
|
||||
class MenuFieldViewSet(CustomModelViewSet):
|
||||
"""
|
||||
列权限视图集
|
||||
"""
|
||||
queryset = Columns.objects.all()
|
||||
serializer_class = ColumnSerializer
|
||||
queryset = MenuField.objects.all()
|
||||
serializer_class = MenuFieldSerializer
|
||||
|
||||
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)
|
||||
menu = request.query_params.get('menu')
|
||||
if not menu:
|
||||
return SuccessResponse([])
|
||||
queryset = self.filter_queryset(self.get_queryset().filter(menu=menu))
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
payload = request.data
|
||||
for model in get_custom_app_models(payload.get('app')):
|
||||
if payload.get('model') == model['model']:
|
||||
for model in apps.get_models():
|
||||
if payload.get('model') == model.__name__:
|
||||
break
|
||||
else:
|
||||
return ErrorResponse(msg='模型表不存在')
|
||||
|
||||
if Columns.objects.filter(app=model['app'], model=model['model'], field_name=payload.get('field_name')).exists():
|
||||
if MenuField.objects.filter(menu=payload.get('menu'),model=model.__name__, field_name=payload.get('field_name')).exists():
|
||||
return ErrorResponse(msg='‘%s’ 字段权限已有,不可重复创建' % payload.get('title'))
|
||||
|
||||
return super().create(request, *args, **kwargs)
|
||||
@@ -55,34 +54,31 @@ class ColumnViewSet(CustomModelViewSet):
|
||||
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']
|
||||
})
|
||||
for model in get_custom_app_models():
|
||||
res.append({
|
||||
'app': model['app'],
|
||||
'title': model['verbose'],
|
||||
'key': model['model']
|
||||
})
|
||||
return DetailResponse(res)
|
||||
|
||||
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def auto_match_fields(self, request):
|
||||
"""自动匹配已有的字段"""
|
||||
role_id = request.data.get('role')
|
||||
app_name = request.data.get('app')
|
||||
menu_id = request.data.get('menu')
|
||||
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 not menu_id or not model_name:
|
||||
return ErrorResponse( msg='参数错误')
|
||||
for model in get_custom_app_models():
|
||||
if model['model'] != model_name:
|
||||
continue
|
||||
for field in model['fields']:
|
||||
if Columns.objects.filter(
|
||||
role_id=role_id, app=app_name, model=model_name, field_name=field['name']
|
||||
if MenuField.objects.filter(
|
||||
menu_id=menu_id, model=model_name, field_name=field['name']
|
||||
).exists():
|
||||
continue
|
||||
data = {
|
||||
'role': role_id,
|
||||
'app': app_name,
|
||||
'menu': menu_id,
|
||||
'model': model_name,
|
||||
'field_name': field['name'],
|
||||
'title': str(field['title']),
|
||||
@@ -90,4 +86,4 @@ class ColumnViewSet(CustomModelViewSet):
|
||||
serializer = self.get_serializer(data=data, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return DetailResponse(msg='匹配成功')
|
||||
return SuccessResponse(msg='匹配成功')
|
||||
@@ -13,6 +13,7 @@ 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
|
||||
@@ -38,6 +39,7 @@ 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,11 +63,21 @@ 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']
|
||||
fields = ['id', 'parent', 'name', 'menuPermission']
|
||||
|
||||
|
||||
class MenuButtonPermissionSerializer(CustomModelSerializer):
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
# -*- 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)
|
||||
@@ -58,38 +58,8 @@ 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_menu_permission(self,request):
|
||||
def save_auth(self,request):
|
||||
"""
|
||||
保存页面菜单授权
|
||||
:param request:
|
||||
|
||||
395
backend/dvadmin/system/views/role_menu_button_permission.py
Normal file
395
backend/dvadmin/system/views/role_menu_button_permission.py
Normal file
@@ -0,0 +1,395 @@
|
||||
# -*- 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 RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, FieldPermission, \
|
||||
MenuField
|
||||
from dvadmin.system.views.menu import MenuSerializer
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单按钮-序列化器
|
||||
"""
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
menu_button__name = serializers.CharField(source='menu_button.name', read_only=True)
|
||||
menu_button__value = serializers.CharField(source='menu_button.value', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
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 RoleMenuButtonPermission.objects.filter(
|
||||
menu_button__id=instance['id'],
|
||||
role__id=params.get('role'),
|
||||
).exists()
|
||||
|
||||
def get_data_range(self, instance):
|
||||
params = self.request.query_params
|
||||
obj = RoleMenuButtonPermission.objects.filter(
|
||||
menu_button__id=instance['id'],
|
||||
role__id=params.get('role'),
|
||||
).first()
|
||||
if obj is None:
|
||||
return None
|
||||
return obj.data_range
|
||||
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id','name','value','isCheck','data_range']
|
||||
|
||||
class RoleFieldPermissionSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = FieldPermission
|
||||
fields = "__all__"
|
||||
|
||||
class RoleMenuFieldSerializer(CustomModelSerializer):
|
||||
is_query = serializers.SerializerMethodField()
|
||||
is_create = serializers.SerializerMethodField()
|
||||
is_update = serializers.SerializerMethodField()
|
||||
|
||||
def get_is_query(self, instance):
|
||||
params = self.request.query_params
|
||||
queryset = instance.menu_field.filter(role=params.get('role')).first()
|
||||
if queryset:
|
||||
return queryset.is_query
|
||||
return False
|
||||
|
||||
def get_is_create(self, instance):
|
||||
params = self.request.query_params
|
||||
queryset = instance.menu_field.filter(role=params.get('role')).first()
|
||||
if queryset:
|
||||
return queryset.is_create
|
||||
return False
|
||||
|
||||
def get_is_update(self, instance):
|
||||
params = self.request.query_params
|
||||
queryset = instance.menu_field.filter(role=params.get('role')).first()
|
||||
if queryset:
|
||||
return queryset.is_update
|
||||
return False
|
||||
class Meta:
|
||||
model = MenuField
|
||||
fields = ['id','field_name','title','is_query','is_create','is_update']
|
||||
|
||||
|
||||
class RoleMenuPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单和按钮权限
|
||||
"""
|
||||
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):
|
||||
col_list = MenuField.objects.filter(menu=instance['id'])
|
||||
serializer = RoleMenuFieldSerializer(col_list,many=True,request=self.request)
|
||||
return serializer.data
|
||||
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['id','name','isCheck','btns','columns']
|
||||
|
||||
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
"""
|
||||
菜单按钮接口
|
||||
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 get_role_premission(self, request):
|
||||
"""
|
||||
角色授权获取:
|
||||
:param request: role
|
||||
:return: menu,btns,columns
|
||||
"""
|
||||
params = request.query_params
|
||||
role = params.get('role',None)
|
||||
if role is None:
|
||||
return ErrorResponse(msg="未获取到角色信息")
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all()
|
||||
else:
|
||||
role_id = request.user.role.values_list('id', flat=True)
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id',flat=True)
|
||||
queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id').all()
|
||||
serializer = RoleMenuPermissionSerializer(queryset,many=True,request=request)
|
||||
data = serializer.data
|
||||
return DetailResponse(data=data)
|
||||
|
||||
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
|
||||
def set_role_premission(self,request,pk):
|
||||
"""
|
||||
对角色的菜单和按钮及按钮范围授权:
|
||||
:param request:
|
||||
:param pk: role
|
||||
:return:
|
||||
"""
|
||||
body = request.data
|
||||
RoleMenuPermission.objects.filter(role=pk).delete()
|
||||
RoleMenuButtonPermission.objects.filter(role=pk).delete()
|
||||
for menu in body:
|
||||
if menu.get('isCheck'):
|
||||
menu_parent = Menu.objects.filter(id=menu.get('id')).values('parent').first()
|
||||
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu_parent.get('parent'))
|
||||
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
|
||||
for btn in menu.get('btns'):
|
||||
if btn.get('isCheck'):
|
||||
data_range = btn.get('data_range',0) or 0
|
||||
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),data_range=data_range)
|
||||
instance.dept.set(btn.get('dept',[]))
|
||||
for col in menu.get('columns'):
|
||||
FieldPermission.objects.update_or_create(role_id=pk,field_id=col.get('id'),is_query=col.get('is_query'),is_create=col.get('is_create'),is_update=col.get('is_update'))
|
||||
return DetailResponse(msg="授权成功")
|
||||
|
||||
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def role_menu_get_button(self, request):
|
||||
"""
|
||||
当前用户角色和菜单获取可下拉选项的按钮:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
if params := request.query_params:
|
||||
if menu_id := params.get('menu', None):
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
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(btn_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_list = request.user.role.values_list('id', flat=True)
|
||||
if params := request.query_params:
|
||||
if menu_button_id := params.get('menu_button', None):
|
||||
role_queryset = RoleMenuButtonPermission.objects.filter(
|
||||
role__in=role_list, menu_button__id=menu_button_id
|
||||
).values_list('data_range', flat=True)
|
||||
data_range_list = list(set(role_queryset))
|
||||
for item in data_range_list:
|
||||
if item == 0:
|
||||
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')
|
||||
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)
|
||||
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list, menu_button=None).values(
|
||||
dept_id=F('dept__id'),
|
||||
name=F('dept__name'),
|
||||
parent=F('dept__parent')
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def menu_to_button(self, request):
|
||||
"""
|
||||
根据所选择菜单获取已配置的按钮/接口权限:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
params = request.query_params
|
||||
menu_id = params.get('menu', None)
|
||||
if menu_id is None:
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
queryset = RoleMenuButtonPermission.objects.filter(menu_button__menu=menu_id).values(
|
||||
'id',
|
||||
'data_range',
|
||||
'menu_button',
|
||||
'menu_button__name',
|
||||
'menu_button__value'
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
else:
|
||||
if params:
|
||||
|
||||
role_id = params.get('role', None)
|
||||
if role_id is None:
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
queryset = RoleMenuButtonPermission.objects.filter(role=role_id, menu_button__menu=menu_id).values(
|
||||
'id',
|
||||
'data_range',
|
||||
'menu_button',
|
||||
'menu_button__name',
|
||||
'menu_button__value'
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def role_to_menu(self, request):
|
||||
"""
|
||||
获取角色对应的按钮权限
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
params = request.query_params
|
||||
role_id = params.get('role', None)
|
||||
if role_id is None:
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
queryset = RoleMenuPermission.objects.filter(role_id=role_id).values_list('menu_id', flat=True).distinct()
|
||||
|
||||
return DetailResponse(data=queryset)
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db.models import F
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Columns
|
||||
from dvadmin.system.models import FieldPermission, MenuField
|
||||
from dvadmin.utils.json_response import DetailResponse
|
||||
from dvadmin.utils.models import get_custom_app_models
|
||||
|
||||
@@ -14,25 +15,24 @@ class FieldPermissionMixin:
|
||||
获取字段权限
|
||||
"""
|
||||
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
|
||||
for model in get_custom_app_models():
|
||||
if model['object'] is self.serializer_class.Meta.model:
|
||||
finded = True
|
||||
break
|
||||
if finded:
|
||||
break
|
||||
if finded is False:
|
||||
return []
|
||||
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')
|
||||
data = MenuField.objects.filter( model=model['model']).values('field_name')
|
||||
for item in data:
|
||||
item['is_create'] = True
|
||||
item['is_query'] = True
|
||||
item['is_update'] = True
|
||||
else:
|
||||
data= Columns.objects.filter(
|
||||
app=model['app'], model=model['model'],role__in=roles
|
||||
).values('field_name', 'is_create', 'is_query', 'is_update')
|
||||
roles = request.user.role.values_list('id', flat=True)
|
||||
data= FieldPermission.objects.filter(
|
||||
field__model=model['model'],role__in=roles
|
||||
).values( 'is_create', 'is_query', 'is_update',field_name=F('field__field_name'))
|
||||
return DetailResponse(data=data)
|
||||
@@ -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, RoleApiPermission
|
||||
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission
|
||||
|
||||
|
||||
def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
|
||||
@@ -116,12 +116,12 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
||||
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_permission_list=RoleMenuButtonPermission.objects.filter(
|
||||
role__in=role_id_list,
|
||||
role__status=1,
|
||||
api=re_api,
|
||||
method=method).values(
|
||||
'data_range',
|
||||
menu_button__api=re_api,
|
||||
menu_button__method=method).values(
|
||||
'data_range'
|
||||
)
|
||||
dataScope_list = [] # 权限范围列表
|
||||
for ele in role_permission_list:
|
||||
|
||||
@@ -13,6 +13,7 @@ from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
from application import settings
|
||||
|
||||
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
||||
|
||||
|
||||
@@ -71,10 +72,13 @@ class CoreModel(models.Model):
|
||||
id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
|
||||
description = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
|
||||
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
|
||||
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False)
|
||||
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL,
|
||||
db_constraint=False)
|
||||
modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人")
|
||||
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True, verbose_name="数据归属部门")
|
||||
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
|
||||
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True,
|
||||
verbose_name="数据归属部门")
|
||||
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间",
|
||||
verbose_name="修改时间")
|
||||
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
|
||||
verbose_name="创建时间")
|
||||
|
||||
@@ -136,10 +140,23 @@ def get_model_from_app(app_name):
|
||||
|
||||
|
||||
def get_custom_app_models(app_name=None):
|
||||
"""获取所有项目写的app里的models"""
|
||||
"""
|
||||
获取所有项目下的app里的models
|
||||
"""
|
||||
if app_name:
|
||||
return get_model_from_app(app_name)
|
||||
all_apps = apps.get_app_configs()
|
||||
res = []
|
||||
for app in settings.CUSTOM_APPS:
|
||||
res.append(get_model_from_app(app))
|
||||
for app in all_apps:
|
||||
if app.name.startswith('django'):
|
||||
continue
|
||||
if app.name in settings.COLUMN_EXCLUDE_APPS:
|
||||
continue
|
||||
try:
|
||||
all_models = get_model_from_app(app.name)
|
||||
if all_models:
|
||||
for model in all_models:
|
||||
res.append(model)
|
||||
except Exception as e:
|
||||
pass
|
||||
return res
|
||||
|
||||
@@ -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, RoleApiPermission
|
||||
from dvadmin.system.models import ApiWhiteList, RoleMenuButtonPermission
|
||||
|
||||
|
||||
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('method',api=F('url'))
|
||||
api_white_list = ApiWhiteList.objects.values(permission__api=F('url'), permission__method=F('method'))
|
||||
api_white_list = [
|
||||
str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('method')) + '$' for item in api_white_list if item.get('api')]
|
||||
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')]
|
||||
# ********#
|
||||
if not hasattr(request.user, "role"):
|
||||
return False
|
||||
role_id_list = request.user.role.values_list('id',flat=True)
|
||||
userApiList = RoleApiPermission.objects.filter(role__in=role_id_list).values('api','method') # 获取当前用户的角色拥有的所有接口
|
||||
userApiList = RoleMenuButtonPermission.objects.filter(role__in=role_id_list).values(permission__api=F('menu_button__api'), permission__method=F('menu_button__method')) # 获取当前用户的角色拥有的所有接口
|
||||
ApiList = [
|
||||
str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('method')) + '$' for item in userApiList if item.get('api')]
|
||||
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')]
|
||||
new_api_ist = api_white_list + ApiList
|
||||
new_api = api + ":" + str(method)
|
||||
for item in new_api_ist:
|
||||
|
||||
@@ -17,7 +17,7 @@ from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSeria
|
||||
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 dvadmin.system.models import FieldPermission, MenuField
|
||||
from django_restql.mixins import QueryArgumentsMixin
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
serializer_class = self.get_serializer_class()
|
||||
kwargs.setdefault('context', self.get_serializer_context())
|
||||
# 全部以可见字段为准
|
||||
can_see = self.get_column_permission(serializer_class)
|
||||
can_see = self.get_menu_field(serializer_class)
|
||||
# 排除掉序列化器级的字段
|
||||
# sub_set = set(serializer_class._declared_fields.keys()) - set(can_see)
|
||||
# for field in sub_set:
|
||||
@@ -79,21 +79,17 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
else:
|
||||
return serializer_class(*args, **kwargs)
|
||||
|
||||
def get_column_permission(self, serializer_class):
|
||||
"""获取列权限"""
|
||||
def get_menu_field(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:
|
||||
for model in get_custom_app_models():
|
||||
if model['object'] is serializer_class.Meta.model:
|
||||
finded = True
|
||||
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')
|
||||
return MenuField.objects.filter(model=model['model']
|
||||
).values('field_name', 'title')
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data, request=request)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Django==4.2.6
|
||||
Django==4.2.7
|
||||
django-comment-migrate==0.1.7
|
||||
django-cors-headers==4.3.0
|
||||
django-filter==23.3
|
||||
|
||||
@@ -28,6 +28,6 @@ server {
|
||||
proxy_send_timeout 600s;
|
||||
real_ip_header X-Forwarded-For;
|
||||
rewrite ^/api/(.*)$ /$1 break; #重写
|
||||
proxy_pass http://177.8.0.12:8000/; # 设置代理服务器的协议和地址
|
||||
proxy_pass http://177.10.0.12:8000/; # 设置代理服务器的协议和地址
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-web:16.19-alpine
|
||||
WORKDIR /web/
|
||||
COPY web/. .
|
||||
RUN yarn install
|
||||
RUN yarn install --registry=https://registry.npm.taobao.org
|
||||
RUN yarn build
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
ENV = 'development'
|
||||
|
||||
# 本地环境接口地址
|
||||
VITE_API_URL = 'http://127.0.0.1:8000'
|
||||
VITE_API_URL = 'http://127.0.0.1:8001'
|
||||
|
||||
# 是否启用按钮权限
|
||||
VITE_PM_ENABLED = true
|
||||
|
||||
168
web/README.en.md
Normal file
168
web/README.en.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Django-Vue3-Admin
|
||||
|
||||
[](https://gitee.com/huge-dream/django-vue3-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/huge-dream/django-vue3-admin)
|
||||
|
||||
[preview](https://demo.dvadmin.com) | [Official website document](https://www.django-vue-admin.com) | [qq group](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [community](https://bbs.django-vue-admin.com) | [plugins market](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
|
||||
|
||||
💡 **「About」**
|
||||
|
||||
We are a group of young people who love Code. In this hot era, we hope to calm down and bring some of our colors and colors through code.
|
||||
|
||||
Because of love, so embrace the future
|
||||
|
||||
## framework introduction
|
||||
|
||||
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) Is a set of all open source rapid development platform, no reservation for individuals and enterprises free use.
|
||||
|
||||
* 🧑🤝🧑Front-end adoption Vue3+TS+pinia+fastcrud。
|
||||
* 👭The backend uses the Python language Django framework as well as the powerful[Django REST Framework](https://pypi.org/project/djangorestframework)。
|
||||
* 👫Permission authentication use[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),Supports the multi-terminal authentication system.
|
||||
* 👬Support loading dynamic permission menu, multi - way easy permission control.
|
||||
* 💏 Special thanks: [vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/).
|
||||
* 💡 💏 Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
|
||||
|
||||
## Online experience
|
||||
|
||||
👩👧👦👩👧👦 demo address:[https://demo.dvadmin.com](https://demo.dvadmin.com)
|
||||
|
||||
* demo account:superadmin
|
||||
|
||||
* demo password:admin123456
|
||||
|
||||
👩👦👦docs:[https://django-vue-admin.com](https://django-vue-admin.com)
|
||||
|
||||
## communication
|
||||
|
||||
* Communication community:[click here](https://bbs.django-vue-admin.com)👩👦👦
|
||||
|
||||
* plugins market:[click here](https://bbs.django-vue-admin.com/plugMarket.html)👩👦👦
|
||||
|
||||
## source code url:
|
||||
|
||||
gitee(Main push):[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
github:no data
|
||||
|
||||
## core function
|
||||
|
||||
1. 👨⚕️ Menu management: Configure the system menu, operation permissions, button permissions, back-end interface permissions, etc.
|
||||
2. 🧑⚕️ Department management: Configure the system organization (company, department, role).
|
||||
3. 👩⚕️ Role management: role menu permission allocation, data permission allocation, set roles according to the department for data range permission division.
|
||||
4. 🧑🎓 Rights Specifies the rights of the authorization role.
|
||||
5. 👨🎓 User management: The user is the system operator, this function mainly completes the system user configuration.
|
||||
6. 👬 Interface whitelist: specifies the interface that does not need permission verification.
|
||||
7. 🧑🔧 Dictionary management: Maintenance of some fixed data frequently used in the system.
|
||||
8. 🧑🔧 Regional management: to manage provinces, cities, counties and regions.
|
||||
9. 📁 Attachment management: Unified management of all files and pictures on the platform.
|
||||
10. 🗓 ️operation logs: log and query the system normal operation; Log and query system exception information.
|
||||
11.🔌 [plugins market] (<https://bbs.django-vue-admin.com/plugMarket.html>) : based on the Django framework - Vue - Admin application and plug-in development.
|
||||
|
||||
## plugins market 🔌
|
||||
|
||||
* Celery Asynchronous task:[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
|
||||
* Upgrade center backend:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
|
||||
* Upgrade center front:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
|
||||
|
||||
## before start project you need:
|
||||
|
||||
~~~
|
||||
Python >= 3.8.0
|
||||
nodejs >= 14.0
|
||||
Mysql >= 5.7.0 (Optional. The default database is sqlite3. 8.0 is recommended)
|
||||
Redis(Optional, the latest edition)
|
||||
~~~
|
||||
|
||||
## frontend♝
|
||||
|
||||
```bash
|
||||
# clone code
|
||||
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||
|
||||
# enter code dir
|
||||
cd web
|
||||
|
||||
# install dependence
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# Start service
|
||||
npm run dev
|
||||
# Visit http://localhost:8080 in your browser
|
||||
# Parameters such as boot port can be configured in the #.env.development file
|
||||
# Build the production environment
|
||||
# npm run build
|
||||
```
|
||||
|
||||
## backend💈
|
||||
|
||||
~~~bash
|
||||
1. enter code dir cd backend
|
||||
2. copy ./conf/env.example.py to ./conf dir,rename as env.py
|
||||
3. in env.py configure database information
|
||||
mysql database recommended version: 8.0
|
||||
mysql database character set: utf8mb4
|
||||
4. install pip dependence
|
||||
pip3 install -r requirements.txt
|
||||
5. Execute the migration command:
|
||||
python3 manage.py makemigrations
|
||||
python3 manage.py migrate
|
||||
6. Initialization data
|
||||
python3 manage.py init
|
||||
7. Initialize provincial, municipal and county data:
|
||||
python3 manage.py init_area
|
||||
8. start backend
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
or daphne :
|
||||
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
||||
~~~
|
||||
|
||||
### visit backend swagger
|
||||
|
||||
* visit url:[http://localhost:8080](http://localhost:8080) (The default address is this one. If you want to change it, follow the configuration file)
|
||||
* account:`superadmin` password:`admin123456`
|
||||
|
||||
### docker-compose
|
||||
|
||||
~~~shell
|
||||
docker-compose up -d
|
||||
# Initialize backend data (first execution only)
|
||||
docker exec -ti dvadmin-django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init_area
|
||||
python manage.py init
|
||||
exit
|
||||
|
||||
frontend url:http://127.0.0.1:8080
|
||||
backend url:http://127.0.0.1:8080/api
|
||||
# Change 127.0.0.1 to your own public ip address on the server
|
||||
account:`superadmin` password:`admin123456`
|
||||
|
||||
# docker-compose stop
|
||||
docker-compose down
|
||||
# docker-compose restart
|
||||
docker-compose restart
|
||||
# docker-compose on start build
|
||||
docker-compose up -d --build
|
||||
~~~
|
||||
|
||||
## Demo screenshot✅
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

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

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
# 运行项目
|
||||
yarn dev
|
||||
|
||||
# 打包发布
|
||||
yarn build
|
||||
```
|
||||
@@ -10,23 +10,22 @@
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="django-vue3-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!"
|
||||
content="django-vue-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>django-vue3-admin</title>
|
||||
<title>django-vue-admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript">
|
||||
// let _hmt = _hmt || [];
|
||||
(function () {
|
||||
let hm = document.createElement('script');
|
||||
hm.src = 'https://hm.baidu.com/hm.js?d9c8b87d10717013641458b300c552e4';
|
||||
let s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?9ba8fc809b5584167a2fb9b31bb3970c";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "django-vue3-admin",
|
||||
"version": "1.0.0",
|
||||
"description": "django-vue3-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!",
|
||||
"version": "3.0.0",
|
||||
"description": "是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。\n django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite --force",
|
||||
@@ -10,10 +10,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@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",
|
||||
"@fast-crud/fast-crud": "^1.19.2",
|
||||
"@fast-crud/fast-extends": "^1.19.2",
|
||||
"@fast-crud/ui-element": "^1.19.2",
|
||||
"@fast-crud/ui-interface": "^1.19.2",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
@@ -62,7 +62,7 @@
|
||||
"@typescript-eslint/parser": "^5.46.0",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vue/compiler-sfc": "^3.2.45",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"prettier": "^2.8.1",
|
||||
"sass": "^1.56.2",
|
||||
|
||||
@@ -59,12 +59,6 @@ onBeforeMount(() => {
|
||||
setIntroduction.cssCdn();
|
||||
// 设置批量第三方 js
|
||||
setIntroduction.jsCdn();
|
||||
//websockt 模块
|
||||
try {
|
||||
//websocket.init(wsReceive)
|
||||
} catch (e) {
|
||||
console.log('websocket错误');
|
||||
}
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
@@ -93,6 +87,14 @@ watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
other.useTitle();
|
||||
if (!websocket.websocket) {
|
||||
//websockt 模块
|
||||
try {
|
||||
websocket.init(wsReceive)
|
||||
} catch (e) {
|
||||
console.log('websocket错误');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
|
||||
@@ -159,14 +159,11 @@ const initModeValueEcho = () => {
|
||||
// 处理 icon 类型,用于回显时,tab 高亮与初始化数据
|
||||
const initFontIconName = () => {
|
||||
let name = 'ali';
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
// 初始化数据
|
||||
|
||||
@@ -1,123 +1,131 @@
|
||||
// 定义内容
|
||||
export default {
|
||||
router: {
|
||||
home: 'home',
|
||||
system: 'system',
|
||||
systemMenu: 'systemMenu',
|
||||
systemRole: 'systemRole',
|
||||
systemUser: 'systemUser',
|
||||
systemDept: 'systemDept',
|
||||
systemDic: 'systemDic',
|
||||
limits: 'limits',
|
||||
limitsFrontEnd: 'FrontEnd',
|
||||
limitsFrontEndPage: 'FrontEndPage',
|
||||
limitsFrontEndBtn: 'FrontEndBtn',
|
||||
limitsBackEnd: 'BackEnd',
|
||||
limitsBackEndEndPage: 'BackEndEndPage',
|
||||
personal: 'personal',
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: 'signIn',
|
||||
notFound: 'notFound',
|
||||
noPower: 'noPower',
|
||||
},
|
||||
user: {
|
||||
title0: 'Component size',
|
||||
title1: 'Language switching',
|
||||
title2: 'Menu search',
|
||||
title3: 'Layout configuration',
|
||||
title4: 'news',
|
||||
title5: 'Full screen on',
|
||||
title6: 'Full screen off',
|
||||
dropdownLarge: 'large',
|
||||
dropdownDefault: 'default',
|
||||
dropdownSmall: 'small',
|
||||
dropdown1: 'home page',
|
||||
dropdown2: 'Personal Center',
|
||||
dropdown3: '404',
|
||||
dropdown4: '401',
|
||||
dropdown5: 'Log out',
|
||||
dropdown6: 'Code warehouse',
|
||||
searchPlaceholder: 'Menu search: support Chinese, routing path',
|
||||
newTitle: 'notice',
|
||||
newBtn: 'All read',
|
||||
newGo: 'Go to the notification center',
|
||||
newDesc: 'No notice',
|
||||
logOutTitle: 'Tips',
|
||||
logOutMessage: 'This operation will log out. Do you want to continue?',
|
||||
logOutConfirm: 'determine',
|
||||
logOutCancel: 'cancel',
|
||||
logOutExit: 'Exiting',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: 'refresh',
|
||||
close: 'close',
|
||||
closeOther: 'closeOther',
|
||||
closeAll: 'closeAll',
|
||||
fullscreen: 'fullscreen',
|
||||
closeFullscreen: 'closeFullscreen',
|
||||
},
|
||||
notFound: {
|
||||
foundTitle: 'Wrong address input, please re-enter the address~',
|
||||
foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
|
||||
foundBtn: 'Back to home page',
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: 'You are not authorized to operate~',
|
||||
accessMsg: 'Contact information: add QQ group discussion 665452019',
|
||||
accessBtn: 'Reauthorization',
|
||||
},
|
||||
layout: {
|
||||
configTitle: 'Layout configuration',
|
||||
oneTitle: 'Global Themes',
|
||||
twoTopTitle: 'top bar set up',
|
||||
twoMenuTitle: 'Menu set up',
|
||||
twoColumnsTitle: 'Columns set up',
|
||||
twoTopBar: 'Top bar background',
|
||||
twoTopBarColor: 'Top bar default font color',
|
||||
twoIsTopBarColorGradual: 'Top bar gradient',
|
||||
twoMenuBar: 'Menu background',
|
||||
twoMenuBarColor: 'Menu default font color',
|
||||
twoIsMenuBarColorGradual: 'Menu gradient',
|
||||
twoColumnsMenuBar: 'Column menu background',
|
||||
twoColumnsMenuBarColor: 'Default font color bar menu',
|
||||
twoIsColumnsMenuBarColorGradual: 'Column gradient',
|
||||
threeTitle: 'Interface settings',
|
||||
threeIsCollapse: 'Menu horizontal collapse',
|
||||
threeIsUniqueOpened: 'Menu accordion',
|
||||
threeIsFixedHeader: 'Fixed header',
|
||||
threeIsClassicSplitMenu: 'Classic layout split menu',
|
||||
threeIsLockScreen: 'Open the lock screen',
|
||||
threeLockScreenTime: 'screen locking(s/s)',
|
||||
fourTitle: 'Interface display',
|
||||
fourIsShowLogo: 'Sidebar logo',
|
||||
fourIsBreadcrumb: 'Open breadcrumb',
|
||||
fourIsBreadcrumbIcon: 'Open breadcrumb icon',
|
||||
fourIsTagsview: 'Open tagsview',
|
||||
fourIsTagsviewIcon: 'Open tagsview Icon',
|
||||
fourIsCacheTagsView: 'Enable tagsview cache',
|
||||
fourIsSortableTagsView: 'Enable tagsview drag',
|
||||
fourIsShareTagsView: 'Enable tagsview sharing',
|
||||
fourIsFooter: 'Open footer',
|
||||
fourIsGrayscale: 'Grey model',
|
||||
fourIsInvert: 'Color weak mode',
|
||||
fourIsDark: 'Dark Mode',
|
||||
fourIsWartermark: 'Turn on watermark',
|
||||
fourWartermarkText: 'Watermark copy',
|
||||
fiveTitle: 'Other settings',
|
||||
fiveTagsStyle: 'Tagsview style',
|
||||
fiveAnimation: 'page animation',
|
||||
fiveColumnsAsideStyle: 'Column style',
|
||||
fiveColumnsAsideLayout: 'Column layout',
|
||||
sixTitle: 'Layout switch',
|
||||
sixDefaults: 'One',
|
||||
sixClassic: 'Two',
|
||||
sixTransverse: 'Three',
|
||||
sixColumns: 'Four',
|
||||
tipText: 'Click the button below to copy the layout configuration to `/src/stores/themeConfig.ts` It has been modified in.',
|
||||
copyText: 'replication configuration',
|
||||
resetText: 'restore default',
|
||||
copyTextSuccess: 'Copy succeeded!',
|
||||
copyTextError: 'Copy failed!',
|
||||
},
|
||||
router: {
|
||||
home: 'home',
|
||||
system: 'system',
|
||||
systemMenu: 'systemMenu',
|
||||
systemRole: 'systemRole',
|
||||
systemUser: 'systemUser',
|
||||
systemDept: 'systemDept',
|
||||
systemDic: 'systemDic',
|
||||
limits: 'limits',
|
||||
limitsFrontEnd: 'FrontEnd',
|
||||
limitsFrontEndPage: 'FrontEndPage',
|
||||
limitsFrontEndBtn: 'FrontEndBtn',
|
||||
limitsBackEnd: 'BackEnd',
|
||||
limitsBackEndEndPage: 'BackEndEndPage',
|
||||
personal: 'personal',
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: 'signIn',
|
||||
notFound: 'notFound',
|
||||
noPower: 'noPower',
|
||||
},
|
||||
user: {
|
||||
title0: 'Component size',
|
||||
title1: 'Language switching',
|
||||
title2: 'Menu search',
|
||||
title3: 'Layout configuration',
|
||||
title4: 'news',
|
||||
title5: 'Full screen on',
|
||||
title6: 'Full screen off',
|
||||
dropdownLarge: 'large',
|
||||
dropdownDefault: 'default',
|
||||
dropdownSmall: 'small',
|
||||
dropdown1: 'home page',
|
||||
dropdown2: 'Personal Center',
|
||||
dropdown3: '404',
|
||||
dropdown4: '401',
|
||||
dropdown5: 'Log out',
|
||||
dropdown6: 'Code warehouse',
|
||||
searchPlaceholder: 'Menu search: support Chinese, routing path',
|
||||
newTitle: 'notice',
|
||||
newBtn: 'All read',
|
||||
newGo: 'Go to the notification center',
|
||||
newDesc: 'No notice',
|
||||
logOutTitle: 'Tips',
|
||||
logOutMessage: 'This operation will log out. Do you want to continue?',
|
||||
logOutConfirm: 'determine',
|
||||
logOutCancel: 'cancel',
|
||||
logOutExit: 'Exiting',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: 'refresh',
|
||||
close: 'close',
|
||||
closeOther: 'closeOther',
|
||||
closeAll: 'closeAll',
|
||||
fullscreen: 'fullscreen',
|
||||
closeFullscreen: 'closeFullscreen',
|
||||
},
|
||||
notFound: {
|
||||
foundTitle: 'Wrong address input, please re-enter the address~',
|
||||
foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
|
||||
foundBtn: 'Back to home page',
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: 'You are not authorized to operate~',
|
||||
accessMsg: 'Contact information: add QQ group discussion 665452019',
|
||||
accessBtn: 'Reauthorization',
|
||||
},
|
||||
layout: {
|
||||
configTitle: 'Layout configuration',
|
||||
oneTitle: 'Global Themes',
|
||||
twoTopTitle: 'top bar set up',
|
||||
twoMenuTitle: 'Menu set up',
|
||||
twoColumnsTitle: 'Columns set up',
|
||||
twoTopBar: 'Top bar background',
|
||||
twoTopBarColor: 'Top bar default font color',
|
||||
twoIsTopBarColorGradual: 'Top bar gradient',
|
||||
twoMenuBar: 'Menu background',
|
||||
twoMenuBarColor: 'Menu default font color',
|
||||
twoIsMenuBarColorGradual: 'Menu gradient',
|
||||
twoColumnsMenuBar: 'Column menu background',
|
||||
twoColumnsMenuBarColor: 'Default font color bar menu',
|
||||
twoIsColumnsMenuBarColorGradual: 'Column gradient',
|
||||
threeTitle: 'Interface settings',
|
||||
threeIsCollapse: 'Menu horizontal collapse',
|
||||
threeIsUniqueOpened: 'Menu accordion',
|
||||
threeIsFixedHeader: 'Fixed header',
|
||||
threeIsClassicSplitMenu: 'Classic layout split menu',
|
||||
threeIsLockScreen: 'Open the lock screen',
|
||||
threeLockScreenTime: 'screen locking(s/s)',
|
||||
fourTitle: 'Interface display',
|
||||
fourIsShowLogo: 'Sidebar logo',
|
||||
fourIsBreadcrumb: 'Open breadcrumb',
|
||||
fourIsBreadcrumbIcon: 'Open breadcrumb icon',
|
||||
fourIsTagsview: 'Open tagsview',
|
||||
fourIsTagsviewIcon: 'Open tagsview Icon',
|
||||
fourIsCacheTagsView: 'Enable tagsview cache',
|
||||
fourIsSortableTagsView: 'Enable tagsview drag',
|
||||
fourIsShareTagsView: 'Enable tagsview sharing',
|
||||
fourIsFooter: 'Open footer',
|
||||
fourIsGrayscale: 'Grey model',
|
||||
fourIsInvert: 'Color weak mode',
|
||||
fourIsDark: 'Dark Mode',
|
||||
fourIsWartermark: 'Turn on watermark',
|
||||
fourWartermarkText: 'Watermark copy',
|
||||
fiveTitle: 'Other settings',
|
||||
fiveTagsStyle: 'Tagsview style',
|
||||
fiveAnimation: 'page animation',
|
||||
fiveColumnsAsideStyle: 'Column style',
|
||||
fiveColumnsAsideLayout: 'Column layout',
|
||||
sixTitle: 'Layout switch',
|
||||
sixDefaults: 'One',
|
||||
sixClassic: 'Two',
|
||||
sixTransverse: 'Three',
|
||||
sixColumns: 'Four',
|
||||
tipText: 'Click the button below to copy the layout configuration to `/src/stores/themeConfig.ts` It has been modified in.',
|
||||
copyText: 'replication configuration',
|
||||
resetText: 'restore default',
|
||||
copyTextSuccess: 'Copy succeeded!',
|
||||
copyTextError: 'Copy failed!',
|
||||
},
|
||||
upgrade: {
|
||||
title: 'New version upgrade',
|
||||
msg: 'It\'s a new version. Update it now!Don\'t worry, update quickly oh!',
|
||||
desc: 'Tip: The update restores the default configuration',
|
||||
btnOne: 'Cruel refusal',
|
||||
btnTwo: 'Update now',
|
||||
btnTwoLoading: 'updating',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,134 +1,146 @@
|
||||
// 定义内容
|
||||
export default {
|
||||
router: {
|
||||
home: '首页',
|
||||
system: '系统管理',
|
||||
config: '常规配置',
|
||||
log: '日志管理',
|
||||
/* 常规配置 */
|
||||
configSystem: '系统配置',
|
||||
configDict: '字典管理',
|
||||
configArea: '地区管理',
|
||||
configFile: '附件管理',
|
||||
/* 系统管理 */
|
||||
systemMenu: '菜单管理',
|
||||
systemRole: '角色管理',
|
||||
systemUser: '用户管理',
|
||||
systemDept: '部门管理',
|
||||
/* 日志管理 */
|
||||
loginLog: '登录日志',
|
||||
operationLog: '操作日志',
|
||||
systemApiWhiteList: '接口白名单',
|
||||
limits: '权限管理',
|
||||
limitsFrontEnd: '前端控制',
|
||||
limitsFrontEndPage: '页面权限',
|
||||
limitsFrontEndBtn: '按钮权限',
|
||||
limitsBackEnd: '后端控制',
|
||||
limitsBackEndEndPage: '页面权限',
|
||||
personal: '个人中心',
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: '登录',
|
||||
notFound: '找不到此页面',
|
||||
noPower: '没有权限',
|
||||
},
|
||||
user: {
|
||||
title0: '组件大小',
|
||||
title1: '语言切换',
|
||||
title2: '菜单搜索',
|
||||
title3: '布局配置',
|
||||
title4: '消息',
|
||||
title5: '开全屏',
|
||||
title6: '关全屏',
|
||||
dropdownLarge: '大型',
|
||||
dropdownDefault: '默认',
|
||||
dropdownSmall: '小型',
|
||||
dropdown1: '首页',
|
||||
dropdown2: '个人中心',
|
||||
dropdown3: '404',
|
||||
dropdown4: '401',
|
||||
dropdown5: '退出登录',
|
||||
dropdown6: '代码仓库',
|
||||
searchPlaceholder: '菜单搜索:支持中文、路由路径',
|
||||
newTitle: '通知',
|
||||
newBtn: '全部已读',
|
||||
newGo: '前往通知中心',
|
||||
newDesc: '暂无通知',
|
||||
logOutTitle: '提示',
|
||||
logOutMessage: '此操作将退出登录, 是否继续?',
|
||||
logOutConfirm: '确定',
|
||||
logOutCancel: '取消',
|
||||
logOutExit: '退出中',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: '刷新',
|
||||
close: '关闭',
|
||||
closeOther: '关闭其它',
|
||||
closeAll: '全部关闭',
|
||||
fullscreen: '当前页全屏',
|
||||
closeFullscreen: '关闭全屏',
|
||||
},
|
||||
notFound: {
|
||||
foundTitle: '地址输入错误,请重新输入地址~',
|
||||
foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
|
||||
foundBtn: '返回首页',
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: '您未被授权,没有操作权限~',
|
||||
accessMsg: '联系方式:加QQ群探讨 665452019',
|
||||
accessBtn: '重新授权',
|
||||
},
|
||||
layout: {
|
||||
configTitle: '布局配置',
|
||||
oneTitle: '全局主题',
|
||||
twoTopTitle: '顶栏设置',
|
||||
twoMenuTitle: '菜单设置',
|
||||
twoColumnsTitle: '分栏设置',
|
||||
twoTopBar: '顶栏背景',
|
||||
twoTopBarColor: '顶栏默认字体颜色',
|
||||
twoIsTopBarColorGradual: '顶栏背景渐变',
|
||||
twoMenuBar: '菜单背景',
|
||||
twoMenuBarColor: '菜单默认字体颜色',
|
||||
twoIsMenuBarColorGradual: '菜单背景渐变',
|
||||
twoColumnsMenuBar: '分栏菜单背景',
|
||||
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
|
||||
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
|
||||
threeTitle: '界面设置',
|
||||
threeIsCollapse: '菜单水平折叠',
|
||||
threeIsUniqueOpened: '菜单手风琴',
|
||||
threeIsFixedHeader: '固定 Header',
|
||||
threeIsClassicSplitMenu: '经典布局分割菜单',
|
||||
threeIsLockScreen: '开启锁屏',
|
||||
threeLockScreenTime: '自动锁屏(s/秒)',
|
||||
fourTitle: '界面显示',
|
||||
fourIsShowLogo: '侧边栏 Logo',
|
||||
fourIsBreadcrumb: '开启 Breadcrumb',
|
||||
fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
|
||||
fourIsTagsview: '开启 Tagsview',
|
||||
fourIsTagsviewIcon: '开启 Tagsview 图标',
|
||||
fourIsCacheTagsView: '开启 TagsView 缓存',
|
||||
fourIsSortableTagsView: '开启 TagsView 拖拽',
|
||||
fourIsShareTagsView: '开启 TagsView 共用',
|
||||
fourIsFooter: '开启 Footer',
|
||||
fourIsGrayscale: '灰色模式',
|
||||
fourIsInvert: '色弱模式',
|
||||
fourIsDark: '深色模式',
|
||||
fourIsWartermark: '开启水印',
|
||||
fourWartermarkText: '水印文案',
|
||||
fiveTitle: '其它设置',
|
||||
fiveTagsStyle: 'Tagsview 风格',
|
||||
fiveAnimation: '主页面切换动画',
|
||||
fiveColumnsAsideStyle: '分栏高亮风格',
|
||||
fiveColumnsAsideLayout: '分栏布局风格',
|
||||
sixTitle: '布局切换',
|
||||
sixDefaults: '默认',
|
||||
sixClassic: '经典',
|
||||
sixTransverse: '横向',
|
||||
sixColumns: '分栏',
|
||||
tipText: '点击下方按钮,复制布局配置去 `src/stores/themeConfig.ts` 中修改。',
|
||||
copyText: '一键复制配置',
|
||||
resetText: '一键恢复默认',
|
||||
copyTextSuccess: '复制成功!',
|
||||
copyTextError: '复制失败!',
|
||||
},
|
||||
router: {
|
||||
home: '首页',
|
||||
system: '系统管理',
|
||||
config: '常规配置',
|
||||
log: '日志管理',
|
||||
/* 常规配置 */
|
||||
configSystem: '系统配置',
|
||||
configDict: '字典管理',
|
||||
configArea: '地区管理',
|
||||
configFile: '附件管理',
|
||||
/* 系统管理 */
|
||||
systemMenu: '菜单管理',
|
||||
systemRole: '角色管理',
|
||||
systemUser: '用户管理',
|
||||
systemDept: '部门管理',
|
||||
/* 日志管理 */
|
||||
loginLog: '登录日志',
|
||||
operationLog: '操作日志',
|
||||
systemApiWhiteList: '接口白名单',
|
||||
limits: '权限管理',
|
||||
limitsFrontEnd: '前端控制',
|
||||
limitsFrontEndPage: '页面权限',
|
||||
limitsFrontEndBtn: '按钮权限',
|
||||
limitsBackEnd: '后端控制',
|
||||
limitsBackEndEndPage: '页面权限',
|
||||
personal: '个人中心',
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: '登录',
|
||||
notFound: '找不到此页面',
|
||||
noPower: '没有权限',
|
||||
},
|
||||
user: {
|
||||
title0: '组件大小',
|
||||
title1: '语言切换',
|
||||
title2: '菜单搜索',
|
||||
title3: '布局配置',
|
||||
title4: '消息',
|
||||
title5: '开全屏',
|
||||
title6: '关全屏',
|
||||
retry: '重试上线',
|
||||
onlinePrompt: '当前离线状态,是否重试上线?',
|
||||
dropdownLarge: '大型',
|
||||
dropdownDefault: '默认',
|
||||
dropdownSmall: '小型',
|
||||
dropdown1: '首页',
|
||||
dropdown2: '个人中心',
|
||||
dropdown3: '404',
|
||||
dropdown4: '401',
|
||||
dropdown5: '退出登录',
|
||||
dropdown6: '代码仓库',
|
||||
searchPlaceholder: '菜单搜索:支持中文、路由路径',
|
||||
newTitle: '通知',
|
||||
newBtn: '全部已读',
|
||||
newGo: '前往通知中心',
|
||||
newDesc: '暂无通知',
|
||||
logOutTitle: '提示',
|
||||
logOutMessage: '此操作将退出登录, 是否继续?',
|
||||
logOutConfirm: '确定',
|
||||
logOutCancel: '取消',
|
||||
logOutExit: '退出中',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: '刷新',
|
||||
close: '关闭',
|
||||
closeOther: '关闭其它',
|
||||
closeAll: '全部关闭',
|
||||
fullscreen: '当前页全屏',
|
||||
closeFullscreen: '关闭全屏',
|
||||
},
|
||||
notFound: {
|
||||
foundTitle: '地址输入错误,请重新输入地址~',
|
||||
foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
|
||||
foundBtn: '返回首页',
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: '您未被授权,没有操作权限~',
|
||||
accessMsg: '请联系管理员',
|
||||
accessBtn: '重新授权',
|
||||
},
|
||||
layout: {
|
||||
configTitle: '布局配置',
|
||||
oneTitle: '全局主题',
|
||||
twoTopTitle: '顶栏设置',
|
||||
twoMenuTitle: '菜单设置',
|
||||
twoColumnsTitle: '分栏设置',
|
||||
twoTopBar: '顶栏背景',
|
||||
twoTopBarColor: '顶栏默认字体颜色',
|
||||
twoIsTopBarColorGradual: '顶栏背景渐变',
|
||||
twoMenuBar: '菜单背景',
|
||||
twoMenuBarColor: '菜单默认字体颜色',
|
||||
twoMenuBarActiveColor: '菜单高亮背景色',
|
||||
twoIsMenuBarColorGradual: '菜单背景渐变',
|
||||
twoColumnsMenuBar: '分栏菜单背景',
|
||||
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
|
||||
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
|
||||
twoIsColumnsMenuHoverPreload: '分栏菜单滑鼠悬停预加载',
|
||||
threeTitle: '界面设置',
|
||||
threeIsCollapse: '菜单水平折叠',
|
||||
threeIsUniqueOpened: '菜单手风琴',
|
||||
threeIsFixedHeader: '固定 Header',
|
||||
threeIsClassicSplitMenu: '经典布局分割菜单',
|
||||
threeIsLockScreen: '开启锁屏',
|
||||
threeLockScreenTime: '自动锁屏(s/秒)',
|
||||
fourTitle: '界面显示',
|
||||
fourIsShowLogo: '侧边栏 Logo',
|
||||
fourIsBreadcrumb: '开启 Breadcrumb',
|
||||
fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
|
||||
fourIsTagsview: '开启 Tagsview',
|
||||
fourIsTagsviewIcon: '开启 Tagsview 图标',
|
||||
fourIsCacheTagsView: '开启 TagsView 缓存',
|
||||
fourIsSortableTagsView: '开启 TagsView 拖拽',
|
||||
fourIsShareTagsView: '开启 TagsView 共用',
|
||||
fourIsFooter: '开启 Footer',
|
||||
fourIsGrayscale: '灰色模式',
|
||||
fourIsInvert: '色弱模式',
|
||||
fourIsDark: '深色模式',
|
||||
fourIsWartermark: '开启水印',
|
||||
fourWartermarkText: '水印文案',
|
||||
fiveTitle: '其它设置',
|
||||
fiveTagsStyle: 'Tagsview 风格',
|
||||
fiveAnimation: '主页面切换动画',
|
||||
fiveColumnsAsideStyle: '分栏高亮风格',
|
||||
fiveColumnsAsideLayout: '分栏布局风格',
|
||||
sixTitle: '布局切换',
|
||||
sixDefaults: '默认',
|
||||
sixClassic: '经典',
|
||||
sixTransverse: '横向',
|
||||
sixColumns: '分栏',
|
||||
tipText: '点击下方按钮,复制布局配置去 `src/stores/themeConfig.ts` 中修改。',
|
||||
copyText: '一键复制配置',
|
||||
resetText: '一键恢复默认',
|
||||
copyTextSuccess: '复制成功!',
|
||||
copyTextError: '复制失败!',
|
||||
},
|
||||
upgrade: {
|
||||
title: '新版本升级',
|
||||
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
|
||||
desc: '提示:更新会还原默认配寘',
|
||||
btnOne: '残忍拒绝',
|
||||
btnTwo: '马上更新',
|
||||
btnTwoLoading: '更新中',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -123,7 +123,7 @@ export default {
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: '您未被授權,沒有操作許可權~',
|
||||
accessMsg: '聯繫方式:加QQ群探討665452019',
|
||||
accessMsg: '請聯系管理員',
|
||||
accessBtn: '重新授權',
|
||||
},
|
||||
layout: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 定义内容
|
||||
export default {
|
||||
label: {
|
||||
one1: '用户名登录',
|
||||
one1: '账号密码登录',
|
||||
two2: '手机号登录',
|
||||
},
|
||||
link: {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<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>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div class="layout-footer pb15">
|
||||
<div class="layout-footer pb5 pt2">
|
||||
<div class="layout-footer-warp">
|
||||
<div>❤️ Powered by Django-Vue3-Admin ❤️</div>
|
||||
<div class="mt5">Copyright DVAdmin团队</div>
|
||||
<div>❤️ Powered by Django-Vue3-Admin Copyright © DVAdmin团队 ❤️</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -57,9 +57,33 @@
|
||||
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
|
||||
></i>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="!isSocketOpen">
|
||||
<el-popconfirm
|
||||
width="250"
|
||||
ref="onlinePopoverRef"
|
||||
:confirm-button-text="$t('message.user.retry')"
|
||||
:icon="InfoFilled"
|
||||
trigger="hover"
|
||||
icon-color="#626AEF"
|
||||
:title="$t('message.user.onlinePrompt')"
|
||||
@confirm="onlineConfirmEvent"
|
||||
>
|
||||
<template #reference>
|
||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
</el-badge>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</span>
|
||||
</div>
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||
<span class="layout-navbars-breadcrumb-user-link">
|
||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
<span v-if="isSocketOpen">
|
||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
</el-badge>
|
||||
</span>
|
||||
{{ userInfos.username === '' ? 'common' : userInfos.username }}
|
||||
<el-icon class="el-icon--right">
|
||||
<ele-ArrowDown />
|
||||
@@ -79,7 +103,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutBreadcrumbUser">
|
||||
import { defineAsyncComponent, ref, computed, reactive, onMounted } from 'vue';
|
||||
import { defineAsyncComponent, ref, computed, reactive, onMounted, unref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import screenfull from 'screenfull';
|
||||
@@ -91,6 +115,8 @@ import other from '/@/utils/other';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import { Session, Local } from '/@/utils/storage';
|
||||
import headerImage from '/@/assets/img/headerImage.png';
|
||||
import websocket from '/@/utils/websocket';
|
||||
import { InfoFilled } from '@element-plus/icons-vue'
|
||||
// 引入组件
|
||||
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
|
||||
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
|
||||
@@ -118,6 +144,21 @@ const layoutUserFlexNum = computed(() => {
|
||||
else num = '';
|
||||
return num;
|
||||
});
|
||||
|
||||
// 定义变量内容
|
||||
const { isSocketOpen } = storeToRefs(useUserInfo());
|
||||
|
||||
// websocket状态
|
||||
const onlinePopoverRef = ref()
|
||||
const onlineConfirmEvent = () => {
|
||||
if (!isSocketOpen.value) {
|
||||
websocket.is_reonnect = true
|
||||
websocket.reconnect_current = 1
|
||||
websocket.reconnect()
|
||||
}
|
||||
// 手动隐藏弹出
|
||||
unref(onlinePopoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
// 全屏点击时
|
||||
const onScreenfullClick = () => {
|
||||
if (!screenfull.isEnabled) {
|
||||
@@ -256,5 +297,29 @@ const messageCenter = messageCenterStore();
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 12px;
|
||||
}
|
||||
.online-status{
|
||||
cursor: pointer;
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 30px;
|
||||
font-size: 14px;
|
||||
left: 5px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
padding: 0;
|
||||
background-color: #18bc9c;
|
||||
}
|
||||
}
|
||||
.online-down{
|
||||
cursor: pointer;
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 30px;
|
||||
font-size: 14px;
|
||||
left: 5px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
padding: 0;
|
||||
background-color: #979b9c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
<div class="upgrade-content">
|
||||
{{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
|
||||
<div class="mt5">
|
||||
<el-link type="primary" class="font12" href="https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md" target="_black">
|
||||
<el-link type="primary" class="font12" href="https://gitee.com/huge-dream/django-vue3-admin/blob/master/CHANGELOG.md" target="_black">
|
||||
CHANGELOG.md
|
||||
</el-link>
|
||||
</div>
|
||||
<div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
|
||||
</div>
|
||||
<div class="upgrade-btn">
|
||||
<el-button round size="default" type="info" text @click="onCancel">{{ $t('message.upgrade.btnOne') }}</el-button>
|
||||
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading">{{ state.btnTxt }}</el-button>
|
||||
<el-button round size="default" type="info" text @click="onCancel" >{{ $t('message.upgrade.btnOne') }}</el-button>
|
||||
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading" >{{ state.btnTxt }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -76,10 +76,10 @@ const delayShow = () => {
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
// delayShow();
|
||||
// setTimeout(() => {
|
||||
// state.btnTxt = t('message.upgrade.btnTwo');
|
||||
// }, 200);
|
||||
delayShow();
|
||||
setTimeout(() => {
|
||||
state.btnTxt = t('message.upgrade.btnTwo');
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import piniaPersist from 'pinia-plugin-persist';
|
||||
// @ts-ignore
|
||||
import fastCrud from './settings.ts';
|
||||
import pinia from './stores';
|
||||
import permission from '/@/plugin/permission/index';
|
||||
import {RegisterPermission} from '/@/plugin/permission/index';
|
||||
// @ts-ignore
|
||||
import eIconPicker, { iconList, analyzingIconForIconfont } from 'e-icon-picker';
|
||||
import 'e-icon-picker/icon/default-icon/symbol.js'; //基本彩色图标库
|
||||
@@ -54,7 +54,6 @@ 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.config.globalProperties.mittBus = mitt();
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import permissionDirective from './directive.permission'
|
||||
import permissionFunc from './func.permission'
|
||||
const install = function (app:any) {
|
||||
export const RegisterPermission = function (app:any) {
|
||||
app.directive('permission', permissionDirective)
|
||||
app.provide('$hasPermissions',permissionFunc.hasPermissions)
|
||||
}
|
||||
|
||||
export default {
|
||||
install
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export const BtnPermissionStore = defineStore('BtnPermission', {
|
||||
actions: {
|
||||
async getBtnPermissionStore() {
|
||||
request({
|
||||
url: '/api/system/menu/menu_button_all_permission/',
|
||||
url: '/api/system/menu_button/menu_button_all_permission/',
|
||||
method: 'get',
|
||||
}).then((ret: {
|
||||
data: []
|
||||
|
||||
@@ -9,7 +9,7 @@ import { request } from '/@/utils/service';
|
||||
//扩展包
|
||||
import { FsExtendsEditor,FsExtendsUploader } from '@fast-crud/fast-extends';
|
||||
import '@fast-crud/fast-extends/dist/style.css';
|
||||
import { ElMessage } from "element-plus";
|
||||
import { successMessage, successNotification } from '/@/utils/message';
|
||||
export default {
|
||||
async install(app: any, options: any) {
|
||||
// 先安装ui
|
||||
@@ -18,9 +18,9 @@ export default {
|
||||
app.use(FastCrud, {
|
||||
//i18n, //i18n配置,可选,默认使用中文,具体用法请看demo里的 src/i18n/index.js 文件
|
||||
// 此处配置公共的dictRequest(字典请求)
|
||||
async dictRequest({ url }: any) {
|
||||
async dictRequest({ dict }: any) {
|
||||
//根据dict的url,异步返回一个字典数组
|
||||
return await request({ url: url, }).then((res:any)=>{
|
||||
return await request({ url: dict.url, params: dict.params || {} }).then((res:any)=>{
|
||||
return res.data
|
||||
});
|
||||
},
|
||||
@@ -41,21 +41,14 @@ export default {
|
||||
transformRes: ({ res }: any) => {
|
||||
//将pageRequest的返回数据,转换为fast-crud所需要的格式
|
||||
//return {records,currentPage,pageSize,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 };
|
||||
}
|
||||
|
||||
return { records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total };
|
||||
},
|
||||
},
|
||||
form: {
|
||||
afterSubmit(ctx:any ) {
|
||||
const {mode} = ctx
|
||||
if (mode === "add") {
|
||||
ElMessage.success({ message: "添加成功" });
|
||||
} else if (mode === "edit") {
|
||||
ElMessage.success({ message: "保存成功" });
|
||||
afterSubmit(ctx: any) {
|
||||
// 增加crud提示
|
||||
if (ctx.res.code == 2000) {
|
||||
successNotification(ctx.res.msg);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -107,12 +100,10 @@ export default {
|
||||
});
|
||||
},
|
||||
successHandle(ret) {
|
||||
console.log(111,ret)
|
||||
// 上传完成后的结果处理, 此处应返回格式为{url:xxx,key:xxx}
|
||||
return {
|
||||
url: ret.data.url,
|
||||
key: ret.data.id,
|
||||
...ret.data
|
||||
url: getBaseURL() + ret.data.url,
|
||||
key: ret.data.id
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
26
web/src/stores/btnPermission.ts
Normal file
26
web/src/stores/btnPermission.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {defineStore} from "pinia";
|
||||
import {DictionaryStates} from "/@/stores/interface";
|
||||
import {request} from "/@/utils/service";
|
||||
|
||||
export const BtnPermissionStore = defineStore('BtnPermission', {
|
||||
state: (): DictionaryStates => ({
|
||||
data: []
|
||||
}),
|
||||
actions: {
|
||||
async getBtnPermissionStore() {
|
||||
request({
|
||||
url: '/api/system/menu_button/menu_button_all_permission/',
|
||||
method: 'get',
|
||||
}).then((ret: {
|
||||
data: []
|
||||
}) => {
|
||||
// 转换数据格式并保存到pinia
|
||||
let dataList = ret.data
|
||||
this.data=dataList
|
||||
})
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
@@ -19,6 +19,7 @@ export interface UserInfosState {
|
||||
}
|
||||
export interface UserInfosStates {
|
||||
userInfos: UserInfosState;
|
||||
isSocketOpen: boolean
|
||||
}
|
||||
|
||||
// 路由缓存列表
|
||||
|
||||
@@ -26,6 +26,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
},
|
||||
],
|
||||
},
|
||||
isSocketOpen: false
|
||||
}),
|
||||
actions: {
|
||||
async updateUserInfos() {
|
||||
@@ -57,6 +58,9 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
Session.set('userInfo', this.userInfos);
|
||||
}
|
||||
},
|
||||
async setWebSocketState(socketState: boolean) {
|
||||
this.isSocketOpen = socketState;
|
||||
},
|
||||
async getApiUserInfo() {
|
||||
return request({
|
||||
url: '/api/system/user/user_info/',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { App } from 'vue';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { judementSameArr } from '/@/utils/arrayOperation';
|
||||
|
||||
import {BtnPermissionStore} from "/@/stores/btnPermission";
|
||||
/**
|
||||
* 用户权限指令
|
||||
* @directive 单个权限验证(v-auth="xxx")
|
||||
@@ -12,16 +11,16 @@ export function authDirective(app: App) {
|
||||
// 单个权限验证(v-auth="xxx")
|
||||
app.directive('auth', {
|
||||
mounted(el, binding) {
|
||||
const stores = useUserInfo();
|
||||
if (!stores.userInfos.authBtnList.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
|
||||
const stores = BtnPermissionStore();
|
||||
if (!stores.data.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
|
||||
},
|
||||
});
|
||||
// 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
|
||||
app.directive('auths', {
|
||||
mounted(el, binding) {
|
||||
let flag = false;
|
||||
const stores = useUserInfo();
|
||||
stores.userInfos.authBtnList.map((val: string) => {
|
||||
const stores = BtnPermissionStore();
|
||||
stores.data.map((val: string) => {
|
||||
binding.value.map((v: string) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
@@ -32,8 +31,8 @@ export function authDirective(app: App) {
|
||||
// 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]")
|
||||
app.directive('auth-all', {
|
||||
mounted(el, binding) {
|
||||
const stores = useUserInfo();
|
||||
const flag = judementSameArr(binding.value, stores.userInfos.authBtnList);
|
||||
const stores = BtnPermissionStore();
|
||||
const flag = judementSameArr(binding.value, stores.data);
|
||||
if (!flag) el.parentNode.removeChild(el);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { judementSameArr } from '/@/utils/arrayOperation';
|
||||
|
||||
import {BtnPermissionStore} from "/@/stores/btnPermission";
|
||||
/**
|
||||
* 单个权限验证
|
||||
* @param value 权限值
|
||||
* @returns 有权限,返回 `true`,反之则反
|
||||
*/
|
||||
export function auth(value: string): boolean {
|
||||
const stores = useUserInfo();
|
||||
return stores.userInfos.authBtnList.some((v: string) => v === value);
|
||||
const stores = BtnPermissionStore();
|
||||
return stores.data.some((v: string) => v === value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -18,8 +17,8 @@ export function auth(value: string): boolean {
|
||||
*/
|
||||
export function auths(value: Array<string>): boolean {
|
||||
let flag = false;
|
||||
const stores = useUserInfo();
|
||||
stores.userInfos.authBtnList.map((val: string) => {
|
||||
const stores = BtnPermissionStore();
|
||||
stores.data.map((val: string) => {
|
||||
value.map((v: string) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
@@ -33,6 +32,6 @@ export function auths(value: Array<string>): boolean {
|
||||
* @returns 有权限,返回 `true`,反之则反
|
||||
*/
|
||||
export function authAll(value: Array<string>): boolean {
|
||||
const stores = useUserInfo();
|
||||
return judementSameArr(value, stores.userInfos.authBtnList);
|
||||
const stores = BtnPermissionStore();
|
||||
return judementSameArr(value, stores.data);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import { toRaw } from 'vue';
|
||||
import { DictionaryStore } from '/@/stores/dictionary';
|
||||
|
||||
/**
|
||||
* @method 获取指定name字典
|
||||
/**
|
||||
* @method 获取指定name字典
|
||||
*/
|
||||
export const dictionary = (key: string,value:string|number|null=null) => {
|
||||
export const dictionary = (name: string) => {
|
||||
const dict = DictionaryStore()
|
||||
const dictionary = toRaw(dict.data)
|
||||
if(value!==null){
|
||||
for (let item of dictionary[key]) {
|
||||
if (item.value === value) {
|
||||
return item.label
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
return dictionary[key]
|
||||
}
|
||||
return dictionary[name]
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import {Session} from "/@/utils/storage";
|
||||
import {getWsBaseURL} from "/@/utils/baseUrl";
|
||||
// @ts-ignore
|
||||
import socket from '@/types/api/socket'
|
||||
|
||||
import {useUserInfo} from "/@/stores/userInfo";
|
||||
const websocket: socket = {
|
||||
websocket: null,
|
||||
connectURL: getWsBaseURL(),
|
||||
@@ -42,6 +42,7 @@ const websocket: socket = {
|
||||
}
|
||||
websocket.websocket.onclose = (e: any) => {
|
||||
websocket.socket_open = false
|
||||
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||
// 需要重新连接
|
||||
if (websocket.is_reonnect) {
|
||||
websocket.reconnect_timer = setTimeout(() => {
|
||||
@@ -49,6 +50,8 @@ const websocket: socket = {
|
||||
if (websocket.reconnect_current > websocket.reconnect_count) {
|
||||
clearTimeout(websocket.reconnect_timer)
|
||||
websocket.is_reonnect = false
|
||||
websocket.socket_open = false
|
||||
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||
return
|
||||
}
|
||||
// 记录重连次数
|
||||
@@ -60,6 +63,7 @@ const websocket: socket = {
|
||||
// 连接成功
|
||||
websocket.websocket.onopen = function () {
|
||||
websocket.socket_open = true
|
||||
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||
websocket.is_reonnect = true
|
||||
// 开启心跳
|
||||
websocket.heartbeat()
|
||||
@@ -85,17 +89,21 @@ const websocket: socket = {
|
||||
callback && callback()
|
||||
} else {
|
||||
clearInterval(websocket.hearbeat_timer)
|
||||
message({
|
||||
type: 'warning',
|
||||
message: 'socket链接已断开',
|
||||
duration: 1000,
|
||||
})
|
||||
// message({
|
||||
// type: 'warning',
|
||||
// message: 'socket链接已断开',
|
||||
// duration: 1000,
|
||||
// })
|
||||
websocket.socket_open = false
|
||||
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||
}
|
||||
},
|
||||
close: () => {
|
||||
websocket.is_reonnect = false
|
||||
websocket.websocket.close()
|
||||
websocket.websocket = null;
|
||||
websocket.socket_open = false
|
||||
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||
},
|
||||
/**
|
||||
* 重新连接
|
||||
|
||||
@@ -12,5 +12,5 @@ export const scanAndInstallPlugins = (app: any) => {
|
||||
pluginNames.add(pluginsName);
|
||||
}
|
||||
pluginsAll = Array.from(pluginNames);
|
||||
console.table('已注册插件:', pluginsAll);
|
||||
console.log('已发现插件:', pluginsAll);
|
||||
};
|
||||
|
||||
@@ -1,225 +1,244 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {
|
||||
dict,
|
||||
UserPageQuery,
|
||||
AddReq,
|
||||
DelReq,
|
||||
EditReq,
|
||||
compute,
|
||||
CreateCrudOptionsProps,
|
||||
CreateCrudOptionsRet
|
||||
} from '@fast-crud/fast-crud';
|
||||
import {dictionary} from '/@/utils/dictionary';
|
||||
import {successMessage} from '/@/utils/message';
|
||||
import {auth} from "/@/utils/authFunction";
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({form, row}: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({row}: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({form}: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
/**
|
||||
* 懒加载
|
||||
* @param row
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
|
||||
pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
|
||||
resolve(res.data);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 懒加载
|
||||
* @param row
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
|
||||
pageRequest({pcode: tree.code}).then((res: APIResponseData) => {
|
||||
resolve(res.data);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
},
|
||||
table: {
|
||||
rowKey: 'id',
|
||||
lazy: true,
|
||||
load: loadContentMethod,
|
||||
treeProps: { children: 'children', hasChildren: 'hasChild' },
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
// pcode: {
|
||||
// title: '父级地区',
|
||||
// show: false,
|
||||
// search: {
|
||||
// show: true,
|
||||
// },
|
||||
// type: 'dict-tree',
|
||||
// form: {
|
||||
// component: {
|
||||
// showAllLevels: false, // 仅显示最后一级
|
||||
// props: {
|
||||
// elProps: {
|
||||
// clearable: true,
|
||||
// showAllLevels: false, // 仅显示最后一级
|
||||
// props: {
|
||||
// checkStrictly: true, // 可以不需要选到最后一级
|
||||
// emitPath: false,
|
||||
// clearable: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
name: {
|
||||
title: '名称',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
treeNode: true,
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '名称必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
code: {
|
||||
title: '地区编码',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 90,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '地区编码必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入地区编码',
|
||||
},
|
||||
},
|
||||
},
|
||||
pinyin: {
|
||||
title: '拼音',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '拼音必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
},
|
||||
},
|
||||
},
|
||||
level: {
|
||||
title: '地区层级',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: false,
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '拼音必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
},
|
||||
},
|
||||
},
|
||||
initials: {
|
||||
title: '首字母',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '首字母必填项' },
|
||||
],
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('area:Create'),
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: auth('area:Update')
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: auth('area:Delete')
|
||||
},
|
||||
},
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
},
|
||||
table: {
|
||||
rowKey: 'id',
|
||||
lazy: true,
|
||||
load: loadContentMethod,
|
||||
treeProps: {children: 'children', hasChildren: 'hasChild'},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: {show: false},
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
// pcode: {
|
||||
// title: '父级地区',
|
||||
// show: false,
|
||||
// search: {
|
||||
// show: true,
|
||||
// },
|
||||
// type: 'dict-tree',
|
||||
// form: {
|
||||
// component: {
|
||||
// showAllLevels: false, // 仅显示最后一级
|
||||
// props: {
|
||||
// elProps: {
|
||||
// clearable: true,
|
||||
// showAllLevels: false, // 仅显示最后一级
|
||||
// props: {
|
||||
// checkStrictly: true, // 可以不需要选到最后一级
|
||||
// emitPath: false,
|
||||
// clearable: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
name: {
|
||||
title: '名称',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
treeNode: true,
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '名称必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
code: {
|
||||
title: '地区编码',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 90,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '地区编码必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入地区编码',
|
||||
},
|
||||
},
|
||||
},
|
||||
pinyin: {
|
||||
title: '拼音',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '拼音必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
},
|
||||
},
|
||||
},
|
||||
level: {
|
||||
title: '地区层级',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: false,
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '拼音必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
},
|
||||
},
|
||||
},
|
||||
initials: {
|
||||
title: '首字母',
|
||||
column: {
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '首字母必填项'},
|
||||
],
|
||||
|
||||
component: {
|
||||
placeholder: '请输入首字母',
|
||||
},
|
||||
},
|
||||
},
|
||||
enable: {
|
||||
title: '是否启用',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
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'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
component: {
|
||||
placeholder: '请输入首字母',
|
||||
},
|
||||
},
|
||||
},
|
||||
enable: {
|
||||
title: '是否启用',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
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'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
list-type="picture-card"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
|
||||
<div slot="tip" class="el-upload__tip">请选取图片,并且只能上传jpg/png文件</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogImgVisible">
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
@@ -150,7 +150,7 @@
|
||||
list-type="picture-card"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
|
||||
<div slot="tip" class="el-upload__tip">请选取图片,并且只能上传jpg/png文件</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogImgVisible">
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
@@ -506,4 +506,8 @@ watch(
|
||||
);
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style scoped>
|
||||
:deep(.el-upload-list--picture-card){
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<el-input v-model="deptFormData.key" />
|
||||
</el-form-item>
|
||||
<el-form-item label="负责人">
|
||||
<el-input v-model="deptFormData.owner" />
|
||||
<el-input v-model="deptFormData.owner" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="deptFormData.description" maxlength="200" show-word-limit type="textarea" />
|
||||
|
||||
@@ -4,6 +4,7 @@ import { request } from '/@/utils/service';
|
||||
import * as api from './api';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {auth} from "/@/utils/authFunction";
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
@@ -39,9 +40,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
return await api.exportData(query);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions: any = inject('$hasPermissions');
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
table: {
|
||||
@@ -58,12 +56,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: hasPermissions('user:Create'),
|
||||
// show:true
|
||||
show: auth('user:Create')
|
||||
},
|
||||
export: {
|
||||
text: '导出', //按钮文字
|
||||
title: '导出', //鼠标停留显示的信息
|
||||
show: auth('user:Export'),
|
||||
click() {
|
||||
return exportRequest(crudExpose!.getSearchFormData());
|
||||
},
|
||||
@@ -89,15 +87,15 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
show: hasPermissions('user:Update'),
|
||||
show: auth('user:Update'),
|
||||
},
|
||||
remove: {
|
||||
show: hasPermissions('user:Delete'),
|
||||
show: auth('user:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '重设密码',
|
||||
type: 'primary',
|
||||
show: hasPermissions('user:ResetPassword'),
|
||||
show: auth('user:ResetPassword'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '重设密码',
|
||||
@@ -266,7 +264,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
url: '/api/system/role/',
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
isTree: true,
|
||||
getData: async ({ url }: { url: string }) => {
|
||||
return request({
|
||||
url: url,
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<el-button :icon="!showCount ? 'Hide' : 'View'" circle @click="showCount = !showCount"></el-button>
|
||||
</template>
|
||||
<template #actionbar-right>
|
||||
<importExcel api="api/system/user/">导入 </importExcel>
|
||||
<importExcel api="api/system/user/" v-auth="'user:Import'">导入 </importExcel>
|
||||
</template>
|
||||
</fs-crud>
|
||||
|
||||
@@ -251,10 +251,10 @@ const handleResetPwdSubmit = async () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
deptCountChart = init(deptCountBar.value as HTMLElement);
|
||||
deptSexChart = init(deptSexPie.value as HTMLElement);
|
||||
getDeptInfo();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
|
||||
@@ -3,7 +3,7 @@ import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOption
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { inject, nextTick, ref } from 'vue';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
|
||||
import {auth} from '/@/utils/authFunction';
|
||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
@@ -19,8 +19,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject('$hasPermissions');
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
@@ -40,17 +38,17 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: hasPermissions('dictionary:Update'),
|
||||
show: auth('dictionary:Update'),
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: hasPermissions('dictionary:Delete'),
|
||||
show: auth('dictionary:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '字典配置',
|
||||
type: 'text',
|
||||
show: hasPermissions('dictionary:Update'),
|
||||
show: auth('dictionary:Update'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '字典配置',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<el-drawer size="70%" v-model="drawer" direction="rtl" destroy-on-close :before-close="handleClose">
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
<div>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
@@ -23,7 +25,6 @@ const handleClose = (done: () => void) => {
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
// catch error
|
||||
|
||||
@@ -34,6 +34,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
show:false,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<div class="login-left-logo">
|
||||
<img :src="logoMini" />
|
||||
<div class="login-left-logo-text">
|
||||
<span>{{ getThemeConfig.globalViceTitle }}</span>
|
||||
<span class="login-left-logo-text-msg">{{ getThemeConfig.globalViceTitleMsg }}</span>
|
||||
<span>{{ getSystemConfig['login.site_title']||getThemeConfig.globalViceTitle }}</span>
|
||||
<span class="login-left-logo-text-msg">{{ getSystemConfig['login.site_name']||getThemeConfig.globalViceTitleMsg }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-left-img">
|
||||
@@ -42,15 +42,15 @@
|
||||
</div>
|
||||
|
||||
<div class="login-authorization">
|
||||
<p>Copyright © 2021-2022 django-vue-admin.com 版权所有</p>
|
||||
<p>Copyright © {{getSystemConfig['login.copyright'] || '2021-2024 django-vue-admin.com'}} 版权所有</p>
|
||||
<p class="la-other">
|
||||
<a href="https://beian.miit.gov.cn" target="_blank">晋ICP备18005113号-3</a>
|
||||
<a href="https://beian.miit.gov.cn" target="_blank">{{getSystemConfig['login.keep_record'] || '晋ICP备18005113号-3'}}</a>
|
||||
|
|
||||
<a href="https://django-vue-admin.com" target="_blank">帮助</a>
|
||||
<a :href="getSystemConfig['login.help_url']?getSystemConfig['login.help_url']:'https://django-vue-admin.com'" target="_blank">帮助</a>
|
||||
|
|
||||
<a href="#">隐私</a>
|
||||
<a :href="getSystemConfig['login.privacy_url']?getSystemConfig['login.privacy_url']:'#'">隐私</a>
|
||||
|
|
||||
<a href="#">条款</a>
|
||||
<a :href="getSystemConfig['login.clause_url']?getSystemConfig['login.clause_url']:'#'">条款</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,7 +64,7 @@ import { NextLoading } from '/@/utils/loading';
|
||||
import logoMini from '/@/assets/logo-mini.svg';
|
||||
import loginMain from '/@/assets/login-main.svg';
|
||||
import loginBg from '/@/assets/login-bg.svg';
|
||||
|
||||
import {SystemConfigStore} from '/@/stores/systemConfig'
|
||||
// 引入组件
|
||||
const Account = defineAsyncComponent(() => import('/@/views/system/login/component/account.vue'));
|
||||
const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
|
||||
@@ -82,6 +82,14 @@ const state = reactive({
|
||||
const getThemeConfig = computed(() => {
|
||||
return themeConfig.value;
|
||||
});
|
||||
|
||||
const systemConfigStore = SystemConfigStore()
|
||||
const {systemConfig} = storeToRefs(systemConfigStore)
|
||||
const getSystemConfig = computed(()=>{
|
||||
return systemConfig.value
|
||||
})
|
||||
|
||||
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
NextLoading.done();
|
||||
|
||||
@@ -5,19 +5,12 @@ export const apiPrefix = '/api/system/menu/';
|
||||
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix+"tree/",
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function GetChildren(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/getChildren/',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
|
||||
41
web/src/views/system/menu/components/MenuButtonCom/api.ts
Normal file
41
web/src/views/system/menu/components/MenuButtonCom/api.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/menu_button/';
|
||||
export function GetList(query: PageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: any) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
215
web/src/views/system/menu/components/MenuButtonCom/crud.tsx
Normal file
215
web/src/views/system/menu/components/MenuButtonCom/crud.tsx
Normal file
@@ -0,0 +1,215 @@
|
||||
import {AddReq, DelReq, EditReq, dict, CreateCrudOptionsRet, CreateCrudOptionsProps} from '@fast-crud/fast-crud';
|
||||
import * as api from './api';
|
||||
import {auth} from '/@/utils/authFunction'
|
||||
import {request} from '/@/utils/service';
|
||||
//此处为crudOptions配置
|
||||
export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async () => {
|
||||
if (context!.selectOptions.value.id) {
|
||||
return await api.GetList({menu: context!.selectOptions.value.id} as any);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const editRequest = async ({form, row}: EditReq) => {
|
||||
return await api.UpdateObj({...form, menu: row.menu});
|
||||
};
|
||||
const delRequest = async ({row}: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({form}: AddReq) => {
|
||||
return await api.AddObj({...form, ...{menu: context!.selectOptions.value.id}});
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
pagination:{
|
||||
show:false
|
||||
},
|
||||
search: {
|
||||
container: {
|
||||
action: {
|
||||
//按钮栏配置
|
||||
col: {
|
||||
span: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('btn:Create')
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
icon: '',
|
||||
type: 'primary',
|
||||
show: auth('btn:Update')
|
||||
},
|
||||
remove: {
|
||||
show: auth('btn:Delete')
|
||||
},
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
col: {span: 24},
|
||||
labelWidth: '100px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
width: '600px',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: {show: false},
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: {show: false},
|
||||
type: 'text',
|
||||
search: {show: true},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
placeholder: '输入关键词搜索',
|
||||
},
|
||||
},
|
||||
},
|
||||
id: {
|
||||
title: 'ID',
|
||||
type: 'text',
|
||||
column: {show: false},
|
||||
search: {show: false},
|
||||
form: {show: false},
|
||||
},
|
||||
name: {
|
||||
title: '权限名称',
|
||||
type: 'text',
|
||||
search: {show: true},
|
||||
column: {
|
||||
minWidth: 120,
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{required: true, message: '权限名称必填'}],
|
||||
component: {
|
||||
placeholder: '输入权限名称搜索',
|
||||
props: {
|
||||
clearable: true,
|
||||
allowCreate: true,
|
||||
filterable: true,
|
||||
},
|
||||
},
|
||||
helper: {
|
||||
render() {
|
||||
return <el-alert title="手动输入" type="warning"
|
||||
description="页面中按钮的名称或者自定义一个名称"/>;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
value: {
|
||||
title: '权限值',
|
||||
type: 'text',
|
||||
search: {show: false},
|
||||
column: {
|
||||
width: 200,
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{required: true, message: '权限标识必填'}],
|
||||
placeholder: '输入权限标识',
|
||||
helper: {
|
||||
render() {
|
||||
return <el-alert title="唯一值" type="warning"
|
||||
description="用于判断前端按钮权限或接口权限"/>;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
method: {
|
||||
title: '请求方式',
|
||||
search: {show: false},
|
||||
type: 'dict-select',
|
||||
column: {
|
||||
width: 120,
|
||||
sortable: true,
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{label: 'GET', value: 0},
|
||||
{label: 'POST', value: 1, color: 'success'},
|
||||
{label: 'PUT', value: 2, color: 'warning'},
|
||||
{label: 'DELETE', value: 3, color: 'danger'},
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
rules: [{required: true, message: '必填项'}],
|
||||
},
|
||||
},
|
||||
api: {
|
||||
title: '接口地址',
|
||||
search: {show: false},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
getData() {
|
||||
return request({url: '/swagger.json'}).then((res: any) => {
|
||||
const ret = Object.keys(res.paths);
|
||||
const data = [];
|
||||
for (const item of ret) {
|
||||
const obj: any = {};
|
||||
obj.label = item;
|
||||
obj.value = item;
|
||||
data.push(obj);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column: {
|
||||
minWidth: 250,
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{required: true, message: '必填项'}],
|
||||
component: {
|
||||
props: {
|
||||
allowCreate: true,
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
helper: {
|
||||
render() {
|
||||
return <el-alert title="请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/"
|
||||
type="warning"/>;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
28
web/src/views/system/menu/components/MenuButtonCom/index.vue
Normal file
28
web/src/views/system/menu/components/MenuButtonCom/index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useFs } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
import { MenuTreeItemType } from '../../types';
|
||||
|
||||
// 当前选择的菜单信息
|
||||
let selectOptions: any = ref({ name: null });
|
||||
|
||||
const { crudRef, crudBinding, crudExpose, context } = useFs({ createCrudOptions, context: { selectOptions } });
|
||||
const { doRefresh, setTableData } = crudExpose;
|
||||
|
||||
const handleRefreshTable = (record: MenuTreeItemType) => {
|
||||
if (!record.is_catalog && record.id) {
|
||||
selectOptions.value = record;
|
||||
doRefresh();
|
||||
} else {
|
||||
//清空表格数据
|
||||
setTableData([]);
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ selectOptions, handleRefreshTable });
|
||||
</script>
|
||||
240
web/src/views/system/menu/components/MenuFieldCom/crud.tsx
Normal file
240
web/src/views/system/menu/components/MenuFieldCom/crud.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { request } from '/@/utils/service';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { inject } from 'vue';
|
||||
import {auth} from "/@/utils/authFunction";
|
||||
|
||||
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose, props,modelDialog,selectOptions,allModelData }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
// return await api.GetList(query);
|
||||
if (selectOptions.value.id) {
|
||||
return await api.GetList({ menu: selectOptions.value.id } as any);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
form.menu = selectOptions.value.id;
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add:{
|
||||
show:auth('column:Create')
|
||||
},
|
||||
auto: {
|
||||
text: '自动匹配',
|
||||
type: 'success',
|
||||
show:auth('column:Match'),
|
||||
click: () => {
|
||||
return modelDialog.value=true;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
show: auth('column:Update')
|
||||
},
|
||||
remove: {
|
||||
show: auth('column:Delete')
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
labelWidth: '110px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
width: '600px',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
//@ts-ignore
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination: any = crudExpose!.crudBinding.value.pagination;
|
||||
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
model: {
|
||||
title: 'model',
|
||||
type: 'dict-select',
|
||||
dict:dict({
|
||||
url:'/api/system/column/get_models/',
|
||||
label:'title',
|
||||
value:'key'
|
||||
}),
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
title: {
|
||||
title: '中文名',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'text',
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
placeholder: '请输入中文名',
|
||||
},
|
||||
},
|
||||
},
|
||||
field_name: {
|
||||
title: '字段名',
|
||||
type: 'text',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
placeholder: '请输入字段名',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// is_create: {
|
||||
// title: '创建时显示',
|
||||
// sortable: 'custom',
|
||||
// search: {
|
||||
// disabled: true,
|
||||
// },
|
||||
// type: 'dict-switch',
|
||||
// dict: dict({
|
||||
// data: [
|
||||
// { value: true, label: '启用' },
|
||||
// { value: false, label: '禁用' },
|
||||
// ],
|
||||
// }),
|
||||
// form: {
|
||||
// value: true,
|
||||
// },
|
||||
// column: {
|
||||
// valueChange(context){
|
||||
// return api.UpdateObj(context.row)
|
||||
// },
|
||||
// component: {
|
||||
// name: 'fs-dict-switch',
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// is_update: {
|
||||
// title: '编辑时显示',
|
||||
// search: {
|
||||
// show: true,
|
||||
// },
|
||||
// type: 'dict-switch',
|
||||
// dict: dict({
|
||||
// data: [
|
||||
// { value: true, label: '启用' },
|
||||
// { value: false, label: '禁用' },
|
||||
// ],
|
||||
// }),
|
||||
// form: {
|
||||
// value: true,
|
||||
// },
|
||||
// column: {
|
||||
// component: {
|
||||
// name: 'fs-dict-switch',
|
||||
// onChange: compute((context) => {
|
||||
// //动态onChange方法测试
|
||||
// return () => {
|
||||
// console.log('onChange', context.row.switch);
|
||||
// };
|
||||
// }),
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// is_query: {
|
||||
// title: '列表中显示',
|
||||
// type: 'dict-switch',
|
||||
// dict: dict({
|
||||
// data: [
|
||||
// { value: true, label: '启用' },
|
||||
// { value: false, label: '禁用' },
|
||||
// ],
|
||||
// }),
|
||||
// form: {
|
||||
// value: true,
|
||||
// },
|
||||
// column: {
|
||||
// component: {
|
||||
// name: 'fs-dict-switch',
|
||||
// onChange: compute((context) => {
|
||||
// //动态onChange方法测试
|
||||
// return () => {
|
||||
// console.log('onChange', context.row.switch);
|
||||
// };
|
||||
// }),
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
110
web/src/views/system/menu/components/MenuFieldCom/index.vue
Normal file
110
web/src/views/system/menu/components/MenuFieldCom/index.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog ref="modelRef" v-model="modelDialog" title="选择model">
|
||||
<div v-show="props.model">
|
||||
<el-tag>已选择:{{ props.model }}</el-tag>
|
||||
</div>
|
||||
<div class="model-card">
|
||||
<div v-for="(item,index) in allModelData" :value="item.key" :key="index">
|
||||
<el-text :type="modelCheckIndex===index?'primary':''" @click="onModelChecked(item,index)">
|
||||
{{ item.app + '--' + item.title + '(' + item.key + ')' }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="modelDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleAutomatch">
|
||||
确定
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<div style="height: 80vh">
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
</fs-crud>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, onMounted, reactive} from 'vue';
|
||||
import {useFs} from '@fast-crud/fast-crud';
|
||||
import {createCrudOptions} from './crud';
|
||||
import {getModelList} from './api'
|
||||
import {MenuTreeItemType} from "/@/views/system/menu/types";
|
||||
import {successMessage, successNotification, warningNotification} from '/@/utils/message';
|
||||
import {automatchColumnsData} from '/@/views/system/columns/components/ColumnsTableCom/api';
|
||||
// 当前选择的菜单信息
|
||||
let selectOptions: any = ref({name: null});
|
||||
|
||||
const props = reactive({
|
||||
model: '',
|
||||
app: '',
|
||||
menu: ''
|
||||
})
|
||||
|
||||
//model弹窗
|
||||
const modelDialog = ref(false)
|
||||
// 获取所有model
|
||||
const allModelData = ref<any[]>([]);
|
||||
const modelCheckIndex = ref(null)
|
||||
const onModelChecked = (row, index) => {
|
||||
modelCheckIndex.value = index
|
||||
props.model = row.key
|
||||
props.app = row.app
|
||||
}
|
||||
/**
|
||||
* 菜单选中时,加载表格数据
|
||||
* @param record
|
||||
*/
|
||||
const handleRefreshTable = (record: MenuTreeItemType) => {
|
||||
if (!record.is_catalog && record.id) {
|
||||
selectOptions.value = record;
|
||||
crudExpose.doRefresh();
|
||||
} else {
|
||||
//清空表格数据
|
||||
crudExpose.setTableData([]);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 自动匹配列
|
||||
*/
|
||||
const handleAutomatch = async () => {
|
||||
props.menu = selectOptions.value.id
|
||||
modelDialog.value = false
|
||||
if (props.menu && props.model) {
|
||||
const res = await automatchColumnsData(props);
|
||||
if (res?.code === 2000) {
|
||||
successNotification('匹配成功');
|
||||
}
|
||||
crudExpose.doSearch({form: {menu: props.menu, model: props.model}});
|
||||
}else {
|
||||
warningNotification('请选择角色和模型表!');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, props, modelDialog, selectOptions,allModelData});
|
||||
onMounted(async () => {
|
||||
const res = await getModelList();
|
||||
allModelData.value = res.data;
|
||||
});
|
||||
|
||||
defineExpose({selectOptions, handleRefreshTable});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.model-card {
|
||||
margin-top: 10px;
|
||||
height: 30vh;
|
||||
overflow-y: scroll;
|
||||
|
||||
div {
|
||||
margin: 15px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
279
web/src/views/system/menu/components/MenuFormCom/index.vue
Normal file
279
web/src/views/system/menu/components/MenuFormCom/index.vue
Normal file
@@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<div class="menu-form-com">
|
||||
<div class="menu-form-alert">
|
||||
1.红色星号表示必填;<br />
|
||||
2.添加菜单,如果是目录,组件地址为空即可;<br />
|
||||
3.添加根节点菜单,父级菜单为空即可;
|
||||
</div>
|
||||
<el-form ref="formRef" :rules="rules" :model="menuFormData" label-width="80px" label-position="right">
|
||||
<el-form-item label="菜单名称" prop="name">
|
||||
<el-input v-model="menuFormData.name" placeholder="请输入菜单名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="父级菜单" prop="parent">
|
||||
<el-tree-select
|
||||
v-model="menuFormData.parent"
|
||||
:props="defaultTreeProps"
|
||||
:data="deptDefaultList"
|
||||
:cache-data="props.cacheData"
|
||||
lazy
|
||||
check-strictly
|
||||
clearable
|
||||
:load="handleTreeLoad"
|
||||
placeholder="请选择父级菜单"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="路由地址" prop="web_path">
|
||||
<el-input v-model="menuFormData.web_path" placeholder="请输入路由地址,请以/开头" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="图标" prop="icon">
|
||||
<IconSelector clearable v-model="menuFormData.icon" />
|
||||
</el-form-item>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item required label="状态">
|
||||
<el-switch v-model="menuFormData.status" width="60" inline-prompt active-text="启用" inactive-text="禁用" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item v-if="menuFormData.status" required label="侧边显示">
|
||||
<el-switch v-model="menuFormData.visible" width="60" inline-prompt active-text="显示" inactive-text="隐藏" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item required label="是否目录">
|
||||
<el-switch v-model="menuFormData.is_catalog" width="60" inline-prompt active-text="是" inactive-text="否" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item v-if="!menuFormData.is_catalog" required label="外链接">
|
||||
<el-switch v-model="menuFormData.is_link" width="60" inline-prompt active-text="是" inactive-text="否" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="menuFormData.description" maxlength="200" show-word-limit type="textarea" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<div style="min-height: 184px">
|
||||
<el-form-item v-if="!menuFormData.is_catalog && !menuFormData.is_link" label="组件地址" prop="component">
|
||||
<el-autocomplete
|
||||
class="w-full"
|
||||
v-model="menuFormData.component"
|
||||
:fetch-suggestions="querySearch"
|
||||
:trigger-on-focus="false"
|
||||
clearable
|
||||
:debounce="100"
|
||||
placeholder="输入组件地址"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!menuFormData.is_catalog && !menuFormData.is_link" label="组件名称" prop="component_name">
|
||||
<el-input v-model="menuFormData.component_name" placeholder="请输入组件名称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!menuFormData.is_catalog && menuFormData.is_link" label="Url" prop="web_path">
|
||||
<el-input v-model="menuFormData.web_path" placeholder="请输入Url" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!menuFormData.is_catalog" label="缓存">
|
||||
<el-switch v-model="menuFormData.cache" width="60" inline-prompt active-text="启用" inactive-text="禁用" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
</el-form>
|
||||
|
||||
<div class="menu-form-btns">
|
||||
<el-button @click="handleSubmit" type="primary" :loading="menuBtnLoading">保存</el-button>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
import { ElForm, FormRules } from 'element-plus';
|
||||
import IconSelector from '/@/components/iconSelector/index.vue';
|
||||
import { lazyLoadMenu, AddObj, UpdateObj } from '../../api';
|
||||
import { successNotification } from '/@/utils/message';
|
||||
import { MenuFormDataType, MenuTreeItemType, ComponentFileItem, APIResponseData } from '../../types';
|
||||
import type Node from 'element-plus/es/components/tree/src/model/node';
|
||||
|
||||
interface IProps {
|
||||
initFormData: Partial<MenuTreeItemType> | null;
|
||||
treeData: MenuTreeItemType[];
|
||||
cacheData: MenuTreeItemType[];
|
||||
}
|
||||
|
||||
const defaultTreeProps: any = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
isLeaf: (data: MenuTreeItemType[], node: Node) => {
|
||||
if (node?.data.hasChild) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
};
|
||||
const validateWebPath = (rule: any, value: string, callback: Function) => {
|
||||
let pattern = /^\/.*?/;
|
||||
let patternUrl = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
|
||||
const reg = menuFormData.is_link ? patternUrl.test(value) : pattern.test(value);
|
||||
if (reg) {
|
||||
callback();
|
||||
} else {
|
||||
callback(new Error('请输入正确的地址'));
|
||||
}
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
initFormData: () => null,
|
||||
treeData: () => [],
|
||||
cacheData: () => [],
|
||||
});
|
||||
const emit = defineEmits(['drawerClose']);
|
||||
|
||||
const formRef = ref<InstanceType<typeof ElForm>>();
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
web_path: [{ required: true, message: '请输入正确的地址', validator: validateWebPath, trigger: 'blur' }],
|
||||
name: [{ required: true, message: '菜单名称必填', trigger: 'blur' }],
|
||||
component: [{ required: true, message: '请输入组件地址', trigger: 'blur' }],
|
||||
component_name: [{ required: true, message: '请输入组件名称', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
let deptDefaultList = ref<MenuTreeItemType[]>([]);
|
||||
let menuFormData = reactive<MenuFormDataType>({
|
||||
parent: '',
|
||||
name: '',
|
||||
component: '',
|
||||
web_path: '',
|
||||
icon: '',
|
||||
cache: true,
|
||||
status: true,
|
||||
visible: true,
|
||||
component_name: '',
|
||||
description: '',
|
||||
is_catalog: false,
|
||||
is_link: false,
|
||||
});
|
||||
let menuBtnLoading = ref(false);
|
||||
|
||||
const setMenuFormData = () => {
|
||||
if (props.initFormData?.id) {
|
||||
menuFormData.id = props.initFormData?.id || '';
|
||||
menuFormData.name = props.initFormData?.name || '';
|
||||
menuFormData.parent = props.initFormData?.parent || '';
|
||||
menuFormData.component = props.initFormData?.component || '';
|
||||
menuFormData.web_path = props.initFormData?.web_path || '';
|
||||
menuFormData.icon = props.initFormData?.icon || '';
|
||||
menuFormData.status = props.initFormData?.status || true;
|
||||
menuFormData.visible = props.initFormData?.visible || true;
|
||||
menuFormData.cache = props.initFormData?.cache || true;
|
||||
menuFormData.component_name = props.initFormData?.component_name || '';
|
||||
menuFormData.description = props.initFormData?.description || '';
|
||||
menuFormData.is_catalog = props.initFormData?.is_catalog || false;
|
||||
menuFormData.is_link = props.initFormData?.is_link || false;
|
||||
}
|
||||
};
|
||||
|
||||
const querySearch = (queryString: string, cb: any) => {
|
||||
const files: any = import.meta.glob('@views/**/*.vue');
|
||||
let fileLists: Array<any> = [];
|
||||
Object.keys(files).forEach((queryString: string) => {
|
||||
fileLists.push({
|
||||
label: queryString.replace(/(\.\/|\.vue)/g, ''),
|
||||
value: queryString.replace(/(\.\/|\.vue)/g, ''),
|
||||
});
|
||||
});
|
||||
const results = queryString ? fileLists.filter(createFilter(queryString)) : fileLists;
|
||||
// 统一去掉/src/views/前缀
|
||||
results.forEach((val) => {
|
||||
val.label = val.label.replace('/src/views/', '');
|
||||
val.value = val.value.replace('/src/views/', '');
|
||||
});
|
||||
cb(results);
|
||||
};
|
||||
|
||||
const createFilter = (queryString: string) => {
|
||||
return (file: ComponentFileItem) => {
|
||||
return file.value.toLowerCase().indexOf(queryString.toLowerCase()) !== -1;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 树的懒加载
|
||||
*/
|
||||
const handleTreeLoad = (node: Node, resolve: Function) => {
|
||||
if (node.level !== 0) {
|
||||
lazyLoadMenu({ parent: node.data.id }).then((res: APIResponseData) => {
|
||||
resolve(res.data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!formRef.value) return;
|
||||
formRef.value.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
try {
|
||||
let res;
|
||||
menuBtnLoading.value = true;
|
||||
if (menuFormData.id) {
|
||||
res = await UpdateObj(menuFormData);
|
||||
} else {
|
||||
res = await AddObj(menuFormData);
|
||||
}
|
||||
if (res?.code === 2000) {
|
||||
successNotification(res.msg as string);
|
||||
handleCancel('submit');
|
||||
}
|
||||
} finally {
|
||||
menuBtnLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = (type: string = '') => {
|
||||
emit('drawerClose', type);
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
props.treeData.map((item) => {
|
||||
deptDefaultList.value.push(item);
|
||||
});
|
||||
setMenuFormData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-form-com {
|
||||
margin: 10px;
|
||||
overflow-y: auto;
|
||||
.menu-form-alert {
|
||||
color: #fff;
|
||||
line-height: 24px;
|
||||
padding: 8px 16px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
.menu-form-btns {
|
||||
padding-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
318
web/src/views/system/menu/components/MenuTreeCom/index.vue
Normal file
318
web/src/views/system/menu/components/MenuTreeCom/index.vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<el-input v-model="filterVal" :prefix-icon="Search" placeholder="请输入菜单名称" />
|
||||
<div class="menu-tree-com">
|
||||
<div class="mtc-head">
|
||||
<el-icon size="16" color="#606266" class="mtc-head-icon">
|
||||
<Menu />
|
||||
</el-icon>
|
||||
菜单列表
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
placement="right"
|
||||
content="1.红色菜单代表状态禁用; 2.添加菜单,如果是目录,组件地址为空即可; 3.添加根节点菜单,父级ID为空即可; 4.支持拖拽菜单;"
|
||||
>
|
||||
<el-icon size="16" color="var(--el-color-primary)" class="mtc-tooltip">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="treeData"
|
||||
:props="defaultTreeProps"
|
||||
:filter-node-method="filterNode"
|
||||
:load="handleTreeLoad"
|
||||
lazy
|
||||
:indent="45"
|
||||
@node-click="handleNodeClick"
|
||||
highlight-current
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<element-tree-line :node="node" :showLabelLine="false" :indent="32">
|
||||
<span v-if="data.status" class="text-center font-black font-normal">
|
||||
<SvgIcon :name="node.data.icon" color="var(--el-color-primary)" />
|
||||
{{ node.label }}
|
||||
</span>
|
||||
<span v-else class="text-center font-black text-red-700 font-normal"> <SvgIcon :name="node.data.icon" /> {{ node.label }} </span>
|
||||
</element-tree-line>
|
||||
</template>
|
||||
</el-tree>
|
||||
|
||||
<div class="mtc-tags">
|
||||
<el-tooltip effect="dark" content="新增">
|
||||
<el-icon size="16" v-auth="'menu:Create'" @click="handleUpdateMenu('create')" class="mtc-tags-icon">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip effect="dark" content="编辑">
|
||||
<el-icon size="16" v-auth="'menu:Update'" @click="handleUpdateMenu('update')" class="mtc-tags-icon">
|
||||
<Edit />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip effect="dark" content="上移">
|
||||
<el-icon size="16" v-auth="'menu:MoveUp'" @click="handleSort('up')" class="mtc-tags-icon">
|
||||
<Top />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip effect="dark" content="下移">
|
||||
<el-icon size="16" v-auth="'menu:MoveDown'" @click="handleSort('down')" class="mtc-tags-icon">
|
||||
<Bottom />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip effect="dark" content="删除">
|
||||
<el-icon size="16" v-auth="'menu:Delete'" @click="handleDeleteMenu()" class="mtc-tags-icon">
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRaw, watch, h } from 'vue';
|
||||
import { ElTree } from 'element-plus';
|
||||
import { getElementLabelLine } from 'element-tree-line';
|
||||
import { Search } from '@element-plus/icons-vue';
|
||||
import SvgIcon from '/@/components/svgIcon/index.vue';
|
||||
import { lazyLoadMenu, menuMoveUp, menuMoveDown } from '../../api';
|
||||
import { warningNotification } from '/@/utils/message';
|
||||
import { TreeTypes, MenuTreeItemType } from '../../types';
|
||||
import type Node from 'element-plus/es/components/tree/src/model/node';
|
||||
|
||||
interface IProps {
|
||||
treeData: TreeTypes[];
|
||||
}
|
||||
|
||||
const ElementTreeLine = getElementLabelLine(h);
|
||||
|
||||
const defaultTreeProps: any = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
icon: 'icon',
|
||||
isLeaf: (data: TreeTypes[], node: Node) => {
|
||||
if (node.data.is_catalog) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>();
|
||||
|
||||
withDefaults(defineProps<IProps>(), {
|
||||
treeData: () => [],
|
||||
});
|
||||
const emit = defineEmits(['treeClick', 'deleteDept', 'updateDept']);
|
||||
|
||||
let filterVal = ref('');
|
||||
let sortDisable = ref(false);
|
||||
let treeSelectMenu = ref<Partial<MenuTreeItemType>>({});
|
||||
let treeSelectNode = ref<Node | null>(null);
|
||||
|
||||
watch(filterVal, (val) => {
|
||||
treeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
/**
|
||||
* 树的搜索事件
|
||||
*/
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) return true;
|
||||
return toRaw(data).name.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* 树的懒加载
|
||||
*/
|
||||
const handleTreeLoad = (node: Node, resolve: Function) => {
|
||||
if (node.level !== 0) {
|
||||
lazyLoadMenu({ parent: node.data.id }).then((res: APIResponseData) => {
|
||||
resolve(res.data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 树的点击事件
|
||||
*/
|
||||
const handleNodeClick = (record: MenuTreeItemType, node: Node) => {
|
||||
treeSelectMenu.value = record;
|
||||
treeSelectNode.value = node;
|
||||
emit('treeClick', record);
|
||||
};
|
||||
|
||||
/**
|
||||
* 点击左侧编辑按钮
|
||||
*/
|
||||
const handleUpdateMenu = (type: string) => {
|
||||
if (type === 'update') {
|
||||
if (!treeSelectMenu.value.id) {
|
||||
warningNotification('请选择菜单!');
|
||||
return;
|
||||
}
|
||||
emit('updateDept', type, treeSelectMenu.value);
|
||||
} else {
|
||||
emit('updateDept', type);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除菜单
|
||||
*/
|
||||
const handleDeleteMenu = () => {
|
||||
if (!treeSelectMenu.value.id) {
|
||||
warningNotification('请选择菜单!');
|
||||
return;
|
||||
}
|
||||
emit('deleteDept', treeSelectMenu.value.id, () => {
|
||||
treeSelectMenu.value = {};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 移动操作
|
||||
*/
|
||||
const handleSort = async (type: string) => {
|
||||
if (!treeSelectMenu.value.id) {
|
||||
warningNotification('请选择菜单!');
|
||||
return;
|
||||
}
|
||||
if (sortDisable.value) return;
|
||||
|
||||
const parentList = treeSelectNode.value?.parent.childNodes || [];
|
||||
const index = parentList.findIndex((i) => i.data.id === treeSelectMenu.value.id);
|
||||
const record = parentList.find((i) => i.data.id === treeSelectMenu.value.id);
|
||||
|
||||
if (type === 'up') {
|
||||
if (index === 0) return;
|
||||
parentList.splice(index - 1, 0, record as any);
|
||||
parentList.splice(index + 1, 1);
|
||||
sortDisable.value = true;
|
||||
await menuMoveUp({ menu_id: treeSelectMenu.value.id });
|
||||
sortDisable.value = false;
|
||||
}
|
||||
if (type === 'down') {
|
||||
parentList.splice(index + 2, 0, record as any);
|
||||
parentList.splice(index, 1);
|
||||
sortDisable.value = true;
|
||||
await menuMoveDown({ menu_id: treeSelectMenu.value.id });
|
||||
sortDisable.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
treeRef,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-tree-com {
|
||||
.mtc-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: -8px;
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
|
||||
.mtc-head-icon {
|
||||
margin-right: 8px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.mtc-tooltip {
|
||||
margin-left: 5px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.mtc-tags {
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
box-sizing: border-box;
|
||||
|
||||
.mtc-tags-icon {
|
||||
cursor: pointer;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.menu-tree-com {
|
||||
height: calc(100% - 60px);
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
|
||||
.el-tree-node__content {
|
||||
height: 32px !important;
|
||||
}
|
||||
|
||||
.el-tree .el-tree-node__expand-icon svg {
|
||||
display: none !important;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.el-tree-node__expand-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.el-tree-node__content > .el-tree-node__expand-icon {
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
margin-right: 5px;
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
.el-tree .el-tree-node__expand-icon.expanded {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.el-tree .el-tree-node__expand-icon.is-leaf {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.el-tree .el-tree-node__expand-icon:before {
|
||||
background: url('../../../../../assets/img/menu-tree-show-icon.png') no-repeat center / 100%;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.el-tree .el-tree-node__expand-icon.expanded:before {
|
||||
background: url('../../../../../assets/img/menu-tree-hidden-icon.png') no-repeat center / 100%;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.el-tree .is-leaf.el-tree-node__expand-icon::before {
|
||||
display: block;
|
||||
background: none !important;
|
||||
content: '';
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,371 +0,0 @@
|
||||
import * as api from './api';
|
||||
import { CreateCrudOptionsProps, CreateCrudOptionsRet, dict, useCompute } from '@fast-crud/fast-crud';
|
||||
const { compute } = useCompute();
|
||||
import { shallowRef } from "vue";
|
||||
import IconSelector from "/@/components/IconSelector/index.vue"
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }) => {
|
||||
form.id = row.id;
|
||||
await api.UpdateObj(form);
|
||||
if (row.parent) {
|
||||
//刷新父节点的状态
|
||||
reloadTreeChildren(row.parent);
|
||||
}
|
||||
};
|
||||
const delRequest = async ({ row }:any) => {
|
||||
await api.DelObj(row.id);
|
||||
if (row.parent) {
|
||||
//刷新父节点的状态
|
||||
reloadTreeChildren(row.parent);
|
||||
}
|
||||
};
|
||||
|
||||
const addRequest = async (context:any) => {
|
||||
const {form} = context;
|
||||
if(form.web_path===undefined||form.web_path===null){
|
||||
form.web_path='/'
|
||||
}
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
//刷新父节点状态
|
||||
function reloadTreeChildren(parent:string|number) {
|
||||
const data = crudExpose.getBaseTableRef().store.states.treeData;
|
||||
if (data.value != null) {
|
||||
const item = data.value[parent];
|
||||
if (item != null) {
|
||||
item.loaded = false;
|
||||
item.expanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createFilter = (queryString: string) => {
|
||||
return (file: any) => {
|
||||
return file.value.toLowerCase().indexOf(queryString.toLowerCase()) !== -1;
|
||||
};
|
||||
};
|
||||
|
||||
// 获取组件地址
|
||||
const getCompoent = (queryString: string, cb: any) => {
|
||||
const files: any = import.meta.glob('@views/**/*.vue');
|
||||
let fileLists: Array<any> = [];
|
||||
Object.keys(files).forEach((queryString: string) => {
|
||||
fileLists.push({
|
||||
label: queryString.replace(/(\.\/|\.vue)/g, ''),
|
||||
value: queryString.replace(/(\.\/|\.vue)/g, ''),
|
||||
});
|
||||
});
|
||||
const results = queryString ? fileLists.filter(createFilter(queryString)) : fileLists;
|
||||
// 统一去掉/src/views/前缀
|
||||
results.forEach((val) => {
|
||||
val.label = val.label.replace('/src/views/', '');
|
||||
val.value = val.value.replace('/src/views/', '');
|
||||
});
|
||||
cb(results);
|
||||
};
|
||||
|
||||
// 验证路由地址
|
||||
const { getFormData } = crudExpose;
|
||||
const validateWebPath = (rule: any, value: string, callback: Function) => {
|
||||
let pattern = /^\/.*?/;
|
||||
let patternUrl = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
|
||||
const reg = getFormData().is_link ? patternUrl.test(value) : pattern.test(value);
|
||||
if (reg) {
|
||||
callback();
|
||||
} else {
|
||||
callback(new Error('请输入正确的地址'));
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
labelWidth: '120px',
|
||||
row: { gutter: 20 },
|
||||
},
|
||||
table: {
|
||||
lazy: true,
|
||||
load: async (row: any, treeNode: unknown, resolve: (date: any[]) => void) => {
|
||||
//懒加载,更新和删除后,需要刷新父节点的状态,见上方
|
||||
const obj = await api.GetChildren(row.id);
|
||||
resolve([...obj.data]);
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
type: 'number',
|
||||
column: {
|
||||
width: 100,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
menu_type: {
|
||||
title: '类型',
|
||||
type: 'dict-radio',
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: '目录', value: 0 },
|
||||
{ label: '菜单', value: 1 },
|
||||
{ label: '按钮', value: 2 },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value:0,
|
||||
component:{
|
||||
optionName: "el-radio-button"
|
||||
},
|
||||
valueChange({ form, value, getComponentRef }) {
|
||||
if (value) {
|
||||
getComponentRef("parent")?.reloadDict(); // 执行city的select组件的reloadDict()方法,触发“city”重新加载字典
|
||||
}
|
||||
}
|
||||
},
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
parent: {
|
||||
title: '父级',
|
||||
dict: dict({
|
||||
prototype: true,
|
||||
url({form}){
|
||||
if(form && form.menu_type===1){
|
||||
return '/api/system/menu/tree/?menu_type=0'
|
||||
}else{
|
||||
return `/api/system/menu/tree/?menu_type=1`
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
}),
|
||||
type: 'dict-select',
|
||||
form: {
|
||||
show:compute(({form})=>{
|
||||
return [1,2].includes(form.menu_type);
|
||||
}),
|
||||
rules: [{ required: true, message: '必填项' }],
|
||||
component: {},
|
||||
},
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
title: '名称',
|
||||
search: { show: true },
|
||||
type: 'text',
|
||||
form: {
|
||||
rules: [{ required: true, message: '请输入名称' }],
|
||||
component: {
|
||||
placeholder: '请输入名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
title: '图标',
|
||||
form:{
|
||||
show:compute(({form})=>{
|
||||
return [0,1].includes(form.menu_type);
|
||||
}),
|
||||
component:{
|
||||
name: shallowRef(IconSelector),
|
||||
vModel: "modelValue",
|
||||
}
|
||||
},
|
||||
column: {
|
||||
component: {
|
||||
style: 'font-size:18px',
|
||||
},
|
||||
},
|
||||
},
|
||||
sort: {
|
||||
title: '排序',
|
||||
type: 'number',
|
||||
form: {
|
||||
value: 1,
|
||||
show:compute(({form})=>{
|
||||
return [0,1].includes(form.menu_type);
|
||||
}),
|
||||
},
|
||||
},
|
||||
is_link: {
|
||||
title: '外链接',
|
||||
type: 'dict-switch',
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
show:compute(({form})=>{
|
||||
return [1].includes(form.menu_type);
|
||||
}),
|
||||
},
|
||||
},
|
||||
web_path: {
|
||||
title: '路由地址',
|
||||
form: {
|
||||
show:compute(({form})=>{
|
||||
return [1].includes(form.menu_type);
|
||||
}),
|
||||
helper: compute(({ form }) => {
|
||||
return form.is_link ? '请输入http开头的地址' : '浏览器中url的地址,请以/开头';
|
||||
}),
|
||||
rules: [{ required: true, message: '请输入路由地址', validator: validateWebPath, trigger: 'blur' }],
|
||||
component: {
|
||||
placeholder: '请输入路由地址',
|
||||
},
|
||||
},
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
component_name: {
|
||||
title: '组件名称',
|
||||
form: {
|
||||
show:compute(({form})=>{
|
||||
return [1].includes(form.menu_type);
|
||||
}),
|
||||
rules: [{ required: true, message: '请输入组件名称', trigger: 'blur' }],
|
||||
component: {
|
||||
placeholder: '请输入组件名称',
|
||||
},
|
||||
},
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
component: {
|
||||
title: compute(({ form }) => {
|
||||
return form.menu_type === 1 ? '组件地址' : '按钮权限值';
|
||||
}),
|
||||
form: {
|
||||
col:{
|
||||
span:24,
|
||||
},
|
||||
show: compute(({ form }) => {
|
||||
return [1,2].includes(form.menu_type)
|
||||
}),
|
||||
helper: compute(({ form }) => {
|
||||
return form.menu_type === 1 ? 'src/views下的文件夹地址' : '按钮权限值是唯一的标识';
|
||||
}),
|
||||
rules: [{ required: true, message: '请输入组件地址', trigger: 'blur' }],
|
||||
|
||||
component: {
|
||||
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
disabled: compute(({ form }) => {
|
||||
if(form.is_link&&form.menu_type===1){
|
||||
form.component ="无"
|
||||
return form.is_link
|
||||
}
|
||||
form.component =null
|
||||
return false
|
||||
}),
|
||||
name: compute(({ form }) => {
|
||||
return [1,2].includes(form.menu_type)? 'el-autocomplete' : 'el-input';
|
||||
}),
|
||||
triggerOnFocus: false,
|
||||
fetchSuggestions: (query, cb) => {
|
||||
return getCompoent(query, cb);
|
||||
},
|
||||
},
|
||||
},
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
visible: {
|
||||
title: '侧边可见',
|
||||
search: { show: true },
|
||||
type: 'dict-radio',
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: true,
|
||||
show:compute(({form})=>{
|
||||
return [1].includes(form.menu_type)
|
||||
}),
|
||||
},
|
||||
},
|
||||
cache: {
|
||||
title: '是否缓存',
|
||||
search: { show: true },
|
||||
type: 'dict-radio',
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
show:compute(({form})=>{
|
||||
return [1].includes(form.menu_type)
|
||||
}),
|
||||
},
|
||||
},
|
||||
frame_out: {
|
||||
title: '主框架外展示',
|
||||
search: { show: true },
|
||||
type: 'dict-radio',
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
show:compute(({form})=>{
|
||||
return [1].includes(form.menu_type)
|
||||
}),
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: '状态',
|
||||
search: { show: true },
|
||||
type: 'dict-radio',
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: '启用', value: true },
|
||||
{ label: '禁用', value: false },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: true,
|
||||
show:compute(({form})=>{
|
||||
return [0,1].includes(form.menu_type);
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,41 +1,154 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<fs-page>
|
||||
<el-row class="menu-el-row">
|
||||
<el-col :span="6">
|
||||
<div class="menu-box menu-left-box">
|
||||
<MenuTreeCom
|
||||
ref="menuTreeRef"
|
||||
:treeData="menuTreeData"
|
||||
@treeClick="handleTreeClick"
|
||||
@updateDept="handleUpdateMenu"
|
||||
@deleteDept="handleDeleteMenu"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
</fs-crud>
|
||||
<fs-form-wrapper ref="addChildrenRef" />
|
||||
</fs-page>
|
||||
<el-col :span="18">
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="按钮权限配置" >
|
||||
<div style="height: 80vh">
|
||||
<MenuButtonCom ref="menuButtonRef" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="列权限配置">
|
||||
<div style="height: 80vh">
|
||||
<MenuFieldCom ref="menuFieldRef"></MenuFieldCom>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-drawer v-model="drawerVisible" title="菜单配置" direction="rtl" size="500px" :close-on-click-modal="false" :before-close="handleDrawerClose">
|
||||
<MenuFormCom
|
||||
v-if="drawerVisible"
|
||||
:initFormData="drawerFormData"
|
||||
:cacheData="menuTreeCacheData"
|
||||
:treeData="menuTreeData"
|
||||
@drawerClose="handleDrawerClose"
|
||||
/>
|
||||
</el-drawer>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, onMounted, reactive, computed} from "vue";
|
||||
import createCrudOptions from "./crud";
|
||||
import {useExpose, useCrud, useCompute} from "@fast-crud/fast-crud";
|
||||
import {AddObj} from "./api";
|
||||
import {ElMessage } from "element-plus"
|
||||
<script lang="ts" setup name="menuPages">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import MenuTreeCom from './components/MenuTreeCom/index.vue';
|
||||
import MenuButtonCom from './components/MenuButtonCom/index.vue';
|
||||
import MenuFormCom from './components/MenuFormCom/index.vue';
|
||||
import MenuFieldCom from './components/MenuFieldCom/index.vue';
|
||||
import { GetList, DelObj } from './api';
|
||||
import { successNotification } from '/@/utils/message';
|
||||
import { APIResponseData, MenuTreeItemType } from './types';
|
||||
|
||||
let menuTreeData = ref([]);
|
||||
let menuTreeCacheData = ref<MenuTreeItemType[]>([]);
|
||||
let drawerVisible = ref(false);
|
||||
let drawerFormData = ref<Partial<MenuTreeItemType>>({});
|
||||
let menuTreeRef = ref<InstanceType<typeof MenuTreeCom> | null>(null);
|
||||
let menuButtonRef = ref<InstanceType<typeof MenuButtonCom> | null>(null);
|
||||
let menuFieldRef = ref<InstanceType<typeof MenuFieldCom> | null>(null);
|
||||
const getData = () => {
|
||||
GetList({}).then((ret: APIResponseData) => {
|
||||
const responseData = ret.data;
|
||||
const result = XEUtils.toArrayTree(responseData, {
|
||||
parentKey: 'parent',
|
||||
children: 'children',
|
||||
strict: true,
|
||||
});
|
||||
menuTreeData.value = result;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 菜单的点击事件
|
||||
*/
|
||||
const handleTreeClick = (record: MenuTreeItemType) => {
|
||||
menuButtonRef.value?.handleRefreshTable(record);
|
||||
menuFieldRef.value?.handleRefreshTable(record)
|
||||
};
|
||||
|
||||
// crud组件的ref
|
||||
const crudRef = ref();
|
||||
// crud 配置的ref
|
||||
const crudBinding = ref();
|
||||
// 暴露的方法
|
||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||
/**
|
||||
* 部门的 新增 or 编辑 事件
|
||||
*/
|
||||
const handleUpdateMenu = (type: string, record?: MenuTreeItemType) => {
|
||||
if (type === 'update' && record) {
|
||||
const parentData = menuTreeRef.value?.treeRef?.currentNode.parent.data || {};
|
||||
menuTreeCacheData.value = [parentData];
|
||||
drawerFormData.value = record;
|
||||
}
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const handleDrawerClose = (type?: string) => {
|
||||
if (type === 'submit') {
|
||||
getData();
|
||||
}
|
||||
drawerVisible.value = false;
|
||||
drawerFormData.value = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* 部门的删除事件
|
||||
*/
|
||||
const handleDeleteMenu = (id: string, callback: Function) => {
|
||||
ElMessageBox.confirm('您确认删除该菜单项吗?', '温馨提示', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(async () => {
|
||||
const res: APIResponseData = await DelObj(id);
|
||||
callback();
|
||||
if (res?.code === 2000) {
|
||||
successNotification(res.msg as string);
|
||||
getData();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose });
|
||||
// 初始化crud配置
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
|
||||
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
|
||||
// 你可以调用此方法,重新初始化crud配置
|
||||
// resetCrudOptions(options)
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
getData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-el-row {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.el-col {
|
||||
height: 100%;
|
||||
padding: 10px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-box {
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.menu-left-box {
|
||||
position: relative;
|
||||
border-radius: 0 8px 8px 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.menu-right-box {
|
||||
border-radius: 8px 0 0 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { dict, useCompute, PageQuery, AddReq, DelReq, EditReq, CrudExpose, CrudO
|
||||
import tableSelector from '/@/components/tableSelector/index.vue';
|
||||
import {shallowRef, computed, ref, inject} from 'vue';
|
||||
import manyToMany from '/@/components/manyToMany/index.vue';
|
||||
|
||||
import {auth} from '/@/utils/authFunction'
|
||||
const { compute } = useCompute();
|
||||
|
||||
interface CreateCrudOptionsTypes {
|
||||
@@ -36,8 +36,6 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
||||
return tabActivted.value === 'receive';
|
||||
});
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject("$hasPermissions")
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
@@ -47,6 +45,15 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar:{
|
||||
buttons:{
|
||||
add:{
|
||||
show:computed(() =>{
|
||||
return tabActivted.value !== 'receive' && auth('messageCenter:Create');
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
fixed:'right',
|
||||
width:150,
|
||||
@@ -58,7 +65,7 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
||||
text:"查看",
|
||||
type:'text',
|
||||
iconRight:'View',
|
||||
show:hasPermissions("messageCenter:Search"),
|
||||
show:auth("messageCenter:Search"),
|
||||
click({ index, row }) {
|
||||
crudExpose.openView({ index, row });
|
||||
if (tabActivted.value === 'receive') {
|
||||
@@ -70,7 +77,7 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show:hasPermissions('messageCenter:Delete')
|
||||
show:auth('messageCenter:Delete')
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
import XEUtils from "xe-utils";
|
||||
|
||||
export const apiPrefix = '/api/system/role_api_permission/';
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据范围授权的所有部门
|
||||
* @param query
|
||||
* @constructor
|
||||
*/
|
||||
export function GetAllDeptData(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix+'role_to_dept_all/',
|
||||
method: 'get',
|
||||
params: query,
|
||||
}).then((res:any)=>{
|
||||
return XEUtils.toArrayTree(res.data,{ parentKey: 'parent', key: 'id', children: 'children',})
|
||||
})
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { request } from '/@/utils/service';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {inject} from "vue";
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose,propsContext }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
form.role = row.role;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
form.role = propsContext.roleId
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject("$hasPermissions")
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
},
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
labelWidth: '110px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
width: '600px',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
//@ts-ignore
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination: any = crudExpose!.crudBinding.value.pagination;
|
||||
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
name:{
|
||||
title: '接口名称',
|
||||
type: 'text',
|
||||
search:{
|
||||
show:true
|
||||
},
|
||||
column:{
|
||||
width:150
|
||||
}
|
||||
},
|
||||
method: {
|
||||
title: '请求方式',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
show:true
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: [
|
||||
{
|
||||
label: 'GET',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: 'POST',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: 'PUT',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: 'DELETE',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: 'PATCH',
|
||||
value: 4,
|
||||
},
|
||||
],
|
||||
}),
|
||||
column:{
|
||||
width: 100,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
api: {
|
||||
title: '接口地址',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
async getData(dict: any) {
|
||||
return request('/swagger.json').then((ret: any) => {
|
||||
const res = Object.keys(ret.paths);
|
||||
const data = [];
|
||||
for (const item of res) {
|
||||
const obj = { label: '', value: '' };
|
||||
obj.label = item;
|
||||
obj.value = item;
|
||||
data.push(obj);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column:{
|
||||
minWidth: 200,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 24,
|
||||
props: {
|
||||
allowCreate: true,
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
helper: {
|
||||
position: 'label',
|
||||
tooltip: {
|
||||
placement: 'top-start',
|
||||
},
|
||||
text: '请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/',
|
||||
},
|
||||
},
|
||||
},
|
||||
data_range: {
|
||||
title: '数据权限范围',
|
||||
search: {
|
||||
show:true
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict:dict({
|
||||
url:'/api/system/role_api_permission/data_scope/'
|
||||
}),
|
||||
column: {
|
||||
minWidth:120,
|
||||
},
|
||||
|
||||
},
|
||||
dept:{
|
||||
title:'数据权限部门',
|
||||
column:{
|
||||
minWidth:120,
|
||||
cellRender(scope){
|
||||
return <div>{scope.row.dept_name}</div>
|
||||
}
|
||||
},
|
||||
form:{
|
||||
show: compute(({form})=>{
|
||||
return form.data_range===4
|
||||
})
|
||||
}
|
||||
},
|
||||
description:{
|
||||
title:'描述',
|
||||
type:'textarea',
|
||||
column:{
|
||||
width:300
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<div style="height: 86vh">
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #form_dept="scope">
|
||||
<div>
|
||||
<el-tree-select
|
||||
v-model="scope.form.dept"
|
||||
:data="allDeptData"
|
||||
:props="treeProps"
|
||||
node-key="id"
|
||||
multiple
|
||||
:render-after-expand="false"
|
||||
show-checkbox
|
||||
check-strictly
|
||||
check-on-click-node
|
||||
>
|
||||
<template #default="{ data: { name } }">
|
||||
{{ name }}
|
||||
</template
|
||||
>
|
||||
</el-tree-select>
|
||||
</div>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, onMounted, reactive} from 'vue';
|
||||
import {useFs} from '@fast-crud/fast-crud';
|
||||
import {createCrudOptions} from './crud';
|
||||
import {GetAllDeptData} from './api'
|
||||
const propsContext = defineProps({
|
||||
roleId:{
|
||||
type:String||Number,
|
||||
required:true
|
||||
}
|
||||
})
|
||||
const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions,propsContext});
|
||||
|
||||
// 获取所有部门数据
|
||||
const allDeptData = ref<any[]>([]);
|
||||
const treeProps ={
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
children: 'children',
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const res = await GetAllDeptData({role:propsContext.roleId});
|
||||
allDeptData.value = res;
|
||||
crudExpose.doSearch({form:{role:propsContext.roleId}})
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,208 +0,0 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { request } from '/@/utils/service';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage, successNotification, warningNotification } from '/@/utils/message';
|
||||
import { inject } from 'vue';
|
||||
import { automatchColumnsData } from '/@/views/system/columns/components/ColumnsTableCom/api';
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose, props }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
form.role = props.role;
|
||||
form.model = props.model;
|
||||
form.model = props.app;
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
/**
|
||||
* 自动匹配列
|
||||
*/
|
||||
const handleAutomatch = async () => {
|
||||
if (props.role && props.model && props.app) {
|
||||
const res = await automatchColumnsData(props);
|
||||
if (res?.code === 2000) {
|
||||
successNotification('匹配成功');
|
||||
}
|
||||
crudExpose.doSearch({ form: { role: props.role, model: props.model, app: props.app } });
|
||||
}
|
||||
warningNotification('请选择角色和模型表!');
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject('$hasPermissions');
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
auto: {
|
||||
text: '自动匹配',
|
||||
type: 'success',
|
||||
click: () => {
|
||||
return handleAutomatch();
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
},
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
labelWidth: '110px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
width: '600px',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
//@ts-ignore
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination: any = crudExpose!.crudBinding.value.pagination;
|
||||
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
field_name: {
|
||||
title: '字段名',
|
||||
type: 'text',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
column: {
|
||||
width: 150,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
title: '中文名',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'text',
|
||||
column: {
|
||||
width: 100,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
is_create: {
|
||||
title: '创建时显示',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-switch',
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: true, label: '启用' },
|
||||
{ value: false, label: '禁用' },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: true,
|
||||
},
|
||||
column: {
|
||||
valueChange(context){
|
||||
return api.UpdateObj(context.row)
|
||||
},
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
},
|
||||
},
|
||||
},
|
||||
is_update: {
|
||||
title: '编辑时显示',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-switch',
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: true, label: '启用' },
|
||||
{ value: false, label: '禁用' },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: true,
|
||||
},
|
||||
column: {
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
onChange: compute((context) => {
|
||||
//动态onChange方法测试
|
||||
return () => {
|
||||
console.log('onChange', context.row.switch);
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
is_query: {
|
||||
title: '列表中显示',
|
||||
type: 'dict-switch',
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: true, label: '启用' },
|
||||
{ value: false, label: '禁用' },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: true,
|
||||
},
|
||||
column: {
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
onChange: compute((context) => {
|
||||
//动态onChange方法测试
|
||||
return () => {
|
||||
console.log('onChange', context.row.switch);
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-divider>模型表</el-divider>
|
||||
<div class="model-card">
|
||||
<div v-for="(item,index) in allModelData" :key="index">
|
||||
<el-text @click="onModelChecked(item,index)" :type="modelCheckIndex===index?'primary':''">{{item.app + '--'+item.title + '('+item.key+')' }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider>字段权限</el-divider>
|
||||
<div style="height: 50vh">
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
</fs-crud>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, onMounted, reactive} from 'vue';
|
||||
import {useFs} from '@fast-crud/fast-crud';
|
||||
import {createCrudOptions} from './crud';
|
||||
import {getModelList} from './api'
|
||||
const propsContext = defineProps({
|
||||
roleId:{
|
||||
type:String||Number,
|
||||
required:true
|
||||
}
|
||||
})
|
||||
|
||||
const props = reactive({
|
||||
role: '',
|
||||
model: '',
|
||||
app: '',
|
||||
menu:''
|
||||
})
|
||||
|
||||
|
||||
|
||||
// 获取所有model
|
||||
const allModelData = ref<any[]>([]);
|
||||
const modelCheckIndex=ref(-1)
|
||||
const onModelChecked = (row,index)=>{
|
||||
modelCheckIndex.value = index
|
||||
props.model = row.key
|
||||
props.app = row.app
|
||||
props.role=propsContext.roleId
|
||||
crudExpose.setTableData([])
|
||||
crudExpose.doSearch({form:{role:propsContext.roleId,model:row.key,app:row.app}})
|
||||
}
|
||||
|
||||
const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions,props});
|
||||
onMounted(async () => {
|
||||
const res = await getModelList();
|
||||
allModelData.value = res.data;
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.model-card{
|
||||
height: 20vh;
|
||||
overflow-y: scroll;
|
||||
div{
|
||||
margin: 15px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,212 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<MenuPermissionTree
|
||||
ref="permissionTreeRef"
|
||||
:tree="menuPermissionTreeData"
|
||||
:default-expand-all="true"
|
||||
:editable="false"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
:props="{ label: 'title' }"></MenuPermissionTree>
|
||||
<div style="margin-top: 2em">
|
||||
<el-button type="primary" @click="updatePermission">确定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, onMounted, defineProps, watch, computed, reactive,nextTick} from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import {errorNotification} from '/@/utils/message';
|
||||
import {getDataPermissionRange, getDataPermissionDept, getMenuPremissionTree, saveMenuPremission,getMenuPremissionChecked} from './api';
|
||||
import {MenuDataType, DataPermissionRangeType, CustomDataPermissionDeptType} from './types';
|
||||
import {ElMessage} from 'element-plus'
|
||||
import MenuPermissionTree from "./menuPermissionTree.vue";
|
||||
|
||||
const props = defineProps({
|
||||
roleId: {
|
||||
type: Number,
|
||||
default: -1
|
||||
}
|
||||
})
|
||||
|
||||
//获取菜单/按钮权限
|
||||
const permissionTreeRef = ref();
|
||||
let menuPermissionTreeData = ref<MenuDataType[]>([]);
|
||||
const getMenuPremissionTreeData = async () => {
|
||||
const resMenu = await getMenuPremissionTree({role: props.roleId})
|
||||
menuPermissionTreeData.value = resMenu
|
||||
nextTick(() => {
|
||||
updateChecked(props.roleId);
|
||||
});
|
||||
}
|
||||
|
||||
// 如果勾选节点中存在非叶子节点,tree组件会将其所有子节点全部勾选
|
||||
// 所以要找出所有叶子节点,仅勾选叶子节点,tree组件会将父节点同步勾选
|
||||
function getAllCheckedLeafNodeId(tree, checkedIds, temp) {
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const item = tree[i];
|
||||
if (item.children && item.children.length !== 0) {
|
||||
getAllCheckedLeafNodeId(item.children, checkedIds, temp);
|
||||
} else {
|
||||
if (checkedIds.indexOf(item.id) !== -1) {
|
||||
temp.push(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
async function updateChecked(roleId:string|number) {
|
||||
let checkedIds = await getMenuPremissionChecked({role: roleId});
|
||||
// 找出所有的叶子节点
|
||||
checkedIds = getAllCheckedLeafNodeId(menuPermissionTreeData.value, checkedIds, []);
|
||||
permissionTreeRef.value.setCheckedKeys(checkedIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新菜单权限
|
||||
*/
|
||||
async function updatePermission() {
|
||||
const roleId = props.roleId;
|
||||
const { checked, halfChecked } = permissionTreeRef.value.getChecked();
|
||||
const allChecked = [...checked, ...halfChecked];
|
||||
const menuIds = allChecked.filter(item=>item !== -1)
|
||||
await saveMenuPremission({role: roleId, menu: menuIds})
|
||||
handleDrawerClose();
|
||||
ElMessage.success("授权成功");
|
||||
}
|
||||
|
||||
const emit = defineEmits(['handleDrawerClose']);
|
||||
const handleDrawerClose = () => {
|
||||
emit('handleDrawerClose')
|
||||
}
|
||||
|
||||
defineExpose({getMenuPremissionTreeData})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.permission-com {
|
||||
margin: 15px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.pc-save-btn {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.pc-collapse-title {
|
||||
line-height: 32px;
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.pc-collapse-main {
|
||||
padding-top: 15px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.pccm-item {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.btn-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.columns-list {
|
||||
.width-txt {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.width-check {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.width-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.columns-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
box-sizing: border-box;
|
||||
|
||||
span {
|
||||
font-weight: 900;
|
||||
}
|
||||
}
|
||||
|
||||
.columns-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.ci-checkout {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pc-dialog {
|
||||
.dialog-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dialog-tree {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.permission-com {
|
||||
.el-collapse {
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.el-collapse-item {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.el-collapse-item__header {
|
||||
height: auto;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
border-left: 1px solid #ebeef5;
|
||||
border-right: 1px solid #ebeef5;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.el-collapse-item__header.is-active {
|
||||
border-radius: 8px 8px 0 0;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.el-collapse-item__wrap {
|
||||
padding: 15px;
|
||||
border-left: 1px solid #ebeef5;
|
||||
border-right: 1px solid #ebeef5;
|
||||
border-top: 1px solid #ebeef5;
|
||||
border-radius: 0 0 8px 8px;
|
||||
background-color: #fafafa;
|
||||
box-sizing: border-box;
|
||||
|
||||
.el-collapse-item__content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,205 +0,0 @@
|
||||
<template>
|
||||
<el-tree
|
||||
v-if="computedTree"
|
||||
ref="treeRef"
|
||||
class="fs-permission-tree"
|
||||
:class="{ 'is-editable': editable }"
|
||||
:data="computedTree"
|
||||
:props="computedProps"
|
||||
@check="onChecked"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<div :class="'node-title-pane'">
|
||||
<div class="node-title">{{ data.name }}</div>
|
||||
<div v-if="editable === true" class="node-suffix">
|
||||
<fs-icon v-if="actions.add !== false" :icon="ui.icons.add" @click.stop="add(data)" />
|
||||
<fs-icon v-if="actions.edit !== false && data.id !== -1" :icon="ui.icons.edit" @click.stop="edit(data)" />
|
||||
<fs-icon
|
||||
v-if="actions.remove !== false && data.id !== -1"
|
||||
:icon="ui.icons.remove"
|
||||
@click.stop="remove(data)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from "lodash-es";
|
||||
import { useUi, utils } from "@fast-crud/fast-crud";
|
||||
import { defineComponent, ref, computed, nextTick, onMounted } from "vue";
|
||||
export default defineComponent({
|
||||
name: "FsPermissionTree",
|
||||
props: {
|
||||
/**
|
||||
* 树形数据
|
||||
* */
|
||||
tree: {},
|
||||
/**
|
||||
* 是否可编辑
|
||||
*/
|
||||
editable: {
|
||||
default: true
|
||||
},
|
||||
actions: {
|
||||
default: {}
|
||||
},
|
||||
props: {}
|
||||
} as any,
|
||||
emits: ["add", "edit", "remove"],
|
||||
setup(props: any, ctx) {
|
||||
const treeRef = ref();
|
||||
const { ui } = useUi();
|
||||
const computedTree = computed(() => {
|
||||
if (props.tree == null) {
|
||||
return null;
|
||||
}
|
||||
const clone = _.cloneDeep(props.tree);
|
||||
utils.deepdash.forEachDeep(clone, (value, key, pNode, context) => {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (!(value instanceof Object) || value instanceof Array) {
|
||||
return;
|
||||
}
|
||||
if (value.class === "is-leaf-node") {
|
||||
//处理过,无需再次处理
|
||||
return;
|
||||
}
|
||||
if (value.children != null && value.children.length > 0) {
|
||||
return;
|
||||
}
|
||||
const parents = context.parents;
|
||||
if (parents.length < 2) {
|
||||
return;
|
||||
}
|
||||
const parent = parents[parents.length - 2].value;
|
||||
//看parent下面的children,是否全部都没有children
|
||||
for (const child of parent.children) {
|
||||
if (child.children != null && child.children.length > 0) {
|
||||
//存在child有children
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 所有的子节点都没有children
|
||||
parent.class = "is-twig-node"; // 连接叶子节点的末梢枝杈节点
|
||||
for (const child of parent.children) {
|
||||
child.class = "is-leaf-node";
|
||||
}
|
||||
});
|
||||
console.log("nodes ", clone);
|
||||
return [
|
||||
{
|
||||
name: "根节点",
|
||||
id: -1,
|
||||
children: clone
|
||||
}
|
||||
];
|
||||
});
|
||||
function add(data) {
|
||||
ctx.emit("add", data);
|
||||
}
|
||||
function edit(data) {
|
||||
ctx.emit("edit", data);
|
||||
}
|
||||
function remove(data) {
|
||||
ctx.emit("remove", data);
|
||||
}
|
||||
function onChecked(a, b, c) {
|
||||
console.log("chedcked", a, b, c);
|
||||
}
|
||||
function getChecked() {
|
||||
const checked = treeRef.value.getCheckedKeys();
|
||||
const halfChecked = treeRef.value.getHalfCheckedKeys();
|
||||
return {
|
||||
checked,
|
||||
halfChecked
|
||||
};
|
||||
}
|
||||
|
||||
function setCheckedKeys(ids) {
|
||||
treeRef.value.setCheckedKeys(ids);
|
||||
}
|
||||
function customNodeClass(data) {
|
||||
if (data.class) {
|
||||
return data.class;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const computedProps = computed(() => {
|
||||
return _.merge({ class: customNodeClass }, props.props);
|
||||
});
|
||||
return {
|
||||
computedTree,
|
||||
add,
|
||||
edit,
|
||||
remove,
|
||||
treeRef,
|
||||
onChecked,
|
||||
getChecked,
|
||||
computedProps,
|
||||
setCheckedKeys,
|
||||
ui
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.fs-permission-tree {
|
||||
height: 80vh;
|
||||
overflow-y: scroll;
|
||||
.el-tree-node.is-expanded.is-twig-node > .el-tree-node__children {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.is-twig-node > .el-tree-node__children > :not(:first-child) .el-tree-node__content {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.el-tree-node__content {
|
||||
box-sizing: content-box;
|
||||
padding: 5px;
|
||||
}
|
||||
.is-leaf-node {
|
||||
//&::before {
|
||||
// display: none;
|
||||
//}
|
||||
}
|
||||
|
||||
.node-title-pane {
|
||||
display: flex;
|
||||
.node-title {
|
||||
width: 100px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-editable {
|
||||
.el-tree-node__content {
|
||||
&:hover {
|
||||
.node-suffix {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.node-suffix {
|
||||
margin-right: 5px;
|
||||
visibility: hidden;
|
||||
i {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
> * {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,49 +1,18 @@
|
||||
import { request } from "/@/utils/service";
|
||||
import XEUtils from 'xe-utils';
|
||||
|
||||
/**
|
||||
* 获取菜单树
|
||||
* 获取角色的授权列表
|
||||
* @param roleId
|
||||
* @param query
|
||||
*/
|
||||
export function getMenuPremissionTree(query:object) {
|
||||
export function getRolePremission(query:object) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_permission/menu_permission_tree/',
|
||||
url: '/api/system/role_menu_button_permission/get_role_premission/',
|
||||
method: 'get',
|
||||
params:query
|
||||
}).then((res:any)=>{
|
||||
return XEUtils.toArrayTree(res.data,{ parentKey: 'parent_id', key: 'id', children: 'children',})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已授权的菜单
|
||||
* @param query
|
||||
*/
|
||||
export function getMenuPremissionChecked(query:object) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_permission/get_menu_permission_checked/',
|
||||
method: 'get',
|
||||
params:query
|
||||
}).then((res:any)=>{
|
||||
return res.data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存菜单权限
|
||||
*/
|
||||
export function saveMenuPremission(data:object) {
|
||||
return request({
|
||||
url:'/api/system/role_menu_permission/save_menu_permission/',
|
||||
method:'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***
|
||||
* 设置角色的权限
|
||||
* @param roleId
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" title="权限配置" direction="rtl" size="60%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
:before-close="handleDrawerClose">
|
||||
<el-drawer v-model="drawerVisible" title="权限配置" direction="rtl" size="60%" :close-on-click-modal="false"
|
||||
:before-close="handleDrawerClose"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<template #header>
|
||||
<el-row>
|
||||
<el-col :span="4">
|
||||
@@ -10,32 +10,118 @@
|
||||
<el-tag>{{ props.roleName }}</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-button size="small" type="primary" class="pc-save-btn" @click="handleSavePermission">保存菜单授权
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<div>
|
||||
<el-tabs type="border-card" v-model="permissionTab">
|
||||
<el-tab-pane label="菜单/按钮授权" name="menu">
|
||||
<MenuPermission ref="menuPermissionRef" :role-id="props.roleId" @handleDrawerClose="handleDrawerClose"></MenuPermission>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="请求接口授权" name="api">
|
||||
<ApiPermission v-if="permissionTab==='api'" :role-id="props.roleId"></ApiPermission>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="表单字段授权" name="field">
|
||||
<FieldPermission v-if="permissionTab==='field'" :role-id="props.roleId"></FieldPermission>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div class="permission-com">
|
||||
<el-collapse v-model="collapseCurrent" @change="handleCollapseChange" accordion>
|
||||
<el-collapse-item v-for="(item,mIndex) in menuData" :key="mIndex" :name="mIndex"
|
||||
style=" background-color: #fafafa;">
|
||||
<template #title>
|
||||
<div>
|
||||
<div class="pc-collapse-title">
|
||||
<el-checkbox v-model="item.isCheck" @click.stop="null">
|
||||
<span>{{ item.name }}</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<div v-show="!collapseCurrent.includes(mIndex)" @click.stop="null" style="text-align: left;">
|
||||
<el-checkbox v-for="btn in item.btns" :key="btn.value" :label="btn.value" v-model="btn.isCheck">
|
||||
{{ btn.name }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="pc-collapse-main">
|
||||
<div class="pccm-item">
|
||||
<p>允许对这些数据有以下操作</p>
|
||||
<el-checkbox v-for="(btn,bIndex) in item.btns" :key="bIndex" v-model="btn.isCheck" :label="btn.value">
|
||||
<div class="btn-item">
|
||||
{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
|
||||
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(item, btn.id)">
|
||||
<el-icon><Setting/></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="pccm-item">
|
||||
<p>对这些数据有以下字段权限</p>
|
||||
|
||||
<ul class="columns-list">
|
||||
<li class="columns-head">
|
||||
<div class="width-txt">
|
||||
<span>字段</span>
|
||||
</div>
|
||||
|
||||
<div v-for="(head,hIndex) in column.header" :key="hIndex" class="width-check">
|
||||
<el-checkbox :label="head.value" @change="handleColumnChange($event, item, head.value)">
|
||||
<span>{{ head.label }}</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li v-for="(c_item, c_index) in item.columns" :key="c_index" class="columns-item">
|
||||
<div class="width-txt">{{ c_item.title }}</div>
|
||||
<div v-for="(col,cIndex) in column.header" :key="cIndex" class="width-check">
|
||||
<el-checkbox v-model="c_item[col.value]" class="ci-checkout"></el-checkbox>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="数据权限配置" width="400px" :close-on-click-modal="false"
|
||||
:before-close="handleDialogClose">
|
||||
<div class="pc-dialog">
|
||||
<el-select v-model="dataPermission" @change="handlePermissionRangeChange" class="dialog-select"
|
||||
placeholder="请选择">
|
||||
<el-option v-for="item in dataPermissionRange" :key="item.value" :label="item.label" :value="item.value"/>
|
||||
</el-select>
|
||||
<el-tree-select
|
||||
v-show="dataPermission === 4"
|
||||
node-key="id"
|
||||
v-model="customDataPermission"
|
||||
:props="defaultTreeProps"
|
||||
:data="deptData"
|
||||
multiple
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
show-checkbox
|
||||
class="dialog-tree"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div>
|
||||
<el-button type="primary" @click="handleDialogConfirm"> 确定</el-button>
|
||||
<el-button @click="handleDialogClose"> 取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, onMounted, defineProps, watch, computed, reactive,nextTick} from 'vue';
|
||||
import {ref, onMounted, defineProps, watch, computed, reactive} from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import {errorNotification} from '/@/utils/message';
|
||||
import {
|
||||
getDataPermissionRange,
|
||||
getDataPermissionDept,
|
||||
getRolePremission,
|
||||
setRolePremission,
|
||||
setBtnDatarange
|
||||
} from './api';
|
||||
import {MenuDataType, DataPermissionRangeType, CustomDataPermissionDeptType} from './types';
|
||||
import {ElMessage} from 'element-plus'
|
||||
import MenuPermission from "./MenuPermission/index.vue";
|
||||
import ApiPermission from "./ApiPermission/index.vue";
|
||||
import FieldPermission from "./FieldPermission/index.vue";
|
||||
|
||||
const props = defineProps({
|
||||
roleId: {
|
||||
type: Number,
|
||||
@@ -51,87 +137,46 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:drawerVisible'])
|
||||
const menuPermissionRef = ref()
|
||||
const permissionTab = ref('menu')
|
||||
|
||||
const drawerVisible = ref(false)
|
||||
watch(
|
||||
() => props.drawerVisible,
|
||||
(val) => {
|
||||
drawerVisible.value = val;
|
||||
nextTick(()=>{
|
||||
menuPermissionRef.value.getMenuPremissionTreeData()
|
||||
})
|
||||
// fetchData()
|
||||
getMenuBtnPermission()
|
||||
fetchData()
|
||||
}
|
||||
);
|
||||
const handleDrawerClose = () => {
|
||||
permissionTab.value ='menu'
|
||||
emit('update:drawerVisible', false);
|
||||
}
|
||||
|
||||
|
||||
const defaultTreeProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
};
|
||||
|
||||
|
||||
let menuData = ref<MenuDataType[]>([]);
|
||||
let collapseCurrent = ref(['1']);
|
||||
let menuCurrent = ref<Partial<MenuDataType>>({});
|
||||
let menuBtnCurrent = ref<number>(-1);
|
||||
let dialogVisible = ref(false);
|
||||
let dataPermissionRange = ref<DataPermissionRangeType[]>([]);
|
||||
const formatDataRange = computed(() => {
|
||||
return function(datarange:number){
|
||||
return function (datarange: number) {
|
||||
const findItem = dataPermissionRange.value.find((i) => i.value === datarange);
|
||||
return findItem?.label || ''
|
||||
return findItem?.label || ''
|
||||
}
|
||||
})
|
||||
let deptData = ref<CustomDataPermissionDeptType[]>([]);
|
||||
let dataPermission = ref();
|
||||
let customDataPermission = ref([]);
|
||||
//获取菜单/按钮权限
|
||||
const permissionTreeRef = ref();
|
||||
let menuPermissionTreeData = ref<MenuDataType[]>([]);
|
||||
const getMenuPremissionTreeData = async () => {
|
||||
const resMenu = await getMenuPremissionTree({role: props.roleId})
|
||||
menuPermissionTreeData.value = resMenu
|
||||
nextTick(() => {
|
||||
updateChecked(props.roleId);
|
||||
});
|
||||
}
|
||||
|
||||
// 如果勾选节点中存在非叶子节点,tree组件会将其所有子节点全部勾选
|
||||
// 所以要找出所有叶子节点,仅勾选叶子节点,tree组件会将父节点同步勾选
|
||||
function getAllCheckedLeafNodeId(tree, checkedIds, temp) {
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const item = tree[i];
|
||||
if (item.children && item.children.length !== 0) {
|
||||
getAllCheckedLeafNodeId(item.children, checkedIds, temp);
|
||||
} else {
|
||||
if (checkedIds.indexOf(item.id) !== -1) {
|
||||
temp.push(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
async function updateChecked(roleId:string|number) {
|
||||
let checkedIds = await getMenuPremissionChecked({role: roleId});
|
||||
// 找出所有的叶子节点
|
||||
checkedIds = getAllCheckedLeafNodeId(menuPermissionTreeData.value, checkedIds, []);
|
||||
permissionTreeRef.value.setCheckedKeys(checkedIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新菜单权限
|
||||
*/
|
||||
async function updatePermission() {
|
||||
const roleId = props.roleId;
|
||||
const { checked, halfChecked } = permissionTreeRef.value.getChecked();
|
||||
const allChecked = [...checked, ...halfChecked];
|
||||
const menuIds = allChecked.filter(item=>item !== -1)
|
||||
await saveMenuPremission({role: roleId, menu: menuIds})
|
||||
handleDrawerClose();
|
||||
//await updateChecked(roleId);
|
||||
|
||||
ElMessage.success("授权成功");
|
||||
//获取菜单,按钮,权限
|
||||
const getMenuBtnPermission = async () => {
|
||||
const resMenu = await getRolePremission({role: props.roleId})
|
||||
menuData.value = resMenu.data
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
@@ -190,7 +235,7 @@ const handleDialogConfirm = () => {
|
||||
if (btn.id === menuBtnCurrent.value) {
|
||||
const findItem = dataPermissionRange.value.find((i) => i.value === dataPermission.value);
|
||||
btn.data_range = findItem?.value || 0;
|
||||
if(btn.data_range===4){
|
||||
if (btn.data_range === 4) {
|
||||
btn.dept = customDataPermission.value
|
||||
}
|
||||
}
|
||||
@@ -216,7 +261,10 @@ const handleSavePermission = () => {
|
||||
}
|
||||
|
||||
const column = reactive({
|
||||
header:[{value:'is_create',label:'新增可见'},{value:'is_update',label:'编辑可见'},{value:'is_query',label:'列表可见'}]
|
||||
header: [{value: 'is_create', label: '新增可见'}, {value: 'is_update', label: '编辑可见'}, {
|
||||
value: 'is_query',
|
||||
label: '列表可见'
|
||||
}]
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
@@ -234,6 +282,7 @@ onMounted(() => {
|
||||
|
||||
.pc-collapse-title {
|
||||
line-height: 32px;
|
||||
text-align: left;
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
@@ -327,6 +376,7 @@ onMounted(() => {
|
||||
border-left: 1px solid #ebeef5;
|
||||
border-right: 1px solid #ebeef5;
|
||||
box-sizing: border-box;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.el-collapse-item__header.is-active {
|
||||
@@ -3,7 +3,7 @@ import * as api from './api';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { columnPermission } from '../../../utils/columnPermission';
|
||||
import { successMessage } from '../../../utils/message';
|
||||
|
||||
import {auth} from '/@/utils/authFunction'
|
||||
interface CreateCrudOptionsTypes {
|
||||
output: any;
|
||||
crudOptions: CrudOptions;
|
||||
@@ -14,12 +14,10 @@ export const createCrudOptions = function ({
|
||||
crudExpose,
|
||||
rolePermission,
|
||||
handleDrawerOpen,
|
||||
hasPermissions,
|
||||
}: {
|
||||
crudExpose: CrudExpose;
|
||||
rolePermission: any;
|
||||
handleDrawerOpen: Function;
|
||||
hasPermissions: Function;
|
||||
}): CreateCrudOptionsTypes {
|
||||
const pageRequest = async (query: any) => {
|
||||
return await api.GetList(query);
|
||||
@@ -47,6 +45,13 @@ export const createCrudOptions = function ({
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('role:Create')
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
@@ -56,15 +61,19 @@ export const createCrudOptions = function ({
|
||||
show: true,
|
||||
},
|
||||
edit: {
|
||||
show: hasPermissions('role:update'),
|
||||
show: auth('role:Update'),
|
||||
},
|
||||
remove: {
|
||||
show: hasPermissions('role:delete'),
|
||||
show: auth('role:Delete'),
|
||||
},
|
||||
customNew: {
|
||||
permission: {
|
||||
type: 'primary',
|
||||
text: '授权',
|
||||
// show: hasPermissions('role:Update'),
|
||||
text: '权限配置',
|
||||
show: auth('role:Permission'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '权限配置',
|
||||
},
|
||||
click: (context: any): void => {
|
||||
const { row } = context;
|
||||
handleDrawerOpen(row);
|
||||
@@ -107,9 +116,9 @@ export const createCrudOptions = function ({
|
||||
sortable: 'custom',
|
||||
show: columnPermission('name', 'is_query'),
|
||||
},
|
||||
addForm: {
|
||||
show: columnPermission('name', 'is_create'),
|
||||
},
|
||||
// addForm: {
|
||||
// show: columnPermission('name', 'is_create'),
|
||||
// },
|
||||
editForm: {
|
||||
show: columnPermission('name', 'is_update'),
|
||||
},
|
||||
@@ -150,7 +159,6 @@ export const createCrudOptions = function ({
|
||||
column: {
|
||||
minWidth: 90,
|
||||
sortable: 'custom',
|
||||
show: columnPermission('sort', 'is_query'),
|
||||
},
|
||||
addForm: {
|
||||
show: columnPermission('sort', 'is_create'),
|
||||
@@ -167,9 +175,6 @@ export const createCrudOptions = function ({
|
||||
title: '状态',
|
||||
search: { show: true },
|
||||
type: 'dict-radio',
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
@@ -192,7 +197,10 @@ export const createCrudOptions = function ({
|
||||
},
|
||||
editForm: {
|
||||
show: columnPermission('status', 'is_update'),
|
||||
}
|
||||
},
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
},
|
||||
update_datetime: {
|
||||
title: '更新时间',
|
||||
|
||||
@@ -6,19 +6,21 @@
|
||||
</template>
|
||||
</fs-crud>
|
||||
|
||||
<permission ref="rolePermission"></permission>
|
||||
|
||||
<PermissionComNew v-model:drawerVisible="drawerVisible" :roleId="roleId" :roleName="roleName" @drawerClose="handleDrawerClose" />
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="role">
|
||||
import { ref, onMounted, inject } from 'vue';
|
||||
import {ref, onMounted, inject, onBeforeUpdate} from 'vue';
|
||||
import { useColumnPermission } from '/@/stores/columnPermission';
|
||||
import { GetPermission } from './api';
|
||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
import permission from './components/PermissionCom/index.vue';
|
||||
import PermissionComNew from './components/index.vue';
|
||||
|
||||
import PermissionComNew from './components/PermissionComNew/index.vue';
|
||||
import _ from "lodash-es";
|
||||
import {columnPermission} from "/@/utils/columnPermission";
|
||||
let drawerVisible = ref(false);
|
||||
let roleId = ref(null);
|
||||
let roleName = ref(null);
|
||||
@@ -29,11 +31,11 @@ const crudRef = ref();
|
||||
// crud 配置的ref
|
||||
const crudBinding = ref();
|
||||
|
||||
const hasPermissions: any = inject('$hasPermissions');
|
||||
|
||||
const fetchColumnPermission = async () => {
|
||||
const res = await GetPermission();
|
||||
useColumnPermission().setPermissionData(res.data);
|
||||
console.log(3333,res)
|
||||
};
|
||||
|
||||
const handleDrawerOpen = (row: any) => {
|
||||
@@ -47,22 +49,53 @@ const handleDrawerClose = () => {
|
||||
};
|
||||
|
||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||
const handlecolumnPermission = async (crudOptions:any)=>{
|
||||
const res = await GetPermission();
|
||||
const columns = crudOptions.columns;
|
||||
for(let col in columns){
|
||||
for(let i in res.data){
|
||||
if(res.data[i].field_name === col){
|
||||
columns[col].column.show = i['is_query']
|
||||
columns[col].addForm = {
|
||||
show:i['is_create']
|
||||
}
|
||||
columns[col].editForm = {
|
||||
show:i['is_update']
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose, rolePermission, handleDrawerOpen });
|
||||
|
||||
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(async () => {
|
||||
await fetchColumnPermission();
|
||||
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose, rolePermission, handleDrawerOpen, hasPermissions });
|
||||
|
||||
// 初始化crud配置
|
||||
const { resetCrudOptions } = useCrud({
|
||||
crudExpose,
|
||||
crudOptions,
|
||||
context: {},
|
||||
});
|
||||
onMounted( async () => {
|
||||
|
||||
await handlecolumnPermission(crudOptions)
|
||||
// //合并新的crudOptions
|
||||
// const newOptions = _.merge(crudOptions, {
|
||||
// columns: {
|
||||
// text: {
|
||||
// title: "追加字段",
|
||||
// type: "text"
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//重置crudBinding
|
||||
// resetCrudOptions(newOptions);
|
||||
// 初始化crud配置
|
||||
const { resetCrudOptions } = useCrud({
|
||||
crudExpose,
|
||||
crudOptions,
|
||||
context: {},
|
||||
});
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
defineExpose(rolePermission);
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOption
|
||||
import { request } from '/@/utils/service';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import { inject } from 'vue';
|
||||
import { auth } from '/@/utils/authFunction';
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
@@ -24,8 +24,6 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
return await api.exportData(query)
|
||||
}
|
||||
|
||||
//权限判定
|
||||
const hasPermissions:any = inject('$hasPermissions');
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
@@ -43,8 +41,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: hasPermissions('user:Create')
|
||||
// show:true
|
||||
show: auth('user:Create')
|
||||
},
|
||||
export:{
|
||||
text:"导出",//按钮文字
|
||||
@@ -66,17 +63,17 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: hasPermissions('user:Update'),
|
||||
show: auth('user:Update'),
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: hasPermissions('user:Delete'),
|
||||
show: auth('user:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '重设密码',
|
||||
type: 'text',
|
||||
show: hasPermissions('user:ResetPassword'),
|
||||
show: auth('user:ResetPassword'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '重设密码',
|
||||
|
||||
@@ -1,237 +1,251 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { request } from '/@/utils/service';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {inject} from "vue";
|
||||
import {
|
||||
dict,
|
||||
UserPageQuery,
|
||||
AddReq,
|
||||
DelReq,
|
||||
EditReq,
|
||||
compute,
|
||||
CreateCrudOptionsProps,
|
||||
CreateCrudOptionsRet
|
||||
} from '@fast-crud/fast-crud';
|
||||
import {request} from '/@/utils/service';
|
||||
import {dictionary} from '/@/utils/dictionary';
|
||||
import {successMessage} from '/@/utils/message';
|
||||
import {auth} from '/@/utils/authFunction'
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({form, row}: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({row}: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({form}: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject("$hasPermissions")
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show:hasPermissions("api_white_list:Update")
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show:hasPermissions("api_white_list:Delete")
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
labelWidth: '110px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
width: '600px',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
//@ts-ignore
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination: any = crudExpose!.crudBinding.value.pagination;
|
||||
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入关键词',
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
method: {
|
||||
title: '请求方式',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: [
|
||||
{
|
||||
label: 'GET',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: 'POST',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: 'PUT',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: 'DELETE',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: 'PATCH',
|
||||
value: 4,
|
||||
},
|
||||
],
|
||||
}),
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
itemProps: {
|
||||
class: { yxtInput: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
url: {
|
||||
title: '接口地址',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
async getData(dict: any) {
|
||||
return request('/swagger.json').then((ret: any) => {
|
||||
const res = Object.keys(ret.paths);
|
||||
const data = [];
|
||||
for (const item of res) {
|
||||
const obj = { label: '', value: '' };
|
||||
obj.label = item;
|
||||
obj.value = item;
|
||||
data.push(obj);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column:{
|
||||
minWidth: 200,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 24,
|
||||
props: {
|
||||
elProps: {
|
||||
allowCreate: true,
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
itemProps: {
|
||||
class: { yxtInput: true },
|
||||
},
|
||||
helper: {
|
||||
position: 'label',
|
||||
tooltip: {
|
||||
placement: 'top-start',
|
||||
},
|
||||
text: '请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/',
|
||||
},
|
||||
},
|
||||
},
|
||||
enable_datasource: {
|
||||
title: '数据权限认证',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
minWidth:120,
|
||||
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'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('api_white_list:Create')
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: auth("api_white_list:Update")
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: auth("api_white_list:Delete")
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
col: {span: 24},
|
||||
labelWidth: '110px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
width: '600px',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: {show: false},
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
//@ts-ignore
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination: any = crudExpose!.crudBinding.value.pagination;
|
||||
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入关键词',
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
method: {
|
||||
title: '请求方式',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: [
|
||||
{
|
||||
label: 'GET',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: 'POST',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: 'PUT',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: 'DELETE',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: 'PATCH',
|
||||
value: 4,
|
||||
},
|
||||
],
|
||||
}),
|
||||
column: {
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
itemProps: {
|
||||
class: {yxtInput: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
url: {
|
||||
title: '接口地址',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
async getData(dict: any) {
|
||||
return request('/swagger.json').then((ret: any) => {
|
||||
const res = Object.keys(ret.paths);
|
||||
const data = [];
|
||||
for (const item of res) {
|
||||
const obj = {label: '', value: ''};
|
||||
obj.label = item;
|
||||
obj.value = item;
|
||||
data.push(obj);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column: {
|
||||
minWidth: 200,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 24,
|
||||
props: {
|
||||
elProps: {
|
||||
allowCreate: true,
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
itemProps: {
|
||||
class: {yxtInput: true},
|
||||
},
|
||||
helper: {
|
||||
position: 'label',
|
||||
tooltip: {
|
||||
placement: 'top-start',
|
||||
},
|
||||
text: '请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/',
|
||||
},
|
||||
},
|
||||
},
|
||||
enable_datasource: {
|
||||
title: '数据权限认证',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
minWidth: 120,
|
||||
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'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user