!92 正式发布v2.1.1版本

Merge pull request !92 from dvadmin/dev
pull/93/MERGE v2.1.1
dvadmin 2023-04-04 17:25:14 +00:00 committed by Gitee
commit fd05cc9726
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
51 changed files with 1639 additions and 488 deletions

View File

@ -2,7 +2,7 @@
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-3.2-blue)](https://docs.djangoproject.com/zh-hans/3.2/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/liqianglog/django-vue-admin/badge/star.svg?theme=dark)](https://gitee.com/liqianglog/django-vue-admin)
[preview](https://demo.django-vue-admin.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)
[中文文档](./README.md) | [preview](https://demo.django-vue-admin.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」**
@ -111,8 +111,8 @@ npm run dev
python3 manage.py init_area
8. start backend
python3 manage.py runserver 0.0.0.0:8000
or daphne :
daphne -b 0.0.0.0 -p 8000 application.asgi:application
or gunicorn :
gunicorn -c gunicorn_conf.py application.asgi:application
~~~
### visit backend swagger
@ -147,22 +147,29 @@ docker-compose up -d --build
## Demo screenshot✅
![image-01](https://images.gitee.com/uploads/images/2022/0530/234137_b58c8f98_5074988.png)
![image-02](https://images.gitee.com/uploads/images/2022/0530/234240_39834603_5074988.png)
![image-01](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-01.jpg)
![image-03](https://images.gitee.com/uploads/images/2022/0530/234339_35e728a0_5074988.png)
![image-02](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-02.jpg)
![image-04](https://images.gitee.com/uploads/images/2022/0530/234426_957036b0_5074988.png)
![image-03](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-03.jpg)
![image-05](https://images.gitee.com/uploads/images/2022/0530/234458_898be492_5074988.png)
![image-04](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-04.jpg)
![image-06](https://images.gitee.com/uploads/images/2022/0530/234521_35b40076_5074988.png)
![image-05](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-05.jpg)
![image-07](https://images.gitee.com/uploads/images/2022/0530/234615_c2325639_5074988.png)
![image-06](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-06.jpg)
![image-08](https://images.gitee.com/uploads/images/2022/0530/234639_1ed6cc93_5074988.png)
![image-07](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-07.jpg)
![image-09](https://images.gitee.com/uploads/images/2022/0530/234815_cea2c53f_5074988.png)
![image-08](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-08.jpg)
![image-10](https://images.gitee.com/uploads/images/2022/0530/234840_5f3e5f53_5074988.png)
![image-09](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-09.jpg)
![image-10](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-10.jpg)
![image-11](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-11.jpg)
![image-12](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-12.jpg)
![image-13](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-13.jpg)

View File

@ -2,7 +2,7 @@
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-3.2-blue)](https://docs.djangoproject.com/zh-hans/3.2/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/liqianglog/django-vue-admin/badge/star.svg?theme=dark)](https://gitee.com/liqianglog/django-vue-admin)
[预 览](https://demo.django-vue-admin.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)
[English](./README.en.md) | [预 览](https://demo.django-vue-admin.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)
@ -129,8 +129,8 @@ npm run dev
python3 manage.py init_area
8. 启动项目
python3 manage.py runserver 0.0.0.0:8000
或使用 daphne :
daphne -b 0.0.0.0 -p 8000 application.asgi:application
或使用 gunicorn :
gunicorn -c gunicorn_conf.py application.asgi:application
~~~
### 访问项目
@ -172,25 +172,30 @@ docker-compose up -d --build
## 演示图✅
![image-01](https://images.gitee.com/uploads/images/2022/0530/234137_b58c8f98_5074988.png)
![image-01](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-01.jpg)
![image-02](https://images.gitee.com/uploads/images/2022/0530/234240_39834603_5074988.png)
![image-02](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-02.jpg)
![image-03](https://images.gitee.com/uploads/images/2022/0530/234339_35e728a0_5074988.png)
![image-03](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-03.jpg)
![image-04](https://images.gitee.com/uploads/images/2022/0530/234426_957036b0_5074988.png)
![image-04](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-04.jpg)
![image-05](https://images.gitee.com/uploads/images/2022/0530/234458_898be492_5074988.png)
![image-05](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-05.jpg)
![image-06](https://images.gitee.com/uploads/images/2022/0530/234521_35b40076_5074988.png)
![image-06](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-06.jpg)
![image-07](https://images.gitee.com/uploads/images/2022/0530/234615_c2325639_5074988.png)
![image-07](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-07.jpg)
![image-08](https://images.gitee.com/uploads/images/2022/0530/234639_1ed6cc93_5074988.png)
![image-08](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-08.jpg)
![image-09](https://images.gitee.com/uploads/images/2022/0530/234815_cea2c53f_5074988.png)
![image-10](https://images.gitee.com/uploads/images/2022/0530/234840_5f3e5f53_5074988.png)
![image-09](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-09.jpg)
![image-10](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-10.jpg)
![image-11](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-11.jpg)
![image-12](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-12.jpg)
![image-13](https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/docs/demo-13.jpg)

View File

@ -2,6 +2,9 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.db import connection
from django.core.cache import cache
dispatch_db_type = getattr(settings, 'DISPATCH_DB_TYPE', 'memory') # redis
def is_tenants_mode():
@ -68,6 +71,9 @@ def init_dictionary():
:return:
"""
try:
if dispatch_db_type == 'redis':
cache.set(f"init_dictionary", _get_all_dictionary())
return
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
@ -88,7 +94,9 @@ def init_system_config():
:return:
"""
try:
if dispatch_db_type == 'redis':
cache.set(f"init_system_config", _get_all_system_config())
return
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
@ -107,6 +115,9 @@ def refresh_dictionary():
刷新字典配置
:return:
"""
if dispatch_db_type == 'redis':
cache.set(f"init_dictionary", _get_all_dictionary())
return
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
@ -122,6 +133,9 @@ def refresh_system_config():
刷新系统配置
:return:
"""
if dispatch_db_type == 'redis':
cache.set(f"init_system_config", _get_all_system_config())
return
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
@ -141,6 +155,11 @@ def get_dictionary_config(schema_name=None):
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
if dispatch_db_type == 'redis':
init_dictionary_data = cache.get(f"init_dictionary")
if not init_dictionary_data:
refresh_dictionary()
return cache.get(f"init_dictionary") or {}
if not settings.DICTIONARY_CONFIG:
refresh_dictionary()
if is_tenants_mode():
@ -157,6 +176,12 @@ def get_dictionary_values(key, schema_name=None):
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
if dispatch_db_type == 'redis':
dictionary_config = cache.get(f"init_dictionary")
if not dictionary_config:
refresh_dictionary()
dictionary_config = cache.get(f"init_dictionary")
return dictionary_config.get(key)
dictionary_config = get_dictionary_config(schema_name)
return dictionary_config.get(key)
@ -169,8 +194,8 @@ def get_dictionary_label(key, name, schema_name=None):
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
children = get_dictionary_values(key, schema_name) or []
for ele in children:
res = get_dictionary_values(key, schema_name) or []
for ele in res.get('children'):
if ele.get("value") == str(name):
return ele.get("label")
return ""
@ -187,6 +212,11 @@ def get_system_config(schema_name=None):
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
if dispatch_db_type == 'redis':
init_dictionary_data = cache.get(f"init_system_config")
if not init_dictionary_data:
refresh_system_config()
return cache.get(f"init_system_config") or {}
if not settings.SYSTEM_CONFIG:
refresh_system_config()
if is_tenants_mode():
@ -203,6 +233,12 @@ def get_system_config_values(key, schema_name=None):
:param schema_name: 对应系统配置的租户schema_name值
:return:
"""
if dispatch_db_type == 'redis':
system_config = cache.get(f"init_system_config")
if not system_config:
refresh_system_config()
system_config = cache.get(f"init_system_config")
return system_config.get(key)
system_config = get_system_config(schema_name)
return system_config.get(key)

View File

@ -54,6 +54,7 @@ INSTALLED_APPS = [
"rest_framework",
"django_filters",
"corsheaders", # 注册跨域app
'rest_framework_simplejwt.token_blacklist',
"dvadmin.system",
"drf_yasg",
"captcha",
@ -93,7 +94,6 @@ TEMPLATES = [
WSGI_APPLICATION = "application.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
@ -190,84 +190,72 @@ CHANNEL_LAYERS = {
# ================================================= #
# log 配置部分BEGIN #
SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
LOGS_FILE = os.path.join(BASE_DIR, "logs")
if not os.path.exists(os.path.join(BASE_DIR, "logs")):
os.makedirs(os.path.join(BASE_DIR, "logs"))
# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
# 格式:[日期][模块.函数名称():行号] [级别] 信息
STANDARD_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
CONSOLE_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {"format": STANDARD_LOG_FORMAT},
"console": {
"format": CONSOLE_LOG_FORMAT,
"servers": {
"format": "%(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
"file": {
"format": CONSOLE_LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
},
}
},
"handlers": {
"file": {
"level": "INFO",
"class": "logging.handlers.RotatingFileHandler",
"filename": SERVER_LOGS_FILE,
"servers": {
"logging_levels": ["info", "error", "warning"],
"class": "dvadmin.utils.log.InterceptTimedRotatingFileHandler", # 这个路径看你本地放在哪里(下面的log文件)
"filename": os.path.join(LOGS_FILE, "server.log"),
"when": "D",
"interval": 1,
"maxBytes": 1024 * 1024 * 100, # 100 MB
"backupCount": 5, # 最多备份5个
"formatter": "standard",
"backupCount": 1,
"formatter": "servers",
"encoding": "utf-8",
},
"error": {
"level": "ERROR",
"class": "logging.handlers.RotatingFileHandler",
"filename": ERROR_LOGS_FILE,
"maxBytes": 1024 * 1024 * 100, # 100 MB
"backupCount": 3, # 最多备份3个
"formatter": "standard",
"encoding": "utf-8",
},
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "console",
},
}
},
"loggers": {
# default日志
"": {
"handlers": ["console", "error", "file"],
"level": "INFO",
'django': {
'handlers': ['servers'],
'propagate': False,
'level': "INFO"
},
"django": {
"handlers": ["console", "error", "file"],
"level": "INFO",
"propagate": False,
'': {
'handlers': ['servers'],
'propagate': False,
'level': "ERROR"
},
"scripts": {
"handlers": ["console", "error", "file"],
"level": "INFO",
"propagate": False,
'celery': {
'handlers': ['servers'],
'propagate': False,
'level': "INFO"
},
# 数据库相关日志
"django.db.backends": {
"handlers": [],
"propagate": True,
'django.db.backends': {
'handlers': ['servers'],
'propagate': False,
'level': "INFO"
},
'django.request': {
'handlers': ['servers'],
'propagate': False,
'level': "DEBUG"
},
"uvicorn.error": {
"level": "INFO",
"handlers": ["servers"],
},
"uvicorn.access": {
"handlers": ["servers"],
"level": "INFO",
"propagate": False
},
},
}
# ================================================= #
# *************** REST_FRAMEWORK配置 *************** #
# ================================================= #
@ -306,12 +294,14 @@ from datetime import timedelta
SIMPLE_JWT = {
# token有效时长
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=120),
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
# token刷新后的有效时间
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
# 设置前缀
"AUTH_HEADER_TYPES": ("JWT",),
"ROTATE_REFRESH_TOKENS": True,
'BLACKLIST_AFTER_ROTATION': True,
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
}
# ====================================#
@ -396,7 +386,13 @@ TENANT_SHARED_APPS = []
PLUGINS_URL_PATTERNS = []
# ********** 一键导入插件配置开始 **********
# 例如:
# from dvadmin_upgrade_center.settings import * # 升级中心
# from dvadmin_celery.settings import * # celery 异步任务
# from dvadmin_upgrade_center.settings import * # 升级中心
# from dvadmin_celery.settings import * # celery 异步任务
# from dvadmin_sms.settings import * # 短信服务
# from dvadmin_third.settings import * # 扫码登录
# from dvadmin_uniapp.settings import * # UniApp后端
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
# from dvadmin_tenants.settings import * # 租户管理
# ...
# ********** 一键导入插件配置结束 **********

View File

@ -29,7 +29,7 @@ from dvadmin.system.views.login import (
LoginView,
CaptchaView,
ApiLogin,
LogoutView,
LogoutView, CustomTokenRefreshView,
)
from dvadmin.system.views.system_config import InitSettingsViewSet
from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator
@ -73,7 +73,7 @@ urlpatterns = (
path("api/system/", include("dvadmin.system.urls")),
path("api/login/", LoginView.as_view(), name="token_obtain_pair"),
path("api/logout/", LogoutView.as_view(), name="token_obtain_pair"),
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
path("token/refresh/", CustomTokenRefreshView.as_view(), name="token_refresh"),
re_path(
r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")
),

View File

@ -44,6 +44,6 @@ LOGIN_NO_CAPTCHA_AUTH = True
# ================================================= #
ALLOWED_HOSTS = ["*"]
# 系统配置存放位置redis/memory(默认)
DISPATCH_DB_TYPE = 'redis'
# daphne启动命令
#daphne application.asgi:application -b 0.0.0.0 -p 8000

View File

@ -2,4 +2,4 @@
# python manage.py makemigrations
# python manage.py migrate
# python manage.py init -y
gunicorn -c gunicorn.py application.asgi:application
gunicorn -c gunicorn_conf.py application.asgi:application

View File

@ -49,6 +49,25 @@
"placeholder": "请输入默认密码",
"setting": null,
"children": []
},
{
"parent": 10,
"title": "开启单点登录",
"key": "single_login",
"value": false,
"sort": 1,
"status": true,
"data_options": null,
"form_item_type": 9,
"rule": [
{
"message": "必填项不能为空",
"required": true
}
],
"placeholder": "请选择",
"setting": null,
"children": []
}
]
},

View File

@ -45,6 +45,7 @@ class Users(CoreModel,AbstractUser):
blank=True,
help_text="关联部门",
)
last_token = models.CharField(max_length=255,null=True,blank=True, verbose_name="最后一次登录Token", help_text="最后一次登录Token")
def set_password(self, raw_password):
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
@ -149,7 +150,7 @@ class Dept(CoreModel):
class Menu(CoreModel):
parent = models.ForeignKey(
to="Menu",
on_delete=models.CASCADE,
on_delete=models.PROTECT,
verbose_name="上级菜单",
null=True,
blank=True,
@ -184,7 +185,7 @@ class MenuButton(CoreModel):
to="Menu",
db_constraint=False,
related_name="menuPermission",
on_delete=models.CASCADE,
on_delete=models.PROTECT,
verbose_name="关联菜单",
help_text="关联菜单",
)
@ -309,7 +310,7 @@ class Area(CoreModel):
to="self",
verbose_name="父地区编码",
to_field="code",
on_delete=models.CASCADE,
on_delete=models.PROTECT,
db_constraint=False,
null=True,
blank=True,
@ -348,15 +349,15 @@ class SystemConfig(CoreModel):
parent = models.ForeignKey(
to="self",
verbose_name="父级",
on_delete=models.CASCADE,
on_delete=models.PROTECT,
db_constraint=False,
null=True,
blank=True,
help_text="父级",
)
title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")
key = models.CharField(max_length=20, verbose_name="", help_text="", db_index=True)
value = models.JSONField(max_length=100, verbose_name="", help_text="", null=True, blank=True)
key = models.CharField(max_length=100, verbose_name="", help_text="", db_index=True)
value = models.JSONField(max_length=200, verbose_name="", help_text="", null=True, blank=True)
sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True)
status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态")
data_options = models.JSONField(verbose_name="数据options", help_text="数据options", null=True, blank=True)
@ -406,7 +407,7 @@ class SystemConfig(CoreModel):
class LoginLog(CoreModel):
LOGIN_TYPE_CHOICES = ((1, "普通登录"), (2, "微信扫码登录"),)
LOGIN_TYPE_CHOICES = ((1, "普通登录"), (2, "微信扫码登录"), (3, "飞书扫码登录"), (4, "钉钉扫码登录"))
username = models.CharField(max_length=32, verbose_name="登录用户名", null=True, blank=True, help_text="登录用户名")
ip = models.CharField(max_length=32, verbose_name="登录ip", null=True, blank=True, help_text="登录ip")
agent = models.TextField(verbose_name="agent信息", null=True, blank=True, help_text="agent信息")
@ -456,4 +457,4 @@ class MessageCenterTargetUser(CoreModel):
class Meta:
db_table = table_prefix + "message_center_target_user"
verbose_name = "消息中心目标用户表"
verbose_name_plural = verbose_name
verbose_name_plural = verbose_name

View File

@ -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
@ -27,7 +28,7 @@ system_url.register(r'area', AreaViewSet)
system_url.register(r'file', FileViewSet)
system_url.register(r'api_white_list', ApiWhiteListViewSet)
system_url.register(r'system_config', SystemConfigViewSet)
system_url.register(r'message_center',MessageCenterViewSet)
system_url.register(r'message_center', MessageCenterViewSet)
urlpatterns = [
path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})),
@ -37,5 +38,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

View 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')

View File

@ -77,9 +77,10 @@ class DeptInitSerializer(CustomModelSerializer):
menu_data['parent'] = instance.id
filter_data = {
"name": menu_data['name'],
"parent": menu_data['parent'],
"key": menu_data['key']
"parent": menu_data['parent']
}
if 'key' in menu_data:
filter_data['key'] = menu_data['key']
instance_obj = Dept.objects.filter(**filter_data).first()
if instance_obj and not self.initial_data.get('reset'):
continue
@ -106,7 +107,7 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
"""
def create(self, validated_data):
value = validated_data.get('parent',None)
value = validated_data.get('parent', None)
if value is None:
validated_data['parent'] = self.request.user.dept
instance = super().create(validated_data)
@ -147,12 +148,12 @@ class DeptViewSet(CustomModelViewSet):
parent = params.get('parent', None)
if params:
if parent:
queryset = self.queryset.filter(status=True, parent=parent)
queryset = self.queryset.filter(parent=parent)
else:
queryset = self.queryset.filter(status=True)
queryset = self.queryset.filter(parent__isnull=True)
else:
queryset = self.queryset.filter(status=True, parent__isnull=True)
queryset = self.filter_queryset(queryset)
queryset = self.queryset.filter(parent__isnull=True)
queryset = self.filter_queryset(queryset.order_by('sort', 'create_datetime'))
serializer = DeptSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data)
@ -168,22 +169,28 @@ class DeptViewSet(CustomModelViewSet):
dept_list = [user_dept_id]
data_range_list = list(set(data_range))
for item in data_range_list:
if item in [0,2]:
if item in [0, 2]:
dept_list = [user_dept_id]
elif item == 1:
dept_list = Dept.recursion_dept_info(dept_id=user_dept_id)
elif item == 3:
dept_list = Dept.objects.values_list('id',flat=True)
dept_list = Dept.objects.values_list('id', flat=True)
elif item == 4:
dept_list = request.user.role.values_list('dept',flat=True)
dept_list = request.user.role.values_list('dept', flat=True)
else:
dept_list = []
queryset = Dept.objects.filter(id__in=dept_list).values('id', 'name', 'parent')
return DetailResponse(data=queryset, msg="获取成功")
@action(methods=["GET"], detail=False, permission_classes=[AnonymousUserPermission])
def all_dept(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent')
return DetailResponse(data=data, msg="获取成功")
@action(methods=["GET"], detail=False, permission_classes=[AnonymousUserPermission])
def all_dept_not_extra(self, request, *args, **kwargs):
self.extra_filter_backends = []
queryset = self.filter_queryset(self.get_queryset())
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent')
return DetailResponse(data=data, msg="获取成功")

View File

@ -10,9 +10,13 @@ from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers
from rest_framework.status import HTTP_401_UNAUTHORIZED
from rest_framework.views import APIView
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.token_blacklist.models import OutstandingToken
from rest_framework_simplejwt.tokens import RefreshToken, AccessToken
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from django.conf import settings
@ -57,36 +61,21 @@ class LoginSerializer(TokenObtainPairSerializer):
captcha = serializers.CharField(
max_length=6, required=False, allow_null=True, allow_blank=True
)
class Meta:
model = Users
fields = "__all__"
read_only_fields = ["id"]
class LoginView(TokenObtainPairView):
"""
登录接口
"""
serializer_class = LoginSerializer
permission_classes = []
def post(self, request, *args, **kwargs):
# username可能携带的不止是用户名可能还是用户的其它唯一标识 手机号 邮箱
username = request.data.get('username',None)
if username is None:
return ErrorResponse(msg="账号不能为空")
password = request.data.get('password',None)
if password is None:
return ErrorResponse(msg="密码不能为空")
default_error_messages = {"no_active_account": _("账号/密码错误")}
def validate(self, attrs):
captcha = self.initial_data.get("captcha", None)
if dispatch.get_system_config_values("base.captcha_state"):
captcha = request.data.get('captcha', None)
captchaKey = request.data.get('captchaKey', None)
if captchaKey is None:
return ErrorResponse(msg="验证码不能为空")
if captcha is None:
raise CustomValidationError("验证码不能为空")
self.image_code = CaptchaStore.objects.filter(
id=captchaKey
id=self.initial_data["captchaKey"]
).first()
five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
if self.image_code and five_minute_ago > self.image_code.expiration:
@ -101,36 +90,62 @@ class LoginView(TokenObtainPairView):
else:
self.image_code and self.image_code.delete()
raise CustomValidationError("图片验证码错误")
try:
# 手动通过 user 签发 jwt-token
user = Users.objects.get(username=username)
except:
return ErrorResponse(msg='该账号未注册')
# 获得用户后校验密码并签发token
if not user.check_password(password):
return ErrorResponse(msg='密码错误')
result = {
"name":user.name,
"userId":user.id,
"avatar":user.avatar,
}
dept = getattr(user, 'dept', None)
data = super().validate(attrs)
data["name"] = self.user.name
data["userId"] = self.user.id
data["avatar"] = self.user.avatar
data['user_type'] = self.user.user_type
dept = getattr(self.user, 'dept', None)
if dept:
result['dept_info'] = {
data['dept_info'] = {
'dept_id': dept.id,
'dept_name': dept.name,
'dept_key': dept.key
}
role = getattr(user, 'role', None)
role = getattr(self.user, 'role', None)
if role:
result['role_info'] = role.values('id', 'name', 'key')
refresh = LoginSerializer.get_token(user)
result["refresh"] = str(refresh)
result["access"] = str(refresh.access_token)
data['role_info'] = role.values('id', 'name', 'key')
request = self.context.get("request")
request.user = self.user
# 记录登录日志
request.user = user
save_login_log(request=request)
return DetailResponse(data=result,msg="获取成功")
# 是否开启单点登录
if dispatch.get_system_config_values("base.single_login"):
# 将之前登录用户的token加入黑名单
user = Users.objects.filter(id=self.user.id).values('last_token').first()
last_token = user.get('last_token')
if last_token:
try:
token = RefreshToken(last_token)
token.blacklist()
except:
pass
# 将最新的token保存到用户表
Users.objects.filter(id=self.user.id).update(last_token=data.get('refresh'))
return {"code": 2000, "msg": "请求成功", "data": data}
class CustomTokenRefreshView(TokenRefreshView):
"""
自定义token刷新
"""
def post(self, request, *args, **kwargs):
refresh_token = request.data.get("refresh")
try:
token = RefreshToken(refresh_token)
data = {
"access":str(token.access_token),
"refresh":str(token)
}
except:
return ErrorResponse(status=HTTP_401_UNAUTHORIZED)
return DetailResponse(data=data)
class LoginView(TokenObtainPairView):
"""
登录接口
"""
serializer_class = LoginSerializer
permission_classes = []
class LoginTokenSerializer(TokenObtainPairSerializer):
@ -165,6 +180,7 @@ class LoginTokenView(TokenObtainPairView):
class LogoutView(APIView):
def post(self, request):
Users.objects.filter(id=self.request.user.id).update(last_token=None)
return DetailResponse(msg="注销成功")

View File

@ -18,7 +18,7 @@ class MenuButtonSerializer(CustomModelSerializer):
class Meta:
model = MenuButton
fields = ['id', 'name', 'value', 'api', 'method']
fields = ['id', 'name', 'value', 'api', 'method', 'menu']
read_only_fields = ["id"]
@ -32,6 +32,7 @@ class MenuButtonInitSerializer(CustomModelSerializer):
fields = ['id', 'name', 'value', 'api', 'method', 'menu']
read_only_fields = ["id"]
class MenuButtonCreateUpdateSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化器

View File

@ -27,6 +27,7 @@ class SystemConfigCreateSerializer(CustomModelSerializer):
"""
form_item_type_label = serializers.CharField(source='get_form_item_type_display', read_only=True)
class Meta:
model = SystemConfig
fields = "__all__"

View File

@ -33,7 +33,6 @@ class UserSerializer(CustomModelSerializer):
"""
dept_name = serializers.CharField(source='dept.name', read_only=True)
role_info = DynamicSerializerMethodField()
dept_name_all = serializers.SerializerMethodField()
class Meta:
model = Users
@ -43,11 +42,6 @@ class UserSerializer(CustomModelSerializer):
"post": {"required": False},
}
def get_dept_name_all(self, instance):
dept_name_all = recursion(instance.dept, "parent", "name")
dept_name_all.reverse()
return "/".join(dept_name_all)
def get_role_info(self, instance, parsed_query):
roles = instance.role.all()
# You can do what ever you want in here

View File

@ -12,7 +12,8 @@ import traceback
from django.db.models import ProtectedError
from django.http import Http404
from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed
from rest_framework.views import set_rollback
from rest_framework.status import HTTP_407_PROXY_AUTHENTICATION_REQUIRED, HTTP_401_UNAUTHORIZED
from rest_framework.views import set_rollback, exception_handler
from dvadmin.utils.json_response import ErrorResponse
@ -30,10 +31,19 @@ def CustomExceptionHandler(ex, context):
"""
msg = ''
code = 4000
# 调用默认的异常处理函数
response = exception_handler(ex, context)
if isinstance(ex, AuthenticationFailed):
code = 401
msg = ex.detail
# 如果是身份验证错误
if response and response.data.get('detail') =="Given token not valid for any token type":
code = 401
msg = ex.detail
elif response and response.data.get('detail') =="Token is blacklisted":
# token在黑名单
return ErrorResponse(status=HTTP_401_UNAUTHORIZED)
else:
code = 401
msg = ex.detail
elif isinstance(ex,Http404):
code = 400
msg = "接口地址不正确"
@ -46,11 +56,11 @@ def CustomExceptionHandler(ex, context):
msg = "%s:%s" % (k, i)
elif isinstance(ex, ProtectedError):
set_rollback()
msg = "删除失败:该条数据与其他数据有相关绑定"
msg = "无法删除:该条数据与其他数据有相关绑定"
# elif isinstance(ex, DatabaseError):
# set_rollback()
# msg = "接口服务器异常,请联系管理员"
elif isinstance(ex, Exception):
logger.error(traceback.format_exc())
logger.exception(traceback.format_exc())
msg = str(ex)
return ErrorResponse(msg=msg, code=code)

View File

@ -0,0 +1,134 @@
import logging
import os.path
import sys
from django.db import connection
from loguru import logger
from logging.handlers import RotatingFileHandler
# 1.🎖先声明一个类继承logging.Handler(制作一件品如的衣服)
from application.dispatch import is_tenants_mode
class InterceptTimedRotatingFileHandler(RotatingFileHandler):
"""
自定义反射时间回滚日志记录器
缺少命名空间
"""
def __init__(self, filename, when='d', interval=1, backupCount=5, encoding="utf-8", delay=False, utc=False,
maxBytes=1024 * 1024 * 100, atTime=None, logging_levels="all", format=None):
super(InterceptTimedRotatingFileHandler, self).__init__(filename)
filename = os.path.abspath(filename)
# 定义默认格式
if not format:
format = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <green>{extra[client_addr]:^18}</green> | <level>{level: <8}</level>| <cyan>{message}</cyan>"
when = when.lower()
# 2.🎖️需要本地用不同的文件名做为不同日志的筛选器
logger.configure(
handlers=[
dict(sink=sys.stderr, format=format),
],
)
self.logger_ = logger.bind(sime=filename, client_addr="-")
self.filename = filename
key_map = {
'h': 'hour',
'w': 'week',
's': 'second',
'm': 'minute',
'd': 'day',
}
# 根据输入文件格式及时间回滚设立文件名称
rotation = f"{maxBytes / 1024 / 1024}MB"
retention = "%d %ss" % (backupCount, key_map[when])
time_format = "{time:%Y-%m-%d_%H-%M-%S}"
if when == "s":
time_format = "{time:%Y-%m-%d_%H-%M-%S}"
elif when == "m":
time_format = "{time:%Y-%m-%d_%H-%M}"
elif when == "h":
time_format = "{time:%Y-%m-%d_%H}"
elif when == "d":
time_format = "{time:%Y-%m-%d}"
elif when == "w":
time_format = "{time:%Y-%m-%d}"
level_keys = ["info"]
# 3.🎖️构建一个筛选器
levels = {
"debug": lambda x: "DEBUG" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"error": lambda x: "ERROR" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"info": lambda x: "INFO" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"warning": lambda x: "WARNING" == x['level'].name.upper() and x['extra'].get('sime') == filename
}
# 4. 🎖️根据输出构建筛选器
if isinstance(logging_levels, str):
if logging_levels.lower() == "all":
level_keys = levels.keys()
elif logging_levels.lower() in levels:
level_keys = [logging_levels]
elif isinstance(logging_levels, (list, tuple)):
level_keys = logging_levels
for k, f in {_: levels[_] for _ in level_keys}.items():
# 5.🎖为防止重复添加sink而重复写入日志需要判断是否已经装载了对应sink防止其使用秘技反复横跳。
filename_fmt = filename.replace(".log", "_%s_%s.log" % (time_format, k))
# noinspection PyUnresolvedReferences,PyProtectedMember
file_key = {_._name: han_id for han_id, _ in self.logger_._core.handlers.items()}
filename_fmt_key = "'{}'".format(filename_fmt)
if filename_fmt_key in file_key:
continue
# self.logger_.remove(file_key[filename_fmt_key])
self.logger_.add(
filename_fmt,
format=format,
retention=retention,
encoding=encoding,
level=self.level,
rotation=rotation,
compression="zip", # 日志归档自行压缩文件
delay=delay,
enqueue=True,
backtrace=True,
filter=f
)
def emit(self, record):
try:
level = self.logger_.level(record.levelname).name
except ValueError:
level = record.levelno
frame, depth = logging.currentframe(), 2
# 6.🎖️把当前帧的栈深度回到发生异常的堆栈深度,不然就是当前帧发生异常而无法回溯
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
# 设置自定义属性
details = frame.f_locals.get('details', None)
msg = self.format(record)
bind = {}
record_client = None
if isinstance(record.args, dict):
record_client = record.args.get('client_addr') or record.args.get('client')
elif isinstance(record.args, tuple) and len(record.args) > 0:
if ":" in str(record.args[0]):
record_client = record.args[0]
if msg.split("-") and len(msg.split("-")) == 2:
msg = f"{msg.split('-')[1].strip(' ')}"
elif isinstance(record.args[0], tuple) and len(record.args[0]) == 2:
record_client = f"{record.args[0][0]}:{record.args[0][1]}"
if msg.split("-") and len(msg.split("-")) == 2:
msg = f"{msg.split('-')[1].strip(' ')}"
client = record_client or (details and details.get('client'))
if client:
bind["client_addr"] = client
if is_tenants_mode():
bind["schema_name"] = connection.tenant.schema_name
bind["domain_url"] = getattr(connection.tenant, 'domain_url', None)
self.logger_ \
.opt(depth=depth, exception=record.exc_info, colors=True, lazy=True) \
.bind(**bind) \
.log(level, msg)

View File

@ -23,7 +23,7 @@ pidfile = './gunicorn.pid'
# 日志级别,这个日志级别指的是错误日志的级别,而访问日志的级别无法设置
loglevel = 'info'
# 设置gunicorn访问日志格式错误日志无法设置
access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"'
access_log_format = '' # worker_class 为 uvicorn.workers.UvicornWorker 时日志格式为Django的loggers
# 监听队列
backlog = 512
#进程名

View File

@ -3,31 +3,31 @@ certifi==2021.5.30
chardet==4.0.0
coreapi==2.3.3
coreschema==0.0.4
Django==3.2.3
django-comment-migrate==0.1.5
Django==3.2.12
django-comment-migrate==0.1.7
django-cors-headers==3.10.1
django-filter==21.1
django-filter==22.1
django-ranged-response==0.2.0
django-restql==0.15.1
django-simple-captcha==0.5.14
django-restql==0.15.3
django-simple-captcha==0.5.17
django-timezone-field==4.2.3
djangorestframework==3.12.4
djangorestframework-simplejwt==5.1.0
drf-yasg==1.20.0
djangorestframework==3.14.0
djangorestframework-simplejwt==5.2.2
packaging==23.0
drf-yasg==1.21.5
idna==2.10
inflection==0.5.1
itypes==1.2.0
Jinja2==3.0.1
Jinja2==3.1.2
MarkupSafe==2.0.1
mysqlclient==2.0.3
packaging==20.9
Pillow==8.3.1
PyJWT==2.1.0
mysqlclient==2.1.1
Pillow==9.4.0
PyJWT==2.6.0
pyparsing==2.4.7
pyPEG2==2.15.2
pypinyin==0.46.0
pypinyin==0.48.0
pytz==2021.1
requests==2.25.1
requests==2.28.2
ruamel.yaml==0.17.10
ruamel.yaml.clib==0.2.4
six==1.16.0
@ -37,13 +37,14 @@ typing-extensions==3.10.0.0
tzlocal==2.1
ua-parser==0.10.0
uritemplate==3.0.1
urllib3==1.26.6
urllib3==1.26.15
user-agents==2.2.0
whitenoise==5.3.0
openpyxl==3.0.9
openpyxl==3.1.2
channels==3.0.5
channels-redis==3.4.1
uvicorn==0.20.0
uvicorn==0.21.1
gunicorn==20.1.0
gevent==22.10.2
websockets==10.4
loguru==0.6.0

View 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;}

View 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>更新日期2023年01月01日
<br>生效日期2023年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>

View 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>更新日期2023年01月01日
<br>生效日期2023年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>

View File

@ -1,22 +1,22 @@
module.exports = [
{ name: 'vue', library: 'Vue', js: 'https://unpkg.com/vue@2.6.10/dist/vue.min.js', css: '' },
{ name: 'vue-i18n', library: 'VueI18n', js: 'https://unpkg.com/vue-i18n@8.15.1/dist/vue-i18n.min.js', css: '' },
{ name: 'vue-router', library: 'VueRouter', js: 'https://unpkg.com/vue-router@3.1.3/dist/vue-router.min.js', css: '' },
{ name: 'vuex', library: 'Vuex', js: 'https://unpkg.com/vuex@3.1.2/dist/vuex.min.js', css: '' },
{ name: 'axios', library: 'axios', js: 'https://unpkg.com/axios@0.19.0/dist/axios.min.js', css: '' },
{ name: 'better-scroll', library: 'BScroll', js: 'https://unpkg.com/better-scroll@1.15.2/dist/bscroll.min.js', css: '' },
{ name: 'axios-mock-adapter', library: 'AxiosMockAdapter', js: 'https://unpkg.com/axios-mock-adapter@1.18.1/dist/axios-mock-adapter.min.js', css: '' },
{ name: 'element-ui', library: 'ELEMENT', js: 'https://unpkg.com/element-ui@2.15.5/lib/index.js', css: 'https://unpkg.com/element-ui@2.15.5/lib/theme-chalk/index.css' },
{ name: 'lodash', library: '_', js: 'https://unpkg.com/lodash@4.17.15/lodash.min.js', css: '' },
{ name: 'ua-parser-js', library: 'UAParser', js: 'https://unpkg.com/ua-parser-js@0.7.20/dist/ua-parser.min.js', css: '' },
{ name: 'js-cookie', library: 'Cookies', js: 'https://unpkg.com/js-cookie@2.2.1/src/js.cookie.js', css: '' },
{ name: 'nprogress', library: 'NProgress', js: 'https://unpkg.com/nprogress@0.2.0/nprogress.js', css: 'https://unpkg.com/nprogress@0.2.0/nprogress.css' },
{ name: 'dayjs', library: 'dayjs', js: 'https://unpkg.com/dayjs@1.8.17/dayjs.min.js', css: '' },
{ name: 'fuse.js', library: 'Fuse', js: 'https://unpkg.com/fuse.js@5.2.3/dist/fuse.min.js', css: '' },
{ name: 'hotkeys-js', library: 'hotkeys', js: 'https://unpkg.com/hotkeys-js@3.7.3/dist/hotkeys.min.js', css: '' },
{ name: 'qs', library: 'Qs', js: 'https://unpkg.com/qs@6.9.1/dist/qs.js', css: '' },
{ name: 'lowdb', library: 'low', js: 'https://unpkg.com/lowdb@1.0.0/dist/low.min.js', css: '' },
{ name: 'lowdb/adapters/LocalStorage', library: 'LocalStorage', js: 'https://unpkg.com/lowdb@1.0.0/dist/LocalStorage.min.js', css: '' },
{ name: 'screenfull', library: 'screenfull', js: 'https://unpkg.com/screenfull@5.0.2/dist/screenfull.js', css: '' },
{ name: 'sortablejs', library: 'Sortable', js: 'https://unpkg.com/sortablejs@1.10.1/Sortable.min.js', css: '' }
// { name: 'vue', library: 'Vue', js: 'https://unpkg.com/vue@2.6.10/dist/vue.min.js', css: '' },
// { name: 'vue-i18n', library: 'VueI18n', js: 'https://unpkg.com/vue-i18n@8.15.1/dist/vue-i18n.min.js', css: '' },
// { name: 'vue-router', library: 'VueRouter', js: 'https://unpkg.com/vue-router@3.1.3/dist/vue-router.min.js', css: '' },
// { name: 'vuex', library: 'Vuex', js: 'https://unpkg.com/vuex@3.1.2/dist/vuex.min.js', css: '' },
// { name: 'axios', library: 'axios', js: 'https://unpkg.com/axios@0.19.0/dist/axios.min.js', css: '' },
// { name: 'better-scroll', library: 'BScroll', js: 'https://unpkg.com/better-scroll@1.15.2/dist/bscroll.min.js', css: '' },
// { name: 'axios-mock-adapter', library: 'AxiosMockAdapter', js: 'https://unpkg.com/axios-mock-adapter@1.18.1/dist/axios-mock-adapter.min.js', css: '' },
// { name: 'element-ui', library: 'ELEMENT', js: 'https://unpkg.com/element-ui@2.15.5/lib/index.js', css: 'https://unpkg.com/element-ui@2.15.5/lib/theme-chalk/index.css' },
// { name: 'lodash', library: '_', js: 'https://unpkg.com/lodash@4.17.15/lodash.min.js', css: '' },
// { name: 'ua-parser-js', library: 'UAParser', js: 'https://unpkg.com/ua-parser-js@0.7.20/dist/ua-parser.min.js', css: '' },
// { name: 'js-cookie', library: 'Cookies', js: 'https://unpkg.com/js-cookie@2.2.1/src/js.cookie.js', css: '' },
// { name: 'nprogress', library: 'NProgress', js: 'https://unpkg.com/nprogress@0.2.0/nprogress.js', css: 'https://unpkg.com/nprogress@0.2.0/nprogress.css' },
// { name: 'dayjs', library: 'dayjs', js: 'https://unpkg.com/dayjs@1.8.17/dayjs.min.js', css: '' },
// { name: 'fuse.js', library: 'Fuse', js: 'https://unpkg.com/fuse.js@5.2.3/dist/fuse.min.js', css: '' },
// { name: 'hotkeys-js', library: 'hotkeys', js: 'https://unpkg.com/hotkeys-js@3.7.3/dist/hotkeys.min.js', css: '' },
// { name: 'qs', library: 'Qs', js: 'https://unpkg.com/qs@6.9.1/dist/qs.js', css: '' },
// { name: 'lowdb', library: 'low', js: 'https://unpkg.com/lowdb@1.0.0/dist/low.min.js', css: '' },
// { name: 'lowdb/adapters/LocalStorage', library: 'LocalStorage', js: 'https://unpkg.com/lowdb@1.0.0/dist/LocalStorage.min.js', css: '' },
// { name: 'screenfull', library: 'screenfull', js: 'https://unpkg.com/screenfull@5.0.2/dist/screenfull.js', css: '' },
// { name: 'sortablejs', library: 'Sortable', js: 'https://unpkg.com/sortablejs@1.10.1/Sortable.min.js', css: '' }
]

View File

@ -1,6 +1,6 @@
{
"name": "django-vue-admin",
"version": "2.0.8",
"version": "2.1.1",
"scripts": {
"serve": "vue-cli-service serve --open",
"start": "npm run serve",
@ -13,7 +13,6 @@
},
"dependencies": {
"@great-dream/template": "^1.0.2",
"@vue/composition-api": "1.0.4",
"axios": "^0.19.0",
"axios-mock-adapter": "^1.18.1",
"better-scroll": "^1.15.2",

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@ -1,13 +1,20 @@
<template>
<div id="app">
<router-view />
<!-- 用来兼容乾坤前端微服务 -->
<router-view/>
<!-- 用来兼容乾坤前端微服务 -->
<div id="qiankun"></div>
<!-- 授权后可以删除-->
<div class="dvadmin-auth">
<span>Powered by Django-Vue-Admin</span>
<el-divider direction="vertical"></el-divider>
<span>Copyright dvadmin团队</span>
</div>
</div>
</template>
<script>
import util from '@/libs/util'
export default {
name: 'app',
watch: {
@ -26,6 +33,22 @@ export default {
</script>
<style lang="scss">
//
.dvadmin-auth {
font-size: 0.8em;
position: fixed;
top: 50vh;
right: -163px;;
text-align: center;
color: #888888;
background-image: linear-gradient(to left, #d3d3d3, #989898, #888888, #363636, #888888, #989898, #d3d3d3);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-background-size: 200% 100%;
animation: bgp 6s infinite linear;
transform: rotate(90deg);
}
@import "~@/assets/style/public-class.scss";
@import "~@/assets/style/yxt-public.scss";
</style>

View File

@ -9,8 +9,6 @@ import qs from 'qs'
/**
* @description 创建请求实例
*/
axios.defaults.retry = 1
axios.defaults.retryDelay = 1000
export function getErrorMessage (msg) {
if (typeof msg === 'string') {
@ -57,9 +55,9 @@ function createService () {
)
// 响应拦截
service.interceptors.response.use(
response => {
async response => {
// dataAxios axios 返回数据中的 data
let dataAxios = response.data
let dataAxios = response.data || null
if (response.headers['content-disposition']) {
dataAxios = response
}
@ -78,13 +76,26 @@ function createService () {
// return dataAxios.data
return dataAxios
case 401:
// TODO 置换token 未完善
util.cookies.remove('token')
util.cookies.remove('uuid')
util.cookies.remove('refresh')
router.push({ path: '/login' })
errorCreate(`${getErrorMessage(dataAxios.msg)}`)
break
if (response.config.url === 'api/login/') {
errorCreate(`${getErrorMessage(dataAxios.msg)}`)
break
}
var res = await refreshTken()
// 设置请求超时次数
var config = response.config
util.cookies.set('token', res.data.access)
config.headers.Authorization = 'JWT ' + res.data.access
config.__retryCount = config.__retryCount || 0
if (config.__retryCount >= config.retry) {
// 如果重试次数超过3次则跳转登录页面
util.cookies.remove('token')
util.cookies.remove('uuid')
router.push({ path: '/login' })
errorCreate('认证已失效,请重新登录~')
break
}
config.__retryCount += 1
return service(config)
case 404:
dataNotFound(`${dataAxios.msg}`)
break
@ -109,13 +120,11 @@ function createService () {
error.message = '请求错误'
break
case 401:
refreshTken().then(res => {
util.cookies.set('token', res.access)
}).catch(e => {
router.push({ name: 'login' })
router.go(0)
error.message = '未认证,请登录'
})
util.cookies.remove('token')
util.cookies.remove('uuid')
util.cookies.remove('refresh')
router.push({ path: '/login' })
error.message = '认证已失效,请重新登录~'
break
case 403:
error.message = '拒绝访问'
@ -180,7 +189,9 @@ function createRequestFunction (service) {
timeout: 60000,
baseURL: util.baseURL(),
data: {},
params: params
params: params,
retry: 3, // 重新请求次数
retryDelay: 1000 // 重新请求间隔
}
return service(Object.assign(configDefault, config))
}
@ -216,7 +227,12 @@ const refreshTken = function () {
* @param method
* @param filename
*/
export const downloadFile = function ({ url, params, method, filename = '文件导出' }) {
export const downloadFile = function ({
url,
params,
method,
filename = '文件导出'
}) {
request({
url: url,
method: method,

View File

@ -1,6 +1,6 @@
<template>
<div>
{{$store.state.d2admin.dept.data[currentValue] || ''}}
{{currentValue}}
</div>
</template>
<script>
@ -24,21 +24,20 @@ export default {
}
},
watch: {
value (value) {
// this.$emit('change', value)
if (this.currentValue === value) {
return
}
this.setValue(value)
value () {
this.setValue()
}
},
created () {
this.setValue(this.value)
},
methods: {
setValue (value) {
async setValue () {
// value
this.currentValue = String(this.value)
if (!this.$store.state.d2admin.dept.data) {
await this.$store.dispatch('d2admin/dept/load')
}
this.currentValue = this.$store.state.d2admin.dept.data[String(this.value)]
// key
}
}

View File

@ -97,12 +97,6 @@
</div>
</div>
</div>
<!-- 授权后可以删除-->
<div class="dvadmin-auth">
<span>Powered by Django-Vue-Admin</span>
<el-divider direction="vertical"></el-divider>
<span>Copyright dvadmin团队</span>
</div>
</div>
</template>
@ -215,21 +209,6 @@ export default {
//
@import "~@/assets/style/theme/register.scss";
//
.dvadmin-auth{
font-size: 0.8em;
position: absolute;
top: 50vh;
right: -9vw;
text-align: center;
color: #888888;
background-image: linear-gradient(to left, #d3d3d3, #989898, #888888, #363636,#888888,#989898,#d3d3d3);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-background-size: 200% 100%;
animation: bgp 6s infinite linear;
transform: rotate(90deg);
}
@-webkit-keyframes bgp {
0% {background-position: 0 0; }
100% {background-position: -100% 0; }

View File

@ -123,6 +123,30 @@ util.randomString = function (e) {
return n
}
util.randomColor = function () {
const color = [
'#50A8F4FF',
'#FD6165FF',
'#E679D8FF',
'#F9AB5BFF'
]
const ran = Math.floor(Math.random() * color.length)
return color[ran]
}
util.randomBackground = function () {
const background = [
'linear-gradient(150deg, #accaff 0%, #3b88ec 100%)',
'linear-gradient(150deg, #c5f8e6 0%, #10a465 100%)',
'linear-gradient(150deg, #e8d6ff 0%, #9f55ff 100%)',
'linear-gradient(150deg, #fdda45 0%, #fe6b62 100%)',
'linear-gradient(150deg, #cefbc8 0%, #00aec5 100%)',
'linear-gradient(150deg, #c5f8e6 0%, #10a465 100%)'
]
const ran = Math.floor(Math.random() * background.length)
return background[ran]
}
util.ArrayToTree = function (rootList, parentValue, parentName, list) {
for (const item of rootList) {
if (item.parent === parentValue) {

View File

@ -34,11 +34,13 @@ import md5 from 'js-md5'
// websocket
import websocket from '@/api/websocket'
import util from '@/libs/util'
// 核心插件
Vue.use(d2Admin)
Vue.use(VXETable)
Vue.prototype.$md5 = md5
Vue.prototype.$util = util
Vue.prototype.$websocket = websocket
new Vue({

View File

@ -1,9 +1,10 @@
<template>
<el-card shadow="hover" header="关于项目" class="item-background">
<p>高性能 / 精致 / 优雅基于Vue2 + Element-UI 的中后台前端解决方案如果喜欢就点个星星支持一下</p>
<p>
<a href='https://liqianglog.gitee.io/django-vue-admin' target="_blank">
<img src='https://liqianglog.gitee.io/django-vue-admin/badge/star.svg?theme=dark' alt='star' style="vertical-align: middle">
<el-card shadow="hover" header="关于项目" class="card-view" :style="{backgroundColor:randomColor()}">
<p>基于RBAC模型的权限控制的一整套基础开发平台前后端分离后端采用 django+django-rest-framework前端采用
vue+ElementUI+d2-crud-plus如果喜欢就点个星星支持一下
<a href='https://gitee.com/liqianglog/django-vue-admin'>
<img src='https://gitee.com/liqianglog/django-vue-admin/badge/star.svg?theme=dark' alt='star'
style="position: absolute;right: 20px;bottom: 10px;"/>
</a>
</p>
</el-card>
@ -16,17 +17,52 @@ export default {
description: '点个星星支持一下',
height: 20,
width: 8,
minH: 20,
minW: 4,
minH: 10,
minW: 2,
isResizable: true,
config: {
color: {
label: '背景颜色',
type: 'color',
value: '',
placeholder: '颜色为空则随机变换颜色'
}
},
props: {
config: {
type: Object,
required: false
}
},
data () {
return {}
},
methods: {
//
randomColor () {
if (this.config?.color?.value) {
return this.config.color.value
}
return this.color || this.$util.randomColor()
}
}
}
</script>
<style scoped>
.item-background p {
color: #999;
<style scoped lang="scss">
.card-view {
color: #FFFFFF;
p {
font-size: 0.7em;
color: #FFFFFF;
}
}
.el-card{
height: 100%;
}
::v-deep .el-card__body {
height: 110px;
}
</style>

View File

@ -0,0 +1,70 @@
<template>
<el-card shadow="never" class="item-background">
<img :src="config.src.value || '/image/card/tencent.jpg'" @click="Jump()" class="img-style">
</el-card>
</template>
<script>
export default {
title: '宣传图',
icon: 'el-icon-medal',
height: 10,
width: 8,
minH: 10,
minW: 1,
maxW: 24,
maxH: 100,
isResizable: true,
config: {
src: {
label: '图片地址',
type: 'input',
value: '/image/card/tencent.jpg',
placeholder: '请输入图片地址',
rules: [{ required: true, message: '不能为空' }]
},
url: {
label: '跳转地址',
type: 'input',
placeholder: '请输入跳转地址',
value: 'https://cloud.tencent.com/act/cps/redirect?redirect=1060&cps_key=b302a514a6688aa30823fac954464e5d&from=console',
rules: [{ required: true, message: '不能为空' }]
}
},
props: {
config: {
type: Object,
required: false
}
},
description: '用于展示各种图片宣传页',
data () {
return {
}
},
methods: {
Jump () {
window.open(this.config.url.value, '_black')
}
}
}
</script>
<style scoped lang="scss">
.img-style {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 4px;
cursor: pointer;
}
.el-card{
height: 100%;
}
::v-deep .el-card__body {
padding: 0;
border-radius: 4px;
}
</style>

View File

@ -14,8 +14,8 @@ export default {
title: '时钟',
icon: 'el-icon-alarm-clock',
description: '演示部件效果',
height: 17,
minH: 17,
height: 20,
minH: 10,
width: 8,
minW: 4,
isResizable: true,
@ -33,7 +33,7 @@ export default {
},
methods: {
showTime () {
this.time = dayjs().format('hh:mm:ss')
this.time = dayjs().format('HH:mm:ss')
this.day = dayjs().format('YYYY年MM月DD日')
}
}
@ -47,12 +47,19 @@ export default {
}
.time h2 {
font-size: 40px;
font-size: 24px;
}
.time p {
font-size: 14px;
font-size: 18px;
margin-top: 10px;
opacity: 0.7;
}
::v-deep .el-card__body {
height: 110px;
}
.el-card{
height: 100%;
}
</style>

View File

@ -1,44 +1,70 @@
<template>
<el-card shadow="hover" header="版本信息">
<div style="height: 70px;text-align: center;">
<h2 style="margin-top: 5px;">Dvadmin</h2>
<el-card shadow="hover" class="card-view" :style="{backgroundColor:randomColor()}" header="版本信息">
<div style="text-align: center;color: #FFFFFF">
<h2 style="margin-top: 5px;">{{ title }}</h2>
<p style="margin-top: 5px;">最新版本 {{ ver }}</p>
</div>
<div style="margin-top: 5px;">
<el-button type="primary" plain round @click="golog"></el-button>
<el-button type="primary" plain round @click="gogit">gitee</el-button>
</div>
</el-card>
</template>
<script>
import { mapState } from 'vuex'
export default {
title: '版本信息',
icon: 'el-icon-monitor',
description: '当前项目版本信息',
height: 22,
height: 20,
minH: 10,
width: 8,
minH: 22,
minW: 4,
isResizable: true,
data () {
return {
ver: 'loading...'
ver: 'loading...',
title: ''
}
},
mounted () {
this.getVer()
},
computed: {
...mapState('d2admin', {
siteName: state => state.settings.data['login.site_name'] //
})
},
methods: {
async getVer () {
this.ver = 'v2.0.9'
this.ver = `v${process.env.VUE_APP_VERSION}` || 'v2.1.1'
this.title = this.siteName || process.env.VUE_APP_TITLE
},
golog () {
window.open('https://gitee.com/liqianglog/django-vue-admin/releases')
},
gogit () {
window.open('https://gitee.com/liqianglog/django-vue-admin')
},
//
randomColor () {
return this.color || this.$util.randomColor()
}
}
}
</script>
<style scoped lang="scss">
.card-view{
color: #FFFFFF;
//background: rgb(80,168,244);
//box-shadow: 1px 6px 8px 2px rgba(80,168,244,0.2);
.card-content{
//text-align: center;
}
}
::v-deep .el-card__body {
height: 110px;
}
.el-card{
height: 100%;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<el-card shadow="hover" header="欢迎">
<el-card shadow="hover" header="欢迎" style="background: linear-gradient(150deg, #3b88ec 0%, #accaff 100%);color: #fff;">
<div class="welcome">
<div class="logo">
<img src="/image/django-vue-admin.png">
@ -37,10 +37,10 @@ export default {
title: '欢迎',
icon: 'el-icon-present',
description: '项目特色以及文档链接',
height: 50,
minH: 50,
width: 8,
minW: 4,
height: 45,
minH: 45,
minW: 1,
isResizable: true,
data () {
return {}
@ -48,15 +48,19 @@ export default {
methods: {
godoc () {
window.open('https://www.django-vue-admin.com/')
},
//
randomColor () {
if (this.config?.color?.value) {
return this.config.color.value
}
return this.color || this.$util.randomColor()
}
}
}
</script>
<style scoped>
.welcome {
}
.welcome .logo {
text-align: center;
}
@ -108,6 +112,9 @@ export default {
.actions {
text-align: center;
margin: 40px 0 20px 0;
margin: 40rpx 0 20rpx 0;
}
.el-card{
height: 100%;
}
</style>

View File

@ -0,0 +1,70 @@
<template>
<div>
<el-drawer
:visible.sync="deviceUpgradeDrawer"
:size="500">
<div slot="title">
<span>部件配置</span>
<el-tag size="small" style="margin-left: 10px">{{ myComp.title }}</el-tag>
</div>
<!-- 组件内容 -->
<el-form ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item
v-for="(item,index) in items.config"
:label="item.label"
:key="index"
:rules="item.rules">
<el-input v-if="item.type==='input'" v-model="item.value" :placeholder="item.placeholder || '请输入'"></el-input>
<el-color-picker v-if="item.type==='color'" v-model="item.value" show-alpha :predefine="predefineColors"></el-color-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="deviceUpgradeDrawer = false">保存</el-button>
<el-button @click="deviceUpgradeDrawer = false">关闭</el-button>
</el-form-item>
</el-form>
</el-drawer>
</div>
</template>
<script>
export default {
name: 'dashboardConfig',
data () {
return {
deviceUpgradeDrawer: false,
myComp: {},
items: {},
predefineColors: [
'#ff4500',
'#ff8c00',
'#ffd700',
'#90ee90',
'#00ced1',
'#1e90ff',
'#c71585',
'rgba(255, 69, 0, 0.68)',
'rgb(255, 120, 0)',
'hsv(51, 100, 98)',
'hsva(120, 40, 94, 0.5)',
'hsl(181, 100%, 37%)',
'hsla(209, 100%, 56%, 0.73)',
'#c7158577'
]
}
},
mounted () {
},
methods: {
initData (myComp, items) {
this.myComp = myComp
this.items = items
console.log(1112, this.myComp, this.items)
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,42 +1,45 @@
<template>
<d2-container>
<div class="component-header">
<div class="set-btn-class">
<el-button v-if="customizing" type="primary" icon="el-icon-check" round @click="save"></el-button>
<suspended-library ref="suspendedLibrary">
<div class="set-btn-class" slot="callbackButton">
<el-button v-if="customizing" type="primary" icon="el-icon-check" round @click="save">&nbsp;&nbsp;
</el-button>
<el-button v-else type="primary" icon="el-icon-edit" round @click="custom"></el-button>
<el-button v-if="minimize" type="warning" icon="el-icon-plus" round @click="clickMinimize">&nbsp;&nbsp;
</el-button>
</div>
<div v-if="customizing" class="all-component-class">
<el-card style="margin-bottom: 20px">
<div slot="header">
<i class="el-icon-circle-plus"></i>
<span>添加部件</span>
<el-button-group style="float: right">
<el-button type="primary" size="mini" @click="backDefaul()"></el-button>
<el-button type="danger" size="mini" @click="close()"></el-button>
</el-button-group>
</div>
<div class="widgets-list">
<div v-if="myCompsList.length<=0" class="widgets-list-nodata">
<el-empty description="没有部件啦" :image-size="60"></el-empty>
</div>
<div v-for="item in myCompsList" :key="item.title" class="widgets-list-item" @drag="onDrag($event,item)"
@dragend="onDragend($event,item)" draggable="true"
unselectable="on">
<el-card style="width: 300px">
<div slot="header">
<i :class="item.icon"></i>
<span> {{ item.title }}</span>
<el-button type="primary" style="float: right;" size="mini" @click="push(item)"></el-button>
</div>
<div class="item-info">
<p>{{ item.description }}</p>
</div>
</el-card>
</div>
</div>
</el-card>
<div slot="operateButton">
<el-tooltip class="item" effect="dark" content="最小化" placement="top">
<el-button v-if="customizing" type="success" icon="el-icon-minus" circle size="mini"
@click="clickMinimize"></el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="恢复默认" placement="top">
<el-button v-if="customizing" type="primary" icon="el-icon-refresh-right" circle size="mini"
@click="backDefault()"></el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="关闭" placement="top">
<el-button v-if="customizing" type="danger" icon="el-icon-close" circle size="mini"
@click="close()"></el-button>
</el-tooltip>
</div>
</div>
<div slot="widgetsList">
<div v-if="myCompsList.length<=0" class="widgets-list-nodata">
<el-empty description="没有部件啦" :image-size="60"></el-empty>
</div>
<div class="widgetsListBox">
<span v-for="item in myCompsList" :key="item.title">
<el-tooltip class="item" effect="dark" :content="item.description" placement="top">
<div class="widgetsListItem" :style="{background: $util.randomBackground()}">
<span style="position: relative;right: 8px;float: right;top: -22px;cursor: pointer;" @click="push(item)">
<i class="el-icon-plus"></i>
</span>
<i :class="item.icon"></i> &nbsp;{{ item.title }}
</div>
</el-tooltip>
</span>
</div>
</div>
</suspended-library>
<div class="widgets" ref="widgets">
<div :class="['widgets-wrapper',customizing?'widgets-wrapper-bg':'']">
<div v-if="nowCompsList.length<=0" class="no-widgets">
@ -45,7 +48,7 @@
<grid-layout
ref="gridlayout"
:layout.sync="layout"
:col-num="24"
:col-num="colNum"
:row-height="1"
:is-draggable="customizing"
:vertical-compact="false"
@ -59,22 +62,31 @@
:w="item.w"
:h="item.h"
:i="item.i"
:minW="item.minW"
:minH="item.minH"
:maxW="item.maxW"
:maxH="item.maxH"
:key="index"
:isResizable="customizing"
>
<div v-if="customizing" class="customize-overlay">
<div v-if="customizing" class="customize-overlay">
<el-button v-if="item.config && Object.keys(item.config).length!==0" class="close" style="right: 60px;"
type="primary" plain icon="el-icon-setting" size="small"
@click="clickConfig(item,index)" circle></el-button>
<el-button class="close" type="danger" plain icon="el-icon-close" size="small"
@click="remove(index)"></el-button>
@click="remove(index)" circle></el-button>
<label>
<i :class="allComps[item.element].icon"></i>
{{ allComps[item.element].title }}</label>
</div>
<component :class="customizing?'set-component-bg':''" :is="allComps[item.element]"></component>
<component :class="customizing?'set-component-bg':''" :is="allComps[item.element]"
:config="item.config || {}"></component>
</grid-item>
</grid-layout>
</div>
</div>
<dashboard-config ref="dashboardConfig"></dashboard-config>
</d2-container>
</template>
@ -83,11 +95,14 @@ import draggable from 'vuedraggable'
import allComps from './components'
import util from '@/libs/util'
import VueGridLayout from 'vue-grid-layout'
import XEUtils from 'xe-utils'
const mouseXY = { x: null, y: null }
const DragPos = { x: 0, y: 0, w: 1, h: 1, i: null }
import SuspendedLibrary from '@/views/dashboard/workbench/suspendedLibrary'
import DashboardConfig from '@/views/dashboard/workbench/config'
import initData from './init.js'
export default {
components: {
DashboardConfig,
SuspendedLibrary,
draggable,
GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem
@ -97,33 +112,16 @@ export default {
customizing: false,
allComps: allComps,
selectLayout: [],
defaultGrid: {
// [24] [18,6] [8,8,8] [6,12,6]
layout: [12, 6, 6],
// com:views/home/components
copmsList: [
['welcome'],
['about', 'ver'],
['time', 'progress']
]
},
defaultLayout: [],
layout: [
// { x: 0, y: 0, w: 2, h: 2, i: '0', element: 'welcome' },
// { x: 2, y: 0, w: 2, h: 4, i: '1', element: 'about' },
// { x: 4, y: 0, w: 2, h: 5, i: '2', element: 'time' },
// { x: 6, y: 0, w: 2, h: 3, i: '3', element: 'progress' }
]
defaultLayout: initData,
layout: [],
colNum: 24,
minimize: false
}
},
created () {
this.layout = JSON.parse(util.cookies.get('grid-layout') || JSON.stringify(this.defaultLayout))
},
mounted () {
document.addEventListener('dragover', function (e) {
mouseXY.x = e.clientX
mouseXY.y = e.clientY
}, false)
this.$emit('on-mounted')
},
computed: {
@ -136,8 +134,11 @@ export default {
icon: allComps[key].icon,
height: allComps[key].height,
width: allComps[key].width,
maxH: allComps[key].maxH || Infinity,
maxW: allComps[key].maxW || Infinity,
minH: allComps[key].minH || 1,
minW: allComps[key].minW || 1,
maxH: allComps[key].maxH || 100,
maxW: (allComps[key].maxW > this.colNum ? this.colNum : allComps[key].maxW) || Infinity,
config: allComps[key].config || {},
isResizable: allComps[key].isResizable || null,
description: allComps[key].description
})
@ -155,36 +156,31 @@ export default {
//
custom () {
this.customizing = true
this.$refs.suspendedLibrary.menu = true
const oldWidth = this.$refs.widgets.offsetWidth
this.$nextTick(() => {
const scale = this.$refs.widgets.offsetWidth / oldWidth
this.$refs.widgets.style.setProperty('transform', `scale(${scale})`)
})
},
//
setLayout (layout) {
//
},
getLayoutElementNumber (elementName) {
// var index = 0
// this.layout.map(res => {
// if (elementName === res.element) {
// index += 1
// }
// })
// return index + 1
return elementName + this.layout.length
},
//
push (item) {
console.log(1, item)
this.layout.push({
x: 6,
y: 0,
i: this.getLayoutElementNumber(item.key),
x: (this.layout.length * 2) % (this.colNum || 12),
y: this.layout.length + (this.colNum || 12),
w: item.width,
h: item.height,
minW: item.minW,
minH: item.minH,
maxW: item.maxW,
maxH: item.maxH,
config: item.config || {},
isResizable: item.isResizable || null,
i: this.getLayoutElementNumber(item.key),
element: item.key
})
},
@ -196,12 +192,16 @@ export default {
save () {
console.log(this.layout)
this.customizing = false
this.minimize = false
this.$refs.suspendedLibrary.menu = false
this.$refs.widgets.style.removeProperty('transform')
util.cookies.set('grid-layout', this.layout)
},
//
backDefaul () {
backDefault () {
this.customizing = false
this.minimize = false
this.$refs.suspendedLibrary.menu = false
this.$refs.widgets.style.removeProperty('transform')
this.layout = JSON.parse(JSON.stringify(this.defaultLayout))
util.cookies.remove('grid-layout')
@ -209,87 +209,50 @@ export default {
//
close () {
this.customizing = false
this.minimize = false
this.$refs.suspendedLibrary.menu = false
this.$refs.widgets.style.removeProperty('transform')
},
//
onDrag (e, item) {
const { key, width, height } = item
const parentRect = this.$refs.widgets.getBoundingClientRect()
let mouseInGrid = false
if (((mouseXY.x > parentRect.left) && (mouseXY.x < parentRect.right)) && ((mouseXY.y > parentRect.top) && (mouseXY.y < parentRect.bottom))) {
mouseInGrid = true
}
const cloneLayout = XEUtils.clone(this.layout, true)
if (mouseInGrid === true && (cloneLayout.findIndex(item => item.i === this.getLayoutElementNumber(key)) === -1)) {
// this.layout.push({
// x: (this.layout.length * 2) % (this.colNum || 12),
// y: this.layout.length + (this.colNum || 12), // puts it at the bottom
// w: width,
// h: height,
// i: this.getLayoutElementNumber(key),
// element: key
// })
}
const index = this.layout.findIndex(item => item.i === this.getLayoutElementNumber(key))
if (index !== -1) {
try {
this.$refs.gridlayout.$children[this.layout.length].$refs.item.style.display = 'none'
} catch {
}
const el = this.$refs.gridlayout.$children[index]
el.dragging = { top: mouseXY.y - parentRect.top, left: mouseXY.x - parentRect.left }
const new_pos = el.calcXY(mouseXY.y - parentRect.top, mouseXY.x - parentRect.left)
if (mouseInGrid === true) {
this.$refs.gridlayout.dragEvent('dragstart', this.getLayoutElementNumber(key), new_pos.x, new_pos.y, 1, 1)
DragPos.i = String(index)
DragPos.x = this.layout[index].x
DragPos.y = this.layout[index].y
}
if (mouseInGrid === false) {
this.$refs.gridlayout.dragEvent('dragend', this.getLayoutElementNumber(key), new_pos.x, new_pos.y, 1, 1)
this.layout = this.layout.filter(obj => obj.i !== this.getLayoutElementNumber(key))
}
}
//
clickMinimize () {
this.minimize = !this.minimize
this.$refs.suspendedLibrary.menu = !this.$refs.suspendedLibrary.menu
},
onDragend (e, item) {
const { key, width, height } = item
const parentRect = this.$refs.widgets.getBoundingClientRect()
let mouseInGrid = false
if (((mouseXY.x > parentRect.left) && (mouseXY.x < parentRect.right)) && ((mouseXY.y > parentRect.top) && (mouseXY.y < parentRect.bottom))) {
mouseInGrid = true
}
if (mouseInGrid === true) {
this.layout.push({
x: DragPos.x,
y: DragPos.y,
w: width,
h: height,
i: this.getLayoutElementNumber(key),
element: key
})
this.$refs.gridlayout.dragEvent('dragend', this.getLayoutElementNumber(key), DragPos.x, DragPos.y, 1, 1)
this.layout = this.layout.filter(obj => obj.i !== this.getLayoutElementNumber(key))
// UNCOMMENT below if you want to add a grid-item
/*
this.layout.push({
x: DragPos.x,
y: DragPos.y,
w: 1,
h: 1,
i: DragPos.i,
});
this.$refs.gridLayout.dragEvent('dragend', DragPos.i, DragPos.x,DragPos.y,1,1);
try {
this.$refs.gridLayout.$children[this.layout.length].$refs.item.style.display="block";
} catch {
}
*/
}
//
clickConfig (itme) {
this.$refs.dashboardConfig.deviceUpgradeDrawer = true
this.$refs.dashboardConfig.initData(this.allComps[itme.element], itme)
this.minimize = false
}
}
}
</script>
<style scoped lang="scss">
::v-deep .d2-container-full__body{
padding: 0!important;
}
.widgetsListItem {
width: 168px;
height: 75px;
border-radius: 4px 4px 4px 4px;
font-size: 16px;
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
font-weight: bold;
color: #ffffff;
text-align: center;
margin-left: 7px;
line-height: 75px;
margin-bottom: 10px;
position: initial;
}
.widgetsListBox {
display: flex;
flex-wrap: wrap;
margin-top: 20px;
z-index: 999999;
}
.component-header {
background-color: #FFFFFF;
position: sticky;
@ -299,7 +262,6 @@ export default {
.set-btn-class {
float: right;
z-index: 99;
margin-bottom: 10px;
}
.all-component-class {
@ -331,9 +293,9 @@ export default {
opacity: 0.5;
}
.set-component-bg{
background:rgba(255, 255, 255, 0.5);
border: 1px solid rgba(0,0,0,.5);
.set-component-bg {
background: rgba(255, 255, 255, 0.5);
border: 1px solid rgba(0, 0, 0, .5);
}
.customize-overlay {
@ -375,8 +337,4 @@ export default {
right: 15px;
}
.customize-overlay .close:focus, .customize-overlay .close:hover {
background: #76B1F9;
}
</style>

View File

@ -0,0 +1,188 @@
const log = [
{
i: 'dashboardImg1',
x: 9,
y: 21,
w: 8,
h: 24,
minW: 1,
minH: 10,
maxW: 24,
maxH: 100,
config: {
src: {
label: '图片地址',
type: 'input',
value: '/image/card/tencent.jpg',
rules: [
{
required: true,
message: '不能为空'
}
]
},
url: {
label: '跳转地址',
type: 'input',
value: 'https://cloud.tencent.com/act/cps/redirect?redirect=1060&cps_key=b302a514a6688aa30823fac954464e5d&from=console',
rules: [
{
required: true,
message: '不能为空'
}
]
}
},
isResizable: true,
element: 'dashboardImg',
moved: false
},
{
i: 'dashboardImg2',
x: 9,
y: 0,
w: 15,
h: 21,
minW: 1,
minH: 10,
maxW: 24,
maxH: 100,
config: {
src: {
label: '图片地址',
type: 'input',
value: 'https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/aliyun-02.png',
placeholder: '请输入图片地址',
rules: [
{
required: true,
message: '不能为空'
}
]
},
url: {
label: '跳转地址',
type: 'input',
placeholder: '请输入跳转地址',
value: 'https://www.aliyun.com/minisite/goods?userCode=jpef8a71&share_source=copy_link',
rules: [
{
required: true,
message: '不能为空'
}
]
}
},
isResizable: true,
element: 'dashboardImg',
moved: false
},
{
i: 'time3',
x: 9,
y: 45,
w: 8,
h: 19,
minW: 4,
minH: 10,
maxW: null,
maxH: 100,
config: {},
isResizable: true,
element: 'time',
moved: false
},
{
i: 'ver4',
x: 17,
y: 45,
w: 7,
h: 19,
minW: 4,
minH: 10,
maxW: null,
maxH: 100,
config: {},
isResizable: true,
element: 'ver',
moved: false
},
{
i: 'about5',
x: 0,
y: 45,
w: 9,
h: 19,
minW: 2,
minH: 10,
maxW: null,
maxH: 100,
config: {
color: {
label: '背景颜色',
type: 'color',
value: null,
placeholder: '颜色为空则随机变换颜色'
}
},
isResizable: true,
element: 'about',
moved: false
},
{
i: 'welcome5',
x: 0,
y: 0,
w: 9,
h: 45,
minW: 1,
minH: 45,
maxW: null,
maxH: 100,
config: {},
isResizable: true,
element: 'welcome',
moved: false
},
{
i: 'dashboardImg6',
x: 17,
y: 21,
w: 7,
h: 24,
minW: 1,
minH: 10,
maxW: 24,
maxH: 100,
config: {
src: {
label: '图片地址',
type: 'input',
value: 'https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/chajianshichang.jpg',
placeholder: '请输入图片地址',
rules: [
{
required: true,
message: '不能为空'
}
]
},
url: {
label: '跳转地址',
type: 'input',
placeholder: '请输入跳转地址',
value: 'https://bbs.django-vue-admin.com/plugMarket.html',
rules: [
{
required: true,
message: '不能为空'
}
]
}
},
isResizable: true,
element: 'dashboardImg',
moved: false
}
]
export default log

View File

@ -0,0 +1,125 @@
<template>
<div>
<div class="sssss">
<div
class="callback float"
@click="onClick"
ref="fu"
>
<slot name="callbackButton"/>
</div>
<div
style="right: 0;position: fixed;bottom: 0;"
v-if="menu"
class="menuclass"
>
<div class="titlea">
添加部件 <i style="color: #409eff" class="el-icon-plus"></i>
<span class="operateCallback">
<slot name="operateButton"/>
</span>
</div>
<slot name="widgetsList"/>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'suspendedLibrary',
data () {
return {
menu: false,
isLoading: false,
background: [
'linear-gradient(150deg, #accaff 0%, #3b88ec 100%)',
'linear-gradient(150deg, #c5f8e6 0%, #10a465 100%)',
'linear-gradient(150deg, #e8d6ff 0%, #9f55ff 100%)',
'linear-gradient(150deg, #fdda45 0%, #fe6b62 100%)',
'linear-gradient(150deg, #cefbc8 0%, #00aec5 100%)',
'linear-gradient(150deg, #c5f8e6 0%, #10a465 100%)'
]
}
},
created () {
},
mounted () {
},
methods: {
onClick () {
this.$emit('click')
}
}
}
</script>
<style scoped>
.callback p {
font-size: 16px;
color: #fff;
background: rgba(56, 57, 58, 0.5);
border-radius: 50%;
text-align: center;
width: 50px;
height: 50px;
line-height: 50px;
font-family: PingFang SC;
font-weight: 600;
box-shadow: 0 0 10px #fff;
}
.callback img {
display: block;
width: 50px;
height: 50px;
box-shadow: 0 0 10px rgb(133, 129, 129);
border-radius: 50%;
background: #fff;
}
.operateCallback {
position: fixed;
height: 40px;
right: 10px;
z-index: 99999;
}
.callback {
position: fixed;
width: 40px;
height: 40px;
background-repeat: no-repeat;
background-size: 100% 100%;
bottom: 55px;
right: 45px;
z-index: 99999;
}
.float {
position: fixed;
touch-action: none;
text-align: center;
border-radius: 24px;
line-height: 48px;
color: white;
}
.menuclass {
text-align: left;
position: absolute;
color: #000;
width: 764px;
background: #ffffff;
box-shadow: 0px 6px 26px 1px rgba(51, 51, 51, 0.16);
padding: 20px;
}
.sssss {
position: relative;
background-color: #000;
right: 0;
z-index: 100;
}
.titlea {
font-size: 18px;
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
font-weight: bold;
color: #333333;
}
</style>

View File

@ -4,13 +4,13 @@ export function GetList (query) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
code: 0,
code: 2000,
msg: 'success',
data: {
total: 99,
current: query.current,
size: 20,
records: [
data: [
{ id: 1, select1: '1', select2: 'sz,wh' },
{ id: 2, select1: '1', select2: 'sz,sh' },
{ id: 3, select1: '0', select2: 'sz,gz' },

View File

@ -182,17 +182,12 @@
</vxe-column>
<vxe-column title="操作" width="100" show-overflow>
<template #default="{ row,index }">
<el-popover
placement="top"
width="160"
v-model="childRemoveVisible">
<p>删除后无法恢复,确定删除吗</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="childRemoveVisible = false">取消</el-button>
<el-button type="primary" size="mini" @click="onRemoveChild(row,index,item.key)"></el-button>
</div>
<el-button type="text" slot="reference">删除</el-button>
</el-popover>
<el-popconfirm
title="删除后无法恢复,确定删除吗?"
@confirm="onRemoveChild(row,index,item.key)"
>
<el-button slot="reference" type="text" >删除</el-button>
</el-popconfirm>
</template>
</vxe-column>
</vxe-table>
@ -350,8 +345,8 @@ export default {
if (!child.id && child.key && child.value) {
child.parent = parentId
child.id = null
this.formList.push(child)
}
this.formList.push(child)
}
//
for (const arr of item.rule) {
@ -400,21 +395,20 @@ export default {
const { tableData } = $table.getTableData()
const tableLength = tableData.length
if (tableLength === 0) {
const { row: newRow } = $table.insert()
console.log(newRow)
const { row } = $table.insert()
console.log(row)
} else {
const errMap = await $table.validate().catch(errMap => errMap)
if (errMap) {
this.$message.error('校验不通过!')
} else {
const { row: newRow } = $table.insert()
console.log(newRow)
const { row } = $table.insert()
console.log(row)
}
}
},
//
onRemoveChild (row, index, refName) {
console.log(row, index)
if (row.id) {
api.DelObj(row.id).then(res => {
this.refreshView()
@ -422,8 +416,7 @@ export default {
} else {
this.childTableData.splice(index, 1)
const tableName = 'xTable_' + refName
const tableData = this.$refs[tableName][0].remove(row)
console.log(tableData)
this.$refs[tableName][0].remove(row)
}
},
//

View File

@ -64,7 +64,9 @@
<addContent></addContent>
</el-col>
</el-row>
<formContent v-else :options="item" :editableTabsItem="item"></formContent>
<div v-else>
<formContent v-if="item.key===editableTabsValue" :options="item" :editableTabsItem="item"></formContent>
</div>
</el-tab-pane>
</el-tabs>
</d2-container>

View File

@ -102,6 +102,7 @@ export const crudOptions = (vm) => {
title: '上级部门',
key: 'parent',
type: 'tree-selector',
minWidth: 200,
dict: {
isTree: true,
label: 'name',
@ -126,6 +127,7 @@ export const crudOptions = (vm) => {
key: 'name',
sortable: true,
treeNode: true, // 设置为树形列
minWidth: 180,
search: {
disabled: false,
component: {
@ -157,6 +159,7 @@ export const crudOptions = (vm) => {
title: '部门标识',
key: 'key',
sortable: true,
minWidth: 100,
form: {
component: {
props: {
@ -173,6 +176,7 @@ export const crudOptions = (vm) => {
title: '负责人',
key: 'owner',
sortable: true,
minWidth: 100,
form: {
component: {
span: 12,
@ -187,6 +191,7 @@ export const crudOptions = (vm) => {
title: '联系电话',
key: 'phone',
sortable: true,
minWidth: 100,
form: {
component: {
span: 12,
@ -201,6 +206,7 @@ export const crudOptions = (vm) => {
title: '邮箱',
key: 'email',
sortable: true,
minWidth: 100,
form: {
component: {
span: 12,

View File

@ -92,7 +92,7 @@ export const crudOptions = (vm) => {
{
title: '字典名称',
key: 'label',
minWidth: 100,
search: {
disabled: false,
component: {
@ -121,6 +121,7 @@ export const crudOptions = (vm) => {
{
title: '字典编号',
key: 'value',
minWidth: 100,
search: {
disabled: true,
component: {
@ -197,8 +198,8 @@ export const crudOptions = (vm) => {
}
].concat(vm.commonEndColumns({
description: {
showForm: true,
showTable: true
showForm: false,
showTable: false
}
}))
}

View File

@ -8,7 +8,7 @@ export const crudOptions = (vm) => {
// rowKey: true, // 必须设置true or false
rowId: 'id',
height: '100%', // 表格高度100%, 使用toolbar必须设置
highlightCurrentRow: false,
highlightCurrentRow: false
},
rowHandle: {
fixed: 'right',

View File

@ -96,6 +96,7 @@ export const crudOptions = (vm) => {
title: '角色名称',
key: 'name',
sortable: true,
minWidth: 120,
search: {
disabled: false,
component: {
@ -125,6 +126,7 @@ export const crudOptions = (vm) => {
title: '权限标识',
key: 'key',
sortable: true,
minWidth: 100,
form: {
rules: [ // 表单校验规则
{ required: true, message: '权限标识必填项' }
@ -157,6 +159,7 @@ export const crudOptions = (vm) => {
key: 'admin',
sortable: true,
type: 'radio',
minWidth: 120,
dict: {
data: vm.dictionary('button_whether_bool')
},
@ -178,6 +181,7 @@ export const crudOptions = (vm) => {
disabled: false
},
type: 'radio',
minWidth: 100,
dict: {
data: vm.dictionary('button_status_bool')
},

View File

@ -1,16 +1,15 @@
import { request } from '@/api/service'
import util from '@/libs/util'
export const crudOptions = (vm) => {
util.filterParams(vm, ['dept_name', 'role_info{name}', 'dept_name_all'])
// util.filterParams(vm, ['dept_name', 'role_info{name}', 'dept_name_all'])
return {
pageOptions: {
compact: true
},
options: {
height: '100%',
// tableType: 'vxe-table',
//rowKey: true,
// tableType: 'vxe-table',
// rowKey: true,
rowId: 'id'
},
selectionRow: {