Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dffe47e79c | ||
|
|
98a5bbd60a | ||
|
|
ac7e8a7764 | ||
|
|
26ed59d1ff | ||
|
|
369157fa4f | ||
|
|
8961733025 | ||
|
|
577b88332f | ||
|
|
411f065bfc | ||
|
|
8d37d929b2 | ||
|
|
c0d6cd54c3 | ||
|
|
ae8366f097 | ||
|
|
4baf5da0ff | ||
|
|
c00f5f2beb | ||
|
|
5143afdd85 | ||
|
|
b4153d1848 | ||
|
|
d2c0c54080 | ||
|
|
7f184b2a9a | ||
|
|
87f9784445 | ||
|
|
e900cc2280 | ||
|
|
4a26257a61 | ||
|
|
06476c182e | ||
|
|
6146e6edb6 | ||
|
|
85f5ff1935 | ||
|
|
86e33e44e1 | ||
|
|
217f3c8e14 | ||
|
|
d0feaa23e5 | ||
|
|
fa67a27908 | ||
|
|
56145dfe61 | ||
|
|
3def205e6e | ||
|
|
873400bcce | ||
|
|
0c1cf1218b | ||
|
|
d8e5ad9057 | ||
|
|
3639cad58c | ||
|
|
c99eb1a9bc | ||
|
|
7ae15022c0 | ||
|
|
68b3d479ca | ||
|
|
2826cafa75 | ||
|
|
ba4a580bb0 | ||
|
|
f322d38ab8 | ||
|
|
7d315a7d28 | ||
|
|
916071814a | ||
|
|
acf113e5e6 | ||
|
|
083eef9df1 | ||
|
|
e8c64d6d11 | ||
|
|
d1abc58b40 | ||
|
|
0c8ce0ce27 | ||
|
|
23d320434e | ||
|
|
14907e140d | ||
|
|
1132e04080 | ||
|
|
39fee0875d | ||
|
|
5898ae85de | ||
|
|
172a867bad | ||
|
|
5a308695fd | ||
|
|
0c5c3c4ecd | ||
|
|
9a8241023b | ||
|
|
27be35616b | ||
|
|
e6f0a5e0ea | ||
|
|
cbebd8305c | ||
|
|
66b68523f5 | ||
|
|
95b14e5a42 | ||
|
|
583b172a37 | ||
|
|
2227564c6f | ||
|
|
5e3ecfc0a6 | ||
|
|
d467915f97 | ||
|
|
197714e845 | ||
|
|
fd3adeed57 | ||
|
|
bc15ed15ca | ||
|
|
349986f14f | ||
|
|
fb4bba9000 | ||
|
|
e0d46871c4 | ||
|
|
7691481006 | ||
|
|
120c737de7 | ||
|
|
5729aeb521 | ||
|
|
205cfcca2e | ||
|
|
0fdc108ebf | ||
|
|
064cbee8a2 | ||
|
|
a2d4d6e07e | ||
|
|
d721405ee4 | ||
|
|
0650c95745 | ||
|
|
f291885d57 | ||
|
|
4b77fb903c | ||
|
|
fca82c093a | ||
|
|
49300fb17e | ||
|
|
c763333024 | ||
|
|
42e6c7b600 | ||
|
|
6172dea399 | ||
|
|
2baba8e36f | ||
|
|
0131d31808 | ||
|
|
dd2dcd4ad1 | ||
|
|
fb8b0a5ac6 | ||
|
|
6045312f7e | ||
|
|
4641d9c774 | ||
|
|
c04b33ed31 | ||
|
|
0286ca003f | ||
|
|
54e4d23cf7 | ||
|
|
645f43c887 | ||
|
|
4ac8ed7627 | ||
|
|
94ad6b1bae | ||
|
|
96748da99d | ||
|
|
b74a196c94 |
10
CHANGELOG.md
Normal file
10
CHANGELOG.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Django-Vue3-Admin 更新日志
|
||||
|
||||
## 正式发布v3.0.0版本
|
||||
### 1.新增:列权限管理与授权;
|
||||
### 2.新增:代码新版本发布后,进行升级提醒;
|
||||
### 3.优化:角色管理中按钮权限的操作;
|
||||
### 4.优化:websocket 连接状态显示;
|
||||
### 5.优化:初始化获取系统配置与字典配置,进行动态渲染登录页面;
|
||||
### 6.修复:登录页面中系统配置不生效问题;
|
||||
### 7.其他优化
|
||||
253
README.md
253
README.md
@@ -1,196 +1,175 @@
|
||||
# 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)
|
||||
|
||||
[预 览](https://demo.dvadmin.com) | [官网文档](https://www.django-vue-admin.com) | [群聊](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [社区](https://bbs.django-vue-admin.com) | [插件市场](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
|
||||
[preview](https://demo.dvadmin.com) | [Official website document](https://www.django-vue-admin.com) | [qq group](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [community](https://bbs.django-vue-admin.com) | [plugins market](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
|
||||
|
||||
💡 **「About」**
|
||||
|
||||
It is a completely open-source rapid development platform, provided free for personal use and authorized for group use.
|
||||
Django-Vue3-Admin is a comprehensive basic development platform based on the RBAC (Role-Based Access Control) model for permission control, with column-level granularity. It follows a frontend-backend separation architecture, with Django and Django Rest Framework used for the backend, and Vue3, Composition API, TypeScript, Vite, and Element Plus used for the frontend.
|
||||
|
||||
|
||||
## framework introduction
|
||||
|
||||
💡 **「关于」**
|
||||
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) Is a set of all open source rapid development platform, no reservation for individuals and enterprises free use.
|
||||
|
||||
我们是一群热爱代码的青年,在这个炙热的时代下,我们希望静下心来通过Code带来一点我们的色彩和颜色。
|
||||
* 🧑🤝🧑Front-end adoption Vue3+TS+pinia+fastcrud。
|
||||
* 👭The backend uses the Python language Django framework as well as the powerful[Django REST Framework](https://pypi.org/project/djangorestframework)。
|
||||
* 👫Permission authentication use[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),Supports the multi-terminal authentication system.
|
||||
* 👬Support loading dynamic permission menu, multi - way easy permission control.
|
||||
* 👬Enhanced Column Permission Control, with granularity down to each column.
|
||||
* 💏Special thanks: [vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/).
|
||||
* 💡Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
|
||||
|
||||
因为热爱,所以拥抱未来
|
||||
## Online experience
|
||||
|
||||
## 平台简介
|
||||
👩👧👦👩👧👦 demo address:[https://demo.dvadmin.com](https://demo.dvadmin.com)
|
||||
|
||||
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
||||
* demo account:superadmin
|
||||
|
||||
* demo password:admin123456
|
||||
|
||||
👩👦👦docs:[https://django-vue-admin.com](https://django-vue-admin.com)
|
||||
|
||||
* 🧑🤝🧑前端采用 Vue3+TS+pinia+fastcrud(感谢[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/))
|
||||
* 👭后端采用 Python 语言 Django 框架以及强大的 [Django REST Framework](https://pypi.org/project/djangorestframework)。
|
||||
* 👫权限认证使用[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),支持多终端认证系统。
|
||||
* 👬支持加载动态权限菜单,多方式轻松权限控制。
|
||||
* 💏特别鸣谢:[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)。
|
||||
* 💡 特别感谢[jetbrains](https://www.jetbrains.com/) 为本开源项目提供免费的 IntelliJ IDEA 授权。
|
||||
## communication
|
||||
|
||||
* Communication community:[click here](https://bbs.django-vue-admin.com)👩👦👦
|
||||
|
||||
* plugins market:[click here](https://bbs.django-vue-admin.com/plugMarket.html)👩👦👦
|
||||
|
||||
## 在线体验
|
||||
## source code url:
|
||||
|
||||
👩👧👦演示地址:[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com)
|
||||
gitee(Main push):[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
- 账号:superadmin
|
||||
github:[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
- 密码:admin123456
|
||||
## core function
|
||||
|
||||
👩👦👦文档地址:[https://django-vue-admin.com](https://django-vue-admin.com)
|
||||
1. 👨⚕️Menu Management: Configure system menus, operation permissions, button permission flags, backend interface permissions, etc.
|
||||
2. 🧑⚕️Department Management: Configure system organizational structure (company, department, role).
|
||||
3. 👩⚕️Role Management: Role menu permission assignment, data permission assignment, set role-based data scope permissions by department.
|
||||
4. 🧑🎓Button Permission Control: Authorize role-specific button permissions and interface permissions, enabling authorization of data scope for each interface.
|
||||
5. 🧑🎓Field Column Permission Control: Authorize page field display permissions, specifically for the display permissions of a certain column.
|
||||
6. 👨🎓User Management: Users are system operators, and this function is mainly used for system user configuration.
|
||||
7. 👬API Whitelist: Configure interfaces that do not require permission verification.
|
||||
8. 🧑🔧Dictionary Management: Maintain frequently used and relatively fixed data in the system.
|
||||
9. 🧑🔧Region Management: Manage provinces, cities, counties, and districts.
|
||||
10. 📁File Management: Unified management of all files, images, etc., on the platform.
|
||||
11. 🗓️Operation Logs: Record and query logs for normal system operations and exceptional system information.
|
||||
12. 🔌[Plugin Market](https://bbs.django-vue-admin.com/plugMarket.html): Applications and plugins developed based on the Django-Vue-Admin framework.
|
||||
|
||||
## plugins market 🔌
|
||||
|
||||
Updating...
|
||||
|
||||
## 交流
|
||||
## Repository Branch Explanation 💈
|
||||
Main Branch: master (stable version)
|
||||
Development Branch: develop
|
||||
|
||||
- 交流社区:[戳我](https://bbs.django-vue-admin.com)👩👦👦
|
||||
## before start project you need:
|
||||
|
||||
- 插件市场:[戳我](https://bbs.django-vue-admin.com/plugMarket.html)👩👦👦
|
||||
|
||||
- django-vue-admin交流01群(已满):812482043 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=aJVwjDvH-Es4MPJQuoO32N0SucK22TE5&jump_from=webapi)
|
||||
- django-vue-admin交流02群:687252418 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=4jJN4IjWGfxJ8YJXbb_gTsuWjR34WLdc&jump_from=webapi)
|
||||
|
||||
- 二维码
|
||||
|
||||
<img src='https://images.gitee.com/uploads/images/2022/0530/233203_5fb11883_5074988.jpeg' width='200'>
|
||||
|
||||
## 源码地址
|
||||
|
||||
gitee地址(主推):[https://gitee.com/liqianglog/django-vue-admin](https://gitee.com/liqianglog/django-vue-admin)👩👦👦
|
||||
|
||||
github地址:[https://github.com/liqianglog/django-vue-admin](https://github.com/liqianglog/django-vue-admin)👩👦👦
|
||||
|
||||
|
||||
|
||||
## 内置功能
|
||||
|
||||
1. 👨⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
||||
2. 🧑⚕️部门管理:配置系统组织机构(公司、部门、角色)。
|
||||
3. 👩⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
|
||||
4. 🧑🎓权限权限:授权角色的权限范围。
|
||||
5. 👨🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
6. 👬接口白名单:配置不需要进行权限校验的接口。
|
||||
7. 🧑🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
8. 🧑🔧地区管理:对省市县区域进行管理。
|
||||
9. 📁附件管理:对平台上所有文件、图片等进行统一管理。
|
||||
10. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
11. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html):基于Django-Vue-Admin框架开发的应用和插件。
|
||||
|
||||
## 插件市场 🔌
|
||||
|
||||
- Celery异步任务:[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
|
||||
- 升级中心后端:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
|
||||
- 升级中心前端:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
|
||||
|
||||
## 准备工作
|
||||
~~~
|
||||
Python >= 3.8.0 (推荐3.8+版本)
|
||||
nodejs >= 14.0 (推荐最新)
|
||||
Mysql >= 5.7.0 (可选,默认数据库sqlite3,推荐8.0版本)
|
||||
Redis(可选,最新版)
|
||||
Python >= 3.11.0 (Minimum version 3.9+)
|
||||
Node.js >= 16.0
|
||||
Mysql >= 8.0 (Optional, default database: SQLite3, supports 5.7+, recommended version: 8.0)
|
||||
Redis (Optional, latest version)
|
||||
~~~
|
||||
|
||||
## 前端♝
|
||||
## frontend♝
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://gitee.com/liqianglog/django-vue-admin.git
|
||||
# clone code
|
||||
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||
|
||||
# 进入项目目录
|
||||
# enter code dir
|
||||
cd web
|
||||
|
||||
# 安装依赖
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
# install dependence
|
||||
npm install yarn
|
||||
yarn install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# 启动服务
|
||||
npm run dev
|
||||
# 浏览器访问 http://localhost:8080
|
||||
# .env.development 文件中可配置启动端口等参数
|
||||
# 构建生产环境
|
||||
# npm run build
|
||||
# Start service
|
||||
yarn run dev
|
||||
# Visit http://localhost:8080 in your browser
|
||||
# Parameters such as boot port can be configured in the #.env.development file
|
||||
# Build the production environment
|
||||
# yarn run build
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 后端💈
|
||||
## backend💈
|
||||
|
||||
~~~bash
|
||||
1. 进入项目目录 cd backend
|
||||
2. 在项目根目录中,复制 ./conf/env.example.py 文件为一份新的到 ./conf 文件夹下,并重命名为 env.py
|
||||
3. 在 env.py 中配置数据库信息
|
||||
mysql数据库版本建议:8.0
|
||||
mysql数据库字符集:utf8mb4
|
||||
4. 安装依赖环境
|
||||
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
|
||||
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 uvicorn :
|
||||
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8
|
||||
~~~
|
||||
|
||||
### 访问项目
|
||||
### visit backend swagger
|
||||
|
||||
- 访问地址:[http://localhost:8080](http://localhost:8080) (默认为此地址,如有修改请按照配置文件)
|
||||
- 账号:`superadmin` 密码:`admin123456`
|
||||
* visit url:[http://localhost:8080](http://localhost:8080) (The default address is this one. If you want to change it, follow the configuration file)
|
||||
* account:`superadmin` password:`admin123456`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### docker-compose 运行
|
||||
### docker-compose
|
||||
|
||||
~~~shell
|
||||
# 先安装docker-compose (自行百度安装),执行此命令等待安装,如有使用celery插件请打开docker-compose.yml中celery 部分注释
|
||||
docker-compose up -d
|
||||
# 初始化后端数据(第一次执行即可)
|
||||
docker exec -ti dvadmin-django bash
|
||||
# Initialize backend data (first execution only)
|
||||
docker exec -ti dvadmin3-django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init_area
|
||||
python manage.py init
|
||||
exit
|
||||
|
||||
前端地址:http://127.0.0.1:8080
|
||||
后端地址:http://127.0.0.1:8080/api
|
||||
# 在服务器上请把127.0.0.1 换成自己公网ip
|
||||
账号:superadmin 密码:admin123456
|
||||
frontend 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 停止
|
||||
# docker-compose stop
|
||||
docker-compose down
|
||||
# docker-compose 重启
|
||||
# docker-compose restart
|
||||
docker-compose restart
|
||||
# docker-compose 启动时重新进行 build
|
||||
# docker-compose on start build
|
||||
docker-compose up -d --build
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## 演示图✅
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Demo screenshot✅
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

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

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -98,3 +98,4 @@ media/
|
||||
__pypackages__/
|
||||
package-lock.json
|
||||
gunicorn.pid
|
||||
!plugins/__init__.py
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import functools
|
||||
import os
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
|
||||
from django.conf import settings
|
||||
from celery import platforms
|
||||
|
||||
# 租户模式
|
||||
if "django_tenants" in settings.INSTALLED_APPS:
|
||||
from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
|
||||
@@ -16,3 +18,23 @@ else:
|
||||
app.config_from_object('django.conf:settings')
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
platforms.C_FORCE_ROOT = True
|
||||
|
||||
|
||||
def retry_base_task_error():
|
||||
"""
|
||||
celery 失败重试装饰器
|
||||
:return:
|
||||
"""
|
||||
|
||||
def wraps(func):
|
||||
@app.task(bind=True, retry_delay=180, max_retries=3)
|
||||
@functools.wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as exc:
|
||||
raise self.retry(exc=exc)
|
||||
|
||||
return wrapper
|
||||
|
||||
return wraps
|
||||
|
||||
@@ -43,10 +43,8 @@ sys.path.insert(0, os.path.join(PLUGINS_PATH))
|
||||
DEBUG = locals().get("DEBUG", True)
|
||||
ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
|
||||
|
||||
# Application definition
|
||||
CUSTOM_APPS = [
|
||||
"dvadmin.system",
|
||||
]
|
||||
# 列权限需要排除的App应用
|
||||
COLUMN_EXCLUDE_APPS = ['channels', 'captcha'] + locals().get("COLUMN_EXCLUDE_APPS", [])
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.auth",
|
||||
@@ -60,11 +58,12 @@ INSTALLED_APPS = [
|
||||
"corsheaders", # 注册跨域app
|
||||
"drf_yasg",
|
||||
"captcha",
|
||||
'channels',
|
||||
*locals().get("CUSTOM_APPS", []), # 所有项目里写的app需要在env.py文件里的CUSTOM_APPS中
|
||||
"channels",
|
||||
"dvadmin.system",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"dvadmin.utils.middleware.HealthCheckMiddleware",
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
@@ -156,6 +155,11 @@ STATICFILES_DIRS = [
|
||||
MEDIA_ROOT = "media" # 项目下的目录
|
||||
MEDIA_URL = "/media/" # 跟STATIC_URL类似,指定用户可以通过这个url找到文件
|
||||
|
||||
#添加以下代码以后就不用写{% load staticfiles %},可以直接引用
|
||||
STATICFILES_FINDERS = (
|
||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||
"django.contrib.staticfiles.finders.AppDirectoriesFinder"
|
||||
)
|
||||
# 收集静态文件,必须将 MEDIA_ROOT,STATICFILES_DIRS先注释
|
||||
# python manage.py collectstatic
|
||||
# STATIC_ROOT=os.path.join(BASE_DIR,'static')
|
||||
@@ -178,7 +182,6 @@ CHANNEL_LAYERS = {
|
||||
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||
}
|
||||
}
|
||||
REDIS_URL = locals().get('REDIS_URL', "")
|
||||
# CHANNEL_LAYERS = {
|
||||
# 'default': {
|
||||
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
|
||||
@@ -400,11 +403,11 @@ TENANT_SHARED_APPS = []
|
||||
PLUGINS_URL_PATTERNS = []
|
||||
# ********** 一键导入插件配置开始 **********
|
||||
# 例如:
|
||||
#from dvadmin3_upgrade_center.settings import * # 升级中心
|
||||
# from dvadmin_upgrade_center.settings import * # 升级中心
|
||||
# from dvadmin_celery.settings import * # celery 异步任务
|
||||
# from dvadmin_third.settings import * # 第三方用户管理
|
||||
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
|
||||
# from dvadmin_tenants.settings import * # 租户管理
|
||||
# from dvadmin_uniapp.settings import *
|
||||
#from dvadmin_social_auth.settings import *
|
||||
# ...
|
||||
# ********** 一键导入插件配置结束 **********
|
||||
|
||||
@@ -73,7 +73,7 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer):
|
||||
unread_count = await _get_message_unread(self.user_id)
|
||||
if unread_count == 0:
|
||||
# 发送连接成功
|
||||
await self.send_json(set_message('system', 'SYSTEM', '连接成功'))
|
||||
await self.send_json(set_message('system', 'SYSTEM', '您已上线'))
|
||||
else:
|
||||
await self.send_json(
|
||||
set_message('system', 'SYSTEM', "请查看您的未读消息~",
|
||||
|
||||
@@ -44,6 +44,5 @@ LOGIN_NO_CAPTCHA_AUTH = True
|
||||
# ================================================= #
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
CUSTOM_APPS = [
|
||||
"dvadmin.system",
|
||||
]
|
||||
# 列权限中排除App应用
|
||||
COLUMN_EXCLUDE_APPS = []
|
||||
|
||||
@@ -5,11 +5,12 @@ from rest_framework import serializers
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
import django
|
||||
|
||||
django.setup()
|
||||
from dvadmin.system.models import (
|
||||
Role, Dept, Users, Menu, MenuButton,
|
||||
ApiWhiteList, Dictionary, SystemConfig,
|
||||
RoleMenuPermission, RoleApiPermission, Columns
|
||||
RoleMenuPermission, RoleMenuButtonPermission, MenuField
|
||||
)
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
|
||||
@@ -42,13 +43,36 @@ class UsersInitSerializer(CustomModelSerializer):
|
||||
}
|
||||
|
||||
|
||||
class MenuButtonInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id', 'name', 'value', 'api', 'method', 'menu']
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MenuFieldInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化列权限-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuField
|
||||
fields = ['id', 'menu', 'field_name', 'title', 'model']
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MenuInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
递归深度获取数信息(用于生成初始化json文件)
|
||||
"""
|
||||
name = serializers.CharField(required=True)
|
||||
menu_type = serializers.IntegerField(required=True)
|
||||
name = serializers.CharField(required=False)
|
||||
children = serializers.SerializerMethodField()
|
||||
menu_button = serializers.SerializerMethodField()
|
||||
menu_field = serializers.SerializerMethodField()
|
||||
|
||||
def get_children(self, obj: Menu):
|
||||
data = []
|
||||
@@ -58,18 +82,34 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
data = serializer.data
|
||||
return data
|
||||
|
||||
def get_menu_button(self, obj: Menu):
|
||||
data = []
|
||||
instance = obj.menuPermission.order_by('method')
|
||||
if instance:
|
||||
data = list(instance.values('name', 'value', 'api', 'method'))
|
||||
return data
|
||||
|
||||
def get_menu_field(self, obj: Menu):
|
||||
data = []
|
||||
instance = obj.menufield_set.order_by('field_name')
|
||||
if instance:
|
||||
data = list(instance.values('field_name', 'title', 'model'))
|
||||
return data
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
children = self.initial_data.get('children')
|
||||
menu_button = self.initial_data.get('menu_button')
|
||||
menu_field = self.initial_data.get('menu_field')
|
||||
# 菜单表
|
||||
if children:
|
||||
for menu_data in children:
|
||||
menu_data['parent'] = instance.id
|
||||
filter_data = {
|
||||
"name": menu_data['name'],
|
||||
"web_path": menu_data['web_path'],
|
||||
"component": menu_data['component'],
|
||||
"menu_type": menu_data['menu_type'],
|
||||
"component_name": menu_data['component_name'],
|
||||
}
|
||||
instance_obj = Menu.objects.filter(**filter_data).first()
|
||||
if instance_obj and not self.initial_data.get('reset'):
|
||||
@@ -77,12 +117,37 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
serializer = MenuInitSerializer(instance_obj, data=menu_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
# 菜单按钮
|
||||
if menu_button:
|
||||
for menu_button_data in menu_button:
|
||||
menu_button_data['menu'] = instance.id
|
||||
filter_data = {
|
||||
"menu": menu_button_data['menu'],
|
||||
"value": menu_button_data['value']
|
||||
}
|
||||
instance_obj = MenuButton.objects.filter(**filter_data).first()
|
||||
serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
# 列权限
|
||||
if menu_field:
|
||||
for field_data in menu_field:
|
||||
field_data['menu'] = instance.id
|
||||
filter_data = {
|
||||
'menu': field_data['menu'],
|
||||
'field_name': field_data['field_name'],
|
||||
'model': field_data['model']
|
||||
}
|
||||
instance_obj = MenuField.objects.filter(**filter_data).first()
|
||||
serializer = MenuFieldInitSerializer(instance_obj, data=field_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['name', 'icon', 'sort', 'is_link', 'menu_type', 'web_path', 'component', 'component_name', 'status',
|
||||
'cache', 'visible', 'parent', 'children', 'creator', 'dept_belong_id']
|
||||
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
|
||||
'cache', 'visible', 'parent', 'children', 'menu_button', 'menu_field', 'creator', 'dept_belong_id']
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
@@ -135,62 +200,37 @@ class RoleMenuInitSerializer(CustomModelSerializer):
|
||||
}
|
||||
|
||||
|
||||
class RoleApiPermissionInitSerializer(CustomModelSerializer):
|
||||
class RoleMenuButtonInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化角色接口权限(用于生成初始化json文件)
|
||||
初始化角色菜单按钮(用于生成初始化json文件)
|
||||
"""
|
||||
role_key = serializers.CharField(max_length=100, required=True)
|
||||
name = serializers.CharField(max_length=255, required=True)
|
||||
api = serializers.CharField(max_length=255, required=True)
|
||||
method = serializers.IntegerField(required=True)
|
||||
menu_button_value = serializers.CharField(max_length=100, required=True)
|
||||
data_range = serializers.CharField(max_length=100, required=False)
|
||||
|
||||
def create(self, validated_data):
|
||||
init_data = self.initial_data
|
||||
validated_data.pop('menu_button_value')
|
||||
validated_data.pop('role_key')
|
||||
role_id = Role.objects.filter(key=init_data['role_key']).first()
|
||||
menu_button_id = MenuButton.objects.filter(value=init_data['menu_button_value']).first()
|
||||
validated_data['role'] = role_id
|
||||
validated_data['menu_button'] = menu_button_id
|
||||
instance = super().create(validated_data)
|
||||
instance.dept.set([])
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = RoleApiPermission
|
||||
fields = ['role_key', 'name','api','method', 'data_range', 'dept', 'creator', 'dept_belong_id']
|
||||
model = RoleMenuButtonPermission
|
||||
fields = ['role_key', 'menu_button_value', 'data_range', 'dept', 'creator', 'dept_belong_id']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
'role': {'required': False},
|
||||
'menu': {'required': False},
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
|
||||
class RoleColumnInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化角色字段权限(用于生成初始化json文件)
|
||||
"""
|
||||
role_key = serializers.CharField(max_length=100, required=True)
|
||||
app = serializers.CharField(max_length=255, required=True)
|
||||
model = serializers.CharField(max_length=255, required=True)
|
||||
field_name = serializers.CharField(max_length=255, required=True)
|
||||
title = serializers.CharField(max_length=255, required=True)
|
||||
|
||||
def create(self, validated_data):
|
||||
init_data = self.initial_data
|
||||
validated_data.pop('role_key')
|
||||
role_id = Role.objects.filter(key=init_data['role_key']).first()
|
||||
validated_data['role'] = role_id
|
||||
instance = super().create(validated_data)
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Columns
|
||||
fields = ['role_key', 'app','model','field_name', 'title']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
'role': {'required': False},
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
|
||||
class ApiWhiteListInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
[
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "id",
|
||||
"title": "Id",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
},
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "description",
|
||||
"title": "描述",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
},
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "creator",
|
||||
"title": "创建人",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
},
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "modifier",
|
||||
"title": "修改人",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
},
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "dept_belong_id",
|
||||
"title": "数据归属部门",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
},
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "update_datetime",
|
||||
"title": "修改时间",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
},
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "create_datetime",
|
||||
"title": "创建时间",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
},
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "name",
|
||||
"title": "角色名称",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
},
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "key",
|
||||
"title": "权限字符",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
},
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "sort",
|
||||
"title": "角色顺序",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
},
|
||||
{
|
||||
"dept_belong_id": "1",
|
||||
"app": "dvadmin.system",
|
||||
"model": "Role",
|
||||
"field_name": "status",
|
||||
"title": "角色状态",
|
||||
"is_query": 1,
|
||||
"is_create": 1,
|
||||
"is_update": 1,
|
||||
"role_key": "admin"
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
||||
[
|
||||
{
|
||||
"role_key": "admin",
|
||||
"name": "菜单列表查询",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"role_key": "public",
|
||||
"name": "菜单列表查询",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 0
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"role_key": "admin",
|
||||
"menu_button_value": "menu:Search",
|
||||
"data_range": 0
|
||||
},
|
||||
{
|
||||
"role_key": "public",
|
||||
"menu_button_value":"menu:Search",
|
||||
"data_range": 0
|
||||
}
|
||||
]
|
||||
@@ -65,6 +65,20 @@
|
||||
"placeholder": null,
|
||||
"setting": null,
|
||||
"children": [
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "网站标题",
|
||||
"key": "site_title",
|
||||
"value": "Dvadmin",
|
||||
"sort": 1,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [],
|
||||
"placeholder": "请输入网站标题",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "网站名称",
|
||||
@@ -116,7 +130,7 @@
|
||||
"parent": 1,
|
||||
"title": "版权信息",
|
||||
"key": "copyright",
|
||||
"value": "2021-2022 django-vue-admin.com 版权所有",
|
||||
"value": "2021-2024 django-vue-admin.com 版权所有",
|
||||
"sort": 4,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
@@ -168,7 +182,7 @@
|
||||
"parent": 1,
|
||||
"title": "隐私链接",
|
||||
"key": "privacy_url",
|
||||
"value": "#",
|
||||
"value": "/api/system/clause/privacy.html",
|
||||
"sort": 7,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
@@ -182,7 +196,7 @@
|
||||
"parent": 1,
|
||||
"title": "条款链接",
|
||||
"key": "clause_url",
|
||||
"value": "#",
|
||||
"value": "/api/system/clause/terms_service.html",
|
||||
"sort": 8,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
|
||||
@@ -11,7 +11,7 @@ from dvadmin.utils.core_initialize import CoreInitialize
|
||||
from dvadmin.system.fixtures.initSerializer import (
|
||||
UsersInitSerializer, DeptInitSerializer, RoleInitSerializer,
|
||||
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer,
|
||||
SystemConfigInitSerializer, RoleMenuInitSerializer, RoleApiPermissionInitSerializer, RoleColumnInitSerializer
|
||||
SystemConfigInitSerializer, RoleMenuInitSerializer, RoleMenuButtonInitSerializer
|
||||
)
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class Initialize(CoreInitialize):
|
||||
"""
|
||||
初始化菜单信息
|
||||
"""
|
||||
self.init_base(MenuInitSerializer, unique_fields=['name'])
|
||||
self.init_base(MenuInitSerializer, unique_fields=['name', 'web_path', 'component', 'component_name'])
|
||||
|
||||
def init_role_menu(self):
|
||||
"""
|
||||
@@ -47,17 +47,11 @@ class Initialize(CoreInitialize):
|
||||
"""
|
||||
self.init_base(RoleMenuInitSerializer, unique_fields=['role', 'menu'])
|
||||
|
||||
def init_role_api_permission(self):
|
||||
def init_role_menu_button(self):
|
||||
"""
|
||||
初始化角色菜单按钮信息
|
||||
"""
|
||||
self.init_base(RoleApiPermissionInitSerializer, unique_fields=['role', 'api','name'])
|
||||
|
||||
def init_role_column(self):
|
||||
"""
|
||||
初始化角色字段权限
|
||||
"""
|
||||
self.init_base(RoleColumnInitSerializer, unique_fields=['app','model','field_name'])
|
||||
self.init_base(RoleMenuButtonInitSerializer, unique_fields=['role', 'menu_button'])
|
||||
|
||||
def init_api_white_list(self):
|
||||
"""
|
||||
@@ -83,8 +77,7 @@ class Initialize(CoreInitialize):
|
||||
self.init_users()
|
||||
self.init_menu()
|
||||
self.init_role_menu()
|
||||
self.init_role_api_permission()
|
||||
self.init_role_column()
|
||||
self.init_role_menu_button()
|
||||
self.init_api_white_list()
|
||||
self.init_dictionary()
|
||||
self.init_system_config()
|
||||
|
||||
@@ -155,45 +155,73 @@ class Menu(CoreModel):
|
||||
help_text="上级菜单",
|
||||
)
|
||||
icon = models.CharField(max_length=64, verbose_name="菜单图标", null=True, blank=True, help_text="菜单图标")
|
||||
name = models.CharField(max_length=64, verbose_name="目录名称/菜单名称/按钮名称", help_text="目录名称/菜单名称/按钮名称")
|
||||
name = models.CharField(max_length=64, verbose_name="菜单名称", help_text="菜单名称")
|
||||
sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序")
|
||||
is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
|
||||
MENU_TYPE_CHOICES =(
|
||||
(0, "目录"),
|
||||
(1, "菜单"),
|
||||
(2, "按钮"),
|
||||
ISLINK_CHOICES = (
|
||||
(0, "否"),
|
||||
(1, "是"),
|
||||
)
|
||||
menu_type = models.IntegerField(default=0, verbose_name="菜单类型", help_text="菜单类型")
|
||||
web_path = models.CharField(max_length=128,default="/", verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
|
||||
component = models.CharField(max_length=200, verbose_name="组件地址/按钮权限值", null=True, blank=True, help_text="组件地址/按钮权限值")
|
||||
is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
|
||||
link_url = models.CharField(max_length=255, verbose_name="链接地址", null=True, blank=True, help_text="链接地址")
|
||||
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录")
|
||||
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
|
||||
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
|
||||
component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True,
|
||||
help_text="组件名称")
|
||||
status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态")
|
||||
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
|
||||
visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示",
|
||||
help_text="侧边栏中是否显示")
|
||||
frame_out = models.BooleanField(default=False, blank=True, verbose_name="是否主框架外", help_text="是否主框架外")
|
||||
is_iframe = models.BooleanField(default=False, blank=True, verbose_name="框架外显示", help_text="框架外显示")
|
||||
is_affix = models.BooleanField(default=False, blank=True, verbose_name="是否固定", help_text="是否固定")
|
||||
|
||||
@classmethod
|
||||
def get_all_parent(cls, id: int, all_list=None, nodes=None):
|
||||
"""
|
||||
递归获取给定ID的所有层级
|
||||
:param id: 参数ID
|
||||
:param all_list: 所有列表
|
||||
:param nodes: 递归列表
|
||||
:return: nodes
|
||||
"""
|
||||
if not all_list:
|
||||
all_list = Menu.objects.values("id", "name", "parent")
|
||||
if nodes is None:
|
||||
nodes = []
|
||||
for ele in all_list:
|
||||
if ele.get("id") == id:
|
||||
parent_id = ele.get("parent")
|
||||
if parent_id is not None:
|
||||
cls.get_all_parent(parent_id, all_list, nodes)
|
||||
nodes.append(ele)
|
||||
return nodes
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_menu"
|
||||
verbose_name = "菜单表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("sort",)
|
||||
|
||||
|
||||
class Columns(CoreModel):
|
||||
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
|
||||
app = models.CharField(max_length=64, verbose_name='应用名')
|
||||
class MenuField(CoreModel):
|
||||
model = models.CharField(max_length=64, verbose_name='表名')
|
||||
menu = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name='菜单', db_constraint=False)
|
||||
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
|
||||
title = models.CharField(max_length=64, verbose_name='字段显示名')
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_menu_field"
|
||||
verbose_name = "菜单字段表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("id",)
|
||||
|
||||
class FieldPermission(CoreModel):
|
||||
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
|
||||
field = models.ForeignKey(to='MenuField', on_delete=models.CASCADE,related_name='menu_field', verbose_name='字段', db_constraint=False)
|
||||
is_query = models.BooleanField(default=1, verbose_name='是否可查询')
|
||||
is_create = models.BooleanField(default=1, verbose_name='是否可创建')
|
||||
is_update = models.BooleanField(default=1, verbose_name='是否可更新')
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_columns"
|
||||
verbose_name = "列权限表"
|
||||
db_table = table_prefix + "system_field_permission"
|
||||
verbose_name = "字段权限表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("id",)
|
||||
|
||||
@@ -251,25 +279,25 @@ class RoleMenuPermission(CoreModel):
|
||||
# ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
class RoleApiPermission(CoreModel):
|
||||
class RoleMenuButtonPermission(CoreModel):
|
||||
role = models.ForeignKey(
|
||||
to="Role",
|
||||
db_constraint=False,
|
||||
related_name="role_api",
|
||||
related_name="role_menu_button",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="关联角色",
|
||||
help_text="关联角色",
|
||||
)
|
||||
name = models.CharField(max_length=64, verbose_name="名称", help_text="名称")
|
||||
api = models.CharField(max_length=200, verbose_name="接口地址", help_text="接口地址")
|
||||
METHOD_CHOICES = (
|
||||
(0, "GET"),
|
||||
(1, "POST"),
|
||||
(2, "PUT"),
|
||||
(3, "DELETE"),
|
||||
menu_button = models.ForeignKey(
|
||||
to="MenuButton",
|
||||
db_constraint=False,
|
||||
related_name="menu_button_permission",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="关联菜单按钮",
|
||||
help_text="关联菜单按钮",
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
|
||||
help_text="接口请求方法")
|
||||
DATASCOPE_CHOICES = (
|
||||
(0, "仅本人数据权限"),
|
||||
(1, "本部门及以下数据权限"),
|
||||
@@ -283,8 +311,8 @@ class RoleApiPermission(CoreModel):
|
||||
help_text="数据权限-关联部门")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "role_api_permission"
|
||||
verbose_name = "角色接口权限表"
|
||||
db_table = table_prefix + "role_menu_button_permission"
|
||||
verbose_name = "角色按钮权限表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
@@ -457,7 +485,7 @@ class SystemConfig(CoreModel):
|
||||
help_text="父级",
|
||||
)
|
||||
title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")
|
||||
key = models.CharField(max_length=50, verbose_name="键", help_text="键", db_index=True)
|
||||
key = models.CharField(max_length=20, verbose_name="键", help_text="键", db_index=True)
|
||||
value = models.JSONField(max_length=100, verbose_name="值", help_text="值", null=True, blank=True)
|
||||
sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True)
|
||||
status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态")
|
||||
|
||||
@@ -3,22 +3,25 @@ from rest_framework import routers
|
||||
|
||||
from dvadmin.system.views.api_white_list import ApiWhiteListViewSet
|
||||
from dvadmin.system.views.area import AreaViewSet
|
||||
from dvadmin.system.views.clause import PrivacyView, TermsServiceView
|
||||
from dvadmin.system.views.dept import DeptViewSet
|
||||
from dvadmin.system.views.dictionary import DictionaryViewSet
|
||||
from dvadmin.system.views.file_list import FileViewSet
|
||||
from dvadmin.system.views.login_log import LoginLogViewSet
|
||||
from dvadmin.system.views.menu import MenuViewSet
|
||||
from dvadmin.system.views.menu_button import MenuButtonViewSet
|
||||
from dvadmin.system.views.message_center import MessageCenterViewSet
|
||||
from dvadmin.system.views.operation_log import OperationLogViewSet
|
||||
from dvadmin.system.views.role import RoleViewSet
|
||||
from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet
|
||||
from dvadmin.system.views.role_api_permission import RoleApiPermissionViewSet
|
||||
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
|
||||
from dvadmin.system.views.system_config import SystemConfigViewSet
|
||||
from dvadmin.system.views.user import UserViewSet
|
||||
from dvadmin.system.views.column import ColumnViewSet
|
||||
from dvadmin.system.views.menu_field import MenuFieldViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
system_url.register(r'menu', MenuViewSet)
|
||||
system_url.register(r'menu_button', MenuButtonViewSet)
|
||||
system_url.register(r'role', RoleViewSet)
|
||||
system_url.register(r'dept', DeptViewSet)
|
||||
system_url.register(r'user', UserViewSet)
|
||||
@@ -29,9 +32,9 @@ system_url.register(r'file', FileViewSet)
|
||||
system_url.register(r'api_white_list', ApiWhiteListViewSet)
|
||||
system_url.register(r'system_config', SystemConfigViewSet)
|
||||
system_url.register(r'message_center', MessageCenterViewSet)
|
||||
system_url.register(r'role_api_permission', RoleApiPermissionViewSet)
|
||||
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
|
||||
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
||||
system_url.register(r'column', ColumnViewSet)
|
||||
system_url.register(r'column', MenuFieldViewSet)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
@@ -44,5 +47,7 @@ urlpatterns = [
|
||||
path('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
|
||||
path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})),
|
||||
path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})),
|
||||
path('clause/privacy.html', PrivacyView.as_view()),
|
||||
path('clause/terms_service.html', TermsServiceView.as_view()),
|
||||
]
|
||||
urlpatterns += system_url.urls
|
||||
|
||||
23
backend/dvadmin/system/views/clause.py
Normal file
23
backend/dvadmin/system/views/clause.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from rest_framework.views import APIView
|
||||
from django.shortcuts import render
|
||||
|
||||
|
||||
class PrivacyView(APIView):
|
||||
"""
|
||||
后台隐私政策
|
||||
"""
|
||||
permission_classes = []
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return render(request, 'privacy.html')
|
||||
|
||||
|
||||
|
||||
class TermsServiceView(APIView):
|
||||
"""
|
||||
后台服务条款
|
||||
"""
|
||||
permission_classes = []
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return render(request, 'terms_service.html')
|
||||
@@ -9,7 +9,7 @@ from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Dept, RoleApiPermission, Users
|
||||
from dvadmin.system.models import Dept, RoleMenuButtonPermission, Users
|
||||
from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
@@ -115,13 +115,10 @@ class DeptViewSet(CustomModelViewSet):
|
||||
del params['page']
|
||||
if limit:
|
||||
del params['limit']
|
||||
if params:
|
||||
if parent:
|
||||
queryset = self.queryset.filter(status=True, parent=parent)
|
||||
else:
|
||||
queryset = self.queryset.filter(status=True)
|
||||
if params and parent:
|
||||
queryset = self.queryset.filter(status=True, parent=parent)
|
||||
else:
|
||||
queryset = self.queryset.filter(status=True, parent__isnull=True)
|
||||
queryset = self.queryset.filter(status=True)
|
||||
queryset = self.filter_queryset(queryset)
|
||||
serializer = DeptSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
@@ -135,7 +132,7 @@ class DeptViewSet(CustomModelViewSet):
|
||||
queryset = Dept.objects.values('id', 'name', 'parent')
|
||||
else:
|
||||
role_ids = request.user.role.values_list('id', flat=True)
|
||||
data_range = RoleApiPermission.objects.filter(role__in=role_ids).values_list('data_range', flat=True)
|
||||
data_range = RoleMenuButtonPermission.objects.filter(role__in=role_ids).values_list('data_range', flat=True)
|
||||
user_dept_id = request.user.dept.id
|
||||
dept_list = [user_dept_id]
|
||||
data_range_list = list(set(data_range))
|
||||
@@ -143,7 +140,7 @@ class DeptViewSet(CustomModelViewSet):
|
||||
if item in [0, 2]:
|
||||
dept_list = [user_dept_id]
|
||||
elif item == 1:
|
||||
dept_list = Dept.recursion_dept_info(dept_id=user_dept_id)
|
||||
dept_list = Dept.recursion_all_dept(dept_id=user_dept_id)
|
||||
elif item == 3:
|
||||
dept_list = Dept.objects.values_list('id', flat=True)
|
||||
elif item == 4:
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Menu, RoleMenuPermission
|
||||
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
|
||||
from dvadmin.system.views.menu_button import MenuButtonSerializer
|
||||
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
@@ -21,7 +21,7 @@ class MenuSerializer(CustomModelSerializer):
|
||||
菜单表的简单序列化器
|
||||
"""
|
||||
menuPermission = serializers.SerializerMethodField(read_only=True)
|
||||
hasChildren = serializers.SerializerMethodField()
|
||||
hasChild = serializers.SerializerMethodField()
|
||||
|
||||
def get_menuPermission(self, instance):
|
||||
queryset = instance.menuPermission.order_by('-name').values('id', 'name', 'value')
|
||||
@@ -31,7 +31,7 @@ class MenuSerializer(CustomModelSerializer):
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_hasChildren(self, instance):
|
||||
def get_hasChild(self, instance):
|
||||
hasChild = Menu.objects.filter(parent=instance.id)
|
||||
if hasChild:
|
||||
return True
|
||||
@@ -71,8 +71,8 @@ class WebRouterSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = (
|
||||
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'menu_type', 'web_path', 'component',
|
||||
'component_name', 'cache', 'visible', 'status')
|
||||
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link','link_url', 'is_catalog', 'web_path', 'component',
|
||||
'component_name', 'cache', 'visible','is_iframe','is_affix', 'status')
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
@@ -90,57 +90,45 @@ class MenuViewSet(CustomModelViewSet):
|
||||
create_serializer_class = MenuCreateSerializer
|
||||
update_serializer_class = MenuCreateSerializer
|
||||
search_fields = ['name', 'status']
|
||||
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'menu_type']
|
||||
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog']
|
||||
|
||||
@action(methods=['get'], detail=False)
|
||||
def tree(self, request):
|
||||
def list(self, request):
|
||||
"""懒加载"""
|
||||
request.query_params._mutable = True
|
||||
params = request.query_params
|
||||
menu_type = params.get('menu_type', 0)
|
||||
queryset = Menu.objects.filter(menu_type=menu_type).order_by('sort')
|
||||
parent = params.get('parent', None)
|
||||
page = params.get('page', None)
|
||||
limit = params.get('limit', None)
|
||||
if page:
|
||||
del params['page']
|
||||
if limit:
|
||||
del params['limit']
|
||||
if params:
|
||||
if parent:
|
||||
queryset = self.queryset.filter(parent=parent)
|
||||
else:
|
||||
queryset = self.queryset.filter()
|
||||
else:
|
||||
queryset = self.queryset.filter(parent__isnull=True)
|
||||
queryset = self.filter_queryset(queryset)
|
||||
serializer = MenuSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return DetailResponse(data=data)
|
||||
|
||||
@action(methods=['get'], detail=True)
|
||||
def getChildren(self,request,pk):
|
||||
"""
|
||||
获取子菜单,用于菜单页面的懒加载
|
||||
"""
|
||||
queryset = Menu.objects.filter(parent=pk)
|
||||
serializer = MenuSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return DetailResponse(data=data)
|
||||
return SuccessResponse(data=data)
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[])
|
||||
def web_router(self, request):
|
||||
"""用于前端获取当前角色的路由"""
|
||||
user = request.user
|
||||
if user.is_superuser:
|
||||
queryset = self.queryset.filter(status=1,menu_type__in=[0,1])
|
||||
queryset = self.queryset.filter(status=1)
|
||||
else:
|
||||
role_list = user.role.values_list('id', flat=True)
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id', flat=True)
|
||||
queryset = Menu.objects.filter(id__in=menu_list,menu_type__in=[0,1])
|
||||
queryset = Menu.objects.filter(id__in=menu_list)
|
||||
serializer = WebRouterSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return SuccessResponse(data=data, total=len(data), msg="获取成功")
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def menu_button_all_permission(self, request):
|
||||
"""
|
||||
获取所有的按钮权限
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = Menu.objects.filter(menu_type=2).values_list('component', flat=True)
|
||||
else:
|
||||
role_id = request.user.role.values_list('id', flat=True)
|
||||
queryset = Menu.objects.filter(role__in=role_id,menu_type=2).values_list('component',flat=True).distinct()
|
||||
return DetailResponse(data=queryset)
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[])
|
||||
def get_all_menu(self, request):
|
||||
"""用于菜单管理获取所有的菜单"""
|
||||
|
||||
83
backend/dvadmin/system/views/menu_button.py
Normal file
83
backend/dvadmin/system/views/menu_button.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 菜单按钮管理
|
||||
"""
|
||||
from django.db.models import F
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import MenuButton, RoleMenuButtonPermission
|
||||
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class MenuButtonSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id', 'name', 'value', 'api', 'method','menu']
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
|
||||
class MenuButtonCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MenuButtonViewSet(CustomModelViewSet):
|
||||
"""
|
||||
菜单按钮接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = MenuButton.objects.order_by('create_datetime')
|
||||
serializer_class = MenuButtonSerializer
|
||||
create_serializer_class = MenuButtonCreateUpdateSerializer
|
||||
update_serializer_class = MenuButtonCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""
|
||||
重写list方法
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
queryset = self.filter_queryset(self.get_queryset()).order_by('name')
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(serializer.data,msg="获取成功")
|
||||
|
||||
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
|
||||
def menu_button_all_permission(self,request):
|
||||
"""
|
||||
获取所有的按钮权限
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = MenuButton.objects.values_list('value',flat=True)
|
||||
else:
|
||||
role_id = request.user.role.values_list('id', flat=True)
|
||||
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_id).values_list('menu_button__value',flat=True).distinct()
|
||||
return DetailResponse(data=queryset)
|
||||
@@ -1,52 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.apps import apps
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Columns, Role
|
||||
from dvadmin.system.models import Role, MenuField
|
||||
from dvadmin.utils.models import get_custom_app_models
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse
|
||||
|
||||
|
||||
class ColumnSerializer(CustomModelSerializer):
|
||||
class MenuFieldSerializer(CustomModelSerializer):
|
||||
"""
|
||||
列权限序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Columns
|
||||
model = MenuField
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id']
|
||||
|
||||
|
||||
class ColumnViewSet(CustomModelViewSet):
|
||||
class MenuFieldViewSet(CustomModelViewSet):
|
||||
"""
|
||||
列权限视图集
|
||||
"""
|
||||
queryset = Columns.objects.all()
|
||||
serializer_class = ColumnSerializer
|
||||
queryset = MenuField.objects.order_by('-model')
|
||||
serializer_class = MenuFieldSerializer
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
role_id = request.query_params.get('role')
|
||||
app_name = request.query_params.get('app')
|
||||
model_name = request.query_params.get('model')
|
||||
if not role_id or not model_name or not app_name:
|
||||
return ErrorResponse(msg="参数错误")
|
||||
queryset = Columns.objects.filter(role_id=role_id, model=model_name, app=app_name)
|
||||
serializer = ColumnSerializer(queryset, many=True, request=request)
|
||||
menu = request.query_params.get('menu')
|
||||
if not menu:
|
||||
return SuccessResponse([])
|
||||
queryset = self.filter_queryset(self.get_queryset().filter(menu=menu))
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
payload = request.data
|
||||
for model in get_custom_app_models(payload.get('app')):
|
||||
if payload.get('model') == model['model']:
|
||||
for model in apps.get_models():
|
||||
if payload.get('model') == model.__name__:
|
||||
break
|
||||
else:
|
||||
return ErrorResponse(msg='模型表不存在')
|
||||
|
||||
if Columns.objects.filter(app=model['app'], model=model['model'], field_name=payload.get('field_name')).exists():
|
||||
if MenuField.objects.filter(menu=payload.get('menu'),model=model.__name__, field_name=payload.get('field_name')).exists():
|
||||
return ErrorResponse(msg='‘%s’ 字段权限已有,不可重复创建' % payload.get('title'))
|
||||
|
||||
return super().create(request, *args, **kwargs)
|
||||
@@ -55,34 +54,31 @@ class ColumnViewSet(CustomModelViewSet):
|
||||
def get_models(self, request):
|
||||
"""获取所有项目app下的model"""
|
||||
res = []
|
||||
for app in get_custom_app_models():
|
||||
for model in app:
|
||||
res.append({
|
||||
'app': model['app'],
|
||||
'title': model['verbose'],
|
||||
'key': model['model']
|
||||
})
|
||||
for model in get_custom_app_models():
|
||||
res.append({
|
||||
'app': model['app'],
|
||||
'title': model['verbose'],
|
||||
'key': model['model']
|
||||
})
|
||||
return DetailResponse(res)
|
||||
|
||||
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def auto_match_fields(self, request):
|
||||
"""自动匹配已有的字段"""
|
||||
role_id = request.data.get('role')
|
||||
app_name = request.data.get('app')
|
||||
menu_id = request.data.get('menu')
|
||||
model_name = request.data.get('model')
|
||||
if not role_id or not model_name or not app_name:
|
||||
return DetailResponse([], msg='无操作')
|
||||
for model in get_custom_app_models(app_name):
|
||||
if not menu_id or not model_name:
|
||||
return ErrorResponse( msg='参数错误')
|
||||
for model in get_custom_app_models():
|
||||
if model['model'] != model_name:
|
||||
continue
|
||||
for field in model['fields']:
|
||||
if Columns.objects.filter(
|
||||
role_id=role_id, app=app_name, model=model_name, field_name=field['name']
|
||||
if MenuField.objects.filter(
|
||||
menu_id=menu_id, model=model_name, field_name=field['name']
|
||||
).exists():
|
||||
continue
|
||||
data = {
|
||||
'role': role_id,
|
||||
'app': app_name,
|
||||
'menu': menu_id,
|
||||
'model': model_name,
|
||||
'field_name': field['name'],
|
||||
'title': str(field['title']),
|
||||
@@ -90,4 +86,4 @@ class ColumnViewSet(CustomModelViewSet):
|
||||
serializer = self.get_serializer(data=data, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return DetailResponse(msg='匹配成功')
|
||||
return SuccessResponse(msg='匹配成功')
|
||||
@@ -13,6 +13,7 @@ from rest_framework.permissions import IsAuthenticated
|
||||
from dvadmin.system.models import Role, Menu, MenuButton, Dept
|
||||
from dvadmin.system.views.dept import DeptSerializer
|
||||
from dvadmin.system.views.menu import MenuSerializer
|
||||
from dvadmin.system.views.menu_button import MenuButtonSerializer
|
||||
from dvadmin.utils.crud_mixin import FastCrudMixin
|
||||
from dvadmin.utils.field_permission import FieldPermissionMixin
|
||||
from dvadmin.utils.json_response import SuccessResponse, DetailResponse
|
||||
@@ -38,6 +39,7 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
menu = MenuSerializer(many=True, read_only=True)
|
||||
dept = DeptSerializer(many=True, read_only=True)
|
||||
permission = MenuButtonSerializer(many=True, read_only=True)
|
||||
key = serializers.CharField(max_length=50,
|
||||
validators=[CustomUniqueValidator(queryset=Role.objects.all(), message="权限字符必须唯一")])
|
||||
name = serializers.CharField(max_length=50, validators=[CustomUniqueValidator(queryset=Role.objects.all())])
|
||||
@@ -61,11 +63,21 @@ class MenuPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单的按钮权限
|
||||
"""
|
||||
menuPermission = serializers.SerializerMethodField()
|
||||
|
||||
def get_menuPermission(self, instance):
|
||||
is_superuser = self.request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = MenuButton.objects.filter(menu__id=instance.id)
|
||||
else:
|
||||
menu_permission_id_list = self.request.user.role.values_list('permission', flat=True)
|
||||
queryset = MenuButton.objects.filter(id__in=menu_permission_id_list, menu__id=instance.id)
|
||||
serializer = MenuButtonSerializer(queryset, many=True, read_only=True)
|
||||
return serializer.data
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['id', 'parent', 'name']
|
||||
fields = ['id', 'parent', 'name', 'menuPermission']
|
||||
|
||||
|
||||
class MenuButtonPermissionSerializer(CustomModelSerializer):
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 接口权限管理
|
||||
"""
|
||||
from django.db.models import F, Subquery, OuterRef, Exists
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import RoleApiPermission, Menu, MenuButton, Dept, RoleMenuPermission, Columns
|
||||
from dvadmin.system.views.menu import MenuSerializer
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class RoleApiPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
接口权限-序列化器
|
||||
"""
|
||||
dept_name = serializers.SerializerMethodField(help_text="部门名称")
|
||||
|
||||
def get_dept_name(self, instance):
|
||||
dept_name_list = instance.dept.values_list("name",flat=True)
|
||||
return ",".join(dept_name_list)
|
||||
|
||||
class Meta:
|
||||
model = RoleApiPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
class RoleApiPermissionCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化接口权限-序列化器
|
||||
"""
|
||||
menu_button__name = serializers.CharField(source='menu_button.name', read_only=True)
|
||||
menu_button__value = serializers.CharField(source='menu_button.value', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RoleApiPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class RoleButtonPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
角色按钮权限
|
||||
"""
|
||||
isCheck = serializers.SerializerMethodField()
|
||||
data_range = serializers.SerializerMethodField()
|
||||
|
||||
def get_isCheck(self, instance):
|
||||
params = self.request.query_params
|
||||
return RoleApiPermission.objects.filter(
|
||||
menu_button__id=instance['id'],
|
||||
role__id=params.get('role'),
|
||||
).exists()
|
||||
|
||||
def get_data_range(self, instance):
|
||||
params = self.request.query_params
|
||||
obj = RoleApiPermission.objects.filter(
|
||||
menu_button__id=instance['id'],
|
||||
role__id=params.get('role'),
|
||||
).first()
|
||||
if obj is None:
|
||||
return None
|
||||
return obj.data_range
|
||||
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id','name','value','isCheck','data_range']
|
||||
|
||||
class RoleColumnsSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Columns
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class RoleMenuPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单和按钮权限
|
||||
"""
|
||||
isCheck = serializers.SerializerMethodField()
|
||||
btns = serializers.SerializerMethodField()
|
||||
columns = serializers.SerializerMethodField()
|
||||
|
||||
def get_isCheck(self, instance):
|
||||
params = self.request.query_params
|
||||
return RoleMenuPermission.objects.filter(
|
||||
menu__id=instance['id'],
|
||||
role__id=params.get('role'),
|
||||
).exists()
|
||||
|
||||
def get_btns(self, instance):
|
||||
btn_list = MenuButton.objects.filter(menu__id=instance['id']).values('id', 'name', 'value')
|
||||
serializer = RoleButtonPermissionSerializer(btn_list,many=True,request=self.request)
|
||||
return serializer.data
|
||||
|
||||
def get_columns(self, instance):
|
||||
params = self.request.query_params
|
||||
col_list = Columns.objects.filter(role__id=params.get('role'))
|
||||
serializer = RoleColumnsSerializer(col_list,many=True,request=self.request)
|
||||
return serializer.data
|
||||
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['id','name','isCheck','btns','columns']
|
||||
|
||||
class RoleApiPermissionViewSet(CustomModelViewSet):
|
||||
"""
|
||||
接口权限接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = RoleApiPermission.objects.all()
|
||||
serializer_class = RoleApiPermissionSerializer
|
||||
create_serializer_class = RoleApiPermissionCreateUpdateSerializer
|
||||
update_serializer_class = RoleApiPermissionCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def data_scope(self, request):
|
||||
"""
|
||||
获取数据权限范围:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
data = [
|
||||
{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"label": '本部门及以下数据权限'
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": '本部门数据权限'
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": '全部数据权限'
|
||||
},
|
||||
{
|
||||
"value": 4,
|
||||
"label": '自定义数据权限'
|
||||
}
|
||||
]
|
||||
return DetailResponse(data=data)
|
||||
else:
|
||||
data = []
|
||||
role_list = request.user.role.values_list('id', flat=True)
|
||||
if params := request.query_params:
|
||||
if menu_button_id := params.get('menu_button', None):
|
||||
role_queryset = RoleApiPermission.objects.filter(
|
||||
role__in=role_list, menu_button__id=menu_button_id
|
||||
).values_list('data_range', flat=True)
|
||||
data_range_list = list(set(role_queryset))
|
||||
for item in data_range_list:
|
||||
if item == 0:
|
||||
data = [{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
}]
|
||||
elif item == 1:
|
||||
data = [{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
}, {
|
||||
"value": 1,
|
||||
"label": '本部门及以下数据权限'
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": '本部门数据权限'
|
||||
}]
|
||||
elif item == 2:
|
||||
data = [{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": '本部门数据权限'
|
||||
}]
|
||||
elif item == 3:
|
||||
data = [{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": '全部数据权限'
|
||||
}, ]
|
||||
elif item == 4:
|
||||
data = [{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
},
|
||||
{
|
||||
"value": 4,
|
||||
"label": '自定义数据权限'
|
||||
}]
|
||||
else:
|
||||
data = []
|
||||
return DetailResponse(data=data)
|
||||
return ErrorResponse(msg="参数错误")
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def role_to_dept_all(self, request):
|
||||
"""
|
||||
当前用户角色下所能授权的部门:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
params = request.query_params
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = Dept.objects.values('id', 'name', 'parent')
|
||||
else:
|
||||
if not params:
|
||||
return ErrorResponse(msg="参数错误")
|
||||
menu_button = params.get('menu_button')
|
||||
if menu_button is None:
|
||||
return ErrorResponse(msg="参数错误")
|
||||
role_list = request.user.role.values_list('id', flat=True)
|
||||
dept_ids = RoleApiPermission.objects.filter(role__in=role_list).values_list('dept__id',flat=True)
|
||||
queryset = Dept.objects.filter(id__in=dept_ids).values('id', 'name', 'parent')
|
||||
return DetailResponse(data=queryset)
|
||||
@@ -58,38 +58,8 @@ class RoleMenuPermissionViewSet(CustomModelViewSet):
|
||||
update_serializer_class = RoleMenuPermissionCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
@action(methods=['get'],detail=False)
|
||||
def menu_permission_tree(self,request):
|
||||
"""
|
||||
获取菜单按钮树
|
||||
"""
|
||||
# params = request.query_params
|
||||
# role_id = params.get('role',None)
|
||||
# if role_id is None:
|
||||
# return ErrorResponse(msg="未获取到角色")
|
||||
if request.user.is_superuser:
|
||||
queryset = Menu.objects.filter(status=1).values("id", "name", "parent_id")
|
||||
else:
|
||||
role_id = request.user.role.values_list('id', flat=True)
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('menu_id', flat=True)
|
||||
queryset = Menu.objects.filter(status=1, id__in=menu_list).values('id','name', "parent_id").all()
|
||||
return DetailResponse(data=queryset)
|
||||
|
||||
@action(methods=['get'],detail=False)
|
||||
def get_menu_permission_checked(self,request):
|
||||
"""
|
||||
获取已授权的菜单ID
|
||||
"""
|
||||
params = request.query_params
|
||||
role_id = params.get('role',None)
|
||||
if role_id is None:
|
||||
return ErrorResponse(msg="未获取到角色")
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('menu_id', flat=True)
|
||||
queryset = Menu.objects.filter(status=1, id__in=menu_list).values_list('id',flat=True)
|
||||
return DetailResponse(data=queryset)
|
||||
|
||||
@action(methods=['post'],detail=False)
|
||||
def save_menu_permission(self,request):
|
||||
def save_auth(self,request):
|
||||
"""
|
||||
保存页面菜单授权
|
||||
:param request:
|
||||
|
||||
400
backend/dvadmin/system/views/role_menu_button_permission.py
Normal file
400
backend/dvadmin/system/views/role_menu_button_permission.py
Normal file
@@ -0,0 +1,400 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 菜单按钮管理
|
||||
"""
|
||||
from django.db.models import F, Subquery, OuterRef, Exists
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, FieldPermission, \
|
||||
MenuField
|
||||
from dvadmin.system.views.menu import MenuSerializer
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单按钮-序列化器
|
||||
"""
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
menu_button__name = serializers.CharField(source='menu_button.name', read_only=True)
|
||||
menu_button__value = serializers.CharField(source='menu_button.value', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class RoleButtonPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
角色按钮权限
|
||||
"""
|
||||
isCheck = serializers.SerializerMethodField()
|
||||
data_range = serializers.SerializerMethodField()
|
||||
|
||||
def get_isCheck(self, instance):
|
||||
params = self.request.query_params
|
||||
return RoleMenuButtonPermission.objects.filter(
|
||||
menu_button__id=instance['id'],
|
||||
role__id=params.get('role'),
|
||||
).exists()
|
||||
|
||||
def get_data_range(self, instance):
|
||||
params = self.request.query_params
|
||||
obj = RoleMenuButtonPermission.objects.filter(
|
||||
menu_button__id=instance['id'],
|
||||
role__id=params.get('role'),
|
||||
).first()
|
||||
if obj is None:
|
||||
return None
|
||||
return obj.data_range
|
||||
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id','name','value','isCheck','data_range']
|
||||
|
||||
class RoleFieldPermissionSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = FieldPermission
|
||||
fields = "__all__"
|
||||
|
||||
class RoleMenuFieldSerializer(CustomModelSerializer):
|
||||
is_query = serializers.SerializerMethodField()
|
||||
is_create = serializers.SerializerMethodField()
|
||||
is_update = serializers.SerializerMethodField()
|
||||
|
||||
def get_is_query(self, instance):
|
||||
params = self.request.query_params
|
||||
queryset = instance.menu_field.filter(role=params.get('role')).first()
|
||||
if queryset:
|
||||
return queryset.is_query
|
||||
return False
|
||||
|
||||
def get_is_create(self, instance):
|
||||
params = self.request.query_params
|
||||
queryset = instance.menu_field.filter(role=params.get('role')).first()
|
||||
if queryset:
|
||||
return queryset.is_create
|
||||
return False
|
||||
|
||||
def get_is_update(self, instance):
|
||||
params = self.request.query_params
|
||||
queryset = instance.menu_field.filter(role=params.get('role')).first()
|
||||
if queryset:
|
||||
return queryset.is_update
|
||||
return False
|
||||
class Meta:
|
||||
model = MenuField
|
||||
fields = ['id','field_name','title','is_query','is_create','is_update']
|
||||
|
||||
|
||||
class RoleMenuPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单和按钮权限
|
||||
"""
|
||||
name = serializers.SerializerMethodField()
|
||||
isCheck = serializers.SerializerMethodField()
|
||||
btns = serializers.SerializerMethodField()
|
||||
columns = serializers.SerializerMethodField()
|
||||
|
||||
def get_name(self, instance):
|
||||
parent_list = Menu.get_all_parent(instance['id'])
|
||||
names = [d["name"] for d in parent_list]
|
||||
return "/".join(names)
|
||||
def get_isCheck(self, instance):
|
||||
params = self.request.query_params
|
||||
return RoleMenuPermission.objects.filter(
|
||||
menu__id=instance['id'],
|
||||
role__id=params.get('role'),
|
||||
).exists()
|
||||
|
||||
def get_btns(self, instance):
|
||||
btn_list = MenuButton.objects.filter(menu__id=instance['id']).values('id', 'name', 'value')
|
||||
serializer = RoleButtonPermissionSerializer(btn_list,many=True,request=self.request)
|
||||
return serializer.data
|
||||
|
||||
def get_columns(self, instance):
|
||||
col_list = MenuField.objects.filter(menu=instance['id'])
|
||||
serializer = RoleMenuFieldSerializer(col_list,many=True,request=self.request)
|
||||
return serializer.data
|
||||
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['id','name','isCheck','btns','columns']
|
||||
|
||||
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
"""
|
||||
菜单按钮接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = RoleMenuButtonPermission.objects.all()
|
||||
serializer_class = RoleMenuButtonPermissionSerializer
|
||||
create_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
|
||||
update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def get_role_premission(self, request):
|
||||
"""
|
||||
角色授权获取:
|
||||
:param request: role
|
||||
:return: menu,btns,columns
|
||||
"""
|
||||
params = request.query_params
|
||||
role = params.get('role',None)
|
||||
if role is None:
|
||||
return ErrorResponse(msg="未获取到角色信息")
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all()
|
||||
else:
|
||||
role_id = request.user.role.values_list('id', flat=True)
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id',flat=True)
|
||||
queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id').all()
|
||||
serializer = RoleMenuPermissionSerializer(queryset,many=True,request=request)
|
||||
data = serializer.data
|
||||
return DetailResponse(data=data)
|
||||
|
||||
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
|
||||
def set_role_premission(self,request,pk):
|
||||
"""
|
||||
对角色的菜单和按钮及按钮范围授权:
|
||||
:param request:
|
||||
:param pk: role
|
||||
:return:
|
||||
"""
|
||||
body = request.data
|
||||
RoleMenuPermission.objects.filter(role=pk).delete()
|
||||
RoleMenuButtonPermission.objects.filter(role=pk).delete()
|
||||
for menu in body:
|
||||
if menu.get('isCheck'):
|
||||
menu_parent = Menu.get_all_parent(menu.get('id'))
|
||||
role_menu_permission_list = []
|
||||
for d in menu_parent:
|
||||
role_menu_permission_list.append(RoleMenuPermission(role_id=pk, menu_id=d["id"]))
|
||||
RoleMenuPermission.objects.bulk_create(role_menu_permission_list)
|
||||
# RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
|
||||
for btn in menu.get('btns'):
|
||||
if btn.get('isCheck'):
|
||||
data_range = btn.get('data_range',0) or 0
|
||||
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),data_range=data_range)
|
||||
instance.dept.set(btn.get('dept',[]))
|
||||
for col in menu.get('columns'):
|
||||
FieldPermission.objects.update_or_create(role_id=pk,field_id=col.get('id'),is_query=col.get('is_query'),is_create=col.get('is_create'),is_update=col.get('is_update'))
|
||||
return DetailResponse(msg="授权成功")
|
||||
|
||||
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def role_menu_get_button(self, request):
|
||||
"""
|
||||
当前用户角色和菜单获取可下拉选项的按钮:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
if params := request.query_params:
|
||||
if menu_id := params.get('menu', None):
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = MenuButton.objects.filter(menu=menu_id).values('id', 'name')
|
||||
else:
|
||||
role_list = request.user.role.values_list('id', flat=True)
|
||||
queryset = RoleMenuButtonPermission.objects.filter(
|
||||
role__in=role_list, menu_button__menu=menu_id
|
||||
).values(btn_id=F('menu_button__id'), name=F('menu_button__name'))
|
||||
return DetailResponse(data=queryset)
|
||||
return ErrorResponse(msg="参数错误")
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def data_scope(self, request):
|
||||
"""
|
||||
获取数据权限范围:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
data = [
|
||||
{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"label": '本部门及以下数据权限'
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": '本部门数据权限'
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": '全部数据权限'
|
||||
},
|
||||
{
|
||||
"value": 4,
|
||||
"label": '自定义数据权限'
|
||||
}
|
||||
]
|
||||
return DetailResponse(data=data)
|
||||
else:
|
||||
data = []
|
||||
role_list = request.user.role.values_list('id', flat=True)
|
||||
if params := request.query_params:
|
||||
if menu_button_id := params.get('menu_button', None):
|
||||
role_queryset = RoleMenuButtonPermission.objects.filter(
|
||||
role__in=role_list, menu_button__id=menu_button_id
|
||||
).values_list('data_range', flat=True)
|
||||
data_range_list = list(set(role_queryset))
|
||||
for item in data_range_list:
|
||||
if item == 0:
|
||||
data = [{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
}]
|
||||
elif item == 1:
|
||||
data = [{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
}, {
|
||||
"value": 1,
|
||||
"label": '本部门及以下数据权限'
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": '本部门数据权限'
|
||||
}]
|
||||
elif item == 2:
|
||||
data = [{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": '本部门数据权限'
|
||||
}]
|
||||
elif item == 3:
|
||||
data = [{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": '全部数据权限'
|
||||
}, ]
|
||||
elif item == 4:
|
||||
data = [{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
},
|
||||
{
|
||||
"value": 4,
|
||||
"label": '自定义数据权限'
|
||||
}]
|
||||
else:
|
||||
data = []
|
||||
return DetailResponse(data=data)
|
||||
return ErrorResponse(msg="参数错误")
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def role_to_dept_all(self, request):
|
||||
"""
|
||||
当前用户角色下所能授权的部门:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
params = request.query_params
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = Dept.objects.values('id', 'name', 'parent')
|
||||
else:
|
||||
if not params:
|
||||
return ErrorResponse(msg="参数错误")
|
||||
menu_button = params.get('menu_button')
|
||||
if menu_button is None:
|
||||
return ErrorResponse(msg="参数错误")
|
||||
role_list = request.user.role.values_list('id', flat=True)
|
||||
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list, menu_button=None).values(
|
||||
dept_id=F('dept__id'),
|
||||
name=F('dept__name'),
|
||||
parent=F('dept__parent')
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def menu_to_button(self, request):
|
||||
"""
|
||||
根据所选择菜单获取已配置的按钮/接口权限:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
params = request.query_params
|
||||
menu_id = params.get('menu', None)
|
||||
if menu_id is None:
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = RoleMenuButtonPermission.objects.filter(menu_button__menu=menu_id).values(
|
||||
'id',
|
||||
'data_range',
|
||||
'menu_button',
|
||||
'menu_button__name',
|
||||
'menu_button__value'
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
else:
|
||||
if params:
|
||||
|
||||
role_id = params.get('role', None)
|
||||
if role_id is None:
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
queryset = RoleMenuButtonPermission.objects.filter(role=role_id, menu_button__menu=menu_id).values(
|
||||
'id',
|
||||
'data_range',
|
||||
'menu_button',
|
||||
'menu_button__name',
|
||||
'menu_button__value'
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def role_to_menu(self, request):
|
||||
"""
|
||||
获取角色对应的按钮权限
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
params = request.query_params
|
||||
role_id = params.get('role', None)
|
||||
if role_id is None:
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
queryset = RoleMenuPermission.objects.filter(role_id=role_id).values_list('menu_id', flat=True).distinct()
|
||||
|
||||
return DetailResponse(data=queryset)
|
||||
@@ -315,7 +315,7 @@ class UserViewSet(CustomModelViewSet):
|
||||
serializer.save()
|
||||
return DetailResponse(data=None, msg="修改成功")
|
||||
|
||||
@action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
|
||||
@action(methods=["PUT"], detail=False, permission_classes=[IsAuthenticated])
|
||||
def change_password(self, request, *args, **kwargs):
|
||||
"""密码修改"""
|
||||
data = request.data
|
||||
@@ -330,7 +330,7 @@ class UserViewSet(CustomModelViewSet):
|
||||
if not verify_password:
|
||||
verify_password = check_password(hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest(), self.request.user.password)
|
||||
if verify_password:
|
||||
request.user.password = make_password(new_pwd)
|
||||
request.user.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest())
|
||||
request.user.save()
|
||||
return DetailResponse(data=None, msg="修改成功")
|
||||
else:
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db.models import F
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Columns
|
||||
from dvadmin.system.models import FieldPermission, MenuField
|
||||
from dvadmin.utils.json_response import DetailResponse
|
||||
from dvadmin.utils.models import get_custom_app_models
|
||||
|
||||
@@ -14,25 +15,24 @@ class FieldPermissionMixin:
|
||||
获取字段权限
|
||||
"""
|
||||
finded = False
|
||||
for app in get_custom_app_models():
|
||||
for model in app:
|
||||
if model['object'] is self.serializer_class.Meta.model:
|
||||
finded = True
|
||||
break
|
||||
for model in get_custom_app_models():
|
||||
if model['object'] is self.serializer_class.Meta.model:
|
||||
finded = True
|
||||
break
|
||||
if finded:
|
||||
break
|
||||
if finded is False:
|
||||
return []
|
||||
roles = request.user.role.values_list('id', flat=True)
|
||||
user = request.user
|
||||
if user.is_superuser==1:
|
||||
data = Columns.objects.filter(app=model['app'], model=model['model']).values('field_name', 'is_create', 'is_query', 'is_update')
|
||||
data = MenuField.objects.filter( model=model['model']).values('field_name')
|
||||
for item in data:
|
||||
item['is_create'] = True
|
||||
item['is_query'] = True
|
||||
item['is_update'] = True
|
||||
else:
|
||||
data= Columns.objects.filter(
|
||||
app=model['app'], model=model['model'],role__in=roles
|
||||
).values('field_name', 'is_create', 'is_query', 'is_update')
|
||||
roles = request.user.role.values_list('id', flat=True)
|
||||
data= FieldPermission.objects.filter(
|
||||
field__model=model['model'],role__in=roles
|
||||
).values( 'is_create', 'is_query', 'is_update',field_name=F('field__field_name'))
|
||||
return DetailResponse(data=data)
|
||||
@@ -20,7 +20,7 @@ from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters.utils import get_model_field
|
||||
from rest_framework.filters import BaseFilterBackend
|
||||
|
||||
from dvadmin.system.models import Dept, ApiWhiteList, RoleApiPermission
|
||||
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission
|
||||
|
||||
|
||||
def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
|
||||
@@ -116,12 +116,12 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
||||
if _pk: # 判断是否是单例查询
|
||||
re_api = re.sub(_pk,'{id}', api)
|
||||
role_id_list = request.user.role.values_list('id', flat=True)
|
||||
role_permission_list=RoleApiPermission.objects.filter(
|
||||
role_permission_list=RoleMenuButtonPermission.objects.filter(
|
||||
role__in=role_id_list,
|
||||
role__status=1,
|
||||
api=re_api,
|
||||
method=method).values(
|
||||
'data_range',
|
||||
menu_button__api=re_api,
|
||||
menu_button__method=method).values(
|
||||
'data_range'
|
||||
)
|
||||
dataScope_list = [] # 权限范围列表
|
||||
for ele in role_permission_list:
|
||||
@@ -150,10 +150,14 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
||||
elif ele == 2:
|
||||
dept_list.append(user_dept_id)
|
||||
elif ele == 4:
|
||||
dept_ids = RoleMenuButtonPermission.objects.filter(
|
||||
role__in=role_id_list,
|
||||
role__status=1,
|
||||
data_range=4).values_list(
|
||||
'dept__id',flat=True
|
||||
)
|
||||
dept_list.extend(
|
||||
request.user.role.filter(status=1).values_list(
|
||||
"dept__id", flat=True
|
||||
)
|
||||
dept_ids
|
||||
)
|
||||
if queryset.model._meta.model_name == 'dept':
|
||||
return queryset.filter(id__in=list(set(dept_list)))
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
日志 django中间件
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.http import HttpResponse, HttpResponseServerError
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
from dvadmin.system.models import OperationLog
|
||||
@@ -87,3 +89,58 @@ class ApiLoggingMiddleware(MiddlewareMixin):
|
||||
if self.methods == 'ALL' or request.method in self.methods:
|
||||
self.__handle_response(request, response)
|
||||
return response
|
||||
|
||||
logger = logging.getLogger("healthz")
|
||||
class HealthCheckMiddleware(object):
|
||||
"""
|
||||
存活检查中间件
|
||||
"""
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
# One-time configuration and initialization.
|
||||
|
||||
def __call__(self, request):
|
||||
if request.method == "GET":
|
||||
if request.path == "/readiness":
|
||||
return self.readiness(request)
|
||||
elif request.path == "/healthz":
|
||||
return self.healthz(request)
|
||||
return self.get_response(request)
|
||||
|
||||
def healthz(self, request):
|
||||
"""
|
||||
Returns that the server is alive.
|
||||
"""
|
||||
return HttpResponse("OK")
|
||||
|
||||
def readiness(self, request):
|
||||
# Connect to each database and do a generic standard SQL query
|
||||
# that doesn't write any data and doesn't depend on any tables
|
||||
# being present.
|
||||
try:
|
||||
from django.db import connections
|
||||
for name in connections:
|
||||
cursor = connections[name].cursor()
|
||||
cursor.execute("SELECT 1;")
|
||||
row = cursor.fetchone()
|
||||
if row is None:
|
||||
return HttpResponseServerError("db: invalid response")
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return HttpResponseServerError("db: cannot connect to database.")
|
||||
|
||||
# Call get_stats() to connect to each memcached instance and get it's stats.
|
||||
# This can effectively check if each is online.
|
||||
try:
|
||||
from django.core.cache import caches
|
||||
from django.core.cache.backends.memcached import BaseMemcachedCache
|
||||
for cache in caches.all():
|
||||
if isinstance(cache, BaseMemcachedCache):
|
||||
stats = cache._cache.get_stats()
|
||||
if len(stats) != len(cache._servers):
|
||||
return HttpResponseServerError("cache: cannot connect to cache.")
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
return HttpResponseServerError("cache: cannot connect to cache.")
|
||||
|
||||
return HttpResponse("OK")
|
||||
|
||||
@@ -13,6 +13,7 @@ from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
from application import settings
|
||||
|
||||
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
||||
|
||||
|
||||
@@ -71,10 +72,13 @@ class CoreModel(models.Model):
|
||||
id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
|
||||
description = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
|
||||
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
|
||||
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False)
|
||||
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL,
|
||||
db_constraint=False)
|
||||
modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人")
|
||||
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True, verbose_name="数据归属部门")
|
||||
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
|
||||
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True,
|
||||
verbose_name="数据归属部门")
|
||||
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间",
|
||||
verbose_name="修改时间")
|
||||
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
|
||||
verbose_name="创建时间")
|
||||
|
||||
@@ -136,10 +140,23 @@ def get_model_from_app(app_name):
|
||||
|
||||
|
||||
def get_custom_app_models(app_name=None):
|
||||
"""获取所有项目写的app里的models"""
|
||||
"""
|
||||
获取所有项目下的app里的models
|
||||
"""
|
||||
if app_name:
|
||||
return get_model_from_app(app_name)
|
||||
all_apps = apps.get_app_configs()
|
||||
res = []
|
||||
for app in settings.CUSTOM_APPS:
|
||||
res.append(get_model_from_app(app))
|
||||
for app in all_apps:
|
||||
if app.name.startswith('django'):
|
||||
continue
|
||||
if app.name in settings.COLUMN_EXCLUDE_APPS:
|
||||
continue
|
||||
try:
|
||||
all_models = get_model_from_app(app.name)
|
||||
if all_models:
|
||||
for model in all_models:
|
||||
res.append(model)
|
||||
except Exception as e:
|
||||
pass
|
||||
return res
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.contrib.auth.models import AnonymousUser
|
||||
from django.db.models import F
|
||||
from rest_framework.permissions import BasePermission
|
||||
|
||||
from dvadmin.system.models import ApiWhiteList, RoleApiPermission
|
||||
from dvadmin.system.models import ApiWhiteList, RoleMenuButtonPermission
|
||||
|
||||
|
||||
def ValidationApi(reqApi, validApi):
|
||||
@@ -74,18 +74,18 @@ class CustomPermission(BasePermission):
|
||||
methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH']
|
||||
method = methodList.index(method)
|
||||
# ***接口白名单***
|
||||
api_white_list = ApiWhiteList.objects.values('method',api=F('url'))
|
||||
api_white_list = ApiWhiteList.objects.values(permission__api=F('url'), permission__method=F('method'))
|
||||
api_white_list = [
|
||||
str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('method')) + '$' for item in api_white_list if item.get('api')]
|
||||
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('permission__method')) + '$' for item in api_white_list if item.get('permission__api')]
|
||||
# ********#
|
||||
if not hasattr(request.user, "role"):
|
||||
return False
|
||||
role_id_list = request.user.role.values_list('id',flat=True)
|
||||
userApiList = RoleApiPermission.objects.filter(role__in=role_id_list).values('api','method') # 获取当前用户的角色拥有的所有接口
|
||||
userApiList = RoleMenuButtonPermission.objects.filter(role__in=role_id_list).values(permission__api=F('menu_button__api'), permission__method=F('menu_button__method')) # 获取当前用户的角色拥有的所有接口
|
||||
ApiList = [
|
||||
str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('method')) + '$' for item in userApiList if item.get('api')]
|
||||
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('permission__method')) + '$' for item in userApiList if item.get('permission__api')]
|
||||
new_api_ist = api_white_list + ApiList
|
||||
new_api = api + ":" + str(method)
|
||||
for item in new_api_ist:
|
||||
|
||||
@@ -26,6 +26,7 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
|
||||
# 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖
|
||||
modifier_field_id = "modifier"
|
||||
modifier_name = serializers.SerializerMethodField(read_only=True)
|
||||
dept_belong_id = serializers.IntegerField(required=False, default=None)
|
||||
|
||||
def get_modifier_name(self, instance):
|
||||
if not hasattr(instance, "modifier"):
|
||||
|
||||
@@ -17,7 +17,7 @@ from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSeria
|
||||
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
|
||||
from dvadmin.utils.permission import CustomPermission
|
||||
from dvadmin.utils.models import get_custom_app_models
|
||||
from dvadmin.system.models import Columns
|
||||
from dvadmin.system.models import FieldPermission, MenuField
|
||||
from django_restql.mixins import QueryArgumentsMixin
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
serializer_class = self.get_serializer_class()
|
||||
kwargs.setdefault('context', self.get_serializer_context())
|
||||
# 全部以可见字段为准
|
||||
can_see = self.get_column_permission(serializer_class)
|
||||
can_see = self.get_menu_field(serializer_class)
|
||||
# 排除掉序列化器级的字段
|
||||
# sub_set = set(serializer_class._declared_fields.keys()) - set(can_see)
|
||||
# for field in sub_set:
|
||||
@@ -79,21 +79,17 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
else:
|
||||
return serializer_class(*args, **kwargs)
|
||||
|
||||
def get_column_permission(self, serializer_class):
|
||||
"""获取列权限"""
|
||||
def get_menu_field(self, serializer_class):
|
||||
"""获取字段权限"""
|
||||
finded = False
|
||||
for app in get_custom_app_models():
|
||||
for model in app:
|
||||
if model['object'] is serializer_class.Meta.model:
|
||||
finded = True
|
||||
break
|
||||
if finded:
|
||||
for model in get_custom_app_models():
|
||||
if model['object'] is serializer_class.Meta.model:
|
||||
finded = True
|
||||
break
|
||||
if finded is False:
|
||||
return []
|
||||
return Columns.objects.filter(
|
||||
app=model['app'], model=model['model']
|
||||
).values('field_name', 'is_create', 'is_query', 'is_update')
|
||||
return MenuField.objects.filter(model=model['model']
|
||||
).values('field_name', 'title')
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data, request=request)
|
||||
|
||||
0
backend/logs/__init__.py
Normal file
0
backend/logs/__init__.py
Normal file
@@ -1,4 +1,4 @@
|
||||
Django==4.2.6
|
||||
Django==4.2.7
|
||||
django-comment-migrate==0.1.7
|
||||
django-cors-headers==4.3.0
|
||||
django-filter==23.3
|
||||
@@ -17,7 +17,7 @@ openpyxl==3.1.2
|
||||
requests==2.31.0
|
||||
typing-extensions==4.8.0
|
||||
tzlocal==5.1
|
||||
channels==4.0.0
|
||||
channels==3.0.5
|
||||
channels-redis==4.1.0
|
||||
websockets==11.0.3
|
||||
user-agents==2.2.0
|
||||
|
||||
34
backend/static/clause/privacy.css
Normal file
34
backend/static/clause/privacy.css
Normal file
@@ -0,0 +1,34 @@
|
||||
html,body{width:100%;overflow-x: hidden;margin: 0;padding: 0;font-size:1rem;font-family: "PingFang SC","SF Pro SC","SF Pro Text","SF Pro Icons","Helvetica Neue","Helvetica","Arial",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;}
|
||||
a{text-decoration:none;}
|
||||
ul,li{list-style:none;padding:0;margin:0;}
|
||||
.bg{background-repeat:no-repeat;background-position:center center;background-size:100%;}
|
||||
.center{margin:0 auto;position:relative;}
|
||||
.abs_center{position:absolute;left: 50%;transform: translateX(-50%);-webkit-transform:translateX(-50%);}
|
||||
/*滚动条统一样式*/
|
||||
.scrollbar{overflow-x:hidden;scrollbar-track-color:none;}
|
||||
.scrollbar::-webkit-scrollbar{width:8px;background-color:transparent;} /*滚动条整体部分*/
|
||||
.scrollbar::-webkit-scrollbar-track{border-radius:4px;-webkit-box-shadow: inset 0 0 0 rgba(0,0,0,0);background-color:transparent;display:none;} /* 滚动条的轨道*/
|
||||
.scrollbar::-webkit-scrollbar-track-piece{background-color:transparent;display:none;}/* 滚动条的内层轨道*/
|
||||
.scrollbar::-webkit-scrollbar-thumb{border-radius:4px;background: #313131;}/*滚动条里面的小方块,能向上向下移动(或往左往右移动,取决于是垂直滚动条还是水平滚动条)*/
|
||||
|
||||
h3,h4,h5{ font-weight: bold;}
|
||||
h3{ font-size: .4rem; color:#000;}
|
||||
h4{ font-size: .35rem; color: #000; margin:.2rem 0 0 0;}
|
||||
p{ line-height: .5rem; margin:.2rem 0;word-break: break-all;text-align:justify;}
|
||||
.app .main{width:90%;overflow:hidden;font-size:.30rem;box-sizing: border-box; margin: 0 auto; color: #484d57;}
|
||||
.app ul { padding-left: .1rem; font-size: .26rem;}
|
||||
.app ul p{ line-height: .4rem;}
|
||||
.app .mainbg{position:absolute;left:50%;transform:translateX(-50%);background-repeat:no-repeat;background-size:100%;}
|
||||
|
||||
.app .poster{width:100%;height:31.85rem;background-image:url(../images/poster_bg.jpg);}
|
||||
|
||||
.pc .main{font-size:.16rem;}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
233
backend/templates/privacy.html
Normal file
233
backend/templates/privacy.html
Normal file
@@ -0,0 +1,233 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-dpr="1" style="font-size: 50px;">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
|
||||
<title>隐私政策</title>
|
||||
<link href="/static/clause/privacy.css" rel="stylesheet">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||
</head>
|
||||
|
||||
<body class="app" style="font-size: 0.28rem;">
|
||||
<div class="main">
|
||||
<h3>隐私政策</h3>
|
||||
<br>更新日期:2024年01月01日
|
||||
<br>生效日期:2024年01月01日
|
||||
<br>
|
||||
<br>欢迎您使用DvAdmin产品。DvAdmin非常重视用户的个人信息和隐私保护,为同时给您提供更准确、有个性化的服务和更安全的互联网环境,我们依据《中华人民共和国网络安全法》、《信息安全技术个人信息安全规范》(GB/T35273-2017)以及其他相关法律法规和技术规范制定了《隐私政策》,阐述了关于您个人信息的相关权利等。
|
||||
<br>
|
||||
<br>本政策适用于我们的所有DvAdmin系列产品及服务,本政策与您所使用的我们的产品与/或服务息息相关,您在下载、安装、启动、浏览、注册、登录、使用巨梦科技的产品及/或服务时,我们将按照本政策的约定处理和保护您的个人信息。我们将尽量以简明、扼要的表述向您解释本政策所涉及的技术词汇,以便于您理解。
|
||||
<br>
|
||||
<br>您在使用DvAdmin的产品及/或服务时,巨梦科技可能会收集和使用您的相关信息。为此,希望通过本《隐私政策》向您说明,巨梦科技如何取得、使用、保存和管理这些信息。请在使用/继续使用我们的各项产品与服务前,仔细阅读并充分理解本政策,特别应重点阅读我们以粗体/粗体下划线标识的条款,并在需要时,按照本政策的指引,作出适当的选择。如果您不同意本政策的内容,将可能导致我们的产品与/或服务无法正常运行,或者无法达到我们拟达到的服务效果,您应立即停止访问/使用我们的产品与/或服务。您使用或继续使用我们提供的产品与/或服务的行为,都表示您充分理解和同意本《隐私政策》的全部内容。
|
||||
<br>
|
||||
<br>本《隐私政策》将帮助您了解以下内容:
|
||||
<br><a href="#1_chapter">第一章 如何收集您的个人信息</a>
|
||||
<br><a href="#2_chapter">第二章 如何使用您的个人信息</a>
|
||||
<br><a href="#3_chapter">第三章 如何使用Cookie和同类技术</a>
|
||||
<br><a href="#4_chapter">第四章 如何共享、转让、公开披露您的个人信息</a>
|
||||
<br><a href="#5_chapter">第五章 如何保护您的个人信息</a>
|
||||
<br><a href="#6_chapter">第六章 如何保存您的个人信息</a>
|
||||
<br><a href="#7_chapter">第七章 管理、查看或删除您的个人信息</a>
|
||||
<br><a href="#8_chapter">第八章 如何处理儿童的个人信息</a>
|
||||
<br><a href="#9_chapter">第九章 本政策如何更新</a>
|
||||
<br><a href="#10_chapter">第十章 投诉及建议</a>
|
||||
<br><a href="#11_chapter">第十一章 其他</a>
|
||||
<br>
|
||||
<h1 id="1_chapter">第一章 如何收集您的个人信息</h1>
|
||||
<br>
|
||||
<br>在您使用我们的产品/服务时,我们需要/可能需要收集和使用的您的个人信息包括如下两种:
|
||||
<br>第一种:为实现向您提供我们产品及/或服务的基本功能,您须授权我们收集、使用的必要的信息。如您拒绝提供相应信息,您将无法正常使用我们的产品及/或服务;
|
||||
<br>第二种:为实现向您提供我们产品及/或服务的附加功能,您可选择单独同意或不同意我们收集、使用的信息。如您拒绝提供,您将无法正常使用相关附加功能或无法达到我们拟达到的功能效果,不会影响您使用我们的基本功能。
|
||||
<br>
|
||||
<br>个人敏感信息是指一旦泄露、非法提供或滥用可能危害人身和财产安全,极易导致个人名誉、身心健康受到损害或歧视性待遇等的个人信息,如个人财产信息、个人健康生理信息、个人生物识别信息、个人身份信息、网络身份标识信息及其他信息。
|
||||
<br>上述信息所包含的内容,均出自于GB/T35273《信息安全技术 个人信息安全规范》。
|
||||
<br>
|
||||
<h3>1.1 您向巨梦科技提供相关个人信息</h3>
|
||||
<h4>1.1.1
|
||||
为使用巨梦科技的产品及/或服务,您首先需要注册账号并登录。您可以选择通过微信、等方式创建巨梦科技产品及/或服务的账号并登录("账号登录")。</h4>
|
||||
<h5>1.1.1.1 通过微信第三方账户登录巨梦科技产品及/或服务时,您同意我们从微信获取您授权共享的信息(包括手机号、位置信息、昵称、头像等),
|
||||
并且您同意将您的第三方账户与您的巨梦科技产品及/或服务账号绑定,使您可以通过第三方账户直接登录并使用巨梦科技的产品及/或服务,并借助该账户实现数据在不同设备之间的同步。
|
||||
我们会依据与第三方的约定,并在符合相关法律法规的前提下,使用您授权共享的个人信息。如果您拒绝同意巨梦科技从第三方获取您授权共享的信息,您将无法通过微信的方式注册账号并登录,也无法借助该第三方账户实现数据的同步。</h5>
|
||||
<h4>1.1.2 在您使用巨梦科技产品及/或服务时,您可能会因账号管理、产品使用等问题联系巨梦科技客服。为此,您可能需要向巨梦科技提供姓名、手机号码、系统操作记录、照片、银行账号、第三方支付平台的账号、
|
||||
与巨梦科技进行联系的通信记录及内容等与您使用巨梦科技产品及/或服务相关的信息,以便巨梦科技的客服人员您联系,并帮助您解决相关问题。
|
||||
如果您拒绝提供相关信息,巨梦科技可能无法帮助您解决相关问题。</h4>
|
||||
<h3>1.2 巨梦科技收集您所使用的相关设备及网络信息</h3>
|
||||
<h4>1.2.1
|
||||
当您使用巨梦科技的产品及/或服务时,巨梦科技将收集您所使用的相关设备信息,以便为您提供持续稳定的服务支持,使您在使用巨梦科技的产品及/或服务过程中获得最优体验。巨梦科技收集的您所使用的相关设备信息包括浏览器信息、网络信息(IP地址)、日志信息(如操作日志、服务日志)等等。</h4>
|
||||
<h3>1.3 巨梦科技向您收集的信息</h3>
|
||||
<h4>1.3.1
|
||||
为提升巨梦科技产品及/或服务的使用体验和便利程度,巨梦科技可能会通过调用设备权限的方式收集您的如下个人信息。如果您不同意提供这些个人信息,您可能无法使用与提升用户体验相关的功能,您也可能需要在使用巨梦科技产品及/或服务过程中手动重复填写一些信息。</h4>
|
||||
<h4>1.3.2
|
||||
若您希望向巨梦科技上传您的头像及/或其他图片信息,您可以选择授权巨梦科技调用您所使用设备中的摄像头或者相册权限。</h4>
|
||||
<h4>1.3.3
|
||||
若您希望快速便捷地通过第三方移动运营商完成支付,在您的设备支持的情况下,您可以选择授权调用您所使用设备中的移动运营相关权限(如短信),以便您快速便捷地完成付款。</h4>
|
||||
<h4>1.3.4
|
||||
若您希望实现或体验上述功能,您可能需要在您的设备中向巨梦科技开启您的相应访问权限。您也可以随时选择关闭相应访问权限以取消相应授权。在不同设备中,权限显示方式及关闭方式可能有所不同,具体请参考设备及系统开发方的说明或指引。</h4>
|
||||
<h3>1.4 第三方向您收集的信息</h3>
|
||||
<h4>1.4.1 第三方付款服务提供商收集相关个人信息</h4>
|
||||
<h5>1.4.1.1
|
||||
您可以通过向巨梦科技付款的方式获取巨梦科技产品及/或服务相关的产品。上述付款服务可能由巨梦科技以外的第三方提供,巨梦科技并不会获取与您付款相关的密码等信息。但可能从您或第三方支付平台获得您的第三方支付账户(微信账号)。若您希望了解第三方服务提供商收集个人信息的具体规则,请查阅您选择的第三方付款服务提供商的隐私政策。</h5>
|
||||
<h3>1.5 您充分知晓,在以下情形下,我们收集、使用个人信息无需征得您的授权同意:</h3>
|
||||
<h4>1.5.1 与国家安全、国防安全有关的;</h4>
|
||||
<h4>1.5.2 与公共安全、公共卫生、重大公共利益有关的;</h4>
|
||||
<h4>1.5.3 与犯罪侦查、起诉、审判和判决执行等有关的;</h4>
|
||||
<h4>1.5.4 出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;</h4>
|
||||
<h4>1.5.5 所收集的个人信息是个人信息主体自行向社会公众公开的;</h4>
|
||||
<h4>1.5.6 从合法公开披露的信息中收集的您的个人信息的,如合法的新闻报道、政府信息公开等渠道;</h4>
|
||||
<h4>1.5.7 根据您要求签订和履行合同所必需的;</h4>
|
||||
<h4>1.5.8 用于维护所提供的产品及/或服务的安全稳定运行所必需的,例如发现、处置产品及/或服务的故障;</h4>
|
||||
<h4>1.5.9 法律法规规定的其他情形。</h4>
|
||||
<h3>1.6
|
||||
根据法律规定,向第三方提供经去标识化处理的个人信息,且确保数据接收方无法复原并重新识别个人信息主体的,不属于个人信息的对外共享、转让及公开披露行为,对此类数据的保存及处理将无需另行向您通知并征得您的同意。</h3>
|
||||
<br>
|
||||
<h1 id="2_chapter">第二章 如何使用您的个人信息</h1>
|
||||
<br>
|
||||
<h2>在收集您的个人信息后,巨梦科技将根据如下规则使用您的个人信息:</h2>
|
||||
<br>
|
||||
<h3>2.1 会根据本《隐私政策》的约定并为实现巨梦科技的产品及/或服务功能对所收集的个人信息进行使用。</h3>
|
||||
<h3>2.2
|
||||
请您注意,对于您在使用巨梦科技的产品及/或服务时所提供的所有个人信息,除非您删除或通过相关设置拒绝我们收集,否则您将在使用产品及/或服务期间、账号注销前持续授权我们使用。</h3>
|
||||
<h3>2.3
|
||||
巨梦科技系列产品或服务对使用情况进行统计,并可能会与公众或第三方共享这些统计信息,以展示巨梦科技产品及/或服务的整体使用趋势。但这些统计信息不会包含您的任何身份识别信息。</h3>
|
||||
<h3>2.4
|
||||
当巨梦科技要将您的个人信息用于本政策未载明的其它用途时,或将基于特定目的收集而来的信息用于其他目的时,巨梦科技会主动事先征求您的明示同意。</h3>
|
||||
<br>
|
||||
<h1 id="3_chapter">第三章 如何使用Cookie和同类技术</h1>
|
||||
<h2>巨梦科技暂未使用任何信息收集工具。</h2>
|
||||
<br>
|
||||
<h1 id="4_chapter">第四章 如何共享、转让、公开披露您的个人信息</h1>
|
||||
<h2>共享</h2>
|
||||
<h3>4.1 巨梦科技将严格遵守相关法律法规,对您的个人信息予以保密。除以下情况外,我们不会向其他人共享您的个人信息:</h3>
|
||||
<h4>4.1.1 事先获得您明确的同意或授权;</h4>
|
||||
<h4>4.1.2 根据适用的法律法规规定,或基于司法或行政主管部门的强制性要求进行提供;</h4>
|
||||
<h4>4.1.3
|
||||
在法律法规允许的范围内,为维护您或其他巨梦科技用户或其他个人的生命、财产等合法权益或是社会公共利益而有必要提供;</h4>
|
||||
<h4>4.1.4 应您的监护人的合法要求而提供您的信息;</h4>
|
||||
<h4>4.1.5 根据与您签署的相关协议(包括在线签署的电子协议以及相应的平台规则)或其他的法律文件约定而提供;</h4>
|
||||
<h4>4.1.6 根据本《隐私政策》所述与第三方进行共享;</h4>
|
||||
<h4>4.1.7
|
||||
巨梦科技可能会基于您的相应授权将您的个人信息与公司的关联方共享。但巨梦科技只会共享必要的个人信息,且受本《隐私政策》所述目的之约束。 如果巨梦科技的关联方要改变个人信息的处理目的,将适时向您征得明示同意。</h4>
|
||||
<h3>4.2
|
||||
对巨梦科技与之共享个人信息的公司、组织和个人,我们将尽合理的努力要求其处理您的个人信息时遵守相关法律法规,尽力要求其采取相关的保密和安全措施,以保障您的个人信息安全。</h3>
|
||||
<h2>转让</h2>
|
||||
<h3>4.3 巨梦科技不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:</h3>
|
||||
<h4>4.3.1 事先获得您明确的同意或授权;</h4>
|
||||
<h4>4.3.2 根据适用的法律法规、法律程序的要求、强制性的行政或司法要求而必须进行提供;</h4>
|
||||
<h4>4.3.3
|
||||
涉及收购、兼并、破产清算、重组等重大变更时,如涉及到个人信息转让的,巨梦科技会要求新的持有您个人信息的公司或组织继续履行本《隐私政策》项下的责任和义务。如变更后的主体需变更个人信息使用目的,我们会要求其事先获得您的明示同意。</h4>
|
||||
<h2>公开披露</h2>
|
||||
<h3>4.4 巨梦科技仅会在以下情况下,且在采取符合业界标准的安全防护措施的前提下,才会公开披露您的个人信息:</h3>
|
||||
<h4>4.4.1根据您的需求,在您明确同意的披露方式下披露您所指定的个人信息;</h4>
|
||||
<h4>4.4.2
|
||||
根据法律、法规的要求、行政或司法机关的强制性要求,我们可能会公开披露您的个人信息。当巨梦科技收到上述披露请求时,巨梦科技会依法要求请求方出具相关法律文件,如传票或协助调查函等。巨梦科技会慎重审查每一个披露请求,以确保该等披露请求符合相关法律规定。在法律法规许可的前提下,巨梦科技会对包含披露信息的文件采取加密保护等措施。</h4>
|
||||
<br>
|
||||
<h1 id="5_chapter">第五章 如何保护您的个人信息</h1>
|
||||
<h3>5.1
|
||||
巨梦科技非常重视个人信息安全,并会采取一切合理可行的措施,持续保护您的个人信息,以防其他人在未经授权的情况下访问、篡改或披露巨梦科技收集的您的个人信息:</h3>
|
||||
<h4>5.1.1
|
||||
巨梦科技已采用符合行业标准的安全防护措施来保护您的个人信息,防止数据遭到未经授权的访问、公开披露、使用、修改、损坏或丢失。我们会采取一切合理可行的措施,保护您的个人信息。我们会使用受信赖的保护机制防止数据遭到恶意攻击。</h4>
|
||||
<h4>5.1.2
|
||||
巨梦科技仅允许有必要知晓的人员访问相关个人信息,并为此设置了严格的访问权限控制和监控机制。巨梦科技同时要求可能接触到您个人信息的所有人员履行相应的保密义务。如果未能履行这些义务,可能会被追究法律责任或被终止与巨梦科技的相应法律关系。</h4>
|
||||
<h3>5.2 巨梦科技会采取一切合理可行的措施,确保未收集无关的信息。</h3>
|
||||
<h3>5.3
|
||||
您理解,由于技术的限制以及可能存在的各种恶意手段,在互联网行业,即便我们竭尽所能加强安全措施,但也不可能始终保证信息百分之百的绝对安全。您需要了解和知悉,您接入巨梦科技的产品及/或服务所用的系统和通讯网络,有可能因我们可控范围外的其他因素而出现问题,在此情形下,我们会依法尽力协助解决。</h3>
|
||||
<h3>5.4
|
||||
如不幸发生信息安全事故,我们将按照法律法规的要求,及时向您告知:安全事件的基本情况和可能的影响、我们已采取或将要采取的处置措施、您可自主防范和降低风险的建议等。我们同时将及时将事件相关情况以邮件、信函、电话、推送通知等适合的方式告知您;难以逐一告知信息主体时,我们会采取合理、有效的方式发布公告。同时,我们还将按照监管部门要求,主动上报信息安全事件的处置情况。</h3>
|
||||
<br>
|
||||
<h1 id="6_chapter">第六章 如何保存您的个人信息</h1>
|
||||
<h2>保存期限</h2>
|
||||
<h3>6.1 在用户使用巨梦科技产品及/或服务期间,巨梦科技会持续保存用户的个人信息。</h3>
|
||||
<h3>6.2
|
||||
巨梦科技承诺始终按照法律的规定在合理必要期限内在存储您个人信息,对于日志信息、记录备份等信息为您注销账号后180天(但根据法律规定有最短保存期限要求的,则我们会保存法律要求的最短期限);交易信息为交易完成日起三年或您注销账号后180天(以较长者为准)。在超出上述期限后,我们会对您的相关信息进行删除或匿名化处理。</h3>
|
||||
<h2>保存地域</h2>
|
||||
<h3>6.3 您的个人信息将全部被存储于中华人民共和国境内。</h3>
|
||||
<h3>6.4 目前我们不存在向境外提供个人信息的场景。</h3>
|
||||
<br>
|
||||
<h1 id="7_chapter">第七章 管理、查看或删除您的个人信息</h1>
|
||||
<h2>巨梦科技非常尊重您对自己的个人信息所享有的权利。我们保障您对个人信息所享有的访问、更正、删除、管理等权利。</h2>
|
||||
<br>
|
||||
<h2>访问和更正您的个人信息</h2>
|
||||
<h3>7.1
|
||||
除法律法规另有规定之外,您有权行使数据访问权。当您发现我们处理关于您的个人信息有错误或者您有其他修改、补充需求时,您也有权要求我们或自行予以更正。您行使数据访问和更正权的方式包括但不限于:</h3>
|
||||
<h4>7.1.1
|
||||
如果您希望访问或修改您在巨梦科技产品及/或服务中的账号信息,包括头像、昵称、性别、生日等,您可以通过访问我们的产品及/或服务进行操作(可通过点击“我的”功能后进行操作)。</h4>
|
||||
<h4>7.1.2
|
||||
如巨梦科技的产品及/或服务中提供发表话题、参与讨论、留言等功能并使得您有机会通过这些服务公开或提供的个人信息的,则巨梦科技在提供前述服务的同时会提供相应的功能确保您可以再次访问或删除您在前述服务过程中公开或提供的个人信息。</h4>
|
||||
<h3>7.2
|
||||
您有权知悉通过巨梦科技获得您的个人信息的第三方的身份或类型。您可以通过本政策第一章和第四章了解第三方的身份或类型。</h3>
|
||||
<br>
|
||||
<h2>删除您的个人信息及撤回已同意的授权</h2>
|
||||
<h3>7.3 在以下情形中,您可以向巨梦科技提出删除个人信息的请求:</h3>
|
||||
<h4>7.3.1 如果巨梦科技处理个人信息的行为违反相关的法律法规;</h4>
|
||||
<h4>7.3.2 如果巨梦科技收集、使用您的个人信息,却未征得您的同意;</h4>
|
||||
<h4>7.3.3 如果巨梦科技处理个人信息的行为违反了与您的约定或法律的规定;</h4>
|
||||
<h4>7.3.4 如果您不再使用巨梦科技的产品及/或服务,或者您注销了相关账号;</h4>
|
||||
<h4>7.3.5 如果巨梦科技不再为您提供产品及/或服务。</h4>
|
||||
<h3>7.4
|
||||
您有权向巨梦科技撤回您此前作出的有关同意收集、使用您的个人信息的授权。当您撤回同意后,我们将不再处理您的相关个人信息。但您撤回同意的决定,不会影响此前基于您的授权而开展的个人信息处理活动。</h3>
|
||||
<h3>7.5 您可以通过删除相关个人信息的方式撤回您此前就特定个人信息而对我们作出的同意授权。</h3>
|
||||
<h2>注销账号</h2>
|
||||
<h3>7.6
|
||||
您有权随时提出申请,注销您在巨梦科技产品及/或服务中注册的账号。为保障账号及财产安全,您需要通过客户服务或本《隐私条款》第十章载明的联系方式向巨梦科技提出您的账号注销请求。巨梦科技将在与您核实相关信息后的15个工作日内为您注销账号。</h3>
|
||||
<h3>7.7
|
||||
请您注意,注销巨梦科技相关产品及/或服务账号是不可恢复的操作。在注销账号之后,我们将停止为您提供产品及/或服务,并将删除该账号项下的您的个人信息,除非法律法规另有规定。</h3>
|
||||
<h2>约束信息系统自动决策</h2>
|
||||
<h3>7.8
|
||||
在巨梦科技仅依据信息系统、算法等在内的非人工自动决策机制做出决定,并且这些决定显著影响您的合法权益的情况下,您有权要求巨梦科技做出解释,巨梦科技也将提供适当的救济方式。</h3>
|
||||
<h2>获取个人信息副本</h2>
|
||||
<h3>7.9
|
||||
根据您的请求,巨梦科技可以向您提供巨梦科技持有的有关您的个人信息副本(如个人基本资料)。您可以通过客户服务或本《隐私条款》第十章载明的联系方式向我们提出请求。</h3>
|
||||
<h2>响应您的上述请求</h2>
|
||||
<h3>
|
||||
7.10如果您对巨梦科技在以上列明的有关访问、更正、删除您的个人信息,以及撤回同意、注销账号、约束信息系统自动决策方法有任何疑问,您可以通过客户服务或本《隐私政策》第十章载明的联系方式与我们取得联系。</h3>
|
||||
<h3>7.11
|
||||
对于您合理的请求,巨梦科技原则上不收取费用,但对多次重复、超出合理限度的请求,巨梦科技将视情况收取一定成本费用。对于那些无端重复、需要过多技术手段、给他人合法权益带来风险或者非常不切实际的请求,巨梦科技可能会予以拒绝。</h3>
|
||||
<h3>7.12 在以下情形下,我们可能无法响应您的请求</h3>
|
||||
<h4>7.12.1 与国家安全、国防安全有关的;</h4>
|
||||
<h4>7.12.2 与公共安全、公共卫生、重大公共利益有关的;</h4>
|
||||
<h4>7.12.3 与犯罪侦查、起诉和审判等有关的;</h4>
|
||||
<h4>7.12.4 有证据表明您存在主观恶意或滥用权利的;</h4>
|
||||
<h4>7.12.5 响应您的请求将导致其他个人、组织的合法权益受到严重损害的;</h4>
|
||||
<h4>7.12.6 涉及商业秘密的。</h4>
|
||||
<h2>申诉机制</h2>
|
||||
<h3>7.13
|
||||
巨梦科技已经建立申诉管理机制,包括跟踪流程等。为了保障账号及财产安全,巨梦科技可能会与您核实相关信息。巨梦科技将在收到您的反馈后尽快受理并处理您的请求,最长不超过15个工作日。若您对答复意见不满意,您可以再次通过客户服务进行申诉。申诉的联系方式及相关具体信息请详见第十章。</h3>
|
||||
<br>
|
||||
<br>
|
||||
<h1 id="8_chapter">第八章 儿童信息的保护</h1>
|
||||
<h3>8.1
|
||||
我们的产品及服务主要面向成人。巨梦科技非常重视对未成年人信息的保护。如果您是未满18周岁的未成年人,应在监护人监护、指导并获得监护人同意情况下仔细阅读本《隐私政策》和使用巨梦科技的产品及/或服务。</h3>
|
||||
<h3>8.2 如果您/您的监护人不同意本《隐私政策》的任何内容,您应该立即停止使用我们的产品及/或服务。</h3>
|
||||
<h3>8.3
|
||||
若您是未成年人的监护人,当您对您所监护的未成年人使用我们的产品及/或服务或其向我们提供的用户信息有任何疑问时,请您及时与我们联系。我们将根据国家相关法律法规及本政策的规定保护未成年人用户信息的保密性及安全性。如果我们发现自己在未事先获得可证实的监护人同意的情况下收集了未成年人的个人信息,则会设法尽快删除相关数据。</h3>
|
||||
<br>
|
||||
<h1 id="9_chapter">第九章 本政策如何更新</h1>
|
||||
<h3>9.1 如巨梦科技产品及/或服务发生以下变化,巨梦科技将及时对本《隐私政策》进行相应的修订:</h3>
|
||||
<h4>9.1.1 巨梦科技产品及/或服务所涉业务功能发生变更,导致处理个人信息的目的、类型、使用方式发生变更;</h4>
|
||||
<h4>9.1.2 您参与个人信息处理方面的权利及其行使方式发生重大变化;</h4>
|
||||
<h4>9.1.3 巨梦科技负责处理您的个人信息安全的部门的联络方式发生变更;</h4>
|
||||
<h4>9.1.4 发生其他可能影响用户个人信息安全或影响用户隐私权利的变更等。</h4>
|
||||
<h3>9.2
|
||||
《隐私政策》修订后巨梦科技会在巨梦科技产品及/或服务相关界面发布最新版本并以弹窗、推送通知等合理的方式告知用户,以便用户及时了解《隐私政策》的最新版本。</h3>
|
||||
<h3>9.3 未经您的明确同意,巨梦科技不会削减您基于本《隐私政策》所享有的权利。</h3>
|
||||
<h3>9.4 如无特殊说明,修订后的《隐私政策》自公布之日起生效。</h3>
|
||||
<br>
|
||||
<h1 id="10_chapter">第十章 投诉及建议</h1>
|
||||
<h3>10.1
|
||||
您在使用巨梦科技产品及/或服务的过程中,如果对本《隐私政策》有任何的疑义或建议,或您认为您的个人信息没有得到本《隐私政策》规定的保护,您可以通过以下方式联系我们,我们将真诚地处理您的投诉及建议。</h3>
|
||||
<h4>公司名称:北京巨梦科技信息技术有限公司</h4>
|
||||
<h4>联系邮箱:qiangli@django-vue-admin.com</h4>
|
||||
<h4>微信公众号:巨梦科技(9:00-22:00)</h4>
|
||||
<br>
|
||||
<h1 id="11_chapter">第十一章 其他</h1>
|
||||
<h3>11.1 因本政策以及我们处理您个人信息事宜引起的任何争议,您可诉至有管辖权的人民法院。</h3>
|
||||
<h3>11.2 如果您认为我们的个人信息处理行为损害了您的合法权益,您也可向有关政府部门进行反映。</h3>
|
||||
<h3>11.3
|
||||
在巨梦科技发布本《隐私政策》或向您提供产品及/或服务均视为本《隐私政策》生效。巨梦科技停止运营或永久停止提供产品及/或服务时本《隐私政策》失效。</h3>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
96
backend/templates/terms_service.html
Normal file
96
backend/templates/terms_service.html
Normal file
@@ -0,0 +1,96 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-dpr="1" style="font-size: 50px;">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>用户服务协议</title>
|
||||
<link href="/static/clause/privacy.css" rel="stylesheet">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||
</head>
|
||||
|
||||
<body class="app" style="font-size: 0.28rem;">
|
||||
<div class="main">
|
||||
<h3>服务使用协议</h3>
|
||||
<br>更新日期:2024年01月01日
|
||||
<br>生效日期:2024年01月01日
|
||||
<br>本《DvAdmin服务使用协议》是您(下称"用户")与北京巨梦科技科技有限公司之间在使用其DvAdmin产品中注册或登录时签署的协议,巨梦科技产品包括django-vue-admin产品。请仔细阅读以下条款,点击“同意”按钮即表示对相关条款的同意。如果您阅读这份协议后有任何疑问或意见,请与巨梦科技客服人员取得联系,联系邮箱:qiangli@django-vue-admin.cn,微信公众号:巨梦科技(9:00-18:00)。
|
||||
<br>
|
||||
<h2>一、重要须知</h2>
|
||||
<h3>
|
||||
1、用户应认真阅读(未成年人应当在监护人陪同下阅读)、充分理解本协议中各条款。除非用户接受本协议,用户应当立即停止注册及使用行为。</h3>
|
||||
<h3>2、用户在进行注册程序过程中点击“同意”按钮即表示用户完全接受本协议项下的全部条款。</h3>
|
||||
<br>
|
||||
<h2>二、服务内容</h2>
|
||||
<h3>1、巨梦科技服务的具体内容由巨梦科技根据实际情况提供,</h3>
|
||||
<h3>2、用户理解,巨梦科技仅提供软件相关服务,
|
||||
除此之外与相关软件服务有关的设备(如手机、个人电脑及其他与接入互联网或移动网有关的装置)及所需的费用(如为接入互联网而支付的电话费及上网费、为使用移动网而支付的手机费)均应由用户自行负担。</h3>
|
||||
<h3>
|
||||
3、用户同意巨梦科技为本协议履行或相关研究的目的收集、使用或授权第三方(且该等第三方同意承担与巨梦科技同等的个人信息保护义务)非商业地使用通过服务获取的用户信息(包括但不限于身份信息、性别、职业、兴趣爱好等资料)。巨梦科技承诺,除非另行取得用户的事先同意,否则巨梦科技不会将用户信息授权给与履行本协议无关的第三方用于商业目的。</h3>
|
||||
<h3>5、根据相关法律法规及国家标准,在以下情形中,巨梦科技可能会依法收集并使用用户的个人信息并且无需征得用户的同意:</h3>
|
||||
<h4>(1)与国家安全、国防安全直接相关的;</h4>
|
||||
<h4>(2)与公共安全、公共卫生、重大公共利益直接相关的;</h4>
|
||||
<h4>(3)与犯罪侦查、起诉、审判和判决执行等直接相关的;</h4>
|
||||
<h4>(4)出于维护用户或他人的生命、财产等重大合法权益但又很难得到用户本人同意的;</h4>
|
||||
<h4>(5)所收集的个人信息是用户自行向社会公众公开的;</h4>
|
||||
<h4>(6)从合法公开披露的信息中收集个人信息,例如:合法的新闻报道、政府信息公开等渠道;</h4>
|
||||
<h4>(7)根据用户的要求签订和履行合同所必需的;</h4>
|
||||
<h4>(8)用于维护所提供的服务的安全稳定运行所必需的,例如:发现、处置产品或服务的故障;</h4>
|
||||
<h4>(9)为合法的新闻报道所必需的;</h4>
|
||||
<h4>
|
||||
(10)学术研究机构基于公共利益开展统计或学术研究所必要,且对外提供学术研究或描述的结果时,对结果中所包含的个人信息进行去标识化处理的;</h4>
|
||||
<h4>(11)法律法规规定的其他情形。</h4>
|
||||
<h3>6、在以下情形中,巨梦科技向第三方提供用户的个人信息无需事先征得用户的授权同意:</h3>
|
||||
<h4>(1)与国家安全、国防安全有关的;</h4>
|
||||
<h4>(2)与公共安全、公共卫生、重大公共利益有关的;</h4>
|
||||
<h4>(3)与犯罪侦查、起诉、审判和判决执行等有关的;</h4>
|
||||
<h4>(4)出于维护用户或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;</h4>
|
||||
<h4>(5)用户自行向社会公众公开的个人信息;</h4>
|
||||
<h4>(6)从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开等渠道。</h4>
|
||||
<h3>
|
||||
根据法律规定,向第三方提供经去标识化处理的个人信息,且确保数据接收方无法复原并重新识别个人信息主体的,不属于个人信息的对外共享、转让及公开披露行为,对此类数据的保存及处理将无需另行向用户通知并征得用户的同意。</h3>
|
||||
<h3>本协议中涉及巨梦科技对于用户提供的个人信息的使用,如与《隐私政策》不符的,以《隐私政策》为准。</h3>
|
||||
<br>
|
||||
<h2>三、用户使用规则</h2>
|
||||
<h3>1、用户授权微信登录成功后,用户有权使用巨梦科技软件功能,包括参与活动、终端管理、经销商业务管理等。</h3>
|
||||
<h3>2、用户有权使用巨梦科技提供的功能进行照片上传、分享个人信息。</h3>
|
||||
<h3>3、用户在使用巨梦科技时须遵守国家相关法律法规,内容不得包含有下列内容之一的信息:</h3>
|
||||
<h4>a) 反对宪法所确定的基本原则的;</h4>
|
||||
<h4>b) 危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;</h4>
|
||||
<h4>c) 损害国家荣誉和利益的;</h4>
|
||||
<h4>d) 煽动民族仇恨、民族歧视、破坏民族团结的;</h4>
|
||||
<h4>e) 破坏国家宗教政策,宣扬邪教和封建迷信的;</h4>
|
||||
<h4>f) 散布谣言,扰乱社会秩序,破坏社会稳定的;</h4>
|
||||
<h4>g) 散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;</h4>
|
||||
<h4>h) 侮辱或者诽谤他人,侵害他人合法权利的;</h4>
|
||||
<h4>i) 含有虚假、有害、胁迫、侵害他人隐私、骚扰、侵害、中伤、粗俗、猥亵、或其它道德上令人反感的内容;</h4>
|
||||
<h4>j) 含有中国法律、法规、规章、条例以及任何具有法律效力之规范所限制或禁止的其它内容的;</h4>
|
||||
<h4>k) 含有巨梦科技认为不利于巨梦科技运营的内容。</h4>
|
||||
<h3>
|
||||
4、用户保证在使用巨梦科技时发布、传播的信息的真实性、准确性,保证不得发布谣言或其他与事实不符引起他人不适的言论、信息。</h3>
|
||||
<h3>
|
||||
5、用户保证在使用巨梦科技不得发布、传播侵犯知识产权或其他合法权益的信息。用户传播内容中涉及第三方拥有知识产权内容的相关授权或其他内容的合法性由用户自行负责,巨梦科技对相关内容的知识产权权属或是否侵犯他人民事权益等情况不进行审查或监督,但将按相关法规对涉嫌侵权的内容履行删除或断开链接等职责。</h3>
|
||||
<h3>
|
||||
6、若用户发生前述3-6的行为时,由用户承担所有的违法、侵权责任,若因此给巨梦科技造成任何损失巨梦科技有权向责任用户主张相关责任。同时,巨梦科技有权对违法、侵权、违规的用户终止提供服务。如果政府或者司法机关要求巨梦科技告知进行侵权行为用户的具体信息的,巨梦科技有权根据现行法规向其告知用户信息。</h3>
|
||||
<h3>
|
||||
7、用户同意巨梦科技有权在提供服务过程中以各种方式投放各种商业性广告或其他任何类型的商业信息,并且,用户同意接受巨梦科技通过电子邮件或其他方式向用户发送商品促销或其他相关商业信息。</h3>
|
||||
<h3>
|
||||
8、用户承诺提供的注册信息的真实性、合法性、有效性承担全部责任,用户不得冒充他人;不得利用他人的名义发布任何信息或享受巨梦科技供的任何服务;不得恶意使用注册帐号导致其他用户误认;否则巨梦科技有权立即停止提供服务,收回其帐号并由用户独自承担由此而产生的一切法律责任。</h3>
|
||||
<br>
|
||||
<h2>四、服务变更、中断或终止</h2>
|
||||
<h3>
|
||||
1、鉴于服务的特殊性,用户同意巨梦科技有权随时变更、中断或终止部分或全部的服务。如变更、中断或终止的服务属于免费服务,巨梦科技无需通知用户,也无需对任何用户或任何第三方承担任何责任。</h3>
|
||||
<br>
|
||||
<h2>五、巨梦科技企业客户服务说明</h2>
|
||||
<h3>
|
||||
1、巨梦科技平台提供给多家企业客户使用,企业客户通过巨梦科技平台进行发布用户活动等。如果功夫企火星企业用户违反了隐私政策或发生其它侵犯用户权益的行为,用户可要求巨梦科技提供必要的配合与巨梦科技企业用户进行沟通和维权,维权过程中产生的费用由用户自行承担。</h3>
|
||||
<br>
|
||||
<h2>六、其他事宜</h2>
|
||||
<h3>
|
||||
1、巨梦科技有权根据法律法规的变化、网站以及相应的巨梦科技运营的需要不时地对本协议及本站的内容进行修改,并在网站以及巨梦科技醒目位置张贴。修改后的协议一旦被张贴在本站上即生效,并代替原来的协议,用户可随时登录查阅最新协议。用户有义务及时关注并阅读最新版的协议及网站公告。如用户不同意更新后的协议,可以立即向巨梦科技进行反馈且应立即停止接受巨梦科技依据本协议提供的服务;如用户继续使用本站提供的服务的,即视为同意更新后的协议。</h3>
|
||||
<h3>2、本协议自用户点击“同意”、“接受”后生效。</h3>
|
||||
<h3>3、本协议所有条款的标题仅为方便而设,不具法律或契约效果。</h3>
|
||||
<h3>4、在法律允许范围内, 本协议的解释权归巨梦科技所有。</h3>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
web/.env
2
web/.env
@@ -1,6 +1,6 @@
|
||||
# port 端口号
|
||||
VITE_PORT = 8080
|
||||
|
||||
VITE_API_URL = 'http://dvadmin3api.django.icu:8001'
|
||||
# open 运行 npm run dev 时自动打开浏览器
|
||||
VITE_OPEN = false
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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)
|
||||
|
||||
@@ -23,7 +23,7 @@ Because of love, so embrace the future
|
||||
|
||||
## Online experience
|
||||
|
||||
👩👧👦👩👧👦 demo address:[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com)
|
||||
👩👧👦👩👧👦 demo address:[https://demo.dvadmin.com](https://demo.dvadmin.com)
|
||||
|
||||
* demo account:superadmin
|
||||
|
||||
@@ -39,9 +39,9 @@ Because of love, so embrace the future
|
||||
|
||||
## source code url:
|
||||
|
||||
gitee(Main push):[https://gitee.com/liqianglog/django-vue-admin](https://gitee.com/liqianglog/django-vue-admin)👩👦👦
|
||||
gitee(Main push):[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
github:[https://github.com/liqianglog/django-vue-admin](https://github.com/liqianglog/django-vue-admin)👩👦👦
|
||||
github:no data
|
||||
|
||||
## core function
|
||||
|
||||
@@ -55,7 +55,7 @@ github:[https://github.com/liqianglog/django-vue-admin](https://github.com/liq
|
||||
8. 🧑🔧 Regional management: to manage provinces, cities, counties and regions.
|
||||
9. 📁 Attachment management: Unified management of all files and pictures on the platform.
|
||||
10. 🗓 ️operation logs: log and query the system normal operation; Log and query system exception information.
|
||||
11.🔌 [plugins market] (<https://bbs.django-vue-admin.com/plugMarket.html>) : based on the Django framework - Vue - Admin application and plug-in development.
|
||||
11.🔌 [plugins market] (<https://bbs.django-vue-admin.com/plugMarket.html>) : based on the Django framework - Vue - Admin application and plug-in development.
|
||||
|
||||
## plugins market 🔌
|
||||
|
||||
@@ -76,7 +76,7 @@ Redis(Optional, the latest edition)
|
||||
|
||||
```bash
|
||||
# clone code
|
||||
git clone https://gitee.com/liqianglog/django-vue-admin.git
|
||||
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||
|
||||
# enter code dir
|
||||
cd web
|
||||
@@ -120,7 +120,7 @@ or daphne :
|
||||
* visit url:[http://localhost:8080](http://localhost:8080) (The default address is this one. If you want to change it, follow the configuration file)
|
||||
* account:`superadmin` password:`admin123456`
|
||||
|
||||
### docker-compose
|
||||
### docker-compose
|
||||
|
||||
~~~shell
|
||||
docker-compose up -d
|
||||
202
web/README.md
202
web/README.md
@@ -1,8 +1,32 @@
|
||||
<div align="center">django-vue3-admin:web </div>
|
||||
# Django-Vue3-Admin
|
||||
|
||||
#### 🌈 介绍
|
||||
[](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/liqianglog/django-vue-admin)
|
||||
|
||||
django-vue3-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!
|
||||
[预 览](https://demo.dvadmin.com) | [官网文档](https://www.django-vue-admin.com) | [群聊](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [社区](https://bbs.django-vue-admin.com) | [插件市场](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
|
||||
|
||||
|
||||
|
||||
💡 **「关于」**
|
||||
|
||||
我们是一群热爱代码的青年,在这个炙热的时代下,我们希望静下心来通过Code带来一点我们的色彩和颜色。
|
||||
|
||||
因为热爱,所以拥抱未来!
|
||||
|
||||
|
||||
## 平台简介
|
||||
|
||||
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
||||
django-vue3-admin 基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!
|
||||
|
||||
|
||||
|
||||
|
||||
* 🧑🤝🧑前端采用 Vue3+TS+pinia+fastcrud(感谢[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/))
|
||||
* 👭后端采用 Python 语言 Django 框架以及强大的 [Django REST Framework](https://pypi.org/project/djangorestframework)。
|
||||
* 👫权限认证使用[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),支持多终端认证系统。
|
||||
* 👬支持加载动态权限菜单,多方式轻松权限控制。
|
||||
* 💏特别鸣谢:[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)。
|
||||
* 💡 特别感谢[jetbrains](https://www.jetbrains.com/) 为本开源项目提供免费的 IntelliJ IDEA 授权。
|
||||
|
||||
#### 🏭 环境支持
|
||||
|
||||
@@ -12,21 +36,173 @@ django-vue3-admin,基于 vue3 + CompositionAPI + typescript + vite + element p
|
||||
|
||||
> 由于 Vue3 不再支持 IE11,故而 ElementPlus 也不支持 IE11 及之前版本。
|
||||
|
||||
#### ⚡ 使用说明
|
||||
|
||||
建议使用 yarn,yarn 是一个类似于npm的包管理器 <a href="http://nodejs.cn/" target="_blank">node 版本 > 16</a>
|
||||
|
||||
## 在线体验
|
||||
|
||||
👩👧👦演示地址:[https://demo.dvadmin.com](https://demo.dvadmin.com)
|
||||
|
||||
- 账号:superadmin
|
||||
|
||||
- 密码:admin123456
|
||||
|
||||
👩👦👦文档地址:[coding](https://dvadmin-private.coding.net/share/km/cec69f3d-30fe-47d5-bd97-e9e851f0b776/K-2)
|
||||
|
||||
|
||||
|
||||
## 交流
|
||||
|
||||
- 交流社区:[戳我](https://bbs.django-vue-admin.com)👩👦👦
|
||||
|
||||
- 插件市场:[戳我](https://bbs.django-vue-admin.com/plugMarket.html)👩👦👦
|
||||
|
||||
- django-vue-admin交流01群(已满):812482043 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=aJVwjDvH-Es4MPJQuoO32N0SucK22TE5&jump_from=webapi)
|
||||
- django-vue-admin交流02群(已满):687252418 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=4jJN4IjWGfxJ8YJXbb_gTsuWjR34WLdc&jump_from=webapi)
|
||||
- django-vue-admin交流03群:442108213 [点击链接加入群聊](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=wsm5oSz3K8dElBYUDtLTcQSEPhINFkl8&authKey=M6sbER0z59ZakgBr5erFeZyFZU15CI52bErNZa%2FxSvvGIuVAbY0N5866v89hm%2FK4&noverify=0&group_code=442108213)
|
||||
|
||||
- 二维码
|
||||
|
||||
<img src='https://images.gitee.com/uploads/images/2022/0530/233203_5fb11883_5074988.jpeg' width='200'>
|
||||
|
||||
## 源码地址
|
||||
|
||||
gitee地址(主推):[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
github地址:暂无
|
||||
|
||||
|
||||
## 内置功能
|
||||
|
||||
1. 👨⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
||||
2. 🧑⚕️部门管理:配置系统组织机构(公司、部门、角色)。
|
||||
3. 👩⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
|
||||
4. 🧑🎓按钮权限权限:授权角色的按钮权限和接口权限,可做到每一个接口都能授权数据范围。
|
||||
5. 🧑🎓字段权限权限:授权页面的字段显示权限。
|
||||
5. 👨🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
6. 👬接口白名单:配置不需要进行权限校验的接口。
|
||||
7. 🧑🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
8. 🧑🔧地区管理:对省市县区域进行管理。
|
||||
9. 📁附件管理:对平台上所有文件、图片等进行统一管理。
|
||||
10. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
11. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html):基于Django-Vue-Admin框架开发的应用和插件。
|
||||
|
||||
## 插件市场 🔌
|
||||
|
||||
- Celery异步任务:[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
|
||||
- 升级中心后端:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
|
||||
- 升级中心前端:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
|
||||
|
||||
## 准备工作
|
||||
~~~
|
||||
Python >= 3.8.0 (推荐3.8+版本)
|
||||
nodejs >= 14.0 (推荐最新)
|
||||
Mysql >= 5.7.0 (可选,默认数据库sqlite3,推荐8.0版本)
|
||||
Redis(可选,最新版)
|
||||
~~~
|
||||
|
||||
## 前端♝
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||
|
||||
# 进入项目
|
||||
cd django-vue-admin/web
|
||||
# 进入项目目录
|
||||
cd web
|
||||
|
||||
# 安装依赖
|
||||
yarn install
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# 启动服务
|
||||
npm run dev
|
||||
# 浏览器访问 http://localhost:8080
|
||||
# .env.development 文件中可配置启动端口等参数
|
||||
# 构建生产环境
|
||||
# npm run build
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 后端💈
|
||||
|
||||
~~~bash
|
||||
1. 进入项目目录 cd backend
|
||||
2. 在项目根目录中,复制 ./conf/env.example.py 文件为一份新的到 ./conf 文件夹下,并重命名为 env.py
|
||||
3. 在 env.py 中配置数据库信息
|
||||
mysql数据库版本建议:8.0
|
||||
mysql数据库字符集:utf8mb4
|
||||
4. 安装依赖环境
|
||||
pip3 install -r requirements.txt
|
||||
5. 执行迁移命令:
|
||||
python3 manage.py makemigrations
|
||||
python3 manage.py migrate
|
||||
6. 初始化数据
|
||||
python3 manage.py init
|
||||
7. 初始化省市县数据:
|
||||
python3 manage.py init_area
|
||||
8. 启动项目
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
或使用 daphne :
|
||||
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
||||
~~~
|
||||
|
||||
### 访问项目
|
||||
|
||||
- 访问地址:[http://localhost:8080](http://localhost:8080) (默认为此地址,如有修改请按照配置文件)
|
||||
- 账号:`superadmin` 密码:`admin123456`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### docker-compose 运行
|
||||
|
||||
~~~shell
|
||||
# 先安装docker-compose (自行百度安装),执行此命令等待安装,如有使用celery插件请打开docker-compose.yml中celery 部分注释
|
||||
docker-compose up -d
|
||||
# 初始化后端数据(第一次执行即可)
|
||||
docker exec -ti dvadmin-django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init_area
|
||||
python manage.py init
|
||||
exit
|
||||
|
||||
前端地址:http://127.0.0.1:8080
|
||||
后端地址:http://127.0.0.1:8080/api
|
||||
# 在服务器上请把127.0.0.1 换成自己公网ip
|
||||
账号:superadmin 密码:admin123456
|
||||
|
||||
# docker-compose 停止
|
||||
docker-compose down
|
||||
# docker-compose 重启
|
||||
docker-compose restart
|
||||
# docker-compose 启动时重新进行 build
|
||||
docker-compose up -d --build
|
||||
~~~
|
||||
|
||||
|
||||
|
||||
## 演示图✅
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
# 运行项目
|
||||
yarn dev
|
||||
|
||||
# 打包发布
|
||||
yarn build
|
||||
```
|
||||
@@ -10,23 +10,22 @@
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="django-vue3-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!"
|
||||
content="django-vue-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>django-vue3-admin</title>
|
||||
<title>django-vue-admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript">
|
||||
// let _hmt = _hmt || [];
|
||||
(function () {
|
||||
let hm = document.createElement('script');
|
||||
hm.src = 'https://hm.baidu.com/hm.js?d9c8b87d10717013641458b300c552e4';
|
||||
let s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?9ba8fc809b5584167a2fb9b31bb3970c";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "django-vue3-admin",
|
||||
"version": "1.0.0",
|
||||
"description": "django-vue3-admin,基于 vue3 + CompositionAPI + typescript + vite + element plus, 是一款全栈,快速,开源的后台管理系统!",
|
||||
"version": "3.0.0",
|
||||
"description": "是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。\n django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite --force",
|
||||
@@ -10,10 +10,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@fast-crud/fast-crud": "^1.18.3",
|
||||
"@fast-crud/fast-extends": "^1.18.3",
|
||||
"@fast-crud/ui-element": "^1.18.3",
|
||||
"@fast-crud/ui-interface": "^1.18.3",
|
||||
"@fast-crud/fast-crud": "^1.19.2",
|
||||
"@fast-crud/fast-extends": "^1.19.2",
|
||||
"@fast-crud/ui-element": "^1.19.2",
|
||||
"@fast-crud/ui-interface": "^1.19.2",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
@@ -62,7 +62,7 @@
|
||||
"@typescript-eslint/parser": "^5.46.0",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vue/compiler-sfc": "^3.2.45",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"prettier": "^2.8.1",
|
||||
"sass": "^1.56.2",
|
||||
|
||||
@@ -59,12 +59,6 @@ onBeforeMount(() => {
|
||||
setIntroduction.cssCdn();
|
||||
// 设置批量第三方 js
|
||||
setIntroduction.jsCdn();
|
||||
//websockt 模块
|
||||
try {
|
||||
//websocket.init(wsReceive)
|
||||
} catch (e) {
|
||||
console.log('websocket错误');
|
||||
}
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
@@ -93,6 +87,14 @@ watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
other.useTitle();
|
||||
if (!websocket.websocket) {
|
||||
//websockt 模块
|
||||
try {
|
||||
websocket.init(wsReceive)
|
||||
} catch (e) {
|
||||
console.log('websocket错误');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
|
||||
29
web/src/components/dept-format/index.vue
Normal file
29
web/src/components/dept-format/index.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
{{ data }}
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {defineProps,ref,watch} from 'vue'
|
||||
import {useDeptInfoStore} from '/@/stores/modules/dept'
|
||||
const props = defineProps({
|
||||
modelValue:{
|
||||
type: Number || String
|
||||
}
|
||||
})
|
||||
const data = ref()
|
||||
watch(()=>{
|
||||
return props.modelValue
|
||||
},async (newVal)=>{
|
||||
const deptInfoStore = useDeptInfoStore()
|
||||
const result = await deptInfoStore.getParentDeptById(newVal)
|
||||
if(result?.nodes){
|
||||
let name = ""
|
||||
console.log(result)
|
||||
result.nodes.forEach((item:any,index:number)=>{
|
||||
name += index>0?`/${item.name}`:item.name
|
||||
})
|
||||
data.value = name
|
||||
}
|
||||
},{immediate: true} )
|
||||
</script>
|
||||
@@ -159,14 +159,11 @@ const initModeValueEcho = () => {
|
||||
// 处理 icon 类型,用于回显时,tab 高亮与初始化数据
|
||||
const initFontIconName = () => {
|
||||
let name = 'ali';
|
||||
if(props.modelValue){
|
||||
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
|
||||
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
|
||||
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
|
||||
// 初始化 tab 高亮回显
|
||||
state.fontIconTabActive = name;
|
||||
}
|
||||
|
||||
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
|
||||
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
|
||||
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
|
||||
// 初始化 tab 高亮回显
|
||||
state.fontIconTabActive = name;
|
||||
return name;
|
||||
};
|
||||
// 初始化数据
|
||||
|
||||
@@ -1,123 +1,131 @@
|
||||
// 定义内容
|
||||
export default {
|
||||
router: {
|
||||
home: 'home',
|
||||
system: 'system',
|
||||
systemMenu: 'systemMenu',
|
||||
systemRole: 'systemRole',
|
||||
systemUser: 'systemUser',
|
||||
systemDept: 'systemDept',
|
||||
systemDic: 'systemDic',
|
||||
limits: 'limits',
|
||||
limitsFrontEnd: 'FrontEnd',
|
||||
limitsFrontEndPage: 'FrontEndPage',
|
||||
limitsFrontEndBtn: 'FrontEndBtn',
|
||||
limitsBackEnd: 'BackEnd',
|
||||
limitsBackEndEndPage: 'BackEndEndPage',
|
||||
personal: 'personal',
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: 'signIn',
|
||||
notFound: 'notFound',
|
||||
noPower: 'noPower',
|
||||
},
|
||||
user: {
|
||||
title0: 'Component size',
|
||||
title1: 'Language switching',
|
||||
title2: 'Menu search',
|
||||
title3: 'Layout configuration',
|
||||
title4: 'news',
|
||||
title5: 'Full screen on',
|
||||
title6: 'Full screen off',
|
||||
dropdownLarge: 'large',
|
||||
dropdownDefault: 'default',
|
||||
dropdownSmall: 'small',
|
||||
dropdown1: 'home page',
|
||||
dropdown2: 'Personal Center',
|
||||
dropdown3: '404',
|
||||
dropdown4: '401',
|
||||
dropdown5: 'Log out',
|
||||
dropdown6: 'Code warehouse',
|
||||
searchPlaceholder: 'Menu search: support Chinese, routing path',
|
||||
newTitle: 'notice',
|
||||
newBtn: 'All read',
|
||||
newGo: 'Go to the notification center',
|
||||
newDesc: 'No notice',
|
||||
logOutTitle: 'Tips',
|
||||
logOutMessage: 'This operation will log out. Do you want to continue?',
|
||||
logOutConfirm: 'determine',
|
||||
logOutCancel: 'cancel',
|
||||
logOutExit: 'Exiting',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: 'refresh',
|
||||
close: 'close',
|
||||
closeOther: 'closeOther',
|
||||
closeAll: 'closeAll',
|
||||
fullscreen: 'fullscreen',
|
||||
closeFullscreen: 'closeFullscreen',
|
||||
},
|
||||
notFound: {
|
||||
foundTitle: 'Wrong address input, please re-enter the address~',
|
||||
foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
|
||||
foundBtn: 'Back to home page',
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: 'You are not authorized to operate~',
|
||||
accessMsg: 'Contact information: add QQ group discussion 665452019',
|
||||
accessBtn: 'Reauthorization',
|
||||
},
|
||||
layout: {
|
||||
configTitle: 'Layout configuration',
|
||||
oneTitle: 'Global Themes',
|
||||
twoTopTitle: 'top bar set up',
|
||||
twoMenuTitle: 'Menu set up',
|
||||
twoColumnsTitle: 'Columns set up',
|
||||
twoTopBar: 'Top bar background',
|
||||
twoTopBarColor: 'Top bar default font color',
|
||||
twoIsTopBarColorGradual: 'Top bar gradient',
|
||||
twoMenuBar: 'Menu background',
|
||||
twoMenuBarColor: 'Menu default font color',
|
||||
twoIsMenuBarColorGradual: 'Menu gradient',
|
||||
twoColumnsMenuBar: 'Column menu background',
|
||||
twoColumnsMenuBarColor: 'Default font color bar menu',
|
||||
twoIsColumnsMenuBarColorGradual: 'Column gradient',
|
||||
threeTitle: 'Interface settings',
|
||||
threeIsCollapse: 'Menu horizontal collapse',
|
||||
threeIsUniqueOpened: 'Menu accordion',
|
||||
threeIsFixedHeader: 'Fixed header',
|
||||
threeIsClassicSplitMenu: 'Classic layout split menu',
|
||||
threeIsLockScreen: 'Open the lock screen',
|
||||
threeLockScreenTime: 'screen locking(s/s)',
|
||||
fourTitle: 'Interface display',
|
||||
fourIsShowLogo: 'Sidebar logo',
|
||||
fourIsBreadcrumb: 'Open breadcrumb',
|
||||
fourIsBreadcrumbIcon: 'Open breadcrumb icon',
|
||||
fourIsTagsview: 'Open tagsview',
|
||||
fourIsTagsviewIcon: 'Open tagsview Icon',
|
||||
fourIsCacheTagsView: 'Enable tagsview cache',
|
||||
fourIsSortableTagsView: 'Enable tagsview drag',
|
||||
fourIsShareTagsView: 'Enable tagsview sharing',
|
||||
fourIsFooter: 'Open footer',
|
||||
fourIsGrayscale: 'Grey model',
|
||||
fourIsInvert: 'Color weak mode',
|
||||
fourIsDark: 'Dark Mode',
|
||||
fourIsWartermark: 'Turn on watermark',
|
||||
fourWartermarkText: 'Watermark copy',
|
||||
fiveTitle: 'Other settings',
|
||||
fiveTagsStyle: 'Tagsview style',
|
||||
fiveAnimation: 'page animation',
|
||||
fiveColumnsAsideStyle: 'Column style',
|
||||
fiveColumnsAsideLayout: 'Column layout',
|
||||
sixTitle: 'Layout switch',
|
||||
sixDefaults: 'One',
|
||||
sixClassic: 'Two',
|
||||
sixTransverse: 'Three',
|
||||
sixColumns: 'Four',
|
||||
tipText: 'Click the button below to copy the layout configuration to `/src/stores/themeConfig.ts` It has been modified in.',
|
||||
copyText: 'replication configuration',
|
||||
resetText: 'restore default',
|
||||
copyTextSuccess: 'Copy succeeded!',
|
||||
copyTextError: 'Copy failed!',
|
||||
},
|
||||
router: {
|
||||
home: 'home',
|
||||
system: 'system',
|
||||
systemMenu: 'systemMenu',
|
||||
systemRole: 'systemRole',
|
||||
systemUser: 'systemUser',
|
||||
systemDept: 'systemDept',
|
||||
systemDic: 'systemDic',
|
||||
limits: 'limits',
|
||||
limitsFrontEnd: 'FrontEnd',
|
||||
limitsFrontEndPage: 'FrontEndPage',
|
||||
limitsFrontEndBtn: 'FrontEndBtn',
|
||||
limitsBackEnd: 'BackEnd',
|
||||
limitsBackEndEndPage: 'BackEndEndPage',
|
||||
personal: 'personal',
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: 'signIn',
|
||||
notFound: 'notFound',
|
||||
noPower: 'noPower',
|
||||
},
|
||||
user: {
|
||||
title0: 'Component size',
|
||||
title1: 'Language switching',
|
||||
title2: 'Menu search',
|
||||
title3: 'Layout configuration',
|
||||
title4: 'news',
|
||||
title5: 'Full screen on',
|
||||
title6: 'Full screen off',
|
||||
dropdownLarge: 'large',
|
||||
dropdownDefault: 'default',
|
||||
dropdownSmall: 'small',
|
||||
dropdown1: 'home page',
|
||||
dropdown2: 'Personal Center',
|
||||
dropdown3: '404',
|
||||
dropdown4: '401',
|
||||
dropdown5: 'Log out',
|
||||
dropdown6: 'Code warehouse',
|
||||
searchPlaceholder: 'Menu search: support Chinese, routing path',
|
||||
newTitle: 'notice',
|
||||
newBtn: 'All read',
|
||||
newGo: 'Go to the notification center',
|
||||
newDesc: 'No notice',
|
||||
logOutTitle: 'Tips',
|
||||
logOutMessage: 'This operation will log out. Do you want to continue?',
|
||||
logOutConfirm: 'determine',
|
||||
logOutCancel: 'cancel',
|
||||
logOutExit: 'Exiting',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: 'refresh',
|
||||
close: 'close',
|
||||
closeOther: 'closeOther',
|
||||
closeAll: 'closeAll',
|
||||
fullscreen: 'fullscreen',
|
||||
closeFullscreen: 'closeFullscreen',
|
||||
},
|
||||
notFound: {
|
||||
foundTitle: 'Wrong address input, please re-enter the address~',
|
||||
foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
|
||||
foundBtn: 'Back to home page',
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: 'You are not authorized to operate~',
|
||||
accessMsg: 'Contact information: add QQ group discussion 665452019',
|
||||
accessBtn: 'Reauthorization',
|
||||
},
|
||||
layout: {
|
||||
configTitle: 'Layout configuration',
|
||||
oneTitle: 'Global Themes',
|
||||
twoTopTitle: 'top bar set up',
|
||||
twoMenuTitle: 'Menu set up',
|
||||
twoColumnsTitle: 'Columns set up',
|
||||
twoTopBar: 'Top bar background',
|
||||
twoTopBarColor: 'Top bar default font color',
|
||||
twoIsTopBarColorGradual: 'Top bar gradient',
|
||||
twoMenuBar: 'Menu background',
|
||||
twoMenuBarColor: 'Menu default font color',
|
||||
twoIsMenuBarColorGradual: 'Menu gradient',
|
||||
twoColumnsMenuBar: 'Column menu background',
|
||||
twoColumnsMenuBarColor: 'Default font color bar menu',
|
||||
twoIsColumnsMenuBarColorGradual: 'Column gradient',
|
||||
threeTitle: 'Interface settings',
|
||||
threeIsCollapse: 'Menu horizontal collapse',
|
||||
threeIsUniqueOpened: 'Menu accordion',
|
||||
threeIsFixedHeader: 'Fixed header',
|
||||
threeIsClassicSplitMenu: 'Classic layout split menu',
|
||||
threeIsLockScreen: 'Open the lock screen',
|
||||
threeLockScreenTime: 'screen locking(s/s)',
|
||||
fourTitle: 'Interface display',
|
||||
fourIsShowLogo: 'Sidebar logo',
|
||||
fourIsBreadcrumb: 'Open breadcrumb',
|
||||
fourIsBreadcrumbIcon: 'Open breadcrumb icon',
|
||||
fourIsTagsview: 'Open tagsview',
|
||||
fourIsTagsviewIcon: 'Open tagsview Icon',
|
||||
fourIsCacheTagsView: 'Enable tagsview cache',
|
||||
fourIsSortableTagsView: 'Enable tagsview drag',
|
||||
fourIsShareTagsView: 'Enable tagsview sharing',
|
||||
fourIsFooter: 'Open footer',
|
||||
fourIsGrayscale: 'Grey model',
|
||||
fourIsInvert: 'Color weak mode',
|
||||
fourIsDark: 'Dark Mode',
|
||||
fourIsWartermark: 'Turn on watermark',
|
||||
fourWartermarkText: 'Watermark copy',
|
||||
fiveTitle: 'Other settings',
|
||||
fiveTagsStyle: 'Tagsview style',
|
||||
fiveAnimation: 'page animation',
|
||||
fiveColumnsAsideStyle: 'Column style',
|
||||
fiveColumnsAsideLayout: 'Column layout',
|
||||
sixTitle: 'Layout switch',
|
||||
sixDefaults: 'One',
|
||||
sixClassic: 'Two',
|
||||
sixTransverse: 'Three',
|
||||
sixColumns: 'Four',
|
||||
tipText: 'Click the button below to copy the layout configuration to `/src/stores/themeConfig.ts` It has been modified in.',
|
||||
copyText: 'replication configuration',
|
||||
resetText: 'restore default',
|
||||
copyTextSuccess: 'Copy succeeded!',
|
||||
copyTextError: 'Copy failed!',
|
||||
},
|
||||
upgrade: {
|
||||
title: 'New version upgrade',
|
||||
msg: 'It\'s a new version. Update it now!Don\'t worry, update quickly oh!',
|
||||
desc: 'Tip: The update restores the default configuration',
|
||||
btnOne: 'Cruel refusal',
|
||||
btnTwo: 'Update now',
|
||||
btnTwoLoading: 'updating',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,134 +1,146 @@
|
||||
// 定义内容
|
||||
export default {
|
||||
router: {
|
||||
home: '首页',
|
||||
system: '系统管理',
|
||||
config: '常规配置',
|
||||
log: '日志管理',
|
||||
/* 常规配置 */
|
||||
configSystem: '系统配置',
|
||||
configDict: '字典管理',
|
||||
configArea: '地区管理',
|
||||
configFile: '附件管理',
|
||||
/* 系统管理 */
|
||||
systemMenu: '菜单管理',
|
||||
systemRole: '角色管理',
|
||||
systemUser: '用户管理',
|
||||
systemDept: '部门管理',
|
||||
/* 日志管理 */
|
||||
loginLog: '登录日志',
|
||||
operationLog: '操作日志',
|
||||
systemApiWhiteList: '接口白名单',
|
||||
limits: '权限管理',
|
||||
limitsFrontEnd: '前端控制',
|
||||
limitsFrontEndPage: '页面权限',
|
||||
limitsFrontEndBtn: '按钮权限',
|
||||
limitsBackEnd: '后端控制',
|
||||
limitsBackEndEndPage: '页面权限',
|
||||
personal: '个人中心',
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: '登录',
|
||||
notFound: '找不到此页面',
|
||||
noPower: '没有权限',
|
||||
},
|
||||
user: {
|
||||
title0: '组件大小',
|
||||
title1: '语言切换',
|
||||
title2: '菜单搜索',
|
||||
title3: '布局配置',
|
||||
title4: '消息',
|
||||
title5: '开全屏',
|
||||
title6: '关全屏',
|
||||
dropdownLarge: '大型',
|
||||
dropdownDefault: '默认',
|
||||
dropdownSmall: '小型',
|
||||
dropdown1: '首页',
|
||||
dropdown2: '个人中心',
|
||||
dropdown3: '404',
|
||||
dropdown4: '401',
|
||||
dropdown5: '退出登录',
|
||||
dropdown6: '代码仓库',
|
||||
searchPlaceholder: '菜单搜索:支持中文、路由路径',
|
||||
newTitle: '通知',
|
||||
newBtn: '全部已读',
|
||||
newGo: '前往通知中心',
|
||||
newDesc: '暂无通知',
|
||||
logOutTitle: '提示',
|
||||
logOutMessage: '此操作将退出登录, 是否继续?',
|
||||
logOutConfirm: '确定',
|
||||
logOutCancel: '取消',
|
||||
logOutExit: '退出中',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: '刷新',
|
||||
close: '关闭',
|
||||
closeOther: '关闭其它',
|
||||
closeAll: '全部关闭',
|
||||
fullscreen: '当前页全屏',
|
||||
closeFullscreen: '关闭全屏',
|
||||
},
|
||||
notFound: {
|
||||
foundTitle: '地址输入错误,请重新输入地址~',
|
||||
foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
|
||||
foundBtn: '返回首页',
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: '您未被授权,没有操作权限~',
|
||||
accessMsg: '联系方式:加QQ群探讨 665452019',
|
||||
accessBtn: '重新授权',
|
||||
},
|
||||
layout: {
|
||||
configTitle: '布局配置',
|
||||
oneTitle: '全局主题',
|
||||
twoTopTitle: '顶栏设置',
|
||||
twoMenuTitle: '菜单设置',
|
||||
twoColumnsTitle: '分栏设置',
|
||||
twoTopBar: '顶栏背景',
|
||||
twoTopBarColor: '顶栏默认字体颜色',
|
||||
twoIsTopBarColorGradual: '顶栏背景渐变',
|
||||
twoMenuBar: '菜单背景',
|
||||
twoMenuBarColor: '菜单默认字体颜色',
|
||||
twoIsMenuBarColorGradual: '菜单背景渐变',
|
||||
twoColumnsMenuBar: '分栏菜单背景',
|
||||
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
|
||||
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
|
||||
threeTitle: '界面设置',
|
||||
threeIsCollapse: '菜单水平折叠',
|
||||
threeIsUniqueOpened: '菜单手风琴',
|
||||
threeIsFixedHeader: '固定 Header',
|
||||
threeIsClassicSplitMenu: '经典布局分割菜单',
|
||||
threeIsLockScreen: '开启锁屏',
|
||||
threeLockScreenTime: '自动锁屏(s/秒)',
|
||||
fourTitle: '界面显示',
|
||||
fourIsShowLogo: '侧边栏 Logo',
|
||||
fourIsBreadcrumb: '开启 Breadcrumb',
|
||||
fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
|
||||
fourIsTagsview: '开启 Tagsview',
|
||||
fourIsTagsviewIcon: '开启 Tagsview 图标',
|
||||
fourIsCacheTagsView: '开启 TagsView 缓存',
|
||||
fourIsSortableTagsView: '开启 TagsView 拖拽',
|
||||
fourIsShareTagsView: '开启 TagsView 共用',
|
||||
fourIsFooter: '开启 Footer',
|
||||
fourIsGrayscale: '灰色模式',
|
||||
fourIsInvert: '色弱模式',
|
||||
fourIsDark: '深色模式',
|
||||
fourIsWartermark: '开启水印',
|
||||
fourWartermarkText: '水印文案',
|
||||
fiveTitle: '其它设置',
|
||||
fiveTagsStyle: 'Tagsview 风格',
|
||||
fiveAnimation: '主页面切换动画',
|
||||
fiveColumnsAsideStyle: '分栏高亮风格',
|
||||
fiveColumnsAsideLayout: '分栏布局风格',
|
||||
sixTitle: '布局切换',
|
||||
sixDefaults: '默认',
|
||||
sixClassic: '经典',
|
||||
sixTransverse: '横向',
|
||||
sixColumns: '分栏',
|
||||
tipText: '点击下方按钮,复制布局配置去 `src/stores/themeConfig.ts` 中修改。',
|
||||
copyText: '一键复制配置',
|
||||
resetText: '一键恢复默认',
|
||||
copyTextSuccess: '复制成功!',
|
||||
copyTextError: '复制失败!',
|
||||
},
|
||||
router: {
|
||||
home: '首页',
|
||||
system: '系统管理',
|
||||
config: '常规配置',
|
||||
log: '日志管理',
|
||||
/* 常规配置 */
|
||||
configSystem: '系统配置',
|
||||
configDict: '字典管理',
|
||||
configArea: '地区管理',
|
||||
configFile: '附件管理',
|
||||
/* 系统管理 */
|
||||
systemMenu: '菜单管理',
|
||||
systemRole: '角色管理',
|
||||
systemUser: '用户管理',
|
||||
systemDept: '部门管理',
|
||||
/* 日志管理 */
|
||||
loginLog: '登录日志',
|
||||
operationLog: '操作日志',
|
||||
systemApiWhiteList: '接口白名单',
|
||||
limits: '权限管理',
|
||||
limitsFrontEnd: '前端控制',
|
||||
limitsFrontEndPage: '页面权限',
|
||||
limitsFrontEndBtn: '按钮权限',
|
||||
limitsBackEnd: '后端控制',
|
||||
limitsBackEndEndPage: '页面权限',
|
||||
personal: '个人中心',
|
||||
},
|
||||
staticRoutes: {
|
||||
signIn: '登录',
|
||||
notFound: '找不到此页面',
|
||||
noPower: '没有权限',
|
||||
},
|
||||
user: {
|
||||
title0: '组件大小',
|
||||
title1: '语言切换',
|
||||
title2: '菜单搜索',
|
||||
title3: '布局配置',
|
||||
title4: '消息',
|
||||
title5: '开全屏',
|
||||
title6: '关全屏',
|
||||
retry: '重试上线',
|
||||
onlinePrompt: '当前离线状态,是否重试上线?',
|
||||
dropdownLarge: '大型',
|
||||
dropdownDefault: '默认',
|
||||
dropdownSmall: '小型',
|
||||
dropdown1: '首页',
|
||||
dropdown2: '个人中心',
|
||||
dropdown3: '404',
|
||||
dropdown4: '401',
|
||||
dropdown5: '退出登录',
|
||||
dropdown6: '代码仓库',
|
||||
searchPlaceholder: '菜单搜索:支持中文、路由路径',
|
||||
newTitle: '通知',
|
||||
newBtn: '全部已读',
|
||||
newGo: '前往通知中心',
|
||||
newDesc: '暂无通知',
|
||||
logOutTitle: '提示',
|
||||
logOutMessage: '此操作将退出登录, 是否继续?',
|
||||
logOutConfirm: '确定',
|
||||
logOutCancel: '取消',
|
||||
logOutExit: '退出中',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: '刷新',
|
||||
close: '关闭',
|
||||
closeOther: '关闭其它',
|
||||
closeAll: '全部关闭',
|
||||
fullscreen: '当前页全屏',
|
||||
closeFullscreen: '关闭全屏',
|
||||
},
|
||||
notFound: {
|
||||
foundTitle: '地址输入错误,请重新输入地址~',
|
||||
foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
|
||||
foundBtn: '返回首页',
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: '您未被授权,没有操作权限~',
|
||||
accessMsg: '请联系管理员',
|
||||
accessBtn: '重新授权',
|
||||
},
|
||||
layout: {
|
||||
configTitle: '布局配置',
|
||||
oneTitle: '全局主题',
|
||||
twoTopTitle: '顶栏设置',
|
||||
twoMenuTitle: '菜单设置',
|
||||
twoColumnsTitle: '分栏设置',
|
||||
twoTopBar: '顶栏背景',
|
||||
twoTopBarColor: '顶栏默认字体颜色',
|
||||
twoIsTopBarColorGradual: '顶栏背景渐变',
|
||||
twoMenuBar: '菜单背景',
|
||||
twoMenuBarColor: '菜单默认字体颜色',
|
||||
twoMenuBarActiveColor: '菜单高亮背景色',
|
||||
twoIsMenuBarColorGradual: '菜单背景渐变',
|
||||
twoColumnsMenuBar: '分栏菜单背景',
|
||||
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
|
||||
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
|
||||
twoIsColumnsMenuHoverPreload: '分栏菜单滑鼠悬停预加载',
|
||||
threeTitle: '界面设置',
|
||||
threeIsCollapse: '菜单水平折叠',
|
||||
threeIsUniqueOpened: '菜单手风琴',
|
||||
threeIsFixedHeader: '固定 Header',
|
||||
threeIsClassicSplitMenu: '经典布局分割菜单',
|
||||
threeIsLockScreen: '开启锁屏',
|
||||
threeLockScreenTime: '自动锁屏(s/秒)',
|
||||
fourTitle: '界面显示',
|
||||
fourIsShowLogo: '侧边栏 Logo',
|
||||
fourIsBreadcrumb: '开启 Breadcrumb',
|
||||
fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
|
||||
fourIsTagsview: '开启 Tagsview',
|
||||
fourIsTagsviewIcon: '开启 Tagsview 图标',
|
||||
fourIsCacheTagsView: '开启 TagsView 缓存',
|
||||
fourIsSortableTagsView: '开启 TagsView 拖拽',
|
||||
fourIsShareTagsView: '开启 TagsView 共用',
|
||||
fourIsFooter: '开启 Footer',
|
||||
fourIsGrayscale: '灰色模式',
|
||||
fourIsInvert: '色弱模式',
|
||||
fourIsDark: '深色模式',
|
||||
fourIsWartermark: '开启水印',
|
||||
fourWartermarkText: '水印文案',
|
||||
fiveTitle: '其它设置',
|
||||
fiveTagsStyle: 'Tagsview 风格',
|
||||
fiveAnimation: '主页面切换动画',
|
||||
fiveColumnsAsideStyle: '分栏高亮风格',
|
||||
fiveColumnsAsideLayout: '分栏布局风格',
|
||||
sixTitle: '布局切换',
|
||||
sixDefaults: '默认',
|
||||
sixClassic: '经典',
|
||||
sixTransverse: '横向',
|
||||
sixColumns: '分栏',
|
||||
tipText: '点击下方按钮,复制布局配置去 `src/stores/themeConfig.ts` 中修改。',
|
||||
copyText: '一键复制配置',
|
||||
resetText: '一键恢复默认',
|
||||
copyTextSuccess: '复制成功!',
|
||||
copyTextError: '复制失败!',
|
||||
},
|
||||
upgrade: {
|
||||
title: '新版本升级',
|
||||
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
|
||||
desc: '提示:更新会还原默认配寘',
|
||||
btnOne: '残忍拒绝',
|
||||
btnTwo: '马上更新',
|
||||
btnTwoLoading: '更新中',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -123,7 +123,7 @@ export default {
|
||||
},
|
||||
noAccess: {
|
||||
accessTitle: '您未被授權,沒有操作許可權~',
|
||||
accessMsg: '聯繫方式:加QQ群探討665452019',
|
||||
accessMsg: '請聯系管理員',
|
||||
accessBtn: '重新授權',
|
||||
},
|
||||
layout: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 定义内容
|
||||
export default {
|
||||
label: {
|
||||
one1: '用户名登录',
|
||||
one1: '账号密码登录',
|
||||
two2: '手机号登录',
|
||||
},
|
||||
link: {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll layout-backtop-header-fixed"
|
||||
wrap-class="layout-main-scroll" view-class="layout-main-scroll">
|
||||
<LayoutParentView />
|
||||
<!-- <LayoutFooter v-if="isFooter" /> -->
|
||||
<LayoutFooter v-if="isFooter" />
|
||||
</el-scrollbar>
|
||||
<el-backtop :target="setBacktopClass" />
|
||||
</el-main>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div class="layout-footer pb15">
|
||||
<div class="layout-footer pb5 pt2">
|
||||
<div class="layout-footer-warp">
|
||||
<div>❤️ Powered by Django-Vue3-Admin ❤️</div>
|
||||
<div class="mt5">Copyright DVAdmin团队</div>
|
||||
<div>❤️ Powered by Django-Vue3-Admin Copyright © DVAdmin团队 ❤️</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
|
||||
<img :src="logoMini" class="layout-logo-medium-img" />
|
||||
<span style="font-size: x-large">{{ themeConfig.globalTitle }}</span>
|
||||
<span style="font-size: x-large">{{ getSystemConfig['login.site_title'] || themeConfig.globalTitle }}</span>
|
||||
</div>
|
||||
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
|
||||
<img :src="logoMini" class="layout-logo-size-img" />
|
||||
@@ -13,6 +13,7 @@ import { computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import logoMini from '/@/assets/logo-mini.svg';
|
||||
import {SystemConfigStore} from "/@/stores/systemConfig";
|
||||
|
||||
// 定义变量内容
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
@@ -28,6 +29,13 @@ const onThemeConfigChange = () => {
|
||||
if (themeConfig.value.layout === 'transverse') return false;
|
||||
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
|
||||
};
|
||||
|
||||
const systemConfigStore = SystemConfigStore()
|
||||
const {systemConfig} = storeToRefs(systemConfigStore)
|
||||
const getSystemConfig = computed(()=>{
|
||||
return systemConfig.value
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -57,9 +57,33 @@
|
||||
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
|
||||
></i>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="!isSocketOpen">
|
||||
<el-popconfirm
|
||||
width="250"
|
||||
ref="onlinePopoverRef"
|
||||
:confirm-button-text="$t('message.user.retry')"
|
||||
:icon="InfoFilled"
|
||||
trigger="hover"
|
||||
icon-color="#626AEF"
|
||||
:title="$t('message.user.onlinePrompt')"
|
||||
@confirm="onlineConfirmEvent"
|
||||
>
|
||||
<template #reference>
|
||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
</el-badge>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</span>
|
||||
</div>
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||
<span class="layout-navbars-breadcrumb-user-link">
|
||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
<span v-if="isSocketOpen">
|
||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
</el-badge>
|
||||
</span>
|
||||
{{ userInfos.username === '' ? 'common' : userInfos.username }}
|
||||
<el-icon class="el-icon--right">
|
||||
<ele-ArrowDown />
|
||||
@@ -79,7 +103,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutBreadcrumbUser">
|
||||
import { defineAsyncComponent, ref, computed, reactive, onMounted } from 'vue';
|
||||
import { defineAsyncComponent, ref, computed, reactive, onMounted, unref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import screenfull from 'screenfull';
|
||||
@@ -91,6 +115,8 @@ import other from '/@/utils/other';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import { Session, Local } from '/@/utils/storage';
|
||||
import headerImage from '/@/assets/img/headerImage.png';
|
||||
import websocket from '/@/utils/websocket';
|
||||
import { InfoFilled } from '@element-plus/icons-vue'
|
||||
// 引入组件
|
||||
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
|
||||
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
|
||||
@@ -118,6 +144,21 @@ const layoutUserFlexNum = computed(() => {
|
||||
else num = '';
|
||||
return num;
|
||||
});
|
||||
|
||||
// 定义变量内容
|
||||
const { isSocketOpen } = storeToRefs(useUserInfo());
|
||||
|
||||
// websocket状态
|
||||
const onlinePopoverRef = ref()
|
||||
const onlineConfirmEvent = () => {
|
||||
if (!isSocketOpen.value) {
|
||||
websocket.is_reonnect = true
|
||||
websocket.reconnect_current = 1
|
||||
websocket.reconnect()
|
||||
}
|
||||
// 手动隐藏弹出
|
||||
unref(onlinePopoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
// 全屏点击时
|
||||
const onScreenfullClick = () => {
|
||||
if (!screenfull.isEnabled) {
|
||||
@@ -256,5 +297,29 @@ const messageCenter = messageCenterStore();
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 12px;
|
||||
}
|
||||
.online-status{
|
||||
cursor: pointer;
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 30px;
|
||||
font-size: 14px;
|
||||
left: 5px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
padding: 0;
|
||||
background-color: #18bc9c;
|
||||
}
|
||||
}
|
||||
.online-down{
|
||||
cursor: pointer;
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 30px;
|
||||
font-size: 14px;
|
||||
left: 5px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
padding: 0;
|
||||
background-color: #979b9c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
<div class="upgrade-content">
|
||||
{{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
|
||||
<div class="mt5">
|
||||
<el-link type="primary" class="font12" href="https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md" target="_black">
|
||||
<el-link type="primary" class="font12" href="https://gitee.com/huge-dream/django-vue3-admin/blob/master/CHANGELOG.md" target="_black">
|
||||
CHANGELOG.md
|
||||
</el-link>
|
||||
</div>
|
||||
<div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
|
||||
</div>
|
||||
<div class="upgrade-btn">
|
||||
<el-button round size="default" type="info" text @click="onCancel">{{ $t('message.upgrade.btnOne') }}</el-button>
|
||||
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading">{{ state.btnTxt }}</el-button>
|
||||
<el-button round size="default" type="info" text @click="onCancel" >{{ $t('message.upgrade.btnOne') }}</el-button>
|
||||
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading" >{{ state.btnTxt }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -36,7 +36,7 @@ import { reactive, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { Local } from '/@/utils/storage';
|
||||
import { Local,Session } from '/@/utils/storage';
|
||||
|
||||
// 定义变量内容
|
||||
const { t } = useI18n();
|
||||
@@ -57,6 +57,7 @@ const getThemeConfig = computed(() => {
|
||||
// 残忍拒绝
|
||||
const onCancel = () => {
|
||||
state.isUpgrade = false;
|
||||
Session.set('isUpgrade', false)
|
||||
};
|
||||
// 马上更新
|
||||
const onUpgrade = () => {
|
||||
@@ -66,20 +67,24 @@ const onUpgrade = () => {
|
||||
Local.clear();
|
||||
window.location.reload();
|
||||
Local.set('version', state.version);
|
||||
Session.set('isUpgrade', false)
|
||||
}, 2000);
|
||||
};
|
||||
// 延迟显示,防止刷新时界面显示太快
|
||||
const delayShow = () => {
|
||||
setTimeout(() => {
|
||||
state.isUpgrade = true;
|
||||
}, 2000);
|
||||
const isUpgrade = Session.get('isUpgrade')===false?Session.get('isUpgrade'):true
|
||||
if(isUpgrade){
|
||||
setTimeout(() => {
|
||||
state.isUpgrade = true;
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
// delayShow();
|
||||
// setTimeout(() => {
|
||||
// state.btnTxt = t('message.upgrade.btnTwo');
|
||||
// }, 200);
|
||||
delayShow();
|
||||
setTimeout(() => {
|
||||
state.btnTxt = t('message.upgrade.btnTwo');
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import piniaPersist from 'pinia-plugin-persist';
|
||||
// @ts-ignore
|
||||
import fastCrud from './settings.ts';
|
||||
import pinia from './stores';
|
||||
import permission from '/@/plugin/permission/index';
|
||||
import {RegisterPermission} from '/@/plugin/permission/index';
|
||||
// @ts-ignore
|
||||
import eIconPicker, { iconList, analyzingIconForIconfont } from 'e-icon-picker';
|
||||
import 'e-icon-picker/icon/default-icon/symbol.js'; //基本彩色图标库
|
||||
@@ -54,7 +54,6 @@ other.elSvg(app);
|
||||
|
||||
|
||||
app.use(VXETable)
|
||||
app.use(permission);
|
||||
app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).use(fastCrud).mount('#app');
|
||||
|
||||
app.config.globalProperties.mittBus = mitt();
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import permissionDirective from './directive.permission'
|
||||
import permissionFunc from './func.permission'
|
||||
const install = function (app:any) {
|
||||
export const RegisterPermission = function (app:any) {
|
||||
app.directive('permission', permissionDirective)
|
||||
app.provide('$hasPermissions',permissionFunc.hasPermissions)
|
||||
}
|
||||
|
||||
export default {
|
||||
install
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export const BtnPermissionStore = defineStore('BtnPermission', {
|
||||
actions: {
|
||||
async getBtnPermissionStore() {
|
||||
request({
|
||||
url: '/api/system/menu/menu_button_all_permission/',
|
||||
url: '/api/system/menu_button/menu_button_all_permission/',
|
||||
method: 'get',
|
||||
}).then((ret: {
|
||||
data: []
|
||||
|
||||
@@ -12,14 +12,16 @@ import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { useMenuApi } from '/@/api/menu/index';
|
||||
import { handleMenu } from '../utils/menu';
|
||||
import { BtnPermissionStore } from '/@/plugin/permission/store.permission';
|
||||
|
||||
import {SystemConfigStore} from "/@/stores/systemConfig";
|
||||
import {useDeptInfoStore} from "/@/stores/modules/dept";
|
||||
import {DictionaryStore} from "/@/stores/dictionary";
|
||||
import {useFrontendMenuStore} from "/@/stores/frontendMenu";
|
||||
import {toRaw} from "vue";
|
||||
const menuApi = useMenuApi();
|
||||
|
||||
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
|
||||
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
|
||||
|
||||
// 后端控制路由
|
||||
|
||||
/**
|
||||
* 获取目录下的 .vue、.tsx 全部文件
|
||||
* @method import.meta.glob
|
||||
@@ -45,15 +47,36 @@ export async function initBackEndControlRoutes() {
|
||||
await useUserInfo().setUserInfos();
|
||||
// 获取路由菜单数据
|
||||
const res = await getBackEndControlRoutes();
|
||||
|
||||
// 无登录权限时,添加判断
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO
|
||||
if (res.data.length <= 0) return Promise.resolve(true);
|
||||
// 处理路由(component),替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
|
||||
dynamicRoutes[0].children = await backEndComponent(handleMenu(res.data));
|
||||
const {frameIn,frameOut} = handleMenu(res.data)
|
||||
dynamicRoutes[0].children = await backEndComponent(frameIn);
|
||||
// 添加动态路由
|
||||
await setAddRoute();
|
||||
// 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
await setFilterMenuAndCacheTagsViewRoutes();
|
||||
}
|
||||
|
||||
export async function setRouters(){
|
||||
const {frameInRoutes,frameOutRoutes} = await useFrontendMenuStore().getRouter()
|
||||
const frameInRouter = toRaw(frameInRoutes)
|
||||
const frameOutRouter = toRaw(frameOutRoutes)
|
||||
dynamicRoutes[0].children = frameInRouter
|
||||
dynamicRoutes.forEach((item:any)=>{
|
||||
router.addRoute(item)
|
||||
})
|
||||
frameOutRouter.forEach((item:any)=>{
|
||||
router.addRoute(item)
|
||||
})
|
||||
const storesRoutesList = useRoutesList(pinia);
|
||||
storesRoutesList.setRoutesList([...dynamicRoutes[0].children,...frameOutRouter]);
|
||||
const storesTagsView = useTagsViewRoutes(pinia);
|
||||
storesTagsView.setTagsViewRoutes([...dynamicRoutes[0].children,...frameOutRouter])
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
* @description 用于左侧菜单、横向菜单的显示
|
||||
@@ -81,6 +104,8 @@ export function setCacheTagsViewRoutes() {
|
||||
*/
|
||||
export function setFilterRouteEnd() {
|
||||
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
|
||||
// notFoundAndNoPower 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
|
||||
// 关联问题 No match found for location with path 'xxx'
|
||||
filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower];
|
||||
return filterRouteEnd;
|
||||
}
|
||||
@@ -105,6 +130,12 @@ export async function setAddRoute() {
|
||||
export function getBackEndControlRoutes() {
|
||||
//获取所有的按钮权限
|
||||
BtnPermissionStore().getBtnPermissionStore();
|
||||
// 获取系统配置
|
||||
SystemConfigStore().getSystemConfigs()
|
||||
// 获取所有部门信息
|
||||
useDeptInfoStore().requestDeptInfo()
|
||||
// 获取字典信息
|
||||
DictionaryStore().getSystemDictionarys()
|
||||
return menuApi.getSystemMenu();
|
||||
}
|
||||
|
||||
@@ -126,6 +157,32 @@ export function backEndComponent(routes: any) {
|
||||
if (!routes) return;
|
||||
return routes.map((item: any) => {
|
||||
if (item.component) item.component = dynamicImport(dynamicViewsModules, item.component as string);
|
||||
if(item.is_catalog){
|
||||
// 对目录的处理
|
||||
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/parent')
|
||||
}
|
||||
if(item.is_link){
|
||||
// 对外链接的处理
|
||||
if(item.is_iframe){
|
||||
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/iframes')
|
||||
}else {
|
||||
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link')
|
||||
}
|
||||
}else{
|
||||
if(item.is_iframe){
|
||||
// const iframeRoute:RouteRecordRaw = {
|
||||
// ...item
|
||||
// }
|
||||
// router.addRoute(iframeRoute)
|
||||
item.meta.isLink = item.link_url
|
||||
// item.path = `${item.path}Link`
|
||||
// item.name = `${item.name}Link`
|
||||
// item.meta.isIframe = item.is_iframe
|
||||
// item.meta.isKeepAlive = false
|
||||
// item.meta.isIframeOpen = true
|
||||
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link.vue')
|
||||
}
|
||||
}
|
||||
item.children && backEndComponent(item.children);
|
||||
return item;
|
||||
});
|
||||
|
||||
@@ -7,9 +7,12 @@ import {useKeepALiveNames} from '/@/stores/keepAliveNames';
|
||||
import {useRoutesList} from '/@/stores/routesList';
|
||||
import {useThemeConfig} from '/@/stores/themeConfig';
|
||||
import {Session} from '/@/utils/storage';
|
||||
import {staticRoutes} from '/@/router/route';
|
||||
import {dynamicRoutes, notFoundAndNoPower, staticRoutes} from '/@/router/route';
|
||||
import {initFrontEndControlRoutes} from '/@/router/frontEnd';
|
||||
import {initBackEndControlRoutes} from '/@/router/backEnd';
|
||||
import {initBackEndControlRoutes, setRouters} from '/@/router/backEnd';
|
||||
import {useFrontendMenuStore} from "/@/stores/frontendMenu";
|
||||
import {useTagsViewRoutes} from "/@/stores/tagsViewRoutes";
|
||||
import {toRaw} from "vue";
|
||||
|
||||
/**
|
||||
* 1、前端控制路由时:isRequestRoutes 为 false,需要写 roles,需要走 setFilterRoute 方法。
|
||||
@@ -32,7 +35,13 @@ const {isRequestRoutes} = themeConfig.value;
|
||||
*/
|
||||
export const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: staticRoutes,
|
||||
/**
|
||||
* 说明:
|
||||
* 1、notFoundAndNoPower 默认添加 404、401 界面,防止一直提示 No match found for location with path 'xxx'
|
||||
* 2、backEnd.ts(后端控制路由)、frontEnd.ts(前端控制路由) 中也需要加 notFoundAndNoPower 404、401 界面。
|
||||
* 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
|
||||
*/
|
||||
routes: [...notFoundAndNoPower, ...staticRoutes]
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -63,14 +72,7 @@ export function formatTwoStageRoutes(arr: any) {
|
||||
const cacheList: Array<string> = [];
|
||||
arr.forEach((v: any) => {
|
||||
if (v.path === '/') {
|
||||
newArr.push({
|
||||
component: v.component,
|
||||
name: v.name,
|
||||
path: v.path,
|
||||
redirect: v.redirect,
|
||||
meta: v.meta,
|
||||
children: []
|
||||
});
|
||||
newArr.push({component: v.component,name: v.name,path: v.path,redirect: v.redirect,meta: v.meta,children: []});
|
||||
} else {
|
||||
// 判断是否是动态路由(xx/:id/:name),用于 tagsView 等中使用
|
||||
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
||||
@@ -108,19 +110,21 @@ router.beforeEach(async (to, from, next) => {
|
||||
next('/home');
|
||||
NProgress.done();
|
||||
} else {
|
||||
|
||||
const storesRoutesList = useRoutesList(pinia);
|
||||
const {routesList} = storeToRefs(storesRoutesList);
|
||||
if (routesList.value.length === 0) {
|
||||
if (isRequestRoutes) {
|
||||
// 后端控制路由:路由数据初始化,防止刷新时丢失
|
||||
await initBackEndControlRoutes();
|
||||
// 动态添加路由:防止非首页刷新时跳转回首页的问题
|
||||
// 确保 addRoute() 时动态添加的路由已经被完全加载上去
|
||||
next({...to, replace: true});
|
||||
// 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx'
|
||||
// to.query 防止页面刷新时,普通路由带参数时,参数丢失。动态路由(xxx/:id/:name")isDynamic 无需处理
|
||||
|
||||
next({ path: to.path, query: to.query });
|
||||
} else {
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
||||
await initFrontEndControlRoutes();
|
||||
next({...to, replace: true});
|
||||
next({ path: to.path, query: to.query });
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
|
||||
@@ -88,4 +88,12 @@ export const staticRoutes: Array<RouteRecordRaw> = [
|
||||
title: '登录',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/demo',
|
||||
name: 'demo',
|
||||
component: () => import('/@/views/system/demo/index.vue'),
|
||||
meta: {
|
||||
title: 'message.router.personal'
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,127 +1,158 @@
|
||||
// 引入fast-crud
|
||||
import { FastCrud, useTypes } from '@fast-crud/fast-crud';
|
||||
const { getType } = useTypes();
|
||||
import {FastCrud, useTypes} from '@fast-crud/fast-crud';
|
||||
|
||||
const {getType} = useTypes();
|
||||
import '@fast-crud/fast-crud/dist/style.css';
|
||||
import { setLogger } from '@fast-crud/fast-crud';
|
||||
import {setLogger} from '@fast-crud/fast-crud';
|
||||
import {getBaseURL} from '/@/utils/baseUrl';
|
||||
// element
|
||||
import ui from '@fast-crud/ui-element';
|
||||
import { request } from '/@/utils/service';
|
||||
import {request} from '/@/utils/service';
|
||||
//扩展包
|
||||
import { FsExtendsEditor,FsExtendsUploader } from '@fast-crud/fast-extends';
|
||||
import {FsExtendsEditor, FsExtendsUploader,FsCropperUploader} from '@fast-crud/fast-extends';
|
||||
import '@fast-crud/fast-extends/dist/style.css';
|
||||
import { ElMessage } from "element-plus";
|
||||
import {successNotification} from '/@/utils/message';
|
||||
import XEUtils from "xe-utils";
|
||||
export default {
|
||||
async install(app: any, options: any) {
|
||||
// 先安装ui
|
||||
app.use(ui);
|
||||
// 然后安装FastCrud
|
||||
app.use(FastCrud, {
|
||||
//i18n, //i18n配置,可选,默认使用中文,具体用法请看demo里的 src/i18n/index.js 文件
|
||||
// 此处配置公共的dictRequest(字典请求)
|
||||
async dictRequest({ url }: any) {
|
||||
//根据dict的url,异步返回一个字典数组
|
||||
return await request({ url: url, }).then((res:any)=>{
|
||||
return res.data
|
||||
});
|
||||
},
|
||||
//公共crud配置
|
||||
commonOptions() {
|
||||
return {
|
||||
request: {
|
||||
//接口请求配置
|
||||
//你项目后台接口大概率与fast-crud所需要的返回结构不一致,所以需要配置此项
|
||||
//请参考文档http://fast-crud.docmirror.cn/api/crud-options/request.html
|
||||
transformQuery: ({ page, form, sort }: any) => {
|
||||
if (sort.asc !== undefined) {
|
||||
form['ordering'] = `${sort.asc ? '' : '-'}${sort.prop}`;
|
||||
}
|
||||
//转换为你pageRequest所需要的请求参数结构
|
||||
return { page: page.currentPage, limit: page.pageSize, ...form };
|
||||
},
|
||||
transformRes: ({ res }: any) => {
|
||||
//将pageRequest的返回数据,转换为fast-crud所需要的格式
|
||||
//return {records,currentPage,pageSize,total};
|
||||
if(res.page){
|
||||
return { records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total };
|
||||
}else{
|
||||
return { records: res.data,currentPage: 1, pageSize: res.data.length, total: res.data.length };
|
||||
}
|
||||
async install(app: any, options: any) {
|
||||
// 先安装ui
|
||||
app.use(ui);
|
||||
// 然后安装FastCrud
|
||||
app.use(FastCrud, {
|
||||
//i18n, //i18n配置,可选,默认使用中文,具体用法请看demo里的 src/i18n/index.js 文件
|
||||
// 此处配置公共的dictRequest(字典请求)
|
||||
async dictRequest({dict}: any) {
|
||||
const {isTree} = dict
|
||||
//根据dict的url,异步返回一个字典数组
|
||||
return await request({url: dict.url, params: dict.params || {}}).then((res: any) => {
|
||||
if (isTree) {
|
||||
return XEUtils.toArrayTree(res.data, {parentKey: 'parent'})
|
||||
}
|
||||
return res.data
|
||||
});
|
||||
},
|
||||
//公共crud配置
|
||||
commonOptions() {
|
||||
return {
|
||||
request: {
|
||||
//接口请求配置
|
||||
//你项目后台接口大概率与fast-crud所需要的返回结构不一致,所以需要配置此项
|
||||
//请参考文档http://fast-crud.docmirror.cn/api/crud-options/request.html
|
||||
transformQuery: ({page, form, sort}: any) => {
|
||||
if (sort.asc !== undefined) {
|
||||
form['ordering'] = `${sort.asc ? '' : '-'}${sort.prop}`;
|
||||
}
|
||||
//转换为你pageRequest所需要的请求参数结构
|
||||
return {page: page.currentPage, limit: page.pageSize, ...form};
|
||||
},
|
||||
transformRes: ({res}: any) => {
|
||||
//将pageRequest的返回数据,转换为fast-crud所需要的格式
|
||||
//return {records,currentPage,pageSize,total};
|
||||
return {records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total};
|
||||
},
|
||||
},
|
||||
form: {
|
||||
afterSubmit(ctx: any) {
|
||||
// 增加crud提示
|
||||
if (ctx.res.code == 2000) {
|
||||
successNotification(ctx.res.msg);
|
||||
}
|
||||
},
|
||||
},
|
||||
/* search: {
|
||||
layout: 'multi-line',
|
||||
collapse: true,
|
||||
col: {
|
||||
span: 4,
|
||||
},
|
||||
options: {
|
||||
labelCol: {
|
||||
style: {
|
||||
width: '100px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}, */
|
||||
};
|
||||
},
|
||||
});
|
||||
//富文本
|
||||
app.use(FsExtendsEditor, {
|
||||
wangEditor: {
|
||||
width: 300,
|
||||
},
|
||||
});
|
||||
// 文件上传
|
||||
app.use(FsExtendsUploader, {
|
||||
defaultType: "form",
|
||||
form: {
|
||||
action: `/api/system/file/`,
|
||||
name: "file",
|
||||
withCredentials: false,
|
||||
uploadRequest: async ({action, file, onProgress}) => {
|
||||
// @ts-ignore
|
||||
const data = new FormData();
|
||||
data.append("file", file);
|
||||
return await request({
|
||||
url: action,
|
||||
method: "post",
|
||||
timeout: 60000,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
},
|
||||
data,
|
||||
onUploadProgress: (p) => {
|
||||
onProgress({percent: Math.round((p.loaded / p.total) * 100)});
|
||||
}
|
||||
});
|
||||
},
|
||||
successHandle(ret) {
|
||||
// 上传完成后的结果处理, 此处应返回格式为{url:xxx,key:xxx}
|
||||
return {
|
||||
url: getBaseURL(ret.data.url),
|
||||
key: ret.data.id,
|
||||
...ret.data
|
||||
};
|
||||
}
|
||||
},
|
||||
valueBuilder(context){
|
||||
const {row,key} = context
|
||||
return getBaseURL(row[key])
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
},
|
||||
form: {
|
||||
afterSubmit(ctx:any ) {
|
||||
const {mode} = ctx
|
||||
if (mode === "add") {
|
||||
ElMessage.success({ message: "添加成功" });
|
||||
} else if (mode === "edit") {
|
||||
ElMessage.success({ message: "保存成功" });
|
||||
}
|
||||
},
|
||||
},
|
||||
/* search: {
|
||||
layout: 'multi-line',
|
||||
collapse: true,
|
||||
col: {
|
||||
span: 4,
|
||||
},
|
||||
options: {
|
||||
labelCol: {
|
||||
style: {
|
||||
width: '100px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}, */
|
||||
};
|
||||
},
|
||||
});
|
||||
//富文本
|
||||
app.use(FsExtendsEditor, {
|
||||
wangEditor: {
|
||||
width: 300,
|
||||
},
|
||||
});
|
||||
// 文件上传
|
||||
app.use(FsExtendsUploader, {
|
||||
defaultType: "form",
|
||||
form: {
|
||||
action: `/api/system/file/`,
|
||||
name: "file",
|
||||
withCredentials: false,
|
||||
uploadRequest: async ({ action, file, onProgress }) => {
|
||||
// @ts-ignore
|
||||
const data = new FormData();
|
||||
data.append("file", file);
|
||||
return await request({
|
||||
url: action,
|
||||
method: "post",
|
||||
timeout: 60000,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
},
|
||||
data,
|
||||
onUploadProgress: (p) => {
|
||||
onProgress({ percent: Math.round((p.loaded / p.total) * 100) });
|
||||
}
|
||||
});
|
||||
},
|
||||
successHandle(ret) {
|
||||
console.log(111,ret)
|
||||
// 上传完成后的结果处理, 此处应返回格式为{url:xxx,key:xxx}
|
||||
return {
|
||||
url: ret.data.url,
|
||||
key: ret.data.id,
|
||||
...ret.data
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
setLogger({ level: 'error' });
|
||||
// 设置自动染色
|
||||
const dictComponentList = ['dict-cascader', 'dict-checkbox', 'dict-radio', 'dict-select', 'dict-switch', 'dict-tree'];
|
||||
dictComponentList.forEach((val) => {
|
||||
getType(val).column.component.color = 'auto';
|
||||
});
|
||||
},
|
||||
setLogger({level: 'error'});
|
||||
// 设置自动染色
|
||||
const dictComponentList = ['dict-cascader', 'dict-checkbox', 'dict-radio', 'dict-select', 'dict-switch', 'dict-tree'];
|
||||
dictComponentList.forEach((val) => {
|
||||
getType(val).column.component.color = 'auto';
|
||||
getType(val).column.align = 'center';
|
||||
});
|
||||
// 设置placeholder 的默认值
|
||||
const placeholderComponentList = [
|
||||
{key: 'text', placeholder: "请输入"},
|
||||
{key: 'textarea', placeholder: "请输入"},
|
||||
{key: 'input', placeholder: "请输入"},
|
||||
{key: 'password', placeholder: "请输入"}
|
||||
]
|
||||
placeholderComponentList.forEach((val) => {
|
||||
if (getType(val.key)?.search?.component) {
|
||||
getType(val.key).search.component.placeholder = val.placeholder;
|
||||
} else if (getType(val.key)?.search) {
|
||||
getType(val.key).search.component = {placeholder: val.placeholder};
|
||||
}
|
||||
if (getType(val.key)?.form?.component) {
|
||||
getType(val.key).form.component.placeholder = val.placeholder;
|
||||
} else if (getType(val.key)?.form) {
|
||||
getType(val.key).form.component = {placeholder: val.placeholder};
|
||||
}
|
||||
if (getType(val.key)?.column?.align) {
|
||||
getType(val.key).column.align = 'center'
|
||||
} else if (getType(val.key)?.column) {
|
||||
getType(val.key).column = {align: 'center'};
|
||||
} else if (getType(val.key)) {
|
||||
getType(val.key).column = {align: 'center'};
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
169
web/src/stores/frontendMenu.ts
Normal file
169
web/src/stores/frontendMenu.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {FrontendMenu} from './interface';
|
||||
import {Session} from '/@/utils/storage';
|
||||
import {request} from '../utils/service';
|
||||
import XEUtils from "xe-utils";
|
||||
import {RouteRecordRaw} from "vue-router";
|
||||
import {useKeepALiveNames} from "/@/stores/keepAliveNames";
|
||||
import pinia from "/@/stores/index";
|
||||
|
||||
|
||||
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
|
||||
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
|
||||
|
||||
|
||||
/**
|
||||
* 获取目录下的 .vue、.tsx 全部文件
|
||||
* @method import.meta.glob
|
||||
* @link 参考:https://cn.vitejs.dev/guide/features.html#json
|
||||
*/
|
||||
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...layouModules }, { ...viewsModules });
|
||||
|
||||
/**
|
||||
* 后端路由 component 转换函数
|
||||
* @param dynamicViewsModules 获取目录下的 .vue、.tsx 全部文件
|
||||
* @param component 当前要处理项 component
|
||||
* @returns 返回处理成函数后的 component
|
||||
*/
|
||||
export function dynamicImport(dynamicViewsModules: Record<string, Function>, component: string) {
|
||||
const keys = Object.keys(dynamicViewsModules);
|
||||
const matchKeys = keys.filter((key) => {
|
||||
const k = key.replace(/..\/views|../, '');
|
||||
return k.startsWith(`${component}`) || k.startsWith(`/${component}`);
|
||||
});
|
||||
if (matchKeys?.length === 1) {
|
||||
const matchKey = matchKeys[0];
|
||||
return dynamicViewsModules[matchKey];
|
||||
}
|
||||
if (matchKeys?.length > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 处理后端菜单数据格式
|
||||
* @param {Array} menuData
|
||||
* @return {*}
|
||||
*/
|
||||
export const handleMenu = (menuData: Array<any>) => {
|
||||
// 框架内路由
|
||||
const frameInRoutes:Array<any> = []
|
||||
// 框架外路由
|
||||
const frameOutRoutes:Array<any> = []
|
||||
// 需要缓存的路由
|
||||
const cacheList:Array<any> = []
|
||||
// 先处理menu meta数据转换
|
||||
const handleMeta = (item: any) => {
|
||||
item.path = item.web_path
|
||||
item.meta = {
|
||||
title: item.title,
|
||||
isLink: item.link_url,
|
||||
isHide: !item.visible,
|
||||
isKeepAlive: item.cache,
|
||||
isAffix: item.is_affix,
|
||||
isIframe: item.is_iframe,
|
||||
roles: ['admin'],
|
||||
icon: item.icon
|
||||
}
|
||||
item.component = dynamicImport(dynamicViewsModules, item.component as string)
|
||||
if(item.is_catalog){
|
||||
// 对目录的处理
|
||||
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/parent')
|
||||
}
|
||||
if(item.is_link){
|
||||
// 对外链接的处理
|
||||
item.meta.isIframe = !item.is_iframe
|
||||
if(item.is_iframe){
|
||||
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link')
|
||||
}else {
|
||||
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/iframes')
|
||||
}
|
||||
}else{
|
||||
if(item.is_iframe){
|
||||
const route = JSON.parse(JSON.stringify(item))
|
||||
route.meta.isLink = ''
|
||||
route.path = `${item.web_path}`
|
||||
route.name = `${item.name}`
|
||||
route.meta.isIframe = true
|
||||
route.meta.isKeepAlive = false
|
||||
route.meta.isIframeOpen = true
|
||||
route.component = item.component
|
||||
frameOutRoutes.push(route)
|
||||
item.path = `${item.web_path}FrameOut`
|
||||
item.name = `${item.name}FrameOut`
|
||||
item.meta.isLink = item.web_path
|
||||
item.meta.isIframe = !item.is_iframe
|
||||
//item.meta.isIframeOpen = true
|
||||
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link.vue')
|
||||
}
|
||||
}
|
||||
item.children && handleMeta(item.children);
|
||||
if (item.meta.isKeepAlive && item.meta.isKeepAlive && item.component_name != "") {
|
||||
cacheList.push(item.name);
|
||||
}
|
||||
return item
|
||||
}
|
||||
menuData.forEach((val) => {
|
||||
frameInRoutes.push(handleMeta(val))
|
||||
})
|
||||
const stores = useKeepALiveNames(pinia);
|
||||
stores.setCacheKeepAlive(cacheList);
|
||||
const data = XEUtils.toArrayTree(frameInRoutes, {
|
||||
parentKey: 'parent',
|
||||
strict: true,
|
||||
})
|
||||
const dynamicRoutes = [
|
||||
{
|
||||
path: '/home', name: 'home',
|
||||
component: dynamicImport(dynamicViewsModules, '/system/home/index'),
|
||||
meta: {
|
||||
title: 'message.router.home',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: true,
|
||||
isIframe: false,
|
||||
roles: ['admin'],
|
||||
icon: 'iconfont icon-shouye'
|
||||
}
|
||||
},
|
||||
...data
|
||||
]
|
||||
return {frameIn:dynamicRoutes,frameOut:frameOutRoutes}
|
||||
}
|
||||
|
||||
export const useFrontendMenuStore = defineStore('frontendMenu',{
|
||||
state: (): FrontendMenu => ({
|
||||
arrayRouter: [],
|
||||
treeRouter: [],
|
||||
frameInRoutes:[],
|
||||
frameOutRoutes:[]
|
||||
}),
|
||||
actions:{
|
||||
async requestMenu(){
|
||||
return request({
|
||||
url: '/api/system/menu/web_router/',
|
||||
method: 'get',
|
||||
params:{},
|
||||
}).then((res:any)=>{
|
||||
return res.data
|
||||
});
|
||||
},
|
||||
async handleRouter(){
|
||||
const menuData = await this.requestMenu();
|
||||
this.arrayRouter = menuData
|
||||
const {frameIn,frameOut} = handleMenu(menuData);
|
||||
this.treeRouter = [...frameIn,...frameOut]
|
||||
this.frameInRoutes=frameIn
|
||||
this.frameOutRoutes=frameOut
|
||||
},
|
||||
async getRouter(){
|
||||
await this.handleRouter()
|
||||
return {
|
||||
frameInRoutes:this.frameInRoutes,
|
||||
frameOutRoutes:this.frameOutRoutes,
|
||||
treeRouter:this.treeRouter
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -2,6 +2,7 @@
|
||||
* 定义接口来定义对象的类型
|
||||
* `stores` 全部类型定义在这里
|
||||
*/
|
||||
import {useFrontendMenuStore} from "/@/stores/frontendMenu";
|
||||
|
||||
// 用户信息
|
||||
export interface UserInfosState {
|
||||
@@ -19,6 +20,7 @@ export interface UserInfosState {
|
||||
}
|
||||
export interface UserInfosStates {
|
||||
userInfos: UserInfosState;
|
||||
isSocketOpen: boolean
|
||||
}
|
||||
|
||||
// 路由缓存列表
|
||||
@@ -101,3 +103,12 @@ export interface DictionaryStates {
|
||||
export interface ConfigStates {
|
||||
systemConfig: any;
|
||||
}
|
||||
|
||||
export interface FrontendMenu {
|
||||
arrayRouter: Array<any>;
|
||||
treeRouter:Array<any>;
|
||||
|
||||
frameOutRoutes:Array<any>;
|
||||
|
||||
frameInRoutes:Array<any>;
|
||||
}
|
||||
|
||||
30
web/src/stores/modules/dept.ts
Normal file
30
web/src/stores/modules/dept.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {defineStore} from "pinia";
|
||||
import {request} from "/@/utils/service";
|
||||
import XEUtils from "xe-utils";
|
||||
import {toRaw} from 'vue'
|
||||
export const useDeptInfoStore = defineStore('deptInfo', {
|
||||
state:()=>(
|
||||
{
|
||||
list:[],
|
||||
tree:[],
|
||||
}
|
||||
),
|
||||
actions:{
|
||||
async requestDeptInfo() {
|
||||
// 请求部门信息
|
||||
const ret = await request({
|
||||
url: '/api/system/dept/all_dept/'
|
||||
})
|
||||
this.list = ret.data
|
||||
this.tree = XEUtils.toArrayTree(ret.data,{parentKey:'parent',strict:true})
|
||||
},
|
||||
async getDeptById(id:any){
|
||||
|
||||
},
|
||||
async getParentDeptById(id: any){
|
||||
const tree = toRaw(this.tree)
|
||||
const obj = XEUtils.findTree(tree, item => item.id == id)
|
||||
return obj
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -22,5 +22,8 @@ export const useRoutesList = defineStore('routesList', {
|
||||
async setColumnsNavHover(bool: Boolean) {
|
||||
this.isColumnsNavHover = bool;
|
||||
},
|
||||
async addRoutesList(data: Array<string>) {
|
||||
this.routesList.push(data);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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/',
|
||||
|
||||
@@ -256,6 +256,13 @@
|
||||
.el-button.is-text {
|
||||
padding: 0;
|
||||
}
|
||||
th.el-table__cell{
|
||||
-webkit-user-select: text !important;
|
||||
-moz-user-select: text !important;
|
||||
-ms-user-select: text !important;
|
||||
user-select:text !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* scrollbar
|
||||
|
||||
4
web/src/theme/fa/css/font-awesome.min.css
vendored
Normal file
4
web/src/theme/fa/css/font-awesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
web/src/theme/fa/fonts/FontAwesome.otf
Normal file
BIN
web/src/theme/fa/fonts/FontAwesome.otf
Normal file
Binary file not shown.
BIN
web/src/theme/fa/fonts/fontawesome-webfont.eot
Normal file
BIN
web/src/theme/fa/fonts/fontawesome-webfont.eot
Normal file
Binary file not shown.
2671
web/src/theme/fa/fonts/fontawesome-webfont.svg
Normal file
2671
web/src/theme/fa/fonts/fontawesome-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 434 KiB |
BIN
web/src/theme/fa/fonts/fontawesome-webfont.ttf
Normal file
BIN
web/src/theme/fa/fonts/fontawesome-webfont.ttf
Normal file
Binary file not shown.
BIN
web/src/theme/fa/fonts/fontawesome-webfont.woff
Normal file
BIN
web/src/theme/fa/fonts/fontawesome-webfont.woff
Normal file
Binary file not shown.
BIN
web/src/theme/fa/fonts/fontawesome-webfont.woff2
Normal file
BIN
web/src/theme/fa/fonts/fontawesome-webfont.woff2
Normal file
Binary file not shown.
@@ -5,3 +5,4 @@
|
||||
@import './media/media.scss';
|
||||
@import './waves.scss';
|
||||
@import './dark.scss';
|
||||
@import './fa/css/font-awesome.min.css';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { pluginsAll } from '/@/views/plugins/index';
|
||||
/**
|
||||
* @description 校验是否为租户模式。租户模式把域名替换成 域名 加端口
|
||||
*/
|
||||
export const getBaseURL = function () {
|
||||
var baseURL = import.meta.env.VITE_API_URL as any;
|
||||
var param = baseURL.split('/')[3] || '';
|
||||
export const getBaseURL = function (url:string) {
|
||||
let baseURL = import.meta.env.VITE_API_URL as any;
|
||||
let param = baseURL.split('/')[3] || '';
|
||||
// @ts-ignore
|
||||
if (pluginsAll && pluginsAll.indexOf('dvadmin3-tenants-web') !== -1 && (!param || baseURL.startsWith('/'))) {
|
||||
// 1.把127.0.0.1 替换成和前端一样域名
|
||||
@@ -26,6 +26,16 @@ export const getBaseURL = function () {
|
||||
baseURL = location.protocol + '//' + location.hostname + (location.port ? ':' : '') + location.port + baseURL;
|
||||
}
|
||||
}
|
||||
if(url){
|
||||
const regex = /^(http|https):\/\//;
|
||||
if(regex.test(url)){
|
||||
return url
|
||||
}else{
|
||||
if(url.startsWith('/')){
|
||||
return baseURL + url;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!baseURL.endsWith('/')) {
|
||||
baseURL += '/';
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useColumnPermission } from '/@/stores/columnPermission';
|
||||
import {useColumnPermission} from '/@/stores/columnPermission';
|
||||
|
||||
type permissionType = 'is_create' | 'is_query' | 'is_update';
|
||||
|
||||
@@ -7,3 +7,54 @@ export const columnPermission = (key: string, type: permissionType): boolean =>
|
||||
|
||||
return !!permissions.some((i) => i.field_name === key && i[type]);
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理字段信息权限
|
||||
* @param func 获取字段信息的接口函数
|
||||
* @param crudOptions 原始的crudOptions信息
|
||||
* @param excludeColumn 需要排除的列
|
||||
*/
|
||||
export const handleColumnPermission = async (func: Function, crudOptions: any,excludeColumn:string[]=[]) => {
|
||||
const res = await func();
|
||||
if(crudOptions.pagination==undefined){
|
||||
crudOptions['pagination'] = {
|
||||
show:true
|
||||
}
|
||||
}
|
||||
const columns = crudOptions.columns;
|
||||
const excludeColumns = ['_index','id', 'create_datetime', 'update_datetime'].concat(excludeColumn)
|
||||
for (let col in columns) {
|
||||
if (excludeColumns.includes(col)) {
|
||||
continue
|
||||
}else{
|
||||
if (columns[col].column) {
|
||||
columns[col].column.show = false
|
||||
} else {
|
||||
columns[col]['column'] = {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
columns[col].addForm = {
|
||||
show: false
|
||||
}
|
||||
columns[col].editForm = {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
|
||||
for (let item of res.data) {
|
||||
if (excludeColumns.includes(item.field_name)) {
|
||||
continue
|
||||
} else if(item.field_name === col) {
|
||||
columns[col].column.show = item['is_query']
|
||||
columns[col].addForm = {
|
||||
show: item['is_create']
|
||||
}
|
||||
columns[col].editForm = {
|
||||
show: item['is_update']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return crudOptions
|
||||
}
|
||||
|
||||
166
web/src/utils/commonCrud.ts
Normal file
166
web/src/utils/commonCrud.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { dict } from "@fast-crud/fast-crud";
|
||||
import {shallowRef} from 'vue'
|
||||
import deptFormat from "/@/components/dept-format/index.vue";
|
||||
export const commonCrudConfig = (options = {
|
||||
create_datetime: {
|
||||
form: false,
|
||||
table: false,
|
||||
search: false
|
||||
},
|
||||
update_datetime: {
|
||||
form: false,
|
||||
table: false,
|
||||
search: false
|
||||
},
|
||||
creator_name: {
|
||||
form: false,
|
||||
table: false,
|
||||
search: false
|
||||
},
|
||||
modifier_name: {
|
||||
form: false,
|
||||
table: false,
|
||||
search: false
|
||||
},
|
||||
dept_belong_id: {
|
||||
form: false,
|
||||
table: false,
|
||||
search: false
|
||||
},
|
||||
description: {
|
||||
form: false,
|
||||
table: false,
|
||||
search: false
|
||||
},
|
||||
}) => {
|
||||
return {
|
||||
dept_belong_id: {
|
||||
title: '所属部门',
|
||||
type: 'dict-tree',
|
||||
search: {
|
||||
show: options.dept_belong_id?.search || false
|
||||
},
|
||||
dict: dict({
|
||||
url: '/api/system/dept/all_dept/',
|
||||
isTree: true,
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
children: 'children',
|
||||
}),
|
||||
column: {
|
||||
align: 'center',
|
||||
width: 300,
|
||||
show: options.dept_belong_id?.table || false,
|
||||
component:{
|
||||
name: shallowRef(deptFormat),
|
||||
vModel: "modelValue",
|
||||
}
|
||||
},
|
||||
form: {
|
||||
show: options.dept_belong_id?.form || false,
|
||||
component: {
|
||||
multiple: false,
|
||||
clearable: true,
|
||||
props: {
|
||||
checkStrictly:true,
|
||||
props: {
|
||||
// 为什么这里要写两层props
|
||||
// 因为props属性名与fs的动态渲染的props命名冲突,所以要多写一层
|
||||
label: "name",
|
||||
value: "id",
|
||||
}
|
||||
}
|
||||
},
|
||||
helper: "默认不填则为当前创建用户的部门ID"
|
||||
}
|
||||
},
|
||||
description: {
|
||||
title: '备注',
|
||||
search: {
|
||||
show: options.description?.search || false
|
||||
},
|
||||
type: 'textarea',
|
||||
column: {
|
||||
width: 100,
|
||||
show: options.description?.table || false,
|
||||
},
|
||||
form: {
|
||||
show: options.description?.form || false,
|
||||
component: {
|
||||
placeholder: '请输入内容',
|
||||
showWordLimit: true,
|
||||
maxlength: '200',
|
||||
}
|
||||
},
|
||||
viewForm: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
modifier_name: {
|
||||
title: '修改人',
|
||||
search: {
|
||||
show: options.modifier_name?.search || false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
show: options.modifier_name?.table || false,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
viewForm: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
creator_name: {
|
||||
title: '创建人',
|
||||
search: {
|
||||
show: options.creator_name?.search || false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
show: options.creator_name?.table || false,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
viewForm: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
update_datetime: {
|
||||
title: '更新时间',
|
||||
type: 'datetime',
|
||||
search: {
|
||||
show: options.update_datetime?.search || false
|
||||
},
|
||||
column: {
|
||||
width: 160,
|
||||
show: options.update_datetime?.table || false,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
viewForm: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
create_datetime: {
|
||||
title: '创建时间',
|
||||
type: 'datetime',
|
||||
search: {
|
||||
show: options.create_datetime?.search || false
|
||||
},
|
||||
column: {
|
||||
width: 160,
|
||||
show: options.create_datetime?.table || false,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
viewForm: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,13 @@ import { DictionaryStore } from '/@/stores/dictionary';
|
||||
/**
|
||||
* @method 获取指定name字典
|
||||
*/
|
||||
export const dictionary = (key: string,value:string|number|null=null) => {
|
||||
export const dictionary = (name: string,key:string|number|undefined) => {
|
||||
const dict = DictionaryStore()
|
||||
const dictionary = toRaw(dict.data)
|
||||
if(value!==null){
|
||||
for (let item of dictionary[key]) {
|
||||
if (item.value === value) {
|
||||
return item.label
|
||||
}
|
||||
}
|
||||
return []
|
||||
if(key!=undefined){
|
||||
const obj = dictionary[name].find((item:any) => item.value === key)
|
||||
return obj?obj.label:''
|
||||
}else{
|
||||
return dictionary[name]
|
||||
}
|
||||
return dictionary[key]
|
||||
}
|
||||
|
||||
@@ -50,6 +50,17 @@ const getAwesomeIconfont = () => {
|
||||
const styles: any = document.styleSheets;
|
||||
let sheetsList = [];
|
||||
let sheetsIconList = [];
|
||||
// 判断fontFamily是否是本地加载
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
const rules = styles[i].cssRules || styles[i].rules;
|
||||
if (rules) {
|
||||
for (let j = 0; j < rules.length; j++) {
|
||||
if (rules[j].style && rules[j].style.fontFamily === 'FontAwesome') {
|
||||
sheetsList.push(styles[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
if (styles[i].href && styles[i].href.indexOf('netdna.bootstrapcdn.com') > -1) {
|
||||
sheetsList.push(styles[i]);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import XEUtils from "xe-utils"
|
||||
import {dynamicRoutes, staticRoutes} from "/@/router/route";
|
||||
|
||||
/**
|
||||
* @description: 处理后端菜单数据格式
|
||||
@@ -10,27 +11,56 @@ export const handleMenu = (menuData: Array<any>) => {
|
||||
const handleMeta = (item: any) => {
|
||||
item.meta = {
|
||||
title: item.title,
|
||||
isLink: item.is_link,
|
||||
isLink: item.link_url,
|
||||
isHide: !item.visible,
|
||||
isKeepAlive: item.cache,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
isAffix: item.is_affix,
|
||||
isIframe: item.is_iframe,
|
||||
roles: ['admin'],
|
||||
icon: item.icon
|
||||
}
|
||||
item.name = item.component_name
|
||||
item.path = item.web_path
|
||||
return item
|
||||
}
|
||||
menuData.forEach((val) => {
|
||||
handleMeta(val)
|
||||
val.path = val.web_path
|
||||
})
|
||||
|
||||
const data = XEUtils.toArrayTree(menuData, {
|
||||
// 处理框架外的路由
|
||||
const handleFrame = (item: any) => {
|
||||
if (item.is_iframe) {
|
||||
item.meta = {
|
||||
title: item.title,
|
||||
isLink: item.link_url,
|
||||
isHide: !item.visible,
|
||||
isKeepAlive: item.cache,
|
||||
isAffix: item.is_affix,
|
||||
isIframe: item.is_iframe,
|
||||
roles: ['admin'],
|
||||
icon: item.icon
|
||||
}
|
||||
item.name = item.component_name
|
||||
item.path = item.web_path
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
// 框架内路由
|
||||
const defaultRoutes:Array<any> = []
|
||||
// 框架外路由
|
||||
const iframeRoutes:Array<any> = []
|
||||
|
||||
menuData.forEach((val) => {
|
||||
// if (val.is_iframe) {
|
||||
// // iframeRoutes.push(handleFrame(val))
|
||||
// } else {
|
||||
// defaultRoutes.push(handleMeta(val))
|
||||
// }
|
||||
defaultRoutes.push(handleMeta(val))
|
||||
})
|
||||
const data = XEUtils.toArrayTree(defaultRoutes, {
|
||||
parentKey: 'parent',
|
||||
strict: true,
|
||||
})
|
||||
const menu = [
|
||||
const dynamicRoutes = [
|
||||
{
|
||||
path: '/home', name: 'home', component: '/system/home/index', meta: {
|
||||
title: 'message.router.home',
|
||||
@@ -45,5 +75,5 @@ export const handleMenu = (menuData: Array<any>) => {
|
||||
},
|
||||
...data
|
||||
]
|
||||
return menu
|
||||
return {frameIn:dynamicRoutes,frameOut:iframeRoutes}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ function createService() {
|
||||
// window.location.reload();
|
||||
break;
|
||||
case 401:
|
||||
Local.clear();
|
||||
// Local.clear();
|
||||
Session.clear();
|
||||
dataAxios.msg = '登录认证失败,请重新登录';
|
||||
ElMessageBox.alert(dataAxios.msg, '提示', {
|
||||
@@ -96,12 +96,13 @@ function createService() {
|
||||
return dataAxios;
|
||||
case 4000:
|
||||
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
|
||||
return dataAxios;
|
||||
break;
|
||||
default:
|
||||
// 不是正确的 code
|
||||
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
|
||||
return dataAxios;
|
||||
break;
|
||||
}
|
||||
return Promise.reject(dataAxios);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
@@ -111,7 +112,7 @@ function createService() {
|
||||
error.message = '请求错误';
|
||||
break;
|
||||
case 401:
|
||||
Local.clear();
|
||||
// Local.clear();
|
||||
Session.clear();
|
||||
error.message = '登录授权过期,请重新登录';
|
||||
ElMessageBox.alert(error.message, '提示', {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
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);
|
||||
},
|
||||
/**
|
||||
* 重新连接
|
||||
|
||||
@@ -12,5 +12,5 @@ export const scanAndInstallPlugins = (app: any) => {
|
||||
pluginNames.add(pluginsName);
|
||||
}
|
||||
pluginsAll = Array.from(pluginNames);
|
||||
console.table('已注册插件:', pluginsAll);
|
||||
console.log('已发现插件:', pluginsAll);
|
||||
};
|
||||
|
||||
@@ -1,225 +1,244 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {
|
||||
dict,
|
||||
UserPageQuery,
|
||||
AddReq,
|
||||
DelReq,
|
||||
EditReq,
|
||||
compute,
|
||||
CreateCrudOptionsProps,
|
||||
CreateCrudOptionsRet
|
||||
} from '@fast-crud/fast-crud';
|
||||
import {dictionary} from '/@/utils/dictionary';
|
||||
import {successMessage} from '/@/utils/message';
|
||||
import {auth} from "/@/utils/authFunction";
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({form, row}: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({row}: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({form}: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
/**
|
||||
* 懒加载
|
||||
* @param row
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
|
||||
pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
|
||||
resolve(res.data);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 懒加载
|
||||
* @param row
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
|
||||
pageRequest({pcode: tree.code}).then((res: APIResponseData) => {
|
||||
resolve(res.data);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
},
|
||||
table: {
|
||||
rowKey: 'id',
|
||||
lazy: true,
|
||||
load: loadContentMethod,
|
||||
treeProps: { children: 'children', hasChildren: 'hasChild' },
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
// pcode: {
|
||||
// title: '父级地区',
|
||||
// show: false,
|
||||
// search: {
|
||||
// show: true,
|
||||
// },
|
||||
// type: 'dict-tree',
|
||||
// form: {
|
||||
// component: {
|
||||
// showAllLevels: false, // 仅显示最后一级
|
||||
// props: {
|
||||
// elProps: {
|
||||
// clearable: true,
|
||||
// showAllLevels: false, // 仅显示最后一级
|
||||
// props: {
|
||||
// checkStrictly: true, // 可以不需要选到最后一级
|
||||
// emitPath: false,
|
||||
// clearable: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
name: {
|
||||
title: '名称',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
treeNode: true,
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '名称必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
code: {
|
||||
title: '地区编码',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 90,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '地区编码必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入地区编码',
|
||||
},
|
||||
},
|
||||
},
|
||||
pinyin: {
|
||||
title: '拼音',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '拼音必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
},
|
||||
},
|
||||
},
|
||||
level: {
|
||||
title: '地区层级',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: false,
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '拼音必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
},
|
||||
},
|
||||
},
|
||||
initials: {
|
||||
title: '首字母',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '首字母必填项' },
|
||||
],
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('area:Create'),
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: auth('area:Update')
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: auth('area:Delete')
|
||||
},
|
||||
},
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
},
|
||||
table: {
|
||||
rowKey: 'id',
|
||||
lazy: true,
|
||||
load: loadContentMethod,
|
||||
treeProps: {children: 'children', hasChildren: 'hasChild'},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: {show: false},
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
// pcode: {
|
||||
// title: '父级地区',
|
||||
// show: false,
|
||||
// search: {
|
||||
// show: true,
|
||||
// },
|
||||
// type: 'dict-tree',
|
||||
// form: {
|
||||
// component: {
|
||||
// showAllLevels: false, // 仅显示最后一级
|
||||
// props: {
|
||||
// elProps: {
|
||||
// clearable: true,
|
||||
// showAllLevels: false, // 仅显示最后一级
|
||||
// props: {
|
||||
// checkStrictly: true, // 可以不需要选到最后一级
|
||||
// emitPath: false,
|
||||
// clearable: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
name: {
|
||||
title: '名称',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
treeNode: true,
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '名称必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
code: {
|
||||
title: '地区编码',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 90,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '地区编码必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入地区编码',
|
||||
},
|
||||
},
|
||||
},
|
||||
pinyin: {
|
||||
title: '拼音',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '拼音必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
},
|
||||
},
|
||||
},
|
||||
level: {
|
||||
title: '地区层级',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: false,
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '拼音必填项'},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
},
|
||||
},
|
||||
},
|
||||
initials: {
|
||||
title: '首字母',
|
||||
column: {
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '首字母必填项'},
|
||||
],
|
||||
|
||||
component: {
|
||||
placeholder: '请输入首字母',
|
||||
},
|
||||
},
|
||||
},
|
||||
enable: {
|
||||
title: '是否启用',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
minWidth:90,
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
activeText: '',
|
||||
inactiveText: '',
|
||||
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
||||
onChange: compute((context) => {
|
||||
return () => {
|
||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
});
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
component: {
|
||||
placeholder: '请输入首字母',
|
||||
},
|
||||
},
|
||||
},
|
||||
enable: {
|
||||
title: '是否启用',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
minWidth: 90,
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
activeText: '',
|
||||
inactiveText: '',
|
||||
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
||||
onChange: compute((context) => {
|
||||
return () => {
|
||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
});
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
list-type="picture-card"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
|
||||
<div slot="tip" class="el-upload__tip">请选取图片,并且只能上传jpg/png文件</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogImgVisible">
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
@@ -150,7 +150,7 @@
|
||||
list-type="picture-card"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
|
||||
<div slot="tip" class="el-upload__tip">请选取图片,并且只能上传jpg/png文件</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogImgVisible">
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
@@ -506,4 +506,8 @@ watch(
|
||||
);
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style scoped>
|
||||
:deep(.el-upload-list--picture-card){
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
13
web/src/views/system/demo/index.vue
Normal file
13
web/src/views/system/demo/index.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
测试框架外显示
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="demo">
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -19,7 +19,7 @@
|
||||
<el-input v-model="deptFormData.key" />
|
||||
</el-form-item>
|
||||
<el-form-item label="负责人">
|
||||
<el-input v-model="deptFormData.owner" />
|
||||
<el-input v-model="deptFormData.owner" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="deptFormData.description" maxlength="200" show-word-limit type="textarea" />
|
||||
|
||||
@@ -4,6 +4,7 @@ import { request } from '/@/utils/service';
|
||||
import * as api from './api';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {auth} from "/@/utils/authFunction";
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
@@ -12,12 +13,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
/**
|
||||
* 处理crud警告:Invalid prop: type check failed for prop "name". Expected String with value "2", got Number with value 2.
|
||||
*/
|
||||
res.data.forEach((item: any) => {
|
||||
item.dept = String(item.dept);
|
||||
if (item.role && Array.isArray(item.role) && item.role.length > 0) {
|
||||
item.role = item.role.map((r: number) => String(r));
|
||||
}
|
||||
});
|
||||
// res.data.forEach((item: any) => {
|
||||
// item.dept = String(item.dept);
|
||||
// if (item.role && Array.isArray(item.role) && item.role.length > 0) {
|
||||
// item.role = item.role.map((r: number) => String(r));
|
||||
// }
|
||||
// });
|
||||
return res;
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
@@ -39,9 +40,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
return await api.exportData(query);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions: any = inject('$hasPermissions');
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
table: {
|
||||
@@ -58,12 +56,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: hasPermissions('user:Create'),
|
||||
// show:true
|
||||
show: auth('user:Create')
|
||||
},
|
||||
export: {
|
||||
text: '导出', //按钮文字
|
||||
title: '导出', //鼠标停留显示的信息
|
||||
show: auth('user:Export'),
|
||||
click() {
|
||||
return exportRequest(crudExpose!.getSearchFormData());
|
||||
},
|
||||
@@ -89,15 +87,15 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
show: hasPermissions('user:Update'),
|
||||
show: auth('user:Update'),
|
||||
},
|
||||
remove: {
|
||||
show: hasPermissions('user:Delete'),
|
||||
show: auth('user:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '重设密码',
|
||||
type: 'primary',
|
||||
show: hasPermissions('user:ResetPassword'),
|
||||
show: auth('user:ResetPassword'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '重设密码',
|
||||
@@ -220,13 +218,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
url: '/api/system/dept/all_dept/',
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
getData: async ({ url }: { url: string }) => {
|
||||
return request({
|
||||
url: url,
|
||||
}).then((ret: any) => {
|
||||
return ret.data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column: {
|
||||
minWidth: 150, //最小列宽
|
||||
@@ -266,18 +257,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,
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
},
|
||||
}).then((ret: any) => {
|
||||
return ret.data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<el-button :icon="!showCount ? 'Hide' : 'View'" circle @click="showCount = !showCount"></el-button>
|
||||
</template>
|
||||
<template #actionbar-right>
|
||||
<importExcel api="api/system/user/">导入 </importExcel>
|
||||
<importExcel api="api/system/user/" v-auth="'user:Import'">导入 </importExcel>
|
||||
</template>
|
||||
</fs-crud>
|
||||
|
||||
@@ -251,10 +251,10 @@ const handleResetPwdSubmit = async () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
deptCountChart = init(deptCountBar.value as HTMLElement);
|
||||
deptSexChart = init(deptSexPie.value as HTMLElement);
|
||||
getDeptInfo();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
|
||||
@@ -57,7 +57,7 @@ const getData = async () => {
|
||||
const result = XEUtils.toArrayTree(res.data, {
|
||||
parentKey: 'parent',
|
||||
children: 'children',
|
||||
strict: true,
|
||||
//strict: true,
|
||||
});
|
||||
|
||||
deptTreeData.value = result;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user