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
|
# 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)
|
[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」**
|
💡 **「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
|
## 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)。
|
* 👭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.
|
* 👫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.
|
* 👬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/).
|
* 👬Enhanced Column Permission Control, with granularity down to each column.
|
||||||
* 💡 💏 Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
|
* 💏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
|
## 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
|
* demo account:superadmin
|
||||||
|
|
||||||
@@ -39,57 +40,61 @@ Because of love, so embrace the future
|
|||||||
|
|
||||||
## source code url:
|
## 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
|
## core function
|
||||||
|
|
||||||
1. 👨⚕️ Menu management: Configure the system menu, operation permissions, button permissions, back-end interface permissions, etc.
|
1. 👨⚕️Menu Management: Configure system menus, operation permissions, button permission flags, backend interface permissions, etc.
|
||||||
2. 🧑⚕️ Department management: Configure the system organization (company, department, role).
|
2. 🧑⚕️Department Management: Configure system organizational structure (company, department, role).
|
||||||
3. 👩⚕️ Role management: role menu permission allocation, data permission allocation, set roles according to the department for data range permission division.
|
3. 👩⚕️Role Management: Role menu permission assignment, data permission assignment, set role-based data scope permissions by department.
|
||||||
4. 🧑🎓 Rights Specifies the rights of the authorization role.
|
4. 🧑🎓Button Permission Control: Authorize role-specific button permissions and interface permissions, enabling authorization of data scope for each interface.
|
||||||
5. 👨🎓 User management: The user is the system operator, this function mainly completes the system user configuration.
|
5. 🧑🎓Field Column Permission Control: Authorize page field display permissions, specifically for the display permissions of a certain column.
|
||||||
6. 👬 Interface whitelist: specifies the interface that does not need permission verification.
|
6. 👨🎓User Management: Users are system operators, and this function is mainly used for system user configuration.
|
||||||
7. 🧑🔧 Dictionary management: Maintenance of some fixed data frequently used in the system.
|
7. 👬API Whitelist: Configure interfaces that do not require permission verification.
|
||||||
8. 🧑🔧 Regional management: to manage provinces, cities, counties and regions.
|
8. 🧑🔧Dictionary Management: Maintain frequently used and relatively fixed data in the system.
|
||||||
9. 📁 Attachment management: Unified management of all files and pictures on the platform.
|
9. 🧑🔧Region Management: Manage provinces, cities, counties, and districts.
|
||||||
10. 🗓 ️operation logs: log and query the system normal operation; Log and query system exception information.
|
10. 📁File Management: Unified management of all files, images, etc., on the platform.
|
||||||
11.🔌 [plugins market] (<https://bbs.django-vue-admin.com/plugMarket.html>) : based on the Django framework - Vue - Admin application and plug-in development.
|
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 🔌
|
## plugins market 🔌
|
||||||
|
|
||||||
* Celery Asynchronous task:[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
|
Updating...
|
||||||
* 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)
|
## Repository Branch Explanation 💈
|
||||||
|
Main Branch: master (stable version)
|
||||||
|
Development Branch: develop
|
||||||
|
|
||||||
## before start project you need:
|
## before start project you need:
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
Python >= 3.8.0
|
Python >= 3.11.0 (Minimum version 3.9+)
|
||||||
nodejs >= 14.0
|
Node.js >= 16.0
|
||||||
Mysql >= 5.7.0 (Optional. The default database is sqlite3. 8.0 is recommended)
|
Mysql >= 8.0 (Optional, default database: SQLite3, supports 5.7+, recommended version: 8.0)
|
||||||
Redis(Optional, the latest edition)
|
Redis (Optional, latest version)
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## frontend♝
|
## frontend♝
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# clone code
|
# 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
|
# enter code dir
|
||||||
cd web
|
cd web
|
||||||
|
|
||||||
# install dependence
|
# install dependence
|
||||||
npm install --registry=https://registry.npm.taobao.org
|
npm install yarn
|
||||||
|
yarn install --registry=https://registry.npm.taobao.org
|
||||||
|
|
||||||
# Start service
|
# Start service
|
||||||
npm run dev
|
yarn run dev
|
||||||
# Visit http://localhost:8080 in your browser
|
# Visit http://localhost:8080 in your browser
|
||||||
# Parameters such as boot port can be configured in the #.env.development file
|
# Parameters such as boot port can be configured in the #.env.development file
|
||||||
# Build the production environment
|
# Build the production environment
|
||||||
# npm run build
|
# yarn run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## backend💈
|
## backend💈
|
||||||
@@ -111,8 +116,8 @@ npm run dev
|
|||||||
python3 manage.py init_area
|
python3 manage.py init_area
|
||||||
8. start backend
|
8. start backend
|
||||||
python3 manage.py runserver 0.0.0.0:8000
|
python3 manage.py runserver 0.0.0.0:8000
|
||||||
or daphne :
|
or uvicorn :
|
||||||
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
### visit backend swagger
|
### 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)
|
* 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`
|
* account:`superadmin` password:`admin123456`
|
||||||
|
|
||||||
### docker-compose
|
### docker-compose
|
||||||
|
|
||||||
~~~shell
|
~~~shell
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
# Initialize backend data (first execution only)
|
# 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 makemigrations
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
python manage.py init_area
|
python manage.py init_area
|
||||||
@@ -147,22 +152,24 @@ docker-compose up -d --build
|
|||||||
|
|
||||||
## Demo screenshot✅
|
## 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://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带来一点我们的色彩和颜色。
|
我们是一群热爱代码的青年,在这个炙热的时代下,我们希望静下心来通过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)。
|
* 👭后端采用 Python 语言 Django 框架以及强大的 [Django REST Framework](https://pypi.org/project/djangorestframework)。
|
||||||
* 👫权限认证使用[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),支持多终端认证系统。
|
* 👫权限认证使用[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),支持多终端认证系统。
|
||||||
* 👬支持加载动态权限菜单,多方式轻松权限控制。
|
* 👬支持加载动态权限菜单,多方式轻松权限控制。
|
||||||
|
* 👬全新的列权限管控,粒度细化到每一列。
|
||||||
* 💏特别鸣谢:[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)。
|
* 💏特别鸣谢:[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
|
- 密码: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)👩👦👦
|
- 插件市场:[戳我](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交流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)👩👦👦
|
gitee地址(主推):[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)👩👦👦
|
||||||
|
|
||||||
|
|
||||||
## 内置功能
|
## 内置功能
|
||||||
@@ -65,47 +77,51 @@ github地址:[https://github.com/liqianglog/django-vue-admin](https://github.c
|
|||||||
1. 👨⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
1. 👨⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
||||||
2. 🧑⚕️部门管理:配置系统组织机构(公司、部门、角色)。
|
2. 🧑⚕️部门管理:配置系统组织机构(公司、部门、角色)。
|
||||||
3. 👩⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
|
3. 👩⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
|
||||||
4. 🧑🎓权限权限:授权角色的权限范围。
|
4. 🧑🎓按钮权限控制:授权角色的按钮权限和接口权限,可做到每一个接口都能授权数据范围。
|
||||||
5. 👨🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
5. 🧑🎓字段列权限控制:授权页面的字段显示权限,具体到某一列的显示权限。
|
||||||
6. 👬接口白名单:配置不需要进行权限校验的接口。
|
7. 👨🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||||
7. 🧑🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
8. 👬接口白名单:配置不需要进行权限校验的接口。
|
||||||
8. 🧑🔧地区管理:对省市县区域进行管理。
|
9. 🧑🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||||
9. 📁附件管理:对平台上所有文件、图片等进行统一管理。
|
10. 🧑🔧地区管理:对省市县区域进行管理。
|
||||||
10. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
11. 📁附件管理:对平台上所有文件、图片等进行统一管理。
|
||||||
11. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html):基于Django-Vue-Admin框架开发的应用和插件。
|
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+版本)
|
Python >= 3.11.0 (最低3.9+版本)
|
||||||
nodejs >= 14.0 (推荐最新)
|
nodejs >= 16.0
|
||||||
Mysql >= 5.7.0 (可选,默认数据库sqlite3,推荐8.0版本)
|
Mysql >= 8.0 (可选,默认数据库sqlite3,支持5.7+,推荐8.0版本)
|
||||||
Redis(可选,最新版)
|
Redis (可选,最新版)
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## 前端♝
|
## 前端♝
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 克隆项目
|
# 克隆项目
|
||||||
git clone https://gitee.com/liqianglog/django-vue-admin.git
|
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||||
|
|
||||||
# 进入项目目录
|
# 进入项目目录
|
||||||
cd web
|
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
|
# 浏览器访问 http://localhost:8080
|
||||||
# .env.development 文件中可配置启动端口等参数
|
# .env.development 文件中可配置启动端口等参数
|
||||||
# 构建生产环境
|
# 构建生产环境
|
||||||
# npm run build
|
# yarn run build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -129,9 +145,11 @@ npm run dev
|
|||||||
python3 manage.py init_area
|
python3 manage.py init_area
|
||||||
8. 启动项目
|
8. 启动项目
|
||||||
python3 manage.py runserver 0.0.0.0:8000
|
python3 manage.py runserver 0.0.0.0:8000
|
||||||
或使用 daphne :
|
或使用 uvicorn :
|
||||||
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
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 (自行百度安装),执行此命令等待安装,如有使用celery插件请打开docker-compose.yml中celery 部分注释
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
# 初始化后端数据(第一次执行即可)
|
# 初始化后端数据(第一次执行即可)
|
||||||
docker exec -ti dvadmin-django bash
|
docker exec -ti dvadmin3-django bash
|
||||||
python manage.py makemigrations
|
python manage.py makemigrations
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
python manage.py init_area
|
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__/
|
__pypackages__/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
gunicorn.pid
|
gunicorn.pid
|
||||||
|
!plugins/__init__.py
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from conf.env import *
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
@@ -24,6 +22,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||||||
# ******************** 动态配置 ******************** #
|
# ******************** 动态配置 ******************** #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
|
|
||||||
|
from conf.env import *
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||||
|
|
||||||
@@ -43,7 +43,8 @@ sys.path.insert(0, os.path.join(PLUGINS_PATH))
|
|||||||
DEBUG = locals().get("DEBUG", True)
|
DEBUG = locals().get("DEBUG", True)
|
||||||
ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
|
ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
|
||||||
|
|
||||||
# Application definition
|
# 列权限需要排除的App应用
|
||||||
|
COLUMN_EXCLUDE_APPS = ['channels', 'captcha'] + locals().get("COLUMN_EXCLUDE_APPS", [])
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
@@ -57,8 +58,8 @@ INSTALLED_APPS = [
|
|||||||
"corsheaders", # 注册跨域app
|
"corsheaders", # 注册跨域app
|
||||||
"drf_yasg",
|
"drf_yasg",
|
||||||
"captcha",
|
"captcha",
|
||||||
'channels',
|
"channels",
|
||||||
*locals().get("CUSTOM_APPS", []), # 所有项目里写的app需要在env.py文件里的CUSTOM_APPS中
|
"dvadmin.system",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer):
|
|||||||
unread_count = await _get_message_unread(self.user_id)
|
unread_count = await _get_message_unread(self.user_id)
|
||||||
if unread_count == 0:
|
if unread_count == 0:
|
||||||
# 发送连接成功
|
# 发送连接成功
|
||||||
await self.send_json(set_message('system', 'SYSTEM', '连接成功'))
|
await self.send_json(set_message('system', 'SYSTEM', '您已上线'))
|
||||||
else:
|
else:
|
||||||
await self.send_json(
|
await self.send_json(
|
||||||
set_message('system', 'SYSTEM', "请查看您的未读消息~",
|
set_message('system', 'SYSTEM', "请查看您的未读消息~",
|
||||||
|
|||||||
@@ -44,8 +44,5 @@ LOGIN_NO_CAPTCHA_AUTH = True
|
|||||||
# ================================================= #
|
# ================================================= #
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["*"]
|
ALLOWED_HOSTS = ["*"]
|
||||||
CUSTOM_APPS = [
|
# 列权限中排除App应用
|
||||||
"dvadmin.system",
|
COLUMN_EXCLUDE_APPS = []
|
||||||
]
|
|
||||||
# daphne启动命令
|
|
||||||
#daphne application.asgi:application -b 0.0.0.0 -p 8000
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ django.setup()
|
|||||||
from dvadmin.system.models import (
|
from dvadmin.system.models import (
|
||||||
Role, Dept, Users, Menu, MenuButton,
|
Role, Dept, Users, Menu, MenuButton,
|
||||||
ApiWhiteList, Dictionary, SystemConfig,
|
ApiWhiteList, Dictionary, SystemConfig,
|
||||||
RoleMenuPermission, RoleMenuButtonPermission
|
RoleMenuPermission, RoleMenuButtonPermission, MenuField
|
||||||
)
|
)
|
||||||
from dvadmin.utils.serializers import CustomModelSerializer
|
from dvadmin.utils.serializers import CustomModelSerializer
|
||||||
|
|
||||||
@@ -53,6 +53,16 @@ class MenuButtonInitSerializer(CustomModelSerializer):
|
|||||||
read_only_fields = ["id"]
|
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):
|
class MenuInitSerializer(CustomModelSerializer):
|
||||||
"""
|
"""
|
||||||
递归深度获取数信息(用于生成初始化json文件)
|
递归深度获取数信息(用于生成初始化json文件)
|
||||||
@@ -60,7 +70,7 @@ class MenuInitSerializer(CustomModelSerializer):
|
|||||||
name = serializers.CharField(required=False)
|
name = serializers.CharField(required=False)
|
||||||
children = serializers.SerializerMethodField()
|
children = serializers.SerializerMethodField()
|
||||||
menu_button = serializers.SerializerMethodField()
|
menu_button = serializers.SerializerMethodField()
|
||||||
|
menu_field = serializers.SerializerMethodField()
|
||||||
def get_children(self, obj: Menu):
|
def get_children(self, obj: Menu):
|
||||||
data = []
|
data = []
|
||||||
instance = Menu.objects.filter(parent_id=obj.id)
|
instance = Menu.objects.filter(parent_id=obj.id)
|
||||||
@@ -76,10 +86,18 @@ class MenuInitSerializer(CustomModelSerializer):
|
|||||||
data = list(instance.values('name', 'value', 'api', 'method'))
|
data = list(instance.values('name', 'value', 'api', 'method'))
|
||||||
return data
|
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):
|
def save(self, **kwargs):
|
||||||
instance = super().save(**kwargs)
|
instance = super().save(**kwargs)
|
||||||
children = self.initial_data.get('children')
|
children = self.initial_data.get('children')
|
||||||
menu_button = self.initial_data.get('menu_button')
|
menu_button = self.initial_data.get('menu_button')
|
||||||
|
menu_field = self.initial_data.get('menu_field')
|
||||||
# 菜单表
|
# 菜单表
|
||||||
if children:
|
if children:
|
||||||
for menu_data in children:
|
for menu_data in children:
|
||||||
@@ -108,12 +126,24 @@ class MenuInitSerializer(CustomModelSerializer):
|
|||||||
serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request)
|
serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
serializer.save()
|
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
|
return instance
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Menu
|
model = Menu
|
||||||
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
|
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
|
||||||
'cache', 'visible', 'parent', 'children', 'menu_button', 'creator', 'dept_belong_id']
|
'cache', 'visible', 'parent', 'children', 'menu_button','menu_field', 'creator', 'dept_belong_id']
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'creator': {'write_only': True},
|
'creator': {'write_only': True},
|
||||||
'dept_belong_id': {'write_only': True}
|
'dept_belong_id': {'write_only': True}
|
||||||
@@ -128,7 +158,7 @@ class RoleInitSerializer(CustomModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Role
|
model = Role
|
||||||
fields = ['name', 'key', 'sort', 'status', 'admin',
|
fields = ['name', 'key', 'sort', 'status',
|
||||||
'creator', 'dept_belong_id']
|
'creator', 'dept_belong_id']
|
||||||
read_only_fields = ["id"]
|
read_only_fields = ["id"]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@
|
|||||||
"key": "admin",
|
"key": "admin",
|
||||||
"sort": 1,
|
"sort": 1,
|
||||||
"status": true,
|
"status": true,
|
||||||
"admin": true,
|
|
||||||
"remark": null
|
"remark": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -12,7 +11,6 @@
|
|||||||
"key": "public",
|
"key": "public",
|
||||||
"sort": 2,
|
"sort": 2,
|
||||||
"status": true,
|
"status": true,
|
||||||
"admin": true,
|
|
||||||
"remark": null
|
"remark": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -65,6 +65,20 @@
|
|||||||
"placeholder": null,
|
"placeholder": null,
|
||||||
"setting": null,
|
"setting": null,
|
||||||
"children": [
|
"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,
|
"parent": 1,
|
||||||
"title": "网站名称",
|
"title": "网站名称",
|
||||||
@@ -116,7 +130,7 @@
|
|||||||
"parent": 1,
|
"parent": 1,
|
||||||
"title": "版权信息",
|
"title": "版权信息",
|
||||||
"key": "copyright",
|
"key": "copyright",
|
||||||
"value": "2021-2022 django-vue-admin.com 版权所有",
|
"value": "2021-2024 django-vue-admin.com 版权所有",
|
||||||
"sort": 4,
|
"sort": 4,
|
||||||
"status": true,
|
"status": true,
|
||||||
"data_options": null,
|
"data_options": null,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class Role(CoreModel):
|
|||||||
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
|
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
|
||||||
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
|
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
|
||||||
status = models.BooleanField(default=True, verbose_name="角色状态", help_text="角色状态")
|
status = models.BooleanField(default=True, verbose_name="角色状态", help_text="角色状态")
|
||||||
admin = models.BooleanField(default=False, verbose_name="是否为admin", help_text="是否为admin")
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = table_prefix + "system_role"
|
db_table = table_prefix + "system_role"
|
||||||
@@ -179,21 +178,27 @@ class Menu(CoreModel):
|
|||||||
verbose_name_plural = verbose_name
|
verbose_name_plural = verbose_name
|
||||||
ordering = ("sort",)
|
ordering = ("sort",)
|
||||||
|
|
||||||
|
class MenuField(CoreModel):
|
||||||
class Columns(CoreModel):
|
|
||||||
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
|
|
||||||
app = models.CharField(max_length=64, verbose_name='应用名')
|
|
||||||
model = models.CharField(max_length=64, verbose_name='表名')
|
model = models.CharField(max_length=64, verbose_name='表名')
|
||||||
menu = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name='菜单', db_constraint=False)
|
menu = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name='菜单', db_constraint=False)
|
||||||
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
|
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
|
||||||
title = 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_query = models.BooleanField(default=1, verbose_name='是否可查询')
|
||||||
is_create = models.BooleanField(default=1, verbose_name='是否可创建')
|
is_create = models.BooleanField(default=1, verbose_name='是否可创建')
|
||||||
is_update = models.BooleanField(default=1, verbose_name='是否可更新')
|
is_update = models.BooleanField(default=1, verbose_name='是否可更新')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = table_prefix + "system_columns"
|
db_table = table_prefix + "system_field_permission"
|
||||||
verbose_name = "列权限表"
|
verbose_name = "字段权限表"
|
||||||
verbose_name_plural = verbose_name
|
verbose_name_plural = verbose_name
|
||||||
ordering = ("id",)
|
ordering = ("id",)
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet
|
|||||||
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
|
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
|
||||||
from dvadmin.system.views.system_config import SystemConfigViewSet
|
from dvadmin.system.views.system_config import SystemConfigViewSet
|
||||||
from dvadmin.system.views.user import UserViewSet
|
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 = routers.SimpleRouter()
|
||||||
system_url.register(r'menu', MenuViewSet)
|
system_url.register(r'menu', MenuViewSet)
|
||||||
@@ -33,7 +33,7 @@ system_url.register(r'system_config', SystemConfigViewSet)
|
|||||||
system_url.register(r'message_center', MessageCenterViewSet)
|
system_url.register(r'message_center', MessageCenterViewSet)
|
||||||
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
|
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
|
||||||
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
||||||
system_url.register(r'column', ColumnViewSet)
|
system_url.register(r'column', MenuFieldViewSet)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
@@ -119,8 +119,7 @@ class MenuViewSet(CustomModelViewSet):
|
|||||||
def web_router(self, request):
|
def web_router(self, request):
|
||||||
"""用于前端获取当前角色的路由"""
|
"""用于前端获取当前角色的路由"""
|
||||||
user = request.user
|
user = request.user
|
||||||
is_admin = user.role.values_list('admin', flat=True)
|
if user.is_superuser:
|
||||||
if user.is_superuser or True in is_admin:
|
|
||||||
queryset = self.queryset.filter(status=1)
|
queryset = self.queryset.filter(status=1)
|
||||||
else:
|
else:
|
||||||
role_list = user.role.values_list('id', flat=True)
|
role_list = user.role.values_list('id', flat=True)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from rest_framework.decorators import action
|
|||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
from dvadmin.system.models import MenuButton, RoleMenuButtonPermission
|
from dvadmin.system.models import MenuButton, RoleMenuButtonPermission
|
||||||
from dvadmin.utils.json_response import DetailResponse
|
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
|
||||||
from dvadmin.utils.serializers import CustomModelSerializer
|
from dvadmin.utils.serializers import CustomModelSerializer
|
||||||
from dvadmin.utils.viewset import CustomModelViewSet
|
from dvadmin.utils.viewset import CustomModelViewSet
|
||||||
|
|
||||||
@@ -49,12 +49,24 @@ class MenuButtonViewSet(CustomModelViewSet):
|
|||||||
retrieve:单例
|
retrieve:单例
|
||||||
destroy:删除
|
destroy:删除
|
||||||
"""
|
"""
|
||||||
queryset = MenuButton.objects.all()
|
queryset = MenuButton.objects.order_by('create_datetime')
|
||||||
serializer_class = MenuButtonSerializer
|
serializer_class = MenuButtonSerializer
|
||||||
create_serializer_class = MenuButtonCreateUpdateSerializer
|
create_serializer_class = MenuButtonCreateUpdateSerializer
|
||||||
update_serializer_class = MenuButtonCreateUpdateSerializer
|
update_serializer_class = MenuButtonCreateUpdateSerializer
|
||||||
extra_filter_class = []
|
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])
|
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
|
||||||
def menu_button_all_permission(self,request):
|
def menu_button_all_permission(self,request):
|
||||||
"""
|
"""
|
||||||
@@ -63,8 +75,7 @@ class MenuButtonViewSet(CustomModelViewSet):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
is_superuser = request.user.is_superuser
|
is_superuser = request.user.is_superuser
|
||||||
is_admin = request.user.role.values_list('admin', flat=True)
|
if is_superuser:
|
||||||
if is_superuser or True in is_admin:
|
|
||||||
queryset = MenuButton.objects.values_list('value',flat=True)
|
queryset = MenuButton.objects.values_list('value',flat=True)
|
||||||
else:
|
else:
|
||||||
role_id = request.user.role.values_list('id', flat=True)
|
role_id = request.user.role.values_list('id', flat=True)
|
||||||
|
|||||||
@@ -1,57 +1,51 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.apps import apps
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.permissions import IsAuthenticated
|
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.models import get_custom_app_models
|
||||||
from dvadmin.utils.viewset import CustomModelViewSet
|
from dvadmin.utils.viewset import CustomModelViewSet
|
||||||
from dvadmin.utils.serializers import CustomModelSerializer
|
from dvadmin.utils.serializers import CustomModelSerializer
|
||||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse
|
from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse
|
||||||
|
|
||||||
|
|
||||||
class ColumnSerializer(CustomModelSerializer):
|
class MenuFieldSerializer(CustomModelSerializer):
|
||||||
"""
|
"""
|
||||||
列权限序列化器
|
列权限序列化器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Columns
|
model = MenuField
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
read_only_fields = ['id']
|
read_only_fields = ['id']
|
||||||
|
|
||||||
|
|
||||||
class ColumnViewSet(CustomModelViewSet):
|
class MenuFieldViewSet(CustomModelViewSet):
|
||||||
"""
|
"""
|
||||||
列权限视图集
|
列权限视图集
|
||||||
"""
|
"""
|
||||||
queryset = Columns.objects.all()
|
queryset = MenuField.objects.all()
|
||||||
serializer_class = ColumnSerializer
|
serializer_class = MenuFieldSerializer
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
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')
|
|
||||||
menu = request.query_params.get('menu')
|
menu = request.query_params.get('menu')
|
||||||
if not role_id or not model_name or not app_name or not menu:
|
if not menu:
|
||||||
return SuccessResponse([])
|
return SuccessResponse([])
|
||||||
queryset = self.filter_queryset(self.get_queryset().filter(role_id=role_id, model=model_name, app=app_name,menu_id=menu))
|
queryset = self.filter_queryset(self.get_queryset().filter(menu=menu))
|
||||||
page = self.paginate_queryset(queryset)
|
|
||||||
if page is not None:
|
|
||||||
serializer = self.get_serializer(page, many=True, request=request)
|
|
||||||
return self.get_paginated_response(serializer.data)
|
|
||||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
payload = request.data
|
payload = request.data
|
||||||
for model in get_custom_app_models(payload.get('app')):
|
for model in apps.get_models():
|
||||||
if payload.get('model') == model['model']:
|
if payload.get('model') == model.__name__:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
return ErrorResponse(msg='模型表不存在')
|
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 ErrorResponse(msg='‘%s’ 字段权限已有,不可重复创建' % payload.get('title'))
|
||||||
|
|
||||||
return super().create(request, *args, **kwargs)
|
return super().create(request, *args, **kwargs)
|
||||||
@@ -60,34 +54,31 @@ class ColumnViewSet(CustomModelViewSet):
|
|||||||
def get_models(self, request):
|
def get_models(self, request):
|
||||||
"""获取所有项目app下的model"""
|
"""获取所有项目app下的model"""
|
||||||
res = []
|
res = []
|
||||||
for app in get_custom_app_models():
|
for model in get_custom_app_models():
|
||||||
for model in app:
|
res.append({
|
||||||
res.append({
|
'app': model['app'],
|
||||||
'app': model['app'],
|
'title': model['verbose'],
|
||||||
'title': model['verbose'],
|
'key': model['model']
|
||||||
'key': model['model']
|
})
|
||||||
})
|
|
||||||
return DetailResponse(res)
|
return DetailResponse(res)
|
||||||
|
|
||||||
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
|
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
|
||||||
def auto_match_fields(self, request):
|
def auto_match_fields(self, request):
|
||||||
"""自动匹配已有的字段"""
|
"""自动匹配已有的字段"""
|
||||||
role_id = request.data.get('role')
|
menu_id = request.data.get('menu')
|
||||||
app_name = request.data.get('app')
|
|
||||||
model_name = request.data.get('model')
|
model_name = request.data.get('model')
|
||||||
if not role_id or not model_name or not app_name:
|
if not menu_id or not model_name:
|
||||||
return DetailResponse([], msg='无操作')
|
return ErrorResponse( msg='参数错误')
|
||||||
for model in get_custom_app_models(app_name):
|
for model in get_custom_app_models():
|
||||||
if model['model'] != model_name:
|
if model['model'] != model_name:
|
||||||
continue
|
continue
|
||||||
for field in model['fields']:
|
for field in model['fields']:
|
||||||
if Columns.objects.filter(
|
if MenuField.objects.filter(
|
||||||
role_id=role_id, app=app_name, model=model_name, field_name=field['name']
|
menu_id=menu_id, model=model_name, field_name=field['name']
|
||||||
).exists():
|
).exists():
|
||||||
continue
|
continue
|
||||||
data = {
|
data = {
|
||||||
'role': role_id,
|
'menu': menu_id,
|
||||||
'app': app_name,
|
|
||||||
'model': model_name,
|
'model': model_name,
|
||||||
'field_name': field['name'],
|
'field_name': field['name'],
|
||||||
'title': str(field['title']),
|
'title': str(field['title']),
|
||||||
@@ -11,7 +11,8 @@ from rest_framework import serializers
|
|||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, Columns
|
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, FieldPermission, \
|
||||||
|
MenuField
|
||||||
from dvadmin.system.views.menu import MenuSerializer
|
from dvadmin.system.views.menu import MenuSerializer
|
||||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
|
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
|
||||||
from dvadmin.utils.serializers import CustomModelSerializer
|
from dvadmin.utils.serializers import CustomModelSerializer
|
||||||
@@ -71,12 +72,41 @@ class RoleButtonPermissionSerializer(CustomModelSerializer):
|
|||||||
model = MenuButton
|
model = MenuButton
|
||||||
fields = ['id','name','value','isCheck','data_range']
|
fields = ['id','name','value','isCheck','data_range']
|
||||||
|
|
||||||
class RoleColumnsSerializer(CustomModelSerializer):
|
class RoleFieldPermissionSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Columns
|
model = FieldPermission
|
||||||
fields = "__all__"
|
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):
|
class RoleMenuPermissionSerializer(CustomModelSerializer):
|
||||||
"""
|
"""
|
||||||
@@ -99,9 +129,8 @@ class RoleMenuPermissionSerializer(CustomModelSerializer):
|
|||||||
return serializer.data
|
return serializer.data
|
||||||
|
|
||||||
def get_columns(self, instance):
|
def get_columns(self, instance):
|
||||||
params = self.request.query_params
|
col_list = MenuField.objects.filter(menu=instance['id'])
|
||||||
col_list = Columns.objects.filter(role__id=params.get('role'),menu__id=instance['id'])
|
serializer = RoleMenuFieldSerializer(col_list,many=True,request=self.request)
|
||||||
serializer = RoleColumnsSerializer(col_list,many=True,request=self.request)
|
|
||||||
return serializer.data
|
return serializer.data
|
||||||
|
|
||||||
|
|
||||||
@@ -165,10 +194,11 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
|||||||
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
|
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
|
||||||
for btn in menu.get('btns'):
|
for btn in menu.get('btns'):
|
||||||
if btn.get('isCheck'):
|
if btn.get('isCheck'):
|
||||||
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),data_range=btn.get('data_range'))
|
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',[]))
|
instance.dept.set(btn.get('dept',[]))
|
||||||
for col in menu.get('columns'):
|
for col in menu.get('columns'):
|
||||||
Columns.objects.filter(id=col.get('id')).update(is_query=col.get('is_query'),is_create=col.get('is_create'),is_update=col.get('is_update'))
|
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="授权成功")
|
return DetailResponse(msg="授权成功")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,8 @@ class UserViewSet(CustomModelViewSet):
|
|||||||
"""
|
"""
|
||||||
密码重置
|
密码重置
|
||||||
"""
|
"""
|
||||||
|
if not self.request.user.is_superuser:
|
||||||
|
return ErrorResponse(msg="只允许超级管理员对其进行密码重置")
|
||||||
instance = Users.objects.filter(id=pk).first()
|
instance = Users.objects.filter(id=pk).first()
|
||||||
data = request.data
|
data = request.data
|
||||||
new_pwd = data.get("newPassword")
|
new_pwd = data.get("newPassword")
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.db.models import F
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.permissions import IsAuthenticated
|
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.json_response import DetailResponse
|
||||||
from dvadmin.utils.models import get_custom_app_models
|
from dvadmin.utils.models import get_custom_app_models
|
||||||
|
|
||||||
@@ -14,25 +15,24 @@ class FieldPermissionMixin:
|
|||||||
获取字段权限
|
获取字段权限
|
||||||
"""
|
"""
|
||||||
finded = False
|
finded = False
|
||||||
for app in get_custom_app_models():
|
for model in get_custom_app_models():
|
||||||
for model in app:
|
if model['object'] is self.serializer_class.Meta.model:
|
||||||
if model['object'] is self.serializer_class.Meta.model:
|
finded = True
|
||||||
finded = True
|
break
|
||||||
break
|
|
||||||
if finded:
|
if finded:
|
||||||
break
|
break
|
||||||
if finded is False:
|
if finded is False:
|
||||||
return []
|
return []
|
||||||
roles = request.user.role.values_list('id', flat=True)
|
|
||||||
user = request.user
|
user = request.user
|
||||||
if user.is_superuser==1:
|
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:
|
for item in data:
|
||||||
item['is_create'] = True
|
item['is_create'] = True
|
||||||
item['is_query'] = True
|
item['is_query'] = True
|
||||||
item['is_update'] = True
|
item['is_update'] = True
|
||||||
else:
|
else:
|
||||||
data= Columns.objects.filter(
|
roles = request.user.role.values_list('id', flat=True)
|
||||||
app=model['app'], model=model['model'],role__in=roles
|
data= FieldPermission.objects.filter(
|
||||||
).values('field_name', 'is_create', 'is_query', 'is_update')
|
field__model=model['model'],role__in=roles
|
||||||
|
).values( 'is_create', 'is_query', 'is_update',field_name=F('field__field_name'))
|
||||||
return DetailResponse(data=data)
|
return DetailResponse(data=data)
|
||||||
@@ -121,13 +121,12 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
|||||||
role__status=1,
|
role__status=1,
|
||||||
menu_button__api=re_api,
|
menu_button__api=re_api,
|
||||||
menu_button__method=method).values(
|
menu_button__method=method).values(
|
||||||
'data_range',
|
'data_range'
|
||||||
role_admin=F('role__admin')
|
|
||||||
)
|
)
|
||||||
dataScope_list = [] # 权限范围列表
|
dataScope_list = [] # 权限范围列表
|
||||||
for ele in role_permission_list:
|
for ele in role_permission_list:
|
||||||
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
|
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
|
||||||
if ele.get("data_range") == 3 or ele.get("role_admin") == True:
|
if ele.get("data_range") == 3:
|
||||||
return queryset
|
return queryset
|
||||||
dataScope_list.append(ele.get("data_range"))
|
dataScope_list.append(ele.get("data_range"))
|
||||||
dataScope_list = list(set(dataScope_list))
|
dataScope_list = list(set(dataScope_list))
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from django.db import models
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from application import settings
|
from application import settings
|
||||||
|
|
||||||
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
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")
|
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="描述")
|
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,
|
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="修改人")
|
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="数据归属部门")
|
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True,
|
||||||
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
|
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="创建时间",
|
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
|
||||||
verbose_name="创建时间")
|
verbose_name="创建时间")
|
||||||
|
|
||||||
@@ -136,10 +140,23 @@ def get_model_from_app(app_name):
|
|||||||
|
|
||||||
|
|
||||||
def get_custom_app_models(app_name=None):
|
def get_custom_app_models(app_name=None):
|
||||||
"""获取所有项目写的app里的models"""
|
"""
|
||||||
|
获取所有项目下的app里的models
|
||||||
|
"""
|
||||||
if app_name:
|
if app_name:
|
||||||
return get_model_from_app(app_name)
|
return get_model_from_app(app_name)
|
||||||
|
all_apps = apps.get_app_configs()
|
||||||
res = []
|
res = []
|
||||||
for app in settings.CUSTOM_APPS:
|
for app in all_apps:
|
||||||
res.append(get_model_from_app(app))
|
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
|
return res
|
||||||
|
|||||||
@@ -79,6 +79,5 @@ class CustomPagination(PageNumberPagination):
|
|||||||
('total', total),
|
('total', total),
|
||||||
('is_next', is_next),
|
('is_next', is_next),
|
||||||
('is_previous', is_previous),
|
('is_previous', is_previous),
|
||||||
('data', data),
|
('data', data)
|
||||||
('permission', self.request.permission_fields)
|
|
||||||
]))
|
]))
|
||||||
|
|||||||
@@ -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.json_response import SuccessResponse, ErrorResponse, DetailResponse
|
||||||
from dvadmin.utils.permission import CustomPermission
|
from dvadmin.utils.permission import CustomPermission
|
||||||
from dvadmin.utils.models import get_custom_app_models
|
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
|
from django_restql.mixins import QueryArgumentsMixin
|
||||||
|
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
|||||||
serializer_class = self.get_serializer_class()
|
serializer_class = self.get_serializer_class()
|
||||||
kwargs.setdefault('context', self.get_serializer_context())
|
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)
|
# sub_set = set(serializer_class._declared_fields.keys()) - set(can_see)
|
||||||
# for field in sub_set:
|
# for field in sub_set:
|
||||||
@@ -79,21 +79,17 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
|||||||
else:
|
else:
|
||||||
return serializer_class(*args, **kwargs)
|
return serializer_class(*args, **kwargs)
|
||||||
|
|
||||||
def get_column_permission(self, serializer_class):
|
def get_menu_field(self, serializer_class):
|
||||||
"""获取列权限"""
|
"""获取字段权限"""
|
||||||
finded = False
|
finded = False
|
||||||
for app in get_custom_app_models():
|
for model in get_custom_app_models():
|
||||||
for model in app:
|
if model['object'] is serializer_class.Meta.model:
|
||||||
if model['object'] is serializer_class.Meta.model:
|
finded = True
|
||||||
finded = True
|
|
||||||
break
|
|
||||||
if finded:
|
|
||||||
break
|
break
|
||||||
if finded is False:
|
if finded is False:
|
||||||
return []
|
return []
|
||||||
return Columns.objects.filter(
|
return MenuField.objects.filter(model=model['model']
|
||||||
app=model['app'], model=model['model']
|
).values('field_name', 'title')
|
||||||
).values('field_name', 'is_create', 'is_query', 'is_update')
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
serializer = self.get_serializer(data=request.data, request=request)
|
serializer = self.get_serializer(data=request.data, request=request)
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
Django==4.1.5
|
Django==4.2.7
|
||||||
django-comment-migrate==0.1.7
|
django-comment-migrate==0.1.7
|
||||||
django-cors-headers==3.13.0
|
django-cors-headers==4.3.0
|
||||||
django-filter==21.1
|
django-filter==23.3
|
||||||
django-ranged-response==0.2.0
|
django-ranged-response==0.2.0
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.14.0
|
||||||
django-restql==0.15.3
|
django-restql==0.15.3
|
||||||
django-simple-captcha==0.5.17
|
django-simple-captcha==0.5.20
|
||||||
django-timezone-field==5.0
|
django-timezone-field==6.0.1
|
||||||
djangorestframework-simplejwt==5.2.2
|
djangorestframework-simplejwt==5.3.0
|
||||||
drf-yasg==1.21.4
|
drf-yasg==1.21.7
|
||||||
mysqlclient==2.1.1
|
mysqlclient==2.2.0
|
||||||
pypinyin==0.48.0
|
pypinyin==0.49.0
|
||||||
ua-parser==0.16.1
|
ua-parser==0.18.0
|
||||||
pyparsing==3.0.9
|
pyparsing==3.1.1
|
||||||
openpyxl==3.0.10
|
openpyxl==3.1.2
|
||||||
requests==2.28.2
|
requests==2.31.0
|
||||||
typing-extensions==4.4.0
|
typing-extensions==4.8.0
|
||||||
smmap==5.0.0
|
tzlocal==5.1
|
||||||
tzlocal==4.1
|
|
||||||
channels==4.0.0
|
channels==4.0.0
|
||||||
channels-redis==4.0.0
|
channels-redis==4.1.0
|
||||||
websockets==10.4
|
websockets==11.0.3
|
||||||
user-agents==2.2.0
|
user-agents==2.2.0
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
whitenoise==6.3.0
|
whitenoise==6.6.0
|
||||||
psycopg2==2.9.5
|
psycopg2==2.9.9
|
||||||
uvicorn==0.21.1
|
uvicorn==0.23.2
|
||||||
gunicorn==20.1.0
|
gunicorn==21.2.0
|
||||||
gevent==22.10.2
|
gevent==23.9.1
|
||||||
Pillow==8.3.2
|
Pillow==10.1.0
|
||||||
|
|||||||
@@ -28,6 +28,6 @@ server {
|
|||||||
proxy_send_timeout 600s;
|
proxy_send_timeout 600s;
|
||||||
real_ip_header X-Forwarded-For;
|
real_ip_header X-Forwarded-For;
|
||||||
rewrite ^/api/(.*)$ /$1 break; #重写
|
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
|
FROM registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-web:16.19-alpine
|
||||||
WORKDIR /web/
|
WORKDIR /web/
|
||||||
COPY web/. .
|
COPY web/. .
|
||||||
RUN yarn install
|
RUN yarn install --registry=https://registry.npm.taobao.org
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
ENV = 'development'
|
ENV = 'development'
|
||||||
|
|
||||||
# 本地环境接口地址
|
# 本地环境接口地址
|
||||||
VITE_API_URL = 'http://127.0.0.1:8000'
|
VITE_API_URL = 'http://127.0.0.1:8001'
|
||||||
|
|
||||||
# 是否启用按钮权限
|
# 是否启用按钮权限
|
||||||
VITE_PM_ENABLED = true
|
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 及之前版本。
|
> 由于 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
|
```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
|
<meta
|
||||||
name="description"
|
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" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<title>django-vue3-admin</title>
|
<title>django-vue-admin</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
// let _hmt = _hmt || [];
|
var _hmt = _hmt || [];
|
||||||
(function () {
|
(function() {
|
||||||
let hm = document.createElement('script');
|
var hm = document.createElement("script");
|
||||||
hm.src = 'https://hm.baidu.com/hm.js?d9c8b87d10717013641458b300c552e4';
|
hm.src = "https://hm.baidu.com/hm.js?9ba8fc809b5584167a2fb9b31bb3970c";
|
||||||
let s = document.getElementsByTagName('script')[0];
|
var s = document.getElementsByTagName("script")[0];
|
||||||
s.parentNode.insertBefore(hm, s);
|
s.parentNode.insertBefore(hm, s);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "django-vue3-admin",
|
"name": "django-vue3-admin",
|
||||||
"version": "1.0.0",
|
"version": "3.0.0",
|
||||||
"description": "django-vue3-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!",
|
"description": "是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。\n django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --force",
|
"dev": "vite --force",
|
||||||
@@ -10,10 +10,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.0.10",
|
"@element-plus/icons-vue": "^2.0.10",
|
||||||
"@fast-crud/fast-crud": "^1.14.7",
|
"@fast-crud/fast-crud": "^1.19.2",
|
||||||
"@fast-crud/fast-extends": "^1.14.7",
|
"@fast-crud/fast-extends": "^1.19.2",
|
||||||
"@fast-crud/ui-element": "^1.14.7",
|
"@fast-crud/ui-element": "^1.19.2",
|
||||||
"@fast-crud/ui-interface": "^1.14.7",
|
"@fast-crud/ui-interface": "^1.19.2",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
"splitpanes": "^3.1.5",
|
"splitpanes": "^3.1.5",
|
||||||
"tailwindcss": "^3.2.7",
|
"tailwindcss": "^3.2.7",
|
||||||
"ts-md5": "^1.3.1",
|
"ts-md5": "^1.3.1",
|
||||||
|
"upgrade": "^1.1.0",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-cropper": "^1.0.8",
|
"vue-cropper": "^1.0.8",
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
"@typescript-eslint/parser": "^5.46.0",
|
"@typescript-eslint/parser": "^5.46.0",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"@vue/compiler-sfc": "^3.2.45",
|
"@vue/compiler-sfc": "^3.2.45",
|
||||||
"eslint": "^8.29.0",
|
"eslint": "^8.54.0",
|
||||||
"eslint-plugin-vue": "^9.8.0",
|
"eslint-plugin-vue": "^9.8.0",
|
||||||
"prettier": "^2.8.1",
|
"prettier": "^2.8.1",
|
||||||
"sass": "^1.56.2",
|
"sass": "^1.56.2",
|
||||||
|
|||||||
@@ -59,12 +59,6 @@ onBeforeMount(() => {
|
|||||||
setIntroduction.cssCdn();
|
setIntroduction.cssCdn();
|
||||||
// 设置批量第三方 js
|
// 设置批量第三方 js
|
||||||
setIntroduction.jsCdn();
|
setIntroduction.jsCdn();
|
||||||
//websockt 模块
|
|
||||||
try {
|
|
||||||
//websocket.init(wsReceive)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('websocket错误');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -93,6 +87,14 @@ watch(
|
|||||||
() => route.path,
|
() => route.path,
|
||||||
() => {
|
() => {
|
||||||
other.useTitle();
|
other.useTitle();
|
||||||
|
if (!websocket.websocket) {
|
||||||
|
//websockt 模块
|
||||||
|
try {
|
||||||
|
websocket.init(wsReceive)
|
||||||
|
} catch (e) {
|
||||||
|
console.log('websocket错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
deep: true,
|
deep: true,
|
||||||
|
|||||||
BIN
web/src/assets/img/headerImage.png
Normal file
BIN
web/src/assets/img/headerImage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
@@ -1,123 +1,131 @@
|
|||||||
// 定义内容
|
// 定义内容
|
||||||
export default {
|
export default {
|
||||||
router: {
|
router: {
|
||||||
home: 'home',
|
home: 'home',
|
||||||
system: 'system',
|
system: 'system',
|
||||||
systemMenu: 'systemMenu',
|
systemMenu: 'systemMenu',
|
||||||
systemRole: 'systemRole',
|
systemRole: 'systemRole',
|
||||||
systemUser: 'systemUser',
|
systemUser: 'systemUser',
|
||||||
systemDept: 'systemDept',
|
systemDept: 'systemDept',
|
||||||
systemDic: 'systemDic',
|
systemDic: 'systemDic',
|
||||||
limits: 'limits',
|
limits: 'limits',
|
||||||
limitsFrontEnd: 'FrontEnd',
|
limitsFrontEnd: 'FrontEnd',
|
||||||
limitsFrontEndPage: 'FrontEndPage',
|
limitsFrontEndPage: 'FrontEndPage',
|
||||||
limitsFrontEndBtn: 'FrontEndBtn',
|
limitsFrontEndBtn: 'FrontEndBtn',
|
||||||
limitsBackEnd: 'BackEnd',
|
limitsBackEnd: 'BackEnd',
|
||||||
limitsBackEndEndPage: 'BackEndEndPage',
|
limitsBackEndEndPage: 'BackEndEndPage',
|
||||||
personal: 'personal',
|
personal: 'personal',
|
||||||
},
|
},
|
||||||
staticRoutes: {
|
staticRoutes: {
|
||||||
signIn: 'signIn',
|
signIn: 'signIn',
|
||||||
notFound: 'notFound',
|
notFound: 'notFound',
|
||||||
noPower: 'noPower',
|
noPower: 'noPower',
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
title0: 'Component size',
|
title0: 'Component size',
|
||||||
title1: 'Language switching',
|
title1: 'Language switching',
|
||||||
title2: 'Menu search',
|
title2: 'Menu search',
|
||||||
title3: 'Layout configuration',
|
title3: 'Layout configuration',
|
||||||
title4: 'news',
|
title4: 'news',
|
||||||
title5: 'Full screen on',
|
title5: 'Full screen on',
|
||||||
title6: 'Full screen off',
|
title6: 'Full screen off',
|
||||||
dropdownLarge: 'large',
|
dropdownLarge: 'large',
|
||||||
dropdownDefault: 'default',
|
dropdownDefault: 'default',
|
||||||
dropdownSmall: 'small',
|
dropdownSmall: 'small',
|
||||||
dropdown1: 'home page',
|
dropdown1: 'home page',
|
||||||
dropdown2: 'Personal Center',
|
dropdown2: 'Personal Center',
|
||||||
dropdown3: '404',
|
dropdown3: '404',
|
||||||
dropdown4: '401',
|
dropdown4: '401',
|
||||||
dropdown5: 'Log out',
|
dropdown5: 'Log out',
|
||||||
dropdown6: 'Code warehouse',
|
dropdown6: 'Code warehouse',
|
||||||
searchPlaceholder: 'Menu search: support Chinese, routing path',
|
searchPlaceholder: 'Menu search: support Chinese, routing path',
|
||||||
newTitle: 'notice',
|
newTitle: 'notice',
|
||||||
newBtn: 'All read',
|
newBtn: 'All read',
|
||||||
newGo: 'Go to the notification center',
|
newGo: 'Go to the notification center',
|
||||||
newDesc: 'No notice',
|
newDesc: 'No notice',
|
||||||
logOutTitle: 'Tips',
|
logOutTitle: 'Tips',
|
||||||
logOutMessage: 'This operation will log out. Do you want to continue?',
|
logOutMessage: 'This operation will log out. Do you want to continue?',
|
||||||
logOutConfirm: 'determine',
|
logOutConfirm: 'determine',
|
||||||
logOutCancel: 'cancel',
|
logOutCancel: 'cancel',
|
||||||
logOutExit: 'Exiting',
|
logOutExit: 'Exiting',
|
||||||
},
|
},
|
||||||
tagsView: {
|
tagsView: {
|
||||||
refresh: 'refresh',
|
refresh: 'refresh',
|
||||||
close: 'close',
|
close: 'close',
|
||||||
closeOther: 'closeOther',
|
closeOther: 'closeOther',
|
||||||
closeAll: 'closeAll',
|
closeAll: 'closeAll',
|
||||||
fullscreen: 'fullscreen',
|
fullscreen: 'fullscreen',
|
||||||
closeFullscreen: 'closeFullscreen',
|
closeFullscreen: 'closeFullscreen',
|
||||||
},
|
},
|
||||||
notFound: {
|
notFound: {
|
||||||
foundTitle: 'Wrong address input, please re-enter the address~',
|
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.',
|
foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
|
||||||
foundBtn: 'Back to home page',
|
foundBtn: 'Back to home page',
|
||||||
},
|
},
|
||||||
noAccess: {
|
noAccess: {
|
||||||
accessTitle: 'You are not authorized to operate~',
|
accessTitle: 'You are not authorized to operate~',
|
||||||
accessMsg: 'Contact information: add QQ group discussion 665452019',
|
accessMsg: 'Contact information: add QQ group discussion 665452019',
|
||||||
accessBtn: 'Reauthorization',
|
accessBtn: 'Reauthorization',
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
configTitle: 'Layout configuration',
|
configTitle: 'Layout configuration',
|
||||||
oneTitle: 'Global Themes',
|
oneTitle: 'Global Themes',
|
||||||
twoTopTitle: 'top bar set up',
|
twoTopTitle: 'top bar set up',
|
||||||
twoMenuTitle: 'Menu set up',
|
twoMenuTitle: 'Menu set up',
|
||||||
twoColumnsTitle: 'Columns set up',
|
twoColumnsTitle: 'Columns set up',
|
||||||
twoTopBar: 'Top bar background',
|
twoTopBar: 'Top bar background',
|
||||||
twoTopBarColor: 'Top bar default font color',
|
twoTopBarColor: 'Top bar default font color',
|
||||||
twoIsTopBarColorGradual: 'Top bar gradient',
|
twoIsTopBarColorGradual: 'Top bar gradient',
|
||||||
twoMenuBar: 'Menu background',
|
twoMenuBar: 'Menu background',
|
||||||
twoMenuBarColor: 'Menu default font color',
|
twoMenuBarColor: 'Menu default font color',
|
||||||
twoIsMenuBarColorGradual: 'Menu gradient',
|
twoIsMenuBarColorGradual: 'Menu gradient',
|
||||||
twoColumnsMenuBar: 'Column menu background',
|
twoColumnsMenuBar: 'Column menu background',
|
||||||
twoColumnsMenuBarColor: 'Default font color bar menu',
|
twoColumnsMenuBarColor: 'Default font color bar menu',
|
||||||
twoIsColumnsMenuBarColorGradual: 'Column gradient',
|
twoIsColumnsMenuBarColorGradual: 'Column gradient',
|
||||||
threeTitle: 'Interface settings',
|
threeTitle: 'Interface settings',
|
||||||
threeIsCollapse: 'Menu horizontal collapse',
|
threeIsCollapse: 'Menu horizontal collapse',
|
||||||
threeIsUniqueOpened: 'Menu accordion',
|
threeIsUniqueOpened: 'Menu accordion',
|
||||||
threeIsFixedHeader: 'Fixed header',
|
threeIsFixedHeader: 'Fixed header',
|
||||||
threeIsClassicSplitMenu: 'Classic layout split menu',
|
threeIsClassicSplitMenu: 'Classic layout split menu',
|
||||||
threeIsLockScreen: 'Open the lock screen',
|
threeIsLockScreen: 'Open the lock screen',
|
||||||
threeLockScreenTime: 'screen locking(s/s)',
|
threeLockScreenTime: 'screen locking(s/s)',
|
||||||
fourTitle: 'Interface display',
|
fourTitle: 'Interface display',
|
||||||
fourIsShowLogo: 'Sidebar logo',
|
fourIsShowLogo: 'Sidebar logo',
|
||||||
fourIsBreadcrumb: 'Open breadcrumb',
|
fourIsBreadcrumb: 'Open breadcrumb',
|
||||||
fourIsBreadcrumbIcon: 'Open breadcrumb icon',
|
fourIsBreadcrumbIcon: 'Open breadcrumb icon',
|
||||||
fourIsTagsview: 'Open tagsview',
|
fourIsTagsview: 'Open tagsview',
|
||||||
fourIsTagsviewIcon: 'Open tagsview Icon',
|
fourIsTagsviewIcon: 'Open tagsview Icon',
|
||||||
fourIsCacheTagsView: 'Enable tagsview cache',
|
fourIsCacheTagsView: 'Enable tagsview cache',
|
||||||
fourIsSortableTagsView: 'Enable tagsview drag',
|
fourIsSortableTagsView: 'Enable tagsview drag',
|
||||||
fourIsShareTagsView: 'Enable tagsview sharing',
|
fourIsShareTagsView: 'Enable tagsview sharing',
|
||||||
fourIsFooter: 'Open footer',
|
fourIsFooter: 'Open footer',
|
||||||
fourIsGrayscale: 'Grey model',
|
fourIsGrayscale: 'Grey model',
|
||||||
fourIsInvert: 'Color weak mode',
|
fourIsInvert: 'Color weak mode',
|
||||||
fourIsDark: 'Dark Mode',
|
fourIsDark: 'Dark Mode',
|
||||||
fourIsWartermark: 'Turn on watermark',
|
fourIsWartermark: 'Turn on watermark',
|
||||||
fourWartermarkText: 'Watermark copy',
|
fourWartermarkText: 'Watermark copy',
|
||||||
fiveTitle: 'Other settings',
|
fiveTitle: 'Other settings',
|
||||||
fiveTagsStyle: 'Tagsview style',
|
fiveTagsStyle: 'Tagsview style',
|
||||||
fiveAnimation: 'page animation',
|
fiveAnimation: 'page animation',
|
||||||
fiveColumnsAsideStyle: 'Column style',
|
fiveColumnsAsideStyle: 'Column style',
|
||||||
fiveColumnsAsideLayout: 'Column layout',
|
fiveColumnsAsideLayout: 'Column layout',
|
||||||
sixTitle: 'Layout switch',
|
sixTitle: 'Layout switch',
|
||||||
sixDefaults: 'One',
|
sixDefaults: 'One',
|
||||||
sixClassic: 'Two',
|
sixClassic: 'Two',
|
||||||
sixTransverse: 'Three',
|
sixTransverse: 'Three',
|
||||||
sixColumns: 'Four',
|
sixColumns: 'Four',
|
||||||
tipText: 'Click the button below to copy the layout configuration to `/src/stores/themeConfig.ts` It has been modified in.',
|
tipText: 'Click the button below to copy the layout configuration to `/src/stores/themeConfig.ts` It has been modified in.',
|
||||||
copyText: 'replication configuration',
|
copyText: 'replication configuration',
|
||||||
resetText: 'restore default',
|
resetText: 'restore default',
|
||||||
copyTextSuccess: 'Copy succeeded!',
|
copyTextSuccess: 'Copy succeeded!',
|
||||||
copyTextError: 'Copy failed!',
|
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 {
|
export default {
|
||||||
router: {
|
router: {
|
||||||
home: '首页',
|
home: '首页',
|
||||||
system: '系统管理',
|
system: '系统管理',
|
||||||
config: '常规配置',
|
config: '常规配置',
|
||||||
log: '日志管理',
|
log: '日志管理',
|
||||||
/* 常规配置 */
|
/* 常规配置 */
|
||||||
configSystem: '系统配置',
|
configSystem: '系统配置',
|
||||||
configDict: '字典管理',
|
configDict: '字典管理',
|
||||||
configArea: '地区管理',
|
configArea: '地区管理',
|
||||||
configFile: '附件管理',
|
configFile: '附件管理',
|
||||||
/* 系统管理 */
|
/* 系统管理 */
|
||||||
systemMenu: '菜单管理',
|
systemMenu: '菜单管理',
|
||||||
systemRole: '角色管理',
|
systemRole: '角色管理',
|
||||||
systemUser: '用户管理',
|
systemUser: '用户管理',
|
||||||
systemDept: '部门管理',
|
systemDept: '部门管理',
|
||||||
/* 日志管理 */
|
/* 日志管理 */
|
||||||
loginLog: '登录日志',
|
loginLog: '登录日志',
|
||||||
operationLog: '操作日志',
|
operationLog: '操作日志',
|
||||||
systemApiWhiteList: '接口白名单',
|
systemApiWhiteList: '接口白名单',
|
||||||
limits: '权限管理',
|
limits: '权限管理',
|
||||||
limitsFrontEnd: '前端控制',
|
limitsFrontEnd: '前端控制',
|
||||||
limitsFrontEndPage: '页面权限',
|
limitsFrontEndPage: '页面权限',
|
||||||
limitsFrontEndBtn: '按钮权限',
|
limitsFrontEndBtn: '按钮权限',
|
||||||
limitsBackEnd: '后端控制',
|
limitsBackEnd: '后端控制',
|
||||||
limitsBackEndEndPage: '页面权限',
|
limitsBackEndEndPage: '页面权限',
|
||||||
personal: '个人中心',
|
personal: '个人中心',
|
||||||
},
|
},
|
||||||
staticRoutes: {
|
staticRoutes: {
|
||||||
signIn: '登录',
|
signIn: '登录',
|
||||||
notFound: '找不到此页面',
|
notFound: '找不到此页面',
|
||||||
noPower: '没有权限',
|
noPower: '没有权限',
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
title0: '组件大小',
|
title0: '组件大小',
|
||||||
title1: '语言切换',
|
title1: '语言切换',
|
||||||
title2: '菜单搜索',
|
title2: '菜单搜索',
|
||||||
title3: '布局配置',
|
title3: '布局配置',
|
||||||
title4: '消息',
|
title4: '消息',
|
||||||
title5: '开全屏',
|
title5: '开全屏',
|
||||||
title6: '关全屏',
|
title6: '关全屏',
|
||||||
dropdownLarge: '大型',
|
retry: '重试上线',
|
||||||
dropdownDefault: '默认',
|
onlinePrompt: '当前离线状态,是否重试上线?',
|
||||||
dropdownSmall: '小型',
|
dropdownLarge: '大型',
|
||||||
dropdown1: '首页',
|
dropdownDefault: '默认',
|
||||||
dropdown2: '个人中心',
|
dropdownSmall: '小型',
|
||||||
dropdown3: '404',
|
dropdown1: '首页',
|
||||||
dropdown4: '401',
|
dropdown2: '个人中心',
|
||||||
dropdown5: '退出登录',
|
dropdown3: '404',
|
||||||
dropdown6: '代码仓库',
|
dropdown4: '401',
|
||||||
searchPlaceholder: '菜单搜索:支持中文、路由路径',
|
dropdown5: '退出登录',
|
||||||
newTitle: '通知',
|
dropdown6: '代码仓库',
|
||||||
newBtn: '全部已读',
|
searchPlaceholder: '菜单搜索:支持中文、路由路径',
|
||||||
newGo: '前往通知中心',
|
newTitle: '通知',
|
||||||
newDesc: '暂无通知',
|
newBtn: '全部已读',
|
||||||
logOutTitle: '提示',
|
newGo: '前往通知中心',
|
||||||
logOutMessage: '此操作将退出登录, 是否继续?',
|
newDesc: '暂无通知',
|
||||||
logOutConfirm: '确定',
|
logOutTitle: '提示',
|
||||||
logOutCancel: '取消',
|
logOutMessage: '此操作将退出登录, 是否继续?',
|
||||||
logOutExit: '退出中',
|
logOutConfirm: '确定',
|
||||||
},
|
logOutCancel: '取消',
|
||||||
tagsView: {
|
logOutExit: '退出中',
|
||||||
refresh: '刷新',
|
},
|
||||||
close: '关闭',
|
tagsView: {
|
||||||
closeOther: '关闭其它',
|
refresh: '刷新',
|
||||||
closeAll: '全部关闭',
|
close: '关闭',
|
||||||
fullscreen: '当前页全屏',
|
closeOther: '关闭其它',
|
||||||
closeFullscreen: '关闭全屏',
|
closeAll: '全部关闭',
|
||||||
},
|
fullscreen: '当前页全屏',
|
||||||
notFound: {
|
closeFullscreen: '关闭全屏',
|
||||||
foundTitle: '地址输入错误,请重新输入地址~',
|
},
|
||||||
foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
|
notFound: {
|
||||||
foundBtn: '返回首页',
|
foundTitle: '地址输入错误,请重新输入地址~',
|
||||||
},
|
foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
|
||||||
noAccess: {
|
foundBtn: '返回首页',
|
||||||
accessTitle: '您未被授权,没有操作权限~',
|
},
|
||||||
accessMsg: '联系方式:加QQ群探讨 665452019',
|
noAccess: {
|
||||||
accessBtn: '重新授权',
|
accessTitle: '您未被授权,没有操作权限~',
|
||||||
},
|
accessMsg: '请联系管理员',
|
||||||
layout: {
|
accessBtn: '重新授权',
|
||||||
configTitle: '布局配置',
|
},
|
||||||
oneTitle: '全局主题',
|
layout: {
|
||||||
twoTopTitle: '顶栏设置',
|
configTitle: '布局配置',
|
||||||
twoMenuTitle: '菜单设置',
|
oneTitle: '全局主题',
|
||||||
twoColumnsTitle: '分栏设置',
|
twoTopTitle: '顶栏设置',
|
||||||
twoTopBar: '顶栏背景',
|
twoMenuTitle: '菜单设置',
|
||||||
twoTopBarColor: '顶栏默认字体颜色',
|
twoColumnsTitle: '分栏设置',
|
||||||
twoIsTopBarColorGradual: '顶栏背景渐变',
|
twoTopBar: '顶栏背景',
|
||||||
twoMenuBar: '菜单背景',
|
twoTopBarColor: '顶栏默认字体颜色',
|
||||||
twoMenuBarColor: '菜单默认字体颜色',
|
twoIsTopBarColorGradual: '顶栏背景渐变',
|
||||||
twoIsMenuBarColorGradual: '菜单背景渐变',
|
twoMenuBar: '菜单背景',
|
||||||
twoColumnsMenuBar: '分栏菜单背景',
|
twoMenuBarColor: '菜单默认字体颜色',
|
||||||
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
|
twoMenuBarActiveColor: '菜单高亮背景色',
|
||||||
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
|
twoIsMenuBarColorGradual: '菜单背景渐变',
|
||||||
threeTitle: '界面设置',
|
twoColumnsMenuBar: '分栏菜单背景',
|
||||||
threeIsCollapse: '菜单水平折叠',
|
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
|
||||||
threeIsUniqueOpened: '菜单手风琴',
|
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
|
||||||
threeIsFixedHeader: '固定 Header',
|
twoIsColumnsMenuHoverPreload: '分栏菜单滑鼠悬停预加载',
|
||||||
threeIsClassicSplitMenu: '经典布局分割菜单',
|
threeTitle: '界面设置',
|
||||||
threeIsLockScreen: '开启锁屏',
|
threeIsCollapse: '菜单水平折叠',
|
||||||
threeLockScreenTime: '自动锁屏(s/秒)',
|
threeIsUniqueOpened: '菜单手风琴',
|
||||||
fourTitle: '界面显示',
|
threeIsFixedHeader: '固定 Header',
|
||||||
fourIsShowLogo: '侧边栏 Logo',
|
threeIsClassicSplitMenu: '经典布局分割菜单',
|
||||||
fourIsBreadcrumb: '开启 Breadcrumb',
|
threeIsLockScreen: '开启锁屏',
|
||||||
fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
|
threeLockScreenTime: '自动锁屏(s/秒)',
|
||||||
fourIsTagsview: '开启 Tagsview',
|
fourTitle: '界面显示',
|
||||||
fourIsTagsviewIcon: '开启 Tagsview 图标',
|
fourIsShowLogo: '侧边栏 Logo',
|
||||||
fourIsCacheTagsView: '开启 TagsView 缓存',
|
fourIsBreadcrumb: '开启 Breadcrumb',
|
||||||
fourIsSortableTagsView: '开启 TagsView 拖拽',
|
fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
|
||||||
fourIsShareTagsView: '开启 TagsView 共用',
|
fourIsTagsview: '开启 Tagsview',
|
||||||
fourIsFooter: '开启 Footer',
|
fourIsTagsviewIcon: '开启 Tagsview 图标',
|
||||||
fourIsGrayscale: '灰色模式',
|
fourIsCacheTagsView: '开启 TagsView 缓存',
|
||||||
fourIsInvert: '色弱模式',
|
fourIsSortableTagsView: '开启 TagsView 拖拽',
|
||||||
fourIsDark: '深色模式',
|
fourIsShareTagsView: '开启 TagsView 共用',
|
||||||
fourIsWartermark: '开启水印',
|
fourIsFooter: '开启 Footer',
|
||||||
fourWartermarkText: '水印文案',
|
fourIsGrayscale: '灰色模式',
|
||||||
fiveTitle: '其它设置',
|
fourIsInvert: '色弱模式',
|
||||||
fiveTagsStyle: 'Tagsview 风格',
|
fourIsDark: '深色模式',
|
||||||
fiveAnimation: '主页面切换动画',
|
fourIsWartermark: '开启水印',
|
||||||
fiveColumnsAsideStyle: '分栏高亮风格',
|
fourWartermarkText: '水印文案',
|
||||||
fiveColumnsAsideLayout: '分栏布局风格',
|
fiveTitle: '其它设置',
|
||||||
sixTitle: '布局切换',
|
fiveTagsStyle: 'Tagsview 风格',
|
||||||
sixDefaults: '默认',
|
fiveAnimation: '主页面切换动画',
|
||||||
sixClassic: '经典',
|
fiveColumnsAsideStyle: '分栏高亮风格',
|
||||||
sixTransverse: '横向',
|
fiveColumnsAsideLayout: '分栏布局风格',
|
||||||
sixColumns: '分栏',
|
sixTitle: '布局切换',
|
||||||
tipText: '点击下方按钮,复制布局配置去 `src/stores/themeConfig.ts` 中修改。',
|
sixDefaults: '默认',
|
||||||
copyText: '一键复制配置',
|
sixClassic: '经典',
|
||||||
resetText: '一键恢复默认',
|
sixTransverse: '横向',
|
||||||
copyTextSuccess: '复制成功!',
|
sixColumns: '分栏',
|
||||||
copyTextError: '复制失败!',
|
tipText: '点击下方按钮,复制布局配置去 `src/stores/themeConfig.ts` 中修改。',
|
||||||
},
|
copyText: '一键复制配置',
|
||||||
|
resetText: '一键恢复默认',
|
||||||
|
copyTextSuccess: '复制成功!',
|
||||||
|
copyTextError: '复制失败!',
|
||||||
|
},
|
||||||
|
upgrade: {
|
||||||
|
title: '新版本升级',
|
||||||
|
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
|
||||||
|
desc: '提示:更新会还原默认配寘',
|
||||||
|
btnOne: '残忍拒绝',
|
||||||
|
btnTwo: '马上更新',
|
||||||
|
btnTwoLoading: '更新中',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export default {
|
|||||||
},
|
},
|
||||||
noAccess: {
|
noAccess: {
|
||||||
accessTitle: '您未被授權,沒有操作許可權~',
|
accessTitle: '您未被授權,沒有操作許可權~',
|
||||||
accessMsg: '聯繫方式:加QQ群探討665452019',
|
accessMsg: '請聯系管理員',
|
||||||
accessBtn: '重新授權',
|
accessBtn: '重新授權',
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// 定义内容
|
// 定义内容
|
||||||
export default {
|
export default {
|
||||||
label: {
|
label: {
|
||||||
one1: '用户名登录',
|
one1: '账号密码登录',
|
||||||
two2: '手机号登录',
|
two2: '手机号登录',
|
||||||
},
|
},
|
||||||
link: {
|
link: {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll layout-backtop-header-fixed"
|
<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll layout-backtop-header-fixed"
|
||||||
wrap-class="layout-main-scroll" view-class="layout-main-scroll">
|
wrap-class="layout-main-scroll" view-class="layout-main-scroll">
|
||||||
<LayoutParentView />
|
<LayoutParentView />
|
||||||
<!-- <LayoutFooter v-if="isFooter" /> -->
|
<LayoutFooter v-if="isFooter" />
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<el-backtop :target="setBacktopClass" />
|
<el-backtop :target="setBacktopClass" />
|
||||||
</el-main>
|
</el-main>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-footer pb15">
|
<div class="layout-footer pb5 pt2">
|
||||||
<div class="layout-footer-warp">
|
<div class="layout-footer-warp">
|
||||||
<div>❤️ Powered by Django-Vue3-Admin ❤️</div>
|
<div>❤️ Powered by Django-Vue3-Admin Copyright © DVAdmin团队 ❤️</div>
|
||||||
<div class="mt5">Copyright DVAdmin团队</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -57,9 +57,33 @@
|
|||||||
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
|
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</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">
|
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||||
<span class="layout-navbars-breadcrumb-user-link">
|
<span class="layout-navbars-breadcrumb-user-link">
|
||||||
<img :src="userInfos.avatar" 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 }}
|
{{ userInfos.username === '' ? 'common' : userInfos.username }}
|
||||||
<el-icon class="el-icon--right">
|
<el-icon class="el-icon--right">
|
||||||
<ele-ArrowDown />
|
<ele-ArrowDown />
|
||||||
@@ -79,7 +103,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="layoutBreadcrumbUser">
|
<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 { useRouter } from 'vue-router';
|
||||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||||
import screenfull from 'screenfull';
|
import screenfull from 'screenfull';
|
||||||
@@ -90,7 +114,9 @@ import { useThemeConfig } from '/@/stores/themeConfig';
|
|||||||
import other from '/@/utils/other';
|
import other from '/@/utils/other';
|
||||||
import mittBus from '/@/utils/mitt';
|
import mittBus from '/@/utils/mitt';
|
||||||
import { Session, Local } from '/@/utils/storage';
|
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 UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
|
||||||
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
|
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
|
||||||
@@ -118,6 +144,21 @@ const layoutUserFlexNum = computed(() => {
|
|||||||
else num = '';
|
else num = '';
|
||||||
return 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 = () => {
|
const onScreenfullClick = () => {
|
||||||
if (!screenfull.isEnabled) {
|
if (!screenfull.isEnabled) {
|
||||||
@@ -256,5 +297,29 @@ const messageCenter = messageCenterStore();
|
|||||||
:deep(.el-badge__content.is-fixed) {
|
:deep(.el-badge__content.is-fixed) {
|
||||||
top: 12px;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -17,15 +17,15 @@
|
|||||||
<div class="upgrade-content">
|
<div class="upgrade-content">
|
||||||
{{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
|
{{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
|
||||||
<div class="mt5">
|
<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
|
CHANGELOG.md
|
||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
|
<div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="upgrade-btn">
|
<div class="upgrade-btn">
|
||||||
<el-button round size="default" type="info" text @click="onCancel">{{ $t('message.upgrade.btnOne') }}</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>
|
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading" >{{ state.btnTxt }}</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,10 +76,10 @@ const delayShow = () => {
|
|||||||
};
|
};
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// delayShow();
|
delayShow();
|
||||||
// setTimeout(() => {
|
setTimeout(() => {
|
||||||
// state.btnTxt = t('message.upgrade.btnTwo');
|
state.btnTxt = t('message.upgrade.btnTwo');
|
||||||
// }, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import piniaPersist from 'pinia-plugin-persist';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import fastCrud from './settings.ts';
|
import fastCrud from './settings.ts';
|
||||||
import pinia from './stores';
|
import pinia from './stores';
|
||||||
import permission from '/@/plugin/permission/index';
|
import {RegisterPermission} from '/@/plugin/permission/index';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import eIconPicker, { iconList, analyzingIconForIconfont } from 'e-icon-picker';
|
import eIconPicker, { iconList, analyzingIconForIconfont } from 'e-icon-picker';
|
||||||
import 'e-icon-picker/icon/default-icon/symbol.js'; //基本彩色图标库
|
import 'e-icon-picker/icon/default-icon/symbol.js'; //基本彩色图标库
|
||||||
@@ -54,7 +54,6 @@ other.elSvg(app);
|
|||||||
|
|
||||||
|
|
||||||
app.use(VXETable)
|
app.use(VXETable)
|
||||||
app.use(permission);
|
|
||||||
app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).use(fastCrud).mount('#app');
|
app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).use(fastCrud).mount('#app');
|
||||||
|
|
||||||
app.config.globalProperties.mittBus = mitt();
|
app.config.globalProperties.mittBus = mitt();
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import permissionDirective from './directive.permission'
|
import permissionDirective from './directive.permission'
|
||||||
import permissionFunc from './func.permission'
|
import permissionFunc from './func.permission'
|
||||||
const install = function (app:any) {
|
export const RegisterPermission = function (app:any) {
|
||||||
app.directive('permission', permissionDirective)
|
app.directive('permission', permissionDirective)
|
||||||
app.provide('$hasPermissions',permissionFunc.hasPermissions)
|
app.provide('$hasPermissions',permissionFunc.hasPermissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
|
||||||
install
|
|
||||||
}
|
|
||||||
|
|||||||
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 {
|
export interface UserInfosStates {
|
||||||
userInfos: UserInfosState;
|
userInfos: UserInfosState;
|
||||||
|
isSocketOpen: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// 路由缓存列表
|
// 路由缓存列表
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
isSocketOpen: false
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async updateUserInfos() {
|
async updateUserInfos() {
|
||||||
@@ -57,6 +58,9 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
Session.set('userInfo', this.userInfos);
|
Session.set('userInfo', this.userInfos);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async setWebSocketState(socketState: boolean) {
|
||||||
|
this.isSocketOpen = socketState;
|
||||||
|
},
|
||||||
async getApiUserInfo() {
|
async getApiUserInfo() {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/system/user/user_info/',
|
url: '/api/system/user/user_info/',
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
import { useUserInfo } from '/@/stores/userInfo';
|
|
||||||
import { judementSameArr } from '/@/utils/arrayOperation';
|
import { judementSameArr } from '/@/utils/arrayOperation';
|
||||||
|
import {BtnPermissionStore} from "/@/stores/btnPermission";
|
||||||
/**
|
/**
|
||||||
* 用户权限指令
|
* 用户权限指令
|
||||||
* @directive 单个权限验证(v-auth="xxx")
|
* @directive 单个权限验证(v-auth="xxx")
|
||||||
@@ -12,16 +11,16 @@ export function authDirective(app: App) {
|
|||||||
// 单个权限验证(v-auth="xxx")
|
// 单个权限验证(v-auth="xxx")
|
||||||
app.directive('auth', {
|
app.directive('auth', {
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
const stores = useUserInfo();
|
const stores = BtnPermissionStore();
|
||||||
if (!stores.userInfos.authBtnList.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
|
if (!stores.data.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
|
// 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
|
||||||
app.directive('auths', {
|
app.directive('auths', {
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
let flag = false;
|
let flag = false;
|
||||||
const stores = useUserInfo();
|
const stores = BtnPermissionStore();
|
||||||
stores.userInfos.authBtnList.map((val: string) => {
|
stores.data.map((val: string) => {
|
||||||
binding.value.map((v: string) => {
|
binding.value.map((v: string) => {
|
||||||
if (val === v) flag = true;
|
if (val === v) flag = true;
|
||||||
});
|
});
|
||||||
@@ -32,8 +31,8 @@ export function authDirective(app: App) {
|
|||||||
// 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]")
|
// 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]")
|
||||||
app.directive('auth-all', {
|
app.directive('auth-all', {
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
const stores = useUserInfo();
|
const stores = BtnPermissionStore();
|
||||||
const flag = judementSameArr(binding.value, stores.userInfos.authBtnList);
|
const flag = judementSameArr(binding.value, stores.data);
|
||||||
if (!flag) el.parentNode.removeChild(el);
|
if (!flag) el.parentNode.removeChild(el);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { useUserInfo } from '/@/stores/userInfo';
|
|
||||||
import { judementSameArr } from '/@/utils/arrayOperation';
|
import { judementSameArr } from '/@/utils/arrayOperation';
|
||||||
|
import {BtnPermissionStore} from "/@/stores/btnPermission";
|
||||||
/**
|
/**
|
||||||
* 单个权限验证
|
* 单个权限验证
|
||||||
* @param value 权限值
|
* @param value 权限值
|
||||||
* @returns 有权限,返回 `true`,反之则反
|
* @returns 有权限,返回 `true`,反之则反
|
||||||
*/
|
*/
|
||||||
export function auth(value: string): boolean {
|
export function auth(value: string): boolean {
|
||||||
const stores = useUserInfo();
|
const stores = BtnPermissionStore();
|
||||||
return stores.userInfos.authBtnList.some((v: string) => v === value);
|
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 {
|
export function auths(value: Array<string>): boolean {
|
||||||
let flag = false;
|
let flag = false;
|
||||||
const stores = useUserInfo();
|
const stores = BtnPermissionStore();
|
||||||
stores.userInfos.authBtnList.map((val: string) => {
|
stores.data.map((val: string) => {
|
||||||
value.map((v: string) => {
|
value.map((v: string) => {
|
||||||
if (val === v) flag = true;
|
if (val === v) flag = true;
|
||||||
});
|
});
|
||||||
@@ -33,6 +32,6 @@ export function auths(value: Array<string>): boolean {
|
|||||||
* @returns 有权限,返回 `true`,反之则反
|
* @returns 有权限,返回 `true`,反之则反
|
||||||
*/
|
*/
|
||||||
export function authAll(value: Array<string>): boolean {
|
export function authAll(value: Array<string>): boolean {
|
||||||
const stores = useUserInfo();
|
const stores = BtnPermissionStore();
|
||||||
return judementSameArr(value, stores.userInfos.authBtnList);
|
return judementSameArr(value, stores.data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
const cssCdnUrlList: Array<string> = [
|
const cssCdnUrlList: Array<string> = [
|
||||||
'//at.alicdn.com/t/font_2298093_y6u00apwst.css',
|
'//at.alicdn.com/t/font_2298093_y6u00apwst.css',
|
||||||
'//at.alicdn.com/t/c/font_3882322_9ah7y8m9175.css', //dvadmin3项目用icon
|
'//at.alicdn.com/t/c/font_3882322_9ah7y8m9175.css', //dvadmin3项目用icon
|
||||||
'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
|
|
||||||
];
|
];
|
||||||
// 第三方 js url
|
// 第三方 js url
|
||||||
const jsCdnUrlList: Array<string> = [];
|
const jsCdnUrlList: Array<string> = [];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {Session} from "/@/utils/storage";
|
|||||||
import {getWsBaseURL} from "/@/utils/baseUrl";
|
import {getWsBaseURL} from "/@/utils/baseUrl";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import socket from '@/types/api/socket'
|
import socket from '@/types/api/socket'
|
||||||
|
import {useUserInfo} from "/@/stores/userInfo";
|
||||||
const websocket: socket = {
|
const websocket: socket = {
|
||||||
websocket: null,
|
websocket: null,
|
||||||
connectURL: getWsBaseURL(),
|
connectURL: getWsBaseURL(),
|
||||||
@@ -42,6 +42,7 @@ const websocket: socket = {
|
|||||||
}
|
}
|
||||||
websocket.websocket.onclose = (e: any) => {
|
websocket.websocket.onclose = (e: any) => {
|
||||||
websocket.socket_open = false
|
websocket.socket_open = false
|
||||||
|
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||||
// 需要重新连接
|
// 需要重新连接
|
||||||
if (websocket.is_reonnect) {
|
if (websocket.is_reonnect) {
|
||||||
websocket.reconnect_timer = setTimeout(() => {
|
websocket.reconnect_timer = setTimeout(() => {
|
||||||
@@ -49,6 +50,8 @@ const websocket: socket = {
|
|||||||
if (websocket.reconnect_current > websocket.reconnect_count) {
|
if (websocket.reconnect_current > websocket.reconnect_count) {
|
||||||
clearTimeout(websocket.reconnect_timer)
|
clearTimeout(websocket.reconnect_timer)
|
||||||
websocket.is_reonnect = false
|
websocket.is_reonnect = false
|
||||||
|
websocket.socket_open = false
|
||||||
|
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 记录重连次数
|
// 记录重连次数
|
||||||
@@ -60,6 +63,7 @@ const websocket: socket = {
|
|||||||
// 连接成功
|
// 连接成功
|
||||||
websocket.websocket.onopen = function () {
|
websocket.websocket.onopen = function () {
|
||||||
websocket.socket_open = true
|
websocket.socket_open = true
|
||||||
|
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||||
websocket.is_reonnect = true
|
websocket.is_reonnect = true
|
||||||
// 开启心跳
|
// 开启心跳
|
||||||
websocket.heartbeat()
|
websocket.heartbeat()
|
||||||
@@ -85,17 +89,21 @@ const websocket: socket = {
|
|||||||
callback && callback()
|
callback && callback()
|
||||||
} else {
|
} else {
|
||||||
clearInterval(websocket.hearbeat_timer)
|
clearInterval(websocket.hearbeat_timer)
|
||||||
message({
|
// message({
|
||||||
type: 'warning',
|
// type: 'warning',
|
||||||
message: 'socket链接已断开',
|
// message: 'socket链接已断开',
|
||||||
duration: 1000,
|
// duration: 1000,
|
||||||
})
|
// })
|
||||||
|
websocket.socket_open = false
|
||||||
|
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
close: () => {
|
close: () => {
|
||||||
websocket.is_reonnect = false
|
websocket.is_reonnect = false
|
||||||
websocket.websocket.close()
|
websocket.websocket.close()
|
||||||
websocket.websocket = null;
|
websocket.websocket = null;
|
||||||
|
websocket.socket_open = false
|
||||||
|
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 重新连接
|
* 重新连接
|
||||||
|
|||||||
@@ -1,225 +1,244 @@
|
|||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
import {
|
||||||
import { dictionary } from '/@/utils/dictionary';
|
dict,
|
||||||
import { successMessage } from '/@/utils/message';
|
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 {
|
export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const pageRequest = async (query: UserPageQuery) => {
|
const pageRequest = async (query: UserPageQuery) => {
|
||||||
return await api.GetList(query);
|
return await api.GetList(query);
|
||||||
};
|
};
|
||||||
const editRequest = async ({ form, row }: EditReq) => {
|
const editRequest = async ({form, row}: EditReq) => {
|
||||||
form.id = row.id;
|
form.id = row.id;
|
||||||
return await api.UpdateObj(form);
|
return await api.UpdateObj(form);
|
||||||
};
|
};
|
||||||
const delRequest = async ({ row }: DelReq) => {
|
const delRequest = async ({row}: DelReq) => {
|
||||||
return await api.DelObj(row.id);
|
return await api.DelObj(row.id);
|
||||||
};
|
};
|
||||||
const addRequest = async ({ form }: AddReq) => {
|
const addRequest = async ({form}: AddReq) => {
|
||||||
return await api.AddObj(form);
|
return await api.AddObj(form);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 懒加载
|
* 懒加载
|
||||||
* @param row
|
* @param row
|
||||||
* @returns {Promise<unknown>}
|
* @returns {Promise<unknown>}
|
||||||
*/
|
*/
|
||||||
const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
|
const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
|
||||||
pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
|
pageRequest({pcode: tree.code}).then((res: APIResponseData) => {
|
||||||
resolve(res.data);
|
resolve(res.data);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
request: {
|
request: {
|
||||||
pageRequest,
|
pageRequest,
|
||||||
addRequest,
|
addRequest,
|
||||||
editRequest,
|
editRequest,
|
||||||
delRequest,
|
delRequest,
|
||||||
},
|
},
|
||||||
rowHandle: {
|
actionbar: {
|
||||||
//固定右侧
|
buttons: {
|
||||||
fixed: 'right',
|
add: {
|
||||||
width: 200,
|
show: auth('area:Create'),
|
||||||
buttons: {
|
}
|
||||||
view: {
|
}
|
||||||
show: false,
|
},
|
||||||
},
|
rowHandle: {
|
||||||
edit: {
|
//固定右侧
|
||||||
iconRight: 'Edit',
|
fixed: 'right',
|
||||||
type: 'text',
|
width: 200,
|
||||||
},
|
buttons: {
|
||||||
remove: {
|
view: {
|
||||||
iconRight: 'Delete',
|
show: false,
|
||||||
type: 'text',
|
},
|
||||||
},
|
edit: {
|
||||||
},
|
iconRight: 'Edit',
|
||||||
},
|
type: 'text',
|
||||||
pagination: {
|
show: auth('area:Update')
|
||||||
show: false,
|
},
|
||||||
},
|
remove: {
|
||||||
table: {
|
iconRight: 'Delete',
|
||||||
rowKey: 'id',
|
type: 'text',
|
||||||
lazy: true,
|
show: auth('area:Delete')
|
||||||
load: loadContentMethod,
|
},
|
||||||
treeProps: { children: 'children', hasChildren: 'hasChild' },
|
},
|
||||||
},
|
},
|
||||||
columns: {
|
pagination: {
|
||||||
_index: {
|
show: false,
|
||||||
title: '序号',
|
},
|
||||||
form: { show: false },
|
table: {
|
||||||
column: {
|
rowKey: 'id',
|
||||||
type: 'index',
|
lazy: true,
|
||||||
align: 'center',
|
load: loadContentMethod,
|
||||||
width: '70px',
|
treeProps: {children: 'children', hasChildren: 'hasChild'},
|
||||||
columnSetDisabled: true, //禁止在列设置中选择
|
},
|
||||||
},
|
columns: {
|
||||||
},
|
_index: {
|
||||||
// pcode: {
|
title: '序号',
|
||||||
// title: '父级地区',
|
form: {show: false},
|
||||||
// show: false,
|
column: {
|
||||||
// search: {
|
type: 'index',
|
||||||
// show: true,
|
align: 'center',
|
||||||
// },
|
width: '70px',
|
||||||
// type: 'dict-tree',
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
// form: {
|
},
|
||||||
// component: {
|
},
|
||||||
// showAllLevels: false, // 仅显示最后一级
|
// pcode: {
|
||||||
// props: {
|
// title: '父级地区',
|
||||||
// elProps: {
|
// show: false,
|
||||||
// clearable: true,
|
// search: {
|
||||||
// showAllLevels: false, // 仅显示最后一级
|
// show: true,
|
||||||
// props: {
|
// },
|
||||||
// checkStrictly: true, // 可以不需要选到最后一级
|
// type: 'dict-tree',
|
||||||
// emitPath: false,
|
// form: {
|
||||||
// clearable: true,
|
// component: {
|
||||||
// },
|
// showAllLevels: false, // 仅显示最后一级
|
||||||
// },
|
// props: {
|
||||||
// },
|
// elProps: {
|
||||||
// },
|
// clearable: true,
|
||||||
// },
|
// showAllLevels: false, // 仅显示最后一级
|
||||||
// },
|
// props: {
|
||||||
name: {
|
// checkStrictly: true, // 可以不需要选到最后一级
|
||||||
title: '名称',
|
// emitPath: false,
|
||||||
search: {
|
// clearable: true,
|
||||||
show: true,
|
// },
|
||||||
},
|
// },
|
||||||
treeNode: true,
|
// },
|
||||||
type: 'input',
|
// },
|
||||||
column:{
|
// },
|
||||||
minWidth: 120,
|
// },
|
||||||
},
|
name: {
|
||||||
form: {
|
title: '名称',
|
||||||
rules: [
|
search: {
|
||||||
// 表单校验规则
|
show: true,
|
||||||
{ required: true, message: '名称必填项' },
|
},
|
||||||
],
|
treeNode: true,
|
||||||
component: {
|
type: 'input',
|
||||||
placeholder: '请输入名称',
|
column: {
|
||||||
},
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
},
|
form: {
|
||||||
code: {
|
rules: [
|
||||||
title: '地区编码',
|
// 表单校验规则
|
||||||
search: {
|
{required: true, message: '名称必填项'},
|
||||||
show: true,
|
],
|
||||||
},
|
component: {
|
||||||
type: 'input',
|
placeholder: '请输入名称',
|
||||||
column:{
|
},
|
||||||
minWidth: 90,
|
},
|
||||||
},
|
},
|
||||||
form: {
|
code: {
|
||||||
rules: [
|
title: '地区编码',
|
||||||
// 表单校验规则
|
search: {
|
||||||
{ required: true, message: '地区编码必填项' },
|
show: true,
|
||||||
],
|
},
|
||||||
component: {
|
type: 'input',
|
||||||
placeholder: '请输入地区编码',
|
column: {
|
||||||
},
|
minWidth: 90,
|
||||||
},
|
},
|
||||||
},
|
form: {
|
||||||
pinyin: {
|
rules: [
|
||||||
title: '拼音',
|
// 表单校验规则
|
||||||
search: {
|
{required: true, message: '地区编码必填项'},
|
||||||
disabled: true,
|
],
|
||||||
},
|
component: {
|
||||||
type: 'input',
|
placeholder: '请输入地区编码',
|
||||||
column:{
|
},
|
||||||
minWidth: 120,
|
},
|
||||||
},
|
},
|
||||||
form: {
|
pinyin: {
|
||||||
rules: [
|
title: '拼音',
|
||||||
// 表单校验规则
|
search: {
|
||||||
{ required: true, message: '拼音必填项' },
|
disabled: true,
|
||||||
],
|
},
|
||||||
component: {
|
type: 'input',
|
||||||
placeholder: '请输入拼音',
|
column: {
|
||||||
},
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
},
|
form: {
|
||||||
level: {
|
rules: [
|
||||||
title: '地区层级',
|
// 表单校验规则
|
||||||
search: {
|
{required: true, message: '拼音必填项'},
|
||||||
disabled: true,
|
],
|
||||||
},
|
component: {
|
||||||
type: 'input',
|
placeholder: '请输入拼音',
|
||||||
column:{
|
},
|
||||||
minWidth: 100,
|
},
|
||||||
},
|
},
|
||||||
form: {
|
level: {
|
||||||
disabled: false,
|
title: '地区层级',
|
||||||
rules: [
|
search: {
|
||||||
// 表单校验规则
|
disabled: true,
|
||||||
{ required: true, message: '拼音必填项' },
|
},
|
||||||
],
|
type: 'input',
|
||||||
component: {
|
column: {
|
||||||
placeholder: '请输入拼音',
|
minWidth: 100,
|
||||||
},
|
},
|
||||||
},
|
form: {
|
||||||
},
|
disabled: false,
|
||||||
initials: {
|
rules: [
|
||||||
title: '首字母',
|
// 表单校验规则
|
||||||
column:{
|
{required: true, message: '拼音必填项'},
|
||||||
minWidth: 100,
|
],
|
||||||
},
|
component: {
|
||||||
form: {
|
placeholder: '请输入拼音',
|
||||||
rules: [
|
},
|
||||||
// 表单校验规则
|
},
|
||||||
{ required: true, message: '首字母必填项' },
|
},
|
||||||
],
|
initials: {
|
||||||
|
title: '首字母',
|
||||||
|
column: {
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
rules: [
|
||||||
|
// 表单校验规则
|
||||||
|
{required: true, message: '首字母必填项'},
|
||||||
|
],
|
||||||
|
|
||||||
component: {
|
component: {
|
||||||
placeholder: '请输入首字母',
|
placeholder: '请输入首字母',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
enable: {
|
enable: {
|
||||||
title: '是否启用',
|
title: '是否启用',
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
type: 'dict-radio',
|
type: 'dict-radio',
|
||||||
column: {
|
column: {
|
||||||
minWidth:90,
|
minWidth: 90,
|
||||||
component: {
|
component: {
|
||||||
name: 'fs-dict-switch',
|
name: 'fs-dict-switch',
|
||||||
activeText: '',
|
activeText: '',
|
||||||
inactiveText: '',
|
inactiveText: '',
|
||||||
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
||||||
onChange: compute((context) => {
|
onChange: compute((context) => {
|
||||||
return () => {
|
return () => {
|
||||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||||
successMessage(res.msg as string);
|
successMessage(res.msg as string);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dict: dict({
|
dict: dict({
|
||||||
data: dictionary('button_status_bool'),
|
data: dictionary('button_status_bool'),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<fs-page class="columns">
|
<fs-page class="columns">
|
||||||
<el-row class="columns-el-row" :gutter="10">
|
<el-row class="columns-el-row" :gutter="10">
|
||||||
<el-col :span="4">
|
<el-col :span="6">
|
||||||
<div class="columns-box columns-left">
|
<div class="columns-box columns-left">
|
||||||
<ItemCom title="角色" type="role" showPagination @fetchData="fetchRoleData" @itemClick="handleClick" />
|
<ItemCom title="角色" type="role" showPagination @fetchData="fetchRoleData" @itemClick="handleClick" />
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="4">
|
<!-- <el-col :span="4">-->
|
||||||
<div class="columns-box columns-left">
|
<!-- <div class="columns-box columns-left">-->
|
||||||
<ItemCom title="菜单" type="menu" showPagination @fetchData="fetchMenuData" @itemClick="handleClick" />
|
<!-- <ItemCom title="菜单" type="menu" showPagination @fetchData="fetchMenuData" @itemClick="handleClick" />-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</el-col>
|
<!-- </el-col>-->
|
||||||
<el-col :span="6">
|
<el-col :span="8">
|
||||||
<div class="columns-box columns-center">
|
<div class="columns-box columns-center">
|
||||||
<ItemCom title="模型表" type="model" label="showText" value="key" @fetchData="fetchModelData" @itemClick="handleClick" />
|
<ItemCom title="模型表" type="model" label="showText" value="key" @fetchData="fetchModelData" @itemClick="handleClick" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@
|
|||||||
list-type="picture-card"
|
list-type="picture-card"
|
||||||
>
|
>
|
||||||
<i class="el-icon-plus"></i>
|
<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-upload>
|
||||||
<el-dialog :visible.sync="dialogImgVisible">
|
<el-dialog :visible.sync="dialogImgVisible">
|
||||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
list-type="picture-card"
|
list-type="picture-card"
|
||||||
>
|
>
|
||||||
<i class="el-icon-plus"></i>
|
<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-upload>
|
||||||
<el-dialog :visible.sync="dialogImgVisible">
|
<el-dialog :visible.sync="dialogImgVisible">
|
||||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||||
@@ -506,4 +506,8 @@ watch(
|
|||||||
);
|
);
|
||||||
</script>
|
</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-input v-model="deptFormData.key" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="负责人">
|
<el-form-item label="负责人">
|
||||||
<el-input v-model="deptFormData.owner" />
|
<el-input v-model="deptFormData.owner" placeholder="请输入" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注">
|
<el-form-item label="备注">
|
||||||
<el-input v-model="deptFormData.description" maxlength="200" show-word-limit type="textarea" />
|
<el-input v-model="deptFormData.description" maxlength="200" show-word-limit type="textarea" />
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { request, downloadFile } from '/@/utils/service';
|
import { request, downloadFile } from '/@/utils/service';
|
||||||
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||||
|
|
||||||
type GetListType = PageQuery & { show_all: string }
|
type GetListType = PageQuery & { show_all: string };
|
||||||
|
|
||||||
export const apiPrefix = '/api/system/user/';
|
export const apiPrefix = '/api/system/user/';
|
||||||
|
|
||||||
export function GetDept(query: PageQuery) {
|
export function GetDept(query: PageQuery) {
|
||||||
return request({
|
return request({
|
||||||
url: "/api/system/dept/dept_lazy_tree/",
|
url: '/api/system/dept/dept_lazy_tree/',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: query,
|
params: query,
|
||||||
});
|
});
|
||||||
@@ -55,8 +55,8 @@ export function exportData(params: any) {
|
|||||||
return downloadFile({
|
return downloadFile({
|
||||||
url: apiPrefix + 'export_data/',
|
url: apiPrefix + 'export_data/',
|
||||||
params: params,
|
params: params,
|
||||||
method: 'get'
|
method: 'get',
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDeptInfoById(id: string, type: string) {
|
export function getDeptInfoById(id: string, type: string) {
|
||||||
@@ -65,3 +65,11 @@ export function getDeptInfoById(id: string, type: string) {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resetPwd(id: number, data: { [key: string]: string }) {
|
||||||
|
return request({
|
||||||
|
url: `/api/system/user/${id}/reset_password/`,
|
||||||
|
method: 'put',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { request } from '/@/utils/service';
|
|||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
import { dictionary } from '/@/utils/dictionary';
|
import { dictionary } from '/@/utils/dictionary';
|
||||||
import { successMessage } from '/@/utils/message';
|
import { successMessage } from '/@/utils/message';
|
||||||
|
import {auth} from "/@/utils/authFunction";
|
||||||
|
|
||||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const pageRequest = async (query: UserPageQuery) => {
|
const pageRequest = async (query: UserPageQuery) => {
|
||||||
@@ -39,9 +40,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
return await api.exportData(query);
|
return await api.exportData(query);
|
||||||
};
|
};
|
||||||
|
|
||||||
//权限判定
|
|
||||||
const hasPermissions: any = inject('$hasPermissions');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
table: {
|
table: {
|
||||||
@@ -58,12 +56,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
actionbar: {
|
actionbar: {
|
||||||
buttons: {
|
buttons: {
|
||||||
add: {
|
add: {
|
||||||
show: hasPermissions('user:Create'),
|
show: auth('user:Create')
|
||||||
// show:true
|
|
||||||
},
|
},
|
||||||
export: {
|
export: {
|
||||||
text: '导出', //按钮文字
|
text: '导出', //按钮文字
|
||||||
title: '导出', //鼠标停留显示的信息
|
title: '导出', //鼠标停留显示的信息
|
||||||
|
show: auth('user:Export'),
|
||||||
click() {
|
click() {
|
||||||
return exportRequest(crudExpose!.getSearchFormData());
|
return exportRequest(crudExpose!.getSearchFormData());
|
||||||
},
|
},
|
||||||
@@ -72,6 +70,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
container: {
|
container: {
|
||||||
|
layout: 'multi-line',
|
||||||
action: {
|
action: {
|
||||||
col: {
|
col: {
|
||||||
span: 10,
|
span: 10,
|
||||||
@@ -88,21 +87,22 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
show: hasPermissions('user:Update'),
|
show: auth('user:Update'),
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
show: hasPermissions('user:Delete'),
|
show: auth('user:Delete'),
|
||||||
},
|
},
|
||||||
custom: {
|
custom: {
|
||||||
text: '重设密码',
|
text: '重设密码',
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
show: hasPermissions('user:ResetPassword'),
|
show: auth('user:ResetPassword'),
|
||||||
tooltip: {
|
tooltip: {
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
content: '重设密码',
|
content: '重设密码',
|
||||||
},
|
},
|
||||||
click: (ctx: any) => {
|
click: (ctx: any) => {
|
||||||
const { row } = ctx;
|
const { row } = ctx;
|
||||||
|
context?.handleResetPwdOpen(row);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -264,7 +264,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
url: '/api/system/role/',
|
url: '/api/system/role/',
|
||||||
value: 'id',
|
value: 'id',
|
||||||
label: 'name',
|
label: 'name',
|
||||||
isTree: true,
|
|
||||||
getData: async ({ url }: { url: string }) => {
|
getData: async ({ url }: { url: string }) => {
|
||||||
return request({
|
return request({
|
||||||
url: url,
|
url: url,
|
||||||
|
|||||||
@@ -40,19 +40,34 @@
|
|||||||
<el-button :icon="!showCount ? 'Hide' : 'View'" circle @click="showCount = !showCount"></el-button>
|
<el-button :icon="!showCount ? 'Hide' : 'View'" circle @click="showCount = !showCount"></el-button>
|
||||||
</template>
|
</template>
|
||||||
<template #actionbar-right>
|
<template #actionbar-right>
|
||||||
<importExcel api="api/system/user/">导入 </importExcel>
|
<importExcel api="api/system/user/" v-auth="'user:Import'">导入 </importExcel>
|
||||||
</template>
|
</template>
|
||||||
</fs-crud>
|
</fs-crud>
|
||||||
|
|
||||||
|
<el-dialog v-model="resetPwdVisible" title="重设密码" width="400px" draggable :before-close="handleResetPwdClose">
|
||||||
|
<div>
|
||||||
|
<el-input v-model="resetPwdFormState.newPassword" type="password" placeholder="请输入密码" show-password style="margin-bottom: 20px" />
|
||||||
|
<el-input v-model="resetPwdFormState.newPassword2" type="password" placeholder="请再次输入密码" show-password />
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleResetPwdClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleResetPwdSubmit"> 保存 </el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="user">
|
<script lang="ts" setup name="user">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||||
|
import { Md5 } from 'ts-md5';
|
||||||
import { createCrudOptions } from './crud';
|
import { createCrudOptions } from './crud';
|
||||||
import importExcel from '/@/components/importExcel/index.vue';
|
import importExcel from '/@/components/importExcel/index.vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import { ECharts, EChartsOption, init } from 'echarts';
|
import { ECharts, EChartsOption, init } from 'echarts';
|
||||||
import { getDeptInfoById } from './api';
|
import { getDeptInfoById, resetPwd } from './api';
|
||||||
|
import { warningNotification, successNotification } from '/@/utils/message';
|
||||||
import { HeadDeptInfoType } from '../../types';
|
import { HeadDeptInfoType } from '../../types';
|
||||||
|
|
||||||
let deptCountChart: ECharts;
|
let deptCountChart: ECharts;
|
||||||
@@ -72,6 +87,13 @@ let isShowChildFlag = ref(false);
|
|||||||
let deptInfo = ref<Partial<HeadDeptInfoType>>({});
|
let deptInfo = ref<Partial<HeadDeptInfoType>>({});
|
||||||
let showCount = ref(false);
|
let showCount = ref(false);
|
||||||
|
|
||||||
|
let resetPwdVisible = ref(false);
|
||||||
|
let resetPwdFormState = reactive({
|
||||||
|
id: 0,
|
||||||
|
newPassword: '',
|
||||||
|
newPassword2: '',
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化顶部部门折线图
|
* 初始化顶部部门折线图
|
||||||
*/
|
*/
|
||||||
@@ -189,11 +211,50 @@ const handleSwitchChange = () => {
|
|||||||
handleDoRefreshUser(currentDeptId.value);
|
handleDoRefreshUser(currentDeptId.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResetPwdOpen = ({ id }: { id: number }) => {
|
||||||
|
resetPwdFormState.id = id;
|
||||||
|
resetPwdVisible.value = true;
|
||||||
|
};
|
||||||
|
const handleResetPwdClose = () => {
|
||||||
|
resetPwdVisible.value = false;
|
||||||
|
resetPwdFormState.id = 0;
|
||||||
|
resetPwdFormState.newPassword = '';
|
||||||
|
resetPwdFormState.newPassword2 = '';
|
||||||
|
};
|
||||||
|
const handleResetPwdSubmit = async () => {
|
||||||
|
if (!resetPwdFormState.id) {
|
||||||
|
warningNotification('请选择用户!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!resetPwdFormState.newPassword || !resetPwdFormState.newPassword2) {
|
||||||
|
warningNotification('请输入密码!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resetPwdFormState.newPassword !== resetPwdFormState.newPassword2) {
|
||||||
|
warningNotification('两次输入密码不一致');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pwdRegex = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z]).{8,30}');
|
||||||
|
if (!pwdRegex.test(resetPwdFormState.newPassword) || !pwdRegex.test(resetPwdFormState.newPassword2)) {
|
||||||
|
warningNotification('您的密码复杂度太低(密码中必须包含字母、数字)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await resetPwd(resetPwdFormState.id, {
|
||||||
|
newPassword: Md5.hashStr(resetPwdFormState.newPassword),
|
||||||
|
newPassword2: Md5.hashStr(resetPwdFormState.newPassword2),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res?.code === 2000) {
|
||||||
|
successNotification(res.msg || '修改成功!');
|
||||||
|
handleResetPwdClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
crudExpose.doRefresh();
|
|
||||||
deptCountChart = init(deptCountBar.value as HTMLElement);
|
deptCountChart = init(deptCountBar.value as HTMLElement);
|
||||||
deptSexChart = init(deptSexPie.value as HTMLElement);
|
deptSexChart = init(deptSexPie.value as HTMLElement);
|
||||||
getDeptInfo();
|
getDeptInfo();
|
||||||
|
crudExpose.doRefresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
@@ -201,7 +262,7 @@ defineExpose({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 你的crud配置
|
// 你的crud配置
|
||||||
const { crudOptions } = createCrudOptions({ crudExpose, context: { getDeptInfo, isShowChildFlag } });
|
const { crudOptions } = createCrudOptions({ crudExpose, context: { getDeptInfo, isShowChildFlag, handleResetPwdOpen } });
|
||||||
|
|
||||||
// 初始化crud配置
|
// 初始化crud配置
|
||||||
const { resetCrudOptions } = useCrud({
|
const { resetCrudOptions } = useCrud({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOption
|
|||||||
import { dictionary } from '/@/utils/dictionary';
|
import { dictionary } from '/@/utils/dictionary';
|
||||||
import { inject, nextTick, ref } from 'vue';
|
import { inject, nextTick, ref } from 'vue';
|
||||||
import { successMessage } from '/@/utils/message';
|
import { successMessage } from '/@/utils/message';
|
||||||
|
import {auth} from '/@/utils/authFunction';
|
||||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const pageRequest = async (query: UserPageQuery) => {
|
const pageRequest = async (query: UserPageQuery) => {
|
||||||
return await api.GetList(query);
|
return await api.GetList(query);
|
||||||
@@ -19,8 +19,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
return await api.AddObj(form);
|
return await api.AddObj(form);
|
||||||
};
|
};
|
||||||
|
|
||||||
//权限判定
|
|
||||||
const hasPermissions = inject('$hasPermissions');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
@@ -40,17 +38,17 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
edit: {
|
edit: {
|
||||||
iconRight: 'Edit',
|
iconRight: 'Edit',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
show: hasPermissions('dictionary:Update'),
|
show: auth('dictionary:Update'),
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
iconRight: 'Delete',
|
iconRight: 'Delete',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
show: hasPermissions('dictionary:Delete'),
|
show: auth('dictionary:Delete'),
|
||||||
},
|
},
|
||||||
custom: {
|
custom: {
|
||||||
text: '字典配置',
|
text: '字典配置',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
show: hasPermissions('dictionary:Update'),
|
show: auth('dictionary:Update'),
|
||||||
tooltip: {
|
tooltip: {
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
content: '字典配置',
|
content: '字典配置',
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer size="70%" v-model="drawer" direction="rtl" destroy-on-close :before-close="handleClose">
|
<el-drawer size="70%" v-model="drawer" direction="rtl" destroy-on-close :before-close="handleClose">
|
||||||
<fs-page>
|
<div>
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||||
</fs-page>
|
</div>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
//固定右侧
|
//固定右侧
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 200,
|
width: 200,
|
||||||
|
show:false,
|
||||||
buttons: {
|
buttons: {
|
||||||
view: {
|
view: {
|
||||||
show: false,
|
show: false,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<div class="login-left-logo">
|
<div class="login-left-logo">
|
||||||
<img :src="logoMini" />
|
<img :src="logoMini" />
|
||||||
<div class="login-left-logo-text">
|
<div class="login-left-logo-text">
|
||||||
<span>{{ getThemeConfig.globalViceTitle }}</span>
|
<span>{{ getSystemConfig['login.site_title']||getThemeConfig.globalViceTitle }}</span>
|
||||||
<span class="login-left-logo-text-msg">{{ getThemeConfig.globalViceTitleMsg }}</span>
|
<span class="login-left-logo-text-msg">{{ getSystemConfig['login.site_name']||getThemeConfig.globalViceTitleMsg }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-left-img">
|
<div class="login-left-img">
|
||||||
@@ -42,15 +42,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="login-authorization">
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,7 +64,7 @@ import { NextLoading } from '/@/utils/loading';
|
|||||||
import logoMini from '/@/assets/logo-mini.svg';
|
import logoMini from '/@/assets/logo-mini.svg';
|
||||||
import loginMain from '/@/assets/login-main.svg';
|
import loginMain from '/@/assets/login-main.svg';
|
||||||
import loginBg from '/@/assets/login-bg.svg';
|
import loginBg from '/@/assets/login-bg.svg';
|
||||||
|
import {SystemConfigStore} from '/@/stores/systemConfig'
|
||||||
// 引入组件
|
// 引入组件
|
||||||
const Account = defineAsyncComponent(() => import('/@/views/system/login/component/account.vue'));
|
const Account = defineAsyncComponent(() => import('/@/views/system/login/component/account.vue'));
|
||||||
const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
|
const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
|
||||||
@@ -82,6 +82,14 @@ const state = reactive({
|
|||||||
const getThemeConfig = computed(() => {
|
const getThemeConfig = computed(() => {
|
||||||
return themeConfig.value;
|
return themeConfig.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const systemConfigStore = SystemConfigStore()
|
||||||
|
const {systemConfig} = storeToRefs(systemConfigStore)
|
||||||
|
const getSystemConfig = computed(()=>{
|
||||||
|
return systemConfig.value
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
NextLoading.done();
|
NextLoading.done();
|
||||||
|
|||||||
@@ -1,202 +1,215 @@
|
|||||||
import { AddReq, DelReq, EditReq, dict, CreateCrudOptionsRet, CreateCrudOptionsProps } from '@fast-crud/fast-crud';
|
import {AddReq, DelReq, EditReq, dict, CreateCrudOptionsRet, CreateCrudOptionsProps} from '@fast-crud/fast-crud';
|
||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
|
import {auth} from '/@/utils/authFunction'
|
||||||
import { request } from '/@/utils/service';
|
import {request} from '/@/utils/service';
|
||||||
//此处为crudOptions配置
|
//此处为crudOptions配置
|
||||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const pageRequest = async () => {
|
const pageRequest = async () => {
|
||||||
if (context!.selectOptions.value.id) {
|
if (context!.selectOptions.value.id) {
|
||||||
return await api.GetList({ menu: context!.selectOptions.value.id } as any);
|
return await api.GetList({menu: context!.selectOptions.value.id} as any);
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const editRequest = async ({ form, row }: EditReq) => {
|
const editRequest = async ({form, row}: EditReq) => {
|
||||||
return await api.UpdateObj({ ...form, menu: row.menu });
|
return await api.UpdateObj({...form, menu: row.menu});
|
||||||
};
|
};
|
||||||
const delRequest = async ({ row }: DelReq) => {
|
const delRequest = async ({row}: DelReq) => {
|
||||||
return await api.DelObj(row.id);
|
return await api.DelObj(row.id);
|
||||||
};
|
};
|
||||||
const addRequest = async ({ form }: AddReq) => {
|
const addRequest = async ({form}: AddReq) => {
|
||||||
return await api.AddObj({ ...form, ...{ menu: context!.selectOptions.value.id } });
|
return await api.AddObj({...form, ...{menu: context!.selectOptions.value.id}});
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
search: {
|
pagination:{
|
||||||
container: {
|
show:false
|
||||||
action: {
|
},
|
||||||
//按钮栏配置
|
search: {
|
||||||
col: {
|
container: {
|
||||||
span: 8,
|
action: {
|
||||||
},
|
//按钮栏配置
|
||||||
},
|
col: {
|
||||||
},
|
span: 8,
|
||||||
},
|
},
|
||||||
rowHandle: {
|
},
|
||||||
//固定右侧
|
},
|
||||||
fixed: 'right',
|
},
|
||||||
width: 200,
|
actionbar: {
|
||||||
buttons: {
|
buttons: {
|
||||||
view: {
|
add: {
|
||||||
show: false,
|
show: auth('btn:Create')
|
||||||
},
|
},
|
||||||
edit: {
|
},
|
||||||
icon: '',
|
},
|
||||||
type: 'primary',
|
rowHandle: {
|
||||||
},
|
//固定右侧
|
||||||
remove: {
|
fixed: 'right',
|
||||||
icon: '',
|
width: 200,
|
||||||
type: 'primary',
|
buttons: {
|
||||||
},
|
view: {
|
||||||
},
|
show: false,
|
||||||
},
|
},
|
||||||
request: {
|
edit: {
|
||||||
pageRequest,
|
icon: '',
|
||||||
addRequest,
|
type: 'primary',
|
||||||
editRequest,
|
show: auth('btn:Update')
|
||||||
delRequest,
|
},
|
||||||
},
|
remove: {
|
||||||
form: {
|
show: auth('btn:Delete')
|
||||||
col: { span: 24 },
|
},
|
||||||
labelWidth: '100px',
|
},
|
||||||
wrapper: {
|
},
|
||||||
is: 'el-dialog',
|
request: {
|
||||||
width: '600px',
|
pageRequest,
|
||||||
},
|
addRequest,
|
||||||
},
|
editRequest,
|
||||||
columns: {
|
delRequest,
|
||||||
_index: {
|
},
|
||||||
title: '序号',
|
form: {
|
||||||
form: { show: false },
|
col: {span: 24},
|
||||||
column: {
|
labelWidth: '100px',
|
||||||
type: 'index',
|
wrapper: {
|
||||||
align: 'center',
|
is: 'el-dialog',
|
||||||
width: '70px',
|
width: '600px',
|
||||||
columnSetDisabled: true, //禁止在列设置中选择
|
},
|
||||||
},
|
},
|
||||||
},
|
columns: {
|
||||||
search: {
|
_index: {
|
||||||
title: '关键词',
|
title: '序号',
|
||||||
column: { show: false },
|
form: {show: false},
|
||||||
type: 'text',
|
column: {
|
||||||
search: { show: true },
|
type: 'index',
|
||||||
form: {
|
align: 'center',
|
||||||
show: false,
|
width: '70px',
|
||||||
component: {
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
placeholder: '输入关键词搜索',
|
},
|
||||||
},
|
},
|
||||||
},
|
search: {
|
||||||
},
|
title: '关键词',
|
||||||
id: {
|
column: {show: false},
|
||||||
title: 'ID',
|
type: 'text',
|
||||||
type: 'text',
|
search: {show: true},
|
||||||
column: { show: false },
|
form: {
|
||||||
search: { show: false },
|
show: false,
|
||||||
form: { show: false },
|
component: {
|
||||||
},
|
placeholder: '输入关键词搜索',
|
||||||
name: {
|
},
|
||||||
title: '权限名称',
|
},
|
||||||
type: 'text',
|
},
|
||||||
search: { show: true },
|
id: {
|
||||||
column: {
|
title: 'ID',
|
||||||
minWidth: 120,
|
type: 'text',
|
||||||
sortable: true,
|
column: {show: false},
|
||||||
},
|
search: {show: false},
|
||||||
form: {
|
form: {show: false},
|
||||||
rules: [{ required: true, message: '权限名称必填' }],
|
},
|
||||||
component: {
|
name: {
|
||||||
placeholder: '输入权限名称搜索',
|
title: '权限名称',
|
||||||
props: {
|
type: 'text',
|
||||||
clearable: true,
|
search: {show: true},
|
||||||
allowCreate: true,
|
column: {
|
||||||
filterable: true,
|
minWidth: 120,
|
||||||
},
|
sortable: true,
|
||||||
},
|
},
|
||||||
helper: {
|
form: {
|
||||||
render() {
|
rules: [{required: true, message: '权限名称必填'}],
|
||||||
return <el-alert title="手动输入" type="warning" description="页面中按钮的名称或者自定义一个名称" />;
|
component: {
|
||||||
},
|
placeholder: '输入权限名称搜索',
|
||||||
},
|
props: {
|
||||||
},
|
clearable: true,
|
||||||
},
|
allowCreate: true,
|
||||||
value: {
|
filterable: true,
|
||||||
title: '权限值',
|
},
|
||||||
type: 'text',
|
},
|
||||||
search: { show: false },
|
helper: {
|
||||||
column: {
|
render() {
|
||||||
width: 120,
|
return <el-alert title="手动输入" type="warning"
|
||||||
sortable: true,
|
description="页面中按钮的名称或者自定义一个名称"/>;
|
||||||
},
|
},
|
||||||
form: {
|
},
|
||||||
rules: [{ required: true, message: '权限标识必填' }],
|
},
|
||||||
placeholder: '输入权限标识',
|
},
|
||||||
helper: {
|
value: {
|
||||||
render() {
|
title: '权限值',
|
||||||
return <el-alert title="唯一值" type="warning" description="用于判断前端按钮权限或接口权限" />;
|
type: 'text',
|
||||||
},
|
search: {show: false},
|
||||||
},
|
column: {
|
||||||
},
|
width: 200,
|
||||||
},
|
sortable: true,
|
||||||
method: {
|
},
|
||||||
title: '请求方式',
|
form: {
|
||||||
search: { show: false },
|
rules: [{required: true, message: '权限标识必填'}],
|
||||||
type: 'dict-select',
|
placeholder: '输入权限标识',
|
||||||
column: {
|
helper: {
|
||||||
width: 120,
|
render() {
|
||||||
sortable: true,
|
return <el-alert title="唯一值" type="warning"
|
||||||
},
|
description="用于判断前端按钮权限或接口权限"/>;
|
||||||
dict: dict({
|
},
|
||||||
data: [
|
},
|
||||||
{ label: 'GET', value: 0 },
|
},
|
||||||
{ label: 'POST', value: 1, color: 'success' },
|
},
|
||||||
{ label: 'PUT', value: 2, color: 'warning' },
|
method: {
|
||||||
{ label: 'DELETE', value: 3, color: 'danger' },
|
title: '请求方式',
|
||||||
],
|
search: {show: false},
|
||||||
}),
|
type: 'dict-select',
|
||||||
form: {
|
column: {
|
||||||
rules: [{ required: true, message: '必填项' }],
|
width: 120,
|
||||||
},
|
sortable: true,
|
||||||
},
|
},
|
||||||
api: {
|
dict: dict({
|
||||||
title: '接口地址',
|
data: [
|
||||||
search: { show: false },
|
{label: 'GET', value: 0},
|
||||||
type: 'dict-select',
|
{label: 'POST', value: 1, color: 'success'},
|
||||||
dict: dict({
|
{label: 'PUT', value: 2, color: 'warning'},
|
||||||
getData() {
|
{label: 'DELETE', value: 3, color: 'danger'},
|
||||||
return request({ url: '/swagger.json' }).then((res: any) => {
|
],
|
||||||
const ret = Object.keys(res.paths);
|
}),
|
||||||
const data = [];
|
form: {
|
||||||
for (const item of ret) {
|
rules: [{required: true, message: '必填项'}],
|
||||||
const obj: any = {};
|
},
|
||||||
obj.label = item;
|
},
|
||||||
obj.value = item;
|
api: {
|
||||||
data.push(obj);
|
title: '接口地址',
|
||||||
}
|
search: {show: false},
|
||||||
return data;
|
type: 'dict-select',
|
||||||
});
|
dict: dict({
|
||||||
},
|
getData() {
|
||||||
}),
|
return request({url: '/swagger.json'}).then((res: any) => {
|
||||||
column: {
|
const ret = Object.keys(res.paths);
|
||||||
minWidth: 250,
|
const data = [];
|
||||||
sortable: true,
|
for (const item of ret) {
|
||||||
},
|
const obj: any = {};
|
||||||
form: {
|
obj.label = item;
|
||||||
rules: [{ required: true, message: '必填项' }],
|
obj.value = item;
|
||||||
component: {
|
data.push(obj);
|
||||||
props: {
|
}
|
||||||
allowCreate: true,
|
return data;
|
||||||
filterable: true,
|
});
|
||||||
clearable: true,
|
},
|
||||||
},
|
}),
|
||||||
},
|
column: {
|
||||||
helper: {
|
minWidth: 250,
|
||||||
render() {
|
sortable: true,
|
||||||
return <el-alert title="请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/" type="warning" />;
|
},
|
||||||
},
|
form: {
|
||||||
},
|
rules: [{required: true, message: '必填项'}],
|
||||||
},
|
component: {
|
||||||
},
|
props: {
|
||||||
},
|
allowCreate: true,
|
||||||
},
|
filterable: true,
|
||||||
};
|
clearable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
render() {
|
||||||
|
return <el-alert title="请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/"
|
||||||
|
type="warning"/>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
65
web/src/views/system/menu/components/MenuFieldCom/api.ts
Normal file
65
web/src/views/system/menu/components/MenuFieldCom/api.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { request } from '/@/utils/service';
|
||||||
|
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||||
|
import XEUtils from "xe-utils";
|
||||||
|
import {CurrentInfoType} from "/@/views/system/columns/types";
|
||||||
|
|
||||||
|
export const apiPrefix = '/api/system/column/';
|
||||||
|
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 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有model
|
||||||
|
*/
|
||||||
|
export function getModelList() {
|
||||||
|
return request({
|
||||||
|
url: '/api/system/column/get_models/',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动匹配field
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export function automatchColumnsData(data: CurrentInfoType) {
|
||||||
|
return request({
|
||||||
|
url: '/api/system/column/auto_match_fields/',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
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>
|
||||||
@@ -41,31 +41,31 @@
|
|||||||
|
|
||||||
<div class="mtc-tags">
|
<div class="mtc-tags">
|
||||||
<el-tooltip effect="dark" content="新增">
|
<el-tooltip effect="dark" content="新增">
|
||||||
<el-icon size="16" @click="handleUpdateMenu('create')" class="mtc-tags-icon">
|
<el-icon size="16" v-auth="'menu:Create'" @click="handleUpdateMenu('create')" class="mtc-tags-icon">
|
||||||
<Plus />
|
<Plus />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<el-tooltip effect="dark" content="编辑">
|
<el-tooltip effect="dark" content="编辑">
|
||||||
<el-icon size="16" @click="handleUpdateMenu('update')" class="mtc-tags-icon">
|
<el-icon size="16" v-auth="'menu:Update'" @click="handleUpdateMenu('update')" class="mtc-tags-icon">
|
||||||
<Edit />
|
<Edit />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<el-tooltip effect="dark" content="上移">
|
<el-tooltip effect="dark" content="上移">
|
||||||
<el-icon size="16" @click="handleSort('up')" class="mtc-tags-icon">
|
<el-icon size="16" v-auth="'menu:MoveUp'" @click="handleSort('up')" class="mtc-tags-icon">
|
||||||
<Top />
|
<Top />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<el-tooltip effect="dark" content="下移">
|
<el-tooltip effect="dark" content="下移">
|
||||||
<el-icon size="16" @click="handleSort('down')" class="mtc-tags-icon">
|
<el-icon size="16" v-auth="'menu:MoveDown'" @click="handleSort('down')" class="mtc-tags-icon">
|
||||||
<Bottom />
|
<Bottom />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<el-tooltip effect="dark" content="删除">
|
<el-tooltip effect="dark" content="删除">
|
||||||
<el-icon size="16" @click="handleDeleteMenu()" class="mtc-tags-icon">
|
<el-icon size="16" v-auth="'menu:Delete'" @click="handleDeleteMenu()" class="mtc-tags-icon">
|
||||||
<Delete />
|
<Delete />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|||||||
@@ -14,9 +14,19 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :span="18">
|
<el-col :span="18">
|
||||||
<div class="menu-box menu-right-box">
|
<el-tabs type="border-card">
|
||||||
<MenuButtonCom ref="menuButtonRef" />
|
<el-tab-pane label="按钮权限配置" >
|
||||||
</div>
|
<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-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
@@ -39,6 +49,7 @@ import { ElMessageBox } from 'element-plus';
|
|||||||
import MenuTreeCom from './components/MenuTreeCom/index.vue';
|
import MenuTreeCom from './components/MenuTreeCom/index.vue';
|
||||||
import MenuButtonCom from './components/MenuButtonCom/index.vue';
|
import MenuButtonCom from './components/MenuButtonCom/index.vue';
|
||||||
import MenuFormCom from './components/MenuFormCom/index.vue';
|
import MenuFormCom from './components/MenuFormCom/index.vue';
|
||||||
|
import MenuFieldCom from './components/MenuFieldCom/index.vue';
|
||||||
import { GetList, DelObj } from './api';
|
import { GetList, DelObj } from './api';
|
||||||
import { successNotification } from '/@/utils/message';
|
import { successNotification } from '/@/utils/message';
|
||||||
import { APIResponseData, MenuTreeItemType } from './types';
|
import { APIResponseData, MenuTreeItemType } from './types';
|
||||||
@@ -49,7 +60,7 @@ let drawerVisible = ref(false);
|
|||||||
let drawerFormData = ref<Partial<MenuTreeItemType>>({});
|
let drawerFormData = ref<Partial<MenuTreeItemType>>({});
|
||||||
let menuTreeRef = ref<InstanceType<typeof MenuTreeCom> | null>(null);
|
let menuTreeRef = ref<InstanceType<typeof MenuTreeCom> | null>(null);
|
||||||
let menuButtonRef = ref<InstanceType<typeof MenuButtonCom> | null>(null);
|
let menuButtonRef = ref<InstanceType<typeof MenuButtonCom> | null>(null);
|
||||||
|
let menuFieldRef = ref<InstanceType<typeof MenuFieldCom> | null>(null);
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
GetList({}).then((ret: APIResponseData) => {
|
GetList({}).then((ret: APIResponseData) => {
|
||||||
const responseData = ret.data;
|
const responseData = ret.data;
|
||||||
@@ -67,6 +78,7 @@ const getData = () => {
|
|||||||
*/
|
*/
|
||||||
const handleTreeClick = (record: MenuTreeItemType) => {
|
const handleTreeClick = (record: MenuTreeItemType) => {
|
||||||
menuButtonRef.value?.handleRefreshTable(record);
|
menuButtonRef.value?.handleRefreshTable(record);
|
||||||
|
menuFieldRef.value?.handleRefreshTable(record)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { dict, useCompute, PageQuery, AddReq, DelReq, EditReq, CrudExpose, CrudO
|
|||||||
import tableSelector from '/@/components/tableSelector/index.vue';
|
import tableSelector from '/@/components/tableSelector/index.vue';
|
||||||
import {shallowRef, computed, ref, inject} from 'vue';
|
import {shallowRef, computed, ref, inject} from 'vue';
|
||||||
import manyToMany from '/@/components/manyToMany/index.vue';
|
import manyToMany from '/@/components/manyToMany/index.vue';
|
||||||
|
import {auth} from '/@/utils/authFunction'
|
||||||
const { compute } = useCompute();
|
const { compute } = useCompute();
|
||||||
|
|
||||||
interface CreateCrudOptionsTypes {
|
interface CreateCrudOptionsTypes {
|
||||||
@@ -36,8 +36,6 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
|||||||
return tabActivted.value === 'receive';
|
return tabActivted.value === 'receive';
|
||||||
});
|
});
|
||||||
|
|
||||||
//权限判定
|
|
||||||
const hasPermissions = inject("$hasPermissions")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
@@ -47,6 +45,15 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
|||||||
editRequest,
|
editRequest,
|
||||||
delRequest,
|
delRequest,
|
||||||
},
|
},
|
||||||
|
actionbar:{
|
||||||
|
buttons:{
|
||||||
|
add:{
|
||||||
|
show:computed(() =>{
|
||||||
|
return tabActivted.value !== 'receive' && auth('messageCenter:Create');
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
fixed:'right',
|
fixed:'right',
|
||||||
width:150,
|
width:150,
|
||||||
@@ -58,7 +65,7 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
|||||||
text:"查看",
|
text:"查看",
|
||||||
type:'text',
|
type:'text',
|
||||||
iconRight:'View',
|
iconRight:'View',
|
||||||
show:hasPermissions("messageCenter:Search"),
|
show:auth("messageCenter:Search"),
|
||||||
click({ index, row }) {
|
click({ index, row }) {
|
||||||
crudExpose.openView({ index, row });
|
crudExpose.openView({ index, row });
|
||||||
if (tabActivted.value === 'receive') {
|
if (tabActivted.value === 'receive') {
|
||||||
@@ -70,7 +77,7 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
|||||||
remove: {
|
remove: {
|
||||||
iconRight: 'Delete',
|
iconRight: 'Delete',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
show:hasPermissions('messageCenter:Delete')
|
show:auth('messageCenter:Delete')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
import { request } from "/@/utils/service";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取角色所拥有的菜单
|
|
||||||
* @param params
|
|
||||||
*/
|
|
||||||
export function GetMenu(params:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/role_get_menu/',
|
|
||||||
method: 'get',
|
|
||||||
params:params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 新增权限
|
|
||||||
* @param data
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function SaveMenuPermission(data:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_permission/save_auth/',
|
|
||||||
method: 'post',
|
|
||||||
data:data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取菜单下的按钮
|
|
||||||
* @param params
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function GetMenuButton(params:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/role_menu_get_button/',
|
|
||||||
method: 'get',
|
|
||||||
params:params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 根据角色获取已授权的菜单
|
|
||||||
* @param params
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function role_to_menu (params:any={}) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/role_to_menu/',
|
|
||||||
method: 'get',
|
|
||||||
params: params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 根据角色获取数据权限范围
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function GetDataScope (params:any={}) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/data_scope/',
|
|
||||||
method: 'get',
|
|
||||||
params: params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 获取权限部门
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function GetDataScopeDept (params:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
|
|
||||||
method: 'get',
|
|
||||||
params: params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 新增权限
|
|
||||||
* @param data
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function CreatePermission(data:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/',
|
|
||||||
method: 'post',
|
|
||||||
data:data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 根据菜单获取菜单下按钮
|
|
||||||
* @param params
|
|
||||||
*/
|
|
||||||
export function getObj(params:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/menu_to_button/',
|
|
||||||
method: 'get',
|
|
||||||
params:params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除按钮权限
|
|
||||||
* @param data
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function DeletePermission(data:any) {
|
|
||||||
return request({
|
|
||||||
url: `/api/system/role_menu_button_permission/${data.id}/`,
|
|
||||||
method: 'delete',
|
|
||||||
data:{}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,350 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-drawer size="70%" v-model="drawer" direction="rtl" destroy-on-close :before-close="handleClose">
|
|
||||||
<template #header>
|
|
||||||
<div>
|
|
||||||
<el-tag>当前角色:{{ editedRoleInfo.name }}</el-tag>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div style="padding: 1em">
|
|
||||||
<div style="margin-bottom: 10px">
|
|
||||||
<el-button type="primary" @click="onSaveAuth">保存菜单授权</el-button>
|
|
||||||
</div>
|
|
||||||
<vxe-table
|
|
||||||
ref="tableRef"
|
|
||||||
border
|
|
||||||
resizable
|
|
||||||
:row-config="{ keyField: 'menu_id' }"
|
|
||||||
:tree-config="{ transform: true, rowField: 'menu_id', parentField: 'parent' }"
|
|
||||||
:checkbox-config="{ labelField: 'menu_id', checkRowKeys: multipleTableData, checkStrictly: true }"
|
|
||||||
:expand-config="{ accordion: true }"
|
|
||||||
@toggle-row-expand="menuNodeClick"
|
|
||||||
:data="menuData"
|
|
||||||
>
|
|
||||||
<vxe-column type="checkbox" title="ID" width="200" tree-node></vxe-column>
|
|
||||||
<vxe-column field="name" title="目录/菜单"></vxe-column>
|
|
||||||
<vxe-column type="expand" title="已授予权限" width="120">
|
|
||||||
<template #content="{ row, rowIndex }">
|
|
||||||
<div style="padding: 10px 0px" v-if="!row.is_catalog">
|
|
||||||
<el-button type="primary" size="small" style="margin-bottom: 0.5em" @click="createBtnPermission">新增 </el-button>
|
|
||||||
<el-table size="small" :data="buttonPermissionData" border style="width: 100%">
|
|
||||||
<el-table-column prop="menu_button" label="权限名称" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<div>{{ scope.row.menu_button__name }}</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="menu_button__value" label="权限值" width="150"> </el-table-column>
|
|
||||||
<el-table-column prop="data_range" label="权限范围" width="140">
|
|
||||||
<template #default="scope">
|
|
||||||
<div>{{ formatDataRange(scope.row.data_range) }}</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="dept" label="权限涉及部门" />
|
|
||||||
<el-table-column fixed="right" label="操作" width="120">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button type="danger" size="small" @click="onDeleteBtn(scope)">删除 </el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</vxe-column>
|
|
||||||
</vxe-table>
|
|
||||||
<!-- 弹窗-->
|
|
||||||
<el-dialog v-model="dialogFormVisible" append-to-body width="400px" title="配置按钮权限">
|
|
||||||
<el-form ref="buttonFormRef" :model="buttonForm" :rules="buttonRules" label-width="120px">
|
|
||||||
<el-form-item label="按钮" prop="menu_button">
|
|
||||||
<el-select v-model="buttonForm.menu_button" placeholder="请选择按钮" @change="onChangeButton">
|
|
||||||
<el-option v-for="(item, index) in buttonOptions" :key="index" :label="item.name" :value="item.id" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="权限范围" prop="data_range">
|
|
||||||
<el-select v-model="buttonForm.data_range" placeholder="请选择按钮">
|
|
||||||
<el-option v-for="(item, index) in dataScopeOptions" :key="index" :label="item.label" :value="item.value" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="数据部门" prop="dept" v-show="buttonForm.data_range === 4">
|
|
||||||
<div class="dept-tree">
|
|
||||||
<el-tree
|
|
||||||
:data="deptOptions"
|
|
||||||
show-checkbox
|
|
||||||
default-expand-all
|
|
||||||
:default-checked-keys="deptCheckedKeys"
|
|
||||||
ref="deptTree"
|
|
||||||
node-key="dept_id"
|
|
||||||
:check-strictly="true"
|
|
||||||
:props="{ label: 'name' }"
|
|
||||||
></el-tree>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="dialogFormVisible = false">取消</el-button>
|
|
||||||
<el-button type="primary" @click="onSaveButtonForm"> 确定 </el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</el-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, defineExpose, reactive, toRefs } from 'vue';
|
|
||||||
import { ElMessageBox, ElTable } from 'element-plus';
|
|
||||||
import * as api from './api.ts';
|
|
||||||
import type { FormRules, FormInstance } from 'element-plus';
|
|
||||||
import { ElMessage } from 'element-plus';
|
|
||||||
import XEUtils from 'xe-utils';
|
|
||||||
import { VXETable, VxeTableInstance, VxeTableEvents } from 'vxe-table';
|
|
||||||
|
|
||||||
interface tableRow {
|
|
||||||
menu_id: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
//抽屉是否显示
|
|
||||||
const drawer = ref(false);
|
|
||||||
//当前编辑的角色信息
|
|
||||||
const editedRoleInfo = ref({});
|
|
||||||
|
|
||||||
//抽屉关闭确认
|
|
||||||
const handleClose = (done: () => void) => {
|
|
||||||
ElMessageBox.confirm('您确定要关闭?', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// catch error
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/*****菜单的配置项***/
|
|
||||||
const defaultProps = {
|
|
||||||
children: 'children',
|
|
||||||
label: 'name',
|
|
||||||
isLeaf: 'hasChild',
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Tree {
|
|
||||||
name: string;
|
|
||||||
children?: Tree[];
|
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
let menuData = ref<Tree>();
|
|
||||||
//获取菜单
|
|
||||||
const getMenuData = () => {
|
|
||||||
api.GetMenu({}).then((res: any) => {
|
|
||||||
const { data } = res;
|
|
||||||
menuData.value = data;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//获取已授权的菜单
|
|
||||||
const tableRef = ref<VxeTableInstance<tableRow>>();
|
|
||||||
const multipleTableData = ref();
|
|
||||||
const getRoleToMenu = () => {
|
|
||||||
api.role_to_menu({ role: editedRoleInfo.value.id }).then((res: any) => {
|
|
||||||
const { data } = res;
|
|
||||||
multipleTableData.value = data;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let isBtnPermissionShow = ref(false);
|
|
||||||
let buttonOptions = ref<[]>();
|
|
||||||
let editedMenuInfo = ref();
|
|
||||||
//菜单节点点击事件
|
|
||||||
const menuNodeClick: VxeTableEvents.ToggleRowExpand<tableRow> = ({ expanded, row }) => {
|
|
||||||
// isBtnPermissionShow.value = !node.is_catalog
|
|
||||||
if (!row.is_catalog) {
|
|
||||||
buttonOptions.value = [];
|
|
||||||
editedMenuInfo.value = row;
|
|
||||||
api.GetMenuButton({ menu: row.menu_id }).then((res: any) => {
|
|
||||||
const { data } = res;
|
|
||||||
buttonOptions.value = data;
|
|
||||||
});
|
|
||||||
api.getObj({ menu: row.menu_id, role: editedRoleInfo.value.id }).then((res: any) => {
|
|
||||||
const { data } = res;
|
|
||||||
buttonPermissionData.value = data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const menuTree = ref();
|
|
||||||
/*****菜单的配置项***/
|
|
||||||
/***按钮授权的弹窗****/
|
|
||||||
//是否显示新增表单
|
|
||||||
const dialogFormVisible = ref(false);
|
|
||||||
//部门树
|
|
||||||
const deptTree = ref();
|
|
||||||
//自定义部门数据
|
|
||||||
const deptOptions = ref();
|
|
||||||
//选中的部门数据
|
|
||||||
const deptCheckedKeys = [];
|
|
||||||
//按钮表单
|
|
||||||
const buttonForm = reactive({
|
|
||||||
menu_button: null,
|
|
||||||
role: null,
|
|
||||||
menu: null,
|
|
||||||
data_range: null,
|
|
||||||
dept: [],
|
|
||||||
});
|
|
||||||
//按钮表格数据
|
|
||||||
let buttonPermissionData = ref([]);
|
|
||||||
//按钮表单验证
|
|
||||||
const buttonRules = reactive<FormRules>({
|
|
||||||
menu_button: [{ required: true, message: '必填项' }],
|
|
||||||
data_range: [{ required: true, message: '必填项' }],
|
|
||||||
});
|
|
||||||
//新增按钮
|
|
||||||
const buttonFormRef = ref<FormInstance>();
|
|
||||||
const createBtnPermission = () => {
|
|
||||||
dialogFormVisible.value = true;
|
|
||||||
buttonForm.menu_button = null;
|
|
||||||
buttonForm.menu = null;
|
|
||||||
buttonForm.role = null;
|
|
||||||
buttonForm.data_range = null;
|
|
||||||
buttonForm.dept = [];
|
|
||||||
};
|
|
||||||
//权限范围数据
|
|
||||||
const dataScopeOptions = ref<[]>();
|
|
||||||
//按钮值变化事件
|
|
||||||
const onChangeButton = (val: any) => {
|
|
||||||
dataScopeOptions.value = [];
|
|
||||||
//获取权限值范围
|
|
||||||
api.GetDataScope({ menu_button: val }).then((res: any) => {
|
|
||||||
dataScopeOptions.value = res.data;
|
|
||||||
});
|
|
||||||
//获取权限部门值
|
|
||||||
api.GetDataScopeDept({ menu_button: val }).then((res: any) => {
|
|
||||||
deptOptions.value = XEUtils.toArrayTree(res.data, { parentKey: 'parent', strict: false });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
//过滤按钮名称
|
|
||||||
const formatMenuBtn = (val: any) => {
|
|
||||||
let obj: any = buttonOptions.value?.find((item: any) => {
|
|
||||||
return item.id === val;
|
|
||||||
});
|
|
||||||
return obj ? obj.name : null;
|
|
||||||
};
|
|
||||||
//过滤权限范围
|
|
||||||
const formatDataRange = (val: any) => {
|
|
||||||
let obj: any = [
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
label: '仅本人数据权限',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 1,
|
|
||||||
label: '本部门及以下数据权限',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 2,
|
|
||||||
label: '本部门数据权限',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 3,
|
|
||||||
label: '全部数据权限',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 4,
|
|
||||||
label: '自定义数据权限',
|
|
||||||
},
|
|
||||||
].find((item: any) => {
|
|
||||||
return item.value === val;
|
|
||||||
});
|
|
||||||
return obj ? obj.label : null;
|
|
||||||
};
|
|
||||||
//保存按钮表单
|
|
||||||
|
|
||||||
const onSaveButtonForm = async () => {
|
|
||||||
const { id: roleId } = editedRoleInfo.value;
|
|
||||||
const { id: menuId } = editedMenuInfo.value;
|
|
||||||
const form: any = Object.assign({}, buttonForm);
|
|
||||||
form.role = roleId;
|
|
||||||
form.menu = menuId;
|
|
||||||
//选中的部门
|
|
||||||
const checkedList = deptTree.value.getCheckedKeys();
|
|
||||||
form.dept = checkedList;
|
|
||||||
if (!buttonFormRef.value) return;
|
|
||||||
await buttonFormRef.value.validate((valid, fields) => {
|
|
||||||
if (valid) {
|
|
||||||
api.CreatePermission(form).then((res: any) => {
|
|
||||||
const { data } = res;
|
|
||||||
buttonPermissionData.value.push(data);
|
|
||||||
dialogFormVisible.value = false;
|
|
||||||
ElMessage({
|
|
||||||
type: 'success',
|
|
||||||
message: res.msg,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ElMessage({
|
|
||||||
type: 'error',
|
|
||||||
title: '提交错误',
|
|
||||||
message: 'F12控制台看详情',
|
|
||||||
});
|
|
||||||
console.log('提交错误', fields);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
//删除按钮权限
|
|
||||||
const onDeleteBtn = (scope: any) => {
|
|
||||||
const { row, $index } = scope;
|
|
||||||
ElMessageBox.confirm('您是否要删除数据?', '温馨提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
api.DeletePermission({ id: row.id }).then((res: any) => {
|
|
||||||
buttonPermissionData.value.splice($index, 1);
|
|
||||||
ElMessage({
|
|
||||||
type: 'success',
|
|
||||||
message: res.msg,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
ElMessage({
|
|
||||||
type: 'info',
|
|
||||||
message: '取消删除',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
/***按钮授权的弹窗****/
|
|
||||||
//初始化数据
|
|
||||||
const initGet = () => {
|
|
||||||
getMenuData();
|
|
||||||
getRoleToMenu();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存授权
|
|
||||||
*/
|
|
||||||
const onSaveAuth = () => {
|
|
||||||
const $table = tableRef.value;
|
|
||||||
if ($table) {
|
|
||||||
const selectRecords = $table.getCheckboxRecords();
|
|
||||||
const menuIdList = selectRecords.map((record: any) => record.menu_id);
|
|
||||||
const { id: roleId } = editedRoleInfo.value;
|
|
||||||
const data = {
|
|
||||||
role: roleId,
|
|
||||||
menu: menuIdList,
|
|
||||||
};
|
|
||||||
api.SaveMenuPermission(data).then((res: any) => {
|
|
||||||
ElMessage({
|
|
||||||
message: res.msg,
|
|
||||||
type: 'success',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
defineExpose({ drawer, editedRoleInfo, initGet });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { request } from "/@/utils/service";
|
|
||||||
|
|
||||||
export function getDataPermissionRange() {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/data_scope/',
|
|
||||||
method: 'get',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
export function getDataPermissionDept() {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDataPermissionMenu() {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/get_role_permissions/',
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="permission-com">
|
|
||||||
<div class="pc-item">
|
|
||||||
<p class="pc-title">数据授权</p>
|
|
||||||
<div class="pc-cell">
|
|
||||||
<el-radio-group v-model="dataPermission" class="pc-data-permission">
|
|
||||||
<el-radio v-for="item in dataPermissionRange" :key="item.label" :label="item.value" @change="handleChange">{{ item.label }}</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
<el-tree-select
|
|
||||||
v-if="dataPermission === 4"
|
|
||||||
node-key="id"
|
|
||||||
v-model="customDataPermission"
|
|
||||||
:props="defaultTreeProps"
|
|
||||||
:data="deptData"
|
|
||||||
multiple
|
|
||||||
check-strictly
|
|
||||||
:render-after-expand="false"
|
|
||||||
show-checkbox
|
|
||||||
class="pc-custom-dept"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pc-item pc-menu">
|
|
||||||
<p class="pc-title">菜单授权</p>
|
|
||||||
<div>
|
|
||||||
<el-tree
|
|
||||||
:props="defaultTreeProps"
|
|
||||||
:data="menuData"
|
|
||||||
show-checkbox
|
|
||||||
node-key="id"
|
|
||||||
default-expand-all
|
|
||||||
:expand-on-click-node="false"
|
|
||||||
class="dc-menu-tree"
|
|
||||||
>
|
|
||||||
<template #default="{ node, data }">
|
|
||||||
<div class="pc-tree-node" :class="{ 'tree-node-label-border': !data.is_catalog }">
|
|
||||||
<p class="tree-node-label">{{ node.label }}</p>
|
|
||||||
<div v-if="!data.is_catalog">
|
|
||||||
<ul class="menu-permission-list">
|
|
||||||
<li v-for="m in data.menuPermission" :key="m.id" class="menu-permission-item">
|
|
||||||
<el-checkbox v-model="m.id" :label="m.name" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul class="menu-permission-list">
|
|
||||||
<li v-for="m in data.columns" :key="m.id" class="menu-permission-item">
|
|
||||||
<el-checkbox v-model="m.id" :label="m.title" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-tree>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pc-btn">
|
|
||||||
<el-button type="primary">确定</el-button>
|
|
||||||
<el-button>取消</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, onMounted } from 'vue';
|
|
||||||
import XEUtils from 'xe-utils';
|
|
||||||
import { getDataPermissionRange, getDataPermissionDept, getDataPermissionMenu } from './api';
|
|
||||||
import { DataPermissionRangeType, CustomDataPermissionDeptType, CustomDataPermissionMenuType } from './types';
|
|
||||||
|
|
||||||
const defaultTreeProps = {
|
|
||||||
children: 'children',
|
|
||||||
label: 'name',
|
|
||||||
value: 'id',
|
|
||||||
};
|
|
||||||
|
|
||||||
const data: any[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
label: 'Level one 1',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
label: 'Level two 1-1',
|
|
||||||
isPenultimate: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: 9,
|
|
||||||
label: 'Level three 1-1-1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 10,
|
|
||||||
label: 'Level three 1-1-2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
label: 'Level one 2',
|
|
||||||
isPenultimate: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
label: 'Level two 2-1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
label: 'Level two 2-2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
label: 'Level one 3',
|
|
||||||
isPenultimate: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
label: 'Level two 3-1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
label: 'Level two 3-2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let dataPermission = ref();
|
|
||||||
let dataPermissionRange = ref<DataPermissionRangeType[]>([]);
|
|
||||||
let customDataPermission = ref();
|
|
||||||
let deptData = ref<CustomDataPermissionDeptType[]>([]);
|
|
||||||
let menuData = ref<CustomDataPermissionMenuType[]>([]);
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const resRange = await getDataPermissionRange();
|
|
||||||
const resMenu = await getDataPermissionMenu();
|
|
||||||
|
|
||||||
if (resRange?.code === 2000) {
|
|
||||||
dataPermissionRange.value = resRange.data;
|
|
||||||
}
|
|
||||||
if (resMenu?.code === 2000) {
|
|
||||||
console.log(resMenu.data);
|
|
||||||
menuData.value = resMenu.data;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = async () => {
|
|
||||||
if (dataPermission.value === 4) {
|
|
||||||
const res = await getDataPermissionDept();
|
|
||||||
const data = XEUtils.toArrayTree(res.data, { parentKey: 'parent', strict: false });
|
|
||||||
deptData.value = data;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTestClick = (node: any, data: any) => {
|
|
||||||
console.log(node, data);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchData();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.permission-com {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 15px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
.pc-item {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
border-bottom: 1px #dcdfe6 solid;
|
|
||||||
}
|
|
||||||
.pc-title {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.pc-cell {
|
|
||||||
display: flex;
|
|
||||||
padding: 10px;
|
|
||||||
overflow-x: auto;
|
|
||||||
.pc-data-permission {
|
|
||||||
min-width: 800px;
|
|
||||||
}
|
|
||||||
.pc-custom-dept {
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pc-menu {
|
|
||||||
height: calc(100% - 140px);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pc-tree-node {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.tree-node-label {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
.menu-permission-list {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
.menu-permission-item {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-node-label-border {
|
|
||||||
border-bottom: 1px #dcdfe6 solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pc-btn {
|
|
||||||
padding-bottom: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.dc-menu-tree {
|
|
||||||
.el-tree-node__content {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
export interface DataPermissionRangeType {
|
|
||||||
label: string;
|
|
||||||
value: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomDataPermissionDeptType {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
patent: number;
|
|
||||||
children: CustomDataPermissionDeptType[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomDataPermissionMenuType {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
is_catalog: boolean;
|
|
||||||
menuPermission: { id: number; name: string; value: string }[] | null;
|
|
||||||
columns: { id: number; name: string; title: string }[] | null;
|
|
||||||
children: CustomDataPermissionMenuType[]
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer v-model="drawerVisible" title="权限配置" direction="rtl" size="60%" :close-on-click-modal="false"
|
<el-drawer v-model="drawerVisible" title="权限配置" direction="rtl" size="60%" :close-on-click-modal="false"
|
||||||
:before-close="handleDrawerClose">
|
:before-close="handleDrawerClose"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="4">
|
<el-col :span="4">
|
||||||
@@ -8,7 +10,7 @@
|
|||||||
<el-tag>{{ props.roleName }}</el-tag>
|
<el-tag>{{ props.roleName }}</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6" :offset="8">
|
<el-col :span="6">
|
||||||
<div>
|
<div>
|
||||||
<el-button size="small" type="primary" class="pc-save-btn" @click="handleSavePermission">保存菜单授权
|
<el-button size="small" type="primary" class="pc-save-btn" @click="handleSavePermission">保存菜单授权
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -18,15 +20,16 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="permission-com">
|
<div class="permission-com">
|
||||||
<el-collapse v-model="collapseCurrent" @change="handleCollapseChange" accordion>
|
<el-collapse v-model="collapseCurrent" @change="handleCollapseChange" accordion>
|
||||||
<el-collapse-item v-for="(item,mIndex) in menuData" :key="mIndex" :name="mIndex">
|
<el-collapse-item v-for="(item,mIndex) in menuData" :key="mIndex" :name="mIndex"
|
||||||
|
style=" background-color: #fafafa;">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div @click.stop="null">
|
<div>
|
||||||
<p class="pc-collapse-title">
|
<div class="pc-collapse-title">
|
||||||
<el-checkbox v-model="item.isCheck">
|
<el-checkbox v-model="item.isCheck" @click.stop="null">
|
||||||
<span>{{ item.name }}</span>
|
<span>{{ item.name }}</span>
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</p>
|
</div>
|
||||||
<div v-show="!collapseCurrent.includes(mIndex)">
|
<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">
|
<el-checkbox v-for="btn in item.btns" :key="btn.value" :label="btn.value" v-model="btn.isCheck">
|
||||||
{{ btn.name }}
|
{{ btn.name }}
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
@@ -38,7 +41,7 @@
|
|||||||
<p>允许对这些数据有以下操作</p>
|
<p>允许对这些数据有以下操作</p>
|
||||||
<el-checkbox v-for="(btn,bIndex) in item.btns" :key="bIndex" v-model="btn.isCheck" :label="btn.value">
|
<el-checkbox v-for="(btn,bIndex) in item.btns" :key="bIndex" v-model="btn.isCheck" :label="btn.value">
|
||||||
<div class="btn-item">
|
<div class="btn-item">
|
||||||
{{ btn.data_range!==null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
|
{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
|
||||||
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(item, btn.id)">
|
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(item, btn.id)">
|
||||||
<el-icon><Setting/></el-icon>
|
<el-icon><Setting/></el-icon>
|
||||||
</span>
|
</span>
|
||||||
@@ -49,7 +52,7 @@
|
|||||||
<div class="pccm-item">
|
<div class="pccm-item">
|
||||||
<p>对这些数据有以下字段权限</p>
|
<p>对这些数据有以下字段权限</p>
|
||||||
|
|
||||||
<ul class="columns-list">
|
<ul class="columns-list">
|
||||||
<li class="columns-head">
|
<li class="columns-head">
|
||||||
<div class="width-txt">
|
<div class="width-txt">
|
||||||
<span>字段</span>
|
<span>字段</span>
|
||||||
@@ -57,7 +60,7 @@
|
|||||||
|
|
||||||
<div v-for="(head,hIndex) in column.header" :key="hIndex" class="width-check">
|
<div v-for="(head,hIndex) in column.header" :key="hIndex" class="width-check">
|
||||||
<el-checkbox :label="head.value" @change="handleColumnChange($event, item, head.value)">
|
<el-checkbox :label="head.value" @change="handleColumnChange($event, item, head.value)">
|
||||||
<span>{{head.label}}</span>
|
<span>{{ head.label }}</span>
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -109,7 +112,13 @@
|
|||||||
import {ref, onMounted, defineProps, watch, computed, reactive} from 'vue';
|
import {ref, onMounted, defineProps, watch, computed, reactive} from 'vue';
|
||||||
import XEUtils from 'xe-utils';
|
import XEUtils from 'xe-utils';
|
||||||
import {errorNotification} from '/@/utils/message';
|
import {errorNotification} from '/@/utils/message';
|
||||||
import {getDataPermissionRange, getDataPermissionDept, getRolePremission, setRolePremission,setBtnDatarange} from './api';
|
import {
|
||||||
|
getDataPermissionRange,
|
||||||
|
getDataPermissionDept,
|
||||||
|
getRolePremission,
|
||||||
|
setRolePremission,
|
||||||
|
setBtnDatarange
|
||||||
|
} from './api';
|
||||||
import {MenuDataType, DataPermissionRangeType, CustomDataPermissionDeptType} from './types';
|
import {MenuDataType, DataPermissionRangeType, CustomDataPermissionDeptType} from './types';
|
||||||
import {ElMessage} from 'element-plus'
|
import {ElMessage} from 'element-plus'
|
||||||
|
|
||||||
@@ -156,9 +165,9 @@ let menuBtnCurrent = ref<number>(-1);
|
|||||||
let dialogVisible = ref(false);
|
let dialogVisible = ref(false);
|
||||||
let dataPermissionRange = ref<DataPermissionRangeType[]>([]);
|
let dataPermissionRange = ref<DataPermissionRangeType[]>([]);
|
||||||
const formatDataRange = computed(() => {
|
const formatDataRange = computed(() => {
|
||||||
return function(datarange:number){
|
return function (datarange: number) {
|
||||||
const findItem = dataPermissionRange.value.find((i) => i.value === datarange);
|
const findItem = dataPermissionRange.value.find((i) => i.value === datarange);
|
||||||
return findItem?.label || ''
|
return findItem?.label || ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
let deptData = ref<CustomDataPermissionDeptType[]>([]);
|
let deptData = ref<CustomDataPermissionDeptType[]>([]);
|
||||||
@@ -226,7 +235,7 @@ const handleDialogConfirm = () => {
|
|||||||
if (btn.id === menuBtnCurrent.value) {
|
if (btn.id === menuBtnCurrent.value) {
|
||||||
const findItem = dataPermissionRange.value.find((i) => i.value === dataPermission.value);
|
const findItem = dataPermissionRange.value.find((i) => i.value === dataPermission.value);
|
||||||
btn.data_range = findItem?.value || 0;
|
btn.data_range = findItem?.value || 0;
|
||||||
if(btn.data_range===4){
|
if (btn.data_range === 4) {
|
||||||
btn.dept = customDataPermission.value
|
btn.dept = customDataPermission.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,7 +261,10 @@ const handleSavePermission = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const column = reactive({
|
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(() => {
|
onMounted(() => {
|
||||||
@@ -270,6 +282,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.pc-collapse-title {
|
.pc-collapse-title {
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@@ -363,6 +376,7 @@ onMounted(() => {
|
|||||||
border-left: 1px solid #ebeef5;
|
border-left: 1px solid #ebeef5;
|
||||||
border-right: 1px solid #ebeef5;
|
border-right: 1px solid #ebeef5;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-collapse-item__header.is-active {
|
.el-collapse-item__header.is-active {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as api from './api';
|
|||||||
import { dictionary } from '/@/utils/dictionary';
|
import { dictionary } from '/@/utils/dictionary';
|
||||||
import { columnPermission } from '../../../utils/columnPermission';
|
import { columnPermission } from '../../../utils/columnPermission';
|
||||||
import { successMessage } from '../../../utils/message';
|
import { successMessage } from '../../../utils/message';
|
||||||
|
import {auth} from '/@/utils/authFunction'
|
||||||
interface CreateCrudOptionsTypes {
|
interface CreateCrudOptionsTypes {
|
||||||
output: any;
|
output: any;
|
||||||
crudOptions: CrudOptions;
|
crudOptions: CrudOptions;
|
||||||
@@ -14,12 +14,10 @@ export const createCrudOptions = function ({
|
|||||||
crudExpose,
|
crudExpose,
|
||||||
rolePermission,
|
rolePermission,
|
||||||
handleDrawerOpen,
|
handleDrawerOpen,
|
||||||
hasPermissions,
|
|
||||||
}: {
|
}: {
|
||||||
crudExpose: CrudExpose;
|
crudExpose: CrudExpose;
|
||||||
rolePermission: any;
|
rolePermission: any;
|
||||||
handleDrawerOpen: Function;
|
handleDrawerOpen: Function;
|
||||||
hasPermissions: Function;
|
|
||||||
}): CreateCrudOptionsTypes {
|
}): CreateCrudOptionsTypes {
|
||||||
const pageRequest = async (query: any) => {
|
const pageRequest = async (query: any) => {
|
||||||
return await api.GetList(query);
|
return await api.GetList(query);
|
||||||
@@ -47,40 +45,31 @@ export const createCrudOptions = function ({
|
|||||||
editRequest,
|
editRequest,
|
||||||
delRequest,
|
delRequest,
|
||||||
},
|
},
|
||||||
|
actionbar: {
|
||||||
|
buttons: {
|
||||||
|
add: {
|
||||||
|
show: auth('role:Create')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
//固定右侧
|
//固定右侧
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 260,
|
width: 320,
|
||||||
buttons: {
|
buttons: {
|
||||||
view: {
|
view: {
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
show: hasPermissions('role:Update'),
|
show: auth('role:Update'),
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
show: hasPermissions('role:Delete'),
|
show: auth('role:Delete'),
|
||||||
},
|
},
|
||||||
/* custom: {
|
permission: {
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
text: '权限配置',
|
text: '权限配置',
|
||||||
show: hasPermissions('role:Update'),
|
show: auth('role:Permission'),
|
||||||
tooltip: {
|
|
||||||
placement: 'top',
|
|
||||||
content: '权限配置',
|
|
||||||
},
|
|
||||||
click: (context: any): void => {
|
|
||||||
const { row } = context;
|
|
||||||
// eslint-disable-next-line no-mixed-spaces-and-tabs
|
|
||||||
rolePermission.value.drawer = true;
|
|
||||||
rolePermission.value.editedRoleInfo = row;
|
|
||||||
rolePermission.value.initGet();
|
|
||||||
},
|
|
||||||
}, */
|
|
||||||
customNew: {
|
|
||||||
type: 'primary',
|
|
||||||
text: '权限配置',
|
|
||||||
show: hasPermissions('role:Update'),
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
content: '权限配置',
|
content: '权限配置',
|
||||||
@@ -127,9 +116,9 @@ export const createCrudOptions = function ({
|
|||||||
sortable: 'custom',
|
sortable: 'custom',
|
||||||
show: columnPermission('name', 'is_query'),
|
show: columnPermission('name', 'is_query'),
|
||||||
},
|
},
|
||||||
addForm: {
|
// addForm: {
|
||||||
show: columnPermission('name', 'is_create'),
|
// show: columnPermission('name', 'is_create'),
|
||||||
},
|
// },
|
||||||
editForm: {
|
editForm: {
|
||||||
show: columnPermission('name', 'is_update'),
|
show: columnPermission('name', 'is_update'),
|
||||||
},
|
},
|
||||||
@@ -170,7 +159,6 @@ export const createCrudOptions = function ({
|
|||||||
column: {
|
column: {
|
||||||
minWidth: 90,
|
minWidth: 90,
|
||||||
sortable: 'custom',
|
sortable: 'custom',
|
||||||
show: columnPermission('sort', 'is_query'),
|
|
||||||
},
|
},
|
||||||
addForm: {
|
addForm: {
|
||||||
show: columnPermission('sort', 'is_create'),
|
show: columnPermission('sort', 'is_create'),
|
||||||
@@ -183,40 +171,6 @@ export const createCrudOptions = function ({
|
|||||||
value: 1,
|
value: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
admin: {
|
|
||||||
title: '是否管理员',
|
|
||||||
search: { show: false },
|
|
||||||
type: 'dict-radio',
|
|
||||||
dict: dict({
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
label: '是',
|
|
||||||
value: true,
|
|
||||||
color: 'success',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '否',
|
|
||||||
value: false,
|
|
||||||
color: 'danger',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
column: {
|
|
||||||
minWidth: 130,
|
|
||||||
sortable: 'custom',
|
|
||||||
show: columnPermission('admin', 'is_query'),
|
|
||||||
},
|
|
||||||
addForm: {
|
|
||||||
show: columnPermission('admin', 'is_create'),
|
|
||||||
},
|
|
||||||
editForm: {
|
|
||||||
show: columnPermission('admin', 'is_update'),
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
rules: [{ required: true, message: '是否管理员必填' }],
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
status: {
|
status: {
|
||||||
title: '状态',
|
title: '状态',
|
||||||
search: { show: true },
|
search: { show: true },
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="role">
|
<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 { useColumnPermission } from '/@/stores/columnPermission';
|
||||||
import { GetPermission } from './api';
|
import { GetPermission } from './api';
|
||||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||||
import { createCrudOptions } from './crud';
|
import { createCrudOptions } from './crud';
|
||||||
import permission from './components/PermissionCom/index.vue';
|
|
||||||
import PermissionComNew from './components/PermissionComNew/index.vue';
|
import PermissionComNew from './components/PermissionComNew/index.vue';
|
||||||
|
import _ from "lodash-es";
|
||||||
|
import {columnPermission} from "/@/utils/columnPermission";
|
||||||
let drawerVisible = ref(false);
|
let drawerVisible = ref(false);
|
||||||
let roleId = ref(null);
|
let roleId = ref(null);
|
||||||
let roleName = ref(null);
|
let roleName = ref(null);
|
||||||
@@ -31,11 +31,11 @@ const crudRef = ref();
|
|||||||
// crud 配置的ref
|
// crud 配置的ref
|
||||||
const crudBinding = ref();
|
const crudBinding = ref();
|
||||||
|
|
||||||
const hasPermissions: any = inject('$hasPermissions');
|
|
||||||
|
|
||||||
const fetchColumnPermission = async () => {
|
const fetchColumnPermission = async () => {
|
||||||
const res = await GetPermission();
|
const res = await GetPermission();
|
||||||
useColumnPermission().setPermissionData(res.data);
|
useColumnPermission().setPermissionData(res.data);
|
||||||
|
console.log(3333,res)
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDrawerOpen = (row: any) => {
|
const handleDrawerOpen = (row: any) => {
|
||||||
@@ -49,21 +49,51 @@ const handleDrawerClose = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
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 () => {
|
onMounted( async () => {
|
||||||
await fetchColumnPermission();
|
|
||||||
|
|
||||||
// 你的crud配置
|
|
||||||
const { crudOptions } = createCrudOptions({ crudExpose, rolePermission, handleDrawerOpen, hasPermissions });
|
|
||||||
|
|
||||||
// 初始化crud配置
|
|
||||||
const { resetCrudOptions } = useCrud({
|
|
||||||
crudExpose,
|
|
||||||
crudOptions,
|
|
||||||
context: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
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();
|
crudExpose.doRefresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
import { request } from "/@/utils/service";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取角色所拥有的菜单
|
|
||||||
* @param params
|
|
||||||
*/
|
|
||||||
export function GetMenu(params:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/role_get_menu/',
|
|
||||||
method: 'get',
|
|
||||||
params:params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 新增权限
|
|
||||||
* @param data
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function SaveMenuPermission(data:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_permission/save_auth/',
|
|
||||||
method: 'post',
|
|
||||||
data:data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取菜单下的按钮
|
|
||||||
* @param params
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function GetMenuButton(params:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/role_menu_get_button/',
|
|
||||||
method: 'get',
|
|
||||||
params:params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 根据角色获取数据权限范围
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function GetDataScope (params:any={}) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/data_scope/',
|
|
||||||
method: 'get',
|
|
||||||
params: params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 获取权限部门
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function GetDataScopeDept (params:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
|
|
||||||
method: 'get',
|
|
||||||
params: params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 新增权限
|
|
||||||
* @param data
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function CreatePermission(data:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/',
|
|
||||||
method: 'post',
|
|
||||||
data:data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 根据菜单获取菜单下按钮
|
|
||||||
* @param params
|
|
||||||
*/
|
|
||||||
export function getObj(params:any) {
|
|
||||||
return request({
|
|
||||||
url: '/api/system/role_menu_button_permission/menu_to_button/',
|
|
||||||
method: 'get',
|
|
||||||
params:params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除按钮权限
|
|
||||||
* @param data
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function DeletePermission(data:any) {
|
|
||||||
return request({
|
|
||||||
url: `/api/system/role_menu_button_permission/${data.id}/`,
|
|
||||||
method: 'delete',
|
|
||||||
data:{}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,432 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-drawer
|
|
||||||
size="70%"
|
|
||||||
v-model="drawer"
|
|
||||||
direction="rtl"
|
|
||||||
destroy-on-close
|
|
||||||
:before-close="handleClose"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<div>
|
|
||||||
<el-tag size="large" type="primary">当前角色:{{ editedRoleInfo.name }}</el-tag>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div style="padding: 1em">
|
|
||||||
<el-row :gutter="10">
|
|
||||||
<el-col :xs="24" :sm="24" :md="8" :lg="6" :xl="6">
|
|
||||||
<el-card header="菜单页面授权">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<el-tooltip effect="dark" content="点击菜单项,可对菜单下的按钮/接口授权"
|
|
||||||
placement="right">
|
|
||||||
<div>
|
|
||||||
<span>菜单页面</span>
|
|
||||||
<el-icon>
|
|
||||||
<QuestionFilled/>
|
|
||||||
</el-icon>
|
|
||||||
</div>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-button size="mini" type="primary" @click="onSaveAuth">保存菜单授权</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<el-tree :data="menuData"
|
|
||||||
ref="menuTree"
|
|
||||||
show-checkbox
|
|
||||||
node-key="id"
|
|
||||||
highlight-current
|
|
||||||
:expand-on-click-node="false"
|
|
||||||
:check-on-click-node="true"
|
|
||||||
:props="defaultProps"
|
|
||||||
@node-click="menuNodeClick"
|
|
||||||
/>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="24" :md="16" :lg="18" :xl="18">
|
|
||||||
<!-- <el-alert title="对页面菜单下按钮授权" description="新增或删除对菜单下的按钮/接口授权" type="warning" />-->
|
|
||||||
<el-card v-if="isBtnPermissionShow">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<el-tooltip effect="dark" content="新增或删除对菜单下的按钮/接口授权" placement="right">
|
|
||||||
<div>
|
|
||||||
<span>按钮/接口授权</span>
|
|
||||||
<el-icon>
|
|
||||||
<QuestionFilled/>
|
|
||||||
</el-icon>
|
|
||||||
</div>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div>
|
|
||||||
<el-divider content-position="left">{{ editedMenuInfo.name }}</el-divider>
|
|
||||||
<el-button type="primary" size="small" style="margin-bottom: 0.5em"
|
|
||||||
@click="createBtnPermission">新增
|
|
||||||
</el-button>
|
|
||||||
<el-table size="small" :data="buttonPermissionData" border style="width: 100%">
|
|
||||||
<el-table-column prop="menu_button" label="权限名称" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<div>{{ formatMenuBtn(scope.row.menu_button) }}</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="data_range" label="权限范围" width="140">
|
|
||||||
<template #default="scope">
|
|
||||||
<div>{{ formatDataRange(scope.row.data_range) }}</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="dept" label="权限涉及部门"/>
|
|
||||||
<el-table-column fixed="right" label="操作" width="120">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button type="danger" size="small" @click="onDeleteBtn(scope)">删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <el-divider content-position="left">字段授权</el-divider>-->
|
|
||||||
<!-- <el-table size="small" :data="crudPermissionData" border style="width: 100%">-->
|
|
||||||
<!-- <el-table-column prop="field" label="字段"></el-table-column>-->
|
|
||||||
<!-- <el-table-column prop="table" label="列表显示">-->
|
|
||||||
<!-- <template #default="scope">-->
|
|
||||||
<!-- <div>-->
|
|
||||||
<!-- <el-switch size="mini" v-model="scope.row.table"/>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column prop="view" label="表单查看">-->
|
|
||||||
<!-- <template #default="scope">-->
|
|
||||||
<!-- <div>-->
|
|
||||||
<!-- <el-switch size="mini" v-model="scope.row.view"/>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column prop="edit" label="表单编辑">-->
|
|
||||||
<!-- <template #default="scope">-->
|
|
||||||
<!-- <div>-->
|
|
||||||
<!-- <el-switch size="mini" v-model="scope.row.edit"/>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- </el-table>-->
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-dialog v-model="dialogFormVisible" width="400px" title="配置按钮权限">
|
|
||||||
<el-form ref="buttonFormRef" :model="buttonForm" :rules="buttonRules" label-width="120px">
|
|
||||||
<el-form-item label="按钮" prop="menu_button">
|
|
||||||
<el-select v-model="buttonForm.menu_button" placeholder="请选择按钮" @change="onChangeButton">
|
|
||||||
<el-option v-for="(item,index) in buttonOptions" :key="index" :label="item.name"
|
|
||||||
:value="item.id"/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="权限范围" prop="data_range">
|
|
||||||
<el-select v-model="buttonForm.data_range" placeholder="请选择按钮">
|
|
||||||
<el-option v-for="(item,index) in dataScopeOptions" :key="index" :label="item.label"
|
|
||||||
:value="item.value"/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="数据部门" prop="dept" v-show="buttonForm.data_range === 4">
|
|
||||||
<div class="dept-tree">
|
|
||||||
<el-tree
|
|
||||||
:data="deptOptions"
|
|
||||||
show-checkbox
|
|
||||||
default-expand-all
|
|
||||||
:default-checked-keys="deptCheckedKeys"
|
|
||||||
ref="deptTree"
|
|
||||||
node-key="id"
|
|
||||||
:check-strictly="true"
|
|
||||||
:props="{ label: 'name' }"
|
|
||||||
></el-tree>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<span class="dialog-footer">
|
|
||||||
<el-button @click="dialogFormVisible = false">取消</el-button>
|
|
||||||
<el-button type="primary" @click="onSaveButtonForm">
|
|
||||||
确定
|
|
||||||
</el-button>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</el-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup name="rolePermission">
|
|
||||||
import {ref, defineExpose, reactive, toRefs} from 'vue'
|
|
||||||
import {ElMessageBox} from 'element-plus'
|
|
||||||
import * as api from './api'
|
|
||||||
import type {FormRules, FormInstance} from 'element-plus'
|
|
||||||
import {ElMessage} from 'element-plus'
|
|
||||||
import XEUtils from 'xe-utils'
|
|
||||||
//抽屉是否显示
|
|
||||||
const drawer = ref(false)
|
|
||||||
//当前编辑的角色信息
|
|
||||||
const editedRoleInfo = ref({})
|
|
||||||
|
|
||||||
//抽屉关闭确认
|
|
||||||
const handleClose = (done: () => void) => {
|
|
||||||
ElMessageBox.confirm('您确定要关闭?', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// catch error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*****菜单的配置项***/
|
|
||||||
const defaultProps = {
|
|
||||||
children: 'children',
|
|
||||||
label: 'name',
|
|
||||||
isLeaf: 'hasChild'
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Tree {
|
|
||||||
name: string
|
|
||||||
children?: Tree[],
|
|
||||||
id: number
|
|
||||||
}
|
|
||||||
|
|
||||||
let menuData = ref<Tree>()
|
|
||||||
//获取菜单
|
|
||||||
const getMenuData = () => {
|
|
||||||
api.GetMenu({}).then((res: any) => {
|
|
||||||
const {data} = res
|
|
||||||
const list = XEUtils.toArrayTree(data, {parentKey: "parent", key:'menu_id',strict: true})
|
|
||||||
menuData.value = list
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let isBtnPermissionShow = ref(false)
|
|
||||||
let buttonOptions = ref<[]>()
|
|
||||||
let editedMenuInfo = ref()
|
|
||||||
//菜单节点点击事件
|
|
||||||
const menuNodeClick = (node: any, obj: any) => {
|
|
||||||
isBtnPermissionShow.value = !node.is_catalog
|
|
||||||
if (!node.is_catalog) {
|
|
||||||
buttonOptions.value = []
|
|
||||||
editedMenuInfo.value = node
|
|
||||||
api.GetMenuButton({menu: node.menu_id}).then((res: any) => {
|
|
||||||
const {data} = res
|
|
||||||
buttonOptions.value = data
|
|
||||||
})
|
|
||||||
api.getObj({menu: node.menu_id, role: editedRoleInfo.value.id}).then((res: any) => {
|
|
||||||
const {data} = res
|
|
||||||
buttonPermissionData.value = data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
const menuTree = ref()
|
|
||||||
/*****菜单的配置项***/
|
|
||||||
/***按钮授权的弹窗****/
|
|
||||||
//是否显示新增表单
|
|
||||||
const dialogFormVisible = ref(false)
|
|
||||||
//部门树
|
|
||||||
const deptTree = ref()
|
|
||||||
//自定义部门数据
|
|
||||||
const deptOptions = ref()
|
|
||||||
//选中的部门数据
|
|
||||||
const deptCheckedKeys = []
|
|
||||||
//按钮表单
|
|
||||||
const buttonForm = reactive({
|
|
||||||
menu_button: null,
|
|
||||||
role: null,
|
|
||||||
menu: null,
|
|
||||||
data_range: null,
|
|
||||||
dept: []
|
|
||||||
})
|
|
||||||
//按钮表格数据
|
|
||||||
let buttonPermissionData = ref([])
|
|
||||||
//按钮表单验证
|
|
||||||
const buttonRules = reactive<FormRules>({
|
|
||||||
menu_button: [
|
|
||||||
{required: true, message: '必填项'}
|
|
||||||
],
|
|
||||||
data_range: [
|
|
||||||
{required: true, message: '必填项'}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
//新增按钮
|
|
||||||
const buttonFormRef = ref<FormInstance>()
|
|
||||||
const createBtnPermission = () => {
|
|
||||||
dialogFormVisible.value = true
|
|
||||||
buttonForm.menu_button = null
|
|
||||||
buttonForm.menu = null
|
|
||||||
buttonForm.role = null
|
|
||||||
buttonForm.data_range = null
|
|
||||||
buttonForm.dept = []
|
|
||||||
}
|
|
||||||
//权限范围数据
|
|
||||||
const dataScopeOptions = ref<[]>()
|
|
||||||
//按钮值变化事件
|
|
||||||
const onChangeButton = (val: any) => {
|
|
||||||
dataScopeOptions.value = []
|
|
||||||
//获取权限值范围
|
|
||||||
api.GetDataScope({menu_button: val}).then((res: any) => {
|
|
||||||
dataScopeOptions.value = res.data
|
|
||||||
})
|
|
||||||
//获取权限部门值
|
|
||||||
api.GetDataScopeDept({menu_button: val}).then((res: any) => {
|
|
||||||
deptOptions.value = XEUtils.toArrayTree(res.data, {parentKey: 'parent', strict: false})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
//过滤按钮名称
|
|
||||||
const formatMenuBtn = (val: any) => {
|
|
||||||
let obj: any = buttonOptions.value?.find((item: any) => {
|
|
||||||
return item.id === val
|
|
||||||
})
|
|
||||||
return obj ? obj.name : null
|
|
||||||
}
|
|
||||||
//过滤权限范围
|
|
||||||
const formatDataRange = (val: any) => {
|
|
||||||
let obj: any = [
|
|
||||||
{
|
|
||||||
"value": 0,
|
|
||||||
"label": '仅本人数据权限'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 1,
|
|
||||||
"label": '本部门及以下数据权限'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 2,
|
|
||||||
"label": '本部门数据权限'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 3,
|
|
||||||
"label": '全部数据权限'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 4,
|
|
||||||
"label": '自定义数据权限'
|
|
||||||
}
|
|
||||||
].find((item: any) => {
|
|
||||||
return item.value === val
|
|
||||||
})
|
|
||||||
return obj ? obj.label : null
|
|
||||||
}
|
|
||||||
//保存按钮表单
|
|
||||||
|
|
||||||
const onSaveButtonForm = async () => {
|
|
||||||
const {id: roleId} = editedRoleInfo.value
|
|
||||||
const {id: menuId} = editedMenuInfo.value
|
|
||||||
const form: any = Object.assign({}, buttonForm)
|
|
||||||
form.role = roleId
|
|
||||||
form.menu = menuId
|
|
||||||
//选中的部门
|
|
||||||
const checkedList = deptTree.value.getCheckedKeys()
|
|
||||||
form.dept = checkedList
|
|
||||||
if (!buttonFormRef.value) return
|
|
||||||
await buttonFormRef.value.validate((valid, fields) => {
|
|
||||||
if (valid) {
|
|
||||||
api.CreatePermission(form).then((res: any) => {
|
|
||||||
const {data} = res
|
|
||||||
buttonPermissionData.value.push(data)
|
|
||||||
dialogFormVisible.value = false
|
|
||||||
ElMessage({
|
|
||||||
type: 'success',
|
|
||||||
message: res.msg,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
ElMessage({
|
|
||||||
type: 'error',
|
|
||||||
title: '提交错误',
|
|
||||||
message: 'F12控制台看详情',
|
|
||||||
})
|
|
||||||
console.log('提交错误', fields)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
//删除按钮权限
|
|
||||||
const onDeleteBtn = (scope: any) => {
|
|
||||||
const {row, $index} = scope
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
'您是否要删除数据?',
|
|
||||||
'温馨提示',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
}
|
|
||||||
).then(() => {
|
|
||||||
api.DeletePermission({id: row.id}).then((res: any) => {
|
|
||||||
buttonPermissionData.value.splice($index, 1)
|
|
||||||
ElMessage({
|
|
||||||
type: 'success',
|
|
||||||
message: res.msg,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
ElMessage({
|
|
||||||
type: 'info',
|
|
||||||
message: '取消删除',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
/***按钮授权的弹窗****/
|
|
||||||
//初始化数据
|
|
||||||
const initGet = () => {
|
|
||||||
getMenuData()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存授权
|
|
||||||
*/
|
|
||||||
const onSaveAuth = () => {
|
|
||||||
//选中的菜单
|
|
||||||
const checkedList = menuTree.value.getCheckedKeys()
|
|
||||||
//半选中的菜单
|
|
||||||
const halfCheckedList = menuTree.value.getHalfCheckedKeys()
|
|
||||||
//合并的菜单数据
|
|
||||||
const menuIdList = [...checkedList, ...halfCheckedList]
|
|
||||||
// console.log(menuIdList)
|
|
||||||
const {id: roleId} = editedRoleInfo.value
|
|
||||||
const data = {
|
|
||||||
role: roleId,
|
|
||||||
menu: menuIdList
|
|
||||||
}
|
|
||||||
api.SaveMenuPermission(data).then((res: any) => {
|
|
||||||
ElMessage({
|
|
||||||
message: res.msg,
|
|
||||||
type: 'success',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
defineExpose({drawer, editedRoleInfo, initGet})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dept-tree::-webkit-scrollbar {
|
|
||||||
display: none; /* Chrome Safari */
|
|
||||||
}
|
|
||||||
|
|
||||||
.dept-tree {
|
|
||||||
height: 160px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
scrollbar-width: none; /* firefox */
|
|
||||||
-ms-overflow-style: none; /* IE 10+ */
|
|
||||||
border: 1px solid #e1e1e1;
|
|
||||||
width: 16em;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -3,7 +3,7 @@ import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOption
|
|||||||
import { request } from '/@/utils/service';
|
import { request } from '/@/utils/service';
|
||||||
import { dictionary } from '/@/utils/dictionary';
|
import { dictionary } from '/@/utils/dictionary';
|
||||||
import { successMessage } from '/@/utils/message';
|
import { successMessage } from '/@/utils/message';
|
||||||
import { inject } from 'vue';
|
import { auth } from '/@/utils/authFunction';
|
||||||
|
|
||||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const pageRequest = async (query: UserPageQuery) => {
|
const pageRequest = async (query: UserPageQuery) => {
|
||||||
@@ -24,8 +24,6 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
return await api.exportData(query)
|
return await api.exportData(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
//权限判定
|
|
||||||
const hasPermissions:any = inject('$hasPermissions');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
@@ -43,8 +41,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
actionbar: {
|
actionbar: {
|
||||||
buttons: {
|
buttons: {
|
||||||
add: {
|
add: {
|
||||||
show: hasPermissions('user:Create')
|
show: auth('user:Create')
|
||||||
// show:true
|
|
||||||
},
|
},
|
||||||
export:{
|
export:{
|
||||||
text:"导出",//按钮文字
|
text:"导出",//按钮文字
|
||||||
@@ -66,17 +63,17 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
edit: {
|
edit: {
|
||||||
iconRight: 'Edit',
|
iconRight: 'Edit',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
show: hasPermissions('user:Update'),
|
show: auth('user:Update'),
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
iconRight: 'Delete',
|
iconRight: 'Delete',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
show: hasPermissions('user:Delete'),
|
show: auth('user:Delete'),
|
||||||
},
|
},
|
||||||
custom: {
|
custom: {
|
||||||
text: '重设密码',
|
text: '重设密码',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
show: hasPermissions('user:ResetPassword'),
|
show: auth('user:ResetPassword'),
|
||||||
tooltip: {
|
tooltip: {
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
content: '重设密码',
|
content: '重设密码',
|
||||||
|
|||||||
@@ -1,237 +1,251 @@
|
|||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
import {
|
||||||
import { request } from '/@/utils/service';
|
dict,
|
||||||
import { dictionary } from '/@/utils/dictionary';
|
UserPageQuery,
|
||||||
import { successMessage } from '/@/utils/message';
|
AddReq,
|
||||||
import {inject} from "vue";
|
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 {
|
export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const pageRequest = async (query: UserPageQuery) => {
|
const pageRequest = async (query: UserPageQuery) => {
|
||||||
return await api.GetList(query);
|
return await api.GetList(query);
|
||||||
};
|
};
|
||||||
const editRequest = async ({ form, row }: EditReq) => {
|
const editRequest = async ({form, row}: EditReq) => {
|
||||||
form.id = row.id;
|
form.id = row.id;
|
||||||
return await api.UpdateObj(form);
|
return await api.UpdateObj(form);
|
||||||
};
|
};
|
||||||
const delRequest = async ({ row }: DelReq) => {
|
const delRequest = async ({row}: DelReq) => {
|
||||||
return await api.DelObj(row.id);
|
return await api.DelObj(row.id);
|
||||||
};
|
};
|
||||||
const addRequest = async ({ form }: AddReq) => {
|
const addRequest = async ({form}: AddReq) => {
|
||||||
return await api.AddObj(form);
|
return await api.AddObj(form);
|
||||||
};
|
};
|
||||||
|
|
||||||
//权限判定
|
|
||||||
const hasPermissions = inject("$hasPermissions")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
request: {
|
request: {
|
||||||
pageRequest,
|
pageRequest,
|
||||||
addRequest,
|
addRequest,
|
||||||
editRequest,
|
editRequest,
|
||||||
delRequest,
|
delRequest,
|
||||||
},
|
},
|
||||||
rowHandle: {
|
actionbar: {
|
||||||
//固定右侧
|
buttons: {
|
||||||
fixed: 'right',
|
add: {
|
||||||
width: 150,
|
show: auth('api_white_list:Create')
|
||||||
buttons: {
|
}
|
||||||
view: {
|
}
|
||||||
show: false,
|
},
|
||||||
},
|
rowHandle: {
|
||||||
edit: {
|
//固定右侧
|
||||||
iconRight: 'Edit',
|
fixed: 'right',
|
||||||
type: 'text',
|
width: 150,
|
||||||
show:hasPermissions("api_white_list:Update")
|
buttons: {
|
||||||
},
|
view: {
|
||||||
remove: {
|
show: false,
|
||||||
iconRight: 'Delete',
|
},
|
||||||
type: 'text',
|
edit: {
|
||||||
show:hasPermissions("api_white_list:Delete")
|
iconRight: 'Edit',
|
||||||
},
|
type: 'text',
|
||||||
},
|
show: auth("api_white_list:Update")
|
||||||
},
|
},
|
||||||
form: {
|
remove: {
|
||||||
col: { span: 24 },
|
iconRight: 'Delete',
|
||||||
labelWidth: '110px',
|
type: 'text',
|
||||||
wrapper: {
|
show: auth("api_white_list:Delete")
|
||||||
is: 'el-dialog',
|
},
|
||||||
width: '600px',
|
},
|
||||||
},
|
},
|
||||||
},
|
form: {
|
||||||
columns: {
|
col: {span: 24},
|
||||||
_index: {
|
labelWidth: '110px',
|
||||||
title: '序号',
|
wrapper: {
|
||||||
form: { show: false },
|
is: 'el-dialog',
|
||||||
column: {
|
width: '600px',
|
||||||
//type: 'index',
|
},
|
||||||
align: 'center',
|
},
|
||||||
width: '70px',
|
columns: {
|
||||||
columnSetDisabled: true, //禁止在列设置中选择
|
_index: {
|
||||||
//@ts-ignore
|
title: '序号',
|
||||||
formatter: (context) => {
|
form: {show: false},
|
||||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
column: {
|
||||||
let index = context.index ?? 1;
|
//type: 'index',
|
||||||
let pagination: any = crudExpose!.crudBinding.value.pagination;
|
align: 'center',
|
||||||
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
width: '70px',
|
||||||
},
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
},
|
//@ts-ignore
|
||||||
},
|
formatter: (context) => {
|
||||||
search: {
|
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||||
title: '关键词',
|
let index = context.index ?? 1;
|
||||||
column: {
|
let pagination: any = crudExpose!.crudBinding.value.pagination;
|
||||||
show: false,
|
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||||
},
|
},
|
||||||
search: {
|
},
|
||||||
show: true,
|
},
|
||||||
component: {
|
search: {
|
||||||
props: {
|
title: '关键词',
|
||||||
clearable: true,
|
column: {
|
||||||
},
|
show: false,
|
||||||
placeholder: '请输入关键词',
|
},
|
||||||
},
|
search: {
|
||||||
},
|
show: true,
|
||||||
form: {
|
component: {
|
||||||
show: false,
|
props: {
|
||||||
component: {
|
clearable: true,
|
||||||
props: {
|
},
|
||||||
clearable: true,
|
placeholder: '请输入关键词',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
form: {
|
||||||
},
|
show: false,
|
||||||
method: {
|
component: {
|
||||||
title: '请求方式',
|
props: {
|
||||||
sortable: 'custom',
|
clearable: true,
|
||||||
search: {
|
},
|
||||||
disabled: false,
|
},
|
||||||
},
|
},
|
||||||
type: 'dict-select',
|
},
|
||||||
dict: dict({
|
method: {
|
||||||
data: [
|
title: '请求方式',
|
||||||
{
|
sortable: 'custom',
|
||||||
label: 'GET',
|
search: {
|
||||||
value: 0,
|
disabled: false,
|
||||||
},
|
},
|
||||||
{
|
type: 'dict-select',
|
||||||
label: 'POST',
|
dict: dict({
|
||||||
value: 1,
|
data: [
|
||||||
},
|
{
|
||||||
{
|
label: 'GET',
|
||||||
label: 'PUT',
|
value: 0,
|
||||||
value: 2,
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'POST',
|
||||||
label: 'DELETE',
|
value: 1,
|
||||||
value: 3,
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'PUT',
|
||||||
label: 'PATCH',
|
value: 2,
|
||||||
value: 4,
|
},
|
||||||
},
|
{
|
||||||
],
|
label: 'DELETE',
|
||||||
}),
|
value: 3,
|
||||||
column:{
|
},
|
||||||
minWidth: 120,
|
{
|
||||||
},
|
label: 'PATCH',
|
||||||
form: {
|
value: 4,
|
||||||
rules: [
|
},
|
||||||
// 表单校验规则
|
],
|
||||||
{
|
}),
|
||||||
required: true,
|
column: {
|
||||||
message: '必填项',
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
],
|
form: {
|
||||||
component: {
|
rules: [
|
||||||
span: 12,
|
// 表单校验规则
|
||||||
},
|
{
|
||||||
itemProps: {
|
required: true,
|
||||||
class: { yxtInput: true },
|
message: '必填项',
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
},
|
component: {
|
||||||
url: {
|
span: 12,
|
||||||
title: '接口地址',
|
},
|
||||||
sortable: 'custom',
|
itemProps: {
|
||||||
search: {
|
class: {yxtInput: true},
|
||||||
disabled: true,
|
},
|
||||||
},
|
},
|
||||||
type: 'dict-select',
|
},
|
||||||
dict: dict({
|
url: {
|
||||||
async getData(dict: any) {
|
title: '接口地址',
|
||||||
return request('/swagger.json').then((ret: any) => {
|
sortable: 'custom',
|
||||||
const res = Object.keys(ret.paths);
|
search: {
|
||||||
const data = [];
|
disabled: true,
|
||||||
for (const item of res) {
|
},
|
||||||
const obj = { label: '', value: '' };
|
type: 'dict-select',
|
||||||
obj.label = item;
|
dict: dict({
|
||||||
obj.value = item;
|
async getData(dict: any) {
|
||||||
data.push(obj);
|
return request('/swagger.json').then((ret: any) => {
|
||||||
}
|
const res = Object.keys(ret.paths);
|
||||||
return data;
|
const data = [];
|
||||||
});
|
for (const item of res) {
|
||||||
},
|
const obj = {label: '', value: ''};
|
||||||
}),
|
obj.label = item;
|
||||||
column:{
|
obj.value = item;
|
||||||
minWidth: 200,
|
data.push(obj);
|
||||||
},
|
}
|
||||||
form: {
|
return data;
|
||||||
rules: [
|
});
|
||||||
// 表单校验规则
|
},
|
||||||
{
|
}),
|
||||||
required: true,
|
column: {
|
||||||
message: '必填项',
|
minWidth: 200,
|
||||||
},
|
},
|
||||||
],
|
form: {
|
||||||
component: {
|
rules: [
|
||||||
span: 24,
|
// 表单校验规则
|
||||||
props: {
|
{
|
||||||
elProps: {
|
required: true,
|
||||||
allowCreate: true,
|
message: '必填项',
|
||||||
filterable: true,
|
},
|
||||||
clearable: true,
|
],
|
||||||
},
|
component: {
|
||||||
},
|
span: 24,
|
||||||
},
|
props: {
|
||||||
itemProps: {
|
elProps: {
|
||||||
class: { yxtInput: true },
|
allowCreate: true,
|
||||||
},
|
filterable: true,
|
||||||
helper: {
|
clearable: true,
|
||||||
position: 'label',
|
},
|
||||||
tooltip: {
|
},
|
||||||
placement: 'top-start',
|
},
|
||||||
},
|
itemProps: {
|
||||||
text: '请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/',
|
class: {yxtInput: true},
|
||||||
},
|
},
|
||||||
},
|
helper: {
|
||||||
},
|
position: 'label',
|
||||||
enable_datasource: {
|
tooltip: {
|
||||||
title: '数据权限认证',
|
placement: 'top-start',
|
||||||
search: {
|
},
|
||||||
disabled: false,
|
text: '请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/',
|
||||||
},
|
},
|
||||||
type: 'dict-radio',
|
},
|
||||||
column: {
|
},
|
||||||
minWidth:120,
|
enable_datasource: {
|
||||||
component: {
|
title: '数据权限认证',
|
||||||
name: 'fs-dict-switch',
|
search: {
|
||||||
activeText: '',
|
disabled: false,
|
||||||
inactiveText: '',
|
},
|
||||||
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
type: 'dict-radio',
|
||||||
onChange: compute((context) => {
|
column: {
|
||||||
return () => {
|
minWidth: 120,
|
||||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
component: {
|
||||||
successMessage(res.msg as string);
|
name: 'fs-dict-switch',
|
||||||
});
|
activeText: '',
|
||||||
};
|
inactiveText: '',
|
||||||
}),
|
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
||||||
},
|
onChange: compute((context) => {
|
||||||
},
|
return () => {
|
||||||
dict: dict({
|
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||||
data: dictionary('button_status_bool'),
|
successMessage(res.msg as string);
|
||||||
}),
|
});
|
||||||
},
|
};
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
|
dict: dict({
|
||||||
|
data: dictionary('button_status_bool'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user