commit
2877e12142
|
@ -147,11 +147,11 @@ docker-compose up -d --build
|
||||||
|
|
||||||
## Demo screenshot✅
|
## Demo screenshot✅
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
@ -172,11 +172,11 @@ docker-compose up -d --build
|
||||||
|
|
||||||
## 演示图✅
|
## 演示图✅
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from dvadmin.utils.validator import CustomValidationError
|
||||||
|
|
||||||
dispatch_db_type = getattr(settings, 'DISPATCH_DB_TYPE', 'memory') # redis
|
dispatch_db_type = getattr(settings, 'DISPATCH_DB_TYPE', 'memory') # redis
|
||||||
|
|
||||||
|
@ -159,7 +160,7 @@ def get_dictionary_config(schema_name=None):
|
||||||
init_dictionary_data = cache.get(f"init_dictionary")
|
init_dictionary_data = cache.get(f"init_dictionary")
|
||||||
if not init_dictionary_data:
|
if not init_dictionary_data:
|
||||||
refresh_dictionary()
|
refresh_dictionary()
|
||||||
return cache.get(f"init_dictionary") or {}
|
return cache.get(f"init_dictionary") or {}
|
||||||
if not settings.DICTIONARY_CONFIG:
|
if not settings.DICTIONARY_CONFIG:
|
||||||
refresh_dictionary()
|
refresh_dictionary()
|
||||||
if is_tenants_mode():
|
if is_tenants_mode():
|
||||||
|
@ -243,6 +244,22 @@ def get_system_config_values(key, schema_name=None):
|
||||||
return system_config.get(key)
|
return system_config.get(key)
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_config_values_to_dict(key, schema_name=None):
|
||||||
|
"""
|
||||||
|
获取系统配置数据并转换为字典 **仅限于数组类型系统配置
|
||||||
|
:param key: 对应系统配置的key值(字典编号)
|
||||||
|
:param schema_name: 对应系统配置的租户schema_name值
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
values_dict = {}
|
||||||
|
config_values = get_system_config_values(key, schema_name)
|
||||||
|
if not isinstance(config_values, list):
|
||||||
|
raise CustomValidationError("该方式仅限于数组类型系统配置")
|
||||||
|
for ele in get_system_config_values(key, schema_name):
|
||||||
|
values_dict[ele.get('key')] = ele.get('value')
|
||||||
|
return values_dict
|
||||||
|
|
||||||
|
|
||||||
def get_system_config_label(key, name, schema_name=None):
|
def get_system_config_label(key, name, schema_name=None):
|
||||||
"""
|
"""
|
||||||
获取获取系统配置label值
|
获取获取系统配置label值
|
||||||
|
|
|
@ -255,21 +255,11 @@ LOGGING = {
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"propagate": False,
|
"propagate": False,
|
||||||
},
|
},
|
||||||
'celery': {
|
|
||||||
'handlers': ["console", "error", "file"],
|
|
||||||
'propagate': False,
|
|
||||||
'level': "INFO"
|
|
||||||
},
|
|
||||||
'django.db.backends': {
|
'django.db.backends': {
|
||||||
'handlers': ["console", "error", "file"],
|
'handlers': ["console", "error", "file"],
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
'level': "INFO"
|
'level': "INFO"
|
||||||
},
|
},
|
||||||
'django.request': {
|
|
||||||
'handlers': ["console", "error", "file"],
|
|
||||||
'propagate': False,
|
|
||||||
'level': "DEBUG"
|
|
||||||
},
|
|
||||||
"uvicorn.error": {
|
"uvicorn.error": {
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"handlers": ["console", "error", "file"],
|
"handlers": ["console", "error", "file"],
|
||||||
|
@ -418,6 +408,7 @@ PLUGINS_URL_PATTERNS = []
|
||||||
# from dvadmin_uniapp.settings import * # UniApp后端
|
# from dvadmin_uniapp.settings import * # UniApp后端
|
||||||
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
|
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
|
||||||
# from dvadmin_tenants.settings import * # 租户管理
|
# from dvadmin_tenants.settings import * # 租户管理
|
||||||
|
# from dvadmin_cloud_storage.settings import * # 云存储
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
# ********** 一键导入插件配置结束 **********
|
# ********** 一键导入插件配置结束 **********
|
||||||
|
|
|
@ -42,7 +42,7 @@ LOGIN_NO_CAPTCHA_AUTH = True
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# ****************** 其他 配置 ******************* #
|
# ****************** 其他 配置 ******************* #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
|
ENVIRONMENT = "local" # 环境,test 测试环境;prod线上环境;local本地环境
|
||||||
ALLOWED_HOSTS = ["*"]
|
ALLOWED_HOSTS = ["*"]
|
||||||
# 系统配置存放位置:redis/memory(默认)
|
# 系统配置存放位置:redis/memory(默认)
|
||||||
DISPATCH_DB_TYPE = 'redis'
|
DISPATCH_DB_TYPE = 'redis'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
|
from pathlib import PurePath, PureWindowsPath, PurePosixPath
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -13,8 +14,11 @@ STATUS_CHOICES = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Users(CoreModel,AbstractUser):
|
class Users(CoreModel, AbstractUser):
|
||||||
username = models.CharField(max_length=150, unique=True, db_index=True, verbose_name="用户账号", help_text="用户账号")
|
username = models.CharField(max_length=150, unique=True, db_index=True, verbose_name="用户账号",
|
||||||
|
help_text="用户账号")
|
||||||
|
employee_no = models.CharField(max_length=150, unique=True, db_index=True, null=True, blank=True,
|
||||||
|
verbose_name="工号", help_text="工号")
|
||||||
email = models.EmailField(max_length=255, verbose_name="邮箱", null=True, blank=True, help_text="邮箱")
|
email = models.EmailField(max_length=255, verbose_name="邮箱", null=True, blank=True, help_text="邮箱")
|
||||||
mobile = models.CharField(max_length=255, verbose_name="电话", null=True, blank=True, help_text="电话")
|
mobile = models.CharField(max_length=255, verbose_name="电话", null=True, blank=True, help_text="电话")
|
||||||
avatar = models.CharField(max_length=255, verbose_name="头像", null=True, blank=True, help_text="头像")
|
avatar = models.CharField(max_length=255, verbose_name="头像", null=True, blank=True, help_text="头像")
|
||||||
|
@ -34,8 +38,10 @@ class Users(CoreModel,AbstractUser):
|
||||||
user_type = models.IntegerField(
|
user_type = models.IntegerField(
|
||||||
choices=USER_TYPE, default=0, verbose_name="用户类型", null=True, blank=True, help_text="用户类型"
|
choices=USER_TYPE, default=0, verbose_name="用户类型", null=True, blank=True, help_text="用户类型"
|
||||||
)
|
)
|
||||||
post = models.ManyToManyField(to="Post",blank=True, verbose_name="关联岗位", db_constraint=False, help_text="关联岗位")
|
post = models.ManyToManyField(to="Post", blank=True, verbose_name="关联岗位", db_constraint=False,
|
||||||
role = models.ManyToManyField(to="Role", blank=True,verbose_name="关联角色", db_constraint=False, help_text="关联角色")
|
help_text="关联岗位")
|
||||||
|
role = models.ManyToManyField(to="Role", blank=True, verbose_name="关联角色", db_constraint=False,
|
||||||
|
help_text="关联角色")
|
||||||
dept = models.ForeignKey(
|
dept = models.ForeignKey(
|
||||||
to="Dept",
|
to="Dept",
|
||||||
verbose_name="所属部门",
|
verbose_name="所属部门",
|
||||||
|
@ -45,7 +51,8 @@ class Users(CoreModel,AbstractUser):
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="关联部门",
|
help_text="关联部门",
|
||||||
)
|
)
|
||||||
last_token = models.CharField(max_length=255,null=True,blank=True, verbose_name="最后一次登录Token", help_text="最后一次登录Token")
|
last_token = models.CharField(max_length=255, null=True, blank=True, verbose_name="最后一次登录Token",
|
||||||
|
help_text="最后一次登录Token")
|
||||||
|
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
|
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
|
||||||
|
@ -87,9 +94,11 @@ class Role(CoreModel):
|
||||||
(3, "全部数据权限"),
|
(3, "全部数据权限"),
|
||||||
(4, "自定数据权限"),
|
(4, "自定数据权限"),
|
||||||
)
|
)
|
||||||
data_range = models.IntegerField(default=0, choices=DATASCOPE_CHOICES, verbose_name="数据权限范围", help_text="数据权限范围")
|
data_range = models.IntegerField(default=0, choices=DATASCOPE_CHOICES, verbose_name="数据权限范围",
|
||||||
|
help_text="数据权限范围")
|
||||||
remark = models.TextField(verbose_name="备注", help_text="备注", null=True, blank=True)
|
remark = models.TextField(verbose_name="备注", help_text="备注", null=True, blank=True)
|
||||||
dept = models.ManyToManyField(to="Dept", verbose_name="数据权限-关联部门", db_constraint=False, help_text="数据权限-关联部门")
|
dept = models.ManyToManyField(to="Dept", verbose_name="数据权限-关联部门", db_constraint=False,
|
||||||
|
help_text="数据权限-关联部门")
|
||||||
menu = models.ManyToManyField(to="Menu", verbose_name="关联菜单", db_constraint=False, help_text="关联菜单")
|
menu = models.ManyToManyField(to="Menu", verbose_name="关联菜单", db_constraint=False, help_text="关联菜单")
|
||||||
permission = models.ManyToManyField(
|
permission = models.ManyToManyField(
|
||||||
to="MenuButton", verbose_name="关联菜单的接口按钮", db_constraint=False, help_text="关联菜单的接口按钮"
|
to="MenuButton", verbose_name="关联菜单的接口按钮", db_constraint=False, help_text="关联菜单的接口按钮"
|
||||||
|
@ -104,7 +113,8 @@ class Role(CoreModel):
|
||||||
|
|
||||||
class Dept(CoreModel):
|
class Dept(CoreModel):
|
||||||
name = models.CharField(max_length=64, verbose_name="部门名称", help_text="部门名称")
|
name = models.CharField(max_length=64, verbose_name="部门名称", help_text="部门名称")
|
||||||
key = models.CharField(max_length=64, unique=True,null=True,blank=True, verbose_name="关联字符", help_text="关联字符")
|
key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符",
|
||||||
|
help_text="关联字符")
|
||||||
sort = models.IntegerField(default=1, verbose_name="显示排序", help_text="显示排序")
|
sort = models.IntegerField(default=1, verbose_name="显示排序", help_text="显示排序")
|
||||||
owner = models.CharField(max_length=32, verbose_name="负责人", null=True, blank=True, help_text="负责人")
|
owner = models.CharField(max_length=32, verbose_name="负责人", null=True, blank=True, help_text="负责人")
|
||||||
phone = models.CharField(max_length=32, verbose_name="联系电话", null=True, blank=True, help_text="联系电话")
|
phone = models.CharField(max_length=32, verbose_name="联系电话", null=True, blank=True, help_text="联系电话")
|
||||||
|
@ -168,10 +178,12 @@ class Menu(CoreModel):
|
||||||
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录")
|
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录")
|
||||||
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
|
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
|
||||||
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
|
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
|
||||||
component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True, help_text="组件名称")
|
component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True,
|
||||||
|
help_text="组件名称")
|
||||||
status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态")
|
status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态")
|
||||||
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
|
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
|
||||||
visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示", help_text="侧边栏中是否显示")
|
visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示",
|
||||||
|
help_text="侧边栏中是否显示")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = table_prefix + "system_menu"
|
db_table = table_prefix + "system_menu"
|
||||||
|
@ -198,7 +210,8 @@ class MenuButton(CoreModel):
|
||||||
(2, "PUT"),
|
(2, "PUT"),
|
||||||
(3, "DELETE"),
|
(3, "DELETE"),
|
||||||
)
|
)
|
||||||
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True, help_text="接口请求方法")
|
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
|
||||||
|
help_text="接口请求方法")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = table_prefix + "system_menu_button"
|
db_table = table_prefix + "system_menu_button"
|
||||||
|
@ -219,7 +232,8 @@ class Dictionary(CoreModel):
|
||||||
(7, "images"),
|
(7, "images"),
|
||||||
)
|
)
|
||||||
label = models.CharField(max_length=100, blank=True, null=True, verbose_name="字典名称", help_text="字典名称")
|
label = models.CharField(max_length=100, blank=True, null=True, verbose_name="字典名称", help_text="字典名称")
|
||||||
value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号", help_text="字典编号/实际值")
|
value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号",
|
||||||
|
help_text="字典编号/实际值")
|
||||||
parent = models.ForeignKey(
|
parent = models.ForeignKey(
|
||||||
to="self",
|
to="self",
|
||||||
related_name="sublist",
|
related_name="sublist",
|
||||||
|
@ -232,7 +246,8 @@ class Dictionary(CoreModel):
|
||||||
)
|
)
|
||||||
type = models.IntegerField(choices=TYPE_LIST, default=0, verbose_name="数据值类型", help_text="数据值类型")
|
type = models.IntegerField(choices=TYPE_LIST, default=0, verbose_name="数据值类型", help_text="数据值类型")
|
||||||
color = models.CharField(max_length=20, blank=True, null=True, verbose_name="颜色", help_text="颜色")
|
color = models.CharField(max_length=20, blank=True, null=True, verbose_name="颜色", help_text="颜色")
|
||||||
is_value = models.BooleanField(default=False, verbose_name="是否为value值", help_text="是否为value值,用来做具体值存放")
|
is_value = models.BooleanField(default=False, verbose_name="是否为value值",
|
||||||
|
help_text="是否为value值,用来做具体值存放")
|
||||||
status = models.BooleanField(default=True, verbose_name="状态", help_text="状态")
|
status = models.BooleanField(default=True, verbose_name="状态", help_text="状态")
|
||||||
sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序")
|
sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序")
|
||||||
remark = models.CharField(max_length=2000, blank=True, null=True, verbose_name="备注", help_text="备注")
|
remark = models.CharField(max_length=2000, blank=True, null=True, verbose_name="备注", help_text="备注")
|
||||||
|
@ -254,14 +269,20 @@ class Dictionary(CoreModel):
|
||||||
|
|
||||||
|
|
||||||
class OperationLog(CoreModel):
|
class OperationLog(CoreModel):
|
||||||
request_modular = models.CharField(max_length=64, verbose_name="请求模块", null=True, blank=True, help_text="请求模块")
|
request_modular = models.CharField(max_length=64, verbose_name="请求模块", null=True, blank=True,
|
||||||
request_path = models.CharField(max_length=400, verbose_name="请求地址", null=True, blank=True, help_text="请求地址")
|
help_text="请求模块")
|
||||||
|
request_path = models.CharField(max_length=400, verbose_name="请求地址", null=True, blank=True,
|
||||||
|
help_text="请求地址")
|
||||||
request_body = models.TextField(verbose_name="请求参数", null=True, blank=True, help_text="请求参数")
|
request_body = models.TextField(verbose_name="请求参数", null=True, blank=True, help_text="请求参数")
|
||||||
request_method = models.CharField(max_length=8, verbose_name="请求方式", null=True, blank=True, help_text="请求方式")
|
request_method = models.CharField(max_length=8, verbose_name="请求方式", null=True, blank=True,
|
||||||
|
help_text="请求方式")
|
||||||
request_msg = models.TextField(verbose_name="操作说明", null=True, blank=True, help_text="操作说明")
|
request_msg = models.TextField(verbose_name="操作说明", null=True, blank=True, help_text="操作说明")
|
||||||
request_ip = models.CharField(max_length=32, verbose_name="请求ip地址", null=True, blank=True, help_text="请求ip地址")
|
request_ip = models.CharField(max_length=32, verbose_name="请求ip地址", null=True, blank=True,
|
||||||
request_browser = models.CharField(max_length=64, verbose_name="请求浏览器", null=True, blank=True, help_text="请求浏览器")
|
help_text="请求ip地址")
|
||||||
response_code = models.CharField(max_length=32, verbose_name="响应状态码", null=True, blank=True, help_text="响应状态码")
|
request_browser = models.CharField(max_length=64, verbose_name="请求浏览器", null=True, blank=True,
|
||||||
|
help_text="请求浏览器")
|
||||||
|
response_code = models.CharField(max_length=32, verbose_name="响应状态码", null=True, blank=True,
|
||||||
|
help_text="响应状态码")
|
||||||
request_os = models.CharField(max_length=64, verbose_name="操作系统", null=True, blank=True, help_text="操作系统")
|
request_os = models.CharField(max_length=64, verbose_name="操作系统", null=True, blank=True, help_text="操作系统")
|
||||||
json_result = models.TextField(verbose_name="返回信息", null=True, blank=True, help_text="返回信息")
|
json_result = models.TextField(verbose_name="返回信息", null=True, blank=True, help_text="返回信息")
|
||||||
status = models.BooleanField(default=False, verbose_name="响应状态", help_text="响应状态")
|
status = models.BooleanField(default=False, verbose_name="响应状态", help_text="响应状态")
|
||||||
|
@ -276,12 +297,16 @@ class OperationLog(CoreModel):
|
||||||
def media_file_name(instance, filename):
|
def media_file_name(instance, filename):
|
||||||
h = instance.md5sum
|
h = instance.md5sum
|
||||||
basename, ext = os.path.splitext(filename)
|
basename, ext = os.path.splitext(filename)
|
||||||
return os.path.join("files", h[0:1], h[1:2], h + ext.lower())
|
return PurePosixPath("files", h[:1], h[1:2], h + ext.lower())
|
||||||
|
|
||||||
|
|
||||||
class FileList(CoreModel):
|
class FileList(CoreModel):
|
||||||
name = models.CharField(max_length=200, null=True, blank=True, verbose_name="名称", help_text="名称")
|
name = models.CharField(max_length=200, null=True, blank=True, verbose_name="名称", help_text="名称")
|
||||||
url = models.FileField(upload_to=media_file_name)
|
url = models.FileField(upload_to=media_file_name, null=True, blank=True,)
|
||||||
|
file_url = models.CharField(max_length=255, blank=True, verbose_name="文件地址", help_text="文件地址")
|
||||||
|
engine = models.CharField(max_length=100, default='local', blank=True, verbose_name="引擎", help_text="引擎")
|
||||||
|
mime_type = models.CharField(max_length=100, blank=True, verbose_name="Mime类型", help_text="Mime类型")
|
||||||
|
size = models.CharField(max_length=36, blank=True, verbose_name="文件大小", help_text="文件大小")
|
||||||
md5sum = models.CharField(max_length=36, blank=True, verbose_name="文件md5", help_text="文件md5")
|
md5sum = models.CharField(max_length=36, blank=True, verbose_name="文件md5", help_text="文件md5")
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
@ -290,6 +315,11 @@ class FileList(CoreModel):
|
||||||
for chunk in self.url.chunks():
|
for chunk in self.url.chunks():
|
||||||
md5.update(chunk)
|
md5.update(chunk)
|
||||||
self.md5sum = md5.hexdigest()
|
self.md5sum = md5.hexdigest()
|
||||||
|
if not self.size:
|
||||||
|
self.size = self.url.size
|
||||||
|
if not self.file_url:
|
||||||
|
url = media_file_name(self,self.name)
|
||||||
|
self.file_url = f'media/{url}'
|
||||||
super(FileList, self).save(*args, **kwargs)
|
super(FileList, self).save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -302,7 +332,8 @@ class FileList(CoreModel):
|
||||||
class Area(CoreModel):
|
class Area(CoreModel):
|
||||||
name = models.CharField(max_length=100, verbose_name="名称", help_text="名称")
|
name = models.CharField(max_length=100, verbose_name="名称", help_text="名称")
|
||||||
code = models.CharField(max_length=20, verbose_name="地区编码", help_text="地区编码", unique=True, db_index=True)
|
code = models.CharField(max_length=20, verbose_name="地区编码", help_text="地区编码", unique=True, db_index=True)
|
||||||
level = models.BigIntegerField(verbose_name="地区层级(1省份 2城市 3区县 4乡级)", help_text="地区层级(1省份 2城市 3区县 4乡级)")
|
level = models.BigIntegerField(verbose_name="地区层级(1省份 2城市 3区县 4乡级)",
|
||||||
|
help_text="地区层级(1省份 2城市 3区县 4乡级)")
|
||||||
pinyin = models.CharField(max_length=255, verbose_name="拼音", help_text="拼音")
|
pinyin = models.CharField(max_length=255, verbose_name="拼音", help_text="拼音")
|
||||||
initials = models.CharField(max_length=20, verbose_name="首字母", help_text="首字母")
|
initials = models.CharField(max_length=20, verbose_name="首字母", help_text="首字母")
|
||||||
enable = models.BooleanField(default=True, verbose_name="是否启用", help_text="是否启用")
|
enable = models.BooleanField(default=True, verbose_name="是否启用", help_text="是否启用")
|
||||||
|
@ -335,8 +366,10 @@ class ApiWhiteList(CoreModel):
|
||||||
(2, "PUT"),
|
(2, "PUT"),
|
||||||
(3, "DELETE"),
|
(3, "DELETE"),
|
||||||
)
|
)
|
||||||
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True, help_text="接口请求方法")
|
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
|
||||||
enable_datasource = models.BooleanField(default=True, verbose_name="激活数据权限", help_text="激活数据权限", blank=True)
|
help_text="接口请求方法")
|
||||||
|
enable_datasource = models.BooleanField(default=True, verbose_name="激活数据权限", help_text="激活数据权限",
|
||||||
|
blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = table_prefix + "api_white_list"
|
db_table = table_prefix + "api_white_list"
|
||||||
|
@ -356,8 +389,8 @@ class SystemConfig(CoreModel):
|
||||||
help_text="父级",
|
help_text="父级",
|
||||||
)
|
)
|
||||||
title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")
|
title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")
|
||||||
key = models.CharField(max_length=100, verbose_name="键", help_text="键", db_index=True)
|
key = models.CharField(max_length=200, verbose_name="键", help_text="键", db_index=True)
|
||||||
value = models.JSONField(max_length=200, verbose_name="值", help_text="值", null=True, blank=True)
|
value = models.JSONField(max_length=500, verbose_name="值", help_text="值", null=True, blank=True)
|
||||||
sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True)
|
sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True)
|
||||||
status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态")
|
status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态")
|
||||||
data_options = models.JSONField(verbose_name="数据options", help_text="数据options", null=True, blank=True)
|
data_options = models.JSONField(verbose_name="数据options", help_text="数据options", null=True, blank=True)
|
||||||
|
@ -383,7 +416,7 @@ class SystemConfig(CoreModel):
|
||||||
choices=FORM_ITEM_TYPE_LIST, verbose_name="表单类型", help_text="表单类型", default=0, blank=True
|
choices=FORM_ITEM_TYPE_LIST, verbose_name="表单类型", help_text="表单类型", default=0, blank=True
|
||||||
)
|
)
|
||||||
rule = models.JSONField(null=True, blank=True, verbose_name="校验规则", help_text="校验规则")
|
rule = models.JSONField(null=True, blank=True, verbose_name="校验规则", help_text="校验规则")
|
||||||
placeholder = models.CharField(max_length=50, null=True, blank=True, verbose_name="提示信息", help_text="提示信息")
|
placeholder = models.CharField(max_length=100, null=True, blank=True, verbose_name="提示信息", help_text="提示信息")
|
||||||
setting = models.JSONField(null=True, blank=True, verbose_name="配置", help_text="配置")
|
setting = models.JSONField(null=True, blank=True, verbose_name="配置", help_text="配置")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -415,8 +448,16 @@ class SystemConfig(CoreModel):
|
||||||
|
|
||||||
|
|
||||||
class LoginLog(CoreModel):
|
class LoginLog(CoreModel):
|
||||||
LOGIN_TYPE_CHOICES = ((1, "普通登录"), (2, "微信扫码登录"), (3, "飞书扫码登录"), (4, "钉钉扫码登录"))
|
LOGIN_TYPE_CHOICES = (
|
||||||
username = models.CharField(max_length=32, verbose_name="登录用户名", null=True, blank=True, help_text="登录用户名")
|
(1, "普通登录"),
|
||||||
|
(2, "普通扫码登录"),
|
||||||
|
(3, "微信扫码登录"),
|
||||||
|
(4, "飞书扫码登录"),
|
||||||
|
(5, "钉钉扫码登录"),
|
||||||
|
(6, "短信登录")
|
||||||
|
)
|
||||||
|
username = models.CharField(max_length=150, verbose_name="登录用户名", null=True, blank=True,
|
||||||
|
help_text="登录用户名")
|
||||||
ip = models.CharField(max_length=32, verbose_name="登录ip", null=True, blank=True, help_text="登录ip")
|
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信息")
|
agent = models.TextField(verbose_name="agent信息", null=True, blank=True, help_text="agent信息")
|
||||||
browser = models.CharField(max_length=200, verbose_name="浏览器名", null=True, blank=True, help_text="浏览器名")
|
browser = models.CharField(max_length=200, verbose_name="浏览器名", null=True, blank=True, help_text="浏览器名")
|
||||||
|
@ -428,11 +469,13 @@ class LoginLog(CoreModel):
|
||||||
district = models.CharField(max_length=50, verbose_name="县区", null=True, blank=True, help_text="县区")
|
district = models.CharField(max_length=50, verbose_name="县区", null=True, blank=True, help_text="县区")
|
||||||
isp = models.CharField(max_length=50, verbose_name="运营商", null=True, blank=True, help_text="运营商")
|
isp = models.CharField(max_length=50, verbose_name="运营商", null=True, blank=True, help_text="运营商")
|
||||||
area_code = models.CharField(max_length=50, verbose_name="区域代码", null=True, blank=True, help_text="区域代码")
|
area_code = models.CharField(max_length=50, verbose_name="区域代码", null=True, blank=True, help_text="区域代码")
|
||||||
country_english = models.CharField(max_length=50, verbose_name="英文全称", null=True, blank=True, help_text="英文全称")
|
country_english = models.CharField(max_length=50, verbose_name="英文全称", null=True, blank=True,
|
||||||
|
help_text="英文全称")
|
||||||
country_code = models.CharField(max_length=50, verbose_name="简称", null=True, blank=True, help_text="简称")
|
country_code = models.CharField(max_length=50, verbose_name="简称", null=True, blank=True, help_text="简称")
|
||||||
longitude = models.CharField(max_length=50, verbose_name="经度", null=True, blank=True, help_text="经度")
|
longitude = models.CharField(max_length=50, verbose_name="经度", null=True, blank=True, help_text="经度")
|
||||||
latitude = models.CharField(max_length=50, verbose_name="纬度", null=True, blank=True, help_text="纬度")
|
latitude = models.CharField(max_length=50, verbose_name="纬度", null=True, blank=True, help_text="纬度")
|
||||||
login_type = models.IntegerField(default=1, choices=LOGIN_TYPE_CHOICES, verbose_name="登录类型", help_text="登录类型")
|
login_type = models.IntegerField(default=1, choices=LOGIN_TYPE_CHOICES, verbose_name="登录类型",
|
||||||
|
help_text="登录类型")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = table_prefix + "system_login_log"
|
db_table = table_prefix + "system_login_log"
|
||||||
|
@ -442,14 +485,16 @@ class LoginLog(CoreModel):
|
||||||
|
|
||||||
|
|
||||||
class MessageCenter(CoreModel):
|
class MessageCenter(CoreModel):
|
||||||
title = models.CharField(max_length=100,verbose_name="标题",help_text="标题")
|
title = models.CharField(max_length=100, verbose_name="标题", help_text="标题")
|
||||||
content = models.TextField(verbose_name="内容",help_text="内容")
|
content = models.TextField(verbose_name="内容", help_text="内容")
|
||||||
target_type=models.IntegerField(default=0,verbose_name="目标类型",help_text="目标类型")
|
target_type = models.IntegerField(default=0, verbose_name="目标类型", help_text="目标类型")
|
||||||
target_user = models.ManyToManyField(to=Users,related_name='user',through='MessageCenterTargetUser', through_fields=('messagecenter','users'),blank=True,verbose_name="目标用户",help_text="目标用户")
|
target_user = models.ManyToManyField(to=Users, related_name='user', through='MessageCenterTargetUser',
|
||||||
target_dept = models.ManyToManyField(to=Dept, blank=True, db_constraint=False,
|
through_fields=('messagecenter', 'users'), blank=True, verbose_name="目标用户",
|
||||||
verbose_name="目标部门", help_text="目标部门")
|
help_text="目标用户")
|
||||||
target_role = models.ManyToManyField(to=Role, blank=True, db_constraint=False,
|
target_dept = models.ManyToManyField(to=Dept, blank=True, db_constraint=False,
|
||||||
verbose_name="目标角色", help_text="目标角色")
|
verbose_name="目标部门", help_text="目标部门")
|
||||||
|
target_role = models.ManyToManyField(to=Role, blank=True, db_constraint=False,
|
||||||
|
verbose_name="目标角色", help_text="目标角色")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = table_prefix + "message_center"
|
db_table = table_prefix + "message_center"
|
||||||
|
@ -457,10 +502,13 @@ class MessageCenter(CoreModel):
|
||||||
verbose_name_plural = verbose_name
|
verbose_name_plural = verbose_name
|
||||||
ordering = ("-create_datetime",)
|
ordering = ("-create_datetime",)
|
||||||
|
|
||||||
|
|
||||||
class MessageCenterTargetUser(CoreModel):
|
class MessageCenterTargetUser(CoreModel):
|
||||||
users = models.ForeignKey(Users,related_name="target_user", on_delete=models.CASCADE,db_constraint=False,verbose_name="关联用户表",help_text="关联用户表")
|
users = models.ForeignKey(Users, related_name="target_user", on_delete=models.CASCADE, db_constraint=False,
|
||||||
messagecenter = models.ForeignKey(MessageCenter, on_delete=models.CASCADE,db_constraint=False,verbose_name="关联消息中心表",help_text="关联消息中心表")
|
verbose_name="关联用户表", help_text="关联用户表")
|
||||||
is_read = models.BooleanField(default=False,blank=True,null=True,verbose_name="是否已读",help_text="是否已读")
|
messagecenter = models.ForeignKey(MessageCenter, on_delete=models.CASCADE, db_constraint=False,
|
||||||
|
verbose_name="关联消息中心表", help_text="关联消息中心表")
|
||||||
|
is_read = models.BooleanField(default=False, blank=True, null=True, verbose_name="是否已读", help_text="是否已读")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = table_prefix + "message_center_target_user"
|
db_table = table_prefix + "message_center_target_user"
|
||||||
|
|
|
@ -4,6 +4,7 @@ from rest_framework import routers
|
||||||
from dvadmin.system.views.api_white_list import ApiWhiteListViewSet
|
from dvadmin.system.views.api_white_list import ApiWhiteListViewSet
|
||||||
from dvadmin.system.views.area import AreaViewSet
|
from dvadmin.system.views.area import AreaViewSet
|
||||||
from dvadmin.system.views.clause import PrivacyView, TermsServiceView
|
from dvadmin.system.views.clause import PrivacyView, TermsServiceView
|
||||||
|
from dvadmin.system.views.datav import DataVViewSet
|
||||||
from dvadmin.system.views.dept import DeptViewSet
|
from dvadmin.system.views.dept import DeptViewSet
|
||||||
from dvadmin.system.views.dictionary import DictionaryViewSet
|
from dvadmin.system.views.dictionary import DictionaryViewSet
|
||||||
from dvadmin.system.views.file_list import FileViewSet
|
from dvadmin.system.views.file_list import FileViewSet
|
||||||
|
@ -29,6 +30,7 @@ system_url.register(r'file', FileViewSet)
|
||||||
system_url.register(r'api_white_list', ApiWhiteListViewSet)
|
system_url.register(r'api_white_list', ApiWhiteListViewSet)
|
||||||
system_url.register(r'system_config', SystemConfigViewSet)
|
system_url.register(r'system_config', SystemConfigViewSet)
|
||||||
system_url.register(r'message_center', MessageCenterViewSet)
|
system_url.register(r'message_center', MessageCenterViewSet)
|
||||||
|
system_url.register(r'datav', DataVViewSet)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})),
|
path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})),
|
||||||
|
|
|
@ -0,0 +1,262 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Time : 2023/4/14 15:49
|
||||||
|
# @Author : harry
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
from django.db.models import Count, Sum, Q
|
||||||
|
from django.db.models.functions import TruncMonth, TruncDay
|
||||||
|
from django.utils import timezone
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
|
||||||
|
from conf.env import DATABASE_USER, DATABASE_NAME
|
||||||
|
from dvadmin.system.models import Users, LoginLog, FileList
|
||||||
|
from dvadmin.system.views.login_log import LoginLogSerializer
|
||||||
|
from dvadmin.utils.json_response import DetailResponse
|
||||||
|
from django.db import connection
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from django.db.models import Count
|
||||||
|
from django.db.models.functions import TruncDate
|
||||||
|
|
||||||
|
from dvadmin.utils.string_util import format_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def jx_timestamp():
|
||||||
|
cur_time = datetime.datetime.now()
|
||||||
|
a = datetime.datetime.strftime(cur_time, '%Y-%m-%d %H:%M:%S')
|
||||||
|
timeStamp = int(time.mktime(time.strptime(a, "%Y-%m-%d %H:%M:%S")))
|
||||||
|
timeArray = time.localtime(timeStamp)
|
||||||
|
otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)
|
||||||
|
return otherStyleTime
|
||||||
|
|
||||||
|
|
||||||
|
class DataVViewSet(GenericViewSet):
|
||||||
|
queryset = LoginLog.objects.all()
|
||||||
|
serializer_class = LoginLogSerializer
|
||||||
|
extra_filter_backends = []
|
||||||
|
ordering_fields = ['create_datetime']
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||||
|
def users_login_total(self, request):
|
||||||
|
"""
|
||||||
|
用户登录总数数据
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
login_total = LoginLog.objects.all().count()
|
||||||
|
return DetailResponse(data={"login_total": login_total}, msg="获取成功")
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||||
|
def users_total(self, request):
|
||||||
|
"""
|
||||||
|
用户总数
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
users_total = Users.objects.all().count()
|
||||||
|
return DetailResponse(data={"users_total": users_total, }, msg="获取成功")
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||||
|
def attachment_total(self, request):
|
||||||
|
"""
|
||||||
|
附件统计数据
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
count = FileList.objects.all().count()
|
||||||
|
data = FileList.objects.aggregate(sum_size=Sum('size'))
|
||||||
|
return DetailResponse(data={"count": count, "occupy_space": format_bytes(data.get('sum_size') or 0)}, msg="获取成功")
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||||
|
def database_total(self, request):
|
||||||
|
"""
|
||||||
|
数据库统计数据
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
count = len(connection.introspection.table_names())
|
||||||
|
database_type = connection.settings_dict['ENGINE']
|
||||||
|
sql = None
|
||||||
|
if 'mysql' in database_type:
|
||||||
|
sql = "SELECT SUM(data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = DATABASE()"
|
||||||
|
elif 'postgres' in database_type or 'psqlextra' in database_type:
|
||||||
|
sql = """SELECT SUM(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))) AS size FROM pg_tables WHERE schemaname = current_schema();"""
|
||||||
|
elif 'oracle' in database_type:
|
||||||
|
sql = "SELECT SUM(bytes) AS size FROM user_segments"
|
||||||
|
elif 'microsoft' in database_type:
|
||||||
|
sql = "SELECT SUM(size) * 8 AS size FROM sys.database_files"
|
||||||
|
else:
|
||||||
|
space = 0
|
||||||
|
if sql:
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
try:
|
||||||
|
cursor.execute(sql)
|
||||||
|
result = cursor.fetchone()
|
||||||
|
space = result[0]
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
space = '无权限'
|
||||||
|
return DetailResponse(data={"count": count, "space": format_bytes(space or 0)}, msg="获取成功")
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||||
|
def registered_user(self, request):
|
||||||
|
"""
|
||||||
|
用户注册趋势
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
today = datetime.datetime.today()
|
||||||
|
seven_days_ago = today - datetime.timedelta(days=30)
|
||||||
|
|
||||||
|
users = Users.objects.filter(date_joined__gte=seven_days_ago).annotate(day=TruncDay('date_joined')).values(
|
||||||
|
'day').annotate(count=Count('id'))
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for i in range(30):
|
||||||
|
date = (today - datetime.timedelta(days=i)).strftime('%Y-%m-%d')
|
||||||
|
count = 0
|
||||||
|
for user in users:
|
||||||
|
if user['day'] == date:
|
||||||
|
count = user['count']
|
||||||
|
break
|
||||||
|
result.append({'day': date, 'count': count})
|
||||||
|
|
||||||
|
# users_last_month = Users.objects.filter(date_joined__gte=last_month).annotate(day=TruncDate('date_joined')).values('day').annotate(count=Count('id'))
|
||||||
|
return DetailResponse(data={"registered_user_list": result}, msg="获取成功")
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||||
|
def registered_user(self, request):
|
||||||
|
"""
|
||||||
|
用户注册趋势
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
day = 30
|
||||||
|
today = datetime.datetime.today()
|
||||||
|
seven_days_ago = today - datetime.timedelta(days=day)
|
||||||
|
users = Users.objects.filter(create_datetime__gte=seven_days_ago).annotate(
|
||||||
|
day=TruncDay('create_datetime')).values(
|
||||||
|
'day').annotate(count=Count('id')).order_by('-day')
|
||||||
|
result = []
|
||||||
|
data_dict = {ele.get('day').strftime('%Y-%m-%d'): ele.get('count') for ele in users}
|
||||||
|
for i in range(day):
|
||||||
|
date = (today - datetime.timedelta(days=i)).strftime('%Y-%m-%d')
|
||||||
|
result.append({'day': date, 'count': data_dict[date] if date in data_dict else 0})
|
||||||
|
result = sorted(result, key=lambda x: x['day'])
|
||||||
|
return DetailResponse(data={"registered_user_list": result}, msg="获取成功")
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||||
|
def login_user(self, request):
|
||||||
|
"""
|
||||||
|
用户登录趋势
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
day = 30
|
||||||
|
today = datetime.datetime.today()
|
||||||
|
seven_days_ago = today - datetime.timedelta(days=day)
|
||||||
|
users = LoginLog.objects.filter(create_datetime__gte=seven_days_ago).annotate(
|
||||||
|
day=TruncDay('create_datetime')).values(
|
||||||
|
'day').annotate(count=Count('id')).order_by('-day')
|
||||||
|
result = []
|
||||||
|
data_dict = {ele.get('day').strftime('%Y-%m-%d'): ele.get('count') for ele in users}
|
||||||
|
for i in range(day):
|
||||||
|
date = (today - datetime.timedelta(days=i)).strftime('%Y-%m-%d')
|
||||||
|
result.append({'day': date, 'count': data_dict[date] if date in data_dict else 0})
|
||||||
|
result = sorted(result, key=lambda x: x['day'])
|
||||||
|
return DetailResponse(data={"login_user": result}, msg="获取成功")
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||||
|
def users_active(self, request):
|
||||||
|
"""
|
||||||
|
用户新增活跃数据统计
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
today = datetime.date.today()
|
||||||
|
seven_days_ago = today - datetime.timedelta(days=6)
|
||||||
|
thirty_days_ago = today - datetime.timedelta(days=29)
|
||||||
|
|
||||||
|
today_users = Users.objects.filter(date_joined__date=today).count()
|
||||||
|
today_logins = Users.objects.filter(last_login__date=today).count()
|
||||||
|
three_days_users = Users.objects.filter(date_joined__gte=seven_days_ago).count()
|
||||||
|
seven_days_users = Users.objects.filter(date_joined__gte=thirty_days_ago).count()
|
||||||
|
seven_days_active = Users.objects.filter(last_login__gte=seven_days_ago).values('last_login').annotate(
|
||||||
|
count=Count('id', distinct=True)).count()
|
||||||
|
monthly_active = Users.objects.filter(last_login__gte=thirty_days_ago).values('last_login').annotate(
|
||||||
|
count=Count('id', distinct=True)).count()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'today_users': today_users,
|
||||||
|
'today_logins': today_logins,
|
||||||
|
'three_days': three_days_users,
|
||||||
|
'seven_days': seven_days_users,
|
||||||
|
'seven_days_active': seven_days_active,
|
||||||
|
'monthly_active': monthly_active
|
||||||
|
}
|
||||||
|
return DetailResponse(data=data, msg="获取成功")
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||||
|
def login_region(self, request):
|
||||||
|
"""
|
||||||
|
登录用户区域分布
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
CHINA_PROVINCES = [
|
||||||
|
{'name': '北京', 'code': '110000'},
|
||||||
|
{'name': '天津', 'code': '120000'},
|
||||||
|
{'name': '河北', 'code': '130000'},
|
||||||
|
{'name': '山西', 'code': '140000'},
|
||||||
|
{'name': '内蒙古', 'code': '150000'},
|
||||||
|
{'name': '辽宁', 'code': '210000'},
|
||||||
|
{'name': '吉林', 'code': '220000'},
|
||||||
|
{'name': '黑龙江', 'code': '230000'},
|
||||||
|
{'name': '上海', 'code': '310000'},
|
||||||
|
{'name': '江苏', 'code': '320000'},
|
||||||
|
{'name': '浙江', 'code': '330000'},
|
||||||
|
{'name': '安徽', 'code': '340000'},
|
||||||
|
{'name': '福建', 'code': '350000'},
|
||||||
|
{'name': '江西', 'code': '360000'},
|
||||||
|
{'name': '山东', 'code': '370000'},
|
||||||
|
{'name': '河南', 'code': '410000'},
|
||||||
|
{'name': '湖北', 'code': '420000'},
|
||||||
|
{'name': '湖南', 'code': '430000'},
|
||||||
|
{'name': '广东', 'code': '440000'},
|
||||||
|
{'name': '广西', 'code': '450000'},
|
||||||
|
{'name': '海南', 'code': '460000'},
|
||||||
|
{'name': '重庆', 'code': '500000'},
|
||||||
|
{'name': '四川', 'code': '510000'},
|
||||||
|
{'name': '贵州', 'code': '520000'},
|
||||||
|
{'name': '云南', 'code': '530000'},
|
||||||
|
{'name': '西藏', 'code': '540000'},
|
||||||
|
{'name': '陕西', 'code': '610000'},
|
||||||
|
{'name': '甘肃', 'code': '620000'},
|
||||||
|
{'name': '青海', 'code': '630000'},
|
||||||
|
{'name': '宁夏', 'code': '640000'},
|
||||||
|
{'name': '新疆', 'code': '650000'},
|
||||||
|
{'name': '台湾', 'code': '710000'},
|
||||||
|
{'name': '香港', 'code': '810000'},
|
||||||
|
{'name': '澳门', 'code': '820000'},
|
||||||
|
{'name': '未知区域', 'code': '000000'},
|
||||||
|
]
|
||||||
|
provinces = [x['name'] for x in CHINA_PROVINCES]
|
||||||
|
day = 30
|
||||||
|
today = datetime.datetime.today()
|
||||||
|
seven_days_ago = today - datetime.timedelta(days=day)
|
||||||
|
province_data = LoginLog.objects.filter(create_datetime__gte=seven_days_ago).values('province').annotate(
|
||||||
|
count=Count('id')).order_by('-count')
|
||||||
|
province_dict = {p: 0 for p in provinces}
|
||||||
|
for ele in province_data:
|
||||||
|
if ele.get('province') in province_dict:
|
||||||
|
province_dict[ele.get('province')] += ele.get('count')
|
||||||
|
else:
|
||||||
|
province_dict['未知区域'] += ele.get('count')
|
||||||
|
data = [{'region': key, 'count': val} for key, val in province_dict.items()]
|
||||||
|
data = sorted(data, key=lambda x: x['count'], reverse=True)
|
||||||
|
return DetailResponse(data=data, msg="获取成功")
|
|
@ -1,5 +1,9 @@
|
||||||
|
import hashlib
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from application import dispatch
|
||||||
from dvadmin.system.models import FileList
|
from dvadmin.system.models import FileList
|
||||||
from dvadmin.utils.serializers import CustomModelSerializer
|
from dvadmin.utils.serializers import CustomModelSerializer
|
||||||
from dvadmin.utils.viewset import CustomModelViewSet
|
from dvadmin.utils.viewset import CustomModelViewSet
|
||||||
|
@ -9,15 +13,49 @@ class FileSerializer(CustomModelSerializer):
|
||||||
url = serializers.SerializerMethodField(read_only=True)
|
url = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
def get_url(self, instance):
|
def get_url(self, instance):
|
||||||
return 'media/' + str(instance.url)
|
# return 'media/' + str(instance.url)
|
||||||
|
return instance.file_url or (f'media/{str(instance.url)}')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FileList
|
model = FileList
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
validated_data['name'] = str(self.initial_data.get('file'))
|
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine")
|
||||||
validated_data['url'] = self.initial_data.get('file')
|
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup")
|
||||||
|
file = self.initial_data.get('file')
|
||||||
|
file_size = file.size
|
||||||
|
validated_data['name'] = file.name
|
||||||
|
validated_data['size'] = file_size
|
||||||
|
validated_data['md5sum'] = hashlib.md5().hexdigest()
|
||||||
|
validated_data['engine'] = file_engine
|
||||||
|
validated_data['mime_type'] = file.content_type
|
||||||
|
if file_backup:
|
||||||
|
validated_data['url'] = file
|
||||||
|
if file_engine =='oss':
|
||||||
|
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
|
||||||
|
file_path = ali_oss_upload(file)
|
||||||
|
if file_path:
|
||||||
|
validated_data['file_url'] = file_path
|
||||||
|
else:
|
||||||
|
raise ValueError("上传失败")
|
||||||
|
elif file_engine == 'cos':
|
||||||
|
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
|
||||||
|
file_path = tencent_cos_upload(file)
|
||||||
|
if file_path:
|
||||||
|
validated_data['file_url'] = file_path
|
||||||
|
else:
|
||||||
|
raise ValueError("上传失败")
|
||||||
|
else:
|
||||||
|
validated_data['url'] = file
|
||||||
|
# 审计字段
|
||||||
|
try:
|
||||||
|
request_user = self.request.user
|
||||||
|
validated_data['dept_belong_id'] = request_user.dept.id
|
||||||
|
validated_data['creator'] = request_user.id
|
||||||
|
validated_data['modifier'] = request_user.id
|
||||||
|
except:
|
||||||
|
pass
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ class LoginSerializer(TokenObtainPairSerializer):
|
||||||
default_error_messages = {"no_active_account": _("账号/密码错误")}
|
default_error_messages = {"no_active_account": _("账号/密码错误")}
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
|
||||||
captcha = self.initial_data.get("captcha", None)
|
captcha = self.initial_data.get("captcha", None)
|
||||||
if dispatch.get_system_config_values("base.captcha_state"):
|
if dispatch.get_system_config_values("base.captcha_state"):
|
||||||
if captcha is None:
|
if captcha is None:
|
||||||
|
|
|
@ -7,12 +7,15 @@
|
||||||
@Remark: 公共基础model类
|
@Remark: 公共基础model类
|
||||||
"""
|
"""
|
||||||
import uuid
|
import uuid
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db import models
|
from django.db import models, connection, ProgrammingError
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
from application import settings
|
from application import settings
|
||||||
|
from application.dispatch import is_tenants_mode
|
||||||
|
|
||||||
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,10 +43,17 @@ class SoftDeleteManager(models.Manager):
|
||||||
return SoftDeleteQuerySet(self.model, using=self._db).exclude(is_deleted=False)
|
return SoftDeleteQuerySet(self.model, using=self._db).exclude(is_deleted=False)
|
||||||
return SoftDeleteQuerySet(self.model).exclude(is_deleted=True)
|
return SoftDeleteQuerySet(self.model).exclude(is_deleted=True)
|
||||||
|
|
||||||
def get_by_natural_key(self,name):
|
def get_by_natural_key(self, name):
|
||||||
return SoftDeleteQuerySet(self.model).get(username=name)
|
return SoftDeleteQuerySet(self.model).get(username=name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_month_range(start_day, end_day):
|
||||||
|
months = (end_day.year - start_day.year) * 12 + end_day.month - start_day.month
|
||||||
|
month_range = ['%s-%s-01' % (start_day.year + mon // 12, str(mon % 12 + 1).zfill(2))
|
||||||
|
for mon in range(start_day.month - 1, start_day.month + months)]
|
||||||
|
return month_range
|
||||||
|
|
||||||
|
|
||||||
class SoftDeleteModel(models.Model):
|
class SoftDeleteModel(models.Model):
|
||||||
"""
|
"""
|
||||||
软删除模型
|
软删除模型
|
||||||
|
@ -73,10 +83,13 @@ class CoreModel(models.Model):
|
||||||
id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
|
id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
|
||||||
description = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
|
description = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
|
||||||
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
|
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
|
||||||
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False)
|
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL,
|
||||||
|
db_constraint=False)
|
||||||
modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人")
|
modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人")
|
||||||
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True, verbose_name="数据归属部门")
|
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True,
|
||||||
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
|
verbose_name="数据归属部门")
|
||||||
|
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间",
|
||||||
|
verbose_name="修改时间")
|
||||||
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
|
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
|
||||||
verbose_name="创建时间")
|
verbose_name="创建时间")
|
||||||
|
|
||||||
|
@ -86,6 +99,113 @@ class CoreModel(models.Model):
|
||||||
verbose_name_plural = verbose_name
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
|
||||||
|
class AddPostgresPartitionedBase:
|
||||||
|
"""
|
||||||
|
pgsql表分表基类
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_hash_partition(cls, number=36):
|
||||||
|
"""
|
||||||
|
创建分区表
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if cls.PartitioningMeta.method != 'hash':
|
||||||
|
raise ProgrammingError("表分区错误,无法进行分区")
|
||||||
|
schema_editor = connection.schema_editor()
|
||||||
|
if is_tenants_mode():
|
||||||
|
schema_editor.sql_add_hash_partition = f'CREATE TABLE "{connection.tenant.schema_name}".%s PARTITION OF "{connection.tenant.schema_name}".%s FOR VALUES WITH (MODULUS %s, REMAINDER %s)'
|
||||||
|
for item in range(number):
|
||||||
|
try:
|
||||||
|
schema_editor.add_hash_partition(
|
||||||
|
model=cls,
|
||||||
|
name="_" + str(item),
|
||||||
|
modulus=number,
|
||||||
|
remainder=item,
|
||||||
|
)
|
||||||
|
except ProgrammingError as e:
|
||||||
|
print(f"{cls.__name__}分表失败:" + str(e).rstrip('\n'))
|
||||||
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_range_day_partition(cls, day=7):
|
||||||
|
"""
|
||||||
|
按照创建时间"天"分表
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if cls.PartitioningMeta.method != 'range':
|
||||||
|
raise ProgrammingError("表分区错误,无法进行分区")
|
||||||
|
day_before = date.today().strftime("%Y-%m-%d")
|
||||||
|
schema_editor = connection.schema_editor()
|
||||||
|
if is_tenants_mode():
|
||||||
|
schema_editor.sql_add_range_partition = (
|
||||||
|
f'CREATE TABLE "{connection.tenant.schema_name}".%s PARTITION OF "{connection.tenant.schema_name}".%s FOR VALUES FROM (%s) TO (%s)'
|
||||||
|
)
|
||||||
|
for index in range(day):
|
||||||
|
try:
|
||||||
|
day_following = (date.today() + timedelta(days=index + 1)).strftime("%Y-%m-%d")
|
||||||
|
schema_editor.add_range_partition(
|
||||||
|
model=cls,
|
||||||
|
name=f"{day_before}_{day_following}",
|
||||||
|
from_values=day_before,
|
||||||
|
to_values=day_following,
|
||||||
|
)
|
||||||
|
day_before = day_following
|
||||||
|
except ProgrammingError as e:
|
||||||
|
print(f"{cls.__name__}分表失败:" + str(e).rstrip('\n'))
|
||||||
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_range_month_partition(cls, start_date, end_date):
|
||||||
|
"""
|
||||||
|
按照创建时间"月"分表
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if cls.PartitioningMeta.method != 'range':
|
||||||
|
raise ProgrammingError("表分区错误,无法进行分区")
|
||||||
|
range_month_partition_list = get_month_range(start_date, end_date)
|
||||||
|
schema_editor = connection.schema_editor()
|
||||||
|
if is_tenants_mode():
|
||||||
|
schema_editor.sql_add_range_partition = (
|
||||||
|
f'CREATE TABLE "{connection.tenant.schema_name}".%s PARTITION OF "{connection.tenant.schema_name}".%s FOR VALUES FROM (%s) TO (%s)'
|
||||||
|
)
|
||||||
|
for index, ele in enumerate(range_month_partition_list):
|
||||||
|
if index == 0:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
schema_editor.add_range_partition(
|
||||||
|
model=cls,
|
||||||
|
name=f"{range_month_partition_list[index - 1][:-3]}_{ele[:-3]}",
|
||||||
|
from_values=range_month_partition_list[index - 1],
|
||||||
|
to_values=ele,
|
||||||
|
)
|
||||||
|
except ProgrammingError as e:
|
||||||
|
print(f"{cls.__name__}分表失败:" + str(e).rstrip('\n'))
|
||||||
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_list_partition(cls, unique_value):
|
||||||
|
"""
|
||||||
|
按照某个值进行分区
|
||||||
|
:param unique_value:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if cls.PartitioningMeta.method != 'list':
|
||||||
|
raise ProgrammingError("表分区错误,无法进行分区")
|
||||||
|
schema_editor = connection.schema_editor()
|
||||||
|
if is_tenants_mode():
|
||||||
|
schema_editor.sql_add_list_partition = (
|
||||||
|
f'CREATE TABLE "{connection.tenant.schema_name}".%s PARTITION OF "{connection.tenant.schema_name}".%s FOR VALUES IN (%s)'
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
schema_editor.add_list_partition(
|
||||||
|
model=cls,
|
||||||
|
name=f"_{unique_value}",
|
||||||
|
values=[unique_value],
|
||||||
|
)
|
||||||
|
except ProgrammingError as e:
|
||||||
|
print(f"{cls.__name__}分表失败:" + str(e).rstrip('\n'))
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def get_all_models_objects(model_name=None):
|
def get_all_models_objects(model_name=None):
|
||||||
|
|
|
@ -202,7 +202,7 @@ def get_ip_analysis(ip):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def save_login_log(request):
|
def save_login_log(request, login_type=1):
|
||||||
"""
|
"""
|
||||||
保存登录日志
|
保存登录日志
|
||||||
:return:
|
:return:
|
||||||
|
@ -214,6 +214,7 @@ def save_login_log(request):
|
||||||
analysis_data['agent'] = str(parse(request.META['HTTP_USER_AGENT']))
|
analysis_data['agent'] = str(parse(request.META['HTTP_USER_AGENT']))
|
||||||
analysis_data['browser'] = get_browser(request)
|
analysis_data['browser'] = get_browser(request)
|
||||||
analysis_data['os'] = get_os(request)
|
analysis_data['os'] = get_os(request)
|
||||||
|
analysis_data['login_type'] = login_type
|
||||||
analysis_data['creator_id'] = request.user.id
|
analysis_data['creator_id'] = request.user.id
|
||||||
analysis_data['dept_belong_id'] = getattr(request.user, 'dept_id', '')
|
analysis_data['dept_belong_id'] = getattr(request.user, 'dept_id', '')
|
||||||
LoginLog.objects.create(**analysis_data)
|
LoginLog.objects.create(**analysis_data)
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"""
|
"""
|
||||||
import hashlib
|
import hashlib
|
||||||
import random
|
import random
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
CHAR_SET = ("2", "3", "4", "5",
|
CHAR_SET = ("2", "3", "4", "5",
|
||||||
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
|
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
|
||||||
|
@ -40,3 +41,25 @@ def has_md5(str, salt='123456'):
|
||||||
md.update(str.encode())
|
md.update(str.encode())
|
||||||
res = md.hexdigest()
|
res = md.hexdigest()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def format_bytes(size, decimals=2):
|
||||||
|
"""
|
||||||
|
格式化字节大小
|
||||||
|
:param size:
|
||||||
|
:param decimals:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if isinstance(size, (str)) and size.isnumeric():
|
||||||
|
size = int(size)
|
||||||
|
elif not isinstance(size, (int, float, Decimal)):
|
||||||
|
return size
|
||||||
|
if size == 0:
|
||||||
|
return "0 Bytes"
|
||||||
|
units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
||||||
|
i = 0
|
||||||
|
while size >= 1024:
|
||||||
|
size /= 1024
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return f"{round(size, decimals)} {units[i]}"
|
||||||
|
|
|
@ -8,8 +8,10 @@ django-comment-migrate==0.1.7
|
||||||
django-cors-headers==3.10.1
|
django-cors-headers==3.10.1
|
||||||
django-filter==22.1
|
django-filter==22.1
|
||||||
django-ranged-response==0.2.0
|
django-ranged-response==0.2.0
|
||||||
|
django-redis==5.2.0
|
||||||
django-restql==0.15.3
|
django-restql==0.15.3
|
||||||
django-simple-captcha==0.5.17
|
django-simple-captcha==0.5.17
|
||||||
|
django-tenants==3.4.8
|
||||||
django-timezone-field==4.2.3
|
django-timezone-field==4.2.3
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.14.0
|
||||||
djangorestframework-simplejwt==5.2.2
|
djangorestframework-simplejwt==5.2.2
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "django-vue-admin",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "django-vue-admin",
|
"name": "django-vue-admin",
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve --open",
|
"serve": "vue-cli-service serve --open",
|
||||||
"start": "npm run serve",
|
"start": "npm run serve",
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
"d2-crud-x": "^2.17.9",
|
"d2-crud-x": "^2.17.9",
|
||||||
"d2p-extends": "^2.17.9",
|
"d2p-extends": "^2.17.9",
|
||||||
"dayjs": "^1.8.17",
|
"dayjs": "^1.8.17",
|
||||||
"echarts": "^5.1.2",
|
"echarts": "^5.4.2",
|
||||||
"el-phone-number-input": "^1.1.5",
|
"el-phone-number-input": "^1.1.5",
|
||||||
"element-ui": "^2.15.5",
|
"element-ui": "^2.15.5",
|
||||||
"faker": "^4.1.0",
|
"faker": "^4.1.0",
|
||||||
|
@ -40,7 +40,9 @@
|
||||||
"screenfull": "^5.0.2",
|
"screenfull": "^5.0.2",
|
||||||
"sortablejs": "^1.10.1",
|
"sortablejs": "^1.10.1",
|
||||||
"ua-parser-js": "^0.7.20",
|
"ua-parser-js": "^0.7.20",
|
||||||
|
"viser-vue": "^2.4.8",
|
||||||
"vue": "2.7.14",
|
"vue": "2.7.14",
|
||||||
|
"vue-echarts": "^6.5.4",
|
||||||
"vue-grid-layout": "^2.4.0",
|
"vue-grid-layout": "^2.4.0",
|
||||||
"vue-i18n": "^8.15.1",
|
"vue-i18n": "^8.15.1",
|
||||||
"vue-infinite-scroll": "^2.0.2",
|
"vue-infinite-scroll": "^2.0.2",
|
||||||
|
|
|
@ -172,5 +172,22 @@ util.ArrayToTree = function (rootList, parentValue, parentName, list) {
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
// 格式化字节大小
|
||||||
|
util.formatBytes = function (bytes, decimals = 2) {
|
||||||
|
if (isNaN(bytes)) {
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes === 0) {
|
||||||
|
return '0 Bytes'
|
||||||
|
}
|
||||||
|
|
||||||
|
const k = 1024
|
||||||
|
const dm = decimals < 0 ? 0 : decimals
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
||||||
|
}
|
||||||
|
|
||||||
export default util
|
export default util
|
||||||
|
|
|
@ -36,13 +36,15 @@ import md5 from 'js-md5'
|
||||||
import websocket from '@/api/websocket'
|
import websocket from '@/api/websocket'
|
||||||
import util from '@/libs/util'
|
import util from '@/libs/util'
|
||||||
|
|
||||||
|
// 引入echarts
|
||||||
|
import * as echarts from 'echarts' // 注册echarts组件
|
||||||
// 核心插件
|
// 核心插件
|
||||||
Vue.use(d2Admin)
|
Vue.use(d2Admin)
|
||||||
Vue.use(VXETable)
|
Vue.use(VXETable)
|
||||||
Vue.prototype.$md5 = md5
|
Vue.prototype.$md5 = md5
|
||||||
Vue.prototype.$util = util
|
Vue.prototype.$util = util
|
||||||
Vue.prototype.$websocket = websocket
|
Vue.prototype.$websocket = websocket
|
||||||
|
Vue.prototype.$echarts = echarts
|
||||||
new Vue({
|
new Vue({
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<el-card shadow="hover" header="关于项目" class="card-view" :style="{backgroundColor:randomColor()}">
|
<el-card shadow="hover" :header="config?.showHeader?.value ? '关于项目' : ''" class="card-view" :style="{backgroundColor:randomColor(),color: config?.fontColor?.value}">
|
||||||
<p>基于RBAC模型的权限控制的一整套基础开发平台,前后端分离,后端采用 django+django-rest-framework,前端采用
|
<p :style="{color: config?.fontColor?.value}">基于RBAC模型的权限控制的一整套基础开发平台,前后端分离,后端采用 django+django-rest-framework,前端采用
|
||||||
vue+ElementUI+d2-crud-plus。如果喜欢就点个星星支持一下。
|
vue+ElementUI+d2-crud-plus。如果喜欢就点个星星支持一下。
|
||||||
<a href='https://gitee.com/liqianglog/django-vue-admin'>
|
<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'
|
<img src='https://gitee.com/liqianglog/django-vue-admin/badge/star.svg?theme=dark' alt='star'
|
||||||
|
@ -12,20 +12,32 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
sort: 9,
|
||||||
title: '关于项目',
|
title: '关于项目',
|
||||||
|
name: 'about',
|
||||||
icon: 'el-icon-setting',
|
icon: 'el-icon-setting',
|
||||||
description: '点个星星支持一下',
|
description: '点个星星支持一下',
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 8,
|
width: 16,
|
||||||
minH: 10,
|
|
||||||
minW: 2,
|
|
||||||
isResizable: true,
|
isResizable: true,
|
||||||
config: {
|
config: {
|
||||||
|
showHeader: {
|
||||||
|
label: '显示头部信息',
|
||||||
|
type: 'boot',
|
||||||
|
value: true,
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
color: {
|
color: {
|
||||||
label: '背景颜色',
|
label: '背景颜色',
|
||||||
type: 'color',
|
type: 'color',
|
||||||
value: '',
|
value: '',
|
||||||
placeholder: '颜色为空则随机变换颜色'
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -43,7 +55,7 @@ export default {
|
||||||
if (this.config?.color?.value) {
|
if (this.config?.color?.value) {
|
||||||
return this.config.color.value
|
return this.config.color.value
|
||||||
}
|
}
|
||||||
return this.color || this.$util.randomColor()
|
return this.$util.randomColor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,11 +63,11 @@ export default {
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.card-view {
|
.card-view {
|
||||||
color: #FFFFFF;
|
color: $color-primary;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 0.7em;
|
font-size: 0.8em;
|
||||||
color: #FFFFFF;
|
color: $color-primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.el-card{
|
.el-card{
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
<template>
|
||||||
|
<el-card
|
||||||
|
class="card-view"
|
||||||
|
:style="{ backgroundColor: randomColor() }"
|
||||||
|
shadow="always"
|
||||||
|
>
|
||||||
|
<div :style="{color: config?.fontColor?.value}">
|
||||||
|
<div>
|
||||||
|
<div class="card-content-label">附件统计</div>
|
||||||
|
<i class="real-time">实时</i>
|
||||||
|
</div>
|
||||||
|
<div class="absolute-left">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="card-content-value">{{ count }}</div>
|
||||||
|
<div class="el-icon-document-copy">
|
||||||
|
附件数量
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute-right">
|
||||||
|
<div class="card-content-time">
|
||||||
|
<div class="attachment-value">{{ occupy_space }}</div>
|
||||||
|
<div class="el-icon-s-flag">
|
||||||
|
附件大小
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { request } from '@/api/service'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
sort: 3,
|
||||||
|
title: '附件统计',
|
||||||
|
name: 'attachmentTotal',
|
||||||
|
icon: 'el-icon-s-order',
|
||||||
|
description: '总附件数以及附件占用大小',
|
||||||
|
height: 14,
|
||||||
|
width: 16,
|
||||||
|
isResizable: true,
|
||||||
|
config: {
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
count: '',
|
||||||
|
occupy_space: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initGet () {
|
||||||
|
request({
|
||||||
|
url: '/api/system/datav/attachment_total/'
|
||||||
|
}).then((res) => {
|
||||||
|
this.count = res.data.count
|
||||||
|
this.occupy_space = this.$util.formatBytes(res.data.occupy_space)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 生成一个随机整数
|
||||||
|
randomColor () {
|
||||||
|
if (this.config?.color?.value) {
|
||||||
|
return this.config.color.value
|
||||||
|
}
|
||||||
|
return this.$util.randomColor()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.initGet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.card-view {
|
||||||
|
//border-radius: 10px;
|
||||||
|
color: $color-primary;
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
.card-content-label {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content-value {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-value {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-document-copy {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-s-flag {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.real-time {
|
||||||
|
background: rgb(53, 59, 86);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 7px 0 7px;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.absolute-right {
|
||||||
|
position: absolute;
|
||||||
|
right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.absolute-left {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -6,14 +6,13 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
sort: 8,
|
||||||
title: '宣传图',
|
title: '宣传图',
|
||||||
|
name: 'dashboardImg',
|
||||||
icon: 'el-icon-medal',
|
icon: 'el-icon-medal',
|
||||||
|
description: '用于展示各种图片宣传页',
|
||||||
height: 10,
|
height: 10,
|
||||||
width: 8,
|
width: 16,
|
||||||
minH: 10,
|
|
||||||
minW: 1,
|
|
||||||
maxW: 24,
|
|
||||||
maxH: 100,
|
|
||||||
isResizable: true,
|
isResizable: true,
|
||||||
config: {
|
config: {
|
||||||
src: {
|
src: {
|
||||||
|
@ -37,7 +36,6 @@ export default {
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
description: '用于展示各种图片宣传页',
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
<template>
|
||||||
|
<el-card
|
||||||
|
class="card-view"
|
||||||
|
:style="{ backgroundColor: randomColor() }"
|
||||||
|
shadow="always"
|
||||||
|
>
|
||||||
|
<div :style="{color: config?.fontColor?.value}">
|
||||||
|
<div>
|
||||||
|
<div class="card-content-label">数据库统计</div>
|
||||||
|
<i class="real-time">实时</i>
|
||||||
|
</div>
|
||||||
|
<div class="absolute-left">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="card-content-value">{{ count }}</div>
|
||||||
|
<div class="el-icon-coin">
|
||||||
|
数据库数量
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute-right">
|
||||||
|
<div class="card-content-time">
|
||||||
|
<div class="attachment-value">{{ space }}</div>
|
||||||
|
<div class="el-icon-s-flag">
|
||||||
|
占用空间
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { request } from '@/api/service'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
sort: 4,
|
||||||
|
title: '数据库统计',
|
||||||
|
name: 'databaseTotal',
|
||||||
|
icon: 'el-icon-coin',
|
||||||
|
description: '数据库统计',
|
||||||
|
height: 14,
|
||||||
|
width: 16,
|
||||||
|
isResizable: true,
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
count: '',
|
||||||
|
space: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initGet () {
|
||||||
|
request({
|
||||||
|
url: '/api/system/datav/database_total/'
|
||||||
|
}).then((res) => {
|
||||||
|
this.count = res.data.count
|
||||||
|
this.space = this.$util.formatBytes(res.data.space)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
randomColor () {
|
||||||
|
if (this.config?.color?.value) {
|
||||||
|
return this.config.color.value
|
||||||
|
}
|
||||||
|
return this.$util.randomColor()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.initGet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.card-view {
|
||||||
|
//border-radius: 10px;
|
||||||
|
color: $color-primary;
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
.card-content-label {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content-value {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-value {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-coin {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-s-flag {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.real-time {
|
||||||
|
background: rgb(53, 59, 86);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 7px 0 7px;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.absolute-right{
|
||||||
|
position: absolute;
|
||||||
|
right: 30px;
|
||||||
|
}
|
||||||
|
.absolute-left{
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,205 @@
|
||||||
|
<template>
|
||||||
|
<el-card
|
||||||
|
class="card-view"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: randomColor(),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div id="region" :style="{width: pxData.wpx+'px',height: pxData.hpx+'px'}"></div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { request } from '@/api/service'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
sort: 7,
|
||||||
|
title: '登录区域分布',
|
||||||
|
name: 'loginRegion',
|
||||||
|
icon: 'el-icon-s-data',
|
||||||
|
description: '登录区域分布详情',
|
||||||
|
height: 28,
|
||||||
|
width: 20,
|
||||||
|
isResizable: true,
|
||||||
|
props: {
|
||||||
|
pxData: {
|
||||||
|
type: Object,
|
||||||
|
require: false,
|
||||||
|
default: () => ({
|
||||||
|
wpx: 0,
|
||||||
|
hpx: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
pxData: {
|
||||||
|
handler () {
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
this.myChart?.resize({ width: this.pxData.wpx, height: this.pxData.hpx })
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
this.myChart = null
|
||||||
|
return {
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initGet () {
|
||||||
|
request({
|
||||||
|
url: '/api/system/datav/login_region/'
|
||||||
|
}).then((res) => {
|
||||||
|
this.data = res.data
|
||||||
|
this.drawLine(this.data)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 生成一个随机整数
|
||||||
|
randomColor () {
|
||||||
|
const color = ['#fffff']
|
||||||
|
const ran = Math.floor(Math.random() * 4)
|
||||||
|
return color[ran]
|
||||||
|
},
|
||||||
|
drawLine () {
|
||||||
|
// 基于准备好的dom,初始化echarts实例
|
||||||
|
// 绘制图表
|
||||||
|
const xAxisData = this.data.map(item => item.region)
|
||||||
|
const seriesData = this.data.map(item => item.count)
|
||||||
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: '登录区域分布',
|
||||||
|
textStyle: {
|
||||||
|
color: '#666666',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600'
|
||||||
|
},
|
||||||
|
left: 'left'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
||||||
|
textStyle: {
|
||||||
|
color: '#666'
|
||||||
|
},
|
||||||
|
axisPointer: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#999',
|
||||||
|
type: 'dotted',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: params => {
|
||||||
|
const param = params[0]
|
||||||
|
return `<div style="padding: 8px;"><div style="color: #333;">${param.name}</div><div style="color: #FFA500;">${param.value} 次</div></div>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['登录区域分布'],
|
||||||
|
textStyle: {
|
||||||
|
color: '#666',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
left: 40,
|
||||||
|
right: 65,
|
||||||
|
bottom: 75
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
data: xAxisData,
|
||||||
|
boundaryGap: true,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#aaa',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
interval: '0',
|
||||||
|
maxInterval: 1,
|
||||||
|
rotate: 0,
|
||||||
|
formatter: function (value) {
|
||||||
|
return value.split('').join('\n')
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#aaa',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#ddd',
|
||||||
|
type: 'dotted',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '用户注册数',
|
||||||
|
type: 'bar',
|
||||||
|
data: seriesData,
|
||||||
|
barWidth: 16,
|
||||||
|
barGap: 0,
|
||||||
|
barCategoryGap: '20%',
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(0, 128, 255, 1)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(0, 128, 255, 0.2)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart.setOption(option)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.myChart = this.$echarts.init(document.getElementById('region'))
|
||||||
|
this.initGet()
|
||||||
|
this.drawLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.card-view {
|
||||||
|
//border-radius: 10px;
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,103 @@
|
||||||
|
<template>
|
||||||
|
<el-card class="card-view" :style="{backgroundColor:randomColor(),color: config?.fontColor?.value}" shadow="always">
|
||||||
|
<div>
|
||||||
|
<el-row type="flex" justify="space-around" style="padding:10px">
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="card-content-label">登录次数</div>
|
||||||
|
<div class="card-content-value">{{ loginTotal }}</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" :offset="6" style="text-align: right;">
|
||||||
|
<i class="el-icon-user-solid" size="48px"></i>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { request } from '@/api/service'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
sort: 2,
|
||||||
|
title: '登录总次数',
|
||||||
|
name: 'loginTotal',
|
||||||
|
icon: 'el-icon-user-solid',
|
||||||
|
description: '用户登录平台总次数',
|
||||||
|
height: 14,
|
||||||
|
width: 16,
|
||||||
|
isResizable: true,
|
||||||
|
config: {
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loginTotal: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initGet () {
|
||||||
|
request({
|
||||||
|
url: '/api/system/datav/users_login_total/'
|
||||||
|
}).then((res) => {
|
||||||
|
this.loginTotal = res.data.login_total
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 生成一个随机整数
|
||||||
|
randomColor () {
|
||||||
|
if (this.config?.color?.value) {
|
||||||
|
return this.config.color.value
|
||||||
|
}
|
||||||
|
return this.$util.randomColor()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.initGet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.card-view {
|
||||||
|
//border-radius: 10px;
|
||||||
|
color: $color-primary;
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
.card-content-label {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content-value {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-user-solid {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,201 @@
|
||||||
|
<template>
|
||||||
|
<el-card
|
||||||
|
class="card-view"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: randomColor(),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div id="main" :style="{width: pxData.wpx+'px',height: pxData.hpx+'px'}"></div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { request } from '@/api/service'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
sort: 7,
|
||||||
|
title: '注册用户趋势',
|
||||||
|
name: 'registeredUser',
|
||||||
|
icon: 'el-icon-s-data',
|
||||||
|
description: '用户注册',
|
||||||
|
height: 28,
|
||||||
|
width: 20,
|
||||||
|
isResizable: true,
|
||||||
|
props: {
|
||||||
|
pxData: {
|
||||||
|
type: Object,
|
||||||
|
require: false,
|
||||||
|
default: () => ({
|
||||||
|
wpx: 0,
|
||||||
|
hpx: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
pxData: {
|
||||||
|
handler () {
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
this.myChart?.resize({ width: this.pxData.wpx, height: this.pxData.hpx })
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
this.myChart = null
|
||||||
|
return {
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initGet () {
|
||||||
|
request({
|
||||||
|
url: '/api/system/datav/registered_user/'
|
||||||
|
}).then((res) => {
|
||||||
|
this.data = res.data.registered_user_list
|
||||||
|
this.drawLine(this.data)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 生成一个随机整数
|
||||||
|
randomColor () {
|
||||||
|
const color = ['#fffff']
|
||||||
|
const ran = Math.floor(Math.random() * 4)
|
||||||
|
return color[ran]
|
||||||
|
},
|
||||||
|
drawLine () {
|
||||||
|
// 基于准备好的dom,初始化echarts实例
|
||||||
|
// 绘制图表
|
||||||
|
const xAxisData = this.data.map(item => item.day)
|
||||||
|
const seriesData = this.data.map(item => item.count)
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
||||||
|
textStyle: {
|
||||||
|
color: '#666'
|
||||||
|
},
|
||||||
|
axisPointer: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#999',
|
||||||
|
type: 'dotted',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: params => {
|
||||||
|
const param = params[0]
|
||||||
|
return `<div style="padding: 8px;"><div style="color: #333;">${param.name}</div><div style="color: #FFA500;">${param.value} 人</div></div>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['用户注册数'],
|
||||||
|
textStyle: {
|
||||||
|
color: '#666',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
left: 40,
|
||||||
|
right: 65,
|
||||||
|
bottom: 60
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
data: xAxisData,
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#aaa',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
interval: 'auto',
|
||||||
|
maxInterval: 1,
|
||||||
|
rotate: 0,
|
||||||
|
textStyle: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#aaa',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#ddd',
|
||||||
|
type: 'dotted',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '用户注册数',
|
||||||
|
type: 'line',
|
||||||
|
data: seriesData,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
|
smooth: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgba(38,204,164, 0.8)',
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(98,206,178, 0.8)',
|
||||||
|
borderColor: 'rgba(38,204,164, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(140,189,250, 0.8)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(0, 128, 255, 0)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.myChart.setOption(option)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.myChart = this.$echarts.init(document.getElementById('main'))
|
||||||
|
this.initGet()
|
||||||
|
this.drawLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.card-view {
|
||||||
|
//border-radius: 10px;
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<el-card shadow="hover" header="时钟" class="item-background">
|
<el-card shadow="hover" class="card-view" :style="{backgroundColor: randomColor(),color: config?.fontColor?.value}">
|
||||||
<div class="time">
|
<div class="time">
|
||||||
<h2>{{ time }}</h2>
|
<h2>{{ time }}</h2>
|
||||||
<p>{{ day }}</p>
|
<p>{{ day }}</p>
|
||||||
|
@ -11,14 +11,34 @@
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
sort: 12,
|
||||||
title: '时钟',
|
title: '时钟',
|
||||||
|
name: 'myTime',
|
||||||
icon: 'el-icon-alarm-clock',
|
icon: 'el-icon-alarm-clock',
|
||||||
description: '演示部件效果',
|
description: '演示部件效果',
|
||||||
height: 20,
|
height: 14,
|
||||||
minH: 10,
|
width: 16,
|
||||||
width: 8,
|
|
||||||
minW: 4,
|
|
||||||
isResizable: true,
|
isResizable: true,
|
||||||
|
config: {
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
time: '',
|
time: '',
|
||||||
|
@ -35,15 +55,21 @@ export default {
|
||||||
showTime () {
|
showTime () {
|
||||||
this.time = dayjs().format('HH:mm:ss')
|
this.time = dayjs().format('HH:mm:ss')
|
||||||
this.day = dayjs().format('YYYY年MM月DD日')
|
this.day = dayjs().format('YYYY年MM月DD日')
|
||||||
|
},
|
||||||
|
// 生成一个颜色
|
||||||
|
randomColor () {
|
||||||
|
if (this.config?.color?.value) {
|
||||||
|
return this.config.color.value
|
||||||
|
}
|
||||||
|
return 'linear-gradient(to right, #8E54E9, #4776E6)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="scss">
|
||||||
.item-background {
|
.card-view {
|
||||||
background: linear-gradient(to right, #8E54E9, #4776E6);
|
color: $color-primary;
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.time h2 {
|
.time h2 {
|
||||||
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
<template>
|
||||||
|
<el-card
|
||||||
|
class="card-view"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: randomColor(),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!-- shadow="always" -->
|
||||||
|
<div id="myChart" :style="{width: pxData.wpx+'px',height: pxData.hpx+'px'}"></div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { request } from '@/api/service'
|
||||||
|
export default {
|
||||||
|
sort: 6,
|
||||||
|
title: '用户登录趋势',
|
||||||
|
name: 'userLogin',
|
||||||
|
icon: 'el-icon-s-data',
|
||||||
|
description: '用户登陆',
|
||||||
|
height: 28,
|
||||||
|
width: 20,
|
||||||
|
isResizable: true,
|
||||||
|
props: {
|
||||||
|
pxData: {
|
||||||
|
type: Object,
|
||||||
|
require: false,
|
||||||
|
default: () => ({
|
||||||
|
wpx: 0,
|
||||||
|
hpx: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
pxData: {
|
||||||
|
handler () {
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
this.myChart?.resize({ width: this.pxData.wpx, height: this.pxData.hpx })
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
this.myChart = null
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
radio: '7'
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initGet () {
|
||||||
|
request({
|
||||||
|
url: '/api/system/datav/login_user/'
|
||||||
|
}).then((res) => {
|
||||||
|
this.data = res.data.login_user
|
||||||
|
this.drawLine(this.data)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 生成一个随机整数
|
||||||
|
randomColor () {
|
||||||
|
const color = ['#fffff']
|
||||||
|
const ran = Math.floor(Math.random() * 4)
|
||||||
|
return color[ran]
|
||||||
|
},
|
||||||
|
drawLine () {
|
||||||
|
// 基于准备好的dom,初始化echarts实例
|
||||||
|
// 绘制图表
|
||||||
|
const xAxisData = this.data.map(item => item.day)
|
||||||
|
const seriesData = this.data.map(item => item.count)
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
||||||
|
textStyle: {
|
||||||
|
color: '#666'
|
||||||
|
},
|
||||||
|
axisPointer: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#999',
|
||||||
|
type: 'dotted',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: params => {
|
||||||
|
const param = params[0]
|
||||||
|
return `<div style="padding: 8px;"><div style="color: #333;">${param.name}</div><div style="color: #FFA500;">${param.value} 次</div></div>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['用户登陆数'],
|
||||||
|
textStyle: {
|
||||||
|
color: '#666',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 40,
|
||||||
|
left: 40,
|
||||||
|
right: 65,
|
||||||
|
bottom: 60
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
data: xAxisData,
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#aaa',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
interval: 'auto',
|
||||||
|
maxInterval: 1,
|
||||||
|
rotate: 0,
|
||||||
|
textStyle: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#aaa',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#ddd',
|
||||||
|
type: 'dotted',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '用户登陆数',
|
||||||
|
type: 'line',
|
||||||
|
data: seriesData,
|
||||||
|
symbol: 'circle',
|
||||||
|
smooth: true,
|
||||||
|
symbolSize: 6,
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgba(0, 128, 255, 0.8)',
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(0, 128, 255, 0.8)',
|
||||||
|
borderColor: 'rgba(0, 128, 255, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(140,189,250, 0.8)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(0, 128, 255, 0)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.myChart.setOption(option)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.myChart = this.$echarts.init(document.getElementById('myChart'))
|
||||||
|
this.initGet()
|
||||||
|
this.drawLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.card-view {
|
||||||
|
//border-radius: 10px;
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
.el-card{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
::v-deep .el-card__body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-radio-button__inner {
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,198 @@
|
||||||
|
<template>
|
||||||
|
<el-card class="card-view" shadow="always"
|
||||||
|
:style="{backgroundColor:randomColor(),color: config?.fontColor?.value}">
|
||||||
|
<div style="display:flex;flex: 1; flex-wrap: wrap;justify-content: space-between; margin-top: 10px;"
|
||||||
|
:style="{color: config?.fontColor?.value}">
|
||||||
|
<div style="flex: 1; min-width: 180px;max-width:180px;height: 80px; display: flex;">
|
||||||
|
<el-col :span="4" class="lightgreen-box">
|
||||||
|
<div class="underline">
|
||||||
|
<i class="el-icon-s-promotion"></i>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="20" class="orange-box">
|
||||||
|
<div class="enroll-time">
|
||||||
|
<div class="enroll-number"><h3>{{ data.today_users || 0 }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="enroll-text">今日注册
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1;min-width: 180px;max-width:180px;height: 80px; display: flex;">
|
||||||
|
<el-col :span="4" class="lightgreen-box">
|
||||||
|
<div class="underline">
|
||||||
|
<i class="el-icon-chat-line-square"></i>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="20" class="orange-box">
|
||||||
|
<div class="enroll-time">
|
||||||
|
<div class="enroll-number"><h3>{{ data.today_logins || 0 }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="enroll-text">今日登录
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1;min-width: 180px;max-width:180px;height: 80px; display: flex;">
|
||||||
|
<el-col :span="4" class="lightgreen-box">
|
||||||
|
<div class="underline">
|
||||||
|
<i class="el-icon-date"></i>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="20" class="orange-box">
|
||||||
|
<div class="enroll-time">
|
||||||
|
<div class="enroll-number"><h3>{{ data.three_days || 0 }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="enroll-text">三日新增
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1;min-width: 180px;max-width:180px;height: 80px; display: flex;">
|
||||||
|
<el-col :span="4" class="lightgreen-box">
|
||||||
|
<div class="underline">
|
||||||
|
<i class="el-icon-user-solid"></i>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="20" class="orange-box">
|
||||||
|
<div class="enroll-time">
|
||||||
|
<div class="enroll-number"><h3>{{ data.seven_days || 0 }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="enroll-text">七日活跃
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1;min-width: 180px;max-width:180px;height: 80px;display: flex;">
|
||||||
|
<el-col :span="4" class="lightgreen-box">
|
||||||
|
<div class="underline">
|
||||||
|
<i class="el-icon-folder-add"></i>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="20" class="orange-box">
|
||||||
|
<div class="enroll-time">
|
||||||
|
<div class="enroll-number"><h3>{{ data.seven_days_active || 0 }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="enroll-text">七日新增
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1;min-width: 180px;max-width:180px;height: 80px; display: flex;">
|
||||||
|
<el-col :span="4" class="lightgreen-box">
|
||||||
|
<div class="underline">
|
||||||
|
<i class="el-icon-user"></i>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="20" class="orange-box">
|
||||||
|
<div class="enroll-time">
|
||||||
|
<div class="enroll-number"><h3>{{ data.monthly_active || 0 }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="enroll-text">月活跃
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { request } from '@/api/service'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
sort: 5,
|
||||||
|
title: '用户新增统计',
|
||||||
|
name: 'usersActive',
|
||||||
|
icon: 'el-icon-user-solid',
|
||||||
|
description: '用户新增以及活跃统计数据',
|
||||||
|
height: 18,
|
||||||
|
width: 20,
|
||||||
|
isResizable: true,
|
||||||
|
config: {
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.initGet()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initGet () {
|
||||||
|
request({
|
||||||
|
url: '/api/system/datav/users_active/'
|
||||||
|
}).then((res) => {
|
||||||
|
this.data = res.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 生成一个随机整数
|
||||||
|
randomColor () {
|
||||||
|
const color = [
|
||||||
|
'#fffff'
|
||||||
|
]
|
||||||
|
const ran = Math.floor(Math.random() * 4)
|
||||||
|
return color[ran]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.card-view {
|
||||||
|
// border-radius: 10px;
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
.enroll-number{
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.lightgreen-box {
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
height: 60px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.underline i {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange-box {
|
||||||
|
height: 60px;
|
||||||
|
color: black;
|
||||||
|
border-bottom: 2px solid rgb(242, 242, 242);
|
||||||
|
}
|
||||||
|
|
||||||
|
.enroll-time {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enroll-text {
|
||||||
|
color: rgb(138, 138, 138);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,103 @@
|
||||||
|
<template>
|
||||||
|
<el-card class="card-view" :style="{backgroundColor:randomColor(),color: config?.fontColor?.value}" shadow="always">
|
||||||
|
<div>
|
||||||
|
<el-row type="flex" justify="space-around" style="padding:10px">
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="card-content-label">用户总数</div>
|
||||||
|
<div class="card-content-value">{{ usersTotal }}</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" :offset="6" style="text-align: right;">
|
||||||
|
<i class="el-icon-user-solid" size="48px"></i>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { request } from '@/api/service'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
sort: 1,
|
||||||
|
title: '用户总数',
|
||||||
|
icon: 'el-icon-user-solid',
|
||||||
|
description: '平台总会员数',
|
||||||
|
name: 'usersTotal',
|
||||||
|
height: 14,
|
||||||
|
width: 16,
|
||||||
|
isResizable: true,
|
||||||
|
config: {
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
usersTotal: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initGet () {
|
||||||
|
request({
|
||||||
|
url: '/api/system/datav/users_total/'
|
||||||
|
}).then((res) => {
|
||||||
|
this.usersTotal = res.data.users_total
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 生成一个随机整数
|
||||||
|
randomColor () {
|
||||||
|
if (this.config?.color?.value) {
|
||||||
|
return this.config.color.value
|
||||||
|
}
|
||||||
|
return this.$util.randomColor()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.initGet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.card-view {
|
||||||
|
//border-radius: 10px;
|
||||||
|
color: $color-primary;
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
.card-content-label {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content-value {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-user-solid {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<el-card shadow="hover" class="card-view" :style="{backgroundColor:randomColor()}" header="版本信息">
|
<el-card
|
||||||
<div style="text-align: center;color: #FFFFFF">
|
shadow="hover"
|
||||||
|
:header="config?.showHeader?.value ? '版本信息' : ''"
|
||||||
|
class="card-view"
|
||||||
|
:style="{backgroundColor:randomColor(),color: config?.fontColor?.value}"
|
||||||
|
>
|
||||||
|
<div style="text-align: center;">
|
||||||
<h2 style="margin-top: 5px;">{{ title }}</h2>
|
<h2 style="margin-top: 5px;">{{ title }}</h2>
|
||||||
<p style="margin-top: 5px;">最新版本 {{ ver }}</p>
|
<p style="margin-top: 5px;">最新版本 {{ ver }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,14 +16,40 @@
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
sort: 10,
|
||||||
title: '版本信息',
|
title: '版本信息',
|
||||||
|
name: 'ver',
|
||||||
icon: 'el-icon-monitor',
|
icon: 'el-icon-monitor',
|
||||||
description: '当前项目版本信息',
|
description: '当前项目版本信息',
|
||||||
height: 20,
|
height: 14,
|
||||||
minH: 10,
|
width: 16,
|
||||||
width: 8,
|
|
||||||
minW: 4,
|
|
||||||
isResizable: true,
|
isResizable: true,
|
||||||
|
config: {
|
||||||
|
showHeader: {
|
||||||
|
label: '显示头部信息',
|
||||||
|
type: 'boot',
|
||||||
|
value: true,
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
ver: 'loading...',
|
ver: 'loading...',
|
||||||
|
@ -38,33 +69,32 @@ export default {
|
||||||
this.ver = `v${process.env.VUE_APP_VERSION}` || 'v2.1.1'
|
this.ver = `v${process.env.VUE_APP_VERSION}` || 'v2.1.1'
|
||||||
this.title = this.siteName || process.env.VUE_APP_TITLE
|
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 () {
|
randomColor () {
|
||||||
return this.color || this.$util.randomColor()
|
if (this.config?.color?.value) {
|
||||||
|
return this.config.color.value
|
||||||
|
}
|
||||||
|
return this.$util.randomColor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.card-view{
|
.card-view {
|
||||||
color: #FFFFFF;
|
color: $color-primary;
|
||||||
//background: rgb(80,168,244);
|
//background: rgb(80,168,244);
|
||||||
//box-shadow: 1px 6px 8px 2px rgba(80,168,244,0.2);
|
//box-shadow: 1px 6px 8px 2px rgba(80,168,244,0.2);
|
||||||
.card-content{
|
.card-content {
|
||||||
//text-align: center;
|
//text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-card__body {
|
::v-deep .el-card__body {
|
||||||
height: 110px;
|
height: 110px;
|
||||||
|
|
||||||
}
|
}
|
||||||
.el-card{
|
|
||||||
|
.el-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<el-card shadow="hover" header="欢迎" style="background: linear-gradient(150deg, #3b88ec 0%, #accaff 100%);color: #fff;">
|
<el-card
|
||||||
|
shadow="hover"
|
||||||
|
:header="config?.showHeader?.value ? '欢迎使用' : ''"
|
||||||
|
class="card-view"
|
||||||
|
:style="{background: randomColor(), color: config?.fontColor?.value}">
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="/image/django-vue-admin.png">
|
<img src="/image/django-vue-admin.png">
|
||||||
|
@ -34,14 +38,40 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
title: '欢迎',
|
sort: 11,
|
||||||
|
title: '欢迎使用',
|
||||||
|
name: 'welcome',
|
||||||
icon: 'el-icon-present',
|
icon: 'el-icon-present',
|
||||||
description: '项目特色以及文档链接',
|
description: '项目特色以及文档链接',
|
||||||
width: 8,
|
width: 16,
|
||||||
height: 45,
|
height: 45,
|
||||||
minH: 45,
|
|
||||||
minW: 1,
|
|
||||||
isResizable: true,
|
isResizable: true,
|
||||||
|
config: {
|
||||||
|
showHeader: {
|
||||||
|
label: '显示头部信息',
|
||||||
|
type: 'boot',
|
||||||
|
value: true,
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: '',
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
|
@ -54,13 +84,16 @@ export default {
|
||||||
if (this.config?.color?.value) {
|
if (this.config?.color?.value) {
|
||||||
return this.config.color.value
|
return this.config.color.value
|
||||||
}
|
}
|
||||||
return this.color || this.$util.randomColor()
|
return 'linear-gradient(150deg, #3b88ec 0%, #accaff 100%)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="scss">
|
||||||
|
.card-view {
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
.welcome .logo {
|
.welcome .logo {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,11 @@
|
||||||
:key="index"
|
:key="index"
|
||||||
:rules="item.rules">
|
:rules="item.rules">
|
||||||
<el-input v-if="item.type==='input'" v-model="item.value" :placeholder="item.placeholder || '请输入'"></el-input>
|
<el-input v-if="item.type==='input'" v-model="item.value" :placeholder="item.placeholder || '请输入'"></el-input>
|
||||||
|
<el-switch v-if="item.type==='boot'" v-model="item.value" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
|
||||||
<el-color-picker v-if="item.type==='color'" v-model="item.value" show-alpha :predefine="predefineColors"></el-color-picker>
|
<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-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="deviceUpgradeDrawer = false">保存</el-button>
|
<el-button type="primary" @click="saveConfig">保存</el-button>
|
||||||
<el-button @click="deviceUpgradeDrawer = false">关闭</el-button>
|
<el-button @click="deviceUpgradeDrawer = false">关闭</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
@ -59,6 +60,10 @@ export default {
|
||||||
this.myComp = myComp
|
this.myComp = myComp
|
||||||
this.items = items
|
this.items = items
|
||||||
console.log(1112, this.myComp, this.items)
|
console.log(1112, this.myComp, this.items)
|
||||||
|
},
|
||||||
|
saveConfig () {
|
||||||
|
this.deviceUpgradeDrawer = false
|
||||||
|
this.$emit('saveConfig', this.myComp, this.items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div slot="operateButton">
|
<div slot="operateButton">
|
||||||
|
<el-tooltip class="item" effect="dark" content="清空画布" placement="top">
|
||||||
|
<el-button v-if="customizing" type="danger" icon="el-icon-delete" circle size="mini"
|
||||||
|
@click="clickEmpty"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
<el-tooltip class="item" effect="dark" content="最小化" placement="top">
|
<el-tooltip class="item" effect="dark" content="最小化" placement="top">
|
||||||
<el-button v-if="customizing" type="success" icon="el-icon-minus" circle size="mini"
|
<el-button v-if="customizing" type="success" icon="el-icon-minus" circle size="mini"
|
||||||
@click="clickMinimize"></el-button>
|
@click="clickMinimize"></el-button>
|
||||||
|
@ -30,7 +34,8 @@
|
||||||
<span v-for="item in myCompsList" :key="item.title">
|
<span v-for="item in myCompsList" :key="item.title">
|
||||||
<el-tooltip class="item" effect="dark" :content="item.description" placement="top">
|
<el-tooltip class="item" effect="dark" :content="item.description" placement="top">
|
||||||
<div class="widgetsListItem" :style="{background: $util.randomBackground()}">
|
<div class="widgetsListItem" :style="{background: $util.randomBackground()}">
|
||||||
<span style="position: relative;right: 8px;float: right;top: -22px;cursor: pointer;" @click="push(item)">
|
<span style="position: relative;right: 8px;float: right;top: -22px;cursor: pointer;"
|
||||||
|
@click="push(item)">
|
||||||
<i class="el-icon-plus"></i>
|
<i class="el-icon-plus"></i>
|
||||||
</span>
|
</span>
|
||||||
<i :class="item.icon"></i> {{ item.title }}
|
<i :class="item.icon"></i> {{ item.title }}
|
||||||
|
@ -55,20 +60,17 @@
|
||||||
:use-css-transforms="true"
|
:use-css-transforms="true"
|
||||||
:autoSize="true"
|
:autoSize="true"
|
||||||
>
|
>
|
||||||
<grid-item v-for="(item, index) in layout"
|
<grid-item
|
||||||
:static="item.static"
|
v-for="(item, index) in layout"
|
||||||
:x="item.x"
|
:static="item.static"
|
||||||
:y="item.y"
|
:x="item.x"
|
||||||
:w="item.w"
|
:y="item.y"
|
||||||
:h="item.h"
|
:w="item.w"
|
||||||
:i="item.i"
|
:h="item.h"
|
||||||
:minW="item.minW"
|
:i="item.i"
|
||||||
:minH="item.minH"
|
:key="index"
|
||||||
:maxW="item.maxW"
|
:isResizable="customizing"
|
||||||
:maxH="item.maxH"
|
@container-resized="containerResizedEvent"
|
||||||
: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;"
|
<el-button v-if="item.config && Object.keys(item.config).length!==0" class="close" style="right: 60px;"
|
||||||
|
@ -79,21 +81,21 @@
|
||||||
<label>
|
<label>
|
||||||
<i :class="allComps[item.element].icon"></i>
|
<i :class="allComps[item.element].icon"></i>
|
||||||
{{ allComps[item.element].title }}</label>
|
{{ allComps[item.element].title }}</label>
|
||||||
|
<div style="color:#000;">宽{{ item.w }} x 高{{ item.h }}</div>
|
||||||
</div>
|
</div>
|
||||||
<component :class="customizing?'set-component-bg':''" :is="allComps[item.element]"
|
<component :class="customizing?'set-component-bg':''" :is="allComps[item.element]"
|
||||||
:config="item.config || {}"></component>
|
:config="item.config || {}" :width="item.w" :height="item.h" :pxData="pxData[item.i]"></component>
|
||||||
</grid-item>
|
</grid-item>
|
||||||
</grid-layout>
|
</grid-layout>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dashboard-config ref="dashboardConfig"></dashboard-config>
|
<dashboard-config ref="dashboardConfig" @saveConfig="saveConfig"></dashboard-config>
|
||||||
</d2-container>
|
</d2-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import allComps from './components'
|
import allComps from './components'
|
||||||
import util from '@/libs/util'
|
|
||||||
import VueGridLayout from 'vue-grid-layout'
|
import VueGridLayout from 'vue-grid-layout'
|
||||||
import SuspendedLibrary from '@/views/dashboard/workbench/suspendedLibrary'
|
import SuspendedLibrary from '@/views/dashboard/workbench/suspendedLibrary'
|
||||||
import DashboardConfig from '@/views/dashboard/workbench/config'
|
import DashboardConfig from '@/views/dashboard/workbench/config'
|
||||||
|
@ -114,12 +116,18 @@ export default {
|
||||||
selectLayout: [],
|
selectLayout: [],
|
||||||
defaultLayout: initData,
|
defaultLayout: initData,
|
||||||
layout: [],
|
layout: [],
|
||||||
colNum: 24,
|
colNum: 48,
|
||||||
minimize: false
|
minimize: false,
|
||||||
|
pxData: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
async created () {
|
||||||
this.layout = JSON.parse(util.cookies.get('grid-layout') || JSON.stringify(this.defaultLayout))
|
this.layout = await this.$store.dispatch('d2admin/db/get', {
|
||||||
|
dbName: 'sys',
|
||||||
|
path: 'grid-layout',
|
||||||
|
defaultValue: JSON.parse(JSON.stringify(this.defaultLayout)),
|
||||||
|
user: true
|
||||||
|
}, { root: true })
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.$emit('on-mounted')
|
this.$emit('on-mounted')
|
||||||
|
@ -130,19 +138,19 @@ export default {
|
||||||
for (var key in this.allComps) {
|
for (var key in this.allComps) {
|
||||||
allCompsList.push({
|
allCompsList.push({
|
||||||
key: key,
|
key: key,
|
||||||
|
sort: allComps[key].sort,
|
||||||
title: allComps[key].title,
|
title: allComps[key].title,
|
||||||
icon: allComps[key].icon,
|
icon: allComps[key].icon,
|
||||||
height: allComps[key].height,
|
height: allComps[key].height,
|
||||||
width: allComps[key].width,
|
width: allComps[key].width,
|
||||||
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 || {},
|
config: allComps[key].config || {},
|
||||||
isResizable: allComps[key].isResizable || null,
|
isResizable: allComps[key].isResizable || null,
|
||||||
description: allComps[key].description
|
description: allComps[key].description
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
allCompsList.sort(function (a, b) {
|
||||||
|
return (a.sort || 0) - (b.sort || 0)
|
||||||
|
})
|
||||||
return allCompsList
|
return allCompsList
|
||||||
},
|
},
|
||||||
myCompsList () {
|
myCompsList () {
|
||||||
|
@ -168,17 +176,12 @@ export default {
|
||||||
},
|
},
|
||||||
// 追加
|
// 追加
|
||||||
push (item) {
|
push (item) {
|
||||||
console.log(1, item)
|
|
||||||
this.layout.push({
|
this.layout.push({
|
||||||
i: this.getLayoutElementNumber(item.key),
|
i: this.getLayoutElementNumber(item.key),
|
||||||
x: (this.layout.length * 2) % (this.colNum || 12),
|
x: (this.layout.length * 2) % (this.colNum || 12),
|
||||||
y: this.layout.length + (this.colNum || 12),
|
y: this.layout.length + (this.colNum || 12),
|
||||||
w: item.width,
|
w: item.width,
|
||||||
h: item.height,
|
h: item.height,
|
||||||
minW: item.minW,
|
|
||||||
minH: item.minH,
|
|
||||||
maxW: item.maxW,
|
|
||||||
maxH: item.maxH,
|
|
||||||
config: item.config || {},
|
config: item.config || {},
|
||||||
isResizable: item.isResizable || null,
|
isResizable: item.isResizable || null,
|
||||||
element: item.key
|
element: item.key
|
||||||
|
@ -189,13 +192,22 @@ export default {
|
||||||
this.layout.splice(index, 1)
|
this.layout.splice(index, 1)
|
||||||
},
|
},
|
||||||
// 保存
|
// 保存
|
||||||
save () {
|
async save () {
|
||||||
console.log(this.layout)
|
console.log(this.layout)
|
||||||
this.customizing = false
|
this.customizing = false
|
||||||
this.minimize = false
|
this.minimize = false
|
||||||
this.$refs.suspendedLibrary.menu = false
|
this.$refs.suspendedLibrary.menu = false
|
||||||
this.$refs.widgets.style.removeProperty('transform')
|
this.$refs.widgets.style.removeProperty('transform')
|
||||||
util.cookies.set('grid-layout', this.layout)
|
var layout = JSON.parse(JSON.stringify(this.layout))
|
||||||
|
layout.map(val => {
|
||||||
|
delete val.pxData
|
||||||
|
})
|
||||||
|
await this.$store.dispatch('d2admin/db/set', {
|
||||||
|
dbName: 'sys',
|
||||||
|
path: 'grid-layout',
|
||||||
|
value: layout,
|
||||||
|
user: true
|
||||||
|
}, { root: true })
|
||||||
},
|
},
|
||||||
// 恢复默认
|
// 恢复默认
|
||||||
backDefault () {
|
backDefault () {
|
||||||
|
@ -204,33 +216,69 @@ export default {
|
||||||
this.$refs.suspendedLibrary.menu = false
|
this.$refs.suspendedLibrary.menu = false
|
||||||
this.$refs.widgets.style.removeProperty('transform')
|
this.$refs.widgets.style.removeProperty('transform')
|
||||||
this.layout = JSON.parse(JSON.stringify(this.defaultLayout))
|
this.layout = JSON.parse(JSON.stringify(this.defaultLayout))
|
||||||
util.cookies.remove('grid-layout')
|
// 设为默认
|
||||||
|
this.$store.dispatch('d2admin/db/set', {
|
||||||
|
dbName: 'sys',
|
||||||
|
path: 'grid-layout',
|
||||||
|
value: this.layout,
|
||||||
|
user: true
|
||||||
|
}, { root: true })
|
||||||
},
|
},
|
||||||
// 关闭
|
// 关闭
|
||||||
close () {
|
async close () {
|
||||||
this.customizing = false
|
this.customizing = false
|
||||||
this.minimize = false
|
this.minimize = false
|
||||||
this.$refs.suspendedLibrary.menu = false
|
this.$refs.suspendedLibrary.menu = false
|
||||||
this.$refs.widgets.style.removeProperty('transform')
|
this.$refs.widgets.style.removeProperty('transform')
|
||||||
|
this.layout = await this.$store.dispatch('d2admin/db/get', {
|
||||||
|
dbName: 'sys',
|
||||||
|
path: 'grid-layout',
|
||||||
|
defaultValue: JSON.stringify(this.defaultLayout),
|
||||||
|
user: true
|
||||||
|
}, { root: true })
|
||||||
|
},
|
||||||
|
// 清空画布
|
||||||
|
clickEmpty () {
|
||||||
|
this.layout = []
|
||||||
|
},
|
||||||
|
// 保存配置
|
||||||
|
saveConfig (myComp, items) {
|
||||||
|
this.layout.map(val => {
|
||||||
|
if (val.i === items.i) {
|
||||||
|
val.config = JSON.parse(JSON.stringify(items.config))
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
// 最小化
|
// 最小化
|
||||||
clickMinimize () {
|
clickMinimize () {
|
||||||
this.minimize = !this.minimize
|
this.minimize = !this.minimize
|
||||||
this.$refs.suspendedLibrary.menu = !this.$refs.suspendedLibrary.menu
|
this.$refs.suspendedLibrary.menu = !this.$refs.suspendedLibrary.menu
|
||||||
},
|
},
|
||||||
// 系统配置
|
// 打开系统配置
|
||||||
clickConfig (itme) {
|
clickConfig (itme) {
|
||||||
this.$refs.dashboardConfig.deviceUpgradeDrawer = true
|
this.$refs.dashboardConfig.deviceUpgradeDrawer = true
|
||||||
this.$refs.dashboardConfig.initData(this.allComps[itme.element], itme)
|
this.$refs.dashboardConfig.initData(this.allComps[itme.element], JSON.parse(JSON.stringify(itme)))
|
||||||
this.minimize = false
|
this.minimize = false
|
||||||
|
},
|
||||||
|
// 设置实际的宽度和高度
|
||||||
|
containerResizedEvent: function (i, newH, newW, newHPx, newWPx) {
|
||||||
|
this.layout.map(val => {
|
||||||
|
if (val.i === i) {
|
||||||
|
this.$set(this.pxData, val.i, {
|
||||||
|
hpx: Number(newHPx),
|
||||||
|
wpx: Number(newWPx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
::v-deep .d2-container-full__body{
|
::v-deep .d2-container-full__body {
|
||||||
padding: 0!important;
|
padding: 0!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widgetsListItem {
|
.widgetsListItem {
|
||||||
width: 168px;
|
width: 168px;
|
||||||
height: 75px;
|
height: 75px;
|
||||||
|
@ -294,7 +342,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.set-component-bg {
|
.set-component-bg {
|
||||||
background: rgba(255, 255, 255, 0.5);
|
//background: rgba(255, 255, 255, 0.5);
|
||||||
border: 1px solid rgba(0, 0, 0, .5);
|
border: 1px solid rgba(0, 0, 0, .5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,134 @@
|
||||||
const log = [
|
const log = [
|
||||||
{
|
{
|
||||||
i: 'dashboardImg1',
|
i: 'usersTotal0',
|
||||||
x: 9,
|
x: 0,
|
||||||
y: 21,
|
y: 0,
|
||||||
w: 8,
|
w: 12,
|
||||||
|
h: 12,
|
||||||
|
config: {
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: 'rgba(255, 255, 255, 1)',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: null,
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isResizable: true,
|
||||||
|
element: 'usersTotal',
|
||||||
|
moved: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: 'loginTotal1',
|
||||||
|
x: 12,
|
||||||
|
y: 0,
|
||||||
|
w: 12,
|
||||||
|
h: 12,
|
||||||
|
config: {
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: 'rgba(255, 255, 255, 1)',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: null,
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isResizable: true,
|
||||||
|
element: 'loginTotal',
|
||||||
|
moved: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: 'attachmentTotal2',
|
||||||
|
x: 24,
|
||||||
|
y: 0,
|
||||||
|
w: 12,
|
||||||
|
h: 12,
|
||||||
|
config: {
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: 'rgba(255, 255, 255, 1)',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: null,
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isResizable: true,
|
||||||
|
element: 'attachmentTotal',
|
||||||
|
moved: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: 'databaseTotal3',
|
||||||
|
x: 36,
|
||||||
|
y: 0,
|
||||||
|
w: 12,
|
||||||
|
h: 12,
|
||||||
|
config: {
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: 'rgba(255, 255, 255, 1)',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: null,
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isResizable: true,
|
||||||
|
element: 'databaseTotal',
|
||||||
|
moved: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: 'userLogin6',
|
||||||
|
x: 14,
|
||||||
|
y: 12,
|
||||||
|
w: 17,
|
||||||
h: 24,
|
h: 24,
|
||||||
minW: 1,
|
config: {},
|
||||||
minH: 10,
|
isResizable: true,
|
||||||
maxW: 24,
|
element: 'userLogin',
|
||||||
maxH: 100,
|
moved: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: 'registeredUser7',
|
||||||
|
x: 31,
|
||||||
|
y: 12,
|
||||||
|
w: 17,
|
||||||
|
h: 24,
|
||||||
|
config: {},
|
||||||
|
isResizable: true,
|
||||||
|
element: 'registeredUser',
|
||||||
|
moved: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: 'dashboardImg8',
|
||||||
|
x: 14,
|
||||||
|
y: 58,
|
||||||
|
w: 16,
|
||||||
|
h: 14,
|
||||||
config: {
|
config: {
|
||||||
src: {
|
src: {
|
||||||
label: '图片地址',
|
label: '图片地址',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
value: '/image/card/tencent.jpg',
|
value: 'https://kfm-waiter.oss-cn-zhangjiakou.aliyuncs.com/dvadmin/img/chajianshichang.jpg',
|
||||||
|
placeholder: '请输入图片地址',
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -24,6 +139,43 @@ const log = [
|
||||||
url: {
|
url: {
|
||||||
label: '跳转地址',
|
label: '跳转地址',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
|
placeholder: '请输入跳转地址',
|
||||||
|
value: 'https://bbs.django-vue-admin.com/plugMarket.html',
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '不能为空'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isResizable: true,
|
||||||
|
element: 'dashboardImg',
|
||||||
|
moved: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: 'dashboardImg9',
|
||||||
|
x: 0,
|
||||||
|
y: 58,
|
||||||
|
w: 14,
|
||||||
|
h: 14,
|
||||||
|
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',
|
value: 'https://cloud.tencent.com/act/cps/redirect?redirect=1060&cps_key=b302a514a6688aa30823fac954464e5d&from=console',
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
@ -38,15 +190,11 @@ const log = [
|
||||||
moved: false
|
moved: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
i: 'dashboardImg2',
|
i: 'dashboardImg10',
|
||||||
x: 9,
|
x: 30,
|
||||||
y: 0,
|
y: 58,
|
||||||
w: 15,
|
w: 18,
|
||||||
h: 21,
|
h: 14,
|
||||||
minW: 1,
|
|
||||||
minH: 10,
|
|
||||||
maxW: 24,
|
|
||||||
maxH: 100,
|
|
||||||
config: {
|
config: {
|
||||||
src: {
|
src: {
|
||||||
label: '图片地址',
|
label: '图片地址',
|
||||||
|
@ -78,110 +226,68 @@ const log = [
|
||||||
moved: false
|
moved: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
i: 'time3',
|
i: 'usersActive11',
|
||||||
x: 9,
|
x: 0,
|
||||||
y: 45,
|
y: 12,
|
||||||
w: 8,
|
w: 14,
|
||||||
h: 19,
|
h: 24,
|
||||||
minW: 4,
|
config: {
|
||||||
minH: 10,
|
color: {
|
||||||
maxW: null,
|
label: '背景颜色',
|
||||||
maxH: 100,
|
type: 'color',
|
||||||
config: {},
|
value: '',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: null,
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
isResizable: true,
|
isResizable: true,
|
||||||
element: 'time',
|
element: 'usersActive',
|
||||||
moved: false
|
moved: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
i: 'ver4',
|
i: 'ver11',
|
||||||
x: 17,
|
x: 35,
|
||||||
y: 45,
|
y: 36,
|
||||||
w: 7,
|
w: 13,
|
||||||
h: 19,
|
h: 22,
|
||||||
minW: 4,
|
config: {
|
||||||
minH: 10,
|
showHeader: {
|
||||||
maxW: null,
|
label: '显示头部信息',
|
||||||
maxH: 100,
|
type: 'boot',
|
||||||
config: {},
|
value: true,
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
label: '背景颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: 'rgba(255, 255, 255, 1)',
|
||||||
|
placeholder: '颜色为空则随机变换颜色'
|
||||||
|
},
|
||||||
|
fontColor: {
|
||||||
|
label: '字体颜色',
|
||||||
|
type: 'color',
|
||||||
|
value: null,
|
||||||
|
placeholder: '请选择字体颜色'
|
||||||
|
}
|
||||||
|
},
|
||||||
isResizable: true,
|
isResizable: true,
|
||||||
element: 'ver',
|
element: 'ver',
|
||||||
moved: false
|
moved: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
i: 'about5',
|
i: 'loginRegion12',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 45,
|
y: 36,
|
||||||
w: 9,
|
w: 35,
|
||||||
h: 19,
|
h: 22,
|
||||||
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: {},
|
config: {},
|
||||||
isResizable: true,
|
isResizable: true,
|
||||||
element: 'welcome',
|
element: 'loginRegion',
|
||||||
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
|
moved: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -331,6 +331,7 @@ export default {
|
||||||
const form = JSON.parse(JSON.stringify(this.form))
|
const form = JSON.parse(JSON.stringify(this.form))
|
||||||
const keys = Object.keys(form)
|
const keys = Object.keys(form)
|
||||||
const values = Object.values(form)
|
const values = Object.values(form)
|
||||||
|
let submitForm = Object.assign([],this.formList)
|
||||||
for (const index in this.formList) {
|
for (const index in this.formList) {
|
||||||
const item = this.formList[index]
|
const item = this.formList[index]
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
|
@ -347,7 +348,7 @@ export default {
|
||||||
child.parent = parentId
|
child.parent = parentId
|
||||||
child.id = null
|
child.id = null
|
||||||
}
|
}
|
||||||
this.formList.push(child)
|
submitForm.push(child)
|
||||||
}
|
}
|
||||||
// 必填项的判断
|
// 必填项的判断
|
||||||
for (const arr of item.rule) {
|
for (const arr of item.rule) {
|
||||||
|
@ -380,7 +381,7 @@ export default {
|
||||||
that.$refs.form.validate((valid) => {
|
that.$refs.form.validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
api.saveContent(this.options.id,
|
api.saveContent(this.options.id,
|
||||||
this.formList).then(res => {
|
submitForm).then(res => {
|
||||||
this.$message.success('保存成功')
|
this.$message.success('保存成功')
|
||||||
this.refreshView()
|
this.refreshView()
|
||||||
})
|
})
|
||||||
|
@ -397,14 +398,13 @@ export default {
|
||||||
const tableLength = tableData.length
|
const tableLength = tableData.length
|
||||||
if (tableLength === 0) {
|
if (tableLength === 0) {
|
||||||
const { row } = $table.insert()
|
const { row } = $table.insert()
|
||||||
console.log(row)
|
|
||||||
} else {
|
} else {
|
||||||
const errMap = await $table.validate().catch(errMap => errMap)
|
const errMap = await $table.validate().catch(errMap => errMap)
|
||||||
if (errMap) {
|
if (errMap) {
|
||||||
this.$message.error('校验不通过!')
|
this.$message.error('校验不通过!')
|
||||||
} else {
|
} else {
|
||||||
const { row } = $table.insert()
|
const { row } = $table.insert()
|
||||||
console.log(row)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -450,7 +450,7 @@ export default {
|
||||||
if (!uploadImgKey || uploadImgKey === '') {
|
if (!uploadImgKey || uploadImgKey === '') {
|
||||||
that.form[imgKey] = []
|
that.form[imgKey] = []
|
||||||
}
|
}
|
||||||
// console.log(len)
|
|
||||||
const dict = {
|
const dict = {
|
||||||
name: name,
|
name: name,
|
||||||
url: util.baseURL() + url
|
url: util.baseURL() + url
|
||||||
|
|
|
@ -239,7 +239,13 @@ export const crudOptions = (vm) => {
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
dict: {
|
dict: {
|
||||||
data: [{ label: '普通登录', value: 1 }, { label: '微信扫码登录', value: 2 }]
|
data: [
|
||||||
|
{ label: '普通登录', value: 1 },
|
||||||
|
{ label: '普通扫码登录', value: 2 },
|
||||||
|
{ label: '微信扫码登录', value: 3 },
|
||||||
|
{ label: '飞书扫码登录', value: 4 },
|
||||||
|
{ label: '钉钉扫码登录', value: 5 },
|
||||||
|
{ label: '短信登录', value: 6 }]
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
component: {
|
component: {
|
||||||
|
|
|
@ -107,7 +107,8 @@ export default {
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 重定向对象不存在则返回顶层路径
|
// 重定向对象不存在则返回顶层路径
|
||||||
this.$router.replace(this.$route.query.redirect || '/')
|
// this.$router.replace(this.$route.query.redirect || '/')
|
||||||
|
this.$router.replace('/')
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.getCaptcha()
|
this.getCaptcha()
|
||||||
|
|
Loading…
Reference in New Issue