175
README.en.md
175
README.en.md
@@ -1,175 +0,0 @@
|
||||
# Django-Vue3-Admin
|
||||
|
||||
[](https://gitee.com/huge-dream/django-vue3-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/huge-dream/django-vue3-admin)
|
||||
|
||||
[preview](https://demo.dvadmin.com) | [Official website document](https://www.django-vue-admin.com) | [qq group](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [community](https://bbs.django-vue-admin.com) | [plugins market](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
|
||||
|
||||
💡 **「About」**
|
||||
|
||||
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.
|
||||
|
||||
* 🧑🤝🧑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)
|
||||
|
||||
* demo account:superadmin
|
||||
|
||||
* demo password:admin123456
|
||||
|
||||
👩👦👦docs:[https://django-vue-admin.com](https://django-vue-admin.com)
|
||||
|
||||
## communication
|
||||
|
||||
* Communication community:[click here](https://bbs.django-vue-admin.com)👩👦👦
|
||||
|
||||
* plugins market:[click here](https://bbs.django-vue-admin.com/plugMarket.html)👩👦👦
|
||||
|
||||
## source code url:
|
||||
|
||||
gitee(Main push):[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
github:[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
## core function
|
||||
|
||||
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
|
||||
|
||||
## before start project you need:
|
||||
|
||||
~~~
|
||||
Python >= 3.11.0 (Minimum version 3.9+)
|
||||
Node.js >= 16.0
|
||||
Mysql >= 8.0 (Optional, default database: SQLite3, supports 5.7+, recommended version: 8.0)
|
||||
Redis (Optional, latest version)
|
||||
~~~
|
||||
|
||||
## frontend♝
|
||||
|
||||
```bash
|
||||
# clone code
|
||||
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||
|
||||
# enter code dir
|
||||
cd web
|
||||
|
||||
# install dependence
|
||||
npm install yarn
|
||||
yarn install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# 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. 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
|
||||
|
||||
* visit url:[http://localhost:8080](http://localhost:8080) (The default address is this one. If you want to change it, follow the configuration file)
|
||||
* account:`superadmin` password:`admin123456`
|
||||
|
||||
### docker-compose
|
||||
|
||||
~~~shell
|
||||
docker-compose up -d
|
||||
# Initialize backend data (first execution only)
|
||||
docker exec -ti dvadmin3-django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init_area
|
||||
python manage.py init
|
||||
exit
|
||||
|
||||
frontend url:http://127.0.0.1:8080
|
||||
backend url:http://127.0.0.1:8080/api
|
||||
# Change 127.0.0.1 to your own public ip address on the server
|
||||
account:`superadmin` password:`admin123456`
|
||||
|
||||
# docker-compose stop
|
||||
docker-compose down
|
||||
# docker-compose restart
|
||||
docker-compose restart
|
||||
# docker-compose on start build
|
||||
docker-compose up -d --build
|
||||
~~~
|
||||
|
||||
## Demo screenshot✅
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
219
README.md
219
README.md
@@ -1,171 +1,135 @@
|
||||
# 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)
|
||||
|
||||
## 平台简介
|
||||
* demo account:superadmin
|
||||
|
||||
💡 [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
|
||||
* demo password:admin123456
|
||||
|
||||
👩👦👦docs:[https://django-vue-admin.com](https://django-vue-admin.com)
|
||||
|
||||
## communication
|
||||
|
||||
* Communication community:[click here](https://bbs.django-vue-admin.com)👩👦👦
|
||||
|
||||
* 🧑🤝🧑前端采用 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 授权。
|
||||
* plugins market:[click here](https://bbs.django-vue-admin.com/plugMarket.html)👩👦👦
|
||||
|
||||
#### 🏭 环境支持
|
||||
## source code url:
|
||||
|
||||
| Edge | Firefox | Chrome | Safari |
|
||||
| --------- | ------------ | ----------- | ----------- |
|
||||
| Edge ≥ 79 | Firefox ≥ 78 | Chrome ≥ 64 | Safari ≥ 12 |
|
||||
gitee(Main push):[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
> 由于 Vue3 不再支持 IE11,故而 ElementPlus 也不支持 IE11 及之前版本。
|
||||
github:[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩👦👦
|
||||
|
||||
## core function
|
||||
|
||||
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 🔌
|
||||
|
||||
👩👧👦演示地址:[https://demo.dvadmin.com](https://demo.dvadmin.com)
|
||||
Updating...
|
||||
|
||||
- 账号:superadmin
|
||||
## Repository Branch Explanation 💈
|
||||
Main Branch: master (stable version)
|
||||
Development Branch: develop
|
||||
|
||||
- 密码:admin123456
|
||||
## before start project you need:
|
||||
|
||||
👩👦👦文档地址:[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 (可选,最新版)
|
||||
Python >= 3.11.0 (Minimum version 3.9+)
|
||||
Node.js >= 16.0
|
||||
Mysql >= 8.0 (Optional, default database: SQLite3, supports 5.7+, recommended version: 8.0)
|
||||
Redis (Optional, latest version)
|
||||
~~~
|
||||
|
||||
## 前端♝
|
||||
## frontend♝
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
# clone code
|
||||
git clone https://gitee.com/huge-dream/django-vue3-admin.git
|
||||
|
||||
# 进入项目目录
|
||||
# enter code dir
|
||||
cd web
|
||||
|
||||
# 安装依赖
|
||||
# install dependence
|
||||
npm install yarn
|
||||
yarn install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# 启动服务
|
||||
yarn build
|
||||
# 浏览器访问 http://localhost:8080
|
||||
# .env.development 文件中可配置启动端口等参数
|
||||
# 构建生产环境
|
||||
# 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
|
||||
或使用 uvicorn :
|
||||
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
|
||||
~~~
|
||||
## 开发建议
|
||||
前后端backend与web各自单独一个窗口打开进行开发
|
||||
|
||||
### 访问项目
|
||||
### 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
|
||||
# 初始化后端数据(第一次执行即可)
|
||||
# Initialize backend data (first execution only)
|
||||
docker exec -ti dvadmin3-django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
@@ -173,22 +137,20 @@ 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✅
|
||||
|
||||

|
||||
|
||||
@@ -211,4 +173,3 @@ docker-compose up -d --build
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
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,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
|
||||
|
||||
@@ -63,6 +63,7 @@ INSTALLED_APPS = [
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"dvadmin.utils.middleware.HealthCheckMiddleware",
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
@@ -154,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')
|
||||
@@ -402,5 +408,6 @@ PLUGINS_URL_PATTERNS = []
|
||||
# from dvadmin_third.settings import * # 第三方用户管理
|
||||
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
|
||||
# from dvadmin_tenants.settings import * # 租户管理
|
||||
#from dvadmin_social_auth.settings import *
|
||||
# ...
|
||||
# ********** 一键导入插件配置结束 **********
|
||||
|
||||
@@ -5,6 +5,7 @@ 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,
|
||||
@@ -60,9 +61,10 @@ class MenuFieldInitSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = MenuField
|
||||
fields = ['id', 'menu','field_name','title','model']
|
||||
fields = ['id', 'menu', 'field_name', 'title', 'model']
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MenuInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
递归深度获取数信息(用于生成初始化json文件)
|
||||
@@ -71,6 +73,7 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
children = serializers.SerializerMethodField()
|
||||
menu_button = serializers.SerializerMethodField()
|
||||
menu_field = serializers.SerializerMethodField()
|
||||
|
||||
def get_children(self, obj: Menu):
|
||||
data = []
|
||||
instance = Menu.objects.filter(parent_id=obj.id)
|
||||
@@ -90,7 +93,7 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
data = []
|
||||
instance = obj.menufield_set.order_by('field_name')
|
||||
if instance:
|
||||
data = list(instance.values('field_name', 'title','model'))
|
||||
data = list(instance.values('field_name', 'title', 'model'))
|
||||
return data
|
||||
|
||||
def save(self, **kwargs):
|
||||
@@ -131,8 +134,9 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
for field_data in menu_field:
|
||||
field_data['menu'] = instance.id
|
||||
filter_data = {
|
||||
'menu':field_data['menu'],
|
||||
'field_name':field_data['field_name']
|
||||
'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)
|
||||
@@ -143,7 +147,7 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Menu
|
||||
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']
|
||||
'cache', 'visible', 'parent', 'children', 'menu_button', 'menu_field', 'creator', 'dept_belong_id']
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
"parent": 1,
|
||||
"title": "隐私链接",
|
||||
"key": "privacy_url",
|
||||
"value": "#",
|
||||
"value": "/api/system/clause/privacy.html",
|
||||
"sort": 7,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
@@ -196,7 +196,7 @@
|
||||
"parent": 1,
|
||||
"title": "条款链接",
|
||||
"key": "clause_url",
|
||||
"value": "#",
|
||||
"value": "/api/system/clause/terms_service.html",
|
||||
"sort": 8,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
|
||||
@@ -162,6 +162,7 @@ class Menu(CoreModel):
|
||||
(1, "是"),
|
||||
)
|
||||
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="组件地址")
|
||||
@@ -171,7 +172,29 @@ class Menu(CoreModel):
|
||||
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
|
||||
visible = models.BooleanField(default=True, 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 = "菜单表"
|
||||
|
||||
@@ -3,6 +3,7 @@ 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
|
||||
@@ -46,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')
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -71,8 +71,8 @@ class WebRouterSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = (
|
||||
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', '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"]
|
||||
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class MenuButtonViewSet(CustomModelViewSet):
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
queryset = self.filter_queryset(self.get_queryset()).order_by('name')
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(serializer.data,msg="获取成功")
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class MenuFieldViewSet(CustomModelViewSet):
|
||||
"""
|
||||
列权限视图集
|
||||
"""
|
||||
queryset = MenuField.objects.all()
|
||||
queryset = MenuField.objects.order_by('-model')
|
||||
serializer_class = MenuFieldSerializer
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
|
||||
@@ -112,10 +112,15 @@ 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(
|
||||
@@ -189,9 +194,12 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
RoleMenuButtonPermission.objects.filter(role=pk).delete()
|
||||
for menu in body:
|
||||
if menu.get('isCheck'):
|
||||
menu_parent = Menu.objects.filter(id=menu.get('id')).values('parent').first()
|
||||
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu_parent.get('parent'))
|
||||
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
|
||||
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
|
||||
@@ -213,8 +221,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
if params := request.query_params:
|
||||
if menu_id := params.get('menu', None):
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
if is_superuser:
|
||||
queryset = MenuButton.objects.filter(menu=menu_id).values('id', 'name')
|
||||
else:
|
||||
role_list = request.user.role.values_list('id', flat=True)
|
||||
@@ -324,8 +331,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
"""
|
||||
params = request.query_params
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
if is_superuser:
|
||||
queryset = Dept.objects.values('id', 'name', 'parent')
|
||||
else:
|
||||
if not params:
|
||||
@@ -353,8 +359,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
if menu_id is None:
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
if is_superuser:
|
||||
queryset = RoleMenuButtonPermission.objects.filter(menu_button__menu=menu_id).values(
|
||||
'id',
|
||||
'data_range',
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"):
|
||||
|
||||
0
backend/logs/__init__.py
Normal file
0
backend/logs/__init__.py
Normal file
@@ -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>
|
||||
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
|
||||
|
||||
|
||||
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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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,13 +67,17 @@ 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(() => {
|
||||
|
||||
@@ -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,118 +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 { successMessage, successNotification } from '/@/utils/message';
|
||||
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({ dict }: any) {
|
||||
//根据dict的url,异步返回一个字典数组
|
||||
return await request({ url: dict.url, params: dict.params || {} }).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};
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
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';
|
||||
});
|
||||
},
|
||||
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])
|
||||
}
|
||||
})
|
||||
|
||||
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'};
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
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 {
|
||||
@@ -102,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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
import { toRaw } from 'vue';
|
||||
import { DictionaryStore } from '/@/stores/dictionary';
|
||||
|
||||
/**
|
||||
* @method 获取指定name字典
|
||||
/**
|
||||
* @method 获取指定name字典
|
||||
*/
|
||||
export const dictionary = (name: string) => {
|
||||
export const dictionary = (name: string,key:string|number|undefined) => {
|
||||
const dict = DictionaryStore()
|
||||
const dictionary = toRaw(dict.data)
|
||||
return dictionary[name]
|
||||
}
|
||||
if(key!=undefined){
|
||||
const obj = dictionary[name].find((item:any) => item.value === key)
|
||||
return obj?obj.label:''
|
||||
}else{
|
||||
return dictionary[name]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> = [];
|
||||
|
||||
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>
|
||||
@@ -13,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) => {
|
||||
@@ -218,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, //最小列宽
|
||||
@@ -264,17 +257,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
url: '/api/system/role/',
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
getData: async ({ url }: { url: string }) => {
|
||||
return request({
|
||||
url: url,
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
},
|
||||
}).then((ret: any) => {
|
||||
return ret.data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
|
||||
@@ -57,7 +57,7 @@ const getData = async () => {
|
||||
const result = XEUtils.toArrayTree(res.data, {
|
||||
parentKey: 'parent',
|
||||
children: 'children',
|
||||
strict: true,
|
||||
//strict: true,
|
||||
});
|
||||
|
||||
deptTreeData.value = result;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<el-drawer size="70%" v-model="drawer" direction="rtl" destroy-on-close :before-close="handleClose">
|
||||
<div>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</div>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
<div class="right">
|
||||
<img
|
||||
src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
|
||||
src="./img404.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
BIN
web/src/views/system/error/img404.png
Normal file
BIN
web/src/views/system/error/img404.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
@@ -126,6 +126,7 @@ export default defineComponent({
|
||||
});
|
||||
};
|
||||
const refreshCaptcha = async () => {
|
||||
state.ruleForm.captcha=''
|
||||
loginApi.getCaptcha().then((ret: any) => {
|
||||
state.ruleForm.captchaImgBase = ret.data.image_base;
|
||||
state.ruleForm.captchaKey = ret.data.key;
|
||||
@@ -150,11 +151,11 @@ export default defineComponent({
|
||||
// 执行完 initBackEndControlRoutes,再执行 signInSuccess
|
||||
loginSuccess();
|
||||
}
|
||||
} else if (res.code === 4000) {
|
||||
// 登录错误之后,刷新验证码
|
||||
refreshCaptcha();
|
||||
}
|
||||
});
|
||||
}).catch((err: any) => {
|
||||
// 登录错误之后,刷新验证码
|
||||
refreshCaptcha();
|
||||
});
|
||||
} else {
|
||||
errorMessage("请填写登录信息")
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<span class="login-right-warp-one"></span>
|
||||
<span class="login-right-warp-two"></span>
|
||||
<div class="login-right-warp-mian">
|
||||
<div class="login-right-warp-main-title">{{ getThemeConfig.globalTitle }} 欢迎您!</div>
|
||||
<div class="login-right-warp-main-title">{{ getSystemConfig['login.site_title'] || getThemeConfig.globalTitle }} 欢迎您!</div>
|
||||
<div class="login-right-warp-main-form">
|
||||
<div v-if="!state.isScan">
|
||||
<el-tabs v-model="state.tabsActiveName">
|
||||
@@ -48,9 +48,9 @@
|
||||
|
|
||||
<a :href="getSystemConfig['login.help_url']?getSystemConfig['login.help_url']:'https://django-vue-admin.com'" target="_blank">帮助</a>
|
||||
|
|
||||
<a :href="getSystemConfig['login.privacy_url']?getSystemConfig['login.privacy_url']:'#'">隐私</a>
|
||||
<a :href="getSystemConfig['login.privacy_url']?getBaseURL(getSystemConfig['login.privacy_url']):'#'">隐私</a>
|
||||
|
|
||||
<a :href="getSystemConfig['login.clause_url']?getSystemConfig['login.clause_url']:'#'">条款</a>
|
||||
<a :href="getSystemConfig['login.clause_url']?getBaseURL(getSystemConfig['login.clause_url']):'#'">条款</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,6 +65,7 @@ import logoMini from '/@/assets/logo-mini.svg';
|
||||
import loginMain from '/@/assets/login-main.svg';
|
||||
import loginBg from '/@/assets/login-bg.svg';
|
||||
import {SystemConfigStore} from '/@/stores/systemConfig'
|
||||
import {getBaseURL} from "/@/utils/baseUrl";
|
||||
// 引入组件
|
||||
const Account = defineAsyncComponent(() => import('/@/views/system/login/component/account.vue'));
|
||||
const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
|
||||
|
||||
@@ -103,6 +103,9 @@ export const createCrudOptions = function ({ crudExpose, props,modelDialog,selec
|
||||
label:'title',
|
||||
value:'key'
|
||||
}),
|
||||
column:{
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
@@ -113,6 +116,13 @@ export const createCrudOptions = function ({ crudExpose, props,modelDialog,selec
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
showSearch: true,
|
||||
filterable: true,
|
||||
//默认的filterOption仅支持value的过滤,label并不会加入查询
|
||||
//所以需要自定义filterOption
|
||||
filterOption(inputValue, option) {
|
||||
return option.label.indexOf(inputValue) >= 0 || option.value.indexOf(inputValue) >= 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -143,6 +153,9 @@ export const createCrudOptions = function ({ crudExpose, props,modelDialog,selec
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
column:{
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
@@ -157,83 +170,6 @@ export const createCrudOptions = function ({ crudExpose, props,modelDialog,selec
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// is_create: {
|
||||
// title: '创建时显示',
|
||||
// sortable: 'custom',
|
||||
// search: {
|
||||
// disabled: true,
|
||||
// },
|
||||
// type: 'dict-switch',
|
||||
// dict: dict({
|
||||
// data: [
|
||||
// { value: true, label: '启用' },
|
||||
// { value: false, label: '禁用' },
|
||||
// ],
|
||||
// }),
|
||||
// form: {
|
||||
// value: true,
|
||||
// },
|
||||
// column: {
|
||||
// valueChange(context){
|
||||
// return api.UpdateObj(context.row)
|
||||
// },
|
||||
// component: {
|
||||
// name: 'fs-dict-switch',
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// is_update: {
|
||||
// title: '编辑时显示',
|
||||
// search: {
|
||||
// show: true,
|
||||
// },
|
||||
// type: 'dict-switch',
|
||||
// dict: dict({
|
||||
// data: [
|
||||
// { value: true, label: '启用' },
|
||||
// { value: false, label: '禁用' },
|
||||
// ],
|
||||
// }),
|
||||
// form: {
|
||||
// value: true,
|
||||
// },
|
||||
// column: {
|
||||
// component: {
|
||||
// name: 'fs-dict-switch',
|
||||
// onChange: compute((context) => {
|
||||
// //动态onChange方法测试
|
||||
// return () => {
|
||||
// console.log('onChange', context.row.switch);
|
||||
// };
|
||||
// }),
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// is_query: {
|
||||
// title: '列表中显示',
|
||||
// type: 'dict-switch',
|
||||
// dict: dict({
|
||||
// data: [
|
||||
// { value: true, label: '启用' },
|
||||
// { value: false, label: '禁用' },
|
||||
// ],
|
||||
// }),
|
||||
// form: {
|
||||
// value: true,
|
||||
// },
|
||||
// column: {
|
||||
// component: {
|
||||
// name: 'fs-dict-switch',
|
||||
// onChange: compute((context) => {
|
||||
// //动态onChange方法测试
|
||||
// return () => {
|
||||
// console.log('onChange', context.row.switch);
|
||||
// };
|
||||
// }),
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="路由地址" prop="web_path">
|
||||
<el-form-item label="路由地址" prop="web_path">
|
||||
<el-input v-model="menuFormData.web_path" placeholder="请输入路由地址,请以/开头" />
|
||||
</el-form-item>
|
||||
|
||||
@@ -56,6 +56,16 @@
|
||||
<el-switch v-model="menuFormData.is_link" width="60" inline-prompt active-text="是" inactive-text="否" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item required v-if="!menuFormData.is_catalog" label="是否固定">
|
||||
<el-switch v-model="menuFormData.is_affix" width="60" inline-prompt active-text="是" inactive-text="否" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item v-if="!menuFormData.is_catalog && menuFormData.is_link" required label="是否内嵌">
|
||||
<el-switch v-model="menuFormData.is_iframe" width="60" inline-prompt active-text="是" inactive-text="否" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="备注">
|
||||
@@ -81,8 +91,8 @@
|
||||
<el-input v-model="menuFormData.component_name" placeholder="请输入组件名称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!menuFormData.is_catalog && menuFormData.is_link" label="Url" prop="web_path">
|
||||
<el-input v-model="menuFormData.web_path" placeholder="请输入Url" />
|
||||
<el-form-item v-if="!menuFormData.is_catalog && menuFormData.is_link" label="外链接" prop="link_url">
|
||||
<el-input v-model="menuFormData.link_url" placeholder="请输入外链接地址" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!menuFormData.is_catalog" label="缓存">
|
||||
@@ -129,8 +139,7 @@ const defaultTreeProps: any = {
|
||||
};
|
||||
const validateWebPath = (rule: any, value: string, callback: Function) => {
|
||||
let pattern = /^\/.*?/;
|
||||
let patternUrl = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
|
||||
const reg = menuFormData.is_link ? patternUrl.test(value) : pattern.test(value);
|
||||
const reg = pattern.test(value);
|
||||
if (reg) {
|
||||
callback();
|
||||
} else {
|
||||
@@ -138,6 +147,17 @@ const validateWebPath = (rule: any, value: string, callback: Function) => {
|
||||
}
|
||||
};
|
||||
|
||||
const validateLinkUrl = (rule: any, value: string, callback: Function) => {
|
||||
let pattern = /^\/.*?/;
|
||||
let patternUrl = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
|
||||
const reg = pattern.test(value) || patternUrl.test(value)
|
||||
if (reg) {
|
||||
callback();
|
||||
} else {
|
||||
callback(new Error('请输入正确的地址'));
|
||||
}
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
initFormData: () => null,
|
||||
treeData: () => [],
|
||||
@@ -152,6 +172,7 @@ const rules = reactive<FormRules>({
|
||||
name: [{ required: true, message: '菜单名称必填', trigger: 'blur' }],
|
||||
component: [{ required: true, message: '请输入组件地址', trigger: 'blur' }],
|
||||
component_name: [{ required: true, message: '请输入组件名称', trigger: 'blur' }],
|
||||
link_url: [{ required: true, message: '请输入外链接地址',validator:validateLinkUrl, trigger: 'blur' }],
|
||||
});
|
||||
|
||||
let deptDefaultList = ref<MenuTreeItemType[]>([]);
|
||||
@@ -168,6 +189,9 @@ let menuFormData = reactive<MenuFormDataType>({
|
||||
description: '',
|
||||
is_catalog: false,
|
||||
is_link: false,
|
||||
is_iframe: false,
|
||||
is_affix: false,
|
||||
link_url:''
|
||||
});
|
||||
let menuBtnLoading = ref(false);
|
||||
|
||||
@@ -179,13 +203,16 @@ const setMenuFormData = () => {
|
||||
menuFormData.component = props.initFormData?.component || '';
|
||||
menuFormData.web_path = props.initFormData?.web_path || '';
|
||||
menuFormData.icon = props.initFormData?.icon || '';
|
||||
menuFormData.status = props.initFormData?.status || true;
|
||||
menuFormData.visible = props.initFormData?.visible || true;
|
||||
menuFormData.cache = props.initFormData?.cache || true;
|
||||
menuFormData.status = !!props.initFormData.status;
|
||||
menuFormData.visible = !!props.initFormData.visible;
|
||||
menuFormData.cache = !!props.initFormData.cache;
|
||||
menuFormData.component_name = props.initFormData?.component_name || '';
|
||||
menuFormData.description = props.initFormData?.description || '';
|
||||
menuFormData.is_catalog = props.initFormData?.is_catalog || false;
|
||||
menuFormData.is_link = props.initFormData?.is_link || false;
|
||||
menuFormData.is_catalog = !!props.initFormData.is_catalog;
|
||||
menuFormData.is_link = !!props.initFormData.is_link;
|
||||
menuFormData.is_iframe =!!props.initFormData.is_iframe;
|
||||
menuFormData.is_affix =!!props.initFormData.is_affix;
|
||||
menuFormData.link_url =props.initFormData.link_url;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@ export interface MenuTreeItemType {
|
||||
visible: boolean;
|
||||
creator: string;
|
||||
parent: number | string;
|
||||
is_iframe:boolean;
|
||||
is_affix:boolean;
|
||||
link_url:string;
|
||||
}
|
||||
|
||||
export interface MenuFormDataType {
|
||||
@@ -60,4 +63,7 @@ export interface MenuFormDataType {
|
||||
description: string;
|
||||
is_catalog: boolean;
|
||||
is_link: boolean;
|
||||
}
|
||||
is_iframe:boolean;
|
||||
is_affix:boolean;
|
||||
link_url: string;
|
||||
}
|
||||
|
||||
@@ -151,6 +151,10 @@ export const createCrudOptions = function ({
|
||||
placeholder: '输入权限标识',
|
||||
},
|
||||
},
|
||||
valueBuilder(context){
|
||||
const {row,key} = context
|
||||
return row[key]
|
||||
}
|
||||
},
|
||||
sort: {
|
||||
title: '排序',
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
<script lang="ts" setup name="role">
|
||||
import {ref, onMounted, inject, onBeforeUpdate} from 'vue';
|
||||
import { useColumnPermission } from '/@/stores/columnPermission';
|
||||
|
||||
import { GetPermission } from './api';
|
||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
import PermissionComNew from './components/PermissionComNew/index.vue';
|
||||
import _ from "lodash-es";
|
||||
import {columnPermission} from "/@/utils/columnPermission";
|
||||
import {handleColumnPermission} from "/@/utils/columnPermission";
|
||||
let drawerVisible = ref(false);
|
||||
let roleId = ref(null);
|
||||
let roleName = ref(null);
|
||||
@@ -32,12 +32,6 @@ const crudRef = ref();
|
||||
const crudBinding = ref();
|
||||
|
||||
|
||||
const fetchColumnPermission = async () => {
|
||||
const res = await GetPermission();
|
||||
useColumnPermission().setPermissionData(res.data);
|
||||
console.log(3333,res)
|
||||
};
|
||||
|
||||
const handleDrawerOpen = (row: any) => {
|
||||
roleId.value = row.id;
|
||||
roleName.value = row.name;
|
||||
@@ -49,24 +43,6 @@ const handleDrawerClose = () => {
|
||||
};
|
||||
|
||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||
const handlecolumnPermission = async (crudOptions:any)=>{
|
||||
const res = await GetPermission();
|
||||
const columns = crudOptions.columns;
|
||||
for(let col in columns){
|
||||
for(let i in res.data){
|
||||
if(res.data[i].field_name === col){
|
||||
columns[col].column.show = i['is_query']
|
||||
columns[col].addForm = {
|
||||
show:i['is_create']
|
||||
}
|
||||
columns[col].editForm = {
|
||||
show:i['is_update']
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose, rolePermission, handleDrawerOpen });
|
||||
@@ -76,24 +52,16 @@ const { crudOptions } = createCrudOptions({ crudExpose, rolePermission, handleDr
|
||||
// 页面打开后获取列表数据
|
||||
onMounted( async () => {
|
||||
|
||||
await handlecolumnPermission(crudOptions)
|
||||
// //合并新的crudOptions
|
||||
// const newOptions = _.merge(crudOptions, {
|
||||
// columns: {
|
||||
// text: {
|
||||
// title: "追加字段",
|
||||
// type: "text"
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//重置crudBinding
|
||||
// resetCrudOptions(newOptions);
|
||||
const newOptions = await handleColumnPermission(GetPermission,crudOptions)
|
||||
|
||||
// 初始化crud配置
|
||||
const { resetCrudOptions } = useCrud({
|
||||
crudExpose,
|
||||
crudOptions,
|
||||
context: {},
|
||||
});
|
||||
//重置crudBinding
|
||||
resetCrudOptions(newOptions);
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,370 +1,385 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { request } from '/@/utils/service';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import { auth } from '/@/utils/authFunction';
|
||||
import {
|
||||
dict,
|
||||
UserPageQuery,
|
||||
AddReq,
|
||||
DelReq,
|
||||
EditReq,
|
||||
compute,
|
||||
CreateCrudOptionsProps,
|
||||
CreateCrudOptionsRet
|
||||
} from '@fast-crud/fast-crud';
|
||||
import {request} from '/@/utils/service';
|
||||
import {dictionary} from '/@/utils/dictionary';
|
||||
import {successMessage} from '/@/utils/message';
|
||||
import {auth} from '/@/utils/authFunction';
|
||||
import {SystemConfigStore} from "/@/stores/systemConfig";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {computed} from "vue";
|
||||
import { Md5 } from 'ts-md5';
|
||||
import {commonCrudConfig} from "/@/utils/commonCrud";
|
||||
export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({form, row}: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({row}: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({form}: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
const exportRequest = async (query: UserPageQuery) => {
|
||||
return await api.exportData(query)
|
||||
}
|
||||
|
||||
const exportRequest = async (query: UserPageQuery) => {
|
||||
return await api.exportData(query)
|
||||
}
|
||||
const systemConfigStore = SystemConfigStore()
|
||||
const {systemConfig} = storeToRefs(systemConfigStore)
|
||||
const getSystemConfig = computed(() => {
|
||||
console.log(systemConfig.value)
|
||||
return systemConfig.value
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
table: {
|
||||
remove: {
|
||||
confirmMessage: '是否删除该用户?',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('user:Create')
|
||||
},
|
||||
export:{
|
||||
text:"导出",//按钮文字
|
||||
title:"导出",//鼠标停留显示的信息
|
||||
click(){
|
||||
return exportRequest(crudExpose!.getSearchFormData())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: auth('user:Update'),
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: auth('user:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '重设密码',
|
||||
type: 'text',
|
||||
show: auth('user:ResetPassword'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '重设密码',
|
||||
},
|
||||
//@ts-ignore
|
||||
click: (ctx: any) => {
|
||||
const { row } = ctx;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
username: {
|
||||
title: '账号',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '账号必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入账号',
|
||||
},
|
||||
},
|
||||
},
|
||||
password: {
|
||||
title: '密码',
|
||||
type: 'input',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
editForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '密码必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
showPassword: true,
|
||||
placeholder: '请输入密码',
|
||||
},
|
||||
// value: vm.systemConfig('base.default_password'),
|
||||
},
|
||||
/* valueResolve(row, key) {
|
||||
if (row.password) {
|
||||
row.password = vm.$md5(row.password)
|
||||
return {
|
||||
crudOptions: {
|
||||
table: {
|
||||
remove: {
|
||||
confirmMessage: '是否删除该用户?',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
initialForm: {
|
||||
password: computed(() => {
|
||||
return systemConfig.value['base.default_password']
|
||||
}),
|
||||
}
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('user:Create')
|
||||
},
|
||||
export: {
|
||||
text: "导出",//按钮文字
|
||||
title: "导出",//鼠标停留显示的信息
|
||||
click() {
|
||||
return exportRequest(crudExpose!.getSearchFormData())
|
||||
}
|
||||
} */
|
||||
},
|
||||
name: {
|
||||
title: '姓名',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '姓名必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
placeholder: '请输入姓名',
|
||||
},
|
||||
},
|
||||
},
|
||||
dept: {
|
||||
title: '部门',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-tree',
|
||||
dict: dict({
|
||||
isTree: true,
|
||||
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, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
filterable: true,
|
||||
placeholder: '请选择',
|
||||
props: {
|
||||
props: {
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
role: {
|
||||
title: '角色',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
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, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
placeholder: '请选择角色',
|
||||
},
|
||||
},
|
||||
},
|
||||
mobile: {
|
||||
title: '手机号码',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 120, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
{
|
||||
max: 20,
|
||||
message: '请输入正确的手机号码',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入正确的手机号码',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入手机号码',
|
||||
},
|
||||
},
|
||||
},
|
||||
email: {
|
||||
title: '邮箱',
|
||||
column: {
|
||||
width: 260,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
{
|
||||
type: 'email',
|
||||
message: '请输入正确的邮箱地址',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入邮箱',
|
||||
},
|
||||
},
|
||||
},
|
||||
gender: {
|
||||
title: '性别',
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: dictionary('gender'),
|
||||
}),
|
||||
form: {
|
||||
value: 1,
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
user_type: {
|
||||
title: '用户类型',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: dictionary('user_type'),
|
||||
}),
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
value: 0,
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
is_active: {
|
||||
title: '锁定',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
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'),
|
||||
}),
|
||||
},
|
||||
avatar: {
|
||||
title: '头像',
|
||||
type: 'avatar-cropper',
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: auth('user:Update'),
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: auth('user:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '重设密码',
|
||||
type: 'text',
|
||||
show: auth('user:ResetPassword'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '重设密码',
|
||||
},
|
||||
//@ts-ignore
|
||||
click: (ctx: any) => {
|
||||
const {row} = ctx;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: {show: false},
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
username: {
|
||||
title: '账号',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '账号必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入账号',
|
||||
},
|
||||
},
|
||||
},
|
||||
password: {
|
||||
title: '密码',
|
||||
type: 'password',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
editForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '密码必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
|
||||
span: 12,
|
||||
showPassword: true,
|
||||
placeholder: '请输入密码',
|
||||
},
|
||||
},
|
||||
valueResolve({form}) {
|
||||
if (form.password) {
|
||||
form.password = Md5.hashStr(form.password)
|
||||
}
|
||||
}
|
||||
},
|
||||
name: {
|
||||
title: '姓名',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '姓名必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
placeholder: '请输入姓名',
|
||||
},
|
||||
},
|
||||
},
|
||||
dept: {
|
||||
title: '部门',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-tree',
|
||||
dict: dict({
|
||||
isTree: true,
|
||||
url: '/api/system/dept/all_dept/',
|
||||
value: 'id',
|
||||
label: 'name'
|
||||
}),
|
||||
column: {
|
||||
minWidth: 150, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
filterable: true,
|
||||
placeholder: '请选择',
|
||||
props: {
|
||||
checkStrictly:true,
|
||||
props: {
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
role: {
|
||||
title: '角色',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
url: '/api/system/role/',
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
}),
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
placeholder: '请选择角色',
|
||||
},
|
||||
},
|
||||
},
|
||||
mobile: {
|
||||
title: '手机号码',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 120, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
{
|
||||
max: 20,
|
||||
message: '请输入正确的手机号码',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入正确的手机号码',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入手机号码',
|
||||
},
|
||||
},
|
||||
},
|
||||
email: {
|
||||
title: '邮箱',
|
||||
column: {
|
||||
width: 260,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
{
|
||||
type: 'email',
|
||||
message: '请输入正确的邮箱地址',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入邮箱',
|
||||
},
|
||||
},
|
||||
},
|
||||
gender: {
|
||||
title: '性别',
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: dictionary('gender'),
|
||||
}),
|
||||
form: {
|
||||
value: 1,
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
component: {props: {color: 'auto'}}, // 自动染色
|
||||
},
|
||||
user_type: {
|
||||
title: '用户类型',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: dictionary('user_type'),
|
||||
}),
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
value: 0,
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
is_active: {
|
||||
title: '锁定',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
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'),
|
||||
}),
|
||||
},
|
||||
avatar: {
|
||||
title: '头像',
|
||||
type: 'avatar-cropper',
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
...commonCrudConfig({
|
||||
dept_belong_id: {
|
||||
form: true,
|
||||
table: true
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,59 +1,69 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<el-row class="mx-2">
|
||||
<el-col xs="24" :sm="8" :md="6" :lg="4" :xl="4" class="p-1">
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<p class="font-mono font-black text-center text-xl pb-5">
|
||||
部门列表
|
||||
<el-tooltip effect="dark" :content="content" placement="right">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</p>
|
||||
<el-input v-model="filterText" :placeholder="placeholder" />
|
||||
<el-tree ref="treeRef" class="font-mono font-bold leading-6 text-7xl" :data="data" :props="treeProps"
|
||||
:filter-node-method="filterNode" icon="ArrowRightBold" :indent="12" @node-click="onTreeNodeClick">
|
||||
<template #default="{ node, data }">
|
||||
<span class="text-center font-black font-normal">{{ node.label }}</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col xs="24" :sm="16" :md="18" :lg="20" :xl="20" class="p-1">
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #actionbar-right>
|
||||
<importExcel api="api/system/user/">导入 </importExcel>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<fs-page>
|
||||
<el-row class="mx-2">
|
||||
<el-col xs="24" :sm="8" :md="6" :lg="4" :xl="4" class="p-1">
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<p class="font-mono font-black text-center text-xl pb-5">
|
||||
部门列表
|
||||
<el-tooltip effect="dark" :content="content" placement="right">
|
||||
<el-icon>
|
||||
<QuestionFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</p>
|
||||
<el-input v-model="filterText" :placeholder="placeholder"/>
|
||||
<el-tree ref="treeRef" class="font-mono font-bold leading-6 text-7xl" :data="data" :props="treeProps"
|
||||
:filter-node-method="filterNode" icon="ArrowRightBold" :indent="38" highlight-current @node-click="onTreeNodeClick">
|
||||
<template #default="{ node, data }">
|
||||
<element-tree-line :node="node" :showLabelLine="false" :indent="32">
|
||||
<span v-if="data.status" class="text-center font-black font-normal">
|
||||
<SvgIcon name="iconfont icon-shouye" color="var(--el-color-primary)"/> {{ node.label }}
|
||||
</span>
|
||||
<span v-else color="var(--el-color-primary)"> <SvgIcon name="iconfont icon-shouye"/> {{
|
||||
node.label
|
||||
}} </span>
|
||||
</element-tree-line>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col xs="24" :sm="16" :md="18" :lg="20" :xl="20" class="p-1">
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #actionbar-right>
|
||||
<importExcel api="api/system/user/">导入</importExcel>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</fs-page>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="user">
|
||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
import {useExpose, useCrud} from '@fast-crud/fast-crud';
|
||||
import {createCrudOptions} from './crud';
|
||||
import * as api from './api';
|
||||
import { ElTree } from 'element-plus';
|
||||
import { ref, onMounted, watch, toRaw } from 'vue';
|
||||
import {ElTree} from 'element-plus';
|
||||
import {ref, onMounted, watch, toRaw, h} from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import {getElementLabelLine} from 'element-tree-line';
|
||||
import importExcel from '/@/components/importExcel/index.vue'
|
||||
|
||||
const ElementTreeLine = getElementLabelLine(h);
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
name: string;
|
||||
status: boolean;
|
||||
children?: Tree[];
|
||||
id: number;
|
||||
name: string;
|
||||
status: boolean;
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
interface APIResponseData {
|
||||
code?: number;
|
||||
data: [];
|
||||
msg?: string;
|
||||
code?: number;
|
||||
data: [];
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
// 引入组件
|
||||
@@ -62,18 +72,18 @@ const filterText = ref('');
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>();
|
||||
|
||||
const treeProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
icon: 'icon',
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
icon: 'icon',
|
||||
};
|
||||
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value!.filter(val);
|
||||
treeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true;
|
||||
return toRaw(data).name.indexOf(value) !== -1;
|
||||
if (!value) return true;
|
||||
return toRaw(data).name.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
let data = ref([]);
|
||||
@@ -83,27 +93,27 @@ const content = `
|
||||
`;
|
||||
|
||||
const getData = () => {
|
||||
api.GetDept({}).then((ret: APIResponseData) => {
|
||||
const responseData = ret.data;
|
||||
const result = XEUtils.toArrayTree(responseData, {
|
||||
parentKey: 'parent',
|
||||
children: 'children',
|
||||
strict: true,
|
||||
});
|
||||
api.GetDept({}).then((ret: APIResponseData) => {
|
||||
const responseData = ret.data;
|
||||
const result = XEUtils.toArrayTree(responseData, {
|
||||
parentKey: 'parent',
|
||||
children: 'children',
|
||||
strict: true,
|
||||
});
|
||||
|
||||
data.value = result;
|
||||
});
|
||||
data.value = result;
|
||||
});
|
||||
};
|
||||
|
||||
//树形点击事件
|
||||
const onTreeNodeClick = (node: any) => {
|
||||
const { id } = node;
|
||||
crudExpose.doSearch({ form: { dept: id } });
|
||||
const {id} = node;
|
||||
crudExpose.doSearch({form: {dept: id}});
|
||||
};
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
getData();
|
||||
getData();
|
||||
});
|
||||
|
||||
// crud组件的ref
|
||||
@@ -111,32 +121,32 @@ const crudRef = ref();
|
||||
// crud 配置的ref
|
||||
const crudBinding = ref();
|
||||
// 暴露的方法
|
||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||
const {crudExpose} = useExpose({crudRef, crudBinding});
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose });
|
||||
const {crudOptions} = createCrudOptions({crudExpose});
|
||||
// 初始化crud配置
|
||||
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
|
||||
const {resetCrudOptions} = useCrud({crudExpose, crudOptions});
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-row {
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
|
||||
.el-col {
|
||||
height: 100%;
|
||||
}
|
||||
.el-col {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.el-card {
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.font-normal {
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user