commit
fd05cc9726
33
README.en.md
33
README.en.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
[](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/liqianglog/django-vue-admin)
|
||||
|
||||
[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✅
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
33
README.md
33
README.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
[](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/liqianglog/django-vue-admin)
|
||||
|
||||
[预 览](https://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
|
|||
|
||||
## 演示图✅
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 * # 租户管理
|
||||
# ...
|
||||
|
||||
# ********** 一键导入插件配置结束 **********
|
||||
|
|
|
@ -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")
|
||||
),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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": []
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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信息")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
|
@ -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="获取成功")
|
||||
|
|
|
@ -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="注销成功")
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
|
|
|
@ -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__"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
#进程名
|
|
@ -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
|
||||
|
|
|
@ -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;}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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>
|
|
@ -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>
|
|
@ -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: '' }
|
||||
]
|
||||
|
|
|
@ -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 |
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 递归获取对应的名称
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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">完成
|
||||
</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">展开
|
||||
</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> {{ 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>
|
||||
|
|
|
@ -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
|
|
@ -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>
|
|
@ -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' },
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
// 图片预览
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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')
|
||||
},
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in New Issue