!58 正式发布v2.0.2版本

1. 新增:新版登录页
2. 新增:用户管理中用户类型
3. 新增:系统配置功能
4. 新增:字典管理功能
5. 新增:登录页获取初始化配置
6. 新增:初始化配置验证码是否开启
7. 新增:一键删除迁移文件的脚本
8. 优化: 文件及图片上传全局配置优化
9. 优化: 配置用户头像为剪切图片方式
10. 优化:docker-compose.yml 无法启动配置
11. 优化:后台数据初始化功能
12. 优化:用户管理中性别类型(添加未知类型)
13. 优化:创建用户时的初始化密码
14. 优化:角色权限点击子级半勾选父级
15. 优化:系统数据库table表名
16. 优化:前端部门选择,支持懒加载
17. 优化:获取ip详细地址时,添加请求超时时间及请求方式更改为get方式
18. 修复:CharFilter字段精确查询无效
19. 修复:find_filter_lookups条件搜索错误 bug
20. 修复:import_export 导入bug
21. 修复:切换[是否目录]时,没有清除组件名称、组件路由信息bug
pull/59/MERGE v2.0.2
dvadmin 2022-05-23 15:40:52 +00:00 committed by Gitee
commit 3d1f5225c2
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
112 changed files with 20216 additions and 3446 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
/backend/venv /backend/venv
/backend/.idea /backend/.idea
.idea .idea
.history/
.vscode/

5
backend/.gitignore vendored
View File

@ -88,11 +88,12 @@ ENV/
.idea/ .idea/
*.db *.db
.DS_Store .DS_Store
__pycache__ **/migrations/*.py
**/migrations
!**/migrations/__init__.py !**/migrations/__init__.py
*.pyc *.pyc
conf/ conf/
!conf/env.example.py !conf/env.example.py
db.sqlite3 db.sqlite3
media/ media/
__pypackages__/
package-lock.json

View File

@ -12,5 +12,6 @@ import os
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
application = get_asgi_application() application = get_asgi_application()

View File

@ -4,9 +4,10 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
from django.conf import settings from django.conf import settings
from celery import platforms from celery import platforms
# 租户模式
if getattr(settings, 'REGISTER_PLUGINS', {}).get('dvadmin_tenant', None): if "django_tenants" in settings.INSTALLED_APPS:
from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
app = TenantAwareCeleryApp() app = TenantAwareCeleryApp()
else: else:
from celery import Celery from celery import Celery

View File

@ -0,0 +1,196 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django.conf import settings
from django.db import ProgrammingError
from django.db import connection
def is_tenants_mode():
"""
判断是否为租户模式
:return:
"""
return hasattr(connection, 'tenant') and connection.tenant.schema_name
# ================================================= #
# ******************** 初始化 ******************** #
# ================================================= #
def _get_all_dictionary():
from dvadmin.system.models import Dictionary
queryset = Dictionary.objects.filter(status=True, is_value=False)
data = []
for instance in queryset:
data.append({
"id": instance.id,
"value": instance.value,
"children": list(Dictionary.objects.filter(parent=instance.id).filter(status=1).
values('label', 'value', 'type', 'color'))
})
return {ele.get("value"): ele for ele in data}
def _get_all_system_config():
data = {}
from dvadmin.system.models import SystemConfig
system_config_obj = SystemConfig.objects.filter(status=True, parent_id__isnull=False).values(
'parent__key', 'key', 'value', 'form_item_type').order_by('sort')
for system_config in system_config_obj:
value = system_config.get('value') or ''
if value and system_config.get('form_item_type') == 7:
value = value[0].get('url')
data[f"{system_config.get('parent__key')}.{system_config.get('key')}"] = value
return data
def init_dictionary():
"""
初始化字典配置
:return:
"""
try:
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
for tenant in get_tenant_model().objects.filter():
with tenant_context(tenant):
settings.DICTIONARY_CONFIG[connection.tenant.schema_name] = _get_all_dictionary()
else:
settings.DICTIONARY_CONFIG = _get_all_dictionary()
print("初始化字典配置完成")
except Exception as e:
print("请先进行数据库迁移!")
return
def init_system_config():
"""
初始化系统配置
:param name:
:return:
"""
try:
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
for tenant in get_tenant_model().objects.filter():
with tenant_context(tenant):
settings.SYSTEM_CONFIG[connection.tenant.schema_name] = _get_all_system_config()
else:
settings.SYSTEM_CONFIG = _get_all_system_config()
print("初始化系统配置完成")
except Exception as e:
print("请先进行数据库迁移!")
return
def refresh_dictionary():
"""
刷新字典配置
:return:
"""
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
for tenant in get_tenant_model().objects.filter():
with tenant_context(tenant):
settings.DICTIONARY_CONFIG[connection.tenant.schema_name] = _get_all_dictionary()
else:
settings.DICTIONARY_CONFIG = _get_all_dictionary()
def refresh_system_config():
"""
刷新系统配置
:return:
"""
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
for tenant in get_tenant_model().objects.filter():
with tenant_context(tenant):
settings.SYSTEM_CONFIG[connection.tenant.schema_name] = _get_all_system_config()
else:
settings.SYSTEM_CONFIG = _get_all_system_config()
# ================================================= #
# ******************** 字典管理 ******************** #
# ================================================= #
def get_dictionary_config(schema_name=None):
"""
获取字典所有配置
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
if is_tenants_mode():
dictionary_config = settings.DICTIONARY_CONFIG[schema_name or connection.tenant.schema_name]
else:
dictionary_config = settings.DICTIONARY_CONFIG
return dictionary_config or {}
def get_dictionary_values(key, schema_name=None):
"""
获取字典数据数组
:param key: 对应字典配置的key值(字典编号)
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
dictionary_config = get_dictionary_config(schema_name)
return dictionary_config.get(key)
def get_dictionary_label(key, name, schema_name=None):
"""
获取获取字典label值
:param key: 字典管理中的key值(字典编号)
:param name: 对应字典配置的value值
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
children = get_dictionary_values(key, schema_name) or []
for ele in children:
if ele.get("value") == str(name):
return ele.get("label")
return ""
# ================================================= #
# ******************** 系统配置 ******************** #
# ================================================= #
def get_system_config(schema_name=None):
"""
获取系统配置中所有配置
1.只传父级的key返回全部子级{ "父级key.子级key" : "" }
2."父级key.子级key"返回子级值
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
if is_tenants_mode():
dictionary_config = settings.SYSTEM_CONFIG[schema_name or connection.tenant.schema_name]
else:
dictionary_config = settings.SYSTEM_CONFIG
return dictionary_config or {}
def get_system_config_values(key, schema_name=None):
"""
获取系统配置数据数组
:param key: 对应系统配置的key值(字典编号)
:param schema_name: 对应系统配置的租户schema_name值
:return:
"""
system_config = get_system_config(schema_name)
return system_config.get(key)
def get_system_config_label(key, name, schema_name=None):
"""
获取获取系统配置label值
:param key: 系统配置中的key值(字典编号)
:param name: 对应系统配置的value值
:param schema_name: 对应系统配置的租户schema_name值
:return:
"""
children = get_system_config_values(key, schema_name) or []
for ele in children:
if ele.get("value") == str(name):
return ele.get("label")
return ""

View File

@ -27,106 +27,111 @@ from conf.env import *
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure--z8%exyzt7e_%i@1+#1mm=%lb5=^fx_57=1@a+_y7bg5-w%)sm' SECRET_KEY = "django-insecure--z8%exyzt7e_%i@1+#1mm=%lb5=^fx_57=1@a+_y7bg5-w%)sm"
# 初始化plugins插件路径到环境变量中 # 初始化plugins插件路径到环境变量中
PLUGINS_PATH = os.path.join(BASE_DIR, 'plugins') PLUGINS_PATH = os.path.join(BASE_DIR, "plugins")
[sys.path.insert(0, os.path.join(PLUGINS_PATH, ele)) for ele in os.listdir(PLUGINS_PATH) if sys.path.insert(0, os.path.join(PLUGINS_PATH))
os.path.isdir(os.path.join(PLUGINS_PATH, ele)) and not ele.startswith('__')]
[
sys.path.insert(0, os.path.join(PLUGINS_PATH, ele))
for ele in os.listdir(PLUGINS_PATH)
if os.path.isdir(os.path.join(PLUGINS_PATH, ele)) and not ele.startswith("__")
]
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = locals().get('DEBUG', True) DEBUG = locals().get("DEBUG", True)
ALLOWED_HOSTS = locals().get('ALLOWED_HOSTS', ['*']) ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'django_comment_migrate', "django_comment_migrate",
'rest_framework', "rest_framework",
'django_filters', "django_filters",
'corsheaders', # 注册跨域app "corsheaders", # 注册跨域app
'dvadmin.system', "dvadmin.system",
'drf_yasg', "drf_yasg",
'captcha', "captcha",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'whitenoise.middleware.WhiteNoiseMiddleware', "whitenoise.middleware.WhiteNoiseMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'corsheaders.middleware.CorsMiddleware', # 跨域中间件 "corsheaders.middleware.CorsMiddleware", # 跨域中间件
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
'dvadmin.utils.middleware.ApiLoggingMiddleware', "dvadmin.utils.middleware.ApiLoggingMiddleware",
] ]
ROOT_URLCONF = 'application.urls' ROOT_URLCONF = "application.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [os.path.join(BASE_DIR, 'templates')], "DIRS": [os.path.join(BASE_DIR, "templates")],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'application.wsgi.application' WSGI_APPLICATION = "application.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases # https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': DATABASE_ENGINE, "ENGINE": DATABASE_ENGINE,
'NAME': DATABASE_NAME, "NAME": DATABASE_NAME,
'USER': DATABASE_USER, "USER": DATABASE_USER,
'PASSWORD': DATABASE_PASSWORD, "PASSWORD": DATABASE_PASSWORD,
'HOST': DATABASE_HOST, "HOST": DATABASE_HOST,
'PORT': DATABASE_PORT, "PORT": DATABASE_PORT,
} }
} }
AUTH_USER_MODEL = 'system.Users' AUTH_USER_MODEL = "system.Users"
USERNAME_FIELD = 'username' USERNAME_FIELD = "username"
# Password validation # Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
}, },
] ]
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/ # https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'zh-hans' LANGUAGE_CODE = "zh-hans"
TIME_ZONE = 'Asia/Shanghai' TIME_ZONE = "Asia/Shanghai"
USE_I18N = True USE_I18N = True
@ -137,13 +142,13 @@ USE_TZ = False
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/ # https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = "/static/"
# # 设置django的静态文件目录 # # 设置django的静态文件目录
STATICFILES_DIRS = [ STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), os.path.join(BASE_DIR, "static"),
] ]
MEDIA_ROOT = 'media' # 项目下的目录 MEDIA_ROOT = "media" # 项目下的目录
MEDIA_URL = "/media/" # 跟STATIC_URL类似指定用户可以通过这个url找到文件 MEDIA_URL = "/media/" # 跟STATIC_URL类似指定用户可以通过这个url找到文件
# 收集静态文件,必须将 MEDIA_ROOT,STATICFILES_DIRS先注释 # 收集静态文件,必须将 MEDIA_ROOT,STATICFILES_DIRS先注释
@ -164,78 +169,82 @@ CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中,后端是否支持
# ================================================= # # ================================================= #
# log 配置部分BEGIN # # log 配置部分BEGIN #
SERVER_LOGS_FILE = os.path.join(BASE_DIR, 'logs', 'server.log') SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
ERROR_LOGS_FILE = os.path.join(BASE_DIR, 'logs', 'error.log') ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
if not os.path.exists(os.path.join(BASE_DIR, 'logs')): if not os.path.exists(os.path.join(BASE_DIR, "logs")):
os.makedirs(os.path.join(BASE_DIR, 'logs')) os.makedirs(os.path.join(BASE_DIR, "logs"))
# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志: # 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
# 格式:[日期][模块.函数名称():行号] [级别] 信息 # 格式:[日期][模块.函数名称():行号] [级别] 信息
STANDARD_LOG_FORMAT = '[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s' STANDARD_LOG_FORMAT = (
CONSOLE_LOG_FORMAT = '[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s' "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
CONSOLE_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
LOGGING = { LOGGING = {
'version': 1, "version": 1,
'disable_existing_loggers': False, "disable_existing_loggers": False,
'formatters': { "formatters": {
'standard': { "standard": {"format": STANDARD_LOG_FORMAT},
'format': STANDARD_LOG_FORMAT "console": {
"format": CONSOLE_LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
}, },
'console': { "file": {
'format': CONSOLE_LOG_FORMAT, "format": CONSOLE_LOG_FORMAT,
'datefmt': '%Y-%m-%d %H:%M:%S', "datefmt": "%Y-%m-%d %H:%M:%S",
},
'file': {
'format': CONSOLE_LOG_FORMAT,
'datefmt': '%Y-%m-%d %H:%M:%S',
}, },
}, },
'handlers': { "handlers": {
'file': { "file": {
'level': 'INFO', "level": "INFO",
'class': 'logging.handlers.RotatingFileHandler', "class": "logging.handlers.RotatingFileHandler",
'filename': SERVER_LOGS_FILE, "filename": SERVER_LOGS_FILE,
'maxBytes': 1024 * 1024 * 100, # 100 MB "maxBytes": 1024 * 1024 * 100, # 100 MB
'backupCount': 5, # 最多备份5个 "backupCount": 5, # 最多备份5个
'formatter': 'standard', "formatter": "standard",
'encoding': 'utf-8', "encoding": "utf-8",
}, },
'error': { "error": {
'level': 'ERROR', "level": "ERROR",
'class': 'logging.handlers.RotatingFileHandler', "class": "logging.handlers.RotatingFileHandler",
'filename': ERROR_LOGS_FILE, "filename": ERROR_LOGS_FILE,
'maxBytes': 1024 * 1024 * 100, # 100 MB "maxBytes": 1024 * 1024 * 100, # 100 MB
'backupCount': 3, # 最多备份3个 "backupCount": 3, # 最多备份3个
'formatter': 'standard', "formatter": "standard",
'encoding': 'utf-8', "encoding": "utf-8",
}, },
'console': { "console": {
'level': 'INFO', "level": "INFO",
'class': 'logging.StreamHandler', "class": "logging.StreamHandler",
'formatter': 'console', "formatter": "console",
}
}, },
'loggers': { },
"loggers": {
# default日志 # default日志
'': { "": {
'handlers': ['console', 'error', 'file'], "handlers": ["console", "error", "file"],
'level': 'INFO', "level": "INFO",
}, },
'django': { "django": {
'handlers': ['console', 'error', 'file'], "handlers": ["console", "error", "file"],
'level': 'INFO', "level": "INFO",
"propagate": False,
}, },
'scripts': { "scripts": {
'handlers': ['console', 'error', 'file'], "handlers": ["console", "error", "file"],
'level': 'INFO', "level": "INFO",
"propagate": False,
}, },
# 数据库相关日志 # 数据库相关日志
'django.db.backends': { "django.db.backends": {
'handlers': [], "handlers": [],
'propagate': True, "propagate": True,
'level': 'INFO', "level": "INFO",
},
}, },
}
} }
# ================================================= # # ================================================= #
@ -243,34 +252,32 @@ LOGGING = {
# ================================================= # # ================================================= #
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S", # 日期时间格式配置 "DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S", # 日期时间格式配置
'DATE_FORMAT': "%Y-%m-%d", "DATE_FORMAT": "%Y-%m-%d",
'DEFAULT_FILTER_BACKENDS': ( "DEFAULT_FILTER_BACKENDS": (
# 'django_filters.rest_framework.DjangoFilterBackend', # 'django_filters.rest_framework.DjangoFilterBackend',
'dvadmin.utils.filters.CustomDjangoFilterBackend', "dvadmin.utils.filters.CustomDjangoFilterBackend",
'rest_framework.filters.SearchFilter', "rest_framework.filters.SearchFilter",
'rest_framework.filters.OrderingFilter', "rest_framework.filters.OrderingFilter",
), ),
'DEFAULT_PAGINATION_CLASS': 'dvadmin.utils.pagination.CustomPagination', # 自定义分页 "DEFAULT_PAGINATION_CLASS": "dvadmin.utils.pagination.CustomPagination", # 自定义分页
'DEFAULT_AUTHENTICATION_CLASSES': ( "DEFAULT_AUTHENTICATION_CLASSES": (
'rest_framework_simplejwt.authentication.JWTAuthentication', "rest_framework_simplejwt.authentication.JWTAuthentication",
'rest_framework.authentication.SessionAuthentication', "rest_framework.authentication.SessionAuthentication",
), ),
'DEFAULT_PERMISSION_CLASSES': [ "DEFAULT_PERMISSION_CLASSES": [
'rest_framework.permissions.IsAuthenticated', # 只有经过身份认证确定用户身份才能访问 "rest_framework.permissions.IsAuthenticated", # 只有经过身份认证确定用户身份才能访问
# 'rest_framework.permissions.IsAdminUser', # is_staff=True才能访问 —— 管理员(员工)权限 # 'rest_framework.permissions.IsAdminUser', # is_staff=True才能访问 —— 管理员(员工)权限
# 'rest_framework.permissions.AllowAny', # 允许所有 # 'rest_framework.permissions.AllowAny', # 允许所有
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 有身份 或者 只读访问(self.list,self.retrieve) # 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 有身份 或者 只读访问(self.list,self.retrieve)
], ],
'EXCEPTION_HANDLER': 'dvadmin.utils.exception.CustomExceptionHandler', # 自定义的异常处理 "EXCEPTION_HANDLER": "dvadmin.utils.exception.CustomExceptionHandler", # 自定义的异常处理
} }
# ================================================= # # ================================================= #
# ******************** 登录方式配置 ******************** # # ******************** 登录方式配置 ******************** #
# ================================================= # # ================================================= #
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = ["dvadmin.utils.backends.CustomBackend"]
'dvadmin.utils.backends.CustomBackend'
]
# ================================================= # # ================================================= #
# ****************** simplejwt配置 ***************** # # ****************** simplejwt配置 ***************** #
# ================================================= # # ================================================= #
@ -278,12 +285,12 @@ from datetime import timedelta
SIMPLE_JWT = { SIMPLE_JWT = {
# token有效时长 # token有效时长
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=120), "ACCESS_TOKEN_LIFETIME": timedelta(minutes=120),
# token刷新后的有效时间 # token刷新后的有效时间
'REFRESH_TOKEN_LIFETIME': timedelta(days=1), "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
# 设置前缀 # 设置前缀
'AUTH_HEADER_TYPES': ('JWT',), "AUTH_HEADER_TYPES": ("JWT",),
'ROTATE_REFRESH_TOKENS': True, "ROTATE_REFRESH_TOKENS": True,
} }
# ====================================# # ====================================#
@ -291,72 +298,84 @@ SIMPLE_JWT = {
# ====================================# # ====================================#
SWAGGER_SETTINGS = { SWAGGER_SETTINGS = {
# 基础样式 # 基础样式
'SECURITY_DEFINITIONS': { "SECURITY_DEFINITIONS": {"basic": {"type": "basic"}},
"basic": {
'type': 'basic'
}
},
# 如果需要登录才能够查看接口文档, 登录的链接使用restframework自带的. # 如果需要登录才能够查看接口文档, 登录的链接使用restframework自带的.
"LOGIN_URL": "apiLogin/",
'LOGIN_URL': 'apiLogin/',
# 'LOGIN_URL': 'rest_framework:login', # 'LOGIN_URL': 'rest_framework:login',
'LOGOUT_URL': 'rest_framework:logout', "LOGOUT_URL": "rest_framework:logout",
# 'DOC_EXPANSION': None, # 'DOC_EXPANSION': None,
# 'SHOW_REQUEST_HEADERS':True, # 'SHOW_REQUEST_HEADERS':True,
# 'USE_SESSION_AUTH': True, # 'USE_SESSION_AUTH': True,
# 'DOC_EXPANSION': 'list', # 'DOC_EXPANSION': 'list',
# 接口文档中方法列表以首字母升序排列 # 接口文档中方法列表以首字母升序排列
'APIS_SORTER': 'alpha', "APIS_SORTER": "alpha",
# 如果支持json提交, 则接口文档中包含json输入框 # 如果支持json提交, 则接口文档中包含json输入框
'JSON_EDITOR': True, "JSON_EDITOR": True,
# 方法列表字母排序 # 方法列表字母排序
'OPERATIONS_SORTER': 'alpha', "OPERATIONS_SORTER": "alpha",
'VALIDATOR_URL': None, "VALIDATOR_URL": None,
'AUTO_SCHEMA_TYPE': 2, # 分组根据url层级分0、1 或 2 层 "AUTO_SCHEMA_TYPE": 2, # 分组根据url层级分0、1 或 2 层
'DEFAULT_AUTO_SCHEMA_CLASS': 'dvadmin.utils.swagger.CustomSwaggerAutoSchema', "DEFAULT_AUTO_SCHEMA_CLASS": "dvadmin.utils.swagger.CustomSwaggerAutoSchema",
} }
# ================================================= # # ================================================= #
# **************** 验证码配置 ******************* # # **************** 验证码配置 ******************* #
# ================================================= # # ================================================= #
CAPTCHA_STATE = True
CAPTCHA_IMAGE_SIZE = (160, 60) # 设置 captcha 图片大小 CAPTCHA_IMAGE_SIZE = (160, 60) # 设置 captcha 图片大小
CAPTCHA_LENGTH = 4 # 字符个数 CAPTCHA_LENGTH = 4 # 字符个数
CAPTCHA_TIMEOUT = 1 # 超时(minutes) CAPTCHA_TIMEOUT = 1 # 超时(minutes)
CAPTCHA_OUTPUT_FORMAT = '%(image)s %(text_field)s %(hidden_field)s ' CAPTCHA_OUTPUT_FORMAT = "%(image)s %(text_field)s %(hidden_field)s "
CAPTCHA_FONT_SIZE = 40 # 字体大小 CAPTCHA_FONT_SIZE = 40 # 字体大小
CAPTCHA_FOREGROUND_COLOR = '#0033FF' # 前景色 CAPTCHA_FOREGROUND_COLOR = "#64DAAA" # 前景色
CAPTCHA_BACKGROUND_COLOR = '#F5F7F4' # 背景色 CAPTCHA_BACKGROUND_COLOR = "#F5F7F4" # 背景色
CAPTCHA_NOISE_FUNCTIONS = ( CAPTCHA_NOISE_FUNCTIONS = (
'captcha.helpers.noise_arcs', # 线 "captcha.helpers.noise_arcs", # 线
'captcha.helpers.noise_dots', # 点 # "captcha.helpers.noise_dots", # 点
) )
# CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' #字母验证码 # CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' #字母验证码
CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge' # 加减乘除验证码 CAPTCHA_CHALLENGE_FUNCT = "captcha.helpers.math_challenge" # 加减乘除验证码
# ================================================= # # ================================================= #
# ******************** 其他配置 ******************** # # ******************** 其他配置 ******************** #
# ================================================= # # ================================================= #
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
API_LOG_ENABLE = True API_LOG_ENABLE = True
# API_LOG_METHODS = 'ALL' # ['POST', 'DELETE'] # API_LOG_METHODS = 'ALL' # ['POST', 'DELETE']
API_LOG_METHODS = ['POST', 'UPDATE', 'DELETE', 'PUT'] # ['POST', 'DELETE'] API_LOG_METHODS = ["POST", "UPDATE", "DELETE", "PUT"] # ['POST', 'DELETE']
API_MODEL_MAP = { API_MODEL_MAP = {
"/token/": "登录模块", "/token/": "登录模块",
"/api/login/": "登录模块", "/api/login/": "登录模块",
"/api/plugins_market/plugins/": "插件市场", "/api/plugins_market/plugins/": "插件市场",
} }
# 表前缀
TABLE_PREFIX = "dvadmin_"
DJANGO_CELERY_BEAT_TZ_AWARE = False DJANGO_CELERY_BEAT_TZ_AWARE = False
CELERY_TIMEZONE = 'Asia/Shanghai' # celery 时区问题 CELERY_TIMEZONE = "Asia/Shanghai" # celery 时区问题
# 静态页面压缩 # 静态页面压缩
STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage' STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage"
# 初始化需要执行的列表,用来初始化后执行
INITIALIZE_RESET_LIST = []
ALL_MODELS_OBJECTS = [] # 所有app models 对象 ALL_MODELS_OBJECTS = [] # 所有app models 对象
# dvadmin 插件
REGISTER_PLUGINS = ( # 初始化需要执行的列表,用来初始化后执行
# "" INITIALIZE_LIST = []
) INITIALIZE_RESET_LIST = []
# 表前缀
TABLE_PREFIX = locals().get('TABLE_PREFIX', "")
# 系统配置
SYSTEM_CONFIG = {}
# 字典配置
DICTIONARY_CONFIG = {}
# ================================================= #
# ******************** 插件配置 ******************** #
# ================================================= #
# 租户共享app
TENANT_SHARED_APPS = []
# 插件 urlpatterns
PLUGINS_URL_PATTERNS = []
# ********** 一键导入插件配置开始 **********
# 例如:
# from dvadmin_upgrade_center.settings import * # 升级中心
# from dvadmin_celery.settings import * # celery 异步任务
# ...
# ********** 一键导入插件配置结束 **********

View File

@ -22,14 +22,27 @@ from rest_framework_simplejwt.views import (
TokenRefreshView, TokenRefreshView,
) )
from application import dispatch
from application import settings from application import settings
from dvadmin.system.views.login import LoginView, CaptchaView, ApiLogin, LogoutView from dvadmin.system.views.dictionary import InitDictionaryViewSet
from dvadmin.system.views.login import (
LoginView,
CaptchaView,
ApiLogin,
LogoutView,
)
from dvadmin.system.views.system_config import InitSettingsViewSet
from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator
# =========== 初始化系统配置 =================
dispatch.init_system_config()
dispatch.init_dictionary()
# =========== 初始化系统配置 =================
schema_view = get_schema_view( schema_view = get_schema_view(
openapi.Info( openapi.Info(
title="Snippets API", title="Snippets API",
default_version='v1', default_version="v1",
description="Test description", description="Test description",
terms_of_service="https://www.google.com/policies/terms/", terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="contact@snippets.local"), contact=openapi.Contact(email="contact@snippets.local"),
@ -38,20 +51,38 @@ schema_view = get_schema_view(
public=True, public=True,
permission_classes=(permissions.AllowAny,), permission_classes=(permissions.AllowAny,),
generator_class=CustomOpenAPISchemaGenerator, generator_class=CustomOpenAPISchemaGenerator,
) )
urlpatterns = [ urlpatterns = (
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), [
name='schema-json'), re_path(
path('', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), r"^swagger(?P<format>\.json|\.yaml)$",
path(r'redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), schema_view.without_ui(cache_timeout=0),
path('api/system/', include('dvadmin.system.urls')), name="schema-json",
path('api/login/', LoginView.as_view(), name='token_obtain_pair'), ),
path('api/logout/', LogoutView.as_view(), name='token_obtain_pair'), path(
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), "",
re_path(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), schema_view.with_ui("swagger", cache_timeout=0),
path('api/captcha/', CaptchaView.as_view()), name="schema-swagger-ui",
path('apiLogin/', ApiLogin.as_view()), ),
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + static(settings.STATIC_URL, path(
document_root=settings.STATIC_URL) r"redoc/",
schema_view.with_ui("redoc", cache_timeout=0),
name="schema-redoc",
),
path("api/system/", include("dvadmin.system.urls")),
path("api/login/", LoginView.as_view(), name="token_obtain_pair"),
path("api/logout/", LogoutView.as_view(), name="token_obtain_pair"),
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
re_path(
r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")
),
path("api/captcha/", CaptchaView.as_view()),
path("api/init/dictionary/", InitDictionaryViewSet.as_view()),
path("api/init/settings/", InitSettingsViewSet.as_view()),
path("apiLogin/", ApiLogin.as_view()),
]
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL)
+ [re_path(ele.get('re_path'), include(ele.get('include'))) for ele in settings.PLUGINS_URL_PATTERNS]
)

View File

@ -12,5 +12,6 @@ import os
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
application = get_wsgi_application() application = get_wsgi_application()

View File

@ -3,17 +3,17 @@ import os
from application.settings import BASE_DIR from application.settings import BASE_DIR
# ================================================= # # ================================================= #
# ************** 数据库 配置 ************** # # *************** mysql数据库 配置 *************** #
# ================================================= # # ================================================= #
# 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库 # 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库
# sqlite3 设置 # sqlite3 设置
DATABASE_ENGINE = "django.db.backends.sqlite3" DATABASE_ENGINE = "django.db.backends.sqlite3"
DATABASE_NAME = os.path.join(BASE_DIR, 'db.sqlite3') DATABASE_NAME = os.path.join(BASE_DIR, "db.sqlite3")
# 使用mysql时改为此配置 # 使用mysql时改为此配置
# DATABASE_ENGINE = "django.db.backends.mysql" # DATABASE_ENGINE = "django.db.backends.mysql"
# DATABASE_NAME = 'django-vue-admin' # mysql 时使用 # DATABASE_NAME = 'django-vue-admin' # mysql 时使用
# 数据库地址 改为自己数据库地址 # 数据库地址 改为自己数据库地址
DATABASE_HOST = "127.0.0.1" DATABASE_HOST = "127.0.0.1"
# # 数据库端口 # # 数据库端口
@ -23,16 +23,24 @@ DATABASE_USER = "root"
# # 数据库密码 # # 数据库密码
DATABASE_PASSWORD = "123456" DATABASE_PASSWORD = "123456"
# 表前缀
TABLE_PREFIX = "dvadmin_"
# ================================================= # # ================================================= #
# ************** redis配置无redis 可不进行配置 ************** # # ******** redis配置无redis 可不进行配置 ******** #
# ================================================= # # ================================================= #
# REDIS_PASSWORD = '' # REDIS_PASSWORD = ''
# REDIS_HOST = '127.0.0.1' # REDIS_HOST = '127.0.0.1'
# REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6380' # REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6380'
# ================================================= # # ================================================= #
# ************** 其他 配置 ************** # # ****************** 功能 启停 ******************* #
# ================================================= # # ================================================= #
DEBUG = True # 线上环境请设置为True DEBUG = True
# 启动登录详细概略获取(通过调用api获取ip详细地址。如果是内网关闭即可)
ENABLE_LOGIN_ANALYSIS_LOG = True
# 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
LOGIN_NO_CAPTCHA_AUTH = True
# ================================================= #
# ****************** 其他 配置 ******************* #
# ================================================= #
ALLOWED_HOSTS = ["*"] ALLOWED_HOSTS = ["*"]
LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址)

15
backend/del_migrations.py Normal file
View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
import os
exclude = ["venv"] # 需要排除的文件目录
for root, dirs, files in os.walk('.'):
dirs[:] = [d for d in set(dirs) - set(exclude)]
if 'migrations' in dirs:
dir = dirs[dirs.index('migrations')]
for root_j, dirs_j, files_j in os.walk(os.path.join(root, dir)):
for file_k in files_j:
if file_k != '__init__.py':
dst_file = os.path.join(root_j, file_k)
print('删除文件>>> ', dst_file)
os.remove(dst_file)

View File

@ -0,0 +1,7 @@
[
{
"url": "/api/system/dept_lazy_tree/",
"method": 0,
"enable_datasource": true
}
]

View File

@ -0,0 +1,33 @@
[
{
"name": "DVAdmin团队",
"sort": 1,
"owner": "",
"phone": "",
"email": "",
"status": true,
"parent": null,
"children": [
{
"name": "运营部",
"sort": 2,
"owner": "",
"phone": "",
"email": "",
"status": true,
"parent": 1,
"children": []
},
{
"name": "技术部",
"sort": 1,
"owner": "",
"phone": "",
"email": "",
"status": true,
"parent": 3,
"children": []
}
]
}
]

View File

@ -0,0 +1,550 @@
[
{
"label": "启用/禁用-布尔值",
"value": "button_status_bool",
"parent": null,
"type": 0,
"color": null,
"is_value": false,
"status": true,
"sort": 1,
"remark": null,
"children": [
{
"label": "启用",
"value": "true",
"parent": 1,
"type": 6,
"color": null,
"is_value": true,
"status": true,
"sort": 1,
"remark": null,
"children": []
},
{
"label": "禁用",
"value": "false",
"parent": 1,
"type": 6,
"color": null,
"is_value": true,
"status": true,
"sort": 2,
"remark": null,
"children": []
}
]
},
{
"label": "系统按钮",
"value": "system_button",
"parent": null,
"type": 0,
"color": null,
"is_value": false,
"status": true,
"sort": 2,
"remark": null,
"children": [
{
"label": "新增",
"value": "Create",
"parent": 66,
"type": 0,
"color": "success",
"is_value": true,
"status": true,
"sort": 1,
"remark": null,
"children": []
},
{
"label": "编辑",
"value": "Update",
"parent": 66,
"type": 0,
"color": "primary",
"is_value": true,
"status": true,
"sort": 2,
"remark": null,
"children": []
},
{
"label": "删除",
"value": "Delete",
"parent": 66,
"type": 0,
"color": "danger",
"is_value": true,
"status": true,
"sort": 3,
"remark": null,
"children": []
},
{
"label": "详情",
"value": "Retrieve",
"parent": 66,
"type": 0,
"color": "info",
"is_value": true,
"status": true,
"sort": 4,
"remark": null,
"children": []
},
{
"label": "查询",
"value": "Search",
"parent": 66,
"type": 0,
"color": "warning",
"is_value": true,
"status": true,
"sort": 5,
"remark": null,
"children": []
},
{
"label": "保存",
"value": "Save",
"parent": 66,
"type": 0,
"color": "success",
"is_value": true,
"status": true,
"sort": 6,
"remark": null,
"children": []
},
{
"label": "导入",
"value": "Import",
"parent": 66,
"type": 0,
"color": "primary",
"is_value": true,
"status": true,
"sort": 7,
"remark": null,
"children": []
},
{
"label": "导出",
"value": "Export",
"parent": 66,
"type": 0,
"color": "warning",
"is_value": true,
"status": true,
"sort": 8,
"remark": null,
"children": []
}
]
},
{
"label": "启用/禁用-数字值",
"value": "button_status_number",
"parent": null,
"type": 0,
"color": null,
"is_value": false,
"status": true,
"sort": 3,
"remark": null,
"children": [
{
"label": "启用",
"value": "1",
"parent": 7,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 1,
"remark": null,
"children": []
},
{
"label": "禁用",
"value": "0",
"parent": 7,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 2,
"remark": null,
"children": []
}
]
},
{
"label": "是/否-布尔值",
"value": "button_whether_bool",
"parent": null,
"type": 0,
"color": null,
"is_value": false,
"status": true,
"sort": 4,
"remark": null,
"children": [
{
"label": "是",
"value": "true",
"parent": 5,
"type": 6,
"color": null,
"is_value": true,
"status": true,
"sort": 1,
"remark": null,
"children": []
},
{
"label": "否",
"value": "false",
"parent": 5,
"type": 6,
"color": null,
"is_value": true,
"status": true,
"sort": 2,
"remark": null,
"children": []
}
]
},
{
"label": "是/否-数字值",
"value": "button_whether_number",
"parent": null,
"type": 0,
"color": null,
"is_value": false,
"status": true,
"sort": 5,
"remark": null,
"children": [
{
"label": "是",
"value": "1",
"parent": 10,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 1,
"remark": null,
"children": []
},
{
"label": "否",
"value": "2",
"parent": 10,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 2,
"remark": null,
"children": []
}
]
},
{
"label": "用户类型",
"value": "user_type",
"parent": null,
"type": 0,
"color": null,
"is_value": false,
"status": true,
"sort": 6,
"remark": null,
"children": [
{
"label": "后台用户",
"value": "0",
"parent": 15,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 1,
"remark": null,
"children": []
},
{
"label": "前台用户",
"value": "1",
"parent": 15,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 2,
"remark": null,
"children": []
}
]
},
{
"label": "表单类型",
"value": "config_form_type",
"parent": null,
"type": 0,
"color": null,
"is_value": false,
"status": true,
"sort": 7,
"remark": null,
"children": [
{
"label": "text",
"value": "0",
"parent": 49,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 0,
"remark": null,
"children": []
},
{
"label": "textarea",
"value": "3",
"parent": 49,
"type": 1,
"color": "",
"is_value": true,
"status": true,
"sort": 0,
"remark": null,
"children": []
},
{
"label": "number",
"value": "10",
"parent": 49,
"type": 1,
"color": "",
"is_value": true,
"status": true,
"sort": 0,
"remark": null,
"children": []
},
{
"label": "datetime",
"value": "1",
"parent": 49,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 1,
"remark": null,
"children": []
},
{
"label": "date",
"value": "2",
"parent": 49,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 2,
"remark": null,
"children": []
},
{
"label": "time",
"value": "15",
"parent": 49,
"type": 1,
"color": "",
"is_value": true,
"status": true,
"sort": 3,
"remark": null,
"children": []
},
{
"label": "select",
"value": "4",
"parent": 49,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 4,
"remark": null,
"children": []
},
{
"label": "checkbox",
"value": "5",
"parent": 49,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 5,
"remark": null,
"children": []
},
{
"label": "radio",
"value": "6",
"parent": 49,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 6,
"remark": null,
"children": []
},
{
"label": "switch",
"value": "9",
"parent": 49,
"type": 1,
"color": "",
"is_value": true,
"status": true,
"sort": 6,
"remark": null,
"children": []
},
{
"label": "文件附件",
"value": "8",
"parent": 49,
"type": 1,
"color": "",
"is_value": true,
"status": true,
"sort": 7,
"remark": null,
"children": []
},
{
"label": "图片(单张)",
"value": "7",
"parent": 49,
"type": 1,
"color": "",
"is_value": true,
"status": true,
"sort": 8,
"remark": null,
"children": []
},
{
"label": "图片(多张)",
"value": "12",
"parent": 49,
"type": 1,
"color": "",
"is_value": true,
"status": true,
"sort": 9,
"remark": null,
"children": []
},
{
"label": "数组",
"value": "11",
"parent": 49,
"type": 1,
"color": "",
"is_value": true,
"status": true,
"sort": 11,
"remark": null,
"children": []
},
{
"label": "关联表",
"value": "13",
"parent": 49,
"type": 1,
"color": "",
"is_value": true,
"status": true,
"sort": 13,
"remark": null,
"children": []
},
{
"label": "关联表(多选)",
"value": "14",
"parent": 49,
"type": 1,
"color": "",
"is_value": true,
"status": true,
"sort": 14,
"remark": null,
"children": []
}
]
},
{
"label": "性别",
"value": "gender",
"parent": null,
"type": 0,
"color": null,
"is_value": false,
"status": true,
"sort": 8,
"remark": null,
"children": [
{
"label": "未知",
"value": "0",
"parent": 18,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 0,
"remark": null,
"children": []
},
{
"label": "男",
"value": "1",
"parent": 18,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 1,
"remark": null,
"children": []
},
{
"label": "女",
"value": "2",
"parent": 18,
"type": 1,
"color": null,
"is_value": true,
"status": true,
"sort": 2,
"remark": null,
"children": []
}
]
}
]

View File

@ -0,0 +1,631 @@
[
{
"name": "系统管理",
"icon": "cog",
"sort": 1,
"is_link": false,
"is_catalog": true,
"web_path": "",
"component": "",
"component_name": "",
"status": true,
"cache": false,
"visible": true,
"parent": null,
"children": [
{
"name": "菜单管理",
"icon": "navicon",
"sort": 1,
"is_link": false,
"is_catalog": false,
"web_path": "/menu",
"component": "system/menu",
"component_name": "menu",
"status": true,
"cache": false,
"visible": true,
"parent": 277,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/menu/",
"method": 0
},
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/menu/{id}/",
"method": 0
},
{
"name": "新增",
"value": "Create",
"api": "/api/system/menu/",
"method": 1
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/menu/{id}/",
"method": 2
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/menu/{id}/",
"method": 3
}
]
},
{
"name": "菜单按钮",
"icon": "dot-circle-o",
"sort": 2,
"is_link": false,
"is_catalog": false,
"web_path": "/menuButton",
"component": "system/menuButton/index",
"component_name": "menuButton",
"status": true,
"cache": false,
"visible": false,
"parent": 277,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/menu_button/",
"method": 0
},
{
"name": "新增",
"value": "Create",
"api": "/api/system/menu_button/",
"method": 1
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/menu_button/{id}/",
"method": 2
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/menu_button/{id}/",
"method": 3
}
]
},
{
"name": "部门管理",
"icon": "bank",
"sort": 3,
"is_link": false,
"is_catalog": false,
"web_path": "/dept",
"component": "system/dept/index",
"component_name": "dept",
"status": true,
"cache": false,
"visible": true,
"parent": 277,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/dept/",
"method": 0
},
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/dept/{id}/",
"method": 0
},
{
"name": "新增",
"value": "Create",
"api": "/api/system/dept/",
"method": 1
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/dept/{id}/",
"method": 2
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/dept/{id}/",
"method": 3
}
]
},
{
"name": "角色管理",
"icon": "address-book",
"sort": 4,
"is_link": false,
"is_catalog": false,
"web_path": "/role",
"component": "system/role/index",
"component_name": "role",
"status": true,
"cache": false,
"visible": true,
"parent": 277,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/role/",
"method": 0
},
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/role/{id}/",
"method": 0
},
{
"name": "新增",
"value": "Create",
"api": "/api/system/role/",
"method": 1
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/role/{id}/",
"method": 2
},
{
"name": "保存",
"value": "Save",
"api": "/api/system/role/{id}/",
"method": 2
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/role/{id}/",
"method": 3
}
]
},
{
"name": "用户管理",
"icon": "users",
"sort": 6,
"is_link": false,
"is_catalog": false,
"web_path": "/user",
"component": "system/user/index",
"component_name": "user",
"status": true,
"cache": false,
"visible": true,
"parent": 277,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/user/",
"method": 0
},
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/user/{id}/",
"method": 0
},
{
"name": "新增",
"value": "Create",
"api": "/api/system/user/",
"method": 1
},
{
"name": "导出",
"value": "Export",
"api": "/api/system/user/export/",
"method": 1
},
{
"name": "导入",
"value": "Import",
"api": "/api/system/user/import/",
"method": 1
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/user/{id}/",
"method": 2
},
{
"name": "重设密码",
"value": "ResetPassword",
"api": "/api/system/user/reset_password/{id}/",
"method": 2
},
{
"name": "重置密码",
"value": "DefaultPassword",
"api": "/api/system/user/reset_to_default_password/{id}/",
"method": 2
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/user/{id}/",
"method": 3
}
]
},
{
"name": "接口白名单",
"icon": "compass",
"sort": 7,
"is_link": false,
"is_catalog": false,
"web_path": "/apiWhiteList",
"component": "system/whiteList/index",
"component_name": "whiteList",
"status": true,
"cache": false,
"visible": true,
"parent": 277,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/api_white_list/",
"method": 0
},
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/api_white_list/{id}/",
"method": 0
},
{
"name": "新增",
"value": "Create",
"api": "/api/system/api_white_list/",
"method": 1
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/api_white_list/{id}/",
"method": 2
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/api_white_list/{id}/",
"method": 3
}
]
}
],
"menu_button": []
},
{
"name": "常规配置",
"icon": "cogs",
"sort": 2,
"is_link": false,
"is_catalog": true,
"web_path": "",
"component": "",
"component_name": "",
"status": true,
"cache": false,
"visible": true,
"parent": null,
"children": [
{
"name": "系统配置",
"icon": "desktop",
"sort": 0,
"is_link": false,
"is_catalog": false,
"web_path": "/config",
"component": "system/config/index",
"component_name": "config",
"status": true,
"cache": false,
"visible": true,
"parent": 285,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/system_config/",
"method": 0
},
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/system_config/{id}/",
"method": 0
},
{
"name": "新增",
"value": "Create",
"api": "/api/system/system_config/",
"method": 1
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/system_config/{id}/",
"method": 2
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/system_config/{id}/",
"method": 3
}
]
},
{
"name": "字典管理",
"icon": "book",
"sort": 1,
"is_link": false,
"is_catalog": false,
"web_path": "/dictionary",
"component": "system/dictionary/index",
"component_name": "dictionary",
"status": true,
"cache": false,
"visible": true,
"parent": 285,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/dictionary/",
"method": 0
},
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/dictionary/{id}/",
"method": 0
},
{
"name": "新增",
"value": "Create",
"api": "/api/system/dictionary/",
"method": 1
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/dictionary/{id}/",
"method": 2
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/dictionary/{id}/",
"method": 3
}
]
},
{
"name": "地区管理",
"icon": "map",
"sort": 2,
"is_link": false,
"is_catalog": false,
"web_path": "/areas",
"component": "system/areas/index",
"component_name": "areas",
"status": true,
"cache": false,
"visible": true,
"parent": 285,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/area/",
"method": 0
},
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/area/{id}/",
"method": 0
},
{
"name": "新增",
"value": "Create",
"api": "/api/system/area/",
"method": 1
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/area/{id}/",
"method": 2
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/area/{id}/",
"method": 3
}
]
},
{
"name": "附件管理",
"icon": "file-text-o",
"sort": 3,
"is_link": false,
"is_catalog": false,
"web_path": "/file",
"component": "system/fileList/index",
"component_name": "file",
"status": true,
"cache": false,
"visible": true,
"parent": 285,
"children": [],
"menu_button": [
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/file/{id}/",
"method": 0
},
{
"name": "查询",
"value": "Search",
"api": "/api/system/file/",
"method": 0
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/file/{id}/",
"method": 1
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/file/{id}/",
"method": 3
}
]
}
],
"menu_button": []
},
{
"name": "日志管理",
"icon": "book",
"sort": 3,
"is_link": false,
"is_catalog": true,
"web_path": "",
"component": "",
"component_name": "",
"status": true,
"cache": false,
"visible": true,
"parent": null,
"children": [
{
"name": "登录日志",
"icon": "file-text",
"sort": 1,
"is_link": false,
"is_catalog": false,
"web_path": "/loginLog",
"component": "system/log/loginLog/index",
"component_name": "loginLog",
"status": true,
"cache": false,
"visible": true,
"parent": 290,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/login_log/",
"method": 0
},
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/login_log/{id}/",
"method": 0
}
]
},
{
"name": "操作日志",
"icon": "file-code-o",
"sort": 2,
"is_link": false,
"is_catalog": false,
"web_path": "/operationLog",
"component": "system/log/operationLog/index",
"component_name": "operationLog",
"status": true,
"cache": false,
"visible": true,
"parent": 290,
"children": [],
"menu_button": [
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/operation_log/{id}/",
"method": 0
},
{
"name": "查询",
"value": "Search",
"api": "/api/system/operation_log/",
"method": 0
}
]
},
{
"name": "前端错误日志",
"icon": "bug",
"sort": 4,
"is_link": false,
"is_catalog": false,
"web_path": "/frontendLog",
"component": "system/log/frontendLog/index",
"component_name": "frontendLog",
"status": true,
"cache": false,
"visible": true,
"parent": 290,
"children": [],
"menu_button": []
}
],
"menu_button": []
},
{
"name": "DVAdmin官网",
"icon": "external-link",
"sort": 4,
"is_link": true,
"is_catalog": false,
"web_path": "https://django-vue-admin.com",
"component": "",
"component_name": "",
"status": true,
"cache": false,
"visible": true,
"parent": null,
"children": [],
"menu_button": []
}
]

View File

@ -0,0 +1,11 @@
[
{
"name": "管理员",
"key": "admin",
"sort": 1,
"status": true,
"admin": true,
"data_range": 3,
"remark": null
}
]

View File

@ -0,0 +1,197 @@
[
{
"parent": null,
"title": "基础配置",
"key": "base",
"value": null,
"sort": 0,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": null,
"placeholder": null,
"setting": null,
"children": [
{
"parent": 10,
"title": "开启验证码",
"key": "captcha_state",
"value": true,
"sort": 1,
"status": true,
"data_options": null,
"form_item_type": 9,
"rule": [
{
"message": "必填项不能为空",
"required": true
}
],
"placeholder": "请选择",
"setting": null,
"children": []
},
{
"parent": 10,
"title": "创建用户默认密码",
"key": "default_password",
"value": "admin123456",
"sort": 2,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"message": "必填项不能为空",
"required": true
}
],
"placeholder": "请输入默认密码",
"setting": null,
"children": []
}
]
},
{
"parent": null,
"title": "登录页配置",
"key": "login",
"value": null,
"sort": 1,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": null,
"placeholder": null,
"setting": null,
"children": [
{
"parent": 1,
"title": "网站名称",
"key": "site_name",
"value": "企业级后台管理系统",
"sort": 1,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"message": "必填项不能为空",
"required": true
}
],
"placeholder": "请输入网站名称",
"setting": null,
"children": []
},
{
"parent": 1,
"title": "登录网站logo",
"key": "site_logo",
"value": null,
"sort": 2,
"status": true,
"data_options": null,
"form_item_type": 7,
"rule": [],
"placeholder": "请上传网站logo",
"setting": null,
"children": []
},
{
"parent": 1,
"title": "登录页背景图",
"key": "login_background",
"value": null,
"sort": 3,
"status": true,
"data_options": null,
"form_item_type": 7,
"rule": [],
"placeholder": "请上传登录背景页",
"setting": null,
"children": []
},
{
"parent": 1,
"title": "版权信息",
"key": "copyright",
"value": "2021-2022 django-vue-admin.com 版权所有",
"sort": 4,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"message": "必填项不能为空",
"required": true
}
],
"placeholder": "请输入版权信息",
"setting": null,
"children": []
},
{
"parent": 1,
"title": "备案信息",
"key": "keep_record",
"value": "晋ICP备18005113号-3",
"sort": 5,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"message": "必填项不能为空",
"required": true
}
],
"placeholder": "请输入备案信息",
"setting": null,
"children": []
},
{
"parent": 1,
"title": "帮助链接",
"key": "help_url",
"value": "https://django-vue-admin.com",
"sort": 6,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": "",
"placeholder": "请输入帮助信息",
"setting": null,
"children": []
},
{
"parent": 1,
"title": "隐私链接",
"key": "privacy_url",
"value": "#",
"sort": 7,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": [],
"placeholder": "请填写隐私链接",
"setting": null,
"children": []
},
{
"parent": 1,
"title": "条款链接",
"key": "clause_url",
"value": "#",
"sort": 8,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": [],
"placeholder": "请输入条款链接",
"setting": null,
"children": []
}
]
}
]

View File

@ -0,0 +1,38 @@
[
{
"username": "admin",
"email": "dvadmin@django-vue-admin.com",
"mobile": "18888888888",
"avatar": "",
"name": "管理员",
"gender": 1,
"user_type": 0,
"dept": 1,
"role": [],
"first_name": "",
"last_name": "",
"is_staff": true,
"is_active": true,
"password": "pbkdf2_sha256$260000$g17x5wlSiW1FZAh1Eudchw$ZeSAqj3Xak0io8v/pmPW0BX9EX5R2zFXDwbbD68oBFk=",
"last_login": null,
"is_superuser": false
},
{
"username": "superadmin",
"email": "dvadmin@django-vue-admin.com",
"mobile": "13333333333",
"avatar": null,
"name": "超级管理员",
"gender": 1,
"user_type": 0,
"dept": 1,
"role": [],
"first_name": "",
"last_name": "",
"is_staff": true,
"is_active": true,
"password": "pbkdf2_sha256$260000$g17x5wlSiW1FZAh1Eudchw$ZeSAqj3Xak0io8v/pmPW0BX9EX5R2zFXDwbbD68oBFk=",
"last_login": null,
"is_superuser": true
}
]

View File

@ -0,0 +1,74 @@
# 初始化
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "application.settings")
django.setup()
from dvadmin.system.views.user import UsersInitSerializer
from dvadmin.system.views.menu import MenuInitSerializer
from dvadmin.utils.core_initialize import CoreInitialize
from dvadmin.system.views.role import RoleInitSerializer
from dvadmin.system.views.api_white_list import ApiWhiteListInitSerializer
from dvadmin.system.views.dept import DeptInitSerializer
from dvadmin.system.views.dictionary import DictionaryInitSerializer
from dvadmin.system.views.system_config import SystemConfigInitSerializer
class Initialize(CoreInitialize):
def init_dept(self):
"""
初始化部门信息
"""
self.init_base(DeptInitSerializer, unique_fields=['name', 'parent'])
def init_role(self):
"""
初始化角色信息
"""
self.init_base(RoleInitSerializer, unique_fields=['key'])
def init_users(self):
"""
初始化用户信息
"""
self.init_base(UsersInitSerializer, unique_fields=['username'])
def init_menu(self):
"""
初始化菜单信息
"""
self.init_base(MenuInitSerializer, unique_fields=['name', 'web_path', 'component', 'component_name'])
def init_api_white_list(self):
"""
初始API白名单
"""
self.init_base(ApiWhiteListInitSerializer, unique_fields=['url', 'method', ])
def init_dictionary(self):
"""
初始化字典表
"""
self.init_base(DictionaryInitSerializer, unique_fields=['value', 'parent', ])
def init_system_config(self):
"""
初始化系统配置表
"""
self.init_base(SystemConfigInitSerializer, unique_fields=['key', 'parent', ])
def run(self):
self.init_dept()
self.init_role()
self.init_users()
self.init_menu()
self.init_api_white_list()
self.init_dictionary()
self.init_system_config()
if __name__ == "__main__":
Initialize(app='dvadmin.system').run()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,100 @@
import json
import logging
import os
import django
from django.db.models import QuerySet
from dvadmin.system.views.system_config import SystemConfigInitSerializer
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
django.setup()
from django.core.management.base import BaseCommand
from application.settings import BASE_DIR
from dvadmin.system.models import Menu, Users, Dept, Role, ApiWhiteList, Dictionary, SystemConfig
from dvadmin.system.views.api_white_list import ApiWhiteListInitSerializer
from dvadmin.system.views.dept import DeptInitSerializer
from dvadmin.system.views.dictionary import DictionaryInitSerializer
from dvadmin.system.views.menu import MenuInitSerializer
from dvadmin.system.views.role import RoleInitSerializer
from dvadmin.system.views.user import UsersInitSerializer
logger = logging.getLogger(__name__)
class Command(BaseCommand):
"""
生产初始化菜单: python3 manage.py generate_init_json 生成初始化的model名
例如
全部生成python3 manage.py generate_init_json
只生成某个model的 python3 manage.py generate_init_json users
"""
def serializer_data(self, serializer, query_set: QuerySet):
serializer = serializer(query_set, many=True)
data = json.loads(json.dumps(serializer.data, ensure_ascii=False))
with open(os.path.join(BASE_DIR, f'init_{query_set.model._meta.model_name}.json'), 'w') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
return
def add_arguments(self, parser):
parser.add_argument("generate_name", nargs="*", type=str, help="初始化生成的表名")
def generate_users(self):
self.serializer_data(UsersInitSerializer, Users.objects.all())
def generate_role(self):
self.serializer_data(RoleInitSerializer, Role.objects.all())
def generate_dept(self):
self.serializer_data(DeptInitSerializer, Dept.objects.filter(parent_id__isnull=True))
def generate_menu(self):
self.serializer_data(MenuInitSerializer, Menu.objects.filter(parent_id__isnull=True))
def generate_api_white_list(self):
self.serializer_data(ApiWhiteListInitSerializer, ApiWhiteList.objects.all())
def generate_dictionary(self):
self.serializer_data(DictionaryInitSerializer, Dictionary.objects.filter(parent_id__isnull=True))
def generate_system_config(self):
self.serializer_data(SystemConfigInitSerializer, SystemConfig.objects.filter(parent_id__isnull=True))
def handle(self, *args, **options):
generate_name = options.get('generate_name')
generate_name_dict = {
"users": self.generate_users,
"role": self.generate_role,
"dept": self.generate_dept,
"menu": self.generate_menu,
"api_white_list": self.generate_api_white_list,
"dictionary": self.generate_dictionary,
"system_config": self.generate_system_config,
}
if not generate_name:
for ele in generate_name_dict.keys():
generate_name_dict[ele]()
return
for generate_name in generate_name:
if generate_name not in generate_name_dict:
print(f"该初始化方法尚未配置\n{generate_name_dict}")
raise Exception(f"该初始化方法尚未配置,已配置项:{list(generate_name_dict.keys())}")
generate_name_dict[generate_name]()
return
if __name__ == '__main__':
# with open(os.path.join(BASE_DIR, 'temp_init_menu.json')) as f:
# for menu_data in json.load(f):
# menu_data['creator'] = 1
# menu_data['modifier'] = 1
# menu_data['dept_belong_id'] = 1
# request.user = Users.objects.order_by('create_datetime').first()
# serializer = MenuInitSerializer(data=menu_data, request=request)
# serializer.is_valid(raise_exception=True)
# serializer.save()
a = Users.objects.filter()
print(type(Users.objects.filter()))

View File

@ -13,27 +13,41 @@ class Command(BaseCommand):
""" """
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('init_name', nargs='*', type=str, ) parser.add_argument(
parser.add_argument('-y', nargs='*') "init_name",
parser.add_argument('-Y', nargs='*') nargs="*",
parser.add_argument('-n', nargs='*') type=str,
parser.add_argument('-N', nargs='*') )
parser.add_argument("-y", nargs="*")
parser.add_argument("-Y", nargs="*")
parser.add_argument("-n", nargs="*")
parser.add_argument("-N", nargs="*")
def handle(self, *args, **options): def handle(self, *args, **options):
reset = False reset = False
if isinstance(options.get('y'), list) or isinstance(options.get('Y'), list): if isinstance(options.get("y"), list) or isinstance(options.get("Y"), list):
reset = True reset = True
if isinstance(options.get('n'), list) or isinstance(options.get('N'), list): if isinstance(options.get("n"), list) or isinstance(options.get("N"), list):
reset = False reset = False
print(f"正在准备初始化数据,{'如有初始化数据,将会不做操作跳过' if not reset else '初始数据将会先删除后新增'}...")
for app in settings.INSTALLED_APPS: for app in settings.INSTALLED_APPS:
try: try:
exec(f""" exec(
f"""
from {app}.fixtures.initialize import Initialize
Initialize(reset={reset},app="{app}").run()
"""
)
except ModuleNotFoundError:
# 兼容之前版本初始化
try:
exec(
f"""
from {app}.initialize import main from {app}.initialize import main
main(reset={reset}) main(reset={reset})
""") """
)
except ModuleNotFoundError: except ModuleNotFoundError:
pass pass
print("初始化数据完成!") print("初始化数据完成!")

View File

@ -13,6 +13,8 @@ import pypinyin
from django.core.management import BaseCommand from django.core.management import BaseCommand
from django.db import connection from django.db import connection
from application import dispatch
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
django.setup() django.setup()
from application.settings import BASE_DIR from application.settings import BASE_DIR
@ -68,9 +70,9 @@ class Command(BaseCommand):
print(f"正在准备初始化省份数据...") print(f"正在准备初始化省份数据...")
if hasattr(connection, 'tenant') and connection.tenant.schema_name: if dispatch.is_tenants_mode():
from django_tenants.utils import get_tenant_model from django_tenants.utils import get_tenant_model
from django_tenants.utils import tenant_context,schema_context from django_tenants.utils import tenant_context
for tenant in get_tenant_model().objects.exclude(schema_name='public'): for tenant in get_tenant_model().objects.exclude(schema_name='public'):
with tenant_context(tenant): with tenant_context(tenant):
print(f"租户[{connection.tenant.schema_name}]初始化数据开始...") print(f"租户[{connection.tenant.schema_name}]初始化数据开始...")
@ -79,4 +81,3 @@ class Command(BaseCommand):
else: else:
main() main()
print("省份数据初始化数据完成!") print("省份数据初始化数据完成!")

View File

@ -4,6 +4,7 @@ import os
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from application import dispatch
from dvadmin.utils.models import CoreModel, table_prefix from dvadmin.utils.models import CoreModel, table_prefix
STATUS_CHOICES = ( STATUS_CHOICES = (
@ -19,10 +20,11 @@ class Users(AbstractUser, CoreModel):
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="头像")
name = models.CharField(max_length=40, verbose_name="姓名", help_text="姓名") name = models.CharField(max_length=40, verbose_name="姓名", help_text="姓名")
GENDER_CHOICES = ( GENDER_CHOICES = (
(0, ""), (0, "未知"),
(1, ""), (1, ""),
(2, ""),
) )
gender = models.IntegerField(choices=GENDER_CHOICES, default=1, verbose_name="性别", null=True, blank=True, gender = models.IntegerField(choices=GENDER_CHOICES, default=0, verbose_name="性别", null=True, blank=True,
help_text="性别") help_text="性别")
USER_TYPE = ( USER_TYPE = (
(0, "后台用户"), (0, "后台用户"),
@ -107,17 +109,6 @@ class Dept(CoreModel):
ordering = ('sort',) ordering = ('sort',)
class Button(CoreModel):
name = models.CharField(max_length=64, unique=True, verbose_name="权限名称", help_text="权限名称")
value = models.CharField(max_length=64, unique=True, verbose_name="权限值", help_text="权限值")
class Meta:
db_table = table_prefix + "system_button"
verbose_name = '权限标识表'
verbose_name_plural = verbose_name
ordering = ('-name',)
class Menu(CoreModel): class Menu(CoreModel):
parent = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name="上级菜单", null=True, blank=True, parent = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name="上级菜单", null=True, blank=True,
db_constraint=False, help_text="上级菜单") db_constraint=False, help_text="上级菜单")
@ -166,22 +157,37 @@ class MenuButton(CoreModel):
class Dictionary(CoreModel): class Dictionary(CoreModel):
code = models.CharField(max_length=100, blank=True, null=True, verbose_name="编码", help_text="编码") TYPE_LIST = (
label = models.CharField(max_length=100, blank=True, null=True, verbose_name="显示名称", help_text="显示名称") (0, 'text'),
value = models.CharField(max_length=100, blank=True, null=True, verbose_name="实际值", help_text="实际值") (1, 'number'),
(2, 'date'),
(3, 'datetime'),
(4, 'time'),
(5, 'files'),
(6, 'boolean'),
(7, 'images'),
)
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="字典编号/实际值")
parent = models.ForeignKey(to='self', related_name='sublist', db_constraint=False, on_delete=models.PROTECT, parent = models.ForeignKey(to='self', related_name='sublist', db_constraint=False, on_delete=models.PROTECT,
blank=True, null=True, blank=True, null=True, verbose_name="父级", help_text="父级")
verbose_name="父级", help_text="父级") type = models.IntegerField(choices=TYPE_LIST, default=0, verbose_name="数据值类型", help_text="数据值类型")
status = models.BooleanField(default=True, blank=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值,用来做具体值存放")
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="备注")
class Meta: class Meta:
db_table = table_prefix + 'dictionary' db_table = table_prefix + 'system_dictionary'
verbose_name = "字典表" verbose_name = "字典表"
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
ordering = ('sort',) ordering = ('sort',)
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
super().save(force_insert, force_update, using, update_fields)
dispatch.refresh_dictionary() # 有更新则刷新字典配置
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, help_text="请求模块")
@ -223,7 +229,7 @@ class FileList(CoreModel):
super(FileList, self).save(*args, **kwargs) super(FileList, self).save(*args, **kwargs)
class Meta: class Meta:
db_table = table_prefix + 'file_list' db_table = table_prefix + 'system_file_list'
verbose_name = '文件管理' verbose_name = '文件管理'
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
ordering = ('-create_datetime',) ordering = ('-create_datetime',)
@ -240,7 +246,7 @@ class Area(CoreModel):
db_constraint=False, null=True, blank=True, help_text="父地区编码") db_constraint=False, null=True, blank=True, help_text="父地区编码")
class Meta: class Meta:
db_table = table_prefix + "area" db_table = table_prefix + "system_area"
verbose_name = '地区表' verbose_name = '地区表'
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
ordering = ('code',) ordering = ('code',)
@ -271,26 +277,29 @@ class SystemConfig(CoreModel):
parent = models.ForeignKey(to='self', verbose_name='父级', on_delete=models.CASCADE, parent = models.ForeignKey(to='self', verbose_name='父级', on_delete=models.CASCADE,
db_constraint=False, null=True, blank=True, help_text="父级") db_constraint=False, null=True, blank=True, 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=20, verbose_name="", help_text="") key = models.CharField(max_length=20, verbose_name="", help_text="", db_index=True)
value = models.JSONField(max_length=100, verbose_name="", help_text="", null=True, blank=True) value = models.JSONField(max_length=100, verbose_name="", help_text="", null=True, blank=True)
sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True) sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True)
status = models.BooleanField(default=False, 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)
FORM_ITEM_TYPE_LIST = ( FORM_ITEM_TYPE_LIST = (
(0, 'text'), (0, 'text'),
(1, 'textarea'), (1, 'datetime'),
(2, 'number'), (2, 'date'),
(3, 'select'), (3, 'textarea'),
(4, 'radio'), (4, 'select'),
(5, 'checkbox'), (5, 'checkbox'),
(6, 'date'), (6, 'radio'),
(7, 'datetime'), (7, 'img'),
(8, 'time'), (8, 'file'),
(9, 'imgs'), (9, 'switch'),
(10, 'files'), (10, 'number'),
(11, 'array'), (11, 'array'),
(12, 'foreignkey'), (12, 'imgs'),
(13, 'manytomany'), (13, 'foreignkey'),
(14, 'manytomany'),
(15, 'time'),
) )
form_item_type = models.IntegerField(choices=FORM_ITEM_TYPE_LIST, verbose_name="表单类型", help_text="表单类型", default=0, form_item_type = models.IntegerField(choices=FORM_ITEM_TYPE_LIST, verbose_name="表单类型", help_text="表单类型", default=0,
blank=True) blank=True)
@ -303,10 +312,15 @@ class SystemConfig(CoreModel):
verbose_name = '系统配置表' verbose_name = '系统配置表'
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
ordering = ('sort',) ordering = ('sort',)
unique_together = (("key", "parent_id"),)
def __str__(self): def __str__(self):
return f"{self.title}" return f"{self.title}"
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
super().save(force_insert, force_update, using, update_fields)
dispatch.refresh_system_config() # 有更新则刷新系统配置
class LoginLog(CoreModel): class LoginLog(CoreModel):
LOGIN_TYPE_CHOICES = ( LOGIN_TYPE_CHOICES = (

View File

@ -1,17 +1,8 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/1 001 23:05
@Remark: 系统管理的路由文件
"""
from django.urls import path from django.urls import path
from rest_framework import routers 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.button import ButtonViewSet
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
@ -25,7 +16,6 @@ from dvadmin.system.views.user import UserViewSet
system_url = routers.SimpleRouter() system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet) system_url.register(r'menu', MenuViewSet)
system_url.register(r'button', ButtonViewSet)
system_url.register(r'menu_button', MenuButtonViewSet) system_url.register(r'menu_button', MenuButtonViewSet)
system_url.register(r'role', RoleViewSet) system_url.register(r'role', RoleViewSet)
system_url.register(r'dept', DeptViewSet) system_url.register(r'dept', DeptViewSet)
@ -42,6 +32,7 @@ urlpatterns = [
path('menu/web_router/', MenuViewSet.as_view({'get': 'web_router'})), path('menu/web_router/', MenuViewSet.as_view({'get': 'web_router'})),
path('user/user_info/', UserViewSet.as_view({'get': 'user_info', 'put': 'update_user_info'})), path('user/user_info/', UserViewSet.as_view({'get': 'user_info', 'put': 'update_user_info'})),
path('user/change_password/<int:pk>/', UserViewSet.as_view({'put': 'change_password'})), path('user/change_password/<int:pk>/', UserViewSet.as_view({'put': 'change_password'})),
path('user/reset_to_default_password/<int:pk>/', UserViewSet.as_view({'put': 'reset_to_default_password'})),
path('user/reset_password/<int:pk>/', UserViewSet.as_view({'put': 'reset_password'})), path('user/reset_password/<int:pk>/', UserViewSet.as_view({'put': 'reset_password'})),
path('user/export/', UserViewSet.as_view({'post': 'export_data', })), path('user/export/', UserViewSet.as_view({'post': 'export_data', })),
path('user/import/', UserViewSet.as_view({'get': 'import_data', 'post': 'import_data'})), path('user/import/', UserViewSet.as_view({'get': 'import_data', 'post': 'import_data'})),
@ -51,5 +42,6 @@ urlpatterns = [
path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})), path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})),
path('login_log/', LoginLogViewSet.as_view({'get': 'list'})), path('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})), path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})),
path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})),
] ]
urlpatterns += system_url.urls urlpatterns += system_url.urls

View File

@ -22,6 +22,21 @@ class ApiWhiteListSerializer(CustomModelSerializer):
read_only_fields = ["id"] read_only_fields = ["id"]
class ApiWhiteListInitSerializer(CustomModelSerializer):
"""
初始化获取数信息(用于生成初始化json文件)
"""
class Meta:
model = ApiWhiteList
fields = ['url', 'method', 'enable_datasource', 'creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
}
class ApiWhiteListViewSet(CustomModelViewSet): class ApiWhiteListViewSet(CustomModelViewSet):
""" """
接口白名单 接口白名单

View File

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/3 003 0:30
@Remark: 按钮权限管理
"""
from dvadmin.system.models import Button
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
class ButtonSerializer(CustomModelSerializer):
"""
按钮权限-序列化器
"""
class Meta:
model = Button
fields = "__all__"
read_only_fields = ["id"]
class ButtonViewSet(CustomModelViewSet):
"""
按钮权限接口
list:查询
create:新增
update:修改
retrieve:单例
destroy:删除
"""
queryset = Button.objects.all()
serializer_class = ButtonSerializer
extra_filter_backends = []

View File

@ -1,15 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
@author: 猿小天 @author: H0nGzA1
@contact: QQ:1638245306 @contact: QQ:2505811377
@Created on: 2021/6/3 003 0:30 @Remark: 部门管理
@Remark: 角色管理
""" """
from rest_framework import serializers from rest_framework import serializers
from dvadmin.system.models import Dept from dvadmin.system.models import Dept
from dvadmin.utils.json_response import SuccessResponse from dvadmin.utils.json_response import DetailResponse, SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@ -19,12 +18,59 @@ class DeptSerializer(CustomModelSerializer):
部门-序列化器 部门-序列化器
""" """
parent_name = serializers.CharField(read_only=True, source='parent.name') parent_name = serializers.CharField(read_only=True, source='parent.name')
has_children = serializers.SerializerMethodField()
def get_has_children(self, obj: Dept):
return Dept.objects.filter(parent_id=obj.id).count()
class Meta: class Meta:
model = Dept model = Dept
fields = "__all__" fields = '__all__'
read_only_fields = ["id"] read_only_fields = ["id"]
class DeptInitSerializer(CustomModelSerializer):
"""
递归深度获取数信息(用于生成初始化json文件)
"""
children = serializers.SerializerMethodField()
def get_children(self, obj: Dept):
data = []
instance = Dept.objects.filter(parent_id=obj.id)
if instance:
serializer = DeptInitSerializer(instance=instance, many=True)
data = serializer.data
return data
def save(self, **kwargs):
instance = super().save(**kwargs)
children = self.initial_data.get('children')
if children:
for menu_data in children:
menu_data['parent'] = instance.id
filter_data = {
"name": menu_data['name'],
"parent": menu_data['parent']
}
instance_obj = Dept.objects.filter(**filter_data).first()
serializer = DeptInitSerializer(instance_obj, data=menu_data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
return instance
class Meta:
model = Dept
fields = ['name', 'sort', 'owner', 'phone', 'email', 'status', 'parent', 'creator', 'dept_belong_id',
'children']
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
}
read_only_fields = ['id', 'children']
class DeptCreateUpdateSerializer(CustomModelSerializer): class DeptCreateUpdateSerializer(CustomModelSerializer):
""" """
部门管理 创建/更新时的列化器 部门管理 创建/更新时的列化器
@ -54,13 +100,40 @@ class DeptViewSet(CustomModelViewSet):
serializer_class = DeptSerializer serializer_class = DeptSerializer
create_serializer_class = DeptCreateUpdateSerializer create_serializer_class = DeptCreateUpdateSerializer
update_serializer_class = DeptCreateUpdateSerializer update_serializer_class = DeptCreateUpdateSerializer
filter_fields = ['name', 'id', 'parent']
search_fields = []
# extra_filter_backends = [] # extra_filter_backends = []
# def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
# queryset = self.filter_queryset(self.get_queryset()) # 如果懒加载,则只返回父级
# page = self.paginate_queryset(queryset) queryset = self.filter_queryset(self.get_queryset())
# if page is not None: lazy = self.request.query_params.get('lazy')
# serializer = self.get_serializer(page, many=True, request=request) parent = self.request.query_params.get('parent')
# return self.get_paginated_response(serializer.data) if lazy:
# serializer = self.get_serializer(queryset, many=True, request=request) # 如果懒加载模式,返回全部
# return SuccessResponse(data=serializer.data, msg="获取成功") if not parent:
if self.request.user.is_superuser:
queryset = queryset.filter(parent__isnull=True)
else:
queryset = queryset.filter(id=self.request.user.dept_id)
serializer = self.get_serializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功")
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True, request=request)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功")
def dept_lazy_tree(self, request, *args, **kwargs):
parent = self.request.query_params.get('parent')
queryset = self.filter_queryset(self.get_queryset())
if not parent:
if self.request.user.is_superuser:
queryset = queryset.filter(parent__isnull=True)
else:
queryset = queryset.filter(id=self.request.user.dept_id)
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent')
return DetailResponse(data=data, msg="获取成功")

View File

@ -7,7 +7,9 @@
@Remark: 字典管理 @Remark: 字典管理
""" """
from rest_framework import serializers from rest_framework import serializers
from rest_framework.views import APIView
from application import dispatch
from dvadmin.system.models import Dictionary from dvadmin.system.models import Dictionary
from dvadmin.utils.json_response import SuccessResponse from dvadmin.utils.json_response import SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
@ -25,6 +27,48 @@ class DictionarySerializer(CustomModelSerializer):
read_only_fields = ["id"] read_only_fields = ["id"]
class DictionaryInitSerializer(CustomModelSerializer):
"""
初始化获取数信息(用于生成初始化json文件)
"""
children = serializers.SerializerMethodField()
def get_children(self, obj: Dictionary):
data = []
instance = Dictionary.objects.filter(parent_id=obj.id)
if instance:
serializer = DictionaryInitSerializer(instance=instance, many=True)
data = serializer.data
return data
def save(self, **kwargs):
instance = super().save(**kwargs)
children = self.initial_data.get('children')
# 菜单表
if children:
for data in children:
data['parent'] = instance.id
filter_data = {
"value": data['value'],
"parent": data['parent']
}
instance_obj = Dictionary.objects.filter(**filter_data).first()
serializer = DictionaryInitSerializer(instance_obj, data=data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
return instance
class Meta:
model = Dictionary
fields = ['label', 'value', 'parent', 'type', 'color', 'is_value', 'status', 'sort', 'remark', 'creator',
'dept_belong_id', 'children']
read_only_fields = ["id"]
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
}
class DictionaryCreateUpdateSerializer(CustomModelSerializer): class DictionaryCreateUpdateSerializer(CustomModelSerializer):
""" """
字典管理 创建/更新时的列化器 字典管理 创建/更新时的列化器
@ -35,26 +79,6 @@ class DictionaryCreateUpdateSerializer(CustomModelSerializer):
fields = '__all__' fields = '__all__'
class DictionaryTreeSerializer(CustomModelSerializer):
"""
字典表的树形序列化器
"""
children = serializers.SerializerMethodField(read_only=True)
def get_children(self, instance):
queryset = Dictionary.objects.filter(parent=instance.id).filter(status=1)
if queryset:
serializer = DictionaryTreeSerializer(queryset, many=True)
return serializer.data
else:
return None
class Meta:
model = Dictionary
fields = "__all__"
read_only_fields = ["id"]
class DictionaryViewSet(CustomModelViewSet): class DictionaryViewSet(CustomModelViewSet):
""" """
字典管理接口 字典管理接口
@ -68,3 +92,26 @@ class DictionaryViewSet(CustomModelViewSet):
serializer_class = DictionarySerializer serializer_class = DictionarySerializer
extra_filter_backends = [] extra_filter_backends = []
search_fields = ['label'] search_fields = ['label']
class InitDictionaryViewSet(APIView):
"""
获取初始化配置
"""
authentication_classes = []
permission_classes = []
queryset = Dictionary.objects.all()
def get(self, request):
dictionary_key = self.request.query_params.get('dictionary_key')
if dictionary_key:
if dictionary_key == 'all':
data = [ele for ele in dispatch.get_dictionary_config().values()]
if not data:
dispatch.refresh_dictionary()
data = [ele for ele in dispatch.get_dictionary_config().values()]
else:
data = self.queryset.filter(parent__value=dictionary_key, status=True).values('label', 'value', 'type',
'color')
return SuccessResponse(data=data, msg="获取成功")
return SuccessResponse(data=[], msg="获取成功")

View File

@ -1,11 +1,3 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/8/9 009 20:48
@Remark:
"""
from rest_framework import serializers from rest_framework import serializers
from dvadmin.system.models import FileList from dvadmin.system.models import FileList

View File

@ -1,11 +1,3 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/2 002 14:20
@Remark:登录视图
"""
import base64 import base64
import hashlib import hashlib
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -22,9 +14,11 @@ from rest_framework.views import APIView
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView from rest_framework_simplejwt.views import TokenObtainPairView
from application import settings from django.conf import settings
from application import dispatch
from dvadmin.system.models import Users from dvadmin.system.models import Users
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse from dvadmin.utils.json_response import ErrorResponse, DetailResponse
from dvadmin.utils.request_util import save_login_log from dvadmin.utils.request_util import save_login_log
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.validator import CustomValidationError from dvadmin.utils.validator import CustomValidationError
@ -35,21 +29,24 @@ class CaptchaView(APIView):
permission_classes = [] permission_classes = []
@swagger_auto_schema( @swagger_auto_schema(
responses={ responses={"200": openapi.Response("获取成功")},
'200': openapi.Response('获取成功')
},
security=[], security=[],
operation_id='captcha-get', operation_id="captcha-get",
operation_description='验证码获取', operation_description="验证码获取",
) )
def get(self, request): def get(self, request):
data = {}
if dispatch.get_system_config_values("base.captcha_state"):
hashkey = CaptchaStore.generate_key() hashkey = CaptchaStore.generate_key()
id = CaptchaStore.objects.filter(hashkey=hashkey).first().id id = CaptchaStore.objects.filter(hashkey=hashkey).first().id
imgage = captcha_image(request, hashkey) imgage = captcha_image(request, hashkey)
# 将图片转换为base64 # 将图片转换为base64
image_base = base64.b64encode(imgage.content) image_base = base64.b64encode(imgage.content)
json_data = {"key": id, "image_base": "data:image/png;base64," + image_base.decode('utf-8')} data = {
return SuccessResponse(data=json_data) "key": id,
"image_base": "data:image/png;base64," + image_base.decode("utf-8"),
}
return DetailResponse(data=data)
class LoginSerializer(TokenObtainPairSerializer): class LoginSerializer(TokenObtainPairSerializer):
@ -57,53 +54,55 @@ class LoginSerializer(TokenObtainPairSerializer):
登录的序列化器: 登录的序列化器:
重写djangorestframework-simplejwt的序列化器 重写djangorestframework-simplejwt的序列化器
""" """
captcha = serializers.CharField(max_length=6, required=False, allow_null=True)
captcha = serializers.CharField(
max_length=6, required=False, allow_null=True, allow_blank=True
)
class Meta: class Meta:
model = Users model = Users
fields = "__all__" fields = "__all__"
read_only_fields = ["id"] read_only_fields = ["id"]
default_error_messages = { default_error_messages = {"no_active_account": _("账号/密码错误")}
'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 settings.CAPTCHA_STATE: if dispatch.get_system_config_values("base.captcha_state"):
if captcha is None: if captcha is None:
raise CustomValidationError("验证码不能为空") raise CustomValidationError("验证码不能为空")
self.image_code = CaptchaStore.objects.filter( self.image_code = CaptchaStore.objects.filter(
id=self.initial_data['captchaKey']).first() id=self.initial_data["captchaKey"]
).first()
five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0) five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
if self.image_code and five_minute_ago > self.image_code.expiration: if self.image_code and five_minute_ago > self.image_code.expiration:
self.image_code and self.image_code.delete() self.image_code and self.image_code.delete()
raise CustomValidationError('验证码过期') raise CustomValidationError("验证码过期")
else: else:
if self.image_code and (self.image_code.response == captcha or self.image_code.challenge == captcha): if self.image_code and (
self.image_code.response == captcha
or self.image_code.challenge == captcha
):
self.image_code and self.image_code.delete() self.image_code and self.image_code.delete()
else: else:
self.image_code and self.image_code.delete() self.image_code and self.image_code.delete()
raise CustomValidationError("图片验证码错误") raise CustomValidationError("图片验证码错误")
data = super().validate(attrs) data = super().validate(attrs)
data['name'] = self.user.name data["name"] = self.user.name
data['userId'] = self.user.id data["userId"] = self.user.id
data['avatar'] = self.user.avatar data["avatar"] = self.user.avatar
request = self.context.get('request') request = self.context.get("request")
request.user = self.user request.user = self.user
# 记录登录日志 # 记录登录日志
save_login_log(request=request) save_login_log(request=request)
return { return {"code": 2000, "msg": "请求成功", "data": data}
"code": 2000,
"msg": "请求成功",
"data": data
}
class LoginView(TokenObtainPairView): class LoginView(TokenObtainPairView):
""" """
登录接口 登录接口
""" """
serializer_class = LoginSerializer serializer_class = LoginSerializer
permission_classes = [] permission_classes = []
@ -118,31 +117,22 @@ class LoginTokenSerializer(TokenObtainPairSerializer):
fields = "__all__" fields = "__all__"
read_only_fields = ["id"] read_only_fields = ["id"]
default_error_messages = { default_error_messages = {"no_active_account": _("账号/密码不正确")}
'no_active_account': _('账号/密码不正确')
}
def validate(self, attrs): def validate(self, attrs):
if not getattr(settings, 'LOGIN_NO_CAPTCHA_AUTH', False): if not getattr(settings, "LOGIN_NO_CAPTCHA_AUTH", False):
return { return {"code": 4000, "msg": "该接口暂未开通!", "data": None}
"code": 4000,
"msg": "该接口暂未开通!",
"data": None
}
data = super().validate(attrs) data = super().validate(attrs)
data['name'] = self.user.name data["name"] = self.user.name
data['userId'] = self.user.id data["userId"] = self.user.id
return { return {"code": 2000, "msg": "请求成功", "data": data}
"code": 2000,
"msg": "请求成功",
"data": data
}
class LoginTokenView(TokenObtainPairView): class LoginTokenView(TokenObtainPairView):
""" """
登录获取token接口 登录获取token接口
""" """
serializer_class = LoginTokenSerializer serializer_class = LoginTokenSerializer
permission_classes = [] permission_classes = []
@ -154,27 +144,32 @@ class LogoutView(APIView):
class ApiLoginSerializer(CustomModelSerializer): class ApiLoginSerializer(CustomModelSerializer):
"""接口文档登录-序列化器""" """接口文档登录-序列化器"""
username = serializers.CharField() username = serializers.CharField()
password = serializers.CharField() password = serializers.CharField()
class Meta: class Meta:
model = Users model = Users
fields = ['username', 'password'] fields = ["username", "password"]
class ApiLogin(APIView): class ApiLogin(APIView):
"""接口文档的登录接口""" """接口文档的登录接口"""
serializer_class = ApiLoginSerializer serializer_class = ApiLoginSerializer
authentication_classes = [] authentication_classes = []
permission_classes = [] permission_classes = []
def post(self, request): def post(self, request):
username = request.data.get('username') username = request.data.get("username")
password = request.data.get('password') password = request.data.get("password")
user_obj = auth.authenticate(request, username=username, user_obj = auth.authenticate(
password=hashlib.md5(password.encode(encoding='UTF-8')).hexdigest()) request,
username=username,
password=hashlib.md5(password.encode(encoding="UTF-8")).hexdigest(),
)
if user_obj: if user_obj:
login(request, user_obj) login(request, user_obj)
return redirect('/') return redirect("/")
else: else:
return ErrorResponse(msg="账号/密码错误") return ErrorResponse(msg="账号/密码错误")

View File

@ -9,7 +9,8 @@
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action from rest_framework.decorators import action
from dvadmin.system.models import Menu, MenuButton, Button from dvadmin.system.models import Menu, MenuButton
from dvadmin.system.views.menu_button import MenuButtonSerializer
from dvadmin.utils.json_response import SuccessResponse from dvadmin.utils.json_response import SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@ -46,6 +47,72 @@ class MenuCreateSerializer(CustomModelSerializer):
read_only_fields = ["id"] read_only_fields = ["id"]
class MenuInitSerializer(CustomModelSerializer):
"""
递归深度获取数信息(用于生成初始化json文件)
"""
name = serializers.CharField(required=False)
children = serializers.SerializerMethodField()
menu_button = serializers.SerializerMethodField()
def get_children(self, obj: Menu):
data = []
instance = Menu.objects.filter(parent_id=obj.id)
if instance:
serializer = MenuInitSerializer(instance=instance, many=True)
data = serializer.data
return data
def get_menu_button(self, obj: Menu):
data = []
instance = MenuButton.objects.filter(menu_id=obj.id).order_by('method')
if instance:
data = list(instance.values('name', 'value', 'api', 'method'))
return data
def save(self, **kwargs):
instance = super().save(**kwargs)
children = self.initial_data.get('children')
menu_button = self.initial_data.get('menu_button')
# 菜单表
if children:
for menu_data in children:
menu_data['parent'] = instance.id
filter_data = {
"name": menu_data['name'],
"web_path": menu_data['web_path'],
"component": menu_data['component'],
"component_name": menu_data['component_name'],
}
instance_obj = Menu.objects.filter(**filter_data).first()
serializer = MenuInitSerializer(instance_obj, data=menu_data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
# 菜单按钮
if menu_button:
for menu_button_data in menu_button:
menu_button_data['menu'] = instance.id
filter_data = {
"menu": menu_button_data['menu'],
"value": menu_button_data['value']
}
instance_obj = MenuButton.objects.filter(**filter_data).first()
serializer = MenuButtonSerializer(instance_obj, data=menu_button_data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
return instance
class Meta:
model = Menu
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
'cache', 'visible', 'parent', 'children', 'menu_button', 'creator', 'dept_belong_id']
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
}
read_only_fields = ['id', 'children']
class WebRouterSerializer(CustomModelSerializer): class WebRouterSerializer(CustomModelSerializer):
""" """
前端菜单路由的简单序列化器 前端菜单路由的简单序列化器
@ -57,7 +124,7 @@ class WebRouterSerializer(CustomModelSerializer):
def get_menuPermission(self, instance): def get_menuPermission(self, instance):
# 判断是否是超级管理员 # 判断是否是超级管理员
if self.request.user.is_superuser: if self.request.user.is_superuser:
return Button.objects.values_list('value', flat=True) return MenuButton.objects.values_list('value', flat=True)
else: else:
# 根据当前角色获取权限按钮id集合 # 根据当前角色获取权限按钮id集合
permissionIds = self.request.user.role.values_list('permission', flat=True) permissionIds = self.request.user.role.values_list('permission', flat=True)

View File

@ -8,7 +8,6 @@
""" """
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Role, Menu from dvadmin.system.models import Role, Menu
from dvadmin.system.views.dept import DeptSerializer from dvadmin.system.views.dept import DeptSerializer
@ -31,6 +30,22 @@ class RoleSerializer(CustomModelSerializer):
read_only_fields = ["id"] read_only_fields = ["id"]
class RoleInitSerializer(CustomModelSerializer):
"""
初始化获取数信息(用于生成初始化json文件)
"""
class Meta:
model = Role
fields = ['name', 'key', 'sort', 'status', 'admin', 'data_range', 'remark',
'creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
}
class RoleCreateUpdateSerializer(CustomModelSerializer): class RoleCreateUpdateSerializer(CustomModelSerializer):
""" """
角色管理 创建/更新时的列化器 角色管理 创建/更新时的列化器
@ -90,4 +105,3 @@ class RoleViewSet(CustomModelViewSet):
queryset = Menu.objects.filter(status=1).all() queryset = Menu.objects.filter(status=1).all()
serializer = MenuPermissonSerializer(queryset, many=True) serializer = MenuPermissonSerializer(queryset, many=True)
return SuccessResponse(data=serializer.data) return SuccessResponse(data=serializer.data)

View File

@ -10,9 +10,10 @@ import django_filters
from django.db.models import Q from django.db.models import Q
from django_filters.rest_framework import BooleanFilter from django_filters.rest_framework import BooleanFilter
from rest_framework import serializers from rest_framework import serializers
from rest_framework.views import APIView
from application import dispatch
from dvadmin.system.models import SystemConfig from dvadmin.system.models import SystemConfig
from dvadmin.utils.filters import DataLevelPermissionsFilter
from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse
from dvadmin.utils.models import get_all_models_objects from dvadmin.utils.models import get_all_models_objects
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
@ -31,7 +32,6 @@ class SystemConfigCreateSerializer(CustomModelSerializer):
fields = "__all__" fields = "__all__"
read_only_fields = ["id"] read_only_fields = ["id"]
def validate_key(self, value): def validate_key(self, value):
""" """
验证key是否允许重复 验证key是否允许重复
@ -42,6 +42,49 @@ class SystemConfigCreateSerializer(CustomModelSerializer):
raise CustomValidationError('已存在相同变量名') raise CustomValidationError('已存在相同变量名')
return value return value
class SystemConfigInitSerializer(CustomModelSerializer):
"""
初始化获取数信息(用于生成初始化json文件)
"""
children = serializers.SerializerMethodField()
def get_children(self, obj: SystemConfig):
data = []
instance = SystemConfig.objects.filter(parent_id=obj.id)
if instance:
serializer = SystemConfigInitSerializer(instance=instance, many=True)
data = serializer.data
return data
def save(self, **kwargs):
instance = super().save(**kwargs)
children = self.initial_data.get('children')
# 菜单表
if children:
for data in children:
data['parent'] = instance.id
filter_data = {
"key": data['key'],
"parent": data['parent']
}
instance_obj = SystemConfig.objects.filter(**filter_data).first()
serializer = SystemConfigInitSerializer(instance_obj, data=data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
return instance
class Meta:
model = SystemConfig
fields = ['parent', 'title', 'key', 'value', 'sort', 'status', 'data_options', 'form_item_type', 'rule',
'placeholder', 'setting', 'creator', 'dept_belong_id', 'children']
read_only_fields = ["id"]
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
}
class SystemConfigSerializer(CustomModelSerializer): class SystemConfigSerializer(CustomModelSerializer):
""" """
系统配置-序列化器 系统配置-序列化器
@ -112,17 +155,9 @@ class SystemConfigFilter(django_filters.rest_framework.FilterSet):
fields = ['id', 'parent', 'status', 'parent__isnull'] fields = ['id', 'parent', 'status', 'parent__isnull']
class SystemConfigViewSet(CustomModelViewSet): class SystemConfigViewSet(CustomModelViewSet):
""" """
系统配置接口 系统配置接口
list:查询
create:新增
update:修改
retrieve:详情
destroy:删除
""" """
queryset = SystemConfig.objects.order_by('sort', 'create_datetime') queryset = SystemConfig.objects.order_by('sort', 'create_datetime')
serializer_class = SystemConfigChinldernSerializer serializer_class = SystemConfigChinldernSerializer
@ -181,7 +216,6 @@ class SystemConfigViewSet(CustomModelViewSet):
return self.get_paginated_response(queryset) return self.get_paginated_response(queryset)
return SuccessResponse(msg="获取成功", data=queryset, total=len(queryset)) return SuccessResponse(msg="获取成功", data=queryset, total=len(queryset))
def get_relation_info(self, request): def get_relation_info(self, request):
""" """
查询关联的模板信息 查询关联的模板信息
@ -205,3 +239,18 @@ class SystemConfigViewSet(CustomModelViewSet):
return ErrorResponse(msg="未获取到关联信息") return ErrorResponse(msg="未获取到关联信息")
serializer = SystemConfigChinldernSerializer(queryset.parent) serializer = SystemConfigChinldernSerializer(queryset.parent)
return DetailResponse(msg="查询成功", data=serializer.data) return DetailResponse(msg="查询成功", data=serializer.data)
class InitSettingsViewSet(APIView):
"""
获取初始化配置
"""
authentication_classes = []
permission_classes = []
def get(self, request):
data = dispatch.get_system_config()
if not data:
dispatch.refresh_system_config()
data = dispatch.get_system_config()
return DetailResponse(data=data)

View File

@ -1,11 +1,3 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/3 003 0:30
@Remark: 用户管理
"""
import hashlib import hashlib
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
@ -13,6 +5,7 @@ from rest_framework import serializers
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from application import dispatch
from dvadmin.system.models import Users from dvadmin.system.models import Users
from dvadmin.utils.json_response import ErrorResponse, DetailResponse from dvadmin.utils.json_response import ErrorResponse, DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
@ -28,9 +21,26 @@ class UserSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Users model = Users
read_only_fields = ["id"] read_only_fields = ["id"]
exclude = ['password'] exclude = ["password"]
extra_kwargs = { extra_kwargs = {
'post': {'required': False}, "post": {"required": False},
}
class UsersInitSerializer(CustomModelSerializer):
"""
初始化获取数信息(用于生成初始化json文件)
"""
class Meta:
model = Users
fields = ["username", "email", 'mobile', 'avatar', "name", 'gender', 'user_type', "dept", 'user_type',
'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'creator', 'dept_belong_id',
'password', 'last_login', 'is_superuser']
read_only_fields = ['id']
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
} }
@ -38,14 +48,31 @@ class UserCreateSerializer(CustomModelSerializer):
""" """
用户新增-序列化器 用户新增-序列化器
""" """
username = serializers.CharField(max_length=50,
validators=[CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")]) username = serializers.CharField(
password = serializers.CharField(required=False, default=make_password( max_length=50,
hashlib.md5('admin123456'.encode(encoding='UTF-8')).hexdigest())) validators=[
CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")
],
)
password = serializers.CharField(
required=False,
)
def validate_password(self, value):
"""
对密码进行验证
"""
password = self.initial_data.get("password")
if password:
return make_password(value)
return value
def save(self, **kwargs): def save(self, **kwargs):
data = super().save(**kwargs) data = super().save(**kwargs)
data.post.set(self.initial_data.get('post', [])) data.dept_belong_id = data.dept_id
data.save()
data.post.set(self.initial_data.get("post", []))
return data return data
class Meta: class Meta:
@ -53,7 +80,7 @@ class UserCreateSerializer(CustomModelSerializer):
fields = "__all__" fields = "__all__"
read_only_fields = ["id"] read_only_fields = ["id"]
extra_kwargs = { extra_kwargs = {
'post': {'required': False}, "post": {"required": False},
} }
@ -61,21 +88,34 @@ class UserUpdateSerializer(CustomModelSerializer):
""" """
用户修改-序列化器 用户修改-序列化器
""" """
username = serializers.CharField(max_length=50,
validators=[CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")]) username = serializers.CharField(
password = serializers.CharField(required=False, allow_blank=True) max_length=50,
validators=[
CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")
],
)
# password = serializers.CharField(required=False, allow_blank=True)
mobile = serializers.CharField(
max_length=50,
validators=[
CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一")
],
)
def save(self, **kwargs): def save(self, **kwargs):
data = super().save(**kwargs) data = super().save(**kwargs)
data.post.set(self.initial_data.get('post', [])) data.dept_belong_id = data.dept_id
data.save()
data.post.set(self.initial_data.get("post", []))
return data return data
class Meta: class Meta:
model = Users model = Users
read_only_fields = ["id"] read_only_fields = ["id", "password"]
fields = "__all__" fields = "__all__"
extra_kwargs = { extra_kwargs = {
'post': {'required': False, 'read_only': True}, "post": {"required": False, "read_only": True},
} }
@ -83,22 +123,35 @@ class ExportUserProfileSerializer(CustomModelSerializer):
""" """
用户导出 序列化器 用户导出 序列化器
""" """
last_login = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False, read_only=True)
dept__deptName = serializers.CharField(source='dept.deptName', default='') last_login = serializers.DateTimeField(
dept__owner = serializers.CharField(source='dept.owner', default='') format="%Y-%m-%d %H:%M:%S", required=False, read_only=True
gender = serializers.CharField(source='get_gender_display', read_only=True) )
dept__deptName = serializers.CharField(source="dept.deptName", default="")
dept__owner = serializers.CharField(source="dept.owner", default="")
gender = serializers.CharField(source="get_gender_display", read_only=True)
class Meta: class Meta:
model = Users model = Users
fields = ('username', 'name', 'email', 'mobile', 'gender', 'is_active', 'last_login', 'dept__deptName', fields = (
'dept__owner') "username",
"name",
"email",
"mobile",
"gender",
"is_active",
"last_login",
"dept__deptName",
"dept__owner",
)
class UserProfileImportSerializer(CustomModelSerializer): class UserProfileImportSerializer(CustomModelSerializer):
def save(self, **kwargs): def save(self, **kwargs):
data = super().save(**kwargs) data = super().save(**kwargs)
password = hashlib.new('md5', str(self.initial_data.get('password', '')).encode(encoding='UTF-8')).hexdigest() password = hashlib.new(
"md5", str(self.initial_data.get("password", "")).encode(encoding="UTF-8")
).hexdigest()
data.set_password(password) data.set_password(password)
data.save() data.save()
return data return data
@ -106,15 +159,22 @@ class UserProfileImportSerializer(CustomModelSerializer):
def run_validation(self, data={}): def run_validation(self, data={}):
# 把excel 数据进行格式转换 # 把excel 数据进行格式转换
if type(data) is dict: if type(data) is dict:
data['role'] = str(data['role']).split(',') data["role"] = str(data["role"]).split(",")
data['dept_id'] = str(data['dept']).split(',') data["dept_id"] = str(data["dept"]).split(",")
data['gender'] = {'': '1', '': '0', '未知': '2'}.get(data['gender']) data["gender"] = {"": "1", "": "0", "未知": "2"}.get(data["gender"])
data['is_active'] = {'启用': True, '禁用': False}.get(data['is_active']) data["is_active"] = {"启用": True, "禁用": False}.get(data["is_active"])
return super().run_validation(data) return super().run_validation(data)
class Meta: class Meta:
model = Users model = Users
exclude = ('password', 'post', 'user_permissions', 'groups', 'is_superuser', 'date_joined') exclude = (
"password",
"post",
"user_permissions",
"groups",
"is_superuser",
"date_joined",
)
class UserViewSet(CustomModelViewSet): class UserViewSet(CustomModelViewSet):
@ -126,29 +186,49 @@ class UserViewSet(CustomModelViewSet):
retrieve:单例 retrieve:单例
destroy:删除 destroy:删除
""" """
queryset = Users.objects.exclude(is_superuser=1).all() queryset = Users.objects.exclude(is_superuser=1).all()
serializer_class = UserSerializer serializer_class = UserSerializer
create_serializer_class = UserCreateSerializer create_serializer_class = UserCreateSerializer
update_serializer_class = UserUpdateSerializer update_serializer_class = UserUpdateSerializer
filter_fields = ['name', 'username', 'gender', 'is_active', 'dept', 'user_type'] # filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"]
# filter_fields = { filter_fields = {
# 'name': ['icontains'], "name": ["icontains"],
# 'username': ['icontains'], "username": ["exact"],
# 'gender': ['icontains'], "gender": ["icontains"],
# 'is_active': ['icontains'], "is_active": ["icontains"],
# 'dept': ['exact'], "dept": ["exact"],
# } "user_type": ["exact"],
search_fields = ['username', 'name', 'gender', 'dept__name', 'role__name'] }
search_fields = ["username", "name", "gender", "dept__name", "role__name"]
# 导出 # 导出
export_field_label = ['用户账号', '用户名称', '用户邮箱', '手机号码', '用户性别', '帐号状态', '最后登录时间', '部门名称', '部门负责人'] export_field_label = [
"用户账号",
"用户名称",
"用户邮箱",
"手机号码",
"用户性别",
"帐号状态",
"最后登录时间",
"部门名称",
"部门负责人",
]
export_serializer_class = ExportUserProfileSerializer export_serializer_class = ExportUserProfileSerializer
# 导入 # 导入
import_serializer_class = UserProfileImportSerializer import_serializer_class = UserProfileImportSerializer
import_field_dict = {'username': '登录账号', 'name': '用户名称', 'email': '用户邮箱', 'mobile': '手机号码', import_field_dict = {
'gender': '用户性别(男/女/未知)', "username": "登录账号",
'is_active': '帐号状态(启用/禁用)', 'password': '登录密码', 'dept': '部门ID', 'role': '角色ID'} "name": "用户名称",
"email": "用户邮箱",
"mobile": "手机号码",
"gender": "用户性别(男/女/未知)",
"is_active": "帐号状态(启用/禁用)",
"password": "登录密码",
"dept": "部门ID",
"role": "角色ID",
}
@action(methods=['GET'], detail=True, permission_classes=[IsAuthenticated]) @action(methods=["GET"], detail=True, permission_classes=[IsAuthenticated])
def user_info(self, request): def user_info(self, request):
"""获取当前用户信息""" """获取当前用户信息"""
user = request.user user = request.user
@ -157,25 +237,25 @@ class UserViewSet(CustomModelViewSet):
"mobile": user.mobile, "mobile": user.mobile,
"gender": user.gender, "gender": user.gender,
"email": user.email, "email": user.email,
'avatar':user.avatar "avatar": user.avatar,
} }
return DetailResponse(data=result, msg="获取成功") return DetailResponse(data=result, msg="获取成功")
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated]) @action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
def update_user_info(self, request): def update_user_info(self, request):
"""修改当前用户信息""" """修改当前用户信息"""
user = request.user user = request.user
Users.objects.filter(id=user.id).update(**request.data) Users.objects.filter(id=user.id).update(**request.data)
return DetailResponse(data=None, msg="修改成功") return DetailResponse(data=None, msg="修改成功")
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated]) @action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
def change_password(self, request, *args, **kwargs): def change_password(self, request, *args, **kwargs):
"""密码修改""" """密码修改"""
instance = Users.objects.filter(id=kwargs.get('pk')).first() instance = Users.objects.filter(id=kwargs.get("pk")).first()
data = request.data data = request.data
old_pwd = data.get('oldPassword') old_pwd = data.get("oldPassword")
new_pwd = data.get('newPassword') new_pwd = data.get("newPassword")
new_pwd2 = data.get('newPassword2') new_pwd2 = data.get("newPassword2")
if instance: if instance:
if new_pwd != new_pwd2: if new_pwd != new_pwd2:
return ErrorResponse(msg="两次密码不匹配") return ErrorResponse(msg="两次密码不匹配")
@ -188,15 +268,26 @@ class UserViewSet(CustomModelViewSet):
else: else:
return ErrorResponse(msg="未获取到用户") return ErrorResponse(msg="未获取到用户")
@action(methods=['PUT'], detail=True) @action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
def reset_to_default_password(self, request, *args, **kwargs):
"""恢复默认密码"""
instance = Users.objects.filter(id=kwargs.get("pk")).first()
if instance:
instance.set_password(dispatch.get_system_config_values("base.default_password"))
instance.save()
return DetailResponse(data=None, msg="密码重置成功")
else:
return ErrorResponse(msg="未获取到用户")
@action(methods=["PUT"], detail=True)
def reset_password(self, request, pk): def reset_password(self, request, pk):
""" """
密码重置 密码重置
""" """
instance = Users.objects.filter(id=pk).first() instance = Users.objects.filter(id=pk).first()
data = request.data data = request.data
new_pwd = data.get('newPassword') new_pwd = data.get("newPassword")
new_pwd2 = data.get('newPassword2') new_pwd2 = data.get("newPassword2")
if instance: if instance:
if new_pwd != new_pwd2: if new_pwd != new_pwd2:
return ErrorResponse(msg="两次密码不匹配") return ErrorResponse(msg="两次密码不匹配")

View File

@ -1,5 +1,12 @@
# 初始化基类 # 初始化基类
import json
import os
from django.apps import apps
from rest_framework import request
from application import settings from application import settings
from dvadmin.system.models import Users
class CoreInitialize: class CoreInitialize:
@ -8,14 +15,43 @@ class CoreInitialize:
""" """
creator_id = None creator_id = None
reset = False reset = False
request = request
file_path = None
def __init__(self, reset=False, creator_id=None): def __init__(self, reset=False, creator_id=None, app=None):
""" """
reset: 是否重置初始化数据 reset: 是否重置初始化数据
creator_id: 创建人id creator_id: 创建人id
""" """
self.reset = reset or self.reset self.reset = reset or self.reset
self.creator_id = creator_id or self.creator_id self.creator_id = creator_id or self.creator_id
self.app = app or ''
self.request.user = Users.objects.order_by('create_datetime').first()
def init_base(self, Serializer, unique_fields=None):
model = Serializer.Meta.model
path_file = os.path.join(apps.get_app_config(self.app.split('.')[-1]).path, 'fixtures',
f'init_{Serializer.Meta.model._meta.model_name}.json')
if not os.path.isfile(path_file):
return
with open(path_file,encoding="utf-8") as f:
for data in json.load(f):
filter_data = {}
# 配置过滤条件,如果有唯一标识字段则使用唯一标识字段,否则使用全部字段
if unique_fields:
for field in unique_fields:
if field in data:
filter_data[field] = data[field]
else:
for key, value in data.items():
if isinstance(value, list) or value == None or value == '':
continue
filter_data[key] = value
instance = model.objects.filter(**filter_data).first()
serializer = Serializer(instance, data=data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
print(f"[{self.app}][{model._meta.model_name}]初始化完成")
def save(self, obj, data: list, name=None, no_reset=False): def save(self, obj, data: list, name=None, no_reset=False):
name = name or obj._meta.verbose_name name = name or obj._meta.verbose_name
@ -31,7 +67,7 @@ class CoreInitialize:
new_data = {} new_data = {}
for key, value in ele.items(): for key, value in ele.items():
# 判断传的 value 为 list 的多对多进行抽离使用set 进行更新 # 判断传的 value 为 list 的多对多进行抽离使用set 进行更新
if isinstance(value, list): if isinstance(value, list) and value and isinstance(value[0], int):
m2m_dict[key] = value m2m_dict[key] = value
else: else:
new_data[key] = value new_data[key] = value

View File

@ -7,8 +7,8 @@
@Remark: 自定义过滤器 @Remark: 自定义过滤器
""" """
import operator import operator
from collections import OrderedDict
import re import re
from collections import OrderedDict
from functools import reduce from functools import reduce
import six import six
@ -32,13 +32,13 @@ def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
:return: :return:
""" """
if not dept_all_list: if not dept_all_list:
dept_all_list = Dept.objects.all().values('id', 'parent') dept_all_list = Dept.objects.all().values("id", "parent")
if dept_list is None: if dept_list is None:
dept_list = [dept_id] dept_list = [dept_id]
for ele in dept_all_list: for ele in dept_all_list:
if ele.get('parent') == dept_id: if ele.get("parent") == dept_id:
dept_list.append(ele.get('id')) dept_list.append(ele.get("id"))
get_dept(ele.get('id'), dept_all_list, dept_list) get_dept(ele.get("id"), dept_all_list, dept_list)
return list(set(dept_list)) return list(set(dept_list))
@ -54,20 +54,26 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
4. 只为仅本人数据权限时只返回过滤本人数据并且部门为自己本部门(考虑到用户会变部门只能看当前用户所在的部门数据) 4. 只为仅本人数据权限时只返回过滤本人数据并且部门为自己本部门(考虑到用户会变部门只能看当前用户所在的部门数据)
5. 自定数据权限 获取部门根据部门过滤 5. 自定数据权限 获取部门根据部门过滤
""" """
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
""" """
接口白名单是否认证数据权限 接口白名单是否认证数据权限
""" """
api = request.path # 当前请求接口 api = request.path # 当前请求接口
method = request.method # 当前请求方法 method = request.method # 当前请求方法
methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] methodList = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
method = methodList.index(method) method = methodList.index(method)
# ***接口白名单*** # ***接口白名单***
api_white_list = ApiWhiteList.objects.filter(enable_datasource=False).values(permission__api=F('url'), api_white_list = ApiWhiteList.objects.filter(enable_datasource=False).values(
permission__method=F('method')) permission__api=F("url"), permission__method=F("method")
)
api_white_list = [ api_white_list = [
str(item.get('permission__api').replace('{id}', '.*?')) + ":" + str(item.get('permission__method')) for item str(item.get("permission__api").replace("{id}", ".*?"))
in api_white_list if item.get('permission__api')] + ":"
+ str(item.get("permission__method"))
for item in api_white_list
if item.get("permission__api")
]
for item in api_white_list: for item in api_white_list:
new_api = api + ":" + str(method) new_api = api + ":" + str(method)
matchObj = re.match(item, new_api, re.M | re.I) matchObj = re.match(item, new_api, re.M | re.I)
@ -81,16 +87,16 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
""" """
if request.user.is_superuser == 0: if request.user.is_superuser == 0:
# 0. 获取用户的部门id没有部门则返回空 # 0. 获取用户的部门id没有部门则返回空
user_dept_id = getattr(request.user, 'dept_id', None) user_dept_id = getattr(request.user, "dept_id", None)
if not user_dept_id: if not user_dept_id:
return queryset.none() return queryset.none()
# 1. 判断过滤的数据是否有创建人所在部门 "dept_belong_id" 字段 # 1. 判断过滤的数据是否有创建人所在部门 "dept_belong_id" 字段
if not getattr(queryset.model, 'dept_belong_id', None): if not getattr(queryset.model, "dept_belong_id", None):
return queryset return queryset
# 2. 如果用户没有关联角色则返回本部门数据 # 2. 如果用户没有关联角色则返回本部门数据
if not hasattr(request.user, 'role'): if not hasattr(request.user, "role"):
return queryset.filter(dept_belong_id=user_dept_id) return queryset.filter(dept_belong_id=user_dept_id)
# 3. 根据所有角色 获取所有权限范围 # 3. 根据所有角色 获取所有权限范围
@ -99,28 +105,41 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
# (2, "本部门数据权限"), # (2, "本部门数据权限"),
# (3, "全部数据权限"), # (3, "全部数据权限"),
# (4, "自定数据权限") # (4, "自定数据权限")
role_list = request.user.role.filter(status=1).values('admin', 'data_range') role_list = request.user.role.filter(status=1).values("admin", "data_range")
dataScope_list = [] # 权限范围列表 dataScope_list = [] # 权限范围列表
for ele in role_list: for ele in role_list:
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据 # 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
if 3 == ele.get('data_range') or ele.get('admin') == True: if 3 == ele.get("data_range") or ele.get("admin") == True:
return queryset return queryset
dataScope_list.append(ele.get('data_range')) dataScope_list.append(ele.get("data_range"))
dataScope_list = list(set(dataScope_list)) dataScope_list = list(set(dataScope_list))
# 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据) # 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据)
if 0 in dataScope_list: if 0 in dataScope_list:
return queryset.filter(creator=request.user, dept_belong_id=user_dept_id) return queryset.filter(
creator=request.user, dept_belong_id=user_dept_id
)
# 5. 自定数据权限 获取部门,根据部门过滤 # 5. 自定数据权限 获取部门,根据部门过滤
dept_list = [] dept_list = []
for ele in dataScope_list: for ele in dataScope_list:
if ele == 4: if ele == 4:
dept_list.extend(request.user.role.filter(status=1).values_list('dept__id', flat=True)) dept_list.extend(
request.user.role.filter(status=1).values_list(
"dept__id", flat=True
)
)
elif ele == 2: elif ele == 2:
dept_list.append(user_dept_id) dept_list.append(user_dept_id)
elif ele == 1: elif ele == 1:
dept_list.extend(get_dept(user_dept_id, )) dept_list.append(user_dept_id)
dept_list.extend(
get_dept(
user_dept_id,
)
)
if queryset.model._meta.model_name == 'dept':
return queryset.filter(id__in=list(set(dept_list)))
return queryset.filter(dept_belong_id__in=list(set(dept_list))) return queryset.filter(dept_belong_id__in=list(set(dept_list)))
else: else:
return queryset return queryset
@ -128,11 +147,11 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
class CustomDjangoFilterBackend(DjangoFilterBackend): class CustomDjangoFilterBackend(DjangoFilterBackend):
lookup_prefixes = { lookup_prefixes = {
'^': 'istartswith', "^": "istartswith",
'=': 'iexact', "=": "iexact",
'@': 'search', "@": "search",
'$': 'iregex', "$": "iregex",
'~': 'icontains' "~": "icontains",
} }
def construct_search(self, field_name): def construct_search(self, field_name):
@ -140,12 +159,14 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
if lookup: if lookup:
field_name = field_name[1:] field_name = field_name[1:]
else: else:
lookup = 'icontains' lookup = "icontains"
return LOOKUP_SEP.join([field_name, lookup]) return LOOKUP_SEP.join([field_name, lookup])
def find_filter_lookups(self, orm_lookups, search_term_key): def find_filter_lookups(self, orm_lookups, search_term_key):
for lookup in orm_lookups: for lookup in orm_lookups:
if lookup.find(search_term_key) >= 0: # if lookup.find(search_term_key) >= 0:
# 修复条件搜索错误 bug
if lookup == search_term_key:
return lookup return lookup
return None return None
@ -153,39 +174,43 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
""" """
Return the `FilterSet` class used to filter the queryset. Return the `FilterSet` class used to filter the queryset.
""" """
filterset_class = getattr(view, 'filterset_class', None) filterset_class = getattr(view, "filterset_class", None)
filterset_fields = getattr(view, 'filterset_fields', None) filterset_fields = getattr(view, "filterset_fields", None)
# TODO: remove assertion in 2.1 # TODO: remove assertion in 2.1
if filterset_class is None and hasattr(view, 'filter_class'): if filterset_class is None and hasattr(view, "filter_class"):
utils.deprecate( utils.deprecate(
"`%s.filter_class` attribute should be renamed `filterset_class`." "`%s.filter_class` attribute should be renamed `filterset_class`."
% view.__class__.__name__) % view.__class__.__name__
filterset_class = getattr(view, 'filter_class', None) )
filterset_class = getattr(view, "filter_class", None)
# TODO: remove assertion in 2.1 # TODO: remove assertion in 2.1
if filterset_fields is None and hasattr(view, 'filter_fields'): if filterset_fields is None and hasattr(view, "filter_fields"):
utils.deprecate( utils.deprecate(
"`%s.filter_fields` attribute should be renamed `filterset_fields`." "`%s.filter_fields` attribute should be renamed `filterset_fields`."
% view.__class__.__name__) % view.__class__.__name__
filterset_fields = getattr(view, 'filter_fields', None) )
filterset_fields = getattr(view, "filter_fields", None)
if filterset_class: if filterset_class:
filterset_model = filterset_class._meta.model filterset_model = filterset_class._meta.model
# FilterSets do not need to specify a Meta class # FilterSets do not need to specify a Meta class
if filterset_model and queryset is not None: if filterset_model and queryset is not None:
assert issubclass(queryset.model, filterset_model), \ assert issubclass(
'FilterSet model %s does not match queryset model %s' % \ queryset.model, filterset_model
(filterset_model, queryset.model) ), "FilterSet model %s does not match queryset model %s" % (
filterset_model,
queryset.model,
)
return filterset_class return filterset_class
if filterset_fields and queryset is not None: if filterset_fields and queryset is not None:
MetaBase = getattr(self.filterset_base, 'Meta', object) MetaBase = getattr(self.filterset_base, "Meta", object)
class AutoFilterSet(self.filterset_base): class AutoFilterSet(self.filterset_base):
@classmethod @classmethod
def get_filters(cls): def get_filters(cls):
""" """
@ -206,6 +231,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
field = get_model_field(cls._meta.model, field_name) field = get_model_field(cls._meta.model, field_name)
from django.db import models from django.db import models
from timezone_field import TimeZoneField from timezone_field import TimeZoneField
# 不进行 过滤的model 类 # 不进行 过滤的model 类
if isinstance(field, (models.JSONField, TimeZoneField)): if isinstance(field, (models.JSONField, TimeZoneField)):
continue continue
@ -222,16 +248,20 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
continue continue
if field is not None: if field is not None:
filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr) filters[filter_name] = cls.filter_for_field(
field, field_name, lookup_expr
)
# Allow Meta.fields to contain declared filters *only* when a list/tuple # Allow Meta.fields to contain declared filters *only* when a list/tuple
if isinstance(cls._meta.fields, (list, tuple)): if isinstance(cls._meta.fields, (list, tuple)):
undefined = [f for f in undefined if f not in cls.declared_filters] undefined = [
f for f in undefined if f not in cls.declared_filters
]
if undefined: if undefined:
raise TypeError( raise TypeError(
"'Meta.fields' must not contain non-model field names: %s" "'Meta.fields' must not contain non-model field names: %s"
% ', '.join(undefined) % ", ".join(undefined)
) )
# Add in declared filters. This is necessary since we don't enforce adding # Add in declared filters. This is necessary since we don't enforce adding
@ -251,14 +281,21 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
filterset = self.get_filterset(request, queryset, view) filterset = self.get_filterset(request, queryset, view)
if filterset is None: if filterset is None:
return queryset return queryset
if filterset.__class__.__name__ == 'AutoFilterSet': if filterset.__class__.__name__ == "AutoFilterSet":
queryset = filterset.queryset queryset = filterset.queryset
orm_lookups = [] orm_lookups = []
for search_field in filterset.filters: for search_field in filterset.filters:
if isinstance(filterset.filters[search_field], CharFilter): if isinstance(filterset.filters[search_field], CharFilter):
orm_lookups.append(self.construct_search(six.text_type(search_field))) orm_lookups.append(
self.construct_search(six.text_type(search_field))
)
else: else:
orm_lookups.append(search_field) orm_lookups.append(search_field)
orm_lookups = (
orm_lookups
if isinstance(filterset.__class__._meta.fields, (list, tuple))
else filterset.filters.keys()
)
conditions = [] conditions = []
queries = [] queries = []
for search_term_key in filterset.data.keys(): for search_term_key in filterset.data.keys():

View File

@ -15,8 +15,7 @@ def import_to_data(file_url, field_data):
:return: :return:
""" """
# 读取excel 文件 # 读取excel 文件
file_path = os.path.join(settings.MEDIA_ROOT, file_url) file_path_dir = os.path.join(settings.BASE_DIR, file_url)
file_path_dir = os.path.join(settings.BASE_DIR, file_path)
workbook = openpyxl.load_workbook(file_path_dir) workbook = openpyxl.load_workbook(file_path_dir)
table = workbook[workbook.sheetnames[0]] table = workbook[workbook.sheetnames[0]]
# 创建一个空列表存储Excel的数据 # 创建一个空列表存储Excel的数据

View File

@ -190,12 +190,15 @@ def get_ip_analysis(ip):
} }
if ip != 'unknown' and ip: if ip != 'unknown' and ip:
if getattr(settings, 'ENABLE_LOGIN_ANALYSIS_LOG', True): if getattr(settings, 'ENABLE_LOGIN_ANALYSIS_LOG', True):
res = requests.post(url='https://ip.django-vue-admin.com/ip/analysis', data=json.dumps({"ip": ip})) try:
res = requests.get(url='https://ip.django-vue-admin.com/ip/analysis', params={"ip": ip}, timeout=5)
if res.status_code == 200: if res.status_code == 200:
res_data = res.json() res_data = res.json()
if res_data.get('code') == 0: if res_data.get('code') == 0:
data = res_data.get('data') data = res_data.get('data')
return data return data
except Exception as e:
print(e)
return data return data

View File

@ -17,37 +17,46 @@ from dvadmin.system.models import Users
from django_restql.mixins import DynamicFieldsMixin from django_restql.mixins import DynamicFieldsMixin
class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer): class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
""" """
增强DRF的ModelSerializer,可自动更新模型的审计字段记录 增强DRF的ModelSerializer,可自动更新模型的审计字段记录
(1)self.request能获取到rest_framework.request.Request对象 (1)self.request能获取到rest_framework.request.Request对象
""" """
# 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖 # 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖
modifier_field_id = 'modifier' modifier_field_id = "modifier"
modifier_name = serializers.SerializerMethodField(read_only=True) modifier_name = serializers.SerializerMethodField(read_only=True)
def get_modifier_name(self, instance): def get_modifier_name(self, instance):
if not hasattr(instance, 'modifier'): if not hasattr(instance, "modifier"):
return None return None
queryset = Users.objects.filter(username=instance.modifier).values_list('name', flat=True).first() queryset = (
Users.objects.filter(id=instance.modifier)
.values_list("name", flat=True)
.first()
)
if queryset: if queryset:
return queryset return queryset
return None return None
# 创建人的审计字段名称, 默认creator, 继承使用时可自定义覆盖 # 创建人的审计字段名称, 默认creator, 继承使用时可自定义覆盖
creator_field_id = 'creator' creator_field_id = "creator"
creator_name = serializers.SlugRelatedField(slug_field="name", source="creator", read_only=True) creator_name = serializers.SlugRelatedField(
slug_field="name", source="creator", read_only=True
)
# 数据所属部门字段 # 数据所属部门字段
dept_belong_id_field_name = 'dept_belong_id' dept_belong_id_field_name = "dept_belong_id"
# 添加默认时间返回格式 # 添加默认时间返回格式
create_datetime = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False, read_only=True) create_datetime = serializers.DateTimeField(
update_datetime = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False) format="%Y-%m-%d %H:%M:%S", required=False, read_only=True
)
update_datetime = serializers.DateTimeField(
format="%Y-%m-%d %H:%M:%S", required=False
)
def __init__(self, instance=None, data=empty, request=None, **kwargs): def __init__(self, instance=None, data=empty, request=None, **kwargs):
super().__init__(instance, data, **kwargs) super().__init__(instance, data, **kwargs)
self.request: Request = request or self.context.get('request', None) self.request: Request = request or self.context.get("request", None)
def save(self, **kwargs): def save(self, **kwargs):
return super().save(**kwargs) return super().save(**kwargs)
@ -60,30 +69,36 @@ class CustomModelSerializer(DynamicFieldsMixin,ModelSerializer):
if self.creator_field_id in self.fields.fields: if self.creator_field_id in self.fields.fields:
validated_data[self.creator_field_id] = self.request.user validated_data[self.creator_field_id] = self.request.user
if self.dept_belong_id_field_name in self.fields.fields and validated_data.get( if (
self.dept_belong_id_field_name, None) is None: self.dept_belong_id_field_name in self.fields.fields
validated_data[self.dept_belong_id_field_name] = getattr(self.request.user, 'dept_id', None) and validated_data.get(self.dept_belong_id_field_name, None) is None
):
validated_data[self.dept_belong_id_field_name] = getattr(
self.request.user, "dept_id", None
)
return super().create(validated_data) return super().create(validated_data)
def update(self, instance, validated_data): def update(self, instance, validated_data):
if self.request: if self.request:
if hasattr(self.instance, self.modifier_field_id): if hasattr(self.instance, self.modifier_field_id):
setattr(self.instance, self.modifier_field_id, self.get_request_user_id()) setattr(
self.instance, self.modifier_field_id, self.get_request_user_id()
)
return super().update(instance, validated_data) return super().update(instance, validated_data)
def get_request_username(self): def get_request_username(self):
if getattr(self.request, 'user', None): if getattr(self.request, "user", None):
return getattr(self.request.user, 'username', None) return getattr(self.request.user, "username", None)
return None return None
def get_request_name(self): def get_request_name(self):
if getattr(self.request, 'user', None): if getattr(self.request, "user", None):
return getattr(self.request.user, 'name', None) return getattr(self.request.user, "name", None)
return None return None
def get_request_user_id(self): def get_request_user_id(self):
if getattr(self.request, 'user', None): if getattr(self.request, "user", None):
return getattr(self.request.user, 'id', None) return getattr(self.request.user, "id", None)
return None return None
# @cached_property # @cached_property
@ -132,4 +147,3 @@ class CustomModelSerializer(DynamicFieldsMixin,ModelSerializer):
# fields.pop(field, None) # fields.pop(field, None)
# #
# return fields # return fields

View File

@ -1,7 +1,7 @@
version: "3" version: "3"
services: services:
DVAdmin-web: dvadmin-web:
container_name: DVAdmin-web container_name: dvadmin-web
ports: ports:
- "8080:8080" - "8080:8080"
build: build:
@ -15,20 +15,20 @@ services:
- "8080" - "8080"
networks: networks:
network: network:
ipv4_address: 177.7.0.11 ipv4_address: 177.8.0.11
DVAdmin-django: dvadmin-django:
build: build:
context: . context: .
dockerfile: ./docker_env/django/Dockerfile dockerfile: ./docker_env/django/Dockerfile
container_name: DVAdmin-django container_name: dvadmin-django
working_dir: /backend working_dir: /backend
# 打开mysql 时,打开此选项 # 打开mysql 时,打开此选项
# depends_on: # depends_on:
# - DVAdmin-mysql # - dvadmin-mysql
environment: environment:
PYTHONUNBUFFERED: 1 PYTHONUNBUFFERED: 1
DATABASE_HOST: DVAdmin-mysql DATABASE_HOST: dvadmin-mysql
TZ: Asia/Shanghai TZ: Asia/Shanghai
volumes: volumes:
- ./backend:/backend - ./backend:/backend
@ -40,11 +40,11 @@ services:
restart: always restart: always
networks: networks:
network: network:
ipv4_address: 177.7.0.12 ipv4_address: 177.8.0.12
# DVAdmin-mysql: # dvadmin-mysql:
# image: mysql:5.7 # image: mysql:5.7
# container_name: DVAdmin-mysql # container_name: dvadmin-mysql
# #使用该参数container内的root拥有真正的root权限否则container内的root只是外部的一个普通用户权限 # #使用该参数container内的root拥有真正的root权限否则container内的root只是外部的一个普通用户权限
# #设置为true不然数据卷可能挂载不了启动不起 # #设置为true不然数据卷可能挂载不了启动不起
## privileged: true ## privileged: true
@ -66,22 +66,22 @@ services:
# - "./docker_env/mysql/logs:/logs" # - "./docker_env/mysql/logs:/logs"
# networks: # networks:
# network: # network:
# ipv4_address: 177.7.0.13 # ipv4_address: 177.8.0.13
# 如果使用celery 插件,请自行打开此注释 # 如果使用celery 插件,请自行打开此注释
# DVAdmin-celery: # dvadmin-celery:
# build: # build:
# context: . # context: .
# dockerfile: ./docker_env/celery/Dockerfile # dockerfile: ./docker_env/celery/Dockerfile
# # image: django:2.2 # # image: django:2.2
# container_name: DVAdmin-celery # container_name: dvadmin-celery
# working_dir: /backend # working_dir: /backend
# depends_on: # depends_on:
# - DVAdmin-mysql # - dvadmin-mysql
# environment: # environment:
# PYTHONUNBUFFERED: 1 # PYTHONUNBUFFERED: 1
# DATABASE_HOST: DVAdmin-mysql # DATABASE_HOST: dvadmin-mysql
# TZ: Asia/Shanghai # TZ: Asia/Shanghai
# volumes: # volumes:
# - ./backend:/backend # - ./backend:/backend
@ -89,12 +89,12 @@ services:
# restart: always # restart: always
# networks: # networks:
# network: # network:
# ipv4_address: 177.7.0.14 # ipv4_address: 177.8.0.14
networks: networks:
network: network:
ipam: ipam:
driver: default driver: default
config: config:
- subnet: '177.7.0.0/16' - subnet: '177.8.0.0/16'

View File

@ -7,7 +7,7 @@ server {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Proto https;
set_real_ip_from 177.7.0.0/16; set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For; real_ip_header X-Forwarded-For;
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html index.php index.htm; index index.html index.php index.htm;
@ -18,7 +18,7 @@ server {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
set_real_ip_from 177.7.0.0/16; set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For; real_ip_header X-Forwarded-For;
rewrite ^/api/(.*)$ /$1 break; #重写 rewrite ^/api/(.*)$ /$1 break; #重写
proxy_pass http://177.7.0.12:8000/; # 设置代理服务器的协议和地址 proxy_pass http://177.7.0.12:8000/; # 设置代理服务器的协议和地址

View File

@ -1,7 +1,7 @@
# 开发环境 # 开发环境
NODE_ENV=development NODE_ENV=development
# 页面 title 前缀 # 页面 title 前缀
VUE_APP_TITLE=DVAdmin VUE_APP_TITLE=企业级后台管理系统
# 启用权限管理 # 启用权限管理
VUE_APP_PM_ENABLED = true VUE_APP_PM_ENABLED = true
# 后端接口地址及端口(域名) # 后端接口地址及端口(域名)

View File

@ -6,7 +6,7 @@ NODE_ENV=production
# 标记当前构建方式 # 标记当前构建方式
VUE_APP_BUILD_MODE=PREVIEW VUE_APP_BUILD_MODE=PREVIEW
# 页面 title 前缀 # 页面 title 前缀
VUE_APP_TITLE=DVAdmin VUE_APP_TITLE=企业级后台管理系统
# 显示源码按钮 # 显示源码按钮
VUE_APP_SCOURCE_LINK=FALSE VUE_APP_SCOURCE_LINK=FALSE

View File

@ -1,7 +1,7 @@
# 测试环境 # 测试环境
# 页面 title 前缀 # 页面 title 前缀
VUE_APP_TITLE=DVAdmin VUE_APP_TITLE=企业级后台管理系统
# 启用权限管理 # 启用权限管理
VUE_APP_PM_ENABLED = true VUE_APP_PM_ENABLED = true
# 后端接口地址及端口(域名) # 后端接口地址及端口(域名)

3
web/.gitignore vendored
View File

@ -1,7 +1,8 @@
.DS_Store .DS_Store
node_modules node_modules
/dist /dist
package-lock.json
yarn.lock
# local env files # local env files
.env.local .env.local
.env.*.local .env.*.local

View File

@ -41,6 +41,7 @@
"ua-parser-js": "^0.7.20", "ua-parser-js": "^0.7.20",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-i18n": "^8.15.1", "vue-i18n": "^8.15.1",
"vue-infinite-scroll": "^2.0.2",
"vue-router": "^3.1.3", "vue-router": "^3.1.3",
"vue-splitpane": "^1.0.6", "vue-splitpane": "^1.0.6",
"vuex": "^3.1.2", "vuex": "^3.1.2",

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@ -58,10 +58,201 @@
text-decoration: none; text-decoration: none;
} }
.d2-home__loading { #loader-wrapper {
height: 32px; position: fixed;
width: 32px; top: 0;
margin-bottom: 20px; left: 0;
width: 100%;
height: 100%;
z-index: 999999;
}
#loader {
display: block;
position: relative;
left: 50%;
top: 50%;
width: 120px;
height: 120px;
margin: -75px 0 0 -75px;
border-radius: 50%;
border: 3px solid transparent;
/* COLOR 1 */
border-top-color: #FFF;
-webkit-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-ms-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-moz-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-o-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
animation: spin 2s linear infinite;
/* Chrome, Firefox 16+, IE 10+, Opera */
z-index: 1001;
}
#loader:before {
content: "";
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
border-radius: 50%;
border: 3px solid transparent;
/* COLOR 2 */
border-top-color: #FFF;
-webkit-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-moz-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-o-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-ms-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
animation: spin 3s linear infinite;
/* Chrome, Firefox 16+, IE 10+, Opera */
}
#loader:after {
content: "";
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
/* COLOR 3 */
-moz-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-o-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-ms-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-webkit-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
animation: spin 1.5s linear infinite;
/* Chrome, Firefox 16+, IE 10+, Opera */
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(0deg);
/* IE 9 */
transform: rotate(0deg);
/* Firefox 16+, IE 10+, Opera */
}
100% {
-webkit-transform: rotate(360deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(360deg);
/* IE 9 */
transform: rotate(360deg);
/* Firefox 16+, IE 10+, Opera */
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(0deg);
/* IE 9 */
transform: rotate(0deg);
/* Firefox 16+, IE 10+, Opera */
}
100% {
-webkit-transform: rotate(360deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(360deg);
/* IE 9 */
transform: rotate(360deg);
/* Firefox 16+, IE 10+, Opera */
}
}
#loader-wrapper .loader-section {
position: fixed;
top: 0;
width: 51%;
height: 100%;
background: #49a9ee;
/* Old browsers */
z-index: 1000;
-webkit-transform: translateX(0);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateX(0);
/* IE 9 */
transform: translateX(0);
/* Firefox 16+, IE 10+, Opera */
}
#loader-wrapper .loader-section.section-left {
left: 0;
}
#loader-wrapper .loader-section.section-right {
right: 0;
}
/* Loaded */
.loaded #loader-wrapper .loader-section.section-left {
-webkit-transform: translateX(-100%);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateX(-100%);
/* IE 9 */
transform: translateX(-100%);
/* Firefox 16+, IE 10+, Opera */
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader-wrapper .loader-section.section-right {
-webkit-transform: translateX(100%);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateX(100%);
/* IE 9 */
transform: translateX(100%);
/* Firefox 16+, IE 10+, Opera */
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader {
opacity: 0;
-webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
}
.loaded #loader-wrapper {
visibility: hidden;
-webkit-transform: translateY(-100%);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateY(-100%);
/* IE 9 */
transform: translateY(-100%);
/* Firefox 16+, IE 10+, Opera */
-webkit-transition: all 0.3s 1s ease-out;
transition: all 0.3s 1s ease-out;
}
/* JavaScript Turned Off */
.no-js #loader-wrapper {
display: none;
}
.no-js h1 {
color: #222222;
}
#loader-wrapper .load_title {
font-family: 'Open Sans';
color: #FFF;
font-size: 14px;
width: 100%;
text-align: center;
z-index: 9999999999999;
position: absolute;
top: 60%;
opacity: 1;
line-height: 30px;
}
#loader-wrapper .load_title span {
font-weight: normal;
font-style: italic;
font-size: 14px;
color: #FFF;
opacity: 0.5;
} }
</style> </style>
<script> <script>
@ -78,15 +269,11 @@
</strong> </strong>
</noscript> </noscript>
<div id="app"> <div id="app">
<div class="d2-home"> <div id="loader-wrapper">
<div class="d2-home__main"> <div id="loader"></div>
<img class="d2-home__loading" src="./image/loading/loading-spin.svg" alt="loading"> <div class="loader-section section-left"></div>
</div> <div class="loader-section section-right"></div>
<div class="d2-home__footer"> <div class="load_title">正在加载中,请耐心等待...</div>
<a href="https://gitee.com/liqianglog/django-vue-admin" target="_blank">
https://gitee.com/liqianglog/django-vue-admin
</a>
</div>
</div> </div>
</div> </div>
<!-- 使用 CDN 加速的 JS 文件配置在 vue.config.js --> <!-- 使用 CDN 加速的 JS 文件配置在 vue.config.js -->

View File

@ -69,3 +69,65 @@
*::-webkit-scrollbar-thumb:hover { *::-webkit-scrollbar-thumb:hover {
background-color: #bbb; background-color: #bbb;
} }
.el-drawer__header{
border-bottom: 1px solid #e8e8e8;
border-radius: 4px 4px 0 0;
margin-bottom: 16px;
}
/*设置抽屉样式*/
.el-drawer__header span{
margin: 0;
color: rgba(0,0,0,.85);
font-weight: 500;
font-size: 16px;
line-height: 22px;
margin-bottom: 16px;
}
.el-drawer__header .el-tag{
background-color: #ecf5ff;
display: inline-block;
font-size: 12px;
color: #409eff;
border: 1px solid #d9ecff;
border-radius: 4px;
box-sizing: border-box;
white-space: nowrap;
height: 24px;
padding: 0 8px;
line-height: 22px;
}
.el-drawer__close-btn{
position: absolute;
top: 0;
right: 0;
z-index: 10;
display: block;
width: 56px;
height: 56px;
padding: 0;
color: rgba(0,0,0,.45);
font-weight: 700;
font-size: 16px;
font-style: normal;
line-height: 56px;
text-align: center;
text-transform: none;
text-decoration: none;
background: transparent;
border: 0;
outline: 0;
cursor: pointer;
transition: color .3s;
text-rendering: auto;
}
.el-dialog__close{
font-weight: 700;
font-size: 16px;
}
.el-drawer__body .d2-container-full {
border: none!important;
/* border-top: none; */
/* border-bottom: none; */
}

View File

@ -3,7 +3,8 @@
v-if="show" v-if="show"
class="d2-source" class="d2-source"
:class="{ 'd2-source--active': isActive }" :class="{ 'd2-source--active': isActive }"
@click="handleClick"> @click="handleClick"
>
<d2-icon name="code" /> 本页源码 <d2-icon name="code" /> 本页源码
</div> </div>
</template> </template>
@ -51,7 +52,7 @@ export default {
$paddingLR: 15px; $paddingLR: 15px;
$paddingTB: 7px; $paddingTB: 7px;
$fontSize: 12px; $fontSize: 12px;
$rightOuter: $paddingLR / 2; $rightOuter: calc($paddingLR / 2);
opacity: 0; opacity: 0;
position: fixed; position: fixed;
z-index: 9999; z-index: 9999;
@ -63,17 +64,17 @@ export default {
border-radius: $borderRadius; border-radius: $borderRadius;
padding: $paddingTB $paddingLR; padding: $paddingTB $paddingLR;
padding-right: $borderRadius + $paddingLR; padding-right: $borderRadius + $paddingLR;
background-color: rgba(#000, .7); background-color: rgba(#000, 0.7);
border: 1px solid #000; border: 1px solid #000;
color: #FFF; color: #fff;
transition: all .3s; transition: all 0.3s;
@extend %unable-select; @extend %unable-select;
&.d2-source--active { &.d2-source--active {
opacity: 1; opacity: 1;
} }
&:hover { &:hover {
right: -$borderRadius; right: -$borderRadius;
background-color: rgba(#000, .9); background-color: rgba(#000, 0.9);
} }
} }
</style> </style>

View File

@ -242,7 +242,7 @@ export default {
...defaultElProps.columns ...defaultElProps.columns
] ]
} else { } else {
defaultElProps.radioConfig = this.elProps.radioConfig defaultElProps.radioConfig = this.elProps
? this.elProps.radioConfig ? this.elProps.radioConfig
: {} : {}
defaultElProps.columns = [ defaultElProps.columns = [

View File

@ -19,7 +19,7 @@ export function getButtonSettings (objectSettings) {
} }
}) })
} }
// v2.0.2 中已弃用改为 vm.dictionary('button_status_bool')
// 启用 true/ 禁用 false // 启用 true/ 禁用 false
export const BUTTON_STATUS_BOOL = getButtonSettings([{ label: '启用', value: true }, { label: '禁用', value: false }]) export const BUTTON_STATUS_BOOL = getButtonSettings([{ label: '启用', value: true }, { label: '禁用', value: false }])
@ -30,3 +30,5 @@ export const BUTTON_STATUS_NUMBER = getButtonSettings([{ label: '启用', value:
export const BUTTON_WHETHER_NUMBER = getButtonSettings([{ label: '', value: 1 }, { label: '', value: 0 }]) export const BUTTON_WHETHER_NUMBER = getButtonSettings([{ label: '', value: 1 }, { label: '', value: 0 }])
// true/ false // true/ false
export const BUTTON_WHETHER_BOOL = getButtonSettings([{ label: '', value: true }, { label: '', value: false }]) export const BUTTON_WHETHER_BOOL = getButtonSettings([{ label: '', value: true }, { label: '', value: false }])
// 用户类型
export const USER_TYPE = getButtonSettings([{ label: '后台用户', value: 0 }, { label: '前台用户', value: 1 }])

View File

@ -0,0 +1,118 @@
export default {
'image-uploader': {
form: { component: { name: 'd2p-file-uploader', props: { elProps: { listType: 'picture-card', accept: '.png,.jpeg,.jpg,.ico,.bmp,.gif' } } } },
component: { name: 'd2p-images-format' },
view: {
component: { props: { height: 100, width: 100 } }
},
align: 'center',
// 提交时,处理数据
valueResolve (row, col) {
const value = row[col.key]
if (value != null) {
if (value.length >= 0) {
if (value instanceof Array) {
row[col.key] = value.toString()
} else {
row[col.key] = value
}
} else {
row[col.key] = null
}
}
},
// 接收时,处理数据
valueBuilder (row, col) {
const value = row[col.key]
if (value != null && value) {
row[col.key] = value.split(',')
}
}
},
'avatar-uploader': {
form: { component: { name: 'd2p-file-uploader', props: { elProps: { limit: 1, listType: 'avatar', accept: '.png,.jpeg,.jpg,.ico,.bmp,.gif', showFileList: false } } } },
component: { name: 'd2p-images-format' },
view: {
component: { props: { height: 100, width: 100 } }
},
align: 'center',
// 提交时,处理数据
valueResolve (row, col) {
const value = row[col.key]
if (value != null) {
if (value.length >= 0) {
if (value instanceof Array) {
row[col.key] = value.toString()
} else {
row[col.key] = value
}
} else {
row[col.key] = null
}
}
},
// 接收时,处理数据
valueBuilder (row, col) {
const value = row[col.key]
if (value != null && value) {
row[col.key] = value.split(',')
}
}
},
'file-uploader': {
form: { component: { name: 'd2p-file-uploader', props: { elProps: { listType: 'text' } } } },
component: { name: 'd2p-files-format' },
// 提交时,处理数据
valueResolve (row, col) {
const value = row[col.key]
if (value != null) {
if (value.length >= 0) {
if (value instanceof Array) {
row[col.key] = value.toString()
} else {
row[col.key] = value
}
} else {
row[col.key] = null
}
}
},
// 接收时,处理数据
valueBuilder (row, col) {
const value = row[col.key]
if (value != null && value) {
row[col.key] = value.split(',')
}
}
},
'avatar-cropper': {
form: { component: { name: 'd2p-cropper-uploader', props: { accept: '.png,.jpeg,.jpg,.ico,.bmp,.gif', cropper: { viewMode: 1 } } } },
component: { name: 'd2p-images-format' },
align: 'center',
view: {
component: { props: { height: 100, width: 100 } }
},
// 提交时,处理数据
valueResolve (row, col) {
const value = row[col.key]
if (value != null) {
if (value.length >= 0) {
if (value instanceof Array) {
row[col.key] = value.toString()
} else {
row[col.key] = value
}
} else {
row[col.key] = null
}
}
},
// 接收时,处理数据
valueBuilder (row, col) {
const value = row[col.key]
if (value != null && value) {
row[col.key] = value.split(',')
}
}
}
}

View File

@ -15,7 +15,11 @@ import {
import { request } from '@/api/service' import { request } from '@/api/service'
import util from '@/libs/util' import util from '@/libs/util'
import XEUtils from 'xe-utils' import XEUtils from 'xe-utils'
import store from '@/store/index'
import { urlPrefix as deptPrefix } from '@/views/system/dept/api' import { urlPrefix as deptPrefix } from '@/views/system/dept/api'
import types from '@/config/d2p-extends/types'
import { checkPlugins } from '@/views/plugins'
const uploadUrl = util.baseURL() + 'api/system/file/'
/** /**
// vxe0 // vxe0
@ -31,7 +35,11 @@ Vue.use(d2CrudX, { name: 'd2-crud-x' })
// // 官方版此处为演示与官方版共存而引入全新项目中可以用d2-crud-x完全替代官方版 // // 官方版此处为演示与官方版共存而引入全新项目中可以用d2-crud-x完全替代官方版
// Vue.use(d2Crud) // Vue.use(d2Crud)
/**
* @description 校验插件是否安装
* @param {String} pluginName 插件名称
*/
Vue.prototype.checkPlugins = checkPlugins
// 引入d2CrudPlus // 引入d2CrudPlus
Vue.use(d2CrudPlus, { Vue.use(d2CrudPlus, {
starTip: false, starTip: false,
@ -105,7 +113,7 @@ Vue.use(D2pFullEditor, {
Vue.use(D2pDemoExtend) Vue.use(D2pDemoExtend)
Vue.use(D2pFileUploader) Vue.use(D2pFileUploader)
Vue.use(D2pUploader, { Vue.use(D2pUploader, {
defaultType: 'cos', defaultType: 'form',
cos: { cos: {
domain: 'https://d2p-demo-1251260344.cos.ap-guangzhou.myqcloud.com', domain: 'https://d2p-demo-1251260344.cos.ap-guangzhou.myqcloud.com',
bucket: 'd2p-demo-1251260344', bucket: 'd2p-demo-1251260344',
@ -159,15 +167,49 @@ Vue.use(D2pUploader, {
domain: 'http://d2p.file.veryreader.com' domain: 'http://d2p.file.veryreader.com'
}, },
form: { form: {
action: util.baseURL() + 'upload/form/upload', action: uploadUrl,
name: 'file' name: 'file',
data: {}, // 上传附加参数
headers () {
return {
Authorization: 'JWT ' + util.cookies.get('token')
}
},
type: 'form',
successHandle (ret, option) {
if (ret.data === null || ret.data === '') {
throw new Error('上传失败')
}
return { url: util.baseURL() + ret.data.url, key: option.data.key }
},
withCredentials: false // 是否带cookie
} }
}) })
d2CrudPlus.util.columnResolve.addTypes(types)
// 修改官方字段类型 // 修改官方字段类型
const selectType = d2CrudPlus.util.columnResolve.getType('select') const selectType = d2CrudPlus.util.columnResolve.getType('select')
selectType.component.props.color = 'auto' // 修改官方的字段类型设置为支持自动染色 selectType.component.props.color = 'auto' // 修改官方的字段类型设置为支持自动染色
// 获取字典配置
Vue.prototype.dictionary = function (name) {
return store.state.d2admin.dictionary.data[name]
}
// 获取字典label值
Vue.prototype.getDictionaryLabel = function (name, value) {
const data = store.state.d2admin.dictionary.data[name]
if (data && data instanceof Array) {
for (var i = 0, len = data.length; i < len; i++) {
if (data[i].value === value) {
return data[i].label
}
}
return ''
}
return store.state.d2admin.dictionary.data[name]
}
// 获取系统配置
Vue.prototype.systemConfig = function (name) {
return store.state.d2admin.settings.data[name]
}
// 默认Columns 结尾 showForm显示在form中showTable显示在table中 // 默认Columns 结尾 showForm显示在form中showTable显示在table中
Vue.prototype.commonEndColumns = function (param = {}) { Vue.prototype.commonEndColumns = function (param = {}) {
/** /**
@ -257,7 +299,7 @@ Vue.prototype.commonEndColumns = function (param = {}) {
type: 'table-selector', type: 'table-selector',
dict: { dict: {
cache: true, cache: true,
url: '/api/system/dept/?limit=999&status=1', url: deptPrefix,
isTree: true, isTree: true,
value: 'id', // 数据字典中value字段的属性名 value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名 label: 'name', // 数据字典中label字段的属性名
@ -268,6 +310,7 @@ Vue.prototype.commonEndColumns = function (param = {}) {
}) => { }) => {
return request({ return request({
url: url, url: url,
params: { limit: 999, status: 1 }
}).then(ret => { }).then(ret => {
return ret.data.data return ret.data.data
}) })

View File

@ -29,9 +29,20 @@
:headers="headers" :headers="headers"
:limit="1" :limit="1"
:disabled="fileList.length === 1" :disabled="fileList.length === 1"
:on-success="handleAvatarSuccess"> :on-success="handleAvatarSuccess"
<!-- <el-image v-if="userInfo.avatar" :src="userInfo.avatar" :preview-src-list="[userInfo.avatar]" style="width: 100px;height: 100px" alt="头像"></el-image>--> >
<!-- <i v-else class="el-icon-plus avatar-uploader-icon" style="width: 100px;height: 100px"></i>--> <!-- <el-image
v-if="userInfo.avatar"
:src="userInfo.avatar"
:preview-src-list="[userInfo.avatar]"
style="width: 100px; height: 100px"
alt="头像"
></el-image>
<i
v-else
class="el-icon-plus avatar-uploader-icon"
style="width: 100px; height: 100px"
></i> -->
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
</el-upload> </el-upload>
</el-form-item> </el-form-item>
@ -159,9 +170,7 @@ export default {
}, },
userInforules: { userInforules: {
name: [{ required: true, message: '请输入昵称', trigger: 'blur' }], name: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
mobile: [ mobile: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确手机号' }]
{ pattern: /^1[3|4|5|7|8]\d{9}$/, message: '请输入正确手机号' }
]
}, },
userPasswordInfo: { userPasswordInfo: {
oldPassword: '', oldPassword: '',
@ -281,7 +290,7 @@ export default {
handleAvatarSuccess (res, file) { handleAvatarSuccess (res, file) {
console.log(11, res) console.log(11, res)
this.fileList = [{ url: util.baseURL() + res.data.url, name: file.name }] this.fileList = [{ url: util.baseURL() + res.data.url, name: file.name }]
this.userInfo.avatar = util.baseURL() + res.data.url; this.userInfo.avatar = util.baseURL() + res.data.url
} }
} }
} }

View File

@ -1,6 +1,8 @@
import cookies from './util.cookies' import cookies from './util.cookies'
import db from './util.db' import db from './util.db'
import log from './util.log' import log from './util.log'
import dayjs from 'dayjs'
const util = { const util = {
cookies, cookies,
db, db,
@ -34,7 +36,7 @@ util.open = function (url) {
*/ */
util.baseURL = function () { util.baseURL = function () {
var baseURL = process.env.VUE_APP_API var baseURL = process.env.VUE_APP_API
if (window.pluginsAll && window.pluginsAll.indexOf('dvadmin-tenant') !== -1) { if (window.pluginsAll && window.pluginsAll.indexOf('dvadmin-tenant-web') !== -1) {
// document.domain // document.domain
var host = baseURL.split('/')[2] var host = baseURL.split('/')[2]
var prot = host.split(':')[1] || 80 var prot = host.split(':')[1] || 80
@ -46,5 +48,33 @@ util.baseURL = function () {
} }
return baseURL return baseURL
} }
/**
* 自动生成ID
*/
util.autoCreateCode = function () {
return dayjs().format('YYYYMMDDHHmmssms') + Math.round(Math.random() * 80 + 20)
}
/**
* 自动生成短 ID
*/
util.autoShortCreateCode = function () {
var Num = ''
for (var i = 0; i < 4; i++) {
Num += Math.floor(Math.random() * 10)
}
return dayjs().format('YYMMDD') + Num
}
/**
* 生产随机字符串
*/
util.randomString = function (e) {
e = e || 32
var t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
var a = t.length
var n = ''
for (let i = 0; i < e; i++) n += t.charAt(Math.floor(Math.random() * a))
return n
}
export default util export default util

View File

@ -42,6 +42,11 @@ new Vue({
store, store,
i18n, i18n,
render: h => h(App), render: h => h(App),
beforeCreate () {
// 初始化配置
this.$store.dispatch('d2admin/settings/load')
this.$store.dispatch('d2admin/dictionary/load')
},
created () { created () {
// 处理路由 得到每一级的路由设置 // 处理路由 得到每一级的路由设置

View File

@ -29,43 +29,7 @@ function supplementPath (menu) {
export const menuHeader = supplementPath([]) export const menuHeader = supplementPath([])
// export const menuHeader = supplementPath([
// { path: '/index', title: '控制台', icon: 'home' },
// {
// title: '页面',
// icon: 'folder-o',
// children: [
// { path: '/page1', title: '页面 1' },
// { path: '/page2', title: '页面 2' },
// { path: '/page3', title: '页面 3' }
// ]
// }
// ])
export const menuAside = supplementPath([]) export const menuAside = supplementPath([])
// export const menuAside = supplementPath([
// { path: '/index', title: '控制台', icon: 'home' },
// {
// title: '系统管理',
// icon: 'folder-o',
// children: [
// // { path: '/page1', title: '页面 1' },
// // { path: '/page2', title: '页面 2' },
// // { path: '/page3', title: '页面 3' },
// { path: '/menu', title: '菜单' },
// { path: '/user', title: '用户' },
// { path: '/button', title: '按钮' },
// { path: '/role', title: '角色' },
// { path: '/dept', title: '部门' },
// { path: '/rolePermission', title: '角色权限' },
// {
// title: '日志管理', children: [
// { path: '/operationLog', title: '操作日志' },
// ]
// },
// ]
// }
// ])
// 请求菜单数据,用于解析路由和侧边栏菜单 // 请求菜单数据,用于解析路由和侧边栏菜单
export const getMenu = function () { export const getMenu = function () {

View File

@ -65,6 +65,8 @@ router.beforeEach(async (to, from, next) => {
store.commit('d2admin/page/init', routes) store.commit('d2admin/page/init', routes)
router.addRoutes(routes) router.addRoutes(routes)
// routes.forEach(route => router.addRoute(route))
const menu = handleAsideMenu(ret) const menu = handleAsideMenu(ret)
const aside = handleAsideMenu(ret.filter(value => value.visible === true)) const aside = handleAsideMenu(ret.filter(value => value.visible === true))
store.commit('d2admin/menu/asideSet', aside) // 设置侧边栏菜单 store.commit('d2admin/menu/asideSet', aside) // 设置侧边栏菜单

View File

@ -18,20 +18,22 @@ export default {
/** /**
* @description 登录 * @description 登录
* @param {Object} context * @param {Object} context
* @param {Object} data * @param {Object} payload username {String} 用户账号
* @param {Object} data username {String} 用户账号 * @param {Object} payload password {String} 密码
* @param {Object} data password {String} 密码 * @param {Object} payload route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
* @param {Object} data route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
* @param {Object} data request function 请求方法
*/ */
async login ({ dispatch }, data) { async login ({ dispatch }, {
let request = data.request username = '',
if (request) { password = '',
delete data.request captcha = '',
} else { captchaKey = ''
request = SYS_USER_LOGIN } = {}) {
} let res = await SYS_USER_LOGIN({
let res = await request(data) username,
password,
captcha,
captchaKey
})
// 设置 cookie 一定要存 uuid token 两个 cookie // 设置 cookie 一定要存 uuid token 两个 cookie
// 整个系统依赖这两个数据进行校验和存储 // 整个系统依赖这两个数据进行校验和存储
// uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复 // uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复

View File

@ -0,0 +1,94 @@
import { request } from '@/api/service'
export const urlPrefix = '/api/init/dictionary/'
export const BUTTON_VALUE_TO_COLOR_MAPPING = {
1: 'success',
true: 'success',
0: 'danger',
false: 'danger',
Search: 'warning', // 查询
Update: 'primary', // 编辑
Create: 'success', // 新增
Retrieve: 'info', // 单例
Delete: 'danger' // 删除
}
export function getButtonSettings (objectSettings) {
return objectSettings.map(item => {
return {
label: item.label,
value: item.value,
color: item.color || BUTTON_VALUE_TO_COLOR_MAPPING[item.value]
}
})
}
// 系统配置
export default {
namespaced: true,
state: {
data: {} // 字典值集合
},
actions: {
/**
* @description 本地加载配置
* @param {Object} context
* @param {String} key
*/
async load ({ state, dispatch, commit }, key = 'all') {
const query = { dictionary_key: key }
request({
url: urlPrefix,
params: query,
method: 'get'
}).then(async res => {
// store 赋值
var newData = {}
if (key === 'all') {
res.data.data.map(data => {
data.children.map((children, index) => {
switch (children.type) {
case 1:
children.value = Number(children.value)
break
case 6:
children.value = children.value === 'true'
break
}
})
newData[data.value] = getButtonSettings(data.children)
})
state.data = newData
} else {
state.data = res.data.data[key]
}
})
}
/**
* @description 获取配置
* @param {Object} state state
* @param {Object} dispatch dispatch
* @param {String} key 字典值
* @param {String} isCache 是否缓存
*/
},
mutations: {
/**
* @description 设置配置
* @param {Object} state state
* @param {Boolean} key active
* @param {Boolean} value active
*/
async set (state, key, value) {
state.data[key] = value
},
/**
* @description 获取配置
* @param {Object} state state
* @param {Boolean} key active
*/
async get (state, key) {
return state.data[key]
}
}
}

View File

@ -0,0 +1,57 @@
import { request } from '@/api/service'
export const urlPrefix = '/api/init/settings/'
// 系统配置
export default {
namespaced: true,
state: {
data: {}
},
actions: {
/**
* @description 请求最新配置
* @param {Object} context
*/
async init ({ state, dispatch, commit }) {
// 请求配置
request({
url: urlPrefix,
method: 'get'
}).then(async res => {
// 赋值
await dispatch('d2admin/db/set', {
dbName: 'sys',
path: 'settings.init',
value: res.data,
user: true
}, { root: true })
dispatch('load')
})
},
/**
* @description 本地加载配置
* @param {Object} context
*/
async load ({ state, dispatch, commit }) {
// store 赋值
state.data = await dispatch('d2admin/db/get', {
dbName: 'sys',
path: 'settings.init',
defaultValue: {},
user: true
}, { root: true })
}
},
mutations: {
/**
* @description 获取配置
* @param {Object} state state
* @param {String} key active
* @param {Object} value active
*/
async get (state, key, value) {
return state[key]
}
}
}

View File

@ -1,6 +1,4 @@
import { request } from '@/api/service' import { request } from '@/api/service'
import util from '@/libs/util'
const uploadUrl = process.env.VUE_APP_API + '/api/system/img/'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
return { return {
pageOptions: { pageOptions: {
@ -85,20 +83,6 @@ export const crudOptions = (vm) => {
form: { form: {
component: { component: {
props: { props: {
uploader: {
action: uploadUrl,
name: 'file',
headers: {
Authorization: 'JWT ' + util.cookies.get('token')
},
type: 'form',
successHandle (ret, option) {
if (ret.data == null || ret.data === '') {
throw new Error('上传失败')
}
return { url: ret.data.data.url, key: option.data.key }
}
},
elProps: { // 与el-uploader 配置一致 elProps: { // 与el-uploader 配置一致
multiple: false, multiple: false,
limit: 5 // 限制5个文件 limit: 5 // 限制5个文件
@ -108,26 +92,6 @@ export const crudOptions = (vm) => {
span: 24 span: 24
}, },
helper: '限制文件大小不能超过50k' helper: '限制文件大小不能超过50k'
},
valueResolve (row, col) {
const value = row[col.key]
if (value != null && value instanceof Array) {
if (value.length >= 0) {
row[col.key] = value[0]
} else {
row[col.key] = null
}
}
},
component: {
props: {
buildUrl (value, item) {
if (value && value.indexOf('http') !== 0) {
return '/api/upload/form/download?key=' + value
}
return value
}
}
} }
}, },
{ {
@ -139,20 +103,6 @@ export const crudOptions = (vm) => {
form: { form: {
component: { component: {
props: { props: {
uploader: {
action: uploadUrl,
name: 'file',
headers: {
Authorization: 'JWT ' + util.cookies.get('token')
},
type: 'form',
successHandle (ret, option) {
if (ret.data == null || ret.data === '') {
throw new Error('上传失败')
}
return { url: ret.data.data.url, key: option.data.key }
}
},
elProps: { // 与el-uploader 配置一致 elProps: { // 与el-uploader 配置一致
multiple: false, multiple: false,
limit: 5 // 限制5个文件 limit: 5 // 限制5个文件
@ -162,26 +112,6 @@ export const crudOptions = (vm) => {
span: 24 span: 24
}, },
helper: '限制文件大小不能超过50k' helper: '限制文件大小不能超过50k'
},
valueResolve (row, col) {
const value = row[col.key]
if (value != null && value instanceof Array) {
if (value.length >= 0) {
row[col.key] = value[0]
} else {
row[col.key] = null
}
}
},
component: {
props: {
buildUrl (value, item) {
if (value && value.indexOf('http') !== 0) {
return '/api/upload/form/download?key=' + value
}
return value
}
}
} }
}, },
{ {

View File

@ -1,5 +1,3 @@
import { BUTTON_STATUS_BOOL } from '@/config/button'
import { request } from '@/api/service' import { request } from '@/api/service'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
@ -234,7 +232,7 @@ export const crudOptions = (vm) => {
width: 90, width: 90,
type: 'radio', type: 'radio',
dict: { dict: {
data: BUTTON_STATUS_BOOL data: vm.dictionary('button_status_bool')
}, },
form: { form: {
value: true, value: true,

View File

@ -1,167 +0,0 @@
/*
* @创建文件时间: 2021-06-03 00:34:42
* @Auther: 猿小天
* @最后修改人: 猿小天
* @最后修改时间: 2021-11-19 22:18:47
* 联系Qq:1638245306
* @文件介绍: 权限配置
*/
export const crudOptions = (vm) => {
return {
pageOptions: {
compact: true
},
options: {
tableType: 'vxe-table',
rowKey: true,
width: '100%',
height: '100%' // 表格高度100%, 使用toolbar必须设置
},
rowHandle: {
edit: {
thin: true,
text: '编辑',
disabled () {
return !vm.hasPermissions('Update')
}
},
remove: {
thin: true,
text: '删除',
disabled () {
return !vm.hasPermissions('Delete')
}
}
},
indexRow: { // 或者直接传true,不显示title不居中
title: '序号',
align: 'center',
width: 100
},
viewOptions: {
disabled: true,
componentType: 'form'
},
formOptions: {
defaultSpan: 24, // 默认的表单 span
width: '35%'
},
columns: [{
title: '关键词',
key: 'search',
show: false,
disabled: true,
search: {
disabled: false
},
form: {
disabled: true,
component: {
placeholder: '请输入关键字'
}
},
view: { // 查看对话框组件的单独配置
disabled: true
}
},
{
title: 'ID',
key: 'id',
show: false,
width: 90,
form: {
disabled: true
}
},
{
title: '名称',
key: 'name',
sortable: true,
search: {
disabled: false
},
type: 'input',
form: {
rules: [ // 表单校验规则
{ required: true, message: '名称必填项' }
],
component: {
placeholder: '请输入名称'
},
itemProps: {
class: { yxtInput: true }
}
}
},
{
title: 'key值',
key: 'value',
sortable: true,
search: {
disabled: false
},
type: 'input',
form: {
rules: [ // 表单校验规则
{ required: true, message: 'key值必填项' }
],
component: {
placeholder: '请输入key值'
},
itemProps: {
class: { yxtInput: true }
}
}
},
{
title: '备注',
key: 'description',
show: false,
search: {
disabled: true
},
type: 'textarea',
form: {
component: {
placeholder: '请输入内容',
showWordLimit: true,
maxlength: '200',
props: {
type: 'textarea'
}
}
}
}, {
title: '创建人',
show: false,
width: 100,
key: 'modifier_name',
form: {
disabled: true
}
},
{
title: '更新时间',
key: 'update_datetime',
width: 160,
type: 'datetime',
sortable: true,
form: {
disabled: true
}
},
{
title: '创建时间',
key: 'create_datetime',
width: 160,
type: 'datetime',
sortable: true,
form: {
disabled: true
}
}
]
}
}

View File

@ -16,9 +16,16 @@
<el-form-item label="表单类型" prop="form_item_type"> <el-form-item label="表单类型" prop="form_item_type">
<el-select v-model="form.form_item_type" placeholder="请选择" clearable> <el-select v-model="form.form_item_type" placeholder="请选择" clearable>
<el-option :label="item.label" :value="item.value" :key="index" <el-option :label="item.label" :value="item.value" :key="index"
v-for="(item,index) in typeOptions"></el-option> v-for="(item,index) in dictionary('config_form_type')"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item
v-if="[4,5,6].indexOf(form.form_item_type)>-1"
label="字典key"
prop="setting"
:rules="[{required: true,message: '不能为空'}]">
<el-input v-model="form.setting" placeholder="请输入dictionary中key值" clearable></el-input>
</el-form-item>
<div v-if="[13,14].indexOf(form.form_item_type)>-1"> <div v-if="[13,14].indexOf(form.form_item_type)>-1">
<associationTable ref="associationTable" v-model="form.setting" <associationTable ref="associationTable" v-model="form.setting"
@updateVal="associationTableUpdate"></associationTable> @updateVal="associationTableUpdate"></associationTable>
@ -81,8 +88,8 @@ export default {
message: '请输入' message: '请输入'
}, },
{ {
pattern: /^[A-Za-z0-9]+$/, pattern: /^[A-Za-z0-9_]+$/,
message: '只支持字母和数字的输入' message: '请输入数字、字母或下划线'
} }
], ],
form_item_type: [ form_item_type: [
@ -94,23 +101,6 @@ export default {
}, },
// //
parentOptions: [], parentOptions: [],
//
typeOptions: [
{ value: 0, label: '短文本' },
{ value: 1, label: '长文本' },
{ value: 2, label: '数字框' },
{ value: 3, label: '选择框' },
{ value: 4, label: '单选框' },
{ value: 5, label: '复选框' },
{ value: 6, label: '日期' },
{ value: 7, label: '日期时间' },
{ value: 8, label: '时间' },
{ value: 9, label: '图片' },
{ value: 10, label: '文件' },
{ value: 11, label: '数组' },
{ value: 12, label: '关联表' },
{ value: 13, label: '关联表(多选)' }
],
ruleOptions: [ ruleOptions: [
{ {
label: '必填项', label: '必填项',
@ -119,6 +109,10 @@ export default {
{ {
label: '邮箱', label: '邮箱',
value: '{ "type": "email", "message": "请输入正确的邮箱地址"}' value: '{ "type": "email", "message": "请输入正确的邮箱地址"}'
},
{
label: 'URL地址',
value: '{ "type": "url", "message": "请输入正确的URL地址"}'
} }
] ]
} }
@ -162,7 +156,6 @@ export default {
const that = this const that = this
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
if (that.$refs.associationTable) { if (that.$refs.associationTable) {
console.log(that.$refs.associationTable.onSubmit())
if (!that.$refs.associationTable.onSubmit()) { if (!that.$refs.associationTable.onSubmit()) {
// eslint-disable-next-line prefer-promise-reject-errors // eslint-disable-next-line prefer-promise-reject-errors
return reject(false) return reject(false)

View File

@ -5,9 +5,9 @@
<el-col :span="12">变量值</el-col> <el-col :span="12">变量值</el-col>
<el-col :span="4" :offset="4">变量名</el-col> <el-col :span="4" :offset="4">变量名</el-col>
</el-row> </el-row>
<el-form ref="form" :model="form" label-width="100px" label-position="left" style="margin-top: 20px"> <el-form ref="form" :model="form" label-width="140px" label-position="left" style="margin-top: 20px">
<el-form-item :label="item.title" :prop="['array'].indexOf(item.form_item_type_label) >-1?'':item.key" <el-form-item :label="item.title" :prop="['array'].indexOf(item.form_item_type_label) >-1?'':item.key"
:key="index" :rules="item.rule" :key="index" :rules="item.rule || []"
v-for="(item,index) in formList" v-for="(item,index) in formList"
> >
@ -19,17 +19,81 @@
<el-input-number :key="index" v-else-if="item.form_item_type_label === 'number'" v-model="form[item.key]" <el-input-number :key="index" v-else-if="item.form_item_type_label === 'number'" v-model="form[item.key]"
:min="0"></el-input-number> :min="0"></el-input-number>
<!-- datetimedatetime -->
<el-date-picker
v-else-if="['datetime','date','time'].indexOf(item.form_item_type_label) >-1"
v-model="form[item.key]"
:key="index"
:type="item.form_item_type_label"
:placeholder="item.placeholder">
</el-date-picker>
<!-- select -->
<el-select
:key="index"
v-else-if="item.form_item_type_label === 'select'"
v-model="form[item.key]"
:placeholder="item.placeholder"
clearable
>
<el-option
v-for="item in dictionary(item.setting) || []"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<!-- checkbox -->
<el-checkbox-group
:key="index"
v-else-if="item.form_item_type_label === 'checkbox'"
v-model="form[item.key]"
:placeholder="item.placeholder"
>
<el-checkbox
v-for="item in dictionary(item.setting) || []"
:key="item.value"
:label="item.value"
:value="item.value">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
<!-- radio -->
<el-radio-group
:key="index"
v-else-if="item.form_item_type_label === 'radio'"
v-model="form[item.key]"
:placeholder="item.placeholder"
clearable
>
<el-radio
v-for="item in dictionary(item.setting) || []"
:key="item.value"
:label="item.label"
:value="item.value">
{{ item.label }}
</el-radio>
</el-radio-group>
<!-- switch -->
<el-switch
:key="index"
v-else-if="item.form_item_type_label === 'switch'"
v-model="form[item.key]"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
<!-- 图片 --> <!-- 图片 -->
<div v-else-if="['img','imgs'].indexOf(item.form_item_type_label) >-1" :key="index"> <div v-else-if="['img','imgs'].indexOf(item.form_item_type_label) >-1" :key="index">
<el-upload <el-upload
:action="imgUploadUrl" :action="uploadUrl"
:headers="uploadHeaders" :headers="uploadHeaders"
name="url" name="file"
:accept="'image/*'" :accept="'image/*'"
:on-preview="handlePictureCardPreview" :on-preview="handlePictureCardPreview"
:on-success="(response, file, fileList)=>{handleUploadSuccess(response, file, fileList,item.key)}" :on-success="(response, file, fileList)=>{handleUploadSuccess(response, file, fileList,item.key)}"
:on-error="handleError" :on-error="handleError"
:on-exceed="handleExceed" :on-exceed="handleExceed"
:before-remove="(file, fileList)=>{beforeRemove(file, fileList, item.key)}"
:multiple="item.form_item_type_label!=='img'"
:limit="item.form_item_type_label==='img'?1:5" :limit="item.form_item_type_label==='img'?1:5"
:ref="'imgUpload_'+item.key" :ref="'imgUpload_'+item.key"
:data-keyname="item.key" :data-keyname="item.key"
@ -43,6 +107,30 @@
<img width="100%" :src="dialogImageUrl" alt=""> <img width="100%" :src="dialogImageUrl" alt="">
</el-dialog> </el-dialog>
</div> </div>
<!-- 文件 -->
<div v-else-if="['file'].indexOf(item.form_item_type_label) >-1" :key="index">
<el-upload
:action="uploadUrl"
:headers="uploadHeaders"
name="file"
:on-preview="handlePictureCardPreview"
:on-success="(response, file, fileList)=>{handleUploadSuccess(response, file, fileList,item.key)}"
:on-error="handleError"
:on-exceed="handleExceed"
:before-remove="(file, fileList)=>{beforeRemove(file, fileList, item.key)}"
:limit="5"
:ref="'fileUpload_'+item.key"
:data-keyname="item.key"
:file-list="item.value"
list-type="picture-card"
>
<i class="el-icon-plus"></i>
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
</el-upload>
<el-dialog :visible.sync="dialogImgVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
</div>
<!-- 关联表 --> <!-- 关联表 -->
<div v-else-if="['foreignkey','manytomany'].indexOf(item.form_item_type_label) >-1" :key="index"> <div v-else-if="['foreignkey','manytomany'].indexOf(item.form_item_type_label) >-1" :key="index">
<table-selector <table-selector
@ -56,7 +144,7 @@
label: item.setting.field, label: item.setting.field,
}" }"
:pagination="true" :pagination="true"
:multiple="item.form_item_type_label==='manytomany'?true:false" :multiple="item.form_item_type_label ==='manytomany'"
></table-selector> ></table-selector>
</div> </div>
<!-- 数组 --> <!-- 数组 -->
@ -171,8 +259,7 @@ export default {
} }
] ]
}, },
imgUploadUrl: util.baseURL() + 'api/system/img/', uploadUrl: util.baseURL() + 'api/system/file/',
// imgUploadUrl: 'http://public.yuanxiaotian.com:8000/api/system/img/',
uploadHeaders: { uploadHeaders: {
Authorization: 'JWT ' + util.cookies.get('token') Authorization: 'JWT ' + util.cookies.get('token')
}, },
@ -191,7 +278,15 @@ export default {
const form = {} const form = {}
for (const item of data) { for (const item of data) {
const key = item.key const key = item.key
if (item.value) {
form[key] = item.value form[key] = item.value
} else {
if ([5, 12, 14].indexOf(item.form_item_type) !== -1) {
form[key] = []
} else {
form[key] = undefined
}
}
if (item.form_item_type_label === 'array') { if (item.form_item_type_label === 'array') {
that.$nextTick(() => { that.$nextTick(() => {
const tableName = 'xTable_' + key const tableName = 'xTable_' + key
@ -237,9 +332,10 @@ export default {
item.value = tableData item.value = tableData
} }
// //
if (keys[index] === item.key) { keys.map((mapKey, mapIndex) => {
if (mapKey === item.key) {
if (item.form_item_type_label !== 'array') { if (item.form_item_type_label !== 'array') {
item.value = values[index] item.value = values[mapIndex]
} }
// //
if (['img', 'imgs'].indexOf(item.form_item_type_label) > -1) { if (['img', 'imgs'].indexOf(item.form_item_type_label) > -1) {
@ -251,10 +347,10 @@ export default {
} }
} }
} }
})
} }
that.$refs.form.clearValidate() that.$refs.form.clearValidate()
that.$refs.form.validate((valid) => { that.$refs.form.validate((valid) => {
console.log(this.formList)
if (valid) { if (valid) {
api.saveContent(this.options.id, api.saveContent(this.options.id,
this.formList).then(res => { this.formList).then(res => {
@ -272,10 +368,16 @@ export default {
const $table = this.$refs[tableName][0] const $table = this.$refs[tableName][0]
const { tableData } = $table.getTableData() const { tableData } = $table.getTableData()
const tableLength = tableData.length const tableLength = tableData.length
if (tableLength !== 0) { if (tableLength === 0) {
const { row: newRow } = $table.insert()
console.log(newRow)
} 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 {
const { row: newRow } = $table.insert()
console.log(newRow)
} }
} }
}, },
@ -292,13 +394,6 @@ export default {
this.dialogImageUrl = file.url this.dialogImageUrl = file.url
this.dialogImgVisible = true this.dialogImgVisible = true
}, },
//
submitUpload (key) {
const refName = 'imgUpload_' + key
const ref = this.$refs[refName][0]
ref.submit()
this.uploadImgKey = key
},
// //
// //
isImage (fileName) { isImage (fileName) {
@ -314,20 +409,20 @@ export default {
msg msg
} = response } = response
if (code === 2000) { if (code === 2000) {
const { url } = response.data.data const { url } = response.data
const { name } = file const { name } = file
const type = that.isImage(name) const type = that.isImage(name)
if (!type) { if (!type) {
this.$message.error('只允许上传图片') this.$message.error('只允许上传图片')
} else { } else {
const uploadImgKey = that.form[imgKey] const uploadImgKey = that.form[imgKey]
if (!uploadImgKey) { if (!uploadImgKey || uploadImgKey === '') {
that.form[imgKey] = [] that.form[imgKey] = []
} }
// console.log(len) // console.log(len)
const dict = { const dict = {
name: name, name: name,
url: url url: util.baseURL() + url
} }
that.form[imgKey].push(dict) that.form[imgKey].push(dict)
} }
@ -342,6 +437,14 @@ export default {
// //
handleExceed () { handleExceed () {
this.$message.error('超过文件上传数量') this.$message.error('超过文件上传数量')
},
//
beforeRemove (file, fileList, key) {
var index = 0
this.form[key].map((value, inx) => {
if (value.uid === file.uid) index = inx
})
this.form[key].splice(index, 1)
} }
}, },
mounted () { mounted () {

View File

@ -58,17 +58,23 @@
:label="item.title" :label="item.title"
:name="item.key" :name="item.key"
> >
<formContent :options="item" :editableTabsItem="item"></formContent> <span slot="label" v-if="item.icon"><i :class="item.icon" style="font-weight: 1000;font-size: 16px;"></i></span>
<el-row v-if="item.icon">
<el-col :offset="4" :span="8">
<addContent></addContent>
</el-col>
</el-row>
<formContent v-else :options="item" :editableTabsItem="item"></formContent>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</d2-container> </d2-container>
</template> </template>
<script> <script>
import addTabs from './components/addTabs' import addTabs from '@/views/system/config/components/addTabs'
import * as api from './api' import * as api from './api'
import addContent from './components/addContent' import addContent from '@/views/system/config/components/addContent'
import formContent from './components/formContent' import formContent from '@/views/system/config/components/formContent'
export default { export default {
name: 'config', name: 'config',
@ -81,7 +87,7 @@ export default {
return { return {
tabsDrawer: false, tabsDrawer: false,
contentDrawer: false, contentDrawer: false,
editableTabsValue: 'basic', editableTabsValue: 'base',
editableTabs: [], editableTabs: [],
tabIndex: 2 tabIndex: 2
} }
@ -93,6 +99,11 @@ export default {
parent__isnull: true parent__isnull: true
}).then(res => { }).then(res => {
const { data } = res.data const { data } = res.data
data.push({
title: '无',
icon: 'el-icon-plus',
key: 'null'
})
this.editableTabs = data this.editableTabs = data
}) })
} }

View File

@ -1,30 +1,18 @@
/*
* @创建文件时间: 2021-06-01 22:41:21
* @Auther: 猿小天
* @最后修改人: 猿小天
* @最后修改时间: 2021-09-26 21:17:30
* 联系Qq:1638245306
* @文件介绍: 部门管理接口
*/
import { request } from '@/api/service' import { request } from '@/api/service'
import XEUtils from 'xe-utils'
export const urlPrefix = '/api/system/dept/' export const urlPrefix = '/api/system/dept/'
/** /**
* 列表查询 * 列表查询
*/ */
export function GetList (query) { export function GetList (query) {
query.limit = 999 // query.limit = 999;
return request({ return request({
url: urlPrefix, url: urlPrefix,
method: 'get', method: 'get',
params: query params: query
}).then(res => {
// 将列表数据转换为树形数据
res.data.data = XEUtils.toArrayTree(res.data.data, { parentKey: 'parent', strict: false })
return res
}) })
} }
/** /**
* 新增 * 新增
*/ */
@ -46,6 +34,7 @@ export function UpdateObj (obj) {
data: obj data: obj
}) })
} }
/** /**
* 删除 * 删除
*/ */
@ -56,3 +45,14 @@ export function DelObj (id) {
data: { id } data: { id }
}) })
} }
/**
* 部门懒加载
*/
export function DeptLazy (query) {
return request({
url: '/api/system/dept_lazy_tree/',
method: 'get',
params: query
})
}

View File

@ -1,24 +1,27 @@
import { request } from '@/api/service' import * as api from './api'
import { BUTTON_STATUS_BOOL } from '@/config/button'
import { urlPrefix as deptPrefix } from './api'
import XEUtils from 'xe-utils'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
return { return {
pagination: false, // pagination: false,
pageOptions: { pageOptions: {
compact: true compact: true
}, },
options: { options: {
// tableType: 'vxe-table', tableType: 'vxe-table',
// rowKey: true, // 必须设置true or false rowKey: true, // 必须设置true or false
rowId: 'id', rowId: 'id',
height: '100%', // 表格高度100%, 使用toolbar必须设置 height: '100%', // 表格高度100%, 使用toolbar必须设置
highlightCurrentRow: false, highlightCurrentRow: false,
defaultExpandAll: true defaultExpandAll: true,
// treeConfig: { // 树形数据配置 treeConfig: {
// expandAll: true, lazy: true,
// children: 'children', hasChild: 'has_children',
// } loadMethod: ({ row }) => {
return api.GetList({ parent: row.id, lazy: true }).then(ret => {
return ret.data.data
})
},
iconLoaded: 'el-icon-loading' // 美化loading图标
}
}, },
rowHandle: { rowHandle: {
width: 140, width: 140,
@ -44,7 +47,8 @@ export const crudOptions = (vm) => {
} }
} }
}, },
indexRow: { // 或者直接传true,不显示title不居中 indexRow: {
// 或者直接传true,不显示title不居中
title: '序号', title: '序号',
align: 'center', align: 'center',
width: 100 width: 100
@ -56,7 +60,8 @@ export const crudOptions = (vm) => {
formOptions: { formOptions: {
defaultSpan: 12 // 默认的表单 span defaultSpan: 12 // 默认的表单 span
}, },
columns: [{ columns: [
{
title: '关键词', title: '关键词',
key: 'search', key: 'search',
show: false, show: false,
@ -73,7 +78,8 @@ export const crudOptions = (vm) => {
placeholder: '请输入关键词' placeholder: '请输入关键词'
} }
}, },
view: { // 查看对话框组件的单独配置 view: {
// 查看对话框组件的单独配置
disabled: true disabled: true
} }
}, },
@ -88,45 +94,86 @@ export const crudOptions = (vm) => {
} }
}, },
{ {
show: false,
title: '上级部门', title: '上级部门',
key: 'parent', key: 'parent',
show: false, type: 'tree-selector',
search: {
disabled: true
},
type: 'cascader',
dict: { dict: {
cache: false,
url: deptPrefix + '?limit=999&status=1',
isTree: true, isTree: true,
value: 'id', // 数据字典中value字段的属性名 label: 'name',
label: 'name', // 数据字典中label字段的属性名 value: 'id',
children: 'children', // 数据字典中children字段的属性名 cache: false,
getData: (url, dict) => { // 配置此参数会覆盖全局的getRemoteDictFunc getData: (url, dict, { form, component }) => { // 配置此参数会覆盖全局的getRemoteDictFunc
return request({ url: url }).then(ret => { return api.DeptLazy().then(ret => { return ret.data })
const data = XEUtils.toArrayTree(ret.data.data, { parentKey: 'parent', strict: true }) },
return [{ id: '0', name: '根节点', children: data }] getNodes (values, data) {
// 配置行展示远程获取nodes
return new Promise((resolve, reject) => {
const row = vm.getEditRow()
resolve(row.parent !== null ? [{ name: row.parent_name, id: row.parent }] : [])
}) })
} }
}, },
form: { form: {
helper: '默认留空为根节点',
component: { component: {
span: 12, span: 12,
props: { props: {
multiple: false,
elProps: { elProps: {
clearable: true, lazy: true,
showAllLevels: false, // 仅显示最后一级 hasChild: 'has_children',
props: { load (node, resolve) {
checkStrictly: true, // 可以不需要选到最后一级 // 懒加载
emitPath: false, api.DeptLazy({ parent: node.data.id }).then((data) => {
clearable: true resolve(data.data)
})
} }
} }
} }
} }
} }
}, },
// {
// title: '上级部门',
// key: 'parent',
// show: false,
// search: {
// disabled: true
// },
// type: 'cascader',
// dict: {
// cache: false,
// url: deptPrefix,
// isTree: true,
// value: 'id', // 数据字典中value字段的属性名
// label: 'name', // 数据字典中label字段的属性名
// children: 'children', // 数据字典中children字段的属性名
// getData: (url, dict) => { // 配置此参数会覆盖全局的getRemoteDictFunc
// return request({ url: url, params: { limit: 999, status: 1 } }).then(ret => {
// const data = XEUtils.toArrayTree(ret.data.data, { parentKey: 'parent', strict: true })
// return [{ id: null, name: '根节点', children: data }]
// })
// }
// },
// form: {
// component: {
// span: 12,
// props: {
// elProps: {
// clearable: true,
// showAllLevels: false, // 仅显示最后一级
// props: {
// checkStrictly: true, // 可以不需要选到最后一级
// emitPath: false,
// clearable: true
// }
// }
// }
// }
// }
// },
{ {
title: '部门名称', title: '部门名称',
key: 'name', key: 'name',
@ -143,7 +190,8 @@ export const crudOptions = (vm) => {
width: 180, width: 180,
type: 'input', type: 'input',
form: { form: {
rules: [ // 表单校验规则 rules: [
// 表单校验规则
{ required: true, message: '部门名称必填项' } { required: true, message: '部门名称必填项' }
], ],
component: { component: {
@ -199,10 +247,15 @@ export const crudOptions = (vm) => {
placeholder: '请输入邮箱' placeholder: '请输入邮箱'
}, },
rules: [ rules: [
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] } {
type: 'email',
message: '请输入正确的邮箱地址',
trigger: ['blur', 'change']
}
] ]
} }
}, { },
{
title: '排序', title: '排序',
key: 'sort', key: 'sort',
sortable: true, sortable: true,
@ -226,7 +279,7 @@ export const crudOptions = (vm) => {
width: 90, width: 90,
type: 'radio', type: 'radio',
dict: { dict: {
data: BUTTON_STATUS_BOOL data: vm.dictionary('button_status_bool')
}, },
form: { form: {
value: true, value: true,

View File

@ -1,11 +1,3 @@
<!--
* @创建文件时间: 2021-06-01 22:41:21
* @Auther: 猿小天
* @最后修改人: 猿小天
* @最后修改时间: 2021-07-31 22:35:15
* 联系Qq:1638245306
* @文件介绍: 部门管理
-->
<template> <template>
<d2-container :class="{ 'page-compact': crud.pageOptions.compact }"> <d2-container :class="{ 'page-compact': crud.pageOptions.compact }">
<!-- <template slot="header">测试页面1</template>--> <!-- <template slot="header">测试页面1</template>-->
@ -52,6 +44,7 @@ export default {
return crudOptions(this) return crudOptions(this)
}, },
pageRequest (query) { pageRequest (query) {
query.lazy = true
return api.GetList(query) return api.GetList(query)
}, },
addRequest (row) { addRequest (row) {

View File

@ -1,11 +1,3 @@
/*
* @创建文件时间: 2021-06-01 22:41:21
* @Auther: 猿小天
* @最后修改人: 猿小天
* @最后修改时间: 2021-08-09 20:21:47
* 联系Qq:1638245306
* @文件介绍: 字典管理接口
*/
import { request } from '@/api/service' import { request } from '@/api/service'
import XEUtils from 'xe-utils' import XEUtils from 'xe-utils'
export const urlPrefix = '/api/system/dictionary/' export const urlPrefix = '/api/system/dictionary/'
@ -14,7 +6,6 @@ export const urlPrefix = '/api/system/dictionary/'
* 列表查询 * 列表查询
*/ */
export function GetList (query) { export function GetList (query) {
query.limit = 999
return request({ return request({
url: urlPrefix, url: urlPrefix,
method: 'get', method: 'get',

View File

@ -1,8 +1,3 @@
import { request } from '@/api/service'
import { BUTTON_STATUS_NUMBER } from '@/config/button'
import { urlPrefix as dictionaryPrefix } from './api'
import XEUtils from 'xe-utils'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
return { return {
@ -22,7 +17,7 @@ export const crudOptions = (vm) => {
} }
}, },
rowHandle: { rowHandle: {
width: 140, width: 230,
view: { view: {
thin: true, thin: true,
text: '', text: '',
@ -43,12 +38,18 @@ export const crudOptions = (vm) => {
disabled () { disabled () {
return !vm.hasPermissions('Delete') return !vm.hasPermissions('Delete')
} }
} },
custom: [{
text: ' 字典配置',
type: 'success',
size: 'small',
emit: 'dictionaryConfigure'
}]
}, },
indexRow: { // 或者直接传true,不显示title不居中 indexRow: { // 或者直接传true,不显示title不居中
title: '序号', title: '序号',
align: 'center', align: 'center',
width: 100 width: 80
}, },
viewOptions: { viewOptions: {
componentType: 'form' componentType: 'form'
@ -89,77 +90,8 @@ export const crudOptions = (vm) => {
} }
}, },
{ {
title: '父级字典', title: '字典名称',
key: 'parent',
show: false,
search: {
disabled: true
},
type: 'cascader',
dict: {
cache: false,
url: dictionaryPrefix + '?status=1&limit=999',
isTree: true,
value: 'id', // 数据字典中value字段的属性名
label: 'label', // 数据字典中label字段的属性名
children: 'children', // 数据字典中children字段的属性名
getData: (url, dict) => { // 配置此参数会覆盖全局的getRemoteDictFunc
return request({ url: url }).then(ret => {
return [{ id: null, label: '根节点', children: XEUtils.toArrayTree(ret.data.data, { parentKey: 'parent', strict: true }) }]
})
}
},
form: {
component: {
props: {
elProps: {
clearable: true,
showAllLevels: false, // 仅显示最后一级
props: {
checkStrictly: true, // 可以不需要选到最后一级
emitPath: false,
clearable: true
}
}
}
}
}
},
{
title: '编码',
key: 'code',
sortable: true,
treeNode: true,
search: {
disabled: true,
component: {
props: {
clearable: true
}
}
},
type: 'input',
form: {
editDisabled: true,
rules: [ // 表单校验规则
{ required: true, message: '编码必填项' }
],
component: {
props: {
clearable: true
},
placeholder: '请输入编码'
},
itemProps: {
class: { yxtInput: true }
}
}
},
{
title: '显示值',
key: 'label', key: 'label',
sortable: true,
search: { search: {
disabled: false, disabled: false,
@ -173,13 +105,13 @@ export const crudOptions = (vm) => {
type: 'input', type: 'input',
form: { form: {
rules: [ // 表单校验规则 rules: [ // 表单校验规则
{ required: true, message: '显示值必填项' } { required: true, message: '字典名称必填项' }
], ],
component: { component: {
props: { props: {
clearable: true clearable: true
}, },
placeholder: '请输入显示值' placeholder: '请输入字典名称'
}, },
itemProps: { itemProps: {
class: { yxtInput: true } class: { yxtInput: true }
@ -187,10 +119,8 @@ export const crudOptions = (vm) => {
} }
}, },
{ {
title: '实际值', title: '字典编号',
key: 'value', key: 'value',
sortable: true,
search: { search: {
disabled: true, disabled: true,
component: { component: {
@ -199,20 +129,25 @@ export const crudOptions = (vm) => {
} }
} }
}, },
type: 'input', type: 'input',
form: { form: {
rules: [ // 表单校验规则 rules: [ // 表单校验规则
{ required: true, message: '实际值必填项' } { required: true, message: '字典编号必填项' }
], ],
component: { component: {
props: { props: {
clearable: true clearable: true
}, },
placeholder: '请输入实际值' placeholder: '请输入字典编号'
}, },
itemProps: { itemProps: {
class: { yxtInput: true } class: { yxtInput: true }
},
helper: {
render (h) {
return (< el-alert title="使用方法vm.dictionary('字典编号')" type="warning"/>
)
}
} }
} }
}, },
@ -220,33 +155,51 @@ export const crudOptions = (vm) => {
{ {
title: '状态', title: '状态',
key: 'status', key: 'status',
sortable: true, width: 90,
search: { search: {
disabled: false disabled: false
}, },
type: 'radio', type: 'radio',
dict: { dict: {
data: BUTTON_STATUS_NUMBER data: vm.dictionary('button_status_bool')
},
component: {
props: {
options: []
}
}, },
form: { form: {
value: 1, rules: [ // 表单校验规则
{ required: true, message: '状态必填项' }
],
value: true,
component: { component: {
placeholder: '请选择状态'
},
itemProps: {
class: { yxtInput: true }
} }
} }
}, },
{ {
title: '排序', title: '排序',
key: 'sort', key: 'sort',
sortable: true, width: 90,
type: 'number', type: 'number',
form: { form: {
value: 1, value: 1,
component: { component: {
},
itemProps: {
class: { yxtInput: true }
} }
} }
} }
].concat(vm.commonEndColumns()) ].concat(vm.commonEndColumns({
description: {
showForm: true,
showTable: true
}
}))
} }
} }

View File

@ -9,7 +9,7 @@
<template> <template>
<d2-container :class="{ 'page-compact': crud.pageOptions.compact }"> <d2-container :class="{ 'page-compact': crud.pageOptions.compact }">
<!-- <template slot="header">测试页面1</template>--> <!-- <template slot="header">测试页面1</template>-->
<d2-crud-x ref="d2Crud" v-bind="_crudProps" v-on="_crudListeners"> <d2-crud-x ref="d2Crud" v-bind="_crudProps" v-on="_crudListeners" @dictionaryConfigure="dictionaryConfigure">
<div slot="header"> <div slot="header">
<crud-search <crud-search
ref="search" ref="search"
@ -30,6 +30,15 @@
/> />
</div> </div>
</d2-crud-x> </d2-crud-x>
<el-drawer
:visible.sync="drawer"
:size="700">
<div slot="title">
<span>字典列表</span>
<el-tag size="small" style="margin-left: 10px">{{dictionaryRow.label}}</el-tag>
</div>
<sub-dictionary style="margin-top: 80px;margin-left: 10px" :dictionaryRow="dictionaryRow"></sub-dictionary>
</el-drawer>
</d2-container> </d2-container>
</template> </template>
@ -37,17 +46,23 @@
import * as api from './api' import * as api from './api'
import { crudOptions } from './crud' import { crudOptions } from './crud'
import { d2CrudPlus } from 'd2-crud-plus' import { d2CrudPlus } from 'd2-crud-plus'
import SubDictionary from '@/views/system/dictionary/subDictionary/index'
export default { export default {
name: 'dictionary', name: 'dictionary',
components: { SubDictionary },
mixins: [d2CrudPlus.crud], mixins: [d2CrudPlus.crud],
data () { data () {
return {} return {
drawer: false,
dictionaryRow: {}
}
}, },
methods: { methods: {
getCrudOptions () { getCrudOptions () {
return crudOptions(this) return crudOptions(this)
}, },
pageRequest (query) { pageRequest (query) {
query.is_value = false
return api.GetList(query) return api.GetList(query)
}, },
addRequest (row) { addRequest (row) {
@ -61,13 +76,10 @@ export default {
delRequest (row) { delRequest (row) {
return api.DelObj(row.id) return api.DelObj(row.id)
}, },
// //
createPermission (scope) { dictionaryConfigure (scope) {
this.$router.push({ this.drawer = true
name: 'menuButton', this.dictionaryRow = scope.row
params: { id: scope.row.id },
query: { name: scope.row.name }
})
} }
} }
} }

View File

@ -1,22 +1,24 @@
/*
* @创建文件时间: 2021-06-01 22:41:21
* @Auther: 猿小天
* @最后修改人: 猿小天
* @最后修改时间: 2021-07-04 22:39:11
* 联系Qq:1638245306
* @文件介绍: 权限管理接口
*/
import { request } from '@/api/service' import { request } from '@/api/service'
import XEUtils from 'xe-utils'
export const urlPrefix = '/api/system/dictionary/'
export const urlPrefix = '/api/system/button/' /**
* 列表查询
*/
export function GetList (query) { export function GetList (query) {
return request({ return request({
url: urlPrefix, url: urlPrefix,
method: 'get', method: 'get',
data: query params: query
}).then(res => {
// 将列表数据转换为树形数据
res.data.data = XEUtils.toArrayTree(res.data.data, { parentKey: 'parent' })
return res
}) })
} }
/**
* 新增
*/
export function createObj (obj) { export function createObj (obj) {
return request({ return request({
url: urlPrefix, url: urlPrefix,
@ -25,6 +27,9 @@ export function createObj (obj) {
}) })
} }
/**
* 修改
*/
export function UpdateObj (obj) { export function UpdateObj (obj) {
return request({ return request({
url: urlPrefix + obj.id + '/', url: urlPrefix + obj.id + '/',
@ -32,6 +37,9 @@ export function UpdateObj (obj) {
data: obj data: obj
}) })
} }
/**
* 删除
*/
export function DelObj (id) { export function DelObj (id) {
return request({ return request({
url: urlPrefix + id + '/', url: urlPrefix + id + '/',

View File

@ -0,0 +1,304 @@
export const crudOptions = (vm) => {
return {
pageOptions: {
compact: true
},
options: {
rowId: 'id',
height: '100%', // 表格高度100%, 使用toolbar必须设置
border: false
},
rowHandle: {
width: 140,
view: {
thin: true,
text: '',
disabled () {
return !vm.hasPermissions('Retrieve')
}
},
edit: {
thin: true,
text: '',
disabled () {
return !vm.hasPermissions('Update')
}
},
remove: {
thin: true,
text: '',
disabled () {
return !vm.hasPermissions('Delete')
}
}
},
viewOptions: {
componentType: 'form'
},
formOptions: {
appendToBody: true, // 子表格必须 否则弹出对话框无法显示最顶层
defaultSpan: 24, // 默认的表单 span
width: '35%'
},
columns: [
{
title: '名称',
key: 'label',
search: {
disabled: false,
component: {
props: {
clearable: true
}
}
},
type: 'input',
form: {
rules: [ // 表单校验规则
{ required: true, message: '名称必填项' }
],
component: {
props: {
clearable: true
},
placeholder: '请输入名称'
},
itemProps: {
class: { yxtInput: true }
}
}
},
{
title: '数据值类型',
type: 'select',
key: 'type',
search: {
disabled: true,
component: {
props: {
clearable: true
}
}
},
show: false,
dict: {
data: [
{ label: 'text', value: 0 },
{ label: 'number', value: 1 },
{ label: 'date', value: 2 },
{ label: 'datetime', value: 3 },
{ label: 'time', value: 4 },
{ label: 'file', value: 5 },
{ label: 'boolean', value: 6 },
{ label: 'images', value: 7 }
]
},
form: {
rules: [ // 表单校验规则
{ required: true, message: '数据值类型必填项' }
],
value: 0,
component: {
props: {
clearable: true
},
placeholder: '请选择数据值类型'
},
itemProps: {
class: { yxtInput: true }
},
valueChange (key, value, form, { getColumn, mode, component, immediate, getComponent }) {
const template = vm.getEditFormTemplate('value')
// 选择框重新选择后情况value值
if (!immediate) {
form.value = undefined
}
if (value === 0) {
template.component.name = 'el-input'
} else if (value === 1) {
template.component.name = 'el-input-number'
} else if (value === 2) {
template.component.name = 'el-date-picker'
template.component.props = {
type: 'date',
valueFormat: 'yyyy-MM-dd'
}
} else if (value === 3) {
template.component.name = 'el-date-picker'
template.component.props = {
type: 'datetime',
valueFormat: 'yyyy-MM-dd HH:mm:ss'
}
} else if (value === 4) {
template.component.name = 'el-time-picker'
template.component.props = {
pickerOptions: {
arrowControl: true
},
valueFormat: 'HH:mm:ss'
}
} else if (value === 5) {
template.component.name = 'd2p-file-uploader'
template.component.props = { elProps: { listType: 'text' } }
} else if (value === 6) {
template.component.name = 'dict-switch'
template.component.value = true
template.component.props = {
dict: {
data: [
{ label: '', value: 'true' },
{ label: '', value: 'false' }
]
}
}
} else if (value === 7) {
template.component.name = 'd2p-cropper-uploader'
template.component.props = { accept: '.png,.jpeg,.jpg,.ico,.bmp,.gif', cropper: { viewMode: 1 } }
}
},
valueChangeImmediate: true
}
},
{
title: '数据值',
key: 'value',
search: {
disabled: true,
component: {
props: {
clearable: true
}
}
},
view: {
component: { props: { height: 100, width: 100 } }
},
// 提交时,处理数据
valueResolve (row, col) {
const value = row[col.key]
const type = row.type
if (type === 5 || type === 7) {
if (value != null) {
if (value.length >= 0) {
if (value instanceof Array) {
row[col.key] = value.toString()
} else {
row[col.key] = value
}
} else {
row[col.key] = null
}
}
} else {
row[col.key] = value
}
},
// 接收时,处理数据
valueBuilder (row, col) {
const value = row[col.key]
const type = row.type
if (type === 5 || type === 7) {
if (value != null && value) {
row[col.key] = value.split(',')
}
} else {
row[col.key] = value
}
},
type: 'input',
form: {
rules: [ // 表单校验规则
{ required: true, message: '数据值必填项' }
],
component: {
props: {
clearable: true
},
placeholder: '请输入数据值'
},
itemProps: {
class: { yxtInput: true }
}
}
}, {
title: '状态',
key: 'status',
width: 80,
search: {
disabled: false
},
type: 'radio',
dict: {
data: vm.dictionary('button_status_bool')
},
form: {
value: true,
rules: [ // 表单校验规则
{ required: true, message: '状态必填项' }
],
component: {
},
itemProps: {
class: { yxtInput: true }
}
}
},
{
title: '排序',
key: 'sort',
width: 70,
type: 'number',
form: {
value: 1,
component: {
},
rules: [ // 表单校验规则
{ required: true, message: '排序必填项' }
],
itemProps: {
class: { yxtInput: true }
}
}
}, {
title: '标签颜色',
key: 'color',
width: 90,
search: {
disabled: true
},
type: 'select',
dict: {
data: [
{ label: 'success', value: 'success', color: 'success' },
{ label: 'primary', value: 'primary', color: 'primary' },
{ label: 'info', value: 'info', color: 'info' },
{ label: 'danger', value: 'danger', color: 'danger' },
{ label: 'warning', value: 'warning', color: 'warning' }
]
},
form: {
component: {
props: {
clearable: true
}
},
itemProps: {
class: { yxtInput: true }
}
}
}
].concat(vm.commonEndColumns({
update_datetime: {
showForm: false,
showTable: false
},
create_datetime: {
showForm: false,
showTable: false
}
}))
}
}

View File

@ -1,20 +1,6 @@
<!--
* @创建文件时间: 2021-06-01 22:41:21
* @Auther: 猿小天
* @最后修改人: 猿小天
* @最后修改时间: 2021-07-29 22:17:28
* 联系Qq:1638245306
* @文件介绍:
-->
<template> <template>
<d2-container :class="{ 'page-compact': crud.pageOptions.compact }"> <d2-container>
<!-- <template slot="header">测试页面1</template>--> <d2-crud-x ref="d2Crud" v-bind="_crudProps" v-on="_crudListeners">
<d2-crud-x
ref="d2Crud"
v-bind="_crudProps"
v-on="_crudListeners"
@createPermission="createPermission"
>
<div slot="header"> <div slot="header">
<crud-search <crud-search
ref="search" ref="search"
@ -22,11 +8,7 @@
@submit="handleSearch" @submit="handleSearch"
/> />
<el-button-group> <el-button-group>
<el-button <el-button size="small" type="primary" @click="addRow"
size="small"
type="primary"
v-permission="'Create'"
@click="addRow"
><i class="el-icon-plus" /> 新增</el-button ><i class="el-icon-plus" /> 新增</el-button
> >
</el-button-group> </el-button-group>
@ -47,41 +29,53 @@ import * as api from './api'
import { crudOptions } from './crud' import { crudOptions } from './crud'
import { d2CrudPlus } from 'd2-crud-plus' import { d2CrudPlus } from 'd2-crud-plus'
export default { export default {
name: 'buttons', name: 'subDictionary',
mixins: [d2CrudPlus.crud], mixins: [d2CrudPlus.crud],
props: {
//
dictionaryRow: {
type: Object,
required: true
}
},
watch: {
dictionaryRow () {
this.doRefresh({ from: 'load' })
}
},
data () { data () {
return {} return {
}
}, },
methods: { methods: {
getCrudOptions () { getCrudOptions () {
return crudOptions(this) return crudOptions(this)
}, },
pageRequest (query) { pageRequest (query) {
query.is_value = true
query.parent = this.dictionaryRow.id
return api.GetList(query) return api.GetList(query)
}, },
addRequest (row) { addRequest (row) {
console.log('api', api) d2CrudPlus.util.dict.clear()
row.is_value = true
row.parent = this.dictionaryRow.id
return api.createObj(row) return api.createObj(row)
}, },
updateRequest (row) { updateRequest (row) {
console.log('----', row) d2CrudPlus.util.dict.clear()
row.is_value = true
row.parent = this.dictionaryRow.id
return api.UpdateObj(row) return api.UpdateObj(row)
}, },
delRequest (row) { delRequest (row) {
return api.DelObj(row.id) return api.DelObj(row.id)
},
//
createPermission (scope) {
console.log('custom btn:', scope)
this.$message(
'自定义操作按钮:' + scope.row.data + ',index:' + scope.index
)
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.yxtInput { .yxtInput {
.el-form-item__label { .el-form-item__label {
color: #49a1ff; color: #49a1ff;

View File

@ -1,5 +1,3 @@
import util from '@/libs/util'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
return { return {
pageOptions: { pageOptions: {
@ -92,11 +90,7 @@ export const crudOptions = (vm) => {
search: { search: {
disabled: true disabled: true
}, },
width: 220, width: 220
valueBuilder (row, key) {
console.log(row, key)
row.url = `${util.baseURL()}media/${row.url}`
}
}, },
{ {
title: '文件MD5', title: '文件MD5',
@ -107,18 +101,7 @@ export const crudOptions = (vm) => {
}, },
form: { form: {
disabled: false disabled: false
},
valueResolve (row, col) {
const value = row[col.key]
if (value != null && value instanceof Array) {
if (value.length >= 0) {
row[col.key] = value[0]
} else {
row[col.key] = null
} }
}
}
}, },
{ {
title: '备注', title: '备注',

View File

@ -115,7 +115,7 @@ export const crudOptions = (vm) => {
type: 'input', type: 'input',
form: { form: {
component: { component: {
placeholder: '请输入操作系统' placeholder: '请输入运营商'
} }
} }
}, { }, {
@ -126,7 +126,7 @@ export const crudOptions = (vm) => {
form: { form: {
disabled: true, disabled: true,
component: { component: {
placeholder: '请输入' placeholder: '请输入'
} }
}, },
component: { props: { color: 'auto' } } // 自动染色 component: { props: { color: 'auto' } } // 自动染色
@ -265,7 +265,7 @@ export const crudOptions = (vm) => {
type: 'input', type: 'input',
form: { form: {
component: { component: {
placeholder: '请输入操作系统' placeholder: '请输入浏览器名'
} }
} }
}, { }, {
@ -276,7 +276,7 @@ export const crudOptions = (vm) => {
type: 'input', type: 'input',
form: { form: {
component: { component: {
placeholder: '请输入操作系统' placeholder: '请输入agent信息'
} }
} }
}, { }, {

View File

@ -1,12 +1,3 @@
/*
* @创建文件时间: 2021-06-02 10:33:33
* @Auther: 猿小天
* @最后修改人: 猿小天
* @最后修改时间: 2021-08-12 22:53:38
* 联系Qq:1638245306
* @文件介绍: 登录的接口
*/
import { request } from '@/api/service' import { request } from '@/api/service'
export function SYS_USER_LOGIN (data) { export function SYS_USER_LOGIN (data) {
@ -31,3 +22,10 @@ export function getCaptcha () {
method: 'get' method: 'get'
}) })
} }
export function getCaptchaStatus () {
return request({
url: 'api/captcha/status/',
method: 'get'
})
}

View File

@ -0,0 +1,239 @@
<template>
<div class="page-login"></div>
</template>
<script>
import { mapActions } from 'vuex'
import localeMixin from '@/locales/mixin.js'
import * as api from '@/views/system/login/api'
export default {
mixins: [localeMixin],
beforeCreate () {
//
this.$store.dispatch('d2admin/settings/init')
},
data () {
return {
siteName: this.systemConfig('login.site_name'), //
siteLogo: this.systemConfig('login.site_logo') || require('./image/dvadmin.png'), // logo
loginBackground: this.systemConfig('login.login_background') || require('./image/bg.jpg'), //
copyright: this.systemConfig('login.copyright'), //
keepRecord: this.systemConfig('login.keep_record'), //
helpUrl: this.systemConfig('login.help_url'), //
privacyUrl: this.systemConfig('login.privacy_url'), //
clauseUrl: this.systemConfig('login.clause_url'), //
captchaState: this.systemConfig('base.captcha_state') || true, //
processTitle: process.env.VUE_APP_TITLE || 'D2Admin',
backgroundImage: 'url(' + this.loginBackground + ')',
//
formLogin: {
username: '',
password: '',
captcha: ''
},
//
rules: {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur'
}
]
},
captchaKey: null,
image_base: null,
// dev
selectUsersDialogVisible: false,
users: [
{
name: '超管',
username: 'superadmin',
password: 'admin123456'
},
{
name: '管理员',
username: 'admin',
password: 'admin123456'
}
]
}
},
mounted () {
},
beforeDestroy () {
},
methods: {
...mapActions('d2admin/account', ['login']),
/**
* 获取验证码
*/
getCaptcha () {
if (this.captchaState !== undefined && !this.captchaState) return
api.getCaptcha().then((ret) => {
this.formLogin.captcha = null
this.captchaKey = ret.data.key
this.image_base = ret.data.image_base
})
},
/**
* @description 提交表单
*/
//
submit () {
const that = this
this.$refs.loginForm.validate((valid) => {
if (valid) {
//
//
//
this.login({
username: that.formLogin.username,
password: that.$md5(that.formLogin.password),
captcha: that.formLogin.captcha,
captchaKey: that.captchaKey
})
.then(() => {
//
this.$router.replace(this.$route.query.redirect || '/')
})
.catch(() => {
this.getCaptcha()
})
} else {
//
this.$message.error('表单校验失败,请检查')
}
})
},
//
handleUserBtnClick (user) {
this.formLogin.username = user.username
this.formLogin.password = user.password
// this.submit()
this.selectUsersDialogVisible = false
if (!this.captchaState) {
this.submit()
}
}
},
created () {
this.$store.dispatch('d2admin/db/databaseClear')
this.getCaptcha()
}
}
</script>
<style lang="scss" scoped>
// ----
.page-login {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-image: url(./image/bg.jpg);
background-position: center 0;
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
-webkit-background-size: cover; /* 兼容Webkit内核浏览器如Chrome和Safari */
-o-background-size: cover; /* 兼容Opera */
zoom: 1;
}
::v-deep .el-card__body {
height: 100%;
padding: 0;
}
.card {
height: 100%;
width: 100%;
border-radius: 30px;
padding: 0;
margin-top: 12%;
}
.right-card {
float: right;
text-align: center;
width: 50%;
height: 100%;
}
.right-card h1 {
color: #098dee;
margin-bottom: 40px;
margin-top: 40px;
}
.button-login {
width: 100%;
margin-top: 30px;
}
::v-deep .el-input-group__append {
padding: 0;
}
// footer
.page-login--content-footer {
margin-top: 10%;
padding: 1em 0;
.page-login--content-footer-locales {
padding: 0px;
margin: 0px;
margin-bottom: 15px;
font-size: 12px;
line-height: 12px;
text-align: center;
color: $color-text-normal;
a {
color: $color-text-normal;
margin: 0 0.5em;
&:hover {
color: $color-text-main;
}
}
}
.page-login--content-footer-copyright {
padding: 0px;
margin: 0px;
margin-bottom: 10px;
font-size: 12px;
line-height: 12px;
text-align: center;
color: $color-text-normal;
a {
color: $color-text-normal;
}
}
.page-login--content-footer-options {
padding: 0px;
margin: 0px;
font-size: 12px;
line-height: 12px;
text-align: center;
a {
color: $color-text-normal;
margin: 0 1em;
}
}
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,557 @@
/*--
Author: W3layouts
Author URL: http://w3layouts.com
--*/
/* reset code */
html {
scroll-behavior: smooth;
}
body,
html {
margin: 0;
padding: 0;
color: #585858;
}
* {
box-sizing: border-box;
font-family: 'Kumbh Sans', sans-serif;
}
/* wrapper */
.wrapper {
width: 100%;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
@media (min-width: 576px) {
.wrapper {
max-width: 540px;
}
}
@media (min-width: 768px) {
.wrapper {
max-width: 720px;
}
}
@media (min-width: 992px) {
.wrapper {
max-width: 960px;
}
}
@media (min-width: 1200px) {
.wrapper {
max-width: 1280px;
}
}
/* /wrapper */
.d-grid {
display: grid;
}
button,
input,
select {
-webkit-appearance: none;
outline: none;
}
button,
.btn,
select {
cursor: pointer;
}
a {
text-decoration: none;
}
h1,
h2,
h3,
h4,
h5,
h6,
p,
ul,
ol {
margin: 0;
padding: 0;
}
body {
background: #f1f1f1;
margin: 0;
padding: 0;
}
form,
fieldset {
border: 0;
padding: 0;
margin: 0;
}
img {
max-width: 100%;
}
.login-code{
max-width: 400px;
}
.el-input-group__append, .el-input-group__prepend{
padding: 0;
}
.page-login--logo{
margin: 0 auto;
top: 10px;
left: 52%;
position: absolute;
}
/*-- //Reset-Code --*/
/*-- form styling --*/
.w3l-form-info {
padding-top: 6em;
}
.w3l-signinform{
padding: 40px 40px;
min-height: 100vh;
background-size: cover;
-webkit-background-size: cover;
-o-background-size: cover;
-moz-background-size: cover;
-ms-background-size: cover;
position: relative;
z-index: 1;
align-items:center;
}
.w3l-signinform::before{
content: "";
position: absolute;
top: 0;
min-height: 100%;
left: 0;
right: 0;
z-index: -1;
}
.btn-block {
display: block;
width: 50%;
margin: 0 auto;
}
.btn:active {
outline: none;
}
.btn-primary {
color: #232005;
background-color: #ffd900;
margin-top: 30px;
outline: none;
width: 100%;
padding: 15px 15px;
cursor: pointer;
font-size: 18px;
font-weight: 600;
border-radius:6px;
-webkit-border-radius:6px;
-moz-border-radius:6px;
-ms-border-radius:6px;
-o-border-radius:6px;
border: none;
text-transform: capitalize;
}
.btn-primary:hover {
background-color:#eac803;
}
.form-row.bottom {
display: flex;
justify-content: space-between;
}
.form-row .form-check input[type="checkbox"] {
display: none;
}
.form-row .form-check input[type="checkbox"]+label:before {
border-radius: 3px;
border: 1px solid #e2e2e2;
color: transparent;
content: "\2714";
display: inline-block;
height: 18px;
margin-right: 5px;
transition: 0.2s;
vertical-align: inherit;
width: 18px;
text-align: center;
line-height: 20px;
}
.form-row .form-check input[type="checkbox"]:checked+label:before {
background-color: #ffd900;
border-color: #ffd900;
color: #232005;
}
.form-row .form-check input[type="checkbox"]+label {
cursor: pointer;
color: #000;
}
.w3_info h2 {
display: inline-block;
font-size: 24px;
line-height: 35px;
margin-bottom: 20px;
font-weight: 600;
color: #fff;
}
.w3_info h4 {
display: inline-block;
font-size: 15px;
padding: 8px 0px;
color: #444;
text-transform: capitalize;
}
.w3_info h1 {
font-size: 36px;
font-weight: 600;
color: #fff;
margin-bottom: 0.4em;
line-height: 40px;
}
.w3_info {
padding: 1em 1em;
background: transparent;
max-width: 450px;
display: grid;
margin-left: auto;
}
.left_grid_info {
padding: 6em 0;
}
.w3l_form {
padding: 0px;
flex-basis: 50%;
-webkit-flex-basis: 50%;
background: #dad1f8;
}
/*.w3_info p {*/
/* padding-bottom: 30px;*/
/* text-align: center;*/
/*}*/
/*.w3_info p.sub-para {*/
/* padding-bottom: 40px;*/
/* text-align:left;*/
/* color:#fff;*/
/* opacity:0.9;*/
/* line-height:28px;*/
/*}*/
p.account,
p.account a {
text-align: center;
padding-top: 20px;
padding-bottom: 0px;
font-size: 16px;
color: #fff;
opacity: 0.9;
}
p.account a {
color: #ffd900;
}
p.account a:hover {
text-decoration: underline;
}
a.forgot {
color: #ffeb3b;
margin-top: 2px;
opacity: 0.8;
}
a.forgot:hover {
text-decoration: underline;
}
h3.w3ls {
margin: 10px 0px;
padding-left: 60px;
}
h3.agileits {
padding-left: 10px;
}
.container {
max-width: 890px;
margin: 0 auto;
}
h5 {
text-align: center;
margin: 10px 0px;
font-size: 15px;
font-weight: 600;
color: #000;
}
.footer {
padding-top: 3em;
}
.footer p {
text-align: center;
font-size: 14px;
line-height: 28px;
color: #fff;
opacity:0.9;
}
.footer p a {
color: #ffd900;
}
.footer p a:hover {
text-decoration: underline;
}
p.continue {
margin-top: 25px;
padding: 0;
margin-bottom: 20px;
color: #fff;
}
p.continue span {
position: relative;
}
p.continue span:before {
position: absolute;
content: '';
height: 1px;
background: #fff;
width: 89%;
left: -100%;
top: 5px;
}
p.continue span:after {
position: absolute;
content: '';
height: 1px;
background: #fff;
width: 89%;
right: -100%;
top: 5px;
}
::-webkit-input-placeholder {
/* Edge */
color: #fff;
}
:-ms-input-placeholder {
/* Internet Explorer 10-11 */
color: #fff;
}
::placeholder {
color:#fff;
}
/** Responsive **/
@media screen and (max-width: 1024px) {
.left_grid_info h3 {
font-size: 2em;
}
.page-login--logo{
display:none
}
}
@media screen and (max-width: 991px) {
.w3_info h2 {
font-size: 24px;
}
.page-login--logo{
display:none
}
h1 {
font-size: 35px;
}
}
@media screen and (max-width: 900px) {
.left_grid_info h4 {
font-size: 1em;
}
.page-login--logo{
display:none
}
}
@media screen and (max-width: 800px) {
.w3_info h2 {
font-size: 23px;
}
.page-login--logo{
display:none
}
}
@media screen and (max-width: 767px) {
.w3_info {
margin-right: auto;
}
.page-login--logo{
display:none
}
}
@media screen and (max-width: 736px) {
.w3_info h2 {
font-size: 22px;
}
.page-login--logo{
display:none
}
}
@media screen and (max-width: 667px) {
.w3l-form-info {
margin: 20px 0 30px;
}
.page-login--logo{
display:none
}
}
@media screen and (max-width: 640px) {
h1 {
font-size: 37px;
}
.page-login--logo{
display:none
}
}
@media screen and (max-width:568px) {
.w3l_form {
padding: 0em;
}
h1 {
font-size: 34px;
}
.page-login--logo{
display:none
}
.w3_info {
padding:0;
}
}
@media screen and (max-width: 415px) {
h1 {
font-size: 32px;
}
.left_grid_info p {
font-size: 13px;
}
.page-login--logo{
display:none
}
.w3l-signinform{
padding: 40px 20px;
}
}
@media screen and (max-width: 384px) {
.w3l-signinform{
padding: 30px 15px;
}
.page-login--logo{
display:none
}
.social-login {
grid-auto-flow: row;
}
}
@media screen and (max-width: 375px) {
.left_grid_info h3 {
font-size: 1.5em;
}
.page-login--logo{
display:none
}
.form-row.bottom {
flex-direction: column;
}
a.forgot {
margin-top: 17px;
}
}
@media screen and (max-width: 320px) {
h1 {
font-size: 25px;
}
.page-login--logo{
display:none
}
.w3_info h2 {
font-size: 18px;
}
.btn-primary {
padding: 13px 12px;
font-size: 13px;
}
input[type="text"],
input[type="email"],
input[type="password"] {
font-size: 13px;
}
.footer p {
font-size: 13px;
}
.footer p a {
font-size: 13px;
}
}
/** /Responsive **/
/*-- //form styling --*/

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Some files were not shown because too many files have changed in this diff Show More