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.其他优化
|
||||
93
README.en.md
93
README.en.md
@@ -1,14 +1,14 @@
|
||||
# Django-Vue3-Admin
|
||||
|
||||
[](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/liqianglog/django-vue-admin)
|
||||
[](https://gitee.com/huge-dream/django-vue3-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/huge-dream/django-vue3-admin)
|
||||
|
||||
[preview](https://demo.dvadmin.com) | [Official website document](https://www.django-vue-admin.com) | [qq group](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [community](https://bbs.django-vue-admin.com) | [plugins market](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
|
||||
|
||||
💡 **「About」**
|
||||
|
||||
We are a group of young people who love Code. In this hot era, we hope to calm down and bring some of our colors and colors through code.
|
||||
It is a completely open-source rapid development platform, provided free for personal use and authorized for group use.
|
||||
Django-Vue3-Admin is a comprehensive basic development platform based on the RBAC (Role-Based Access Control) model for permission control, with column-level granularity. It follows a frontend-backend separation architecture, with Django and Django Rest Framework used for the backend, and Vue3, Composition API, TypeScript, Vite, and Element Plus used for the frontend.
|
||||
|
||||
Because of love, so embrace the future
|
||||
|
||||
## framework introduction
|
||||
|
||||
@@ -18,12 +18,13 @@ Because of love, so embrace the future
|
||||
* 👭The backend uses the Python language Django framework as well as the powerful[Django REST Framework](https://pypi.org/project/djangorestframework)。
|
||||
* 👫Permission authentication use[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),Supports the multi-terminal authentication system.
|
||||
* 👬Support loading dynamic permission menu, multi - way easy permission control.
|
||||
* 💏 Special thanks: [vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/).
|
||||
* 💡 💏 Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
|
||||
* 👬Enhanced Column Permission Control, with granularity down to each column.
|
||||
* 💏Special thanks: [vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/).
|
||||
* 💡Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
|
||||
|
||||
## Online experience
|
||||
|
||||
👩👧👦👩👧👦 demo address:[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com)
|
||||
👩👧👦👩👧👦 demo address:[https://demo.dvadmin.com](https://demo.dvadmin.com)
|
||||
|
||||
* demo account:superadmin
|
||||
|
||||
@@ -39,57 +40,61 @@ Because of love, so embrace the future
|
||||
|
||||
## source code url:
|
||||
|
||||
gitee(Main push):[https://gitee.com/liqianglog/django-vue-admin](https://gitee.com/liqianglog/django-vue-admin)👩👦👦
|
||||
gitee(Main push):[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
github:[https://github.com/liqianglog/django-vue-admin](https://github.com/liqianglog/django-vue-admin)👩👦👦
|
||||
github:[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
## core function
|
||||
|
||||
1. 👨⚕️ Menu management: Configure the system menu, operation permissions, button permissions, back-end interface permissions, etc.
|
||||
2. 🧑⚕️ Department management: Configure the system organization (company, department, role).
|
||||
3. 👩⚕️ Role management: role menu permission allocation, data permission allocation, set roles according to the department for data range permission division.
|
||||
4. 🧑🎓 Rights Specifies the rights of the authorization role.
|
||||
5. 👨🎓 User management: The user is the system operator, this function mainly completes the system user configuration.
|
||||
6. 👬 Interface whitelist: specifies the interface that does not need permission verification.
|
||||
7. 🧑🔧 Dictionary management: Maintenance of some fixed data frequently used in the system.
|
||||
8. 🧑🔧 Regional management: to manage provinces, cities, counties and regions.
|
||||
9. 📁 Attachment management: Unified management of all files and pictures on the platform.
|
||||
10. 🗓 ️operation logs: log and query the system normal operation; Log and query system exception information.
|
||||
11.🔌 [plugins market] (<https://bbs.django-vue-admin.com/plugMarket.html>) : based on the Django framework - Vue - Admin application and plug-in development.
|
||||
1. 👨⚕️Menu Management: Configure system menus, operation permissions, button permission flags, backend interface permissions, etc.
|
||||
2. 🧑⚕️Department Management: Configure system organizational structure (company, department, role).
|
||||
3. 👩⚕️Role Management: Role menu permission assignment, data permission assignment, set role-based data scope permissions by department.
|
||||
4. 🧑🎓Button Permission Control: Authorize role-specific button permissions and interface permissions, enabling authorization of data scope for each interface.
|
||||
5. 🧑🎓Field Column Permission Control: Authorize page field display permissions, specifically for the display permissions of a certain column.
|
||||
6. 👨🎓User Management: Users are system operators, and this function is mainly used for system user configuration.
|
||||
7. 👬API Whitelist: Configure interfaces that do not require permission verification.
|
||||
8. 🧑🔧Dictionary Management: Maintain frequently used and relatively fixed data in the system.
|
||||
9. 🧑🔧Region Management: Manage provinces, cities, counties, and districts.
|
||||
10. 📁File Management: Unified management of all files, images, etc., on the platform.
|
||||
11. 🗓️Operation Logs: Record and query logs for normal system operations and exceptional system information.
|
||||
12. 🔌[Plugin Market](https://bbs.django-vue-admin.com/plugMarket.html): Applications and plugins developed based on the Django-Vue-Admin framework.
|
||||
|
||||
## plugins market 🔌
|
||||
|
||||
* Celery Asynchronous task:[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
|
||||
* Upgrade center backend:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
|
||||
* Upgrade center front:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
|
||||
Updating...
|
||||
|
||||
## Repository Branch Explanation 💈
|
||||
Main Branch: master (stable version)
|
||||
Development Branch: develop
|
||||
|
||||
## before start project you need:
|
||||
|
||||
~~~
|
||||
Python >= 3.8.0
|
||||
nodejs >= 14.0
|
||||
Mysql >= 5.7.0 (Optional. The default database is sqlite3. 8.0 is recommended)
|
||||
Redis(Optional, the latest edition)
|
||||
Python >= 3.11.0 (Minimum version 3.9+)
|
||||
Node.js >= 16.0
|
||||
Mysql >= 8.0 (Optional, default database: SQLite3, supports 5.7+, recommended version: 8.0)
|
||||
Redis (Optional, latest version)
|
||||
~~~
|
||||
|
||||
## frontend♝
|
||||
|
||||
```bash
|
||||
# clone code
|
||||
git clone https://gitee.com/liqianglog/django-vue-admin.git
|
||||
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||
|
||||
# enter code dir
|
||||
cd web
|
||||
|
||||
# install dependence
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
npm install yarn
|
||||
yarn install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# Start service
|
||||
npm run dev
|
||||
yarn run dev
|
||||
# Visit http://localhost:8080 in your browser
|
||||
# Parameters such as boot port can be configured in the #.env.development file
|
||||
# Build the production environment
|
||||
# npm run build
|
||||
# yarn run build
|
||||
```
|
||||
|
||||
## backend💈
|
||||
@@ -111,8 +116,8 @@ npm run dev
|
||||
python3 manage.py init_area
|
||||
8. start backend
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
or daphne :
|
||||
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
||||
or uvicorn :
|
||||
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8
|
||||
~~~
|
||||
|
||||
### visit backend swagger
|
||||
@@ -125,7 +130,7 @@ or daphne :
|
||||
~~~shell
|
||||
docker-compose up -d
|
||||
# Initialize backend data (first execution only)
|
||||
docker exec -ti dvadmin-django bash
|
||||
docker exec -ti dvadmin3-django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init_area
|
||||
@@ -147,22 +152,24 @@ docker-compose up -d --build
|
||||
|
||||
## Demo screenshot✅
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

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

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -98,3 +98,4 @@ media/
|
||||
__pypackages__/
|
||||
package-lock.json
|
||||
gunicorn.pid
|
||||
!plugins/__init__.py
|
||||
|
||||
@@ -15,8 +15,6 @@ import sys
|
||||
from pathlib import Path
|
||||
from datetime import timedelta
|
||||
|
||||
from conf.env import *
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
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
|
||||
# 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)
|
||||
ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
|
||||
|
||||
# Application definition
|
||||
# 列权限需要排除的App应用
|
||||
COLUMN_EXCLUDE_APPS = ['channels', 'captcha'] + locals().get("COLUMN_EXCLUDE_APPS", [])
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.auth",
|
||||
@@ -57,8 +58,8 @@ INSTALLED_APPS = [
|
||||
"corsheaders", # 注册跨域app
|
||||
"drf_yasg",
|
||||
"captcha",
|
||||
'channels',
|
||||
*locals().get("CUSTOM_APPS", []), # 所有项目里写的app需要在env.py文件里的CUSTOM_APPS中
|
||||
"channels",
|
||||
"dvadmin.system",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
@@ -73,7 +73,7 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer):
|
||||
unread_count = await _get_message_unread(self.user_id)
|
||||
if unread_count == 0:
|
||||
# 发送连接成功
|
||||
await self.send_json(set_message('system', 'SYSTEM', '连接成功'))
|
||||
await self.send_json(set_message('system', 'SYSTEM', '您已上线'))
|
||||
else:
|
||||
await self.send_json(
|
||||
set_message('system', 'SYSTEM', "请查看您的未读消息~",
|
||||
|
||||
@@ -44,8 +44,5 @@ LOGIN_NO_CAPTCHA_AUTH = True
|
||||
# ================================================= #
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
CUSTOM_APPS = [
|
||||
"dvadmin.system",
|
||||
]
|
||||
# daphne启动命令
|
||||
#daphne application.asgi:application -b 0.0.0.0 -p 8000
|
||||
# 列权限中排除App应用
|
||||
COLUMN_EXCLUDE_APPS = []
|
||||
|
||||
@@ -9,7 +9,7 @@ django.setup()
|
||||
from dvadmin.system.models import (
|
||||
Role, Dept, Users, Menu, MenuButton,
|
||||
ApiWhiteList, Dictionary, SystemConfig,
|
||||
RoleMenuPermission, RoleMenuButtonPermission
|
||||
RoleMenuPermission, RoleMenuButtonPermission, MenuField
|
||||
)
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
|
||||
@@ -53,6 +53,16 @@ class MenuButtonInitSerializer(CustomModelSerializer):
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MenuFieldInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化列权限-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuField
|
||||
fields = ['id', 'menu','field_name','title','model']
|
||||
read_only_fields = ["id"]
|
||||
|
||||
class MenuInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
递归深度获取数信息(用于生成初始化json文件)
|
||||
@@ -60,7 +70,7 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
name = serializers.CharField(required=False)
|
||||
children = serializers.SerializerMethodField()
|
||||
menu_button = serializers.SerializerMethodField()
|
||||
|
||||
menu_field = serializers.SerializerMethodField()
|
||||
def get_children(self, obj: Menu):
|
||||
data = []
|
||||
instance = Menu.objects.filter(parent_id=obj.id)
|
||||
@@ -76,10 +86,18 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
data = list(instance.values('name', 'value', 'api', 'method'))
|
||||
return data
|
||||
|
||||
def get_menu_field(self, obj: Menu):
|
||||
data = []
|
||||
instance = obj.menufield_set.order_by('field_name')
|
||||
if instance:
|
||||
data = list(instance.values('field_name', 'title','model'))
|
||||
return data
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
children = self.initial_data.get('children')
|
||||
menu_button = self.initial_data.get('menu_button')
|
||||
menu_field = self.initial_data.get('menu_field')
|
||||
# 菜单表
|
||||
if children:
|
||||
for menu_data in children:
|
||||
@@ -108,12 +126,24 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
# 列权限
|
||||
if menu_field:
|
||||
for field_data in menu_field:
|
||||
field_data['menu'] = instance.id
|
||||
filter_data = {
|
||||
'menu':field_data['menu'],
|
||||
'field_name':field_data['field_name']
|
||||
}
|
||||
instance_obj = MenuField.objects.filter(**filter_data).first()
|
||||
serializer = MenuFieldInitSerializer(instance_obj, data=field_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
|
||||
'cache', 'visible', 'parent', 'children', 'menu_button', 'creator', 'dept_belong_id']
|
||||
'cache', 'visible', 'parent', 'children', 'menu_button','menu_field', 'creator', 'dept_belong_id']
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
@@ -128,7 +158,7 @@ class RoleInitSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = ['name', 'key', 'sort', 'status', 'admin',
|
||||
fields = ['name', 'key', 'sort', 'status',
|
||||
'creator', 'dept_belong_id']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@
|
||||
"key": "admin",
|
||||
"sort": 1,
|
||||
"status": true,
|
||||
"admin": true,
|
||||
"remark": null
|
||||
},
|
||||
{
|
||||
@@ -12,7 +11,6 @@
|
||||
"key": "public",
|
||||
"sort": 2,
|
||||
"status": true,
|
||||
"admin": true,
|
||||
"remark": null
|
||||
}
|
||||
]
|
||||
|
||||
@@ -65,6 +65,20 @@
|
||||
"placeholder": null,
|
||||
"setting": null,
|
||||
"children": [
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "网站标题",
|
||||
"key": "site_title",
|
||||
"value": "Dvadmin",
|
||||
"sort": 1,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [],
|
||||
"placeholder": "请输入网站标题",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "网站名称",
|
||||
@@ -116,7 +130,7 @@
|
||||
"parent": 1,
|
||||
"title": "版权信息",
|
||||
"key": "copyright",
|
||||
"value": "2021-2022 django-vue-admin.com 版权所有",
|
||||
"value": "2021-2024 django-vue-admin.com 版权所有",
|
||||
"sort": 4,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
|
||||
@@ -13,7 +13,6 @@ class Role(CoreModel):
|
||||
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
|
||||
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
|
||||
status = models.BooleanField(default=True, verbose_name="角色状态", help_text="角色状态")
|
||||
admin = models.BooleanField(default=False, verbose_name="是否为admin", help_text="是否为admin")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_role"
|
||||
@@ -179,21 +178,27 @@ class Menu(CoreModel):
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("sort",)
|
||||
|
||||
|
||||
class Columns(CoreModel):
|
||||
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
|
||||
app = models.CharField(max_length=64, verbose_name='应用名')
|
||||
class MenuField(CoreModel):
|
||||
model = models.CharField(max_length=64, verbose_name='表名')
|
||||
menu = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name='菜单', db_constraint=False)
|
||||
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
|
||||
title = models.CharField(max_length=64, verbose_name='字段显示名')
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_menu_field"
|
||||
verbose_name = "菜单字段表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("id",)
|
||||
|
||||
class FieldPermission(CoreModel):
|
||||
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
|
||||
field = models.ForeignKey(to='MenuField', on_delete=models.CASCADE,related_name='menu_field', verbose_name='字段', db_constraint=False)
|
||||
is_query = models.BooleanField(default=1, verbose_name='是否可查询')
|
||||
is_create = models.BooleanField(default=1, verbose_name='是否可创建')
|
||||
is_update = models.BooleanField(default=1, verbose_name='是否可更新')
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_columns"
|
||||
verbose_name = "列权限表"
|
||||
db_table = table_prefix + "system_field_permission"
|
||||
verbose_name = "字段权限表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("id",)
|
||||
|
||||
|
||||
@@ -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.system_config import SystemConfigViewSet
|
||||
from dvadmin.system.views.user import UserViewSet
|
||||
from dvadmin.system.views.column import ColumnViewSet
|
||||
from dvadmin.system.views.menu_field import MenuFieldViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
system_url.register(r'menu', MenuViewSet)
|
||||
@@ -33,7 +33,7 @@ system_url.register(r'system_config', SystemConfigViewSet)
|
||||
system_url.register(r'message_center', MessageCenterViewSet)
|
||||
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
|
||||
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
||||
system_url.register(r'column', ColumnViewSet)
|
||||
system_url.register(r'column', MenuFieldViewSet)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
@@ -119,8 +119,7 @@ class MenuViewSet(CustomModelViewSet):
|
||||
def web_router(self, request):
|
||||
"""用于前端获取当前角色的路由"""
|
||||
user = request.user
|
||||
is_admin = user.role.values_list('admin', flat=True)
|
||||
if user.is_superuser or True in is_admin:
|
||||
if user.is_superuser:
|
||||
queryset = self.queryset.filter(status=1)
|
||||
else:
|
||||
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 dvadmin.system.models import MenuButton, RoleMenuButtonPermission
|
||||
from dvadmin.utils.json_response import DetailResponse
|
||||
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
@@ -49,12 +49,24 @@ class MenuButtonViewSet(CustomModelViewSet):
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = MenuButton.objects.all()
|
||||
queryset = MenuButton.objects.order_by('create_datetime')
|
||||
serializer_class = MenuButtonSerializer
|
||||
create_serializer_class = MenuButtonCreateUpdateSerializer
|
||||
update_serializer_class = MenuButtonCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""
|
||||
重写list方法
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(serializer.data,msg="获取成功")
|
||||
|
||||
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
|
||||
def menu_button_all_permission(self,request):
|
||||
"""
|
||||
@@ -63,8 +75,7 @@ class MenuButtonViewSet(CustomModelViewSet):
|
||||
:return:
|
||||
"""
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
if is_superuser:
|
||||
queryset = MenuButton.objects.values_list('value',flat=True)
|
||||
else:
|
||||
role_id = request.user.role.values_list('id', flat=True)
|
||||
|
||||
@@ -1,57 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.apps import apps
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Columns, Role
|
||||
from dvadmin.system.models import Role, MenuField
|
||||
from dvadmin.utils.models import get_custom_app_models
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse
|
||||
|
||||
|
||||
class ColumnSerializer(CustomModelSerializer):
|
||||
class MenuFieldSerializer(CustomModelSerializer):
|
||||
"""
|
||||
列权限序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Columns
|
||||
model = MenuField
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id']
|
||||
|
||||
|
||||
class ColumnViewSet(CustomModelViewSet):
|
||||
class MenuFieldViewSet(CustomModelViewSet):
|
||||
"""
|
||||
列权限视图集
|
||||
"""
|
||||
queryset = Columns.objects.all()
|
||||
serializer_class = ColumnSerializer
|
||||
queryset = MenuField.objects.all()
|
||||
serializer_class = MenuFieldSerializer
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
role_id = request.query_params.get('role')
|
||||
app_name = request.query_params.get('app')
|
||||
model_name = request.query_params.get('model')
|
||||
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([])
|
||||
queryset = self.filter_queryset(self.get_queryset().filter(role_id=role_id, model=model_name, app=app_name,menu_id=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)
|
||||
queryset = self.filter_queryset(self.get_queryset().filter(menu=menu))
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
payload = request.data
|
||||
for model in get_custom_app_models(payload.get('app')):
|
||||
if payload.get('model') == model['model']:
|
||||
for model in apps.get_models():
|
||||
if payload.get('model') == model.__name__:
|
||||
break
|
||||
else:
|
||||
return ErrorResponse(msg='模型表不存在')
|
||||
|
||||
if Columns.objects.filter(app=model['app'], model=model['model'], field_name=payload.get('field_name')).exists():
|
||||
if MenuField.objects.filter(menu=payload.get('menu'),model=model.__name__, field_name=payload.get('field_name')).exists():
|
||||
return ErrorResponse(msg='‘%s’ 字段权限已有,不可重复创建' % payload.get('title'))
|
||||
|
||||
return super().create(request, *args, **kwargs)
|
||||
@@ -60,8 +54,7 @@ class ColumnViewSet(CustomModelViewSet):
|
||||
def get_models(self, request):
|
||||
"""获取所有项目app下的model"""
|
||||
res = []
|
||||
for app in get_custom_app_models():
|
||||
for model in app:
|
||||
for model in get_custom_app_models():
|
||||
res.append({
|
||||
'app': model['app'],
|
||||
'title': model['verbose'],
|
||||
@@ -72,22 +65,20 @@ class ColumnViewSet(CustomModelViewSet):
|
||||
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def auto_match_fields(self, request):
|
||||
"""自动匹配已有的字段"""
|
||||
role_id = request.data.get('role')
|
||||
app_name = request.data.get('app')
|
||||
menu_id = request.data.get('menu')
|
||||
model_name = request.data.get('model')
|
||||
if not role_id or not model_name or not app_name:
|
||||
return DetailResponse([], msg='无操作')
|
||||
for model in get_custom_app_models(app_name):
|
||||
if not menu_id or not model_name:
|
||||
return ErrorResponse( msg='参数错误')
|
||||
for model in get_custom_app_models():
|
||||
if model['model'] != model_name:
|
||||
continue
|
||||
for field in model['fields']:
|
||||
if Columns.objects.filter(
|
||||
role_id=role_id, app=app_name, model=model_name, field_name=field['name']
|
||||
if MenuField.objects.filter(
|
||||
menu_id=menu_id, model=model_name, field_name=field['name']
|
||||
).exists():
|
||||
continue
|
||||
data = {
|
||||
'role': role_id,
|
||||
'app': app_name,
|
||||
'menu': menu_id,
|
||||
'model': model_name,
|
||||
'field_name': field['name'],
|
||||
'title': str(field['title']),
|
||||
@@ -11,7 +11,8 @@ from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, Columns
|
||||
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, FieldPermission, \
|
||||
MenuField
|
||||
from dvadmin.system.views.menu import MenuSerializer
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
@@ -71,12 +72,41 @@ class RoleButtonPermissionSerializer(CustomModelSerializer):
|
||||
model = MenuButton
|
||||
fields = ['id','name','value','isCheck','data_range']
|
||||
|
||||
class RoleColumnsSerializer(CustomModelSerializer):
|
||||
class RoleFieldPermissionSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Columns
|
||||
model = FieldPermission
|
||||
fields = "__all__"
|
||||
|
||||
class RoleMenuFieldSerializer(CustomModelSerializer):
|
||||
is_query = serializers.SerializerMethodField()
|
||||
is_create = serializers.SerializerMethodField()
|
||||
is_update = serializers.SerializerMethodField()
|
||||
|
||||
def get_is_query(self, instance):
|
||||
params = self.request.query_params
|
||||
queryset = instance.menu_field.filter(role=params.get('role')).first()
|
||||
if queryset:
|
||||
return queryset.is_query
|
||||
return False
|
||||
|
||||
def get_is_create(self, instance):
|
||||
params = self.request.query_params
|
||||
queryset = instance.menu_field.filter(role=params.get('role')).first()
|
||||
if queryset:
|
||||
return queryset.is_create
|
||||
return False
|
||||
|
||||
def get_is_update(self, instance):
|
||||
params = self.request.query_params
|
||||
queryset = instance.menu_field.filter(role=params.get('role')).first()
|
||||
if queryset:
|
||||
return queryset.is_update
|
||||
return False
|
||||
class Meta:
|
||||
model = MenuField
|
||||
fields = ['id','field_name','title','is_query','is_create','is_update']
|
||||
|
||||
|
||||
class RoleMenuPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
@@ -99,9 +129,8 @@ class RoleMenuPermissionSerializer(CustomModelSerializer):
|
||||
return serializer.data
|
||||
|
||||
def get_columns(self, instance):
|
||||
params = self.request.query_params
|
||||
col_list = Columns.objects.filter(role__id=params.get('role'),menu__id=instance['id'])
|
||||
serializer = RoleColumnsSerializer(col_list,many=True,request=self.request)
|
||||
col_list = MenuField.objects.filter(menu=instance['id'])
|
||||
serializer = RoleMenuFieldSerializer(col_list,many=True,request=self.request)
|
||||
return serializer.data
|
||||
|
||||
|
||||
@@ -165,10 +194,11 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
|
||||
for btn in menu.get('btns'):
|
||||
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',[]))
|
||||
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="授权成功")
|
||||
|
||||
|
||||
|
||||
@@ -352,6 +352,8 @@ class UserViewSet(CustomModelViewSet):
|
||||
"""
|
||||
密码重置
|
||||
"""
|
||||
if not self.request.user.is_superuser:
|
||||
return ErrorResponse(msg="只允许超级管理员对其进行密码重置")
|
||||
instance = Users.objects.filter(id=pk).first()
|
||||
data = request.data
|
||||
new_pwd = data.get("newPassword")
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db.models import F
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Columns
|
||||
from dvadmin.system.models import FieldPermission, MenuField
|
||||
from dvadmin.utils.json_response import DetailResponse
|
||||
from dvadmin.utils.models import get_custom_app_models
|
||||
|
||||
@@ -14,8 +15,7 @@ class FieldPermissionMixin:
|
||||
获取字段权限
|
||||
"""
|
||||
finded = False
|
||||
for app in get_custom_app_models():
|
||||
for model in app:
|
||||
for model in get_custom_app_models():
|
||||
if model['object'] is self.serializer_class.Meta.model:
|
||||
finded = True
|
||||
break
|
||||
@@ -23,16 +23,16 @@ class FieldPermissionMixin:
|
||||
break
|
||||
if finded is False:
|
||||
return []
|
||||
roles = request.user.role.values_list('id', flat=True)
|
||||
user = request.user
|
||||
if user.is_superuser==1:
|
||||
data = Columns.objects.filter(app=model['app'], model=model['model']).values('field_name', 'is_create', 'is_query', 'is_update')
|
||||
data = MenuField.objects.filter( model=model['model']).values('field_name')
|
||||
for item in data:
|
||||
item['is_create'] = True
|
||||
item['is_query'] = True
|
||||
item['is_update'] = True
|
||||
else:
|
||||
data= Columns.objects.filter(
|
||||
app=model['app'], model=model['model'],role__in=roles
|
||||
).values('field_name', 'is_create', 'is_query', 'is_update')
|
||||
roles = request.user.role.values_list('id', flat=True)
|
||||
data= FieldPermission.objects.filter(
|
||||
field__model=model['model'],role__in=roles
|
||||
).values( 'is_create', 'is_query', 'is_update',field_name=F('field__field_name'))
|
||||
return DetailResponse(data=data)
|
||||
@@ -121,13 +121,12 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
||||
role__status=1,
|
||||
menu_button__api=re_api,
|
||||
menu_button__method=method).values(
|
||||
'data_range',
|
||||
role_admin=F('role__admin')
|
||||
'data_range'
|
||||
)
|
||||
dataScope_list = [] # 权限范围列表
|
||||
for ele in role_permission_list:
|
||||
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
|
||||
if ele.get("data_range") == 3 or ele.get("role_admin") == True:
|
||||
if ele.get("data_range") == 3:
|
||||
return queryset
|
||||
dataScope_list.append(ele.get("data_range"))
|
||||
dataScope_list = list(set(dataScope_list))
|
||||
|
||||
@@ -13,6 +13,7 @@ from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
from application import settings
|
||||
|
||||
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
||||
|
||||
|
||||
@@ -71,10 +72,13 @@ class CoreModel(models.Model):
|
||||
id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
|
||||
description = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
|
||||
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
|
||||
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False)
|
||||
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL,
|
||||
db_constraint=False)
|
||||
modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人")
|
||||
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True, verbose_name="数据归属部门")
|
||||
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
|
||||
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True,
|
||||
verbose_name="数据归属部门")
|
||||
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间",
|
||||
verbose_name="修改时间")
|
||||
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
|
||||
verbose_name="创建时间")
|
||||
|
||||
@@ -136,10 +140,23 @@ def get_model_from_app(app_name):
|
||||
|
||||
|
||||
def get_custom_app_models(app_name=None):
|
||||
"""获取所有项目写的app里的models"""
|
||||
"""
|
||||
获取所有项目下的app里的models
|
||||
"""
|
||||
if app_name:
|
||||
return get_model_from_app(app_name)
|
||||
all_apps = apps.get_app_configs()
|
||||
res = []
|
||||
for app in settings.CUSTOM_APPS:
|
||||
res.append(get_model_from_app(app))
|
||||
for app in all_apps:
|
||||
if app.name.startswith('django'):
|
||||
continue
|
||||
if app.name in settings.COLUMN_EXCLUDE_APPS:
|
||||
continue
|
||||
try:
|
||||
all_models = get_model_from_app(app.name)
|
||||
if all_models:
|
||||
for model in all_models:
|
||||
res.append(model)
|
||||
except Exception as e:
|
||||
pass
|
||||
return res
|
||||
|
||||
@@ -79,6 +79,5 @@ class CustomPagination(PageNumberPagination):
|
||||
('total', total),
|
||||
('is_next', is_next),
|
||||
('is_previous', is_previous),
|
||||
('data', data),
|
||||
('permission', self.request.permission_fields)
|
||||
('data', data)
|
||||
]))
|
||||
|
||||
@@ -17,7 +17,7 @@ from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSeria
|
||||
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
|
||||
from dvadmin.utils.permission import CustomPermission
|
||||
from dvadmin.utils.models import get_custom_app_models
|
||||
from dvadmin.system.models import Columns
|
||||
from dvadmin.system.models import FieldPermission, MenuField
|
||||
from django_restql.mixins import QueryArgumentsMixin
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
serializer_class = self.get_serializer_class()
|
||||
kwargs.setdefault('context', self.get_serializer_context())
|
||||
# 全部以可见字段为准
|
||||
can_see = self.get_column_permission(serializer_class)
|
||||
can_see = self.get_menu_field(serializer_class)
|
||||
# 排除掉序列化器级的字段
|
||||
# sub_set = set(serializer_class._declared_fields.keys()) - set(can_see)
|
||||
# for field in sub_set:
|
||||
@@ -79,21 +79,17 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
else:
|
||||
return serializer_class(*args, **kwargs)
|
||||
|
||||
def get_column_permission(self, serializer_class):
|
||||
"""获取列权限"""
|
||||
def get_menu_field(self, serializer_class):
|
||||
"""获取字段权限"""
|
||||
finded = False
|
||||
for app in get_custom_app_models():
|
||||
for model in app:
|
||||
for model in get_custom_app_models():
|
||||
if model['object'] is serializer_class.Meta.model:
|
||||
finded = True
|
||||
break
|
||||
if finded:
|
||||
break
|
||||
if finded is False:
|
||||
return []
|
||||
return Columns.objects.filter(
|
||||
app=model['app'], model=model['model']
|
||||
).values('field_name', 'is_create', 'is_query', 'is_update')
|
||||
return MenuField.objects.filter(model=model['model']
|
||||
).values('field_name', 'title')
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data, request=request)
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
Django==4.1.5
|
||||
Django==4.2.7
|
||||
django-comment-migrate==0.1.7
|
||||
django-cors-headers==3.13.0
|
||||
django-filter==21.1
|
||||
django-cors-headers==4.3.0
|
||||
django-filter==23.3
|
||||
django-ranged-response==0.2.0
|
||||
djangorestframework==3.14.0
|
||||
django-restql==0.15.3
|
||||
django-simple-captcha==0.5.17
|
||||
django-timezone-field==5.0
|
||||
djangorestframework-simplejwt==5.2.2
|
||||
drf-yasg==1.21.4
|
||||
mysqlclient==2.1.1
|
||||
pypinyin==0.48.0
|
||||
ua-parser==0.16.1
|
||||
pyparsing==3.0.9
|
||||
openpyxl==3.0.10
|
||||
requests==2.28.2
|
||||
typing-extensions==4.4.0
|
||||
smmap==5.0.0
|
||||
tzlocal==4.1
|
||||
django-simple-captcha==0.5.20
|
||||
django-timezone-field==6.0.1
|
||||
djangorestframework-simplejwt==5.3.0
|
||||
drf-yasg==1.21.7
|
||||
mysqlclient==2.2.0
|
||||
pypinyin==0.49.0
|
||||
ua-parser==0.18.0
|
||||
pyparsing==3.1.1
|
||||
openpyxl==3.1.2
|
||||
requests==2.31.0
|
||||
typing-extensions==4.8.0
|
||||
tzlocal==5.1
|
||||
channels==4.0.0
|
||||
channels-redis==4.0.0
|
||||
websockets==10.4
|
||||
channels-redis==4.1.0
|
||||
websockets==11.0.3
|
||||
user-agents==2.2.0
|
||||
six==1.16.0
|
||||
whitenoise==6.3.0
|
||||
psycopg2==2.9.5
|
||||
uvicorn==0.21.1
|
||||
gunicorn==20.1.0
|
||||
gevent==22.10.2
|
||||
Pillow==8.3.2
|
||||
whitenoise==6.6.0
|
||||
psycopg2==2.9.9
|
||||
uvicorn==0.23.2
|
||||
gunicorn==21.2.0
|
||||
gevent==23.9.1
|
||||
Pillow==10.1.0
|
||||
|
||||
@@ -28,6 +28,6 @@ server {
|
||||
proxy_send_timeout 600s;
|
||||
real_ip_header X-Forwarded-For;
|
||||
rewrite ^/api/(.*)$ /$1 break; #重写
|
||||
proxy_pass http://177.8.0.12:8000/; # 设置代理服务器的协议和地址
|
||||
proxy_pass http://177.10.0.12:8000/; # 设置代理服务器的协议和地址
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-web:16.19-alpine
|
||||
WORKDIR /web/
|
||||
COPY web/. .
|
||||
RUN yarn install
|
||||
RUN yarn install --registry=https://registry.npm.taobao.org
|
||||
RUN yarn build
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
ENV = 'development'
|
||||
|
||||
# 本地环境接口地址
|
||||
VITE_API_URL = 'http://127.0.0.1:8000'
|
||||
VITE_API_URL = 'http://127.0.0.1:8001'
|
||||
|
||||
# 是否启用按钮权限
|
||||
VITE_PM_ENABLED = true
|
||||
|
||||
168
web/README.en.md
Normal file
168
web/README.en.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Django-Vue3-Admin
|
||||
|
||||
[](https://gitee.com/huge-dream/django-vue3-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/huge-dream/django-vue3-admin)
|
||||
|
||||
[preview](https://demo.dvadmin.com) | [Official website document](https://www.django-vue-admin.com) | [qq group](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [community](https://bbs.django-vue-admin.com) | [plugins market](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
|
||||
|
||||
💡 **「About」**
|
||||
|
||||
We are a group of young people who love Code. In this hot era, we hope to calm down and bring some of our colors and colors through code.
|
||||
|
||||
Because of love, so embrace the future
|
||||
|
||||
## framework introduction
|
||||
|
||||
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) Is a set of all open source rapid development platform, no reservation for individuals and enterprises free use.
|
||||
|
||||
* 🧑🤝🧑Front-end adoption Vue3+TS+pinia+fastcrud。
|
||||
* 👭The backend uses the Python language Django framework as well as the powerful[Django REST Framework](https://pypi.org/project/djangorestframework)。
|
||||
* 👫Permission authentication use[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),Supports the multi-terminal authentication system.
|
||||
* 👬Support loading dynamic permission menu, multi - way easy permission control.
|
||||
* 💏 Special thanks: [vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/).
|
||||
* 💡 💏 Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
|
||||
|
||||
## Online experience
|
||||
|
||||
👩👧👦👩👧👦 demo address:[https://demo.dvadmin.com](https://demo.dvadmin.com)
|
||||
|
||||
* demo account:superadmin
|
||||
|
||||
* demo password:admin123456
|
||||
|
||||
👩👦👦docs:[https://django-vue-admin.com](https://django-vue-admin.com)
|
||||
|
||||
## communication
|
||||
|
||||
* Communication community:[click here](https://bbs.django-vue-admin.com)👩👦👦
|
||||
|
||||
* plugins market:[click here](https://bbs.django-vue-admin.com/plugMarket.html)👩👦👦
|
||||
|
||||
## source code url:
|
||||
|
||||
gitee(Main push):[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
github:no data
|
||||
|
||||
## core function
|
||||
|
||||
1. 👨⚕️ Menu management: Configure the system menu, operation permissions, button permissions, back-end interface permissions, etc.
|
||||
2. 🧑⚕️ Department management: Configure the system organization (company, department, role).
|
||||
3. 👩⚕️ Role management: role menu permission allocation, data permission allocation, set roles according to the department for data range permission division.
|
||||
4. 🧑🎓 Rights Specifies the rights of the authorization role.
|
||||
5. 👨🎓 User management: The user is the system operator, this function mainly completes the system user configuration.
|
||||
6. 👬 Interface whitelist: specifies the interface that does not need permission verification.
|
||||
7. 🧑🔧 Dictionary management: Maintenance of some fixed data frequently used in the system.
|
||||
8. 🧑🔧 Regional management: to manage provinces, cities, counties and regions.
|
||||
9. 📁 Attachment management: Unified management of all files and pictures on the platform.
|
||||
10. 🗓 ️operation logs: log and query the system normal operation; Log and query system exception information.
|
||||
11.🔌 [plugins market] (<https://bbs.django-vue-admin.com/plugMarket.html>) : based on the Django framework - Vue - Admin application and plug-in development.
|
||||
|
||||
## plugins market 🔌
|
||||
|
||||
* Celery Asynchronous task:[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
|
||||
* Upgrade center backend:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
|
||||
* Upgrade center front:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
|
||||
|
||||
## before start project you need:
|
||||
|
||||
~~~
|
||||
Python >= 3.8.0
|
||||
nodejs >= 14.0
|
||||
Mysql >= 5.7.0 (Optional. The default database is sqlite3. 8.0 is recommended)
|
||||
Redis(Optional, the latest edition)
|
||||
~~~
|
||||
|
||||
## frontend♝
|
||||
|
||||
```bash
|
||||
# clone code
|
||||
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||
|
||||
# enter code dir
|
||||
cd web
|
||||
|
||||
# install dependence
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# Start service
|
||||
npm run dev
|
||||
# Visit http://localhost:8080 in your browser
|
||||
# Parameters such as boot port can be configured in the #.env.development file
|
||||
# Build the production environment
|
||||
# npm run build
|
||||
```
|
||||
|
||||
## backend💈
|
||||
|
||||
~~~bash
|
||||
1. enter code dir cd backend
|
||||
2. copy ./conf/env.example.py to ./conf dir,rename as env.py
|
||||
3. in env.py configure database information
|
||||
mysql database recommended version: 8.0
|
||||
mysql database character set: utf8mb4
|
||||
4. install pip dependence
|
||||
pip3 install -r requirements.txt
|
||||
5. Execute the migration command:
|
||||
python3 manage.py makemigrations
|
||||
python3 manage.py migrate
|
||||
6. Initialization data
|
||||
python3 manage.py init
|
||||
7. Initialize provincial, municipal and county data:
|
||||
python3 manage.py init_area
|
||||
8. start backend
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
or daphne :
|
||||
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
||||
~~~
|
||||
|
||||
### visit backend swagger
|
||||
|
||||
* visit url:[http://localhost:8080](http://localhost:8080) (The default address is this one. If you want to change it, follow the configuration file)
|
||||
* account:`superadmin` password:`admin123456`
|
||||
|
||||
### docker-compose
|
||||
|
||||
~~~shell
|
||||
docker-compose up -d
|
||||
# Initialize backend data (first execution only)
|
||||
docker exec -ti dvadmin-django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init_area
|
||||
python manage.py init
|
||||
exit
|
||||
|
||||
frontend url:http://127.0.0.1:8080
|
||||
backend url:http://127.0.0.1:8080/api
|
||||
# Change 127.0.0.1 to your own public ip address on the server
|
||||
account:`superadmin` password:`admin123456`
|
||||
|
||||
# docker-compose stop
|
||||
docker-compose down
|
||||
# docker-compose restart
|
||||
docker-compose restart
|
||||
# docker-compose on start build
|
||||
docker-compose up -d --build
|
||||
~~~
|
||||
|
||||
## Demo screenshot✅
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

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

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,23 +10,22 @@
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="django-vue3-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!"
|
||||
content="django-vue-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>django-vue3-admin</title>
|
||||
<title>django-vue-admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript">
|
||||
// let _hmt = _hmt || [];
|
||||
(function () {
|
||||
let hm = document.createElement('script');
|
||||
hm.src = 'https://hm.baidu.com/hm.js?d9c8b87d10717013641458b300c552e4';
|
||||
let s = document.getElementsByTagName('script')[0];
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?9ba8fc809b5584167a2fb9b31bb3970c";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "django-vue3-admin",
|
||||
"version": "1.0.0",
|
||||
"description": "django-vue3-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!",
|
||||
"version": "3.0.0",
|
||||
"description": "是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。\n django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite --force",
|
||||
@@ -10,10 +10,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@fast-crud/fast-crud": "^1.14.7",
|
||||
"@fast-crud/fast-extends": "^1.14.7",
|
||||
"@fast-crud/ui-element": "^1.14.7",
|
||||
"@fast-crud/ui-interface": "^1.14.7",
|
||||
"@fast-crud/fast-crud": "^1.19.2",
|
||||
"@fast-crud/fast-extends": "^1.19.2",
|
||||
"@fast-crud/ui-element": "^1.19.2",
|
||||
"@fast-crud/ui-interface": "^1.19.2",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
@@ -44,6 +44,7 @@
|
||||
"splitpanes": "^3.1.5",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"ts-md5": "^1.3.1",
|
||||
"upgrade": "^1.1.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-cropper": "^1.0.8",
|
||||
@@ -61,7 +62,7 @@
|
||||
"@typescript-eslint/parser": "^5.46.0",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vue/compiler-sfc": "^3.2.45",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"prettier": "^2.8.1",
|
||||
"sass": "^1.56.2",
|
||||
|
||||
@@ -59,12 +59,6 @@ onBeforeMount(() => {
|
||||
setIntroduction.cssCdn();
|
||||
// 设置批量第三方 js
|
||||
setIntroduction.jsCdn();
|
||||
//websockt 模块
|
||||
try {
|
||||
//websocket.init(wsReceive)
|
||||
} catch (e) {
|
||||
console.log('websocket错误');
|
||||
}
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
@@ -93,6 +87,14 @@ watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
other.useTitle();
|
||||
if (!websocket.websocket) {
|
||||
//websockt 模块
|
||||
try {
|
||||
websocket.init(wsReceive)
|
||||
} catch (e) {
|
||||
console.log('websocket错误');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
|
||||
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 |
@@ -120,4 +120,12 @@ export default {
|
||||
copyTextSuccess: 'Copy succeeded!',
|
||||
copyTextError: 'Copy failed!',
|
||||
},
|
||||
upgrade: {
|
||||
title: 'New version upgrade',
|
||||
msg: 'It\'s a new version. Update it now!Don\'t worry, update quickly oh!',
|
||||
desc: 'Tip: The update restores the default configuration',
|
||||
btnOne: 'Cruel refusal',
|
||||
btnTwo: 'Update now',
|
||||
btnTwoLoading: 'updating',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,6 +40,8 @@ export default {
|
||||
title4: '消息',
|
||||
title5: '开全屏',
|
||||
title6: '关全屏',
|
||||
retry: '重试上线',
|
||||
onlinePrompt: '当前离线状态,是否重试上线?',
|
||||
dropdownLarge: '大型',
|
||||
dropdownDefault: '默认',
|
||||
dropdownSmall: '小型',
|
||||
@@ -75,7 +77,7 @@ export default {
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: '您未被授权,没有操作权限~',
|
||||
accessMsg: '联系方式:加QQ群探讨 665452019',
|
||||
accessMsg: '请联系管理员',
|
||||
accessBtn: '重新授权',
|
||||
},
|
||||
layout: {
|
||||
@@ -89,10 +91,12 @@ export default {
|
||||
twoIsTopBarColorGradual: '顶栏背景渐变',
|
||||
twoMenuBar: '菜单背景',
|
||||
twoMenuBarColor: '菜单默认字体颜色',
|
||||
twoMenuBarActiveColor: '菜单高亮背景色',
|
||||
twoIsMenuBarColorGradual: '菜单背景渐变',
|
||||
twoColumnsMenuBar: '分栏菜单背景',
|
||||
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
|
||||
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
|
||||
twoIsColumnsMenuHoverPreload: '分栏菜单滑鼠悬停预加载',
|
||||
threeTitle: '界面设置',
|
||||
threeIsCollapse: '菜单水平折叠',
|
||||
threeIsUniqueOpened: '菜单手风琴',
|
||||
@@ -131,4 +135,12 @@ export default {
|
||||
copyTextSuccess: '复制成功!',
|
||||
copyTextError: '复制失败!',
|
||||
},
|
||||
upgrade: {
|
||||
title: '新版本升级',
|
||||
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
|
||||
desc: '提示:更新会还原默认配寘',
|
||||
btnOne: '残忍拒绝',
|
||||
btnTwo: '马上更新',
|
||||
btnTwoLoading: '更新中',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -123,7 +123,7 @@ export default {
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: '您未被授權,沒有操作許可權~',
|
||||
accessMsg: '聯繫方式:加QQ群探討665452019',
|
||||
accessMsg: '請聯系管理員',
|
||||
accessBtn: '重新授權',
|
||||
},
|
||||
layout: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 定义内容
|
||||
export default {
|
||||
label: {
|
||||
one1: '用户名登录',
|
||||
one1: '账号密码登录',
|
||||
two2: '手机号登录',
|
||||
},
|
||||
link: {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll layout-backtop-header-fixed"
|
||||
wrap-class="layout-main-scroll" view-class="layout-main-scroll">
|
||||
<LayoutParentView />
|
||||
<!-- <LayoutFooter v-if="isFooter" /> -->
|
||||
<LayoutFooter v-if="isFooter" />
|
||||
</el-scrollbar>
|
||||
<el-backtop :target="setBacktopClass" />
|
||||
</el-main>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div class="layout-footer pb15">
|
||||
<div class="layout-footer pb5 pt2">
|
||||
<div class="layout-footer-warp">
|
||||
<div>❤️ Powered by Django-Vue3-Admin ❤️</div>
|
||||
<div class="mt5">Copyright DVAdmin团队</div>
|
||||
<div>❤️ Powered by Django-Vue3-Admin Copyright © DVAdmin团队 ❤️</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -57,9 +57,33 @@
|
||||
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
|
||||
></i>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="!isSocketOpen">
|
||||
<el-popconfirm
|
||||
width="250"
|
||||
ref="onlinePopoverRef"
|
||||
:confirm-button-text="$t('message.user.retry')"
|
||||
:icon="InfoFilled"
|
||||
trigger="hover"
|
||||
icon-color="#626AEF"
|
||||
:title="$t('message.user.onlinePrompt')"
|
||||
@confirm="onlineConfirmEvent"
|
||||
>
|
||||
<template #reference>
|
||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
</el-badge>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</span>
|
||||
</div>
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||
<span class="layout-navbars-breadcrumb-user-link">
|
||||
<img :src="userInfos.avatar" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
<span v-if="isSocketOpen">
|
||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
</el-badge>
|
||||
</span>
|
||||
{{ userInfos.username === '' ? 'common' : userInfos.username }}
|
||||
<el-icon class="el-icon--right">
|
||||
<ele-ArrowDown />
|
||||
@@ -79,7 +103,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutBreadcrumbUser">
|
||||
import { defineAsyncComponent, ref, computed, reactive, onMounted } from 'vue';
|
||||
import { defineAsyncComponent, ref, computed, reactive, onMounted, unref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import screenfull from 'screenfull';
|
||||
@@ -90,7 +114,9 @@ import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import other from '/@/utils/other';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import { Session, Local } from '/@/utils/storage';
|
||||
|
||||
import headerImage from '/@/assets/img/headerImage.png';
|
||||
import websocket from '/@/utils/websocket';
|
||||
import { InfoFilled } from '@element-plus/icons-vue'
|
||||
// 引入组件
|
||||
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
|
||||
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
|
||||
@@ -118,6 +144,21 @@ const layoutUserFlexNum = computed(() => {
|
||||
else num = '';
|
||||
return num;
|
||||
});
|
||||
|
||||
// 定义变量内容
|
||||
const { isSocketOpen } = storeToRefs(useUserInfo());
|
||||
|
||||
// websocket状态
|
||||
const onlinePopoverRef = ref()
|
||||
const onlineConfirmEvent = () => {
|
||||
if (!isSocketOpen.value) {
|
||||
websocket.is_reonnect = true
|
||||
websocket.reconnect_current = 1
|
||||
websocket.reconnect()
|
||||
}
|
||||
// 手动隐藏弹出
|
||||
unref(onlinePopoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
// 全屏点击时
|
||||
const onScreenfullClick = () => {
|
||||
if (!screenfull.isEnabled) {
|
||||
@@ -256,5 +297,29 @@ const messageCenter = messageCenterStore();
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 12px;
|
||||
}
|
||||
.online-status{
|
||||
cursor: pointer;
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 30px;
|
||||
font-size: 14px;
|
||||
left: 5px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
padding: 0;
|
||||
background-color: #18bc9c;
|
||||
}
|
||||
}
|
||||
.online-down{
|
||||
cursor: pointer;
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 30px;
|
||||
font-size: 14px;
|
||||
left: 5px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
padding: 0;
|
||||
background-color: #979b9c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
<div class="upgrade-content">
|
||||
{{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
|
||||
<div class="mt5">
|
||||
<el-link type="primary" class="font12" href="https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md" target="_black">
|
||||
<el-link type="primary" class="font12" href="https://gitee.com/huge-dream/django-vue3-admin/blob/master/CHANGELOG.md" target="_black">
|
||||
CHANGELOG.md
|
||||
</el-link>
|
||||
</div>
|
||||
<div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
|
||||
</div>
|
||||
<div class="upgrade-btn">
|
||||
<el-button round size="default" type="info" text @click="onCancel">{{ $t('message.upgrade.btnOne') }}</el-button>
|
||||
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading">{{ state.btnTxt }}</el-button>
|
||||
<el-button round size="default" type="info" text @click="onCancel" >{{ $t('message.upgrade.btnOne') }}</el-button>
|
||||
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading" >{{ state.btnTxt }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -76,10 +76,10 @@ const delayShow = () => {
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
// delayShow();
|
||||
// setTimeout(() => {
|
||||
// state.btnTxt = t('message.upgrade.btnTwo');
|
||||
// }, 200);
|
||||
delayShow();
|
||||
setTimeout(() => {
|
||||
state.btnTxt = t('message.upgrade.btnTwo');
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import piniaPersist from 'pinia-plugin-persist';
|
||||
// @ts-ignore
|
||||
import fastCrud from './settings.ts';
|
||||
import pinia from './stores';
|
||||
import permission from '/@/plugin/permission/index';
|
||||
import {RegisterPermission} from '/@/plugin/permission/index';
|
||||
// @ts-ignore
|
||||
import eIconPicker, { iconList, analyzingIconForIconfont } from 'e-icon-picker';
|
||||
import 'e-icon-picker/icon/default-icon/symbol.js'; //基本彩色图标库
|
||||
@@ -54,7 +54,6 @@ other.elSvg(app);
|
||||
|
||||
|
||||
app.use(VXETable)
|
||||
app.use(permission);
|
||||
app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).use(fastCrud).mount('#app');
|
||||
|
||||
app.config.globalProperties.mittBus = mitt();
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import permissionDirective from './directive.permission'
|
||||
import permissionFunc from './func.permission'
|
||||
const install = function (app:any) {
|
||||
export const RegisterPermission = function (app:any) {
|
||||
app.directive('permission', permissionDirective)
|
||||
app.provide('$hasPermissions',permissionFunc.hasPermissions)
|
||||
}
|
||||
|
||||
export default {
|
||||
install
|
||||
}
|
||||
|
||||
26
web/src/stores/btnPermission.ts
Normal file
26
web/src/stores/btnPermission.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {defineStore} from "pinia";
|
||||
import {DictionaryStates} from "/@/stores/interface";
|
||||
import {request} from "/@/utils/service";
|
||||
|
||||
export const BtnPermissionStore = defineStore('BtnPermission', {
|
||||
state: (): DictionaryStates => ({
|
||||
data: []
|
||||
}),
|
||||
actions: {
|
||||
async getBtnPermissionStore() {
|
||||
request({
|
||||
url: '/api/system/menu_button/menu_button_all_permission/',
|
||||
method: 'get',
|
||||
}).then((ret: {
|
||||
data: []
|
||||
}) => {
|
||||
// 转换数据格式并保存到pinia
|
||||
let dataList = ret.data
|
||||
this.data=dataList
|
||||
})
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
@@ -19,6 +19,7 @@ export interface UserInfosState {
|
||||
}
|
||||
export interface UserInfosStates {
|
||||
userInfos: UserInfosState;
|
||||
isSocketOpen: boolean
|
||||
}
|
||||
|
||||
// 路由缓存列表
|
||||
|
||||
@@ -26,6 +26,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
},
|
||||
],
|
||||
},
|
||||
isSocketOpen: false
|
||||
}),
|
||||
actions: {
|
||||
async updateUserInfos() {
|
||||
@@ -57,6 +58,9 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
Session.set('userInfo', this.userInfos);
|
||||
}
|
||||
},
|
||||
async setWebSocketState(socketState: boolean) {
|
||||
this.isSocketOpen = socketState;
|
||||
},
|
||||
async getApiUserInfo() {
|
||||
return request({
|
||||
url: '/api/system/user/user_info/',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { App } from 'vue';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { judementSameArr } from '/@/utils/arrayOperation';
|
||||
|
||||
import {BtnPermissionStore} from "/@/stores/btnPermission";
|
||||
/**
|
||||
* 用户权限指令
|
||||
* @directive 单个权限验证(v-auth="xxx")
|
||||
@@ -12,16 +11,16 @@ export function authDirective(app: App) {
|
||||
// 单个权限验证(v-auth="xxx")
|
||||
app.directive('auth', {
|
||||
mounted(el, binding) {
|
||||
const stores = useUserInfo();
|
||||
if (!stores.userInfos.authBtnList.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
|
||||
const stores = BtnPermissionStore();
|
||||
if (!stores.data.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
|
||||
},
|
||||
});
|
||||
// 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
|
||||
app.directive('auths', {
|
||||
mounted(el, binding) {
|
||||
let flag = false;
|
||||
const stores = useUserInfo();
|
||||
stores.userInfos.authBtnList.map((val: string) => {
|
||||
const stores = BtnPermissionStore();
|
||||
stores.data.map((val: string) => {
|
||||
binding.value.map((v: string) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
@@ -32,8 +31,8 @@ export function authDirective(app: App) {
|
||||
// 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]")
|
||||
app.directive('auth-all', {
|
||||
mounted(el, binding) {
|
||||
const stores = useUserInfo();
|
||||
const flag = judementSameArr(binding.value, stores.userInfos.authBtnList);
|
||||
const stores = BtnPermissionStore();
|
||||
const flag = judementSameArr(binding.value, stores.data);
|
||||
if (!flag) el.parentNode.removeChild(el);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { judementSameArr } from '/@/utils/arrayOperation';
|
||||
|
||||
import {BtnPermissionStore} from "/@/stores/btnPermission";
|
||||
/**
|
||||
* 单个权限验证
|
||||
* @param value 权限值
|
||||
* @returns 有权限,返回 `true`,反之则反
|
||||
*/
|
||||
export function auth(value: string): boolean {
|
||||
const stores = useUserInfo();
|
||||
return stores.userInfos.authBtnList.some((v: string) => v === value);
|
||||
const stores = BtnPermissionStore();
|
||||
return stores.data.some((v: string) => v === value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -18,8 +17,8 @@ export function auth(value: string): boolean {
|
||||
*/
|
||||
export function auths(value: Array<string>): boolean {
|
||||
let flag = false;
|
||||
const stores = useUserInfo();
|
||||
stores.userInfos.authBtnList.map((val: string) => {
|
||||
const stores = BtnPermissionStore();
|
||||
stores.data.map((val: string) => {
|
||||
value.map((v: string) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
@@ -33,6 +32,6 @@ export function auths(value: Array<string>): boolean {
|
||||
* @returns 有权限,返回 `true`,反之则反
|
||||
*/
|
||||
export function authAll(value: Array<string>): boolean {
|
||||
const stores = useUserInfo();
|
||||
return judementSameArr(value, stores.userInfos.authBtnList);
|
||||
const stores = BtnPermissionStore();
|
||||
return judementSameArr(value, stores.data);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
const cssCdnUrlList: Array<string> = [
|
||||
'//at.alicdn.com/t/font_2298093_y6u00apwst.css',
|
||||
'//at.alicdn.com/t/c/font_3882322_9ah7y8m9175.css', //dvadmin3项目用icon
|
||||
'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
|
||||
];
|
||||
// 第三方 js url
|
||||
const jsCdnUrlList: Array<string> = [];
|
||||
|
||||
@@ -3,7 +3,7 @@ import {Session} from "/@/utils/storage";
|
||||
import {getWsBaseURL} from "/@/utils/baseUrl";
|
||||
// @ts-ignore
|
||||
import socket from '@/types/api/socket'
|
||||
|
||||
import {useUserInfo} from "/@/stores/userInfo";
|
||||
const websocket: socket = {
|
||||
websocket: null,
|
||||
connectURL: getWsBaseURL(),
|
||||
@@ -42,6 +42,7 @@ const websocket: socket = {
|
||||
}
|
||||
websocket.websocket.onclose = (e: any) => {
|
||||
websocket.socket_open = false
|
||||
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||
// 需要重新连接
|
||||
if (websocket.is_reonnect) {
|
||||
websocket.reconnect_timer = setTimeout(() => {
|
||||
@@ -49,6 +50,8 @@ const websocket: socket = {
|
||||
if (websocket.reconnect_current > websocket.reconnect_count) {
|
||||
clearTimeout(websocket.reconnect_timer)
|
||||
websocket.is_reonnect = false
|
||||
websocket.socket_open = false
|
||||
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||
return
|
||||
}
|
||||
// 记录重连次数
|
||||
@@ -60,6 +63,7 @@ const websocket: socket = {
|
||||
// 连接成功
|
||||
websocket.websocket.onopen = function () {
|
||||
websocket.socket_open = true
|
||||
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||
websocket.is_reonnect = true
|
||||
// 开启心跳
|
||||
websocket.heartbeat()
|
||||
@@ -85,17 +89,21 @@ const websocket: socket = {
|
||||
callback && callback()
|
||||
} else {
|
||||
clearInterval(websocket.hearbeat_timer)
|
||||
message({
|
||||
type: 'warning',
|
||||
message: 'socket链接已断开',
|
||||
duration: 1000,
|
||||
})
|
||||
// message({
|
||||
// type: 'warning',
|
||||
// message: 'socket链接已断开',
|
||||
// duration: 1000,
|
||||
// })
|
||||
websocket.socket_open = false
|
||||
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||
}
|
||||
},
|
||||
close: () => {
|
||||
websocket.is_reonnect = false
|
||||
websocket.websocket.close()
|
||||
websocket.websocket = null;
|
||||
websocket.socket_open = false
|
||||
useUserInfo().setWebSocketState(websocket.socket_open);
|
||||
},
|
||||
/**
|
||||
* 重新连接
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {
|
||||
dict,
|
||||
UserPageQuery,
|
||||
AddReq,
|
||||
DelReq,
|
||||
EditReq,
|
||||
compute,
|
||||
CreateCrudOptionsProps,
|
||||
CreateCrudOptionsRet
|
||||
} from '@fast-crud/fast-crud';
|
||||
import {dictionary} from '/@/utils/dictionary';
|
||||
import {successMessage} from '/@/utils/message';
|
||||
import {auth} from "/@/utils/authFunction";
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
const editRequest = async ({form, row}: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
const delRequest = async ({row}: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
const addRequest = async ({form}: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
@@ -24,7 +34,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
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);
|
||||
});
|
||||
};
|
||||
@@ -37,6 +47,13 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('area:Create'),
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
@@ -48,10 +65,12 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: auth('area:Update')
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: auth('area:Delete')
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -62,12 +81,12 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
rowKey: 'id',
|
||||
lazy: true,
|
||||
load: loadContentMethod,
|
||||
treeProps: { children: 'children', hasChildren: 'hasChild' },
|
||||
treeProps: {children: 'children', hasChildren: 'hasChild'},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
form: {show: false},
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
@@ -106,13 +125,13 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
},
|
||||
treeNode: true,
|
||||
type: 'input',
|
||||
column:{
|
||||
column: {
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '名称必填项' },
|
||||
{required: true, message: '名称必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入名称',
|
||||
@@ -125,13 +144,13 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
column: {
|
||||
minWidth: 90,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '地区编码必填项' },
|
||||
{required: true, message: '地区编码必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入地区编码',
|
||||
@@ -144,13 +163,13 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
column: {
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '拼音必填项' },
|
||||
{required: true, message: '拼音必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
@@ -163,14 +182,14 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
column: {
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: false,
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '拼音必填项' },
|
||||
{required: true, message: '拼音必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
@@ -179,13 +198,13 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
},
|
||||
initials: {
|
||||
title: '首字母',
|
||||
column:{
|
||||
column: {
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '首字母必填项' },
|
||||
{required: true, message: '首字母必填项'},
|
||||
],
|
||||
|
||||
component: {
|
||||
@@ -200,7 +219,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
minWidth:90,
|
||||
minWidth: 90,
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
activeText: '',
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<fs-page class="columns">
|
||||
<el-row class="columns-el-row" :gutter="10">
|
||||
<el-col :span="4">
|
||||
<el-col :span="6">
|
||||
<div class="columns-box columns-left">
|
||||
<ItemCom title="角色" type="role" showPagination @fetchData="fetchRoleData" @itemClick="handleClick" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="columns-box columns-left">
|
||||
<ItemCom title="菜单" type="menu" showPagination @fetchData="fetchMenuData" @itemClick="handleClick" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<!-- <el-col :span="4">-->
|
||||
<!-- <div class="columns-box columns-left">-->
|
||||
<!-- <ItemCom title="菜单" type="menu" showPagination @fetchData="fetchMenuData" @itemClick="handleClick" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </el-col>-->
|
||||
<el-col :span="8">
|
||||
<div class="columns-box columns-center">
|
||||
<ItemCom title="模型表" type="model" label="showText" value="key" @fetchData="fetchModelData" @itemClick="handleClick" />
|
||||
</div>
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
list-type="picture-card"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
|
||||
<div slot="tip" class="el-upload__tip">请选取图片,并且只能上传jpg/png文件</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogImgVisible">
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
@@ -150,7 +150,7 @@
|
||||
list-type="picture-card"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
|
||||
<div slot="tip" class="el-upload__tip">请选取图片,并且只能上传jpg/png文件</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogImgVisible">
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
@@ -506,4 +506,8 @@ watch(
|
||||
);
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style scoped>
|
||||
:deep(.el-upload-list--picture-card){
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<el-input v-model="deptFormData.key" />
|
||||
</el-form-item>
|
||||
<el-form-item label="负责人">
|
||||
<el-input v-model="deptFormData.owner" />
|
||||
<el-input v-model="deptFormData.owner" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="deptFormData.description" maxlength="200" show-word-limit type="textarea" />
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { request, downloadFile } from '/@/utils/service';
|
||||
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 function GetDept(query: PageQuery) {
|
||||
return request({
|
||||
url: "/api/system/dept/dept_lazy_tree/",
|
||||
url: '/api/system/dept/dept_lazy_tree/',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
@@ -55,8 +55,8 @@ export function exportData(params: any) {
|
||||
return downloadFile({
|
||||
url: apiPrefix + 'export_data/',
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function getDeptInfoById(id: string, type: string) {
|
||||
@@ -65,3 +65,11 @@ export function getDeptInfoById(id: string, type: string) {
|
||||
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 { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {auth} from "/@/utils/authFunction";
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
@@ -39,9 +40,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
return await api.exportData(query);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions: any = inject('$hasPermissions');
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
table: {
|
||||
@@ -58,12 +56,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: hasPermissions('user:Create'),
|
||||
// show:true
|
||||
show: auth('user:Create')
|
||||
},
|
||||
export: {
|
||||
text: '导出', //按钮文字
|
||||
title: '导出', //鼠标停留显示的信息
|
||||
show: auth('user:Export'),
|
||||
click() {
|
||||
return exportRequest(crudExpose!.getSearchFormData());
|
||||
},
|
||||
@@ -72,6 +70,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
},
|
||||
search: {
|
||||
container: {
|
||||
layout: 'multi-line',
|
||||
action: {
|
||||
col: {
|
||||
span: 10,
|
||||
@@ -88,21 +87,22 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
show: hasPermissions('user:Update'),
|
||||
show: auth('user:Update'),
|
||||
},
|
||||
remove: {
|
||||
show: hasPermissions('user:Delete'),
|
||||
show: auth('user:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '重设密码',
|
||||
type: 'primary',
|
||||
show: hasPermissions('user:ResetPassword'),
|
||||
show: auth('user:ResetPassword'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '重设密码',
|
||||
},
|
||||
click: (ctx: any) => {
|
||||
const { row } = ctx;
|
||||
context?.handleResetPwdOpen(row);
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -264,7 +264,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
url: '/api/system/role/',
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
isTree: true,
|
||||
getData: async ({ url }: { url: string }) => {
|
||||
return request({
|
||||
url: url,
|
||||
|
||||
@@ -40,19 +40,34 @@
|
||||
<el-button :icon="!showCount ? 'Hide' : 'View'" circle @click="showCount = !showCount"></el-button>
|
||||
</template>
|
||||
<template #actionbar-right>
|
||||
<importExcel api="api/system/user/">导入 </importExcel>
|
||||
<importExcel api="api/system/user/" v-auth="'user:Import'">导入 </importExcel>
|
||||
</template>
|
||||
</fs-crud>
|
||||
|
||||
<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>
|
||||
|
||||
<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 { Md5 } from 'ts-md5';
|
||||
import { createCrudOptions } from './crud';
|
||||
import importExcel from '/@/components/importExcel/index.vue';
|
||||
import * as echarts 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';
|
||||
|
||||
let deptCountChart: ECharts;
|
||||
@@ -72,6 +87,13 @@ let isShowChildFlag = ref(false);
|
||||
let deptInfo = ref<Partial<HeadDeptInfoType>>({});
|
||||
let showCount = ref(false);
|
||||
|
||||
let resetPwdVisible = ref(false);
|
||||
let resetPwdFormState = reactive({
|
||||
id: 0,
|
||||
newPassword: '',
|
||||
newPassword2: '',
|
||||
});
|
||||
|
||||
/**
|
||||
* 初始化顶部部门折线图
|
||||
*/
|
||||
@@ -189,11 +211,50 @@ const handleSwitchChange = () => {
|
||||
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(() => {
|
||||
crudExpose.doRefresh();
|
||||
deptCountChart = init(deptCountBar.value as HTMLElement);
|
||||
deptSexChart = init(deptSexPie.value as HTMLElement);
|
||||
getDeptInfo();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
@@ -201,7 +262,7 @@ defineExpose({
|
||||
});
|
||||
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose, context: { getDeptInfo, isShowChildFlag } });
|
||||
const { crudOptions } = createCrudOptions({ crudExpose, context: { getDeptInfo, isShowChildFlag, handleResetPwdOpen } });
|
||||
|
||||
// 初始化crud配置
|
||||
const { resetCrudOptions } = useCrud({
|
||||
|
||||
@@ -3,7 +3,7 @@ import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOption
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { inject, nextTick, ref } from 'vue';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
|
||||
import {auth} from '/@/utils/authFunction';
|
||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
@@ -19,8 +19,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject('$hasPermissions');
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
@@ -40,17 +38,17 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: hasPermissions('dictionary:Update'),
|
||||
show: auth('dictionary:Update'),
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: hasPermissions('dictionary:Delete'),
|
||||
show: auth('dictionary:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '字典配置',
|
||||
type: 'text',
|
||||
show: hasPermissions('dictionary:Update'),
|
||||
show: auth('dictionary:Update'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '字典配置',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<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-page>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
show:false,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<div class="login-left-logo">
|
||||
<img :src="logoMini" />
|
||||
<div class="login-left-logo-text">
|
||||
<span>{{ getThemeConfig.globalViceTitle }}</span>
|
||||
<span class="login-left-logo-text-msg">{{ getThemeConfig.globalViceTitleMsg }}</span>
|
||||
<span>{{ getSystemConfig['login.site_title']||getThemeConfig.globalViceTitle }}</span>
|
||||
<span class="login-left-logo-text-msg">{{ getSystemConfig['login.site_name']||getThemeConfig.globalViceTitleMsg }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-left-img">
|
||||
@@ -42,15 +42,15 @@
|
||||
</div>
|
||||
|
||||
<div class="login-authorization">
|
||||
<p>Copyright © 2021-2022 django-vue-admin.com 版权所有</p>
|
||||
<p>Copyright © {{getSystemConfig['login.copyright'] || '2021-2024 django-vue-admin.com'}} 版权所有</p>
|
||||
<p class="la-other">
|
||||
<a href="https://beian.miit.gov.cn" target="_blank">晋ICP备18005113号-3</a>
|
||||
<a href="https://beian.miit.gov.cn" target="_blank">{{getSystemConfig['login.keep_record'] || '晋ICP备18005113号-3'}}</a>
|
||||
|
|
||||
<a href="https://django-vue-admin.com" target="_blank">帮助</a>
|
||||
<a :href="getSystemConfig['login.help_url']?getSystemConfig['login.help_url']:'https://django-vue-admin.com'" target="_blank">帮助</a>
|
||||
|
|
||||
<a href="#">隐私</a>
|
||||
<a :href="getSystemConfig['login.privacy_url']?getSystemConfig['login.privacy_url']:'#'">隐私</a>
|
||||
|
|
||||
<a href="#">条款</a>
|
||||
<a :href="getSystemConfig['login.clause_url']?getSystemConfig['login.clause_url']:'#'">条款</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,7 +64,7 @@ import { NextLoading } from '/@/utils/loading';
|
||||
import logoMini from '/@/assets/logo-mini.svg';
|
||||
import loginMain from '/@/assets/login-main.svg';
|
||||
import loginBg from '/@/assets/login-bg.svg';
|
||||
|
||||
import {SystemConfigStore} from '/@/stores/systemConfig'
|
||||
// 引入组件
|
||||
const Account = defineAsyncComponent(() => import('/@/views/system/login/component/account.vue'));
|
||||
const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
|
||||
@@ -82,6 +82,14 @@ const state = reactive({
|
||||
const getThemeConfig = computed(() => {
|
||||
return themeConfig.value;
|
||||
});
|
||||
|
||||
const systemConfigStore = SystemConfigStore()
|
||||
const {systemConfig} = storeToRefs(systemConfigStore)
|
||||
const getSystemConfig = computed(()=>{
|
||||
return systemConfig.value
|
||||
})
|
||||
|
||||
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
NextLoading.done();
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
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 { request } from '/@/utils/service';
|
||||
import {auth} from '/@/utils/authFunction'
|
||||
import {request} from '/@/utils/service';
|
||||
//此处为crudOptions配置
|
||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async () => {
|
||||
if (context!.selectOptions.value.id) {
|
||||
return await api.GetList({ menu: context!.selectOptions.value.id } as any);
|
||||
return await api.GetList({menu: context!.selectOptions.value.id} as any);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
return await api.UpdateObj({ ...form, menu: row.menu });
|
||||
const editRequest = async ({form, row}: EditReq) => {
|
||||
return await api.UpdateObj({...form, menu: row.menu});
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
const delRequest = async ({row}: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj({ ...form, ...{ menu: context!.selectOptions.value.id } });
|
||||
const addRequest = async ({form}: AddReq) => {
|
||||
return await api.AddObj({...form, ...{menu: context!.selectOptions.value.id}});
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
pagination:{
|
||||
show:false
|
||||
},
|
||||
search: {
|
||||
container: {
|
||||
action: {
|
||||
@@ -32,6 +35,13 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
},
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('btn:Create')
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
@@ -43,10 +53,10 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
edit: {
|
||||
icon: '',
|
||||
type: 'primary',
|
||||
show: auth('btn:Update')
|
||||
},
|
||||
remove: {
|
||||
icon: '',
|
||||
type: 'primary',
|
||||
show: auth('btn:Delete')
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -57,7 +67,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
col: {span: 24},
|
||||
labelWidth: '100px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
@@ -67,7 +77,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
form: {show: false},
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
@@ -77,9 +87,9 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: { show: false },
|
||||
column: {show: false},
|
||||
type: 'text',
|
||||
search: { show: true },
|
||||
search: {show: true},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
@@ -90,20 +100,20 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
id: {
|
||||
title: 'ID',
|
||||
type: 'text',
|
||||
column: { show: false },
|
||||
search: { show: false },
|
||||
form: { show: false },
|
||||
column: {show: false},
|
||||
search: {show: false},
|
||||
form: {show: false},
|
||||
},
|
||||
name: {
|
||||
title: '权限名称',
|
||||
type: 'text',
|
||||
search: { show: true },
|
||||
search: {show: true},
|
||||
column: {
|
||||
minWidth: 120,
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '权限名称必填' }],
|
||||
rules: [{required: true, message: '权限名称必填'}],
|
||||
component: {
|
||||
placeholder: '输入权限名称搜索',
|
||||
props: {
|
||||
@@ -114,7 +124,8 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
},
|
||||
helper: {
|
||||
render() {
|
||||
return <el-alert title="手动输入" type="warning" description="页面中按钮的名称或者自定义一个名称" />;
|
||||
return <el-alert title="手动输入" type="warning"
|
||||
description="页面中按钮的名称或者自定义一个名称"/>;
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -122,24 +133,25 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
value: {
|
||||
title: '权限值',
|
||||
type: 'text',
|
||||
search: { show: false },
|
||||
search: {show: false},
|
||||
column: {
|
||||
width: 120,
|
||||
width: 200,
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '权限标识必填' }],
|
||||
rules: [{required: true, message: '权限标识必填'}],
|
||||
placeholder: '输入权限标识',
|
||||
helper: {
|
||||
render() {
|
||||
return <el-alert title="唯一值" type="warning" description="用于判断前端按钮权限或接口权限" />;
|
||||
return <el-alert title="唯一值" type="warning"
|
||||
description="用于判断前端按钮权限或接口权限"/>;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
method: {
|
||||
title: '请求方式',
|
||||
search: { show: false },
|
||||
search: {show: false},
|
||||
type: 'dict-select',
|
||||
column: {
|
||||
width: 120,
|
||||
@@ -147,23 +159,23 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: 'GET', value: 0 },
|
||||
{ label: 'POST', value: 1, color: 'success' },
|
||||
{ label: 'PUT', value: 2, color: 'warning' },
|
||||
{ label: 'DELETE', value: 3, color: 'danger' },
|
||||
{label: 'GET', value: 0},
|
||||
{label: 'POST', value: 1, color: 'success'},
|
||||
{label: 'PUT', value: 2, color: 'warning'},
|
||||
{label: 'DELETE', value: 3, color: 'danger'},
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
rules: [{ required: true, message: '必填项' }],
|
||||
rules: [{required: true, message: '必填项'}],
|
||||
},
|
||||
},
|
||||
api: {
|
||||
title: '接口地址',
|
||||
search: { show: false },
|
||||
search: {show: false},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
getData() {
|
||||
return request({ url: '/swagger.json' }).then((res: any) => {
|
||||
return request({url: '/swagger.json'}).then((res: any) => {
|
||||
const ret = Object.keys(res.paths);
|
||||
const data = [];
|
||||
for (const item of ret) {
|
||||
@@ -181,7 +193,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '必填项' }],
|
||||
rules: [{required: true, message: '必填项'}],
|
||||
component: {
|
||||
props: {
|
||||
allowCreate: true,
|
||||
@@ -191,7 +203,8 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
},
|
||||
helper: {
|
||||
render() {
|
||||
return <el-alert title="请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/" type="warning" />;
|
||||
return <el-alert title="请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/"
|
||||
type="warning"/>;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
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">
|
||||
<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 />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<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 />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<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 />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<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 />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<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 />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
@@ -14,9 +14,19 @@
|
||||
</el-col>
|
||||
|
||||
<el-col :span="18">
|
||||
<div class="menu-box menu-right-box">
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="按钮权限配置" >
|
||||
<div style="height: 80vh">
|
||||
<MenuButtonCom ref="menuButtonRef" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="列权限配置">
|
||||
<div style="height: 80vh">
|
||||
<MenuFieldCom ref="menuFieldRef"></MenuFieldCom>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
@@ -39,6 +49,7 @@ import { ElMessageBox } from 'element-plus';
|
||||
import MenuTreeCom from './components/MenuTreeCom/index.vue';
|
||||
import MenuButtonCom from './components/MenuButtonCom/index.vue';
|
||||
import MenuFormCom from './components/MenuFormCom/index.vue';
|
||||
import MenuFieldCom from './components/MenuFieldCom/index.vue';
|
||||
import { GetList, DelObj } from './api';
|
||||
import { successNotification } from '/@/utils/message';
|
||||
import { APIResponseData, MenuTreeItemType } from './types';
|
||||
@@ -49,7 +60,7 @@ let drawerVisible = ref(false);
|
||||
let drawerFormData = ref<Partial<MenuTreeItemType>>({});
|
||||
let menuTreeRef = ref<InstanceType<typeof MenuTreeCom> | null>(null);
|
||||
let menuButtonRef = ref<InstanceType<typeof MenuButtonCom> | null>(null);
|
||||
|
||||
let menuFieldRef = ref<InstanceType<typeof MenuFieldCom> | null>(null);
|
||||
const getData = () => {
|
||||
GetList({}).then((ret: APIResponseData) => {
|
||||
const responseData = ret.data;
|
||||
@@ -67,6 +78,7 @@ const getData = () => {
|
||||
*/
|
||||
const handleTreeClick = (record: MenuTreeItemType) => {
|
||||
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 {shallowRef, computed, ref, inject} from 'vue';
|
||||
import manyToMany from '/@/components/manyToMany/index.vue';
|
||||
|
||||
import {auth} from '/@/utils/authFunction'
|
||||
const { compute } = useCompute();
|
||||
|
||||
interface CreateCrudOptionsTypes {
|
||||
@@ -36,8 +36,6 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
||||
return tabActivted.value === 'receive';
|
||||
});
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject("$hasPermissions")
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
@@ -47,6 +45,15 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar:{
|
||||
buttons:{
|
||||
add:{
|
||||
show:computed(() =>{
|
||||
return tabActivted.value !== 'receive' && auth('messageCenter:Create');
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
fixed:'right',
|
||||
width:150,
|
||||
@@ -58,7 +65,7 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
||||
text:"查看",
|
||||
type:'text',
|
||||
iconRight:'View',
|
||||
show:hasPermissions("messageCenter:Search"),
|
||||
show:auth("messageCenter:Search"),
|
||||
click({ index, row }) {
|
||||
crudExpose.openView({ index, row });
|
||||
if (tabActivted.value === 'receive') {
|
||||
@@ -70,7 +77,7 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show:hasPermissions('messageCenter:Delete')
|
||||
show:auth('messageCenter:Delete')
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,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>
|
||||
<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>
|
||||
<el-row>
|
||||
<el-col :span="4">
|
||||
@@ -8,7 +10,7 @@
|
||||
<el-tag>{{ props.roleName }}</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" :offset="8">
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-button size="small" type="primary" class="pc-save-btn" @click="handleSavePermission">保存菜单授权
|
||||
</el-button>
|
||||
@@ -18,15 +20,16 @@
|
||||
</template>
|
||||
<div class="permission-com">
|
||||
<el-collapse v-model="collapseCurrent" @change="handleCollapseChange" accordion>
|
||||
<el-collapse-item v-for="(item,mIndex) in menuData" :key="mIndex" :name="mIndex">
|
||||
<el-collapse-item v-for="(item,mIndex) in menuData" :key="mIndex" :name="mIndex"
|
||||
style=" background-color: #fafafa;">
|
||||
<template #title>
|
||||
<div @click.stop="null">
|
||||
<p class="pc-collapse-title">
|
||||
<el-checkbox v-model="item.isCheck">
|
||||
<div>
|
||||
<div class="pc-collapse-title">
|
||||
<el-checkbox v-model="item.isCheck" @click.stop="null">
|
||||
<span>{{ item.name }}</span>
|
||||
</el-checkbox>
|
||||
</p>
|
||||
<div v-show="!collapseCurrent.includes(mIndex)">
|
||||
</div>
|
||||
<div v-show="!collapseCurrent.includes(mIndex)" @click.stop="null" style="text-align: left;">
|
||||
<el-checkbox v-for="btn in item.btns" :key="btn.value" :label="btn.value" v-model="btn.isCheck">
|
||||
{{ btn.name }}
|
||||
</el-checkbox>
|
||||
@@ -38,7 +41,7 @@
|
||||
<p>允许对这些数据有以下操作</p>
|
||||
<el-checkbox v-for="(btn,bIndex) in item.btns" :key="bIndex" v-model="btn.isCheck" :label="btn.value">
|
||||
<div class="btn-item">
|
||||
{{ btn.data_range!==null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
|
||||
{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
|
||||
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(item, btn.id)">
|
||||
<el-icon><Setting/></el-icon>
|
||||
</span>
|
||||
@@ -57,7 +60,7 @@
|
||||
|
||||
<div v-for="(head,hIndex) in column.header" :key="hIndex" class="width-check">
|
||||
<el-checkbox :label="head.value" @change="handleColumnChange($event, item, head.value)">
|
||||
<span>{{head.label}}</span>
|
||||
<span>{{ head.label }}</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</li>
|
||||
@@ -109,7 +112,13 @@
|
||||
import {ref, onMounted, defineProps, watch, computed, reactive} from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import {errorNotification} from '/@/utils/message';
|
||||
import {getDataPermissionRange, getDataPermissionDept, getRolePremission, setRolePremission,setBtnDatarange} from './api';
|
||||
import {
|
||||
getDataPermissionRange,
|
||||
getDataPermissionDept,
|
||||
getRolePremission,
|
||||
setRolePremission,
|
||||
setBtnDatarange
|
||||
} from './api';
|
||||
import {MenuDataType, DataPermissionRangeType, CustomDataPermissionDeptType} from './types';
|
||||
import {ElMessage} from 'element-plus'
|
||||
|
||||
@@ -156,7 +165,7 @@ let menuBtnCurrent = ref<number>(-1);
|
||||
let dialogVisible = ref(false);
|
||||
let dataPermissionRange = ref<DataPermissionRangeType[]>([]);
|
||||
const formatDataRange = computed(() => {
|
||||
return function(datarange:number){
|
||||
return function (datarange: number) {
|
||||
const findItem = dataPermissionRange.value.find((i) => i.value === datarange);
|
||||
return findItem?.label || ''
|
||||
}
|
||||
@@ -226,7 +235,7 @@ const handleDialogConfirm = () => {
|
||||
if (btn.id === menuBtnCurrent.value) {
|
||||
const findItem = dataPermissionRange.value.find((i) => i.value === dataPermission.value);
|
||||
btn.data_range = findItem?.value || 0;
|
||||
if(btn.data_range===4){
|
||||
if (btn.data_range === 4) {
|
||||
btn.dept = customDataPermission.value
|
||||
}
|
||||
}
|
||||
@@ -252,7 +261,10 @@ const handleSavePermission = () => {
|
||||
}
|
||||
|
||||
const column = reactive({
|
||||
header:[{value:'is_create',label:'新增可见'},{value:'is_update',label:'编辑可见'},{value:'is_query',label:'列表可见'}]
|
||||
header: [{value: 'is_create', label: '新增可见'}, {value: 'is_update', label: '编辑可见'}, {
|
||||
value: 'is_query',
|
||||
label: '列表可见'
|
||||
}]
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
@@ -270,6 +282,7 @@ onMounted(() => {
|
||||
|
||||
.pc-collapse-title {
|
||||
line-height: 32px;
|
||||
text-align: left;
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
@@ -363,6 +376,7 @@ onMounted(() => {
|
||||
border-left: 1px solid #ebeef5;
|
||||
border-right: 1px solid #ebeef5;
|
||||
box-sizing: border-box;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.el-collapse-item__header.is-active {
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as api from './api';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { columnPermission } from '../../../utils/columnPermission';
|
||||
import { successMessage } from '../../../utils/message';
|
||||
|
||||
import {auth} from '/@/utils/authFunction'
|
||||
interface CreateCrudOptionsTypes {
|
||||
output: any;
|
||||
crudOptions: CrudOptions;
|
||||
@@ -14,12 +14,10 @@ export const createCrudOptions = function ({
|
||||
crudExpose,
|
||||
rolePermission,
|
||||
handleDrawerOpen,
|
||||
hasPermissions,
|
||||
}: {
|
||||
crudExpose: CrudExpose;
|
||||
rolePermission: any;
|
||||
handleDrawerOpen: Function;
|
||||
hasPermissions: Function;
|
||||
}): CreateCrudOptionsTypes {
|
||||
const pageRequest = async (query: any) => {
|
||||
return await api.GetList(query);
|
||||
@@ -47,40 +45,31 @@ export const createCrudOptions = function ({
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('role:Create')
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 260,
|
||||
width: 320,
|
||||
buttons: {
|
||||
view: {
|
||||
show: true,
|
||||
},
|
||||
edit: {
|
||||
show: hasPermissions('role:Update'),
|
||||
show: auth('role:Update'),
|
||||
},
|
||||
remove: {
|
||||
show: hasPermissions('role:Delete'),
|
||||
show: auth('role:Delete'),
|
||||
},
|
||||
/* custom: {
|
||||
permission: {
|
||||
type: 'primary',
|
||||
text: '权限配置',
|
||||
show: hasPermissions('role:Update'),
|
||||
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'),
|
||||
show: auth('role:Permission'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '权限配置',
|
||||
@@ -127,9 +116,9 @@ export const createCrudOptions = function ({
|
||||
sortable: 'custom',
|
||||
show: columnPermission('name', 'is_query'),
|
||||
},
|
||||
addForm: {
|
||||
show: columnPermission('name', 'is_create'),
|
||||
},
|
||||
// addForm: {
|
||||
// show: columnPermission('name', 'is_create'),
|
||||
// },
|
||||
editForm: {
|
||||
show: columnPermission('name', 'is_update'),
|
||||
},
|
||||
@@ -170,7 +159,6 @@ export const createCrudOptions = function ({
|
||||
column: {
|
||||
minWidth: 90,
|
||||
sortable: 'custom',
|
||||
show: columnPermission('sort', 'is_query'),
|
||||
},
|
||||
addForm: {
|
||||
show: columnPermission('sort', 'is_create'),
|
||||
@@ -183,40 +171,6 @@ export const createCrudOptions = function ({
|
||||
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: {
|
||||
title: '状态',
|
||||
search: { show: true },
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="role">
|
||||
import { ref, onMounted, inject } from 'vue';
|
||||
import {ref, onMounted, inject, onBeforeUpdate} from 'vue';
|
||||
import { useColumnPermission } from '/@/stores/columnPermission';
|
||||
import { GetPermission } from './api';
|
||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
import permission from './components/PermissionCom/index.vue';
|
||||
import PermissionComNew from './components/PermissionComNew/index.vue';
|
||||
|
||||
import _ from "lodash-es";
|
||||
import {columnPermission} from "/@/utils/columnPermission";
|
||||
let drawerVisible = ref(false);
|
||||
let roleId = ref(null);
|
||||
let roleName = ref(null);
|
||||
@@ -31,11 +31,11 @@ const crudRef = ref();
|
||||
// crud 配置的ref
|
||||
const crudBinding = ref();
|
||||
|
||||
const hasPermissions: any = inject('$hasPermissions');
|
||||
|
||||
const fetchColumnPermission = async () => {
|
||||
const res = await GetPermission();
|
||||
useColumnPermission().setPermissionData(res.data);
|
||||
console.log(3333,res)
|
||||
};
|
||||
|
||||
const handleDrawerOpen = (row: any) => {
|
||||
@@ -49,21 +49,51 @@ const handleDrawerClose = () => {
|
||||
};
|
||||
|
||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||
const handlecolumnPermission = async (crudOptions:any)=>{
|
||||
const res = await GetPermission();
|
||||
const columns = crudOptions.columns;
|
||||
for(let col in columns){
|
||||
for(let i in res.data){
|
||||
if(res.data[i].field_name === col){
|
||||
columns[col].column.show = i['is_query']
|
||||
columns[col].addForm = {
|
||||
show:i['is_create']
|
||||
}
|
||||
columns[col].editForm = {
|
||||
show:i['is_update']
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose, rolePermission, handleDrawerOpen });
|
||||
|
||||
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(async () => {
|
||||
await fetchColumnPermission();
|
||||
onMounted( async () => {
|
||||
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose, rolePermission, handleDrawerOpen, hasPermissions });
|
||||
|
||||
// 初始化crud配置
|
||||
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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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 { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import { inject } from 'vue';
|
||||
import { auth } from '/@/utils/authFunction';
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
@@ -24,8 +24,6 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
return await api.exportData(query)
|
||||
}
|
||||
|
||||
//权限判定
|
||||
const hasPermissions:any = inject('$hasPermissions');
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
@@ -43,8 +41,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: hasPermissions('user:Create')
|
||||
// show:true
|
||||
show: auth('user:Create')
|
||||
},
|
||||
export:{
|
||||
text:"导出",//按钮文字
|
||||
@@ -66,17 +63,17 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: hasPermissions('user:Update'),
|
||||
show: auth('user:Update'),
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: hasPermissions('user:Delete'),
|
||||
show: auth('user:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '重设密码',
|
||||
type: 'text',
|
||||
show: hasPermissions('user:ResetPassword'),
|
||||
show: auth('user:ResetPassword'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '重设密码',
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { request } from '/@/utils/service';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {inject} from "vue";
|
||||
import {
|
||||
dict,
|
||||
UserPageQuery,
|
||||
AddReq,
|
||||
DelReq,
|
||||
EditReq,
|
||||
compute,
|
||||
CreateCrudOptionsProps,
|
||||
CreateCrudOptionsRet
|
||||
} from '@fast-crud/fast-crud';
|
||||
import {request} from '/@/utils/service';
|
||||
import {dictionary} from '/@/utils/dictionary';
|
||||
import {successMessage} from '/@/utils/message';
|
||||
import {auth} from '/@/utils/authFunction'
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
const editRequest = async ({form, row}: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
const delRequest = async ({row}: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
const addRequest = async ({form}: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject("$hasPermissions")
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
@@ -31,6 +38,13 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('api_white_list:Create')
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
@@ -42,17 +56,17 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show:hasPermissions("api_white_list:Update")
|
||||
show: auth("api_white_list:Update")
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show:hasPermissions("api_white_list:Delete")
|
||||
show: auth("api_white_list:Delete")
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
col: {span: 24},
|
||||
labelWidth: '110px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
@@ -62,7 +76,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
form: {show: false},
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
@@ -131,7 +145,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
},
|
||||
],
|
||||
}),
|
||||
column:{
|
||||
column: {
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
@@ -146,7 +160,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
span: 12,
|
||||
},
|
||||
itemProps: {
|
||||
class: { yxtInput: true },
|
||||
class: {yxtInput: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -163,7 +177,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
const res = Object.keys(ret.paths);
|
||||
const data = [];
|
||||
for (const item of res) {
|
||||
const obj = { label: '', value: '' };
|
||||
const obj = {label: '', value: ''};
|
||||
obj.label = item;
|
||||
obj.value = item;
|
||||
data.push(obj);
|
||||
@@ -172,7 +186,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
});
|
||||
},
|
||||
}),
|
||||
column:{
|
||||
column: {
|
||||
minWidth: 200,
|
||||
},
|
||||
form: {
|
||||
@@ -194,7 +208,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
},
|
||||
},
|
||||
itemProps: {
|
||||
class: { yxtInput: true },
|
||||
class: {yxtInput: true},
|
||||
},
|
||||
helper: {
|
||||
position: 'label',
|
||||
@@ -212,7 +226,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
minWidth:120,
|
||||
minWidth: 120,
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
activeText: '',
|
||||
|
||||
Reference in New Issue
Block a user