!53 功能重构 & 部分bug修复
后端 refactor: 部分定制化配置迁移至conf.env feature:验证码启停状态查询接口 feature:重置密码(自动恢复为系统默认密码,原代码块已注释,可以转为更改密码功能) feature:unique mobile fixed:修复控制台log重复打印 前端 fixed:更新手机号正则,适配19x/16x等号段 feature:前后端登录验证码启停同步(建议结合system_config集成至后台管理) refactor: getData请求参数pull/54/head
commit
8509a18816
|
@ -1,3 +1,5 @@
|
|||
/backend/venv
|
||||
/backend/.idea
|
||||
.idea
|
||||
|
||||
.history/
|
|
@ -88,11 +88,11 @@ ENV/
|
|||
.idea/
|
||||
*.db
|
||||
.DS_Store
|
||||
__pycache__
|
||||
**/migrations
|
||||
**/migrations/*.py
|
||||
!**/migrations/__init__.py
|
||||
*.pyc
|
||||
conf/
|
||||
!conf/env.example.py
|
||||
db.sqlite3
|
||||
media/
|
||||
__pypackages__/
|
|
@ -27,108 +27,111 @@ from conf.env import *
|
|||
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||
|
||||
# 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_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, 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!
|
||||
DEBUG = locals().get('DEBUG', True)
|
||||
ALLOWED_HOSTS = locals().get('ALLOWED_HOSTS', ['*'])
|
||||
DEBUG = locals().get("DEBUG", True)
|
||||
ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django_comment_migrate',
|
||||
'rest_framework',
|
||||
'django_filters',
|
||||
'corsheaders', # 注册跨域app
|
||||
'dvadmin.system',
|
||||
'drf_yasg',
|
||||
'captcha',
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django_comment_migrate",
|
||||
"rest_framework",
|
||||
"django_filters",
|
||||
"corsheaders", # 注册跨域app
|
||||
"dvadmin.system",
|
||||
"drf_yasg",
|
||||
"captcha",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware', # 跨域中间件
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'dvadmin.utils.middleware.ApiLoggingMiddleware',
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"corsheaders.middleware.CorsMiddleware", # 跨域中间件
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"dvadmin.utils.middleware.ApiLoggingMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'application.urls'
|
||||
ROOT_URLCONF = "application.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [os.path.join(BASE_DIR, "templates")],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'application.wsgi.application'
|
||||
WSGI_APPLICATION = "application.wsgi.application"
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': DATABASE_ENGINE,
|
||||
'NAME': DATABASE_NAME,
|
||||
'USER': DATABASE_USER,
|
||||
'PASSWORD': DATABASE_PASSWORD,
|
||||
'HOST': DATABASE_HOST,
|
||||
'PORT': DATABASE_PORT,
|
||||
"default": {
|
||||
"ENGINE": DATABASE_ENGINE,
|
||||
"NAME": DATABASE_NAME,
|
||||
"USER": DATABASE_USER,
|
||||
"PASSWORD": DATABASE_PASSWORD,
|
||||
"HOST": DATABASE_HOST,
|
||||
"PORT": DATABASE_PORT,
|
||||
}
|
||||
}
|
||||
AUTH_USER_MODEL = 'system.Users'
|
||||
USERNAME_FIELD = 'username'
|
||||
AUTH_USER_MODEL = "system.Users"
|
||||
USERNAME_FIELD = "username"
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#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
|
||||
# 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
|
||||
|
||||
|
@ -139,13 +142,13 @@ USE_TZ = False
|
|||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_URL = "/static/"
|
||||
# # 设置django的静态文件目录
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "static"),
|
||||
]
|
||||
|
||||
MEDIA_ROOT = 'media' # 项目下的目录
|
||||
MEDIA_ROOT = "media" # 项目下的目录
|
||||
MEDIA_URL = "/media/" # 跟STATIC_URL类似,指定用户可以通过这个url找到文件
|
||||
|
||||
# 收集静态文件,必须将 MEDIA_ROOT,STATICFILES_DIRS先注释
|
||||
|
@ -166,78 +169,82 @@ CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中,后端是否支持
|
|||
# ================================================= #
|
||||
|
||||
# log 配置部分BEGIN #
|
||||
SERVER_LOGS_FILE = os.path.join(BASE_DIR, 'logs', 'server.log')
|
||||
ERROR_LOGS_FILE = os.path.join(BASE_DIR, 'logs', 'error.log')
|
||||
if not os.path.exists(os.path.join(BASE_DIR, 'logs')):
|
||||
os.makedirs(os.path.join(BASE_DIR, 'logs'))
|
||||
SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
|
||||
ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
|
||||
if not os.path.exists(os.path.join(BASE_DIR, "logs")):
|
||||
os.makedirs(os.path.join(BASE_DIR, "logs"))
|
||||
|
||||
# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
|
||||
# 格式:[日期][模块.函数名称():行号] [级别] 信息
|
||||
STANDARD_LOG_FORMAT = '[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s'
|
||||
CONSOLE_LOG_FORMAT = '[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s'
|
||||
STANDARD_LOG_FORMAT = (
|
||||
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
|
||||
)
|
||||
CONSOLE_LOG_FORMAT = (
|
||||
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
|
||||
)
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'standard': {
|
||||
'format': STANDARD_LOG_FORMAT
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"standard": {"format": STANDARD_LOG_FORMAT},
|
||||
"console": {
|
||||
"format": CONSOLE_LOG_FORMAT,
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
'console': {
|
||||
'format': CONSOLE_LOG_FORMAT,
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
},
|
||||
'file': {
|
||||
'format': CONSOLE_LOG_FORMAT,
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
"file": {
|
||||
"format": CONSOLE_LOG_FORMAT,
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'file': {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': SERVER_LOGS_FILE,
|
||||
'maxBytes': 1024 * 1024 * 100, # 100 MB
|
||||
'backupCount': 5, # 最多备份5个
|
||||
'formatter': 'standard',
|
||||
'encoding': 'utf-8',
|
||||
"handlers": {
|
||||
"file": {
|
||||
"level": "INFO",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": SERVER_LOGS_FILE,
|
||||
"maxBytes": 1024 * 1024 * 100, # 100 MB
|
||||
"backupCount": 5, # 最多备份5个
|
||||
"formatter": "standard",
|
||||
"encoding": "utf-8",
|
||||
},
|
||||
'error': {
|
||||
'level': 'ERROR',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': ERROR_LOGS_FILE,
|
||||
'maxBytes': 1024 * 1024 * 100, # 100 MB
|
||||
'backupCount': 3, # 最多备份3个
|
||||
'formatter': 'standard',
|
||||
'encoding': 'utf-8',
|
||||
"error": {
|
||||
"level": "ERROR",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": ERROR_LOGS_FILE,
|
||||
"maxBytes": 1024 * 1024 * 100, # 100 MB
|
||||
"backupCount": 3, # 最多备份3个
|
||||
"formatter": "standard",
|
||||
"encoding": "utf-8",
|
||||
},
|
||||
"console": {
|
||||
"level": "INFO",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "console",
|
||||
},
|
||||
'console': {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'console',
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
"loggers": {
|
||||
# default日志
|
||||
'': {
|
||||
'handlers': ['console', 'error', 'file'],
|
||||
'level': 'INFO',
|
||||
"": {
|
||||
"handlers": ["console", "error", "file"],
|
||||
"level": "INFO",
|
||||
},
|
||||
'django': {
|
||||
'handlers': ['console', 'error', 'file'],
|
||||
'level': 'INFO',
|
||||
"django": {
|
||||
"handlers": ["console", "error", "file"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
'scripts': {
|
||||
'handlers': ['console', 'error', 'file'],
|
||||
'level': 'INFO',
|
||||
"scripts": {
|
||||
"handlers": ["console", "error", "file"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
# 数据库相关日志
|
||||
'django.db.backends': {
|
||||
'handlers': [],
|
||||
'propagate': True,
|
||||
'level': 'INFO',
|
||||
"django.db.backends": {
|
||||
"handlers": [],
|
||||
"propagate": True,
|
||||
"level": "INFO",
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# ================================================= #
|
||||
|
@ -245,34 +252,32 @@ LOGGING = {
|
|||
# ================================================= #
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S", # 日期时间格式配置
|
||||
'DATE_FORMAT': "%Y-%m-%d",
|
||||
'DEFAULT_FILTER_BACKENDS': (
|
||||
"DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S", # 日期时间格式配置
|
||||
"DATE_FORMAT": "%Y-%m-%d",
|
||||
"DEFAULT_FILTER_BACKENDS": (
|
||||
# 'django_filters.rest_framework.DjangoFilterBackend',
|
||||
'dvadmin.utils.filters.CustomDjangoFilterBackend',
|
||||
'rest_framework.filters.SearchFilter',
|
||||
'rest_framework.filters.OrderingFilter',
|
||||
"dvadmin.utils.filters.CustomDjangoFilterBackend",
|
||||
"rest_framework.filters.SearchFilter",
|
||||
"rest_framework.filters.OrderingFilter",
|
||||
),
|
||||
'DEFAULT_PAGINATION_CLASS': 'dvadmin.utils.pagination.CustomPagination', # 自定义分页
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
"DEFAULT_PAGINATION_CLASS": "dvadmin.utils.pagination.CustomPagination", # 自定义分页
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
),
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.IsAuthenticated', # 只有经过身份认证确定用户身份才能访问
|
||||
"DEFAULT_PERMISSION_CLASSES": [
|
||||
"rest_framework.permissions.IsAuthenticated", # 只有经过身份认证确定用户身份才能访问
|
||||
# 'rest_framework.permissions.IsAdminUser', # is_staff=True才能访问 —— 管理员(员工)权限
|
||||
# 'rest_framework.permissions.AllowAny', # 允许所有
|
||||
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 有身份 或者 只读访问(self.list,self.retrieve)
|
||||
],
|
||||
'EXCEPTION_HANDLER': 'dvadmin.utils.exception.CustomExceptionHandler', # 自定义的异常处理
|
||||
"EXCEPTION_HANDLER": "dvadmin.utils.exception.CustomExceptionHandler", # 自定义的异常处理
|
||||
}
|
||||
# ================================================= #
|
||||
# ******************** 登录方式配置 ******************** #
|
||||
# ================================================= #
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'dvadmin.utils.backends.CustomBackend'
|
||||
]
|
||||
AUTHENTICATION_BACKENDS = ["dvadmin.utils.backends.CustomBackend"]
|
||||
# ================================================= #
|
||||
# ****************** simplejwt配置 ***************** #
|
||||
# ================================================= #
|
||||
|
@ -280,12 +285,12 @@ from datetime import timedelta
|
|||
|
||||
SIMPLE_JWT = {
|
||||
# token有效时长
|
||||
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=120),
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=120),
|
||||
# token刷新后的有效时间
|
||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
|
||||
# 设置前缀
|
||||
'AUTH_HEADER_TYPES': ('JWT',),
|
||||
'ROTATE_REFRESH_TOKENS': True,
|
||||
"AUTH_HEADER_TYPES": ("JWT",),
|
||||
"ROTATE_REFRESH_TOKENS": True,
|
||||
}
|
||||
|
||||
# ====================================#
|
||||
|
@ -293,70 +298,63 @@ SIMPLE_JWT = {
|
|||
# ====================================#
|
||||
SWAGGER_SETTINGS = {
|
||||
# 基础样式
|
||||
'SECURITY_DEFINITIONS': {
|
||||
"basic": {
|
||||
'type': 'basic'
|
||||
}
|
||||
},
|
||||
"SECURITY_DEFINITIONS": {"basic": {"type": "basic"}},
|
||||
# 如果需要登录才能够查看接口文档, 登录的链接使用restframework自带的.
|
||||
|
||||
'LOGIN_URL': 'apiLogin/',
|
||||
"LOGIN_URL": "apiLogin/",
|
||||
# 'LOGIN_URL': 'rest_framework:login',
|
||||
'LOGOUT_URL': 'rest_framework:logout',
|
||||
"LOGOUT_URL": "rest_framework:logout",
|
||||
# 'DOC_EXPANSION': None,
|
||||
# 'SHOW_REQUEST_HEADERS':True,
|
||||
# 'USE_SESSION_AUTH': True,
|
||||
# 'DOC_EXPANSION': 'list',
|
||||
# 接口文档中方法列表以首字母升序排列
|
||||
'APIS_SORTER': 'alpha',
|
||||
"APIS_SORTER": "alpha",
|
||||
# 如果支持json提交, 则接口文档中包含json输入框
|
||||
'JSON_EDITOR': True,
|
||||
"JSON_EDITOR": True,
|
||||
# 方法列表字母排序
|
||||
'OPERATIONS_SORTER': 'alpha',
|
||||
'VALIDATOR_URL': None,
|
||||
'AUTO_SCHEMA_TYPE': 2, # 分组根据url层级分,0、1 或 2 层
|
||||
'DEFAULT_AUTO_SCHEMA_CLASS': 'dvadmin.utils.swagger.CustomSwaggerAutoSchema',
|
||||
"OPERATIONS_SORTER": "alpha",
|
||||
"VALIDATOR_URL": None,
|
||||
"AUTO_SCHEMA_TYPE": 2, # 分组根据url层级分,0、1 或 2 层
|
||||
"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_LENGTH = 4 # 字符个数
|
||||
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_FOREGROUND_COLOR = '#0033FF' # 前景色
|
||||
CAPTCHA_BACKGROUND_COLOR = '#F5F7F4' # 背景色
|
||||
CAPTCHA_FOREGROUND_COLOR = "#0033FF" # 前景色
|
||||
CAPTCHA_BACKGROUND_COLOR = "#F5F7F4" # 背景色
|
||||
CAPTCHA_NOISE_FUNCTIONS = (
|
||||
'captcha.helpers.noise_arcs', # 线
|
||||
'captcha.helpers.noise_dots', # 点
|
||||
"captcha.helpers.noise_arcs", # 线
|
||||
"captcha.helpers.noise_dots", # 点
|
||||
)
|
||||
# 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_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 = {
|
||||
"/token/": "登录模块",
|
||||
"/api/login/": "登录模块",
|
||||
"/api/plugins_market/plugins/": "插件市场",
|
||||
}
|
||||
# 表前缀
|
||||
TABLE_PREFIX = "dvadmin_"
|
||||
|
||||
DJANGO_CELERY_BEAT_TZ_AWARE = False
|
||||
CELERY_TIMEZONE = 'Asia/Shanghai' # celery 时区问题
|
||||
CELERY_TIMEZONE = "Asia/Shanghai" # celery 时区问题
|
||||
# 静态页面压缩
|
||||
STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage'
|
||||
# 初始化需要执行的列表,用来初始化后执行
|
||||
INITIALIZE_RESET_LIST = []
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage"
|
||||
|
||||
ALL_MODELS_OBJECTS = [] # 所有app models 对象
|
||||
# dvadmin 插件
|
||||
REGISTER_PLUGINS = (
|
||||
|
|
|
@ -23,13 +23,19 @@ from rest_framework_simplejwt.views import (
|
|||
)
|
||||
|
||||
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.utils.swagger import CustomOpenAPISchemaGenerator
|
||||
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="Snippets API",
|
||||
default_version='v1',
|
||||
default_version="v1",
|
||||
description="Test description",
|
||||
terms_of_service="https://www.google.com/policies/terms/",
|
||||
contact=openapi.Contact(email="contact@snippets.local"),
|
||||
|
@ -38,20 +44,38 @@ schema_view = get_schema_view(
|
|||
public=True,
|
||||
permission_classes=(permissions.AllowAny,),
|
||||
generator_class=CustomOpenAPISchemaGenerator,
|
||||
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0),
|
||||
name='schema-json'),
|
||||
path('', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
path(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('apiLogin/', ApiLogin.as_view()),
|
||||
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + static(settings.STATIC_URL,
|
||||
document_root=settings.STATIC_URL)
|
||||
urlpatterns = (
|
||||
[
|
||||
re_path(
|
||||
r"^swagger(?P<format>\.json|\.yaml)$",
|
||||
schema_view.without_ui(cache_timeout=0),
|
||||
name="schema-json",
|
||||
),
|
||||
path(
|
||||
"",
|
||||
schema_view.with_ui("swagger", cache_timeout=0),
|
||||
name="schema-swagger-ui",
|
||||
),
|
||||
path(
|
||||
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("apiLogin/", ApiLogin.as_view()),
|
||||
# 业务路由
|
||||
# re_path(r'^api/app_route/', include('apps.app_name.urls')),
|
||||
]
|
||||
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL)
|
||||
)
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
import os
|
||||
|
||||
from application.settings import BASE_DIR
|
||||
|
||||
# ================================================= #
|
||||
# ************** 数据库 配置 ************** #
|
||||
# *************** mysql数据库 配置 *************** #
|
||||
# ================================================= #
|
||||
|
||||
# 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库
|
||||
# sqlite3 设置
|
||||
DATABASE_ENGINE = "django.db.backends.sqlite3"
|
||||
DATABASE_NAME = os.path.join(BASE_DIR, 'db.sqlite3')
|
||||
|
||||
# 使用mysql时,改为此配置
|
||||
# DATABASE_ENGINE = "django.db.backends.mysql"
|
||||
# DATABASE_NAME = 'django-vue-admin' # mysql 时使用
|
||||
# 数据库地址
|
||||
DATABASE_ENGINE = "django.db.backends.mysql"
|
||||
# 数据库地址 改为自己数据库地址
|
||||
DATABASE_HOST = "127.0.0.1"
|
||||
# # 数据库端口
|
||||
|
@ -22,17 +11,39 @@ DATABASE_PORT = 3306
|
|||
DATABASE_USER = "root"
|
||||
# # 数据库密码
|
||||
DATABASE_PASSWORD = "123456"
|
||||
# 数据库名
|
||||
DATABASE_NAME = "database_name"
|
||||
|
||||
# 表前缀
|
||||
TABLE_PREFIX = "sys_"
|
||||
APP_PREFIX = "app_"
|
||||
# ================================================= #
|
||||
# ************** redis配置,无redis 可不进行配置 ************** #
|
||||
# ******** redis配置,无redis 可不进行配置 ******** #
|
||||
# ================================================= #
|
||||
# REDIS_PASSWORD = ''
|
||||
# REDIS_HOST = '127.0.0.1'
|
||||
# REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6380'
|
||||
# ================================================= #
|
||||
# ************** 其他 配置 ************** #
|
||||
# ****************** 功能 启停 ******************* #
|
||||
# ================================================= #
|
||||
DEBUG = True # 线上环境请设置为True
|
||||
DEBUG = False
|
||||
# 是否启用插件,不需要可以设置为False
|
||||
ENABLE_PLUGINS = False
|
||||
# 启动登录详细概略获取(通过调用api获取ip详细地址)
|
||||
ENABLE_LOGIN_ANALYSIS_LOG = True
|
||||
# 是否启用登录验证码,不需要可以设置为False
|
||||
CAPTCHA_STATE = False
|
||||
# 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
|
||||
LOGIN_NO_CAPTCHA_AUTH = True
|
||||
# ================================================= #
|
||||
# ****************** 其他 配置 ******************* #
|
||||
# ================================================= #
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
|
||||
ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址)
|
||||
|
||||
# 默认密码
|
||||
DEFAULT_PASSWORD = "admin123456"
|
||||
|
||||
# 初始化需要执行的列表,用来初始化后执行
|
||||
INITIALIZE_LIST = []
|
||||
INITIALIZE_RESET_LIST = []
|
||||
|
|
|
@ -149,7 +149,7 @@ button_data = [
|
|||
"update_datetime": datetime.datetime.now(),
|
||||
"create_datetime": datetime.datetime.now(),
|
||||
"name": "重置密码",
|
||||
"value": "ResetPwd",
|
||||
"value": "ResetPassword",
|
||||
"creator_id": 1,
|
||||
},
|
||||
]
|
||||
|
@ -1251,7 +1251,7 @@ menu_button_data = [
|
|||
"update_datetime": datetime.datetime.now(),
|
||||
"create_datetime": datetime.datetime.now(),
|
||||
"name": "重置密码",
|
||||
"value": "ResetPwd",
|
||||
"value": "ResetPassword",
|
||||
"api": "/api/system/user/reset_password/{id}/",
|
||||
"method": 2,
|
||||
"creator_id": 1,
|
||||
|
@ -1362,7 +1362,7 @@ staff_data = [
|
|||
"create_datetime": datetime.datetime.now(),
|
||||
"username": "admin",
|
||||
"email": "dvadmin@django-vue-admin.com",
|
||||
"mobile": "13333333333",
|
||||
"mobile": "18888888888",
|
||||
"avatar": "",
|
||||
"name": "管理员",
|
||||
"gender": 1,
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/2 002 14:20
|
||||
@Remark:登录视图
|
||||
"""
|
||||
import base64
|
||||
import hashlib
|
||||
from datetime import datetime, timedelta
|
||||
|
@ -24,7 +16,7 @@ from rest_framework_simplejwt.views import TokenObtainPairView
|
|||
|
||||
from application import settings
|
||||
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.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.validator import CustomValidationError
|
||||
|
@ -35,21 +27,33 @@ class CaptchaView(APIView):
|
|||
permission_classes = []
|
||||
|
||||
@swagger_auto_schema(
|
||||
responses={
|
||||
'200': openapi.Response('获取成功')
|
||||
},
|
||||
responses={"200": openapi.Response("获取成功")},
|
||||
security=[],
|
||||
operation_id='captcha-get',
|
||||
operation_description='验证码获取',
|
||||
operation_id="captcha-get",
|
||||
operation_description="验证码获取",
|
||||
)
|
||||
def get(self, request):
|
||||
hashkey = CaptchaStore.generate_key()
|
||||
id = CaptchaStore.objects.filter(hashkey=hashkey).first().id
|
||||
imgage = captcha_image(request, hashkey)
|
||||
# 将图片转换为base64
|
||||
image_base = base64.b64encode(imgage.content)
|
||||
json_data = {"key": id, "image_base": "data:image/png;base64," + image_base.decode('utf-8')}
|
||||
return SuccessResponse(data=json_data)
|
||||
data = {}
|
||||
if settings.CAPTCHA_STATE:
|
||||
hashkey = CaptchaStore.generate_key()
|
||||
id = CaptchaStore.objects.filter(hashkey=hashkey).first().id
|
||||
imgage = captcha_image(request, hashkey)
|
||||
# 将图片转换为base64
|
||||
image_base = base64.b64encode(imgage.content)
|
||||
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):
|
||||
|
@ -57,53 +61,55 @@ class LoginSerializer(TokenObtainPairSerializer):
|
|||
登录的序列化器:
|
||||
重写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:
|
||||
model = Users
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
default_error_messages = {
|
||||
'no_active_account': _('账号/密码不正确')
|
||||
}
|
||||
default_error_messages = {"no_active_account": _("账号/密码错误")}
|
||||
|
||||
def validate(self, attrs):
|
||||
captcha = self.initial_data.get('captcha', None)
|
||||
captcha = self.initial_data.get("captcha", None)
|
||||
if settings.CAPTCHA_STATE:
|
||||
if captcha is None:
|
||||
raise CustomValidationError("验证码不能为空")
|
||||
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)
|
||||
if self.image_code and five_minute_ago > self.image_code.expiration:
|
||||
self.image_code and self.image_code.delete()
|
||||
raise CustomValidationError('验证码过期')
|
||||
raise CustomValidationError("验证码过期")
|
||||
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()
|
||||
else:
|
||||
self.image_code and self.image_code.delete()
|
||||
raise CustomValidationError("图片验证码错误")
|
||||
data = super().validate(attrs)
|
||||
data['name'] = self.user.name
|
||||
data['userId'] = self.user.id
|
||||
data['avatar'] = self.user.avatar
|
||||
request = self.context.get('request')
|
||||
data["name"] = self.user.name
|
||||
data["userId"] = self.user.id
|
||||
data["avatar"] = self.user.avatar
|
||||
request = self.context.get("request")
|
||||
request.user = self.user
|
||||
# 记录登录日志
|
||||
save_login_log(request=request)
|
||||
return {
|
||||
"code": 2000,
|
||||
"msg": "请求成功",
|
||||
"data": data
|
||||
}
|
||||
return {"code": 2000, "msg": "请求成功", "data": data}
|
||||
|
||||
|
||||
class LoginView(TokenObtainPairView):
|
||||
"""
|
||||
登录接口
|
||||
"""
|
||||
|
||||
serializer_class = LoginSerializer
|
||||
permission_classes = []
|
||||
|
||||
|
@ -118,31 +124,22 @@ class LoginTokenSerializer(TokenObtainPairSerializer):
|
|||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
default_error_messages = {
|
||||
'no_active_account': _('账号/密码不正确')
|
||||
}
|
||||
default_error_messages = {"no_active_account": _("账号/密码不正确")}
|
||||
|
||||
def validate(self, attrs):
|
||||
if not getattr(settings, 'LOGIN_NO_CAPTCHA_AUTH', False):
|
||||
return {
|
||||
"code": 4000,
|
||||
"msg": "该接口暂未开通!",
|
||||
"data": None
|
||||
}
|
||||
if not getattr(settings, "LOGIN_NO_CAPTCHA_AUTH", False):
|
||||
return {"code": 4000, "msg": "该接口暂未开通!", "data": None}
|
||||
data = super().validate(attrs)
|
||||
data['name'] = self.user.name
|
||||
data['userId'] = self.user.id
|
||||
return {
|
||||
"code": 2000,
|
||||
"msg": "请求成功",
|
||||
"data": data
|
||||
}
|
||||
data["name"] = self.user.name
|
||||
data["userId"] = self.user.id
|
||||
return {"code": 2000, "msg": "请求成功", "data": data}
|
||||
|
||||
|
||||
class LoginTokenView(TokenObtainPairView):
|
||||
"""
|
||||
登录获取token接口
|
||||
"""
|
||||
|
||||
serializer_class = LoginTokenSerializer
|
||||
permission_classes = []
|
||||
|
||||
|
@ -154,27 +151,31 @@ class LogoutView(APIView):
|
|||
|
||||
class ApiLoginSerializer(CustomModelSerializer):
|
||||
"""接口文档登录-序列化器"""
|
||||
|
||||
username = serializers.CharField()
|
||||
password = serializers.CharField()
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
fields = ['username', 'password']
|
||||
fields = ["username", "password"]
|
||||
|
||||
|
||||
class ApiLogin(APIView):
|
||||
"""接口文档的登录接口"""
|
||||
|
||||
serializer_class = ApiLoginSerializer
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def post(self, request):
|
||||
username = request.data.get('username')
|
||||
password = request.data.get('password')
|
||||
user_obj = auth.authenticate(request, username=username,
|
||||
password=hashlib.md5(password.encode(encoding='UTF-8')).hexdigest())
|
||||
if user_obj:
|
||||
username = request.data.get("username")
|
||||
password = request.data.get("password")
|
||||
if user_obj := auth.authenticate(
|
||||
request,
|
||||
username=username,
|
||||
password=hashlib.md5(password.encode(encoding="UTF-8")).hexdigest(),
|
||||
):
|
||||
login(request, user_obj)
|
||||
return redirect('/')
|
||||
return redirect("/")
|
||||
else:
|
||||
return ErrorResponse(msg="账号/密码错误")
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 用户管理
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
from application import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
|
@ -28,9 +21,9 @@ class UserSerializer(CustomModelSerializer):
|
|||
class Meta:
|
||||
model = Users
|
||||
read_only_fields = ["id"]
|
||||
exclude = ['password']
|
||||
exclude = ["password"]
|
||||
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="账号必须唯一")])
|
||||
password = serializers.CharField(required=False, default=make_password(
|
||||
hashlib.md5('admin123456'.encode(encoding='UTF-8')).hexdigest()))
|
||||
|
||||
username = serializers.CharField(
|
||||
max_length=50,
|
||||
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):
|
||||
data = super().save(**kwargs)
|
||||
data.post.set(self.initial_data.get('post', []))
|
||||
data.post.set(self.initial_data.get("post", []))
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
|
@ -53,7 +55,7 @@ class UserCreateSerializer(CustomModelSerializer):
|
|||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
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)
|
||||
mobile = serializers.CharField(
|
||||
max_length=50,
|
||||
validators=[
|
||||
CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一")
|
||||
],
|
||||
)
|
||||
|
||||
def save(self, **kwargs):
|
||||
data = super().save(**kwargs)
|
||||
data.post.set(self.initial_data.get('post', []))
|
||||
data.post.set(self.initial_data.get("post", []))
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
|
@ -75,7 +88,7 @@ class UserUpdateSerializer(CustomModelSerializer):
|
|||
read_only_fields = ["id"]
|
||||
fields = "__all__"
|
||||
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='')
|
||||
dept__owner = serializers.CharField(source='dept.owner', default='')
|
||||
gender = serializers.CharField(source='get_gender_display', read_only=True)
|
||||
|
||||
last_login = serializers.DateTimeField(
|
||||
format="%Y-%m-%d %H:%M:%S", required=False, 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:
|
||||
model = Users
|
||||
fields = ('username', 'name', 'email', 'mobile', 'gender', 'is_active', 'last_login', 'dept__deptName',
|
||||
'dept__owner')
|
||||
fields = (
|
||||
"username",
|
||||
"name",
|
||||
"email",
|
||||
"mobile",
|
||||
"gender",
|
||||
"is_active",
|
||||
"last_login",
|
||||
"dept__deptName",
|
||||
"dept__owner",
|
||||
)
|
||||
|
||||
|
||||
class UserProfileImportSerializer(CustomModelSerializer):
|
||||
|
||||
def save(self, **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.save()
|
||||
return data
|
||||
|
@ -106,15 +132,22 @@ class UserProfileImportSerializer(CustomModelSerializer):
|
|||
def run_validation(self, data={}):
|
||||
# 把excel 数据进行格式转换
|
||||
if type(data) is dict:
|
||||
data['role'] = str(data['role']).split(',')
|
||||
data['dept_id'] = str(data['dept']).split(',')
|
||||
data['gender'] = {'男': '1', '女': '0', '未知': '2'}.get(data['gender'])
|
||||
data['is_active'] = {'启用': True, '禁用': False}.get(data['is_active'])
|
||||
data["role"] = str(data["role"]).split(",")
|
||||
data["dept_id"] = str(data["dept"]).split(",")
|
||||
data["gender"] = {"男": "1", "女": "0", "未知": "2"}.get(data["gender"])
|
||||
data["is_active"] = {"启用": True, "禁用": False}.get(data["is_active"])
|
||||
return super().run_validation(data)
|
||||
|
||||
class Meta:
|
||||
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):
|
||||
|
@ -126,11 +159,12 @@ class UserViewSet(CustomModelViewSet):
|
|||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
|
||||
queryset = Users.objects.exclude(is_superuser=1).all()
|
||||
serializer_class = UserSerializer
|
||||
create_serializer_class = UserCreateSerializer
|
||||
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 = {
|
||||
# 'name': ['icontains'],
|
||||
# 'username': ['icontains'],
|
||||
|
@ -138,17 +172,35 @@ class UserViewSet(CustomModelViewSet):
|
|||
# 'is_active': ['icontains'],
|
||||
# 'dept': ['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
|
||||
# 导入
|
||||
import_serializer_class = UserProfileImportSerializer
|
||||
import_field_dict = {'username': '登录账号', 'name': '用户名称', 'email': '用户邮箱', 'mobile': '手机号码',
|
||||
'gender': '用户性别(男/女/未知)',
|
||||
'is_active': '帐号状态(启用/禁用)', 'password': '登录密码', 'dept': '部门ID', 'role': '角色ID'}
|
||||
import_field_dict = {
|
||||
"username": "登录账号",
|
||||
"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):
|
||||
"""获取当前用户信息"""
|
||||
user = request.user
|
||||
|
@ -157,25 +209,25 @@ class UserViewSet(CustomModelViewSet):
|
|||
"mobile": user.mobile,
|
||||
"gender": user.gender,
|
||||
"email": user.email,
|
||||
'avatar':user.avatar
|
||||
"avatar": user.avatar,
|
||||
}
|
||||
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):
|
||||
"""修改当前用户信息"""
|
||||
user = request.user
|
||||
Users.objects.filter(id=user.id).update(**request.data)
|
||||
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):
|
||||
"""密码修改"""
|
||||
instance = Users.objects.filter(id=kwargs.get('pk')).first()
|
||||
instance = Users.objects.filter(id=kwargs.get("pk")).first()
|
||||
data = request.data
|
||||
old_pwd = data.get('oldPassword')
|
||||
new_pwd = data.get('newPassword')
|
||||
new_pwd2 = data.get('newPassword2')
|
||||
old_pwd = data.get("oldPassword")
|
||||
new_pwd = data.get("newPassword")
|
||||
new_pwd2 = data.get("newPassword2")
|
||||
if instance:
|
||||
if new_pwd != new_pwd2:
|
||||
return ErrorResponse(msg="两次密码不匹配")
|
||||
|
@ -188,21 +240,32 @@ class UserViewSet(CustomModelViewSet):
|
|||
else:
|
||||
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')
|
||||
@action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
|
||||
def reset_password(self, request, *args, **kwargs):
|
||||
"""重置密码"""
|
||||
instance = Users.objects.filter(id=kwargs.get("pk")).first()
|
||||
if instance:
|
||||
if new_pwd != new_pwd2:
|
||||
return ErrorResponse(msg="两次密码不匹配")
|
||||
else:
|
||||
instance.password = make_password(new_pwd)
|
||||
instance.save()
|
||||
return DetailResponse(data=None, msg="修改成功")
|
||||
instance.set_password(settings.DEFAULT_PASSWORD)
|
||||
instance.save()
|
||||
return DetailResponse(data=None, msg="密码重置成功")
|
||||
else:
|
||||
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="未获取到用户")
|
||||
|
|
|
@ -273,7 +273,7 @@ Vue.prototype.commonEndColumns = function (param = {}) {
|
|||
type: 'table-selector',
|
||||
dict: {
|
||||
cache: true,
|
||||
url: deptPrefix + '?limit=999&status=1',
|
||||
url: deptPrefix,
|
||||
isTree: true,
|
||||
value: 'id', // 数据字典中value字段的属性名
|
||||
label: 'name', // 数据字典中label字段的属性名
|
||||
|
@ -283,7 +283,8 @@ Vue.prototype.commonEndColumns = function (param = {}) {
|
|||
component
|
||||
}) => {
|
||||
return request({
|
||||
url: url
|
||||
url: url,
|
||||
params: { limit: 999, status: 1 }
|
||||
}).then(ret => {
|
||||
return ret.data.data
|
||||
})
|
||||
|
|
|
@ -28,10 +28,21 @@
|
|||
:action="action"
|
||||
:headers="headers"
|
||||
:limit="1"
|
||||
:disabled="fileList.length===1"
|
||||
: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>-->
|
||||
:disabled="fileList.length === 1"
|
||||
: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> -->
|
||||
<i class="el-icon-plus"></i>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
|
@ -159,9 +170,7 @@ export default {
|
|||
},
|
||||
userInforules: {
|
||||
name: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
|
||||
mobile: [
|
||||
{ pattern: /^1[3|4|5|7|8]\d{9}$/, message: '请输入正确手机号' }
|
||||
]
|
||||
mobile: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确手机号' }]
|
||||
},
|
||||
userPasswordInfo: {
|
||||
oldPassword: '',
|
||||
|
|
|
@ -64,7 +64,9 @@ router.beforeEach(async (to, from, next) => {
|
|||
// 处理路由 得到每一级的路由设置
|
||||
store.commit('d2admin/page/init', routes)
|
||||
|
||||
router.addRoutes(routes)
|
||||
// router.addRoutes(routes)
|
||||
routes.forEach(route => router.addRoute(route))
|
||||
|
||||
const menu = handleAsideMenu(ret)
|
||||
const aside = handleAsideMenu(ret.filter(value => value.visible === true))
|
||||
store.commit('d2admin/menu/asideSet', aside) // 设置侧边栏菜单
|
||||
|
|
|
@ -97,13 +97,13 @@ export const crudOptions = (vm) => {
|
|||
type: 'cascader',
|
||||
dict: {
|
||||
cache: false,
|
||||
url: deptPrefix + '?limit=999&status=1',
|
||||
url: deptPrefix,
|
||||
isTree: true,
|
||||
value: 'id', // 数据字典中value字段的属性名
|
||||
label: 'name', // 数据字典中label字段的属性名
|
||||
children: 'children', // 数据字典中children字段的属性名
|
||||
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 })
|
||||
return [{ id: '0', name: '根节点', children: data }]
|
||||
})
|
||||
|
|
|
@ -98,13 +98,13 @@ export const crudOptions = (vm) => {
|
|||
type: 'cascader',
|
||||
dict: {
|
||||
cache: false,
|
||||
url: dictionaryPrefix + '?status=1&limit=999',
|
||||
url: dictionaryPrefix,
|
||||
isTree: true,
|
||||
value: 'id', // 数据字典中value字段的属性名
|
||||
label: 'label', // 数据字典中label字段的属性名
|
||||
children: 'children', // 数据字典中children字段的属性名
|
||||
getData: (url, dict) => { // 配置此参数会覆盖全局的getRemoteDictFunc
|
||||
return request({ url: url }).then(ret => {
|
||||
return request({ url: url, params: { limit: 999, status: 1 } }).then(ret => {
|
||||
return [{ id: null, label: '根节点', children: XEUtils.toArrayTree(ret.data.data, { parentKey: 'parent', strict: true }) }]
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
/*
|
||||
* @创建文件时间: 2021-06-02 10:33:33
|
||||
* @Auther: 猿小天
|
||||
* @最后修改人: 猿小天
|
||||
* @最后修改时间: 2021-08-12 22:53:38
|
||||
* 联系Qq:1638245306
|
||||
* @文件介绍: 登录的接口
|
||||
*/
|
||||
|
||||
import { request } from '@/api/service'
|
||||
|
||||
export function SYS_USER_LOGIN (data) {
|
||||
|
@ -31,3 +22,10 @@ export function getCaptcha () {
|
|||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getCaptchaStatus () {
|
||||
return request({
|
||||
url: 'api/captcha/status/',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,26 +5,13 @@
|
|||
<li v-for="n in 10" :key="n"></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="page-login--layer page-login--layer-time"
|
||||
flex="main:center cross:center"
|
||||
>
|
||||
{{ time }}
|
||||
</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" flex="dir:top main:justify cross:stretch box:justify">
|
||||
<div class="page-login--content-header">
|
||||
<p class="page-login--content-header-motto">
|
||||
时间是一切财富中最宝贵的财富
|
||||
</p>
|
||||
<p class="page-login--content-header-motto">时间是一切财富中最宝贵的财富</p>
|
||||
</div>
|
||||
<div
|
||||
class="page-login--content-main"
|
||||
flex="dir:top main:center cross:center"
|
||||
>
|
||||
<div class="page-login--content-main" flex="dir:top main:center cross:center">
|
||||
<!-- logo -->
|
||||
<img class="page-login--logo" src="./image/dvadmin.png" />
|
||||
<!-- form -->
|
||||
|
@ -38,11 +25,7 @@
|
|||
size="default"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
type="text"
|
||||
v-model="formLogin.username"
|
||||
placeholder="用户名"
|
||||
>
|
||||
<el-input type="text" v-model="formLogin.username" placeholder="用户名">
|
||||
<i slot="prepend" class="fa fa-user-circle-o"></i>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
@ -56,42 +39,27 @@
|
|||
<i slot="prepend" class="fa fa-keyboard-o"></i>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="captcha">
|
||||
<el-input
|
||||
type="text"
|
||||
v-model="formLogin.captcha"
|
||||
placeholder="验证码"
|
||||
>
|
||||
<el-form-item prop="captcha" v-if="captchaVisible">
|
||||
<el-input type="text" v-model="formLogin.captcha" placeholder="验证码">
|
||||
<template slot="append">
|
||||
<img
|
||||
class="login-code"
|
||||
:src="image_base"
|
||||
@click="getCaptcha"
|
||||
/>
|
||||
<img class="login-code" :src="image_base" @click="getCaptcha" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-button
|
||||
size="default"
|
||||
@click="submit"
|
||||
type="primary"
|
||||
class="button-login"
|
||||
>
|
||||
登录
|
||||
</el-button>
|
||||
<el-button size="default" @click="submit" type="primary" class="button-login">登录</el-button>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<!-- <p class="page-login--options" flex="main:justify cross:center">
|
||||
<span><d2-icon name="question-circle" /> 忘记密码</span>
|
||||
<span>注册用户</span>
|
||||
</p> -->
|
||||
</p>-->
|
||||
<!-- quick login -->
|
||||
<el-button
|
||||
class="page-login--quick"
|
||||
size="default"
|
||||
type="info"
|
||||
@click="dialogVisible = true"
|
||||
v-if="$env === 'development'"
|
||||
@click="dialogVisible = true"
|
||||
>
|
||||
快速选择用户(限dev环境)
|
||||
</el-button>
|
||||
|
@ -109,12 +77,9 @@
|
|||
</p>
|
||||
<p class="page-login--content-footer-copyright">
|
||||
Copyright
|
||||
<d2-icon name="copyright" />
|
||||
Copyright © 2018-2021 pro.django-vue-admin.com All Rights Reserved.
|
||||
<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>
|
||||
<a href="https://beian.miit.gov.cn" target="_blank">晋ICP备18005113号-3</a>
|
||||
</p>
|
||||
<p class="page-login--content-footer-options">
|
||||
<a href="#">帮助</a>
|
||||
|
@ -150,6 +115,7 @@ export default {
|
|||
time: dayjs().format('HH:mm:ss'),
|
||||
// 快速选择用户
|
||||
dialogVisible: false,
|
||||
captchaVisible: false,
|
||||
users: [
|
||||
{
|
||||
name: '超管',
|
||||
|
@ -196,6 +162,10 @@ export default {
|
|||
image_base: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('d2admin/db/databaseClear')
|
||||
this.getCaptcha()
|
||||
},
|
||||
mounted () {
|
||||
this.timeInterval = setInterval(() => {
|
||||
this.refreshTime()
|
||||
|
@ -216,8 +186,10 @@ export default {
|
|||
handleUserBtnClick (user) {
|
||||
this.formLogin.username = user.username
|
||||
this.formLogin.password = user.password
|
||||
// this.submit()
|
||||
this.dialogVisible = false
|
||||
this.dialogVisible = !this.dialogVisible
|
||||
if (!this.captchaVisible) {
|
||||
this.submit()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 提交表单
|
||||
|
@ -253,16 +225,19 @@ export default {
|
|||
* 获取验证码
|
||||
*/
|
||||
getCaptcha () {
|
||||
api.getCaptcha().then((ret) => {
|
||||
this.formLogin.captcha = null
|
||||
this.captchaKey = ret.data.data.key
|
||||
this.image_base = ret.data.data.image_base
|
||||
api.getCaptchaStatus().then((ret) => {
|
||||
this.captchaVisible = ret.data.status
|
||||
if (this.captchaVisible) {
|
||||
api.getCaptcha().then((ret) => {
|
||||
this.formLogin.captcha = null
|
||||
if (ret.data) {
|
||||
this.captchaKey = ret.data.key
|
||||
this.image_base = ret.data.image_base
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('d2admin/db/databaseClear')
|
||||
this.getCaptcha()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -393,7 +368,6 @@ export default {
|
|||
.page-login--content-footer-copyright {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
text-align: center;
|
||||
|
@ -405,6 +379,7 @@ export default {
|
|||
.page-login--content-footer-options {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
text-align: center;
|
||||
|
|
|
@ -27,7 +27,8 @@ export const crudOptions = (vm) => {
|
|||
options: {
|
||||
rowId: 'id',
|
||||
height: '100%', // 表格高度100%, 使用toolbar必须设置
|
||||
highlightCurrentRow: false
|
||||
highlightCurrentRow: false,
|
||||
defaultExpandAll: true
|
||||
},
|
||||
rowHandle: {
|
||||
view: {
|
||||
|
@ -129,14 +130,14 @@ export const crudOptions = (vm) => {
|
|||
},
|
||||
type: 'cascader',
|
||||
dict: {
|
||||
url: menuPrefix + '?limit=999&status=1&is_catalog=1',
|
||||
url: menuPrefix,
|
||||
cache: false,
|
||||
isTree: true,
|
||||
value: 'id', // 数据字典中value字段的属性名
|
||||
label: 'name', // 数据字典中label字段的属性名
|
||||
children: 'children', // 数据字典中children字段的属性名
|
||||
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 result = XEUtils.toArrayTree(responseData, { parentKey: 'parent', strict: true })
|
||||
return [{ id: null, name: '根节点', children: result }]
|
||||
|
@ -457,6 +458,8 @@ export const crudOptions = (vm) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
].concat(vm.commonEndColumns({ update_datetime: { showTable: false } }))
|
||||
].concat(vm.commonEndColumns({
|
||||
update_datetime: { showTable: false }
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,13 @@ export const crudOptions = (vm) => {
|
|||
compact: true
|
||||
},
|
||||
options: {
|
||||
height: '100%'
|
||||
height: '100%',
|
||||
tableType: 'vxe-table',
|
||||
rowKey: true // 必须设置,true or false
|
||||
},
|
||||
rowHandle: {
|
||||
width: 320,
|
||||
fixed: 'right',
|
||||
width: 180,
|
||||
view: {
|
||||
thin: true,
|
||||
text: '',
|
||||
|
@ -36,18 +38,18 @@ export const crudOptions = (vm) => {
|
|||
},
|
||||
custom: [
|
||||
{
|
||||
thin: true,
|
||||
text: '',
|
||||
size: 'small',
|
||||
type: 'warning',
|
||||
icon: 'el-icon-key',
|
||||
show () {
|
||||
return vm.hasPermissions('ResetPwd')
|
||||
return vm.hasPermissions('ResetPassword')
|
||||
},
|
||||
emit: 'resetPwd'
|
||||
disabled () {
|
||||
return !vm.hasPermissions('ResetPassword')
|
||||
},
|
||||
text: '重置密码',
|
||||
type: 'warning',
|
||||
size: 'small',
|
||||
emit: 'resetPassword'
|
||||
}
|
||||
]
|
||||
|
||||
},
|
||||
viewOptions: {
|
||||
componentType: 'form'
|
||||
|
@ -58,7 +60,7 @@ export const crudOptions = (vm) => {
|
|||
indexRow: { // 或者直接传true,不显示title,不居中
|
||||
title: '序号',
|
||||
align: 'center',
|
||||
width: 70
|
||||
width: 60
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
|
@ -82,7 +84,6 @@ export const crudOptions = (vm) => {
|
|||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
width: 90,
|
||||
disabled: true,
|
||||
form: {
|
||||
disabled: true
|
||||
|
@ -94,7 +95,7 @@ export const crudOptions = (vm) => {
|
|||
search: {
|
||||
disabled: false
|
||||
},
|
||||
width: 140,
|
||||
minWidth: 100,
|
||||
type: 'input',
|
||||
form: {
|
||||
rules: [ // 表单校验规则
|
||||
|
@ -117,6 +118,7 @@ export const crudOptions = (vm) => {
|
|||
{
|
||||
title: '姓名',
|
||||
key: 'name',
|
||||
minWidth: 90,
|
||||
search: {
|
||||
disabled: false
|
||||
},
|
||||
|
@ -165,9 +167,9 @@ export const crudOptions = (vm) => {
|
|||
},
|
||||
component: {
|
||||
span: 12,
|
||||
pagination: true,
|
||||
props: { multiple: false },
|
||||
elProps: {
|
||||
pagination: true,
|
||||
columns: [
|
||||
{
|
||||
field: 'name',
|
||||
|
@ -197,7 +199,7 @@ export const crudOptions = (vm) => {
|
|||
form: {
|
||||
rules: [
|
||||
{ max: 20, message: '请输入正确的手机号码', trigger: 'blur' },
|
||||
{ pattern: /^1[3|4|5|7|8]\d{9}$/, message: '请输入正确的手机号码' }
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
|
||||
],
|
||||
itemProps: {
|
||||
class: { yxtInput: true }
|
||||
|
@ -275,7 +277,7 @@ export const crudOptions = (vm) => {
|
|||
title: '头像',
|
||||
key: 'avatar',
|
||||
type: 'avatar-cropper',
|
||||
width: 100,
|
||||
width: 60,
|
||||
align: 'left',
|
||||
form: {
|
||||
component: {
|
||||
|
@ -322,9 +324,9 @@ export const crudOptions = (vm) => {
|
|||
},
|
||||
component: {
|
||||
span: 12,
|
||||
pagination: true,
|
||||
props: { multiple: true },
|
||||
elProps: {
|
||||
pagination: true,
|
||||
columns: [
|
||||
{
|
||||
field: 'name',
|
||||
|
@ -343,6 +345,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 }
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
ref="d2Crud"
|
||||
v-bind="_crudProps"
|
||||
v-on="_crudListeners"
|
||||
crud.options.tableType="vxe-table"
|
||||
@resetPwd="resetPwd"
|
||||
@resetPassword="resetPassword"
|
||||
>
|
||||
<div slot="header">
|
||||
<crud-search
|
||||
|
@ -19,8 +18,9 @@
|
|||
v-permission="'Create'"
|
||||
type="primary"
|
||||
@click="addRow"
|
||||
><i class="el-icon-plus" /> 新增</el-button
|
||||
>
|
||||
<i class="el-icon-plus" /> 新增
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<crud-toolbar
|
||||
:search.sync="crud.searchOptions.show"
|
||||
|
@ -31,7 +31,7 @@
|
|||
/>
|
||||
</div>
|
||||
</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-item label="密码" prop="pwd">
|
||||
<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 type="primary" @click="resetPwdSubmit">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</el-dialog> -->
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
|
@ -57,39 +57,39 @@ export default {
|
|||
name: 'user',
|
||||
mixins: [d2CrudPlus.crud],
|
||||
data () {
|
||||
var validatePass = (rule, value, callback) => {
|
||||
const pwdRegex = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z]).{8,30}')
|
||||
if (value === '') {
|
||||
callback(new Error('请输入密码'))
|
||||
} else if (!pwdRegex.test(value)) {
|
||||
callback(new Error('您的密码复杂度太低(密码中必须包含字母、数字)'))
|
||||
} else {
|
||||
if (this.resetPwdForm.pwd2 !== '') {
|
||||
this.$refs.resetPwdForm.validateField('pwd2')
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
var validatePass2 = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请再次输入密码'))
|
||||
} else if (value !== this.resetPwdForm.pwd) {
|
||||
callback(new Error('两次输入密码不一致!'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
// var validatePass = (rule, value, callback) => {
|
||||
// const pwdRegex = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z]).{8,30}')
|
||||
// if (value === '') {
|
||||
// callback(new Error('请输入密码'))
|
||||
// } else if (!pwdRegex.test(value)) {
|
||||
// callback(new Error('您的密码复杂度太低(密码中必须包含字母、数字)'))
|
||||
// } else {
|
||||
// if (this.resetPwdForm.pwd2 !== '') {
|
||||
// this.$refs.resetPwdForm.validateField('pwd2')
|
||||
// }
|
||||
// callback()
|
||||
// }
|
||||
// }
|
||||
// var validatePass2 = (rule, value, callback) => {
|
||||
// if (value === '') {
|
||||
// callback(new Error('请再次输入密码'))
|
||||
// } else if (value !== this.resetPwdForm.pwd) {
|
||||
// callback(new Error('两次输入密码不一致!'))
|
||||
// } else {
|
||||
// callback()
|
||||
// }
|
||||
// }
|
||||
return {
|
||||
dialogFormVisible: false,
|
||||
resetPwdForm: {
|
||||
id: null,
|
||||
pwd: null,
|
||||
pwd2: null
|
||||
},
|
||||
passwordRules: {
|
||||
pwd: [{ required: true, message: '必填项' }, { validator: validatePass, trigger: 'blur' }],
|
||||
pwd2: [{ required: true, message: '必填项' }, { validator: validatePass2, trigger: 'blur' }]
|
||||
}
|
||||
// dialogFormVisible: false,
|
||||
// resetPwdForm: {
|
||||
// id: null,
|
||||
// pwd: null,
|
||||
// pwd2: null
|
||||
// },
|
||||
// passwordRules: {
|
||||
// pwd: [{ required: true, message: '必填项' }, { validator: validatePass, trigger: 'blur' }],
|
||||
// pwd2: [{ required: true, message: '必填项' }, { validator: validatePass2, trigger: 'blur' }]
|
||||
// }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -109,35 +109,40 @@ export default {
|
|||
delRequest (row) {
|
||||
return api.DelObj(row.id)
|
||||
},
|
||||
// 重置密码弹框
|
||||
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('表单校验失败,请检查')
|
||||
}
|
||||
resetPassword (scope) {
|
||||
api.ResetPwd(scope.row).then((res) => {
|
||||
this.$message.success('密码重置成功')
|
||||
})
|
||||
}
|
||||
// // 重置密码弹框
|
||||
// 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>
|
||||
|
|
Loading…
Reference in New Issue