Merge remote-tracking branch 'origin/v2.x' into local-v2.x

pull/56/head
Angelo 2022-05-11 12:07:12 +08:00
commit b4f7d65ca2
69 changed files with 19125 additions and 2687 deletions

3
.gitignore vendored
View File

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

4
backend/.gitignore vendored
View File

@ -88,11 +88,11 @@ 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__/

View File

@ -27,108 +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)) sys.path.insert(0, os.path.join(PLUGINS_PATH))
[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('__')] 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
@ -139,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先注释
@ -166,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",
},
}, },
}
} }
# ================================================= # # ================================================= #
@ -245,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配置 ***************** #
# ================================================= # # ================================================= #
@ -280,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,
} }
# ====================================# # ====================================#
@ -293,72 +298,72 @@ 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_STATE = locals().get("CAPTCHA_STATE", False)
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 插件 # dvadmin 插件
REGISTER_PLUGINS = ( REGISTER_PLUGINS = (
# "" # ""
) )
# 初始化需要执行的列表,用来初始化后执行
INITIALIZE_LIST = []
INITIALIZE_RESET_LIST = []
# 表前缀
TABLE_PREFIX = locals().get('TABLE_PREFIX', "")
DEFAULT_PASSWORD = locals().get("DEFAULT_PASSWORD", "admin123456")

View File

@ -23,13 +23,21 @@ from rest_framework_simplejwt.views import (
) )
from application import settings from application import settings
from dvadmin.system.views.login import LoginView, CaptchaView, ApiLogin, LogoutView from dvadmin.system.views.login import (
LoginView,
CaptchaStatusView,
CaptchaView,
ApiLogin,
LogoutView,
)
from dvadmin.system.views.system_config import InitSettingsViewSet
from dvadmin.system.views.dictionary import InitDictionaryViewSet
from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator
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 +46,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/captcha/status/", CaptchaStatusView.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)
)

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,29 @@ DATABASE_USER = "root"
# # 数据库密码 # # 数据库密码
DATABASE_PASSWORD = "123456" DATABASE_PASSWORD = "123456"
# 表前缀
TABLE_PREFIX = "sys_"
# ================================================= # # ================================================= #
# ************** 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
# 是否启用登录验证码不需要可以设置为False线上环境建议开启
CAPTCHA_STATE = 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详细地址) # 默认密码
DEFAULT_PASSWORD = "admin123456"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -13,27 +13,33 @@ 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 '初始数据将会先删除后新增'}...") 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}.initialize import main from {app}.initialize import main
main(reset={reset}) main(reset={reset})
""") """
)
except ModuleNotFoundError: except ModuleNotFoundError:
pass pass
print("初始化数据完成!") print("初始化数据完成!")

View File

@ -19,10 +19,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, "后台用户"),
@ -166,13 +167,23 @@ 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="状态") 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="备注")

View File

@ -7,6 +7,7 @@
@Remark: 字典管理 @Remark: 字典管理
""" """
from rest_framework import serializers from rest_framework import serializers
from rest_framework.views import APIView
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
@ -42,16 +43,15 @@ class DictionaryTreeSerializer(CustomModelSerializer):
children = serializers.SerializerMethodField(read_only=True) children = serializers.SerializerMethodField(read_only=True)
def get_children(self, instance): def get_children(self, instance):
queryset = Dictionary.objects.filter(parent=instance.id).filter(status=1) queryset = Dictionary.objects.filter(parent=instance.id).filter(status=1).values('label', 'value', 'type')
if queryset: if queryset:
serializer = DictionaryTreeSerializer(queryset, many=True) return queryset
return serializer.data
else: else:
return None return []
class Meta: class Meta:
model = Dictionary model = Dictionary
fields = "__all__" fields = ['id', 'value', 'children']
read_only_fields = ["id"] read_only_fields = ["id"]
@ -68,3 +68,24 @@ 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':
queryset = self.queryset.filter(status=True, is_value=False)
serializer = DictionaryTreeSerializer(queryset, many=True, request=request)
data = serializer.data
else:
data = self.queryset.filter(parent__value=dictionary_key, status=True).values('label', 'value', 'type')
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
@ -24,7 +16,7 @@ from rest_framework_simplejwt.views import TokenObtainPairView
from application import settings from application import settings
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 +27,33 @@ 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 settings.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 CaptchaStatusView(APIView):
authentication_classes = []
permission_classes = []
def get(self, request):
return DetailResponse(data={"status": settings.CAPTCHA_STATE})
class LoginSerializer(TokenObtainPairSerializer): class LoginSerializer(TokenObtainPairSerializer):
@ -57,53 +61,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 settings.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 +124,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 +151,31 @@ 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, if user_obj := auth.authenticate(
password=hashlib.md5(password.encode(encoding='UTF-8')).hexdigest()) request,
if user_obj: username=username,
password=hashlib.md5(password.encode(encoding="UTF-8")).hexdigest(),
):
login(request, user_obj) login(request, user_obj)
return redirect('/') return redirect("/")
else: else:
return ErrorResponse(msg="账号/密码错误") return ErrorResponse(msg="账号/密码错误")

View File

@ -10,9 +10,9 @@ 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 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 +31,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 +41,7 @@ class SystemConfigCreateSerializer(CustomModelSerializer):
raise CustomValidationError('已存在相同变量名') raise CustomValidationError('已存在相同变量名')
return value return value
class SystemConfigSerializer(CustomModelSerializer): class SystemConfigSerializer(CustomModelSerializer):
""" """
系统配置-序列化器 系统配置-序列化器
@ -112,17 +112,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 +173,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 +196,24 @@ 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 = {
"site_name": "企业级后台管理系统", # 网站名称
"site_logo": "", # 网站logo地址
"login_background": "", # 登录页背景图
"copyright": "2021-2022 django-vue-admin.com 版权所有", # 版权
"keep_record": "晋ICP备18005113号-3", # 备案
"help_url": "https://django-vue-admin.com", # 帮助
"privacy_url": "#", # 隐私
"clause_url": "#", # 条款
}
return DetailResponse(data=data)

View File

@ -1,13 +1,6 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/3 003 0:30
@Remark: 用户管理
"""
import hashlib import hashlib
from application import settings
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action from rest_framework.decorators import action
@ -28,9 +21,9 @@ 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},
} }
@ -38,14 +31,23 @@ 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,
default=make_password(
hashlib.md5(settings.DEFAULT_PASSWORD.encode(encoding="UTF-8")).hexdigest()
),
)
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.post.set(self.initial_data.get("post", []))
return data return data
class Meta: class Meta:
@ -53,7 +55,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,13 +63,24 @@ class UserUpdateSerializer(CustomModelSerializer):
""" """
用户修改-序列化器 用户修改-序列化器
""" """
username = serializers.CharField(max_length=50,
validators=[CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")]) username = serializers.CharField(
max_length=50,
validators=[
CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")
],
)
password = serializers.CharField(required=False, allow_blank=True) 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.post.set(self.initial_data.get("post", []))
return data return data
class Meta: class Meta:
@ -75,7 +88,7 @@ class UserUpdateSerializer(CustomModelSerializer):
read_only_fields = ["id"] read_only_fields = ["id"]
fields = "__all__" fields = "__all__"
extra_kwargs = { extra_kwargs = {
'post': {'required': False, 'read_only': True}, "post": {"required": False, "read_only": True},
} }
@ -83,22 +96,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 +132,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 +159,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 +210,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,21 +241,32 @@ 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_password(self, request, pk): def reset_password(self, request, *args, **kwargs):
""" """重置密码"""
密码重置 instance = Users.objects.filter(id=kwargs.get("pk")).first()
"""
instance = Users.objects.filter(id=pk).first()
data = request.data
new_pwd = data.get('newPassword')
new_pwd2 = data.get('newPassword2')
if instance: if instance:
if new_pwd != new_pwd2: instance.set_password(settings.DEFAULT_PASSWORD)
return ErrorResponse(msg="两次密码不匹配")
else:
instance.password = make_password(new_pwd)
instance.save() instance.save()
return DetailResponse(data=None, msg="修改成功") return DetailResponse(data=None, msg="密码重置成功")
else: else:
return ErrorResponse(msg="未获取到用户") return ErrorResponse(msg="未获取到用户")
# @action(methods=['PUT'], detail=True)
# def reset_password(self, request, pk):
# """
# 密码重置
# """
# instance = Users.objects.filter(id=pk).first()
# data = request.data
# new_pwd = data.get('newPassword')
# new_pwd2 = data.get('newPassword2')
# if instance:
# if new_pwd != new_pwd2:
# return ErrorResponse(msg="两次密码不匹配")
# else:
# instance.password = make_password(new_pwd)
# instance.save()
# return DetailResponse(data=None, msg="修改成功")
# else:
# return ErrorResponse(msg="未获取到用户")

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,38 @@ 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.extend(
get_dept(
user_dept_id,
)
)
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 +144,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 +156,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 +171,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 +228,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 +245,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 +278,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

@ -190,7 +190,7 @@ 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})) res = requests.get(url='https://ip.django-vue-admin.com/ip/analysis', params={"ip": ip})
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:

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

@ -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
# 后端接口地址及端口(域名) # 后端接口地址及端口(域名)

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

@ -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,9 @@ 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 { urlPrefix as deptPrefix } from '@/views/system/dept/' import store from '@/store/index'
import { urlPrefix as deptPrefix } from '@/views/system/dept/api'
import types from '@/config/d2p-extends/types'
const uploadUrl = util.baseURL() + 'api/system/file/' const uploadUrl = util.baseURL() + 'api/system/file/'
/** /**
@ -163,24 +165,29 @@ Vue.use(D2pUploader, {
action: uploadUrl, action: uploadUrl,
name: 'file', name: 'file',
data: {}, // 上传附加参数 data: {}, // 上传附加参数
headers: { headers () {
return {
Authorization: 'JWT ' + util.cookies.get('token') Authorization: 'JWT ' + util.cookies.get('token')
}
}, },
type: 'form', type: 'form',
successHandle (ret, option) { successHandle (ret, option) {
if (ret.data === null || ret.data === '') { if (ret.data === null || ret.data === '') {
throw new Error('上传失败') throw new Error('上传失败')
} }
return { url: ret.data.data.url, key: option.data.key } return { url: util.baseURL() + ret.data.url, key: option.data.key }
}, },
withCredentials: false // 是否带cookie 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]
}
// 默认Columns 结尾 showForm显示在form中showTable显示在table中 // 默认Columns 结尾 showForm显示在form中showTable显示在table中
Vue.prototype.commonEndColumns = function (param = {}) { Vue.prototype.commonEndColumns = function (param = {}) {
/** /**
@ -270,7 +277,7 @@ Vue.prototype.commonEndColumns = function (param = {}) {
type: 'table-selector', type: 'table-selector',
dict: { dict: {
cache: true, cache: true,
url: deptPrefix + '?limit=999&status=1', url: deptPrefix,
isTree: true, isTree: true,
value: 'id', // 数据字典中value字段的属性名 value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名 label: 'name', // 数据字典中label字段的属性名
@ -280,7 +287,8 @@ Vue.prototype.commonEndColumns = function (param = {}) {
component component
}) => { }) => {
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: '',

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

@ -64,7 +64,9 @@ 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: 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,72 @@
import { request } from '@/api/service'
export const urlPrefix = '/api/init/settings/'
// 系统配置
export default {
namespaced: true,
state: {
siteName: '', // 网站名称
siteLogo: '', // 网站logo地址
loginBackground: '', // 登录页背景图
copyright: '', // 版权
keepRecord: '', // 备案
helpUrl: '', // 帮助地址
privacyUrl: '', // 隐私
clauseUrl: '' // 条款
},
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 }) {
const res = await dispatch('d2admin/db/get', {
dbName: 'sys',
path: 'settings.init',
defaultValue: {},
user: true
}, { root: true })
// store 赋值
state.siteName = res.site_name
state.siteLogo = res.site_logo
state.loginBackground = res.login_background
state.copyright = res.copyright
state.keepRecord = res.keep_record
state.helpUrl = res.help_url
state.privacyUrl = res.privacy_url
state.clauseUrl = res.clause_url
}
},
mutations: {
/**
* @description 获取配置
* @param {Object} state state
* @param {String} key active
* @param {Object} value active
*/
async get (state, key, value) {
return state[key]
}
}
}

View File

@ -92,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
}
}
} }
}, },
{ {
@ -132,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

@ -14,7 +14,7 @@ export function GetList (query) {
return request({ return request({
url: urlPrefix, url: urlPrefix,
method: 'get', method: 'get',
data: query params: query
}) })
} }
export function createObj (obj) { export function createObj (obj) {

View File

@ -1,5 +1,4 @@
import { request } from '@/api/service' import { request } from '@/api/service'
import { BUTTON_STATUS_BOOL } from '@/config/button'
import { urlPrefix as deptPrefix } from './api' import { urlPrefix as deptPrefix } from './api'
import XEUtils from 'xe-utils' import XEUtils from 'xe-utils'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
@ -97,15 +96,15 @@ export const crudOptions = (vm) => {
type: 'cascader', type: 'cascader',
dict: { dict: {
cache: false, cache: false,
url: deptPrefix + '?limit=999&status=1', url: deptPrefix,
isTree: true, isTree: true,
value: 'id', // 数据字典中value字段的属性名 value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名 label: 'name', // 数据字典中label字段的属性名
children: 'children', // 数据字典中children字段的属性名 children: 'children', // 数据字典中children字段的属性名
getData: (url, dict) => { // 配置此参数会覆盖全局的getRemoteDictFunc getData: (url, dict) => { // 配置此参数会覆盖全局的getRemoteDictFunc
return request({ url: url }).then(ret => { return request({ url: url, params: { limit: 999, status: 1 } }).then(ret => {
const data = XEUtils.toArrayTree(ret.data.data, { parentKey: 'parent', strict: true }) const data = XEUtils.toArrayTree(ret.data.data, { parentKey: 'parent', strict: true })
return [{ id: '0', name: '根节点', children: data }] return [{ id: null, name: '根节点', children: data }]
}) })
} }
}, },
@ -226,7 +225,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-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

@ -0,0 +1,49 @@
import { request } from '@/api/service'
import XEUtils from 'xe-utils'
export const urlPrefix = '/api/system/dictionary/'
/**
* 列表查询
*/
export function GetList (query) {
return request({
url: urlPrefix,
method: 'get',
params: query
}).then(res => {
// 将列表数据转换为树形数据
res.data.data = XEUtils.toArrayTree(res.data.data, { parentKey: 'parent' })
return res
})
}
/**
* 新增
*/
export function createObj (obj) {
return request({
url: urlPrefix,
method: 'post',
data: obj
})
}
/**
* 修改
*/
export function UpdateObj (obj) {
return request({
url: urlPrefix + obj.id + '/',
method: 'put',
data: obj
})
}
/**
* 删除
*/
export function DelObj (id) {
return request({
url: urlPrefix + id + '/',
method: 'delete',
data: { id }
})
}

View File

@ -0,0 +1,279 @@
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 }
}
}
}
].concat(vm.commonEndColumns({
update_datetime: {
showForm: false,
showTable: false
},
create_datetime: {
showForm: false,
showTable: false
}
}))
}
}

View File

@ -0,0 +1,95 @@
<template>
<d2-container>
<d2-crud-x ref="d2Crud" v-bind="_crudProps" v-on="_crudListeners" @dictionaryConfigure="dictionaryConfigure">
<div slot="header">
<crud-search
ref="search"
:options="crud.searchOptions"
@submit="handleSearch"
/>
<el-button-group>
<el-button size="small" type="primary" @click="addRow"
><i class="el-icon-plus" /> 新增</el-button
>
</el-button-group>
<crud-toolbar
:search.sync="crud.searchOptions.show"
:compact.sync="crud.pageOptions.compact"
:columns="crud.columns"
@refresh="doRefresh()"
@columns-filter-changed="handleColumnsFilterChanged"
/>
</div>
</d2-crud-x>
<el-drawer
title="我是标题"
:visible.sync="drawer"
size="40%">
<span>我来啦!</span>
</el-drawer>
</d2-container>
</template>
<script>
import * as api from './api'
import { crudOptions } from './crud'
import { d2CrudPlus } from 'd2-crud-plus'
export default {
name: 'subDictionary',
mixins: [d2CrudPlus.crud],
props: {
//
dictionaryRow: {
type: Object,
required: true
}
},
watch: {
dictionaryRow () {
this.doRefresh({ from: 'load' })
}
},
data () {
return {
drawer: false
}
},
methods: {
getCrudOptions () {
return crudOptions(this)
},
pageRequest (query) {
query.is_value = true
query.parent = this.dictionaryRow.id
return api.GetList(query)
},
addRequest (row) {
d2CrudPlus.util.dict.clear()
row.is_value = true
row.parent = this.dictionaryRow.id
return api.createObj(row)
},
updateRequest (row) {
d2CrudPlus.util.dict.clear()
row.is_value = true
row.parent = this.dictionaryRow.id
return api.UpdateObj(row)
},
delRequest (row) {
return api.DelObj(row.id)
},
//
dictionaryConfigure (scope) {
this.drawer = true
}
}
}
</script>
<style lang="scss" scoped>
.yxtInput {
.el-form-item__label {
color: #49a1ff;
}
}
</style>

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

@ -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,226 @@
<template>
<div class="page-login">
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
import localeMixin from '@/locales/mixin.js'
import * as api from '@/views/system/login/api'
export default {
mixins: [localeMixin],
computed: {
...mapState('d2admin', {
siteName: (state) => state.settings.siteName, //
siteLogo: (state) => state.settings.siteLogo || require('./image/dvadmin.png'), // logo
loginBackground: (state) => state.settings.loginBackground || require('./image/bg.jpg'), //
copyright: (state) => state.settings.copyright, //
keepRecord: (state) => state.settings.keepRecord, //
helpUrl: (state) => state.settings.helpUrl, //
privacyUrl: (state) => state.settings.privacyUrl, //
clauseUrl: (state) => state.settings.clauseUrl //
})
},
beforeCreate () {
//
this.$store.dispatch('d2admin/settings/init')
},
data () {
return {
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'
}
],
captcha: [
{
required: true,
message: '请输入验证码',
trigger: 'blur'
}
]
},
captchaKey: null,
image_base: null
}
},
mounted () {
},
beforeDestroy () {
},
methods: {
...mapActions('d2admin/account', ['login']),
/**
* 获取验证码
*/
getCaptcha () {
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('表单校验失败,请检查')
}
})
}
},
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: 52em;
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;
}
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

View File

@ -1,35 +1,18 @@
<template> <template>
<div class="page-login"> <div class="w3l-signinform" :style="{background: 'url('+ (loginBackground || require('./image/bg.jpg')) + ') no-repeat center'}">
<div class="page-login--layer page-login--layer-area"> <!-- container -->
<ul class="circles"> <div class="wrapper">
<li v-for="n in 10" :key="n"></li> <!-- main content -->
</ul> <div class="w3l-form-info">
</div>
<div
class="page-login--layer page-login--layer-time"
flex="main:center cross:center"
>
{{ time }}
</div>
<div class="page-login--layer">
<div
class="page-login--content"
flex="dir:top main:justify cross:stretch box:justify"
>
<div class="page-login--content-header">
<p class="page-login--content-header-motto">
时间是一切财富中最宝贵的财富
</p>
</div>
<div
class="page-login--content-main"
flex="dir:top main:center cross:center"
>
<!-- logo --> <!-- logo -->
<img class="page-login--logo" src="./image/dvadmin.png" /> <img class="page-login--logo" :src="siteLogo" width="300"/>
<!-- form --> <div class="w3_info">
<div class="page-login--form"> <h2 style="text-align: center;">{{siteName || processTitle}}</h2>
<el-card shadow="never"> <el-card shadow="always" class="card">
<el-tabs v-model="activeName">
<el-tab-pane label="账号密码登录" name="first" stretch>
<span slot="label"><span style="margin: 30px;">账号密码登录</span></span>
<br>
<el-form <el-form
ref="loginForm" ref="loginForm"
label-position="top" label-position="top"
@ -41,19 +24,19 @@
<el-input <el-input
type="text" type="text"
v-model="formLogin.username" v-model="formLogin.username"
prefix-icon="el-icon-user-solid"
placeholder="用户名" placeholder="用户名"
> >
<i slot="prepend" class="fa fa-user-circle-o"></i>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input <el-input
type="password" type="password"
v-model="formLogin.password" v-model="formLogin.password"
prefix-icon="el-icon-s-promotion"
show-password show-password
placeholder="密码" placeholder="密码"
> >
<i slot="prepend" class="fa fa-keyboard-o"></i>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="captcha"> <el-form-item prop="captcha">
@ -61,459 +44,71 @@
type="text" type="text"
v-model="formLogin.captcha" v-model="formLogin.captcha"
placeholder="验证码" placeholder="验证码"
@keyup.enter.native="submit"
> >
<template slot="append"> <template slot="append">
<img <img
class="login-code" class="login-code"
height="33px"
width="145px"
slot="suffix"
:src="image_base" :src="image_base"
@click="getCaptcha" @click="getCaptcha"
/> />
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-button <button class="btn btn-primary btn-block" @click="submit"></button>
size="default"
@click="submit"
type="primary"
class="button-login"
>
登录
</el-button>
</el-form> </el-form>
</el-tab-pane>
</el-tabs>
</el-card> </el-card>
<!-- <p class="page-login--options" flex="main:justify cross:center"> <!-- footer -->
<span><d2-icon name="question-circle" /> 忘记密码</span> <div class="footer">
<span>注册用户</span> <p>Copyright &copy; {{copyright}}</p>
</p> --> <p>
<!-- quick login --> <a href="https://beian.miit.gov.cn" target="_blank">{{keepRecord}}</a> |
<!-- <el-button <a :href="helpUrl || '#'" target="_blank">帮助</a> |
class="page-login--quick" <a :href="privacyUrl || '#'" target="_blank">隐私</a> |
size="default" <a :href="clauseUrl || '#'" target="_blank">条款</a>
type="info"
@click="dialogVisible = true"
>
快速选择用户测试功能
</el-button> -->
</div>
</div>
<div class="page-login--content-footer">
<p class="page-login--content-footer-locales">
<a
v-for="language in $languages"
:key="language.value"
@click="onChangeLocale(language.value)"
>
{{ language.label }}
</a>
</p>
<p class="page-login--content-footer-copyright">
Copyright
<d2-icon name="copyright" />
Copyright © 2018-2021 pro.django-vue-admin.com All Rights Reserved.
|
<a href="https://beian.miit.gov.cn" target="_blank">
晋ICP备18005113号-3
</a>
</p>
<p class="page-login--content-footer-options">
<a href="#">帮助</a>
<a href="#">隐私</a>
<a href="#">条款</a>
</p> </p>
</div> </div>
<!-- footer -->
</div> </div>
</div> </div>
<el-dialog title="快速选择用户" :visible.sync="dialogVisible" width="400px"> <!-- //main content -->
<el-row :gutter="10" style="margin: -20px 0px -10px 0px">
<el-col v-for="(user, index) in users" :key="index" :span="8">
<div class="page-login--quick-user" @click="handleUserBtnClick(user)">
<d2-icon name="user-circle-o" />
<span>{{ user.name }}</span>
</div> </div>
</el-col> <!-- //container -->
</el-row>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import dayjs from 'dayjs' import base from './base.vue'
import { mapActions } from 'vuex'
import localeMixin from '@/locales/mixin.js'
import * as api from './api'
export default { export default {
mixins: [localeMixin], extends: base,
name: 'page',
data () { data () {
return { return {
timeInterval: null, activeName: 'first'
time: dayjs().format('HH:mm:ss'),
//
dialogVisible: false,
users: [
{
name: 'Admin',
username: 'admin',
password: 'admin'
},
{
name: 'Editor',
username: 'editor',
password: 'editor'
},
{
name: 'User1',
username: 'user1',
password: 'user1'
}
],
//
formLogin: {
username: '',
password: '',
captcha: ''
},
//
rules: {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur'
}
],
captcha: [
{
required: true,
message: '请输入验证码',
trigger: 'blur'
}
]
},
captchaKey: null,
image_base: null
}
},
mounted () {
this.timeInterval = setInterval(() => {
this.refreshTime()
}, 1000)
},
beforeDestroy () {
clearInterval(this.timeInterval)
},
methods: {
...mapActions('d2admin/account', ['login']),
refreshTime () {
this.time = dayjs().format('HH:mm:ss')
},
/**
* @description 接收选择一个用户快速登录的事件
* @param {Object} user 用户信息
*/
handleUserBtnClick (user) {
this.formLogin.username = user.username
this.formLogin.password = user.password
this.submit()
},
/**
* @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('表单校验失败,请检查')
}
})
},
/**
* 获取验证码
*/
getCaptcha () {
api.getCaptcha().then((ret) => {
this.formLogin.captcha = null
this.captchaKey = ret.data.data.key
this.image_base = ret.data.data.image_base
})
} }
}, },
created () { created () {
this.$store.dispatch('d2admin/db/databaseClear') },
this.getCaptcha() mounted () {
} },
methods: {}
} }
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.page-login { @import './css/style.css';
@extend %unable-select;
$backgroundColor: #f0f2f5; .copyrights {
// --- text-indent: -9999px;
background-color: $backgroundColor; height: 0;
height: 100%; line-height: 0;
position: relative; font-size: 0;
//
.page-login--layer {
@extend %full;
overflow: auto;
}
.page-login--layer-area {
overflow: hidden; overflow: hidden;
} }
//
.page-login--layer-time {
font-size: 24em;
font-weight: bold;
color: rgba(0, 0, 0, 0.03);
overflow: hidden;
}
//
.page-login--content {
height: 100%;
min-height: 500px;
}
// header
.page-login--content-header {
padding: 1em 0;
.page-login--content-header-motto {
margin: 0px;
padding: 0px;
color: $color-text-normal;
text-align: center;
font-size: 12px;
}
}
// main
.page-login--logo {
width: 240px;
margin-bottom: 2em;
margin-top: -2em;
}
//
.page-login--form {
width: 280px;
//
.el-card {
margin-bottom: 15px;
}
//
.button-login {
width: 100%;
}
//
.el-input-group__prepend {
padding: 0px 14px;
}
.login-code {
height: 40px - 2px;
display: block;
margin: 0px -20px;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
//
.page-login--options {
margin: 0px;
padding: 0px;
font-size: 14px;
color: $color-primary;
margin-bottom: 15px;
font-weight: bold;
}
.page-login--quick {
width: 100%;
}
}
//
.page-login--quick-user {
@extend %flex-center-col;
padding: 10px 0px;
border-radius: 4px;
&:hover {
background-color: $color-bg;
i {
color: $color-text-normal;
}
span {
color: $color-text-normal;
}
}
i {
font-size: 36px;
color: $color-text-sub;
}
span {
font-size: 12px;
margin-top: 10px;
color: $color-text-sub;
}
}
// footer
.page-login--content-footer {
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;
}
}
}
//
.circles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
margin: 0px;
padding: 0px;
li {
position: absolute;
display: block;
list-style: none;
width: 20px;
height: 20px;
background: #fff;
animation: animate 25s linear infinite;
bottom: -200px;
@keyframes animate {
0% {
transform: translateY(0) rotate(0deg);
opacity: 1;
border-radius: 0;
}
100% {
transform: translateY(-1000px) rotate(720deg);
opacity: 0;
border-radius: 50%;
}
}
&:nth-child(1) {
left: 15%;
width: 80px;
height: 80px;
animation-delay: 0s;
}
&:nth-child(2) {
left: 5%;
width: 20px;
height: 20px;
animation-delay: 2s;
animation-duration: 12s;
}
&:nth-child(3) {
left: 70%;
width: 20px;
height: 20px;
animation-delay: 4s;
}
&:nth-child(4) {
left: 40%;
width: 60px;
height: 60px;
animation-delay: 0s;
animation-duration: 18s;
}
&:nth-child(5) {
left: 65%;
width: 20px;
height: 20px;
animation-delay: 0s;
}
&:nth-child(6) {
left: 75%;
width: 150px;
height: 150px;
animation-delay: 3s;
}
&:nth-child(7) {
left: 35%;
width: 200px;
height: 200px;
animation-delay: 7s;
}
&:nth-child(8) {
left: 50%;
width: 25px;
height: 25px;
animation-delay: 15s;
animation-duration: 45s;
}
&:nth-child(9) {
left: 20%;
width: 15px;
height: 15px;
animation-delay: 2s;
animation-duration: 35s;
}
&:nth-child(10) {
left: 85%;
width: 150px;
height: 150px;
animation-delay: 0s;
animation-duration: 11s;
}
}
}
}
</style> </style>

View File

@ -1,5 +1,5 @@
import { request } from '@/api/service' import { request } from '@/api/service'
import { BUTTON_STATUS_BOOL, BUTTON_WHETHER_BOOL, BUTTON_VALUE_TO_COLOR_MAPPING } from '@/config/button' import { BUTTON_VALUE_TO_COLOR_MAPPING } from '@/config/button'
import { urlPrefix as menuPrefix } from './api' import { urlPrefix as menuPrefix } from './api'
import { urlPrefix as buttonPrefix } from '../button/api' import { urlPrefix as buttonPrefix } from '../button/api'
import XEUtils from 'xe-utils' import XEUtils from 'xe-utils'
@ -27,7 +27,8 @@ export const crudOptions = (vm) => {
options: { options: {
rowId: 'id', rowId: 'id',
height: '100%', // 表格高度100%, 使用toolbar必须设置 height: '100%', // 表格高度100%, 使用toolbar必须设置
highlightCurrentRow: false highlightCurrentRow: false,
defaultExpandAll: true
}, },
rowHandle: { rowHandle: {
view: { view: {
@ -129,14 +130,14 @@ export const crudOptions = (vm) => {
}, },
type: 'cascader', type: 'cascader',
dict: { dict: {
url: menuPrefix + '?limit=999&status=1&is_catalog=1', url: menuPrefix,
cache: false, cache: false,
isTree: true, isTree: true,
value: 'id', // 数据字典中value字段的属性名 value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名 label: 'name', // 数据字典中label字段的属性名
children: 'children', // 数据字典中children字段的属性名 children: 'children', // 数据字典中children字段的属性名
getData: (url, dict, { form, component }) => { // 配置此参数会覆盖全局的getRemoteDictFunc getData: (url, dict, { form, component }) => { // 配置此参数会覆盖全局的getRemoteDictFunc
return request({ url: url }).then(ret => { return request({ url: url, params: { limit: 999, status: 1, is_catalog: 1 } }).then(ret => {
const responseData = ret.data.data const responseData = ret.data.data
const result = XEUtils.toArrayTree(responseData, { parentKey: 'parent', strict: true }) const result = XEUtils.toArrayTree(responseData, { parentKey: 'parent', strict: true })
return [{ id: null, name: '根节点', children: result }] return [{ id: null, name: '根节点', children: result }]
@ -222,7 +223,7 @@ export const crudOptions = (vm) => {
disabled: true disabled: true
}, },
dict: { dict: {
data: BUTTON_WHETHER_BOOL data: vm.dictionary('button_whether_bool')
}, },
form: { form: {
value: false, value: false,
@ -237,7 +238,7 @@ export const crudOptions = (vm) => {
width: 70, width: 70,
type: 'radio', type: 'radio',
dict: { dict: {
data: BUTTON_WHETHER_BOOL data: vm.dictionary('button_whether_bool')
}, },
form: { form: {
value: false, value: false,
@ -395,7 +396,7 @@ export const crudOptions = (vm) => {
width: 50, width: 50,
type: 'radio', type: 'radio',
dict: { dict: {
data: BUTTON_WHETHER_BOOL data: vm.dictionary('button_whether_bool')
}, },
form: { form: {
value: false, value: false,
@ -423,7 +424,7 @@ export const crudOptions = (vm) => {
width: 75, width: 75,
type: 'radio', type: 'radio',
dict: { dict: {
data: BUTTON_WHETHER_BOOL data: vm.dictionary('button_whether_bool')
}, },
form: { form: {
value: true, value: true,
@ -448,7 +449,7 @@ export const crudOptions = (vm) => {
width: 70, width: 70,
type: 'radio', type: 'radio',
dict: { dict: {
data: BUTTON_STATUS_BOOL data: vm.dictionary('button_status_bool')
}, },
form: { form: {
value: true, value: true,
@ -457,6 +458,8 @@ export const crudOptions = (vm) => {
} }
} }
} }
].concat(vm.commonEndColumns({ update_datetime: { showTable: false } })) ].concat(vm.commonEndColumns({
update_datetime: { showTable: false }
}))
} }
} }

View File

@ -1,5 +1,3 @@
import { BUTTON_STATUS_BOOL, BUTTON_WHETHER_BOOL } from '@/config/button'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
return { return {
pageOptions: { pageOptions: {
@ -161,7 +159,7 @@ export const crudOptions = (vm) => {
type: 'radio', type: 'radio',
dict: { dict: {
data: BUTTON_WHETHER_BOOL data: vm.dictionary('button_whether_bool')
}, },
form: { form: {
value: false, value: false,
@ -180,7 +178,7 @@ export const crudOptions = (vm) => {
}, },
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,7 +1,5 @@
import { request } from '@/api/service' import { request } from '@/api/service'
import { BUTTON_STATUS_BOOL } from '@/config/button'
import { urlPrefix as deptPrefix } from '../dept/api' import { urlPrefix as deptPrefix } from '../dept/api'
import util from '@/libs/util'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
return { return {
@ -9,11 +7,13 @@ export const crudOptions = (vm) => {
compact: true compact: true
}, },
options: { options: {
height: '100%' height: '100%',
tableType: 'vxe-table',
rowKey: true // 必须设置true or false
}, },
rowHandle: { rowHandle: {
width: 230,
fixed: 'right', fixed: 'right',
width: 180,
view: { view: {
thin: true, thin: true,
text: '', text: '',
@ -37,18 +37,18 @@ export const crudOptions = (vm) => {
}, },
custom: [ custom: [
{ {
thin: true,
text: '',
size: 'small',
type: 'warning',
icon: 'el-icon-refresh-left',
show () { show () {
return vm.hasPermissions('ResetPwd') return vm.hasPermissions('ResetPassword')
}, },
emit: 'resetPwd' disabled () {
return !vm.hasPermissions('ResetPassword')
},
text: '重置密码',
type: 'warning',
size: 'small',
emit: 'resetPassword'
} }
] ]
}, },
viewOptions: { viewOptions: {
componentType: 'form' componentType: 'form'
@ -59,7 +59,7 @@ export const crudOptions = (vm) => {
indexRow: { // 或者直接传true,不显示title不居中 indexRow: { // 或者直接传true,不显示title不居中
title: '序号', title: '序号',
align: 'center', align: 'center',
width: 70 width: 60
}, },
columns: [ columns: [
{ {
@ -83,7 +83,6 @@ export const crudOptions = (vm) => {
{ {
title: 'ID', title: 'ID',
key: 'id', key: 'id',
width: 90,
disabled: true, disabled: true,
form: { form: {
disabled: true disabled: true
@ -95,7 +94,7 @@ export const crudOptions = (vm) => {
search: { search: {
disabled: false disabled: false
}, },
width: 140, minWidth: 100,
type: 'input', type: 'input',
form: { form: {
rules: [ // 表单校验规则 rules: [ // 表单校验规则
@ -118,6 +117,7 @@ export const crudOptions = (vm) => {
{ {
title: '姓名', title: '姓名',
key: 'name', key: 'name',
minWidth: 90,
search: { search: {
disabled: false disabled: false
}, },
@ -166,9 +166,9 @@ export const crudOptions = (vm) => {
}, },
component: { component: {
span: 12, span: 12,
pagination: true,
props: { multiple: false }, props: { multiple: false },
elProps: { elProps: {
pagination: true,
columns: [ columns: [
{ {
field: 'name', field: 'name',
@ -198,7 +198,7 @@ export const crudOptions = (vm) => {
form: { form: {
rules: [ rules: [
{ max: 20, message: '请输入正确的手机号码', trigger: 'blur' }, { max: 20, message: '请输入正确的手机号码', trigger: 'blur' },
{ pattern: /^1[3|4|5|7|8]\d{9}$/, message: '请输入正确的手机号码' } { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
], ],
itemProps: { itemProps: {
class: { yxtInput: true } class: { yxtInput: true }
@ -210,7 +210,7 @@ export const crudOptions = (vm) => {
}, { }, {
title: '邮箱', title: '邮箱',
key: 'email', key: 'email',
minWidth: 160, minWidth: 180,
form: { form: {
rules: [ rules: [
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] } { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
@ -226,7 +226,7 @@ export const crudOptions = (vm) => {
type: 'radio', type: 'radio',
width: 70, width: 70,
dict: { dict: {
data: [{ label: '', value: 1 }, { label: '', value: 0 }] data: vm.dictionary('gender')
}, },
form: { form: {
value: 1, value: 1,
@ -235,8 +235,26 @@ export const crudOptions = (vm) => {
} }
}, },
component: { props: { color: 'auto' } } // 自动染色 component: { props: { color: 'auto' } } // 自动染色
}, {
title: '用户类型',
key: 'user_type',
search: {
value: 0,
disabled: false
}, },
{ width: 140,
type: 'select',
dict: {
data: vm.dictionary('user_type')
},
form: {
show: false,
value: 0,
component: {
span: 12
}
}
}, {
title: '状态', title: '状态',
key: 'is_active', key: 'is_active',
search: { search: {
@ -245,7 +263,7 @@ export const crudOptions = (vm) => {
width: 70, width: 70,
type: 'radio', type: 'radio',
dict: { dict: {
data: BUTTON_STATUS_BOOL data: vm.dictionary('button_status_bool')
}, },
form: { form: {
value: true, value: true,
@ -257,41 +275,21 @@ export const crudOptions = (vm) => {
{ {
title: '头像', title: '头像',
key: 'avatar', key: 'avatar',
type: 'avatar-uploader', type: 'avatar-cropper',
width: 100, width: 60,
align: 'left', align: 'left',
form: { form: {
component: { component: {
props: { props: {
elProps: { // 与el-uploader 配置一致 elProps: { // 与el-uploader 配置一致
multiple: true, multiple: false,
limit: 5 // 限制5个文件 limit: 1 // 限制5个文件
}, },
sizeLimit: 100 * 1024 // 不能超过限制 sizeLimit: 500 * 1024 // 不能超过限制
}, },
span: 24 span: 24
}, },
helper: '限制文件大小不能超过50k' helper: '限制文件大小不能超过500k'
},
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 util.baseURL() + value
}
return value
}
}
} }
}, },
{ {
@ -325,9 +323,9 @@ export const crudOptions = (vm) => {
}, },
component: { component: {
span: 12, span: 12,
pagination: true,
props: { multiple: true }, props: { multiple: true },
elProps: { elProps: {
pagination: true,
columns: [ columns: [
{ {
field: 'name', field: 'name',
@ -346,6 +344,9 @@ export const crudOptions = (vm) => {
} }
} }
} }
].concat(vm.commonEndColumns({ update_datetime: { showForm: false, showTable: false }, create_datetime: { showForm: false, showTable: true } })) ].concat(vm.commonEndColumns({
create_datetime: { showTable: false },
update_datetime: { showTable: false }
}))
} }
} }

View File

@ -4,8 +4,7 @@
ref="d2Crud" ref="d2Crud"
v-bind="_crudProps" v-bind="_crudProps"
v-on="_crudListeners" v-on="_crudListeners"
crud.options.tableType="vxe-table" @resetPassword="resetPassword"
@resetPwd="resetPwd"
> >
<div slot="header"> <div slot="header">
<crud-search <crud-search
@ -19,8 +18,9 @@
v-permission="'Create'" v-permission="'Create'"
type="primary" type="primary"
@click="addRow" @click="addRow"
><i class="el-icon-plus" /> 新增</el-button
> >
<i class="el-icon-plus" /> 新增
</el-button>
</el-button-group> </el-button-group>
<crud-toolbar <crud-toolbar
:search.sync="crud.searchOptions.show" :search.sync="crud.searchOptions.show"
@ -31,7 +31,7 @@
/> />
</div> </div>
</d2-crud-x> </d2-crud-x>
<el-dialog title="密码重置" :visible.sync="dialogFormVisible" :close-on-click-modal="false"> <!-- <el-dialog title="密码重置" :visible.sync="dialogFormVisible" :close-on-click-modal="false">
<el-form :model="resetPwdForm" ref="resetPwdForm" :rules="passwordRules"> <el-form :model="resetPwdForm" ref="resetPwdForm" :rules="passwordRules">
<el-form-item label="密码" prop="pwd"> <el-form-item label="密码" prop="pwd">
<el-input v-model="resetPwdForm.pwd" type="password" show-password clearable autocomplete="off"></el-input> <el-input v-model="resetPwdForm.pwd" type="password" show-password clearable autocomplete="off"></el-input>
@ -44,7 +44,7 @@
<el-button @click="dialogFormVisible = false"> </el-button> <el-button @click="dialogFormVisible = false"> </el-button>
<el-button type="primary" @click="resetPwdSubmit"> </el-button> <el-button type="primary" @click="resetPwdSubmit"> </el-button>
</div> </div>
</el-dialog> </el-dialog> -->
</d2-container> </d2-container>
</template> </template>
@ -57,43 +57,44 @@ export default {
name: 'user', name: 'user',
mixins: [d2CrudPlus.crud], mixins: [d2CrudPlus.crud],
data () { data () {
var validatePass = (rule, value, callback) => { // var validatePass = (rule, value, callback) => {
const pwdRegex = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z]).{8,30}') // const pwdRegex = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z]).{8,30}')
if (value === '') { // if (value === '') {
callback(new Error('请输入密码')) // callback(new Error(''))
} else if (!pwdRegex.test(value)) { // } else if (!pwdRegex.test(value)) {
callback(new Error('您的密码复杂度太低(密码中必须包含字母、数字)')) // callback(new Error('()'))
} else { // } else {
if (this.resetPwdForm.pwd2 !== '') { // if (this.resetPwdForm.pwd2 !== '') {
this.$refs.resetPwdForm.validateField('pwd2') // this.$refs.resetPwdForm.validateField('pwd2')
} // }
callback() // callback()
} // }
} // }
var validatePass2 = (rule, value, callback) => { // var validatePass2 = (rule, value, callback) => {
if (value === '') { // if (value === '') {
callback(new Error('请再次输入密码')) // callback(new Error(''))
} else if (value !== this.resetPwdForm.pwd) { // } else if (value !== this.resetPwdForm.pwd) {
callback(new Error('两次输入密码不一致!')) // callback(new Error('!'))
} else { // } else {
callback() // callback()
} // }
} // }
return { return {
dialogFormVisible: false, // dialogFormVisible: false,
resetPwdForm: { // resetPwdForm: {
id: null, // id: null,
pwd: null, // pwd: null,
pwd2: null // pwd2: null
}, // },
passwordRules: { // passwordRules: {
pwd: [{ required: true, message: '必填项' }, { validator: validatePass, trigger: 'blur' }], // pwd: [{ required: true, message: '' }, { validator: validatePass, trigger: 'blur' }],
pwd2: [{ required: true, message: '必填项' }, { validator: validatePass2, trigger: 'blur' }] // pwd2: [{ required: true, message: '' }, { validator: validatePass2, trigger: 'blur' }]
} // }
} }
}, },
methods: { methods: {
getCrudOptions () { getCrudOptions () {
this.crud.searchOptions.form.user_type = 0
return crudOptions(this) return crudOptions(this)
}, },
pageRequest (query) { pageRequest (query) {
@ -108,35 +109,40 @@ export default {
delRequest (row) { delRequest (row) {
return api.DelObj(row.id) return api.DelObj(row.id)
}, },
// resetPassword (scope) {
resetPwd ({ row }) { api.ResetPwd(scope.row).then((res) => {
this.dialogFormVisible = true this.$message.success('密码重置成功')
this.resetPwdForm.id = row.id
},
//
resetPwdSubmit () {
const that = this
that.$refs.resetPwdForm.validate((valid) => {
if (valid) {
const params = {
id: that.resetPwdForm.id,
newPassword: that.$md5(that.resetPwdForm.pwd),
newPassword2: that.$md5(that.resetPwdForm.pwd2)
}
api.ResetPwd(params).then(res => {
that.dialogFormVisible = false
that.resetPwdForm = {
id: null,
pwd: null,
pwd2: null
}
that.$message.success('修改成功')
})
} else {
that.$message.error('表单校验失败,请检查')
}
}) })
} }
// //
// resetPwd ({ row }) {
// this.dialogFormVisible = true
// this.resetPwdForm.id = row.id
// },
// //
// resetPwdSubmit () {
// const that = this
// that.$refs.resetPwdForm.validate((valid) => {
// if (valid) {
// const params = {
// id: that.resetPwdForm.id,
// newPassword: that.$md5(that.resetPwdForm.pwd),
// newPassword2: that.$md5(that.resetPwdForm.pwd2)
// }
// api.ResetPwd(params).then(res => {
// that.dialogFormVisible = false
// that.resetPwdForm = {
// id: null,
// pwd: null,
// pwd2: null
// }
// that.$message.success('')
// })
// } else {
// that.$message.error('')
// }
// })
// }
} }
} }
</script> </script>

View File

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

11920
web/yarn.lock Normal file

File diff suppressed because it is too large Load Diff