!49 v2.0.1 版本发布

Merge pull request !49 from dvadmin/v2.x
pull/50/MERGE v2.0.1
dvadmin 2022-04-19 16:49:59 +00:00 committed by Gitee
commit 89f156cb90
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
55 changed files with 1022 additions and 285 deletions

View File

@ -31,9 +31,9 @@
👩‍👧‍👦演示地址:[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com) 👩‍👧‍👦演示地址:[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com)
账号superadmin - 账号superadmin
密码superadmin123456 - 密码:admin123456
👩‍👦‍👦文档地址:[https://django-vue-admin.com](https://django-vue-admin.com) 👩‍👦‍👦文档地址:[https://django-vue-admin.com](https://django-vue-admin.com)
@ -130,7 +130,7 @@ npm run dev
8. 启动项目 8. 启动项目
python3 manage.py runserver 0.0.0.0:8000 python3 manage.py runserver 0.0.0.0:8000
或使用 daphne : 或使用 daphne :
daphne -b 0.0.0.0 -8000 application.asgi:application daphne -b 0.0.0.0 -p 8000 application.asgi:application
~~~ ~~~
### 访问项目 ### 访问项目

View File

@ -28,7 +28,10 @@ from conf.env import *
# 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'
sys.path.insert(0, os.path.join(BASE_DIR, 'plugins')) # 初始化plugins插件路径到环境变量中
PLUGINS_PATH = os.path.join(BASE_DIR, 'plugins')
[sys.path.insert(0, os.path.join(PLUGINS_PATH, ele)) for ele in os.listdir(PLUGINS_PATH) if
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)

View File

@ -5,19 +5,24 @@ from application.settings import BASE_DIR
# ================================================= # # ================================================= #
# ************** 数据库 配置 ************** # # ************** 数据库 配置 ************** #
# ================================================= # # ================================================= #
#
# 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库 # 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库
# 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时改为此配置
# DATABASE_ENGINE = "django.db.backends.mysql"
# DATABASE_NAME = 'django-vue-admin' # mysql 时使用
# 数据库地址 改为自己数据库地址 # 数据库地址 改为自己数据库地址
DATABASE_HOST = "127.0.0.1" DATABASE_HOST = "127.0.0.1"
# 数据库端口 # # 数据库端口
DATABASE_PORT = 3306 DATABASE_PORT = 3306
# 数据库用户名 # # 数据库用户名
DATABASE_USER = "root" DATABASE_USER = "root"
# 数据库密码 # # 数据库密码
DATABASE_PASSWORD = "123456" DATABASE_PASSWORD = "123456"
# ================================================= # # ================================================= #
# ************** redis配置无redis 可不进行配置 ************** # # ************** redis配置无redis 可不进行配置 ************** #
# ================================================= # # ================================================= #
@ -30,3 +35,4 @@ DATABASE_PASSWORD = "123456"
DEBUG = True # 线上环境请设置为True DEBUG = True # 线上环境请设置为True
ALLOWED_HOSTS = ["*"] ALLOWED_HOSTS = ["*"]
LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消 LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址)

View File

@ -163,6 +163,17 @@ class Initialize(CoreInitialize):
"name": "导出", "name": "导出",
"value": "Export", "value": "Export",
"creator_id": 1 "creator_id": 1
},
{
"id": 9,
"description": None,
"modifier": "1",
"dept_belong_id": 1,
"update_datetime": datetime.datetime.now(),
"create_datetime": datetime.datetime.now(),
"name": "重置密码",
"value": "ResetPwd",
"creator_id": 1
} }
] ]
self.save(Button, self.button_data, "权限表标识") self.save(Button, self.button_data, "权限表标识")
@ -507,6 +518,27 @@ class Initialize(CoreInitialize):
"creator_id": 1, "creator_id": 1,
"parent_id": 15, "parent_id": 15,
"is_catalog": 0 "is_catalog": 0
},
{
"id": 20,
"description": "",
"modifier": "1",
"dept_belong_id": 1,
"update_datetime": datetime.datetime.now(),
"create_datetime": datetime.datetime.now(),
"icon": "file-text",
"name": "登录日志",
"sort": 1,
"is_link": 0,
"web_path": "/loginLog",
"component": "system/log/loginLog/index",
"component_name": "loginLog",
"status": 1,
"cache": 0,
"visible": 1,
"creator_id": 1,
"parent_id": 15,
"is_catalog": 0
} }
] ]
self.save(Menu, self.menu_data, "菜单表") self.save(Menu, self.menu_data, "菜单表")
@ -1243,6 +1275,47 @@ class Initialize(CoreInitialize):
"method": 1, "method": 1,
"creator_id": 1, "creator_id": 1,
"menu_id": 13 "menu_id": 13
},
{
"id": 53,
"description": None,
"modifier": "1",
"dept_belong_id": 1,
"update_datetime": datetime.datetime.now(),
"create_datetime": datetime.datetime.now(),
"name": "重置密码",
"value": "ResetPwd",
"api": "/api/system/user/reset_password/{id}/",
"method": 2,
"creator_id": 1,
"menu_id": 3
},{
"id": 54,
"description": None,
"modifier": "1",
"dept_belong_id": "1",
"update_datetime": datetime.datetime.now(),
"create_datetime": datetime.datetime.now(),
"name": "查询",
"value": "Search",
"api": "/api/system/login_log/",
"method": 0,
"creator_id": 1,
"menu_id": 20
},
{
"id": 55,
"description": None,
"modifier": "1",
"dept_belong_id": "1",
"update_datetime": datetime.datetime.now(),
"create_datetime": datetime.datetime.now(),
"name": "详情",
"value": "Retrieve",
"api": "/api/system/login_log/{id}/",
"method": 0,
"creator_id": 1,
"menu_id": 20
} }
] ]
self.save(MenuButton, self.menu_button_data, "菜单按钮表") self.save(MenuButton, self.menu_button_data, "菜单按钮表")

View File

@ -206,7 +206,7 @@ class OperationLog(CoreModel):
def media_file_name(instance, filename): def media_file_name(instance, filename):
h = instance.md5sum h = instance.md5sum
basename, ext = os.path.splitext(filename) basename, ext = os.path.splitext(filename)
return os.path.join('media/files', h[0:1], h[1:2], h + ext.lower()) return os.path.join('files', h[0:1], h[1:2], h + ext.lower())
class FileList(CoreModel): class FileList(CoreModel):
@ -306,3 +306,32 @@ class SystemConfig(CoreModel):
def __str__(self): def __str__(self):
return f"{self.title}" return f"{self.title}"
class LoginLog(CoreModel):
LOGIN_TYPE_CHOICES = (
(1, '普通登录'),
)
username = models.CharField(max_length=32, verbose_name="登录用户名", null=True, blank=True, help_text="登录用户名")
ip = models.CharField(max_length=32, verbose_name="登录ip", null=True, blank=True, help_text="登录ip")
agent = models.TextField(verbose_name="agent信息", null=True, blank=True, help_text="agent信息")
browser = models.CharField(max_length=200, verbose_name="浏览器名", null=True, blank=True, help_text="浏览器名")
os = models.CharField(max_length=200, verbose_name="操作系统", null=True, blank=True, help_text="操作系统")
continent = models.CharField(max_length=50, verbose_name="", null=True, blank=True, help_text="")
country = models.CharField(max_length=50, verbose_name="国家", null=True, blank=True, help_text="国家")
province = models.CharField(max_length=50, verbose_name="省份", null=True, blank=True, help_text="省份")
city = models.CharField(max_length=50, verbose_name="城市", null=True, blank=True, help_text="城市")
district = models.CharField(max_length=50, verbose_name="县区", null=True, blank=True, help_text="县区")
isp = models.CharField(max_length=50, verbose_name="运营商", null=True, blank=True, help_text="运营商")
area_code = models.CharField(max_length=50, verbose_name="区域代码", null=True, blank=True, help_text="区域代码")
country_english = models.CharField(max_length=50, verbose_name="英文全称", null=True, blank=True, help_text="英文全称")
country_code = models.CharField(max_length=50, verbose_name="简称", null=True, blank=True, help_text="简称")
longitude = models.CharField(max_length=50, verbose_name="经度", null=True, blank=True, help_text="经度")
latitude = models.CharField(max_length=50, verbose_name="纬度", null=True, blank=True, help_text="纬度")
login_type = models.IntegerField(default=1, choices=LOGIN_TYPE_CHOICES, verbose_name="登录类型", help_text="登录类型")
class Meta:
db_table = table_prefix + 'system_login_log'
verbose_name = '登录日志'
verbose_name_plural = verbose_name
ordering = ('-create_datetime',)

View File

@ -15,6 +15,7 @@ from dvadmin.system.views.button import ButtonViewSet
from dvadmin.system.views.dept import DeptViewSet from dvadmin.system.views.dept import DeptViewSet
from dvadmin.system.views.dictionary import DictionaryViewSet from dvadmin.system.views.dictionary import DictionaryViewSet
from dvadmin.system.views.file_list import FileViewSet from dvadmin.system.views.file_list import FileViewSet
from dvadmin.system.views.login_log import LoginLogViewSet
from dvadmin.system.views.menu import MenuViewSet from dvadmin.system.views.menu import MenuViewSet
from dvadmin.system.views.menu_button import MenuButtonViewSet from dvadmin.system.views.menu_button import MenuButtonViewSet
from dvadmin.system.views.operation_log import OperationLogViewSet from dvadmin.system.views.operation_log import OperationLogViewSet
@ -41,11 +42,14 @@ urlpatterns = [
path('menu/web_router/', MenuViewSet.as_view({'get': 'web_router'})), path('menu/web_router/', MenuViewSet.as_view({'get': 'web_router'})),
path('user/user_info/', UserViewSet.as_view({'get': 'user_info', 'put': 'update_user_info'})), path('user/user_info/', UserViewSet.as_view({'get': 'user_info', 'put': 'update_user_info'})),
path('user/change_password/<int:pk>/', UserViewSet.as_view({'put': 'change_password'})), path('user/change_password/<int:pk>/', UserViewSet.as_view({'put': 'change_password'})),
path('user/reset_password/<int:pk>/', UserViewSet.as_view({'put': 'reset_password'})),
path('user/export/', UserViewSet.as_view({'post': 'export_data', })), path('user/export/', UserViewSet.as_view({'post': 'export_data', })),
path('user/import/', UserViewSet.as_view({'get': 'import_data', 'post': 'import_data'})), path('user/import/', UserViewSet.as_view({'get': 'import_data', 'post': 'import_data'})),
path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})), path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})),
path('system_config/get_association_table/', SystemConfigViewSet.as_view({'get': 'get_association_table'})), path('system_config/get_association_table/', SystemConfigViewSet.as_view({'get': 'get_association_table'})),
path('system_config/get_table_data/<int:pk>/', SystemConfigViewSet.as_view({'get': 'get_table_data'})), path('system_config/get_table_data/<int:pk>/', SystemConfigViewSet.as_view({'get': 'get_table_data'})),
path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})), path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})),
path('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})),
] ]
urlpatterns += system_url.urls urlpatterns += system_url.urls

View File

@ -6,6 +6,7 @@
@Created on: 2021/6/3 003 0:30 @Created on: 2021/6/3 003 0:30
@Remark: 角色管理 @Remark: 角色管理
""" """
from rest_framework import serializers
from dvadmin.system.models import Dept from dvadmin.system.models import Dept
from dvadmin.utils.json_response import SuccessResponse from dvadmin.utils.json_response import SuccessResponse
@ -17,7 +18,7 @@ class DeptSerializer(CustomModelSerializer):
""" """
部门-序列化器 部门-序列化器
""" """
parent_name = serializers.CharField(read_only=True,source='parent.name')
class Meta: class Meta:
model = Dept model = Dept
fields = "__all__" fields = "__all__"
@ -55,11 +56,11 @@ class DeptViewSet(CustomModelViewSet):
update_serializer_class = DeptCreateUpdateSerializer update_serializer_class = DeptCreateUpdateSerializer
# extra_filter_backends = [] # extra_filter_backends = []
def list(self, request, *args, **kwargs): # def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()) # queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset) # page = self.paginate_queryset(queryset)
if page is not None: # if page is not None:
serializer = self.get_serializer(page, many=True, request=request) # serializer = self.get_serializer(page, many=True, request=request)
return self.get_paginated_response(serializer.data) # return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True, request=request) # serializer = self.get_serializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功") # return SuccessResponse(data=serializer.data, msg="获取成功")

View File

@ -17,7 +17,7 @@ class FileSerializer(CustomModelSerializer):
url = serializers.SerializerMethodField(read_only=True) url = serializers.SerializerMethodField(read_only=True)
def get_url(self, instance): def get_url(self, instance):
return str(instance.url) return 'media/'+str(instance.url)
class Meta: class Meta:
model = FileList model = FileList
@ -41,3 +41,4 @@ class FileViewSet(CustomModelViewSet):
queryset = FileList.objects.all() queryset = FileList.objects.all()
serializer_class = FileSerializer serializer_class = FileSerializer
filter_fields = ['name', ] filter_fields = ['name', ]
permission_classes = []

View File

@ -25,6 +25,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 SuccessResponse, ErrorResponse, DetailResponse
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
@ -56,7 +57,7 @@ class LoginSerializer(TokenObtainPairSerializer):
登录的序列化器: 登录的序列化器:
重写djangorestframework-simplejwt的序列化器 重写djangorestframework-simplejwt的序列化器
""" """
captcha = serializers.CharField(max_length=6) captcha = serializers.CharField(max_length=6, required=False, allow_null=True)
class Meta: class Meta:
model = Users model = Users
@ -67,7 +68,11 @@ class LoginSerializer(TokenObtainPairSerializer):
'no_active_account': _('账号/密码不正确') 'no_active_account': _('账号/密码不正确')
} }
def validate_captcha(self, captcha): def validate(self, attrs):
captcha = self.initial_data.get('captcha', None)
if settings.CAPTCHA_STATE:
if captcha is None:
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)
@ -80,11 +85,14 @@ class LoginSerializer(TokenObtainPairSerializer):
else: else:
self.image_code and self.image_code.delete() self.image_code and self.image_code.delete()
raise CustomValidationError("图片验证码错误") raise CustomValidationError("图片验证码错误")
def validate(self, attrs):
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
request = self.context.get('request')
request.user = self.user
# 记录登录日志
save_login_log(request=request)
return { return {
"code": 2000, "code": 2000,
"msg": "请求成功", "msg": "请求成功",

View File

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

View File

@ -11,6 +11,7 @@ import hashlib
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
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Users from dvadmin.system.models import Users
from dvadmin.utils.json_response import ErrorResponse, DetailResponse from dvadmin.utils.json_response import ErrorResponse, DetailResponse
@ -147,7 +148,7 @@ class UserViewSet(CustomModelViewSet):
'gender': '用户性别(男/女/未知)', 'gender': '用户性别(男/女/未知)',
'is_active': '帐号状态(启用/禁用)', 'password': '登录密码', 'dept': '部门ID', 'role': '角色ID'} 'is_active': '帐号状态(启用/禁用)', 'password': '登录密码', 'dept': '部门ID', 'role': '角色ID'}
@action(methods=['GET'], detail=True, permission_classes=[]) @action(methods=['GET'], detail=True, permission_classes=[IsAuthenticated])
def user_info(self, request): def user_info(self, request):
"""获取当前用户信息""" """获取当前用户信息"""
user = request.user user = request.user
@ -155,18 +156,19 @@ class UserViewSet(CustomModelViewSet):
"name": user.name, "name": user.name,
"mobile": user.mobile, "mobile": user.mobile,
"gender": user.gender, "gender": user.gender,
"email": user.email "email": user.email,
'avatar':user.avatar
} }
return DetailResponse(data=result, msg="获取成功") return DetailResponse(data=result, msg="获取成功")
@action(methods=['PUT'], detail=True, permission_classes=[]) @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=[]) @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()
@ -185,3 +187,22 @@ class UserViewSet(CustomModelViewSet):
return ErrorResponse(msg="旧密码不正确") return ErrorResponse(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

@ -36,7 +36,7 @@ def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
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('parentId') == 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))
@ -94,10 +94,15 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
return queryset.filter(dept_belong_id=user_dept_id) return queryset.filter(dept_belong_id=user_dept_id)
# 3. 根据所有角色 获取所有权限范围 # 3. 根据所有角色 获取所有权限范围
# (0, "仅本人数据权限"),
# (1, "本部门及以下数据权限"),
# (2, "本部门数据权限"),
# (3, "全部数据权限"),
# (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:
# 3.1 判断用户是否为超级管理员角色/如果有1(所有数据) 则返回所有数据 # 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
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'))

View File

@ -3,12 +3,16 @@ Request工具类
""" """
import json import json
import requests
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.urls.resolvers import ResolverMatch from django.urls.resolvers import ResolverMatch
from rest_framework_simplejwt.authentication import JWTAuthentication from rest_framework_simplejwt.authentication import JWTAuthentication
from user_agents import parse from user_agents import parse
from dvadmin.system.models import LoginLog
def get_request_user(request): def get_request_user(request):
""" """
@ -163,3 +167,50 @@ def get_verbose_name(queryset=None, view=None, model=None):
except Exception as e: except Exception as e:
pass pass
return model if model else "" return model if model else ""
def get_ip_analysis(ip):
"""
获取ip详细概略
:param ip: ip地址
:return:
"""
data = {
"continent": "",
"country": "",
"province": "",
"city": "",
"district": "",
"isp": "",
"area_code": "",
"country_english": "",
"country_code": "",
"longitude": "",
"latitude": ""
}
if ip != 'unknown' and ip:
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}))
if res.status_code == 200:
res_data = res.json()
if res_data.get('code') == 0:
data = res_data.get('data')
return data
return data
def save_login_log(request):
"""
保存登录日志
:return:
"""
ip = get_request_ip(request=request)
analysis_data = get_ip_analysis(ip)
analysis_data['username'] = request.user.username
analysis_data['ip'] = ip
analysis_data['agent'] = str(parse(request.META['HTTP_USER_AGENT']))
analysis_data['browser'] = get_browser(request)
analysis_data['os'] = get_os(request)
analysis_data['creator_id'] = request.user.id
analysis_data['dept_belong_id'] = getattr(request.user, 'dept_id', '')
LoginLog.objects.create(**analysis_data)

View File

@ -68,7 +68,7 @@ class CustomModelSerializer(DynamicFieldsMixin,ModelSerializer):
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):
self.instance.modifier = self.get_request_username() 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):

View File

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

View File

@ -1,22 +1,22 @@
module.exports = [ module.exports = [
{ name: 'vue', library: 'Vue', js: 'https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js', css: '' }, // { name: 'vue', library: 'Vue', js: 'https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js', css: '' },
{ name: 'vue-i18n', library: 'VueI18n', js: 'https://cdn.jsdelivr.net/npm/vue-i18n@8.15.1/dist/vue-i18n.min.js', css: '' }, // { name: 'vue-i18n', library: 'VueI18n', js: 'https://cdn.jsdelivr.net/npm/vue-i18n@8.15.1/dist/vue-i18n.min.js', css: '' },
{ name: 'vue-router', library: 'VueRouter', js: 'https://cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.min.js', css: '' }, // { name: 'vue-router', library: 'VueRouter', js: 'https://cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.min.js', css: '' },
{ name: 'vuex', library: 'Vuex', js: 'https://cdn.jsdelivr.net/npm/vuex@3.1.2/dist/vuex.min.js', css: '' }, // { name: 'vuex', library: 'Vuex', js: 'https://cdn.jsdelivr.net/npm/vuex@3.1.2/dist/vuex.min.js', css: '' },
{ name: 'axios', library: 'axios', js: 'https://cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js', css: '' }, // { name: 'axios', library: 'axios', js: 'https://cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js', css: '' },
{ name: 'better-scroll', library: 'BScroll', js: 'https://cdn.jsdelivr.net/npm/better-scroll@1.15.2/dist/bscroll.min.js', css: '' }, // { name: 'better-scroll', library: 'BScroll', js: 'https://cdn.jsdelivr.net/npm/better-scroll@1.15.2/dist/bscroll.min.js', css: '' },
{ name: 'axios-mock-adapter', library: 'AxiosMockAdapter', js: 'https://cdn.jsdelivr.net/npm/axios-mock-adapter@1.18.1/dist/axios-mock-adapter.min.js', css: '' }, // { name: 'axios-mock-adapter', library: 'AxiosMockAdapter', js: 'https://cdn.jsdelivr.net/npm/axios-mock-adapter@1.18.1/dist/axios-mock-adapter.min.js', css: '' },
{ name: 'element-ui', library: 'ELEMENT', js: 'https://cdn.jsdelivr.net/npm/element-ui@2.15.5/lib/index.js', css: 'https://cdn.jsdelivr.net/npm/element-ui@2.15.5/lib/theme-chalk/index.css' }, // { name: 'element-ui', library: 'ELEMENT', js: 'https://cdn.jsdelivr.net/npm/element-ui@2.15.5/lib/index.js', css: 'https://cdn.jsdelivr.net/npm/element-ui@2.15.5/lib/theme-chalk/index.css' },
{ name: 'lodash', library: '_', js: 'https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js', css: '' }, // { name: 'lodash', library: '_', js: 'https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js', css: '' },
{ name: 'ua-parser-js', library: 'UAParser', js: 'https://cdn.jsdelivr.net/npm/ua-parser-js@0.7.20/dist/ua-parser.min.js', css: '' }, // { name: 'ua-parser-js', library: 'UAParser', js: 'https://cdn.jsdelivr.net/npm/ua-parser-js@0.7.20/dist/ua-parser.min.js', css: '' },
{ name: 'js-cookie', library: 'Cookies', js: 'https://cdn.jsdelivr.net/npm/js-cookie@2.2.1/src/js.cookie.min.js', css: '' }, // { name: 'js-cookie', library: 'Cookies', js: 'https://cdn.jsdelivr.net/npm/js-cookie@2.2.1/src/js.cookie.min.js', css: '' },
{ name: 'nprogress', library: 'NProgress', js: 'https://cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js', css: 'https://cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.css' }, // { name: 'nprogress', library: 'NProgress', js: 'https://cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js', css: 'https://cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.css' },
{ name: 'dayjs', library: 'dayjs', js: 'https://cdn.jsdelivr.net/npm/dayjs@1.8.17/dayjs.min.js', css: '' }, // { name: 'dayjs', library: 'dayjs', js: 'https://cdn.jsdelivr.net/npm/dayjs@1.8.17/dayjs.min.js', css: '' },
{ name: 'fuse.js', library: 'Fuse', js: 'https://cdn.jsdelivr.net/npm/fuse.js@5.2.3/dist/fuse.min.js', css: '' }, // { name: 'fuse.js', library: 'Fuse', js: 'https://cdn.jsdelivr.net/npm/fuse.js@5.2.3/dist/fuse.min.js', css: '' },
{ name: 'hotkeys-js', library: 'hotkeys', js: 'https://cdn.jsdelivr.net/npm/hotkeys-js@3.7.3/dist/hotkeys.min.js', css: '' }, // { name: 'hotkeys-js', library: 'hotkeys', js: 'https://cdn.jsdelivr.net/npm/hotkeys-js@3.7.3/dist/hotkeys.min.js', css: '' },
{ name: 'qs', library: 'Qs', js: 'https://cdn.jsdelivr.net/npm/qs@6.9.1/dist/qs.js', css: '' }, // { name: 'qs', library: 'Qs', js: 'https://cdn.jsdelivr.net/npm/qs@6.9.1/dist/qs.js', css: '' },
{ name: 'lowdb', library: 'low', js: 'https://cdn.jsdelivr.net/npm/lowdb@1.0.0/dist/low.min.js', css: '' }, // { name: 'lowdb', library: 'low', js: 'https://cdn.jsdelivr.net/npm/lowdb@1.0.0/dist/low.min.js', css: '' },
{ name: 'lowdb/adapters/LocalStorage', library: 'LocalStorage', js: 'https://cdn.jsdelivr.net/npm/lowdb@1.0.0/dist/LocalStorage.min.js', css: '' }, // { name: 'lowdb/adapters/LocalStorage', library: 'LocalStorage', js: 'https://cdn.jsdelivr.net/npm/lowdb@1.0.0/dist/LocalStorage.min.js', css: '' },
{ name: 'screenfull', library: 'screenfull', js: 'https://cdn.jsdelivr.net/npm/screenfull@5.0.2/dist/screenfull.min.js', css: '' }, // { name: 'screenfull', library: 'screenfull', js: 'https://cdn.jsdelivr.net/npm/screenfull@5.0.2/dist/screenfull.min.js', css: '' },
{ name: 'sortablejs', library: 'Sortable', js: 'https://cdn.jsdelivr.net/npm/sortablejs@1.10.1/Sortable.min.js', css: '' } // { name: 'sortablejs', library: 'Sortable', js: 'https://cdn.jsdelivr.net/npm/sortablejs@1.10.1/Sortable.min.js', css: '' }
] ]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@ -254,7 +254,7 @@ Vue.prototype.commonEndColumns = function (param = {}) {
search: { search: {
disabled: true disabled: true
}, },
type: 'cascader', type: 'table-selector',
dict: { dict: {
cache: true, cache: true,
url: '/api/system/dept/?limit=999&status=1', url: '/api/system/dept/?limit=999&status=1',
@ -262,9 +262,14 @@ Vue.prototype.commonEndColumns = function (param = {}) {
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, {
return request({ url: url }).then(ret => { _,
return [{ id: null, name: '根节点', children: XEUtils.toArrayTree(ret.data.data, { parentKey: 'parent', strict: true }) }] component
}) => {
return request({
url: url,
}).then(ret => {
return ret.data.data
}) })
} }
}, },
@ -273,13 +278,27 @@ Vue.prototype.commonEndColumns = function (param = {}) {
component: { component: {
props: { props: {
elProps: { elProps: {
clearable: true, treeConfig: {
showAllLevels: false, // 仅显示最后一级 transform: true,
props: { rowField: 'id',
checkStrictly: true, // 可以不需要选到最后一级 parentField: 'parent',
emitPath: false, expandAll: true
clearable: true },
columns: [
{
field: 'name',
title: '部门名称',
treeNode: true
},
{
field: 'status',
title: '状态'
},
{
field: 'parent_name',
title: '父级部门'
} }
]
} }
} }
}, },
@ -289,21 +308,6 @@ Vue.prototype.commonEndColumns = function (param = {}) {
) )
} }
} }
},
component: {
dict: {
cache: true,
url: deptPrefix + '?limit=999&status=1',
isTree: true,
value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名
children: 'children', // 数据字典中children字段的属性名
getData: (url, dict) => { // 配置此参数会覆盖全局的getRemoteDictFunc
return request({ url: url }).then(ret => {
return [{ id: null, name: '根节点', children: XEUtils.toArrayTree(ret.data.data, { parentKey: 'parent', strict: true }) }]
})
}
}
} }
}, },
{ {

View File

@ -20,6 +20,7 @@
注销 注销
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
<el-image v-if="info.avatar" :src="info.avatar" :preview-src-list="[info.avatar]" style="width: 20px;height: 20px" alt="头像"></el-image>
</el-dropdown> </el-dropdown>
</template> </template>

View File

@ -20,6 +20,21 @@
:label-position="position" :label-position="position"
center center
> >
<el-form-item prop="avatar" required label="头像">
<el-upload
class="avatar-uploader"
list-type="picture-card"
:file-list="fileList"
: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>-->
<i class="el-icon-plus"></i>
</el-upload>
</el-form-item>
<el-form-item prop="name" required label="昵称"> <el-form-item prop="name" required label="昵称">
<el-input v-model="userInfo.name" clearable></el-input> <el-input v-model="userInfo.name" clearable></el-input>
</el-form-item> </el-form-item>
@ -130,6 +145,11 @@ export default {
return { return {
position: 'left', position: 'left',
activeName: 'userInfo', activeName: 'userInfo',
action: util.baseURL() + 'api/system/file/',
headers: {
Authorization: 'JWT ' + util.cookies.get('token')
},
fileList:[],
userInfo: { userInfo: {
name: '', name: '',
gender: '', gender: '',
@ -177,6 +197,7 @@ export default {
params: {} params: {}
}).then((res) => { }).then((res) => {
_self.userInfo = res.data _self.userInfo = res.data
_self.fileList = [{name:'avatar.png',url:res.data.avatar}]
}) })
}, },
/** /**
@ -251,6 +272,16 @@ export default {
this.$message.error('表单校验失败,请检查') this.$message.error('表单校验失败,请检查')
} }
}) })
},
/**
* 头像上传
* @param res
* @param file
*/
handleAvatarSuccess(res, file) {
console.log(11,res)
this.fileList =[{ url: util.baseURL() + res.data.url, name:file.name }]
this.userInfo.avatar = util.baseURL() + res.data.url;
} }
} }
} }

View File

@ -41,7 +41,7 @@ const router = new VueRouter({
*/ */
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
// 白名单 // 白名单
const whiteList = ['/login', '/auth-redirect', '/bind', '/register'] const whiteList = ['/login', '/auth-redirect', '/bind', '/register', '/oauth2']
// 确认已经加载多标签页数据 https://github.com/d2-projects/d2-admin/issues/201 // 确认已经加载多标签页数据 https://github.com/d2-projects/d2-admin/issues/201
await store.dispatch('d2admin/page/isLoaded') await store.dispatch('d2admin/page/isLoaded')
// 确认已经加载组件尺寸设置 https://github.com/d2-projects/d2-admin/issues/198 // 确认已经加载组件尺寸设置 https://github.com/d2-projects/d2-admin/issues/198

View File

@ -1,8 +1,8 @@
import layoutHeaderAside from '@/layout/header-aside' import layoutHeaderAside from '@/layout/header-aside'
import { checkPlugins } from '@/views/plugins/index.js'
// 由于懒加载页面太多的话会造成webpack热更新太慢所以开发环境不使用懒加载只有生产环境使用懒加载 // 由于懒加载页面太多的话会造成webpack热更新太慢所以开发环境不使用懒加载只有生产环境使用懒加载
const _import = require('@/libs/util.import.' + process.env.NODE_ENV) const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
const pluginImport = require('@/libs/util.import.plugin')
/** /**
* 在主框架内显示 * 在主框架内显示
*/ */
@ -88,25 +88,25 @@ const frameIn = [{
// component: _import('system/user') // component: _import('system/user')
// }, // },
// // 系统 按钮配置 // // 系统 按钮配置
{ // {
path: 'button', // path: 'button',
name: 'button', // name: 'button',
meta: { // meta: {
title: '按钮', // title: '按钮',
auth: true // auth: true
}, // },
component: _import('system/button') // component: _import('system/button')
}, // },
// // 系统 菜单权限 // // // 系统 菜单权限
{ // {
path: 'menuButton/:id', // path: 'menuButton/:id',
name: 'menuButton', // name: 'menuButton',
meta: { // meta: {
title: '菜单按钮', // title: '菜单按钮',
auth: true // auth: true
}, // },
component: _import('system/menuButton') // component: _import('system/menuButton')
}, // },
// // 系统 角色管理 // // 系统 角色管理
// { // {
// path: 'role', // path: 'role',
@ -149,15 +149,15 @@ const frameIn = [{
// component: _import('system/log/operationLog') // component: _import('system/log/operationLog')
// }, // },
// 系统 前端日志 // 系统 前端日志
{ // {
path: 'frontendLog', // path: 'frontendLog',
name: 'frontendLog', // name: 'frontendLog',
meta: { // meta: {
title: '前端日志', // title: '前端日志',
auth: true // auth: true
}, // },
component: _import('system/log/frontendLog') // component: _import('system/log/frontendLog')
}, // },
// 刷新页面 必须保留 // 刷新页面 必须保留
{ {
path: 'refresh', path: 'refresh',
@ -186,7 +186,17 @@ const frameOut = [
component: _import('system/login') component: _import('system/login')
} }
] ]
/**
* 第三方登录
*/
const pluginsType = checkPlugins('dvadmin-oauth2-web')
if (pluginsType) {
frameOut.push({
path: '/oauth2',
name: 'login',
component: pluginsType === 'local' ? _import('plugins/dvadmin-oauth2-web/src/login/index') : pluginImport('dvadmin-oauth2-web/src/login/index')
})
}
/** /**
* 错误页面 * 错误页面
*/ */

View File

@ -36,39 +36,39 @@ export default {
title: 'd2admin 经典', title: 'd2admin 经典',
name: 'd2', name: 'd2',
preview: 'image/theme/d2/preview@2x.png' preview: 'image/theme/d2/preview@2x.png'
},
{
title: 'Chester',
name: 'chester',
preview: 'image/theme/chester/preview@2x.jpg'
},
{
title: 'Element',
name: 'element',
preview: 'image/theme/element/preview@2x.jpg'
},
{
title: '紫罗兰',
name: 'violet',
preview: 'image/theme/violet/preview@2x.jpg'
},
{
title: '简约线条',
name: 'line',
backgroundImage: 'image/theme/line/bg.jpg',
preview: 'image/theme/line/preview@2x.jpg'
},
{
title: '流星',
name: 'star',
backgroundImage: 'image/theme/star/bg.jpg',
preview: 'image/theme/star/preview@2x.jpg'
},
{
title: 'Tomorrow Night Blue (vsCode)',
name: 'tomorrow-night-blue',
preview: 'image/theme/tomorrow-night-blue/preview@2x.jpg'
} }
// {
// title: 'Chester',
// name: 'chester',
// preview: 'image/theme/chester/preview@2x.png'
// },
// {
// title: 'Element',
// name: 'element',
// preview: 'image/theme/element/preview@2x.png'
// },
// {
// title: '紫罗兰',
// name: 'violet',
// preview: 'image/theme/violet/preview@2x.png'
// },
// {
// title: '简约线条',
// name: 'line',
// backgroundImage: 'image/theme/line/bg.jpg',
// preview: 'image/theme/line/preview@2x.png'
// },
// {
// title: '流星',
// name: 'star',
// backgroundImage: 'image/theme/star/bg.jpg',
// preview: 'image/theme/star/preview@2x.png'
// },
// {
// title: 'Tomorrow Night Blue (vsCode)',
// name: 'tomorrow-night-blue',
// preview: 'image/theme/tomorrow-night-blue/preview@2x.png'
// }
] ]
}, },
// 是否默认开启页面切换动画 // 是否默认开启页面切换动画

View File

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

View File

@ -21,6 +21,7 @@ export default {
console.log('演示地址https://demo.django-vue-admin.com') console.log('演示地址https://demo.django-vue-admin.com')
console.log('社区地址https://bbs.django-vue-admin.com') console.log('社区地址https://bbs.django-vue-admin.com')
console.log('文档地址https://www.django-vue-admin.com') console.log('文档地址https://www.django-vue-admin.com')
console.log('前端配置文档地址https://d2.pub/zh/doc/d2-crud-v2')
console.log('请不要吝啬您的 star谢谢 ~') console.log('请不要吝啬您的 star谢谢 ~')
} }
} }

View File

@ -1,3 +1,5 @@
import Vue from 'vue'
function importAll (r) { function importAll (r) {
const __modules = [] const __modules = []
r.keys().forEach(key => { r.keys().forEach(key => {
@ -8,10 +10,42 @@ function importAll (r) {
return __modules return __modules
} }
export const checkPlugins = function install (pluginName) {
let pluginsList
pluginsList = importAll(require.context('./', true, /index\.js$/))
if (pluginsList && pluginsList.indexOf(pluginName) !== -1) {
try {
const Module = import('@/views/plugins/' + pluginName + '/src/index')
// 注册组件
if (Module.default) {
Vue.use(Module.default)
}
// 本地插件
return 'local'
} catch (exception) {}
}
pluginsList = importAll(require.context('@great-dream/', true, /index\.js$/))
if (pluginsList && pluginsList.indexOf(pluginName) !== -1) {
// node_modules 封装插件
try {
const Module = import('@great-dream/' + pluginName + '/src/index')
// 注册组件
if (Module.default) {
Vue.use(Module.default)
}
// 本地插件
return 'plugins'
} catch (exception) {}
}
// 未找到插件
return undefined
}
export const plugins = async function install (Vue, options) { export const plugins = async function install (Vue, options) {
// 查找 src/views/plugins 目录所有插件插件目录下需有 index.js 文件 // 查找 src/views/plugins 目录所有插件插件目录下需有 index.js 文件
// 再查找 node_modules/@great-dream/ 目录下所有插件 // 再查找 node_modules/@great-dream/ 目录下所有插件
// 进行去重并vue注册导入 // 进行去重并vue注册导入
if (window.pluginsAll) return
let components = [] let components = []
components = components.concat(importAll(require.context('./', true, /index\.js$/))) components = components.concat(importAll(require.context('./', true, /index\.js$/)))
components = components.concat(importAll(require.context('@great-dream/', true, /index\.js$/))) components = components.concat(importAll(require.context('@great-dream/', true, /index\.js$/)))

View File

@ -78,8 +78,12 @@ export const crudOptions = (vm) => {
disabled: false disabled: false
}, },
width: 160, width: 160,
type: 'input' type: 'input',
form: {
component: {
placeholder: '请输入文件名称'
}
}
}, },
{ {
title: '文件地址', title: '文件地址',

View File

@ -0,0 +1,19 @@
/*
* @创建文件时间: 2021-06-08 10:40:32
* @Auther: 猿小天
* @最后修改人: 猿小天
* @最后修改时间: 2021-06-09 10:36:20
* 联系Qq:1638245306
* @文件介绍: 操作日志
*/
import { request } from '@/api/service'
export const urlPrefix = '/api/system/login_log/'
export function GetList (query) {
return request({
url: urlPrefix,
method: 'get',
params: query
})
}

View File

@ -0,0 +1,293 @@
import { BUTTON_WHETHER_BOOL } from '@/config/button'
export const crudOptions = (vm) => {
return {
pageOptions: {
compact: true
},
options: {
tableType: 'vxe-table',
rowKey: true, // 必须设置true or false
rowId: 'id',
height: '100%', // 表格高度100%, 使用toolbar必须设置
highlightCurrentRow: false
},
rowHandle: {
fixed: 'right',
view: {
thin: true,
text: '',
disabled () {
return !vm.hasPermissions('Retrieve')
}
},
width: 70,
edit: {
thin: true,
text: '',
show: false,
disabled () {
return !vm.hasPermissions('Update')
}
},
remove: {
thin: true,
text: '删除',
show: false,
disabled () {
return !vm.hasPermissions('Delete')
}
}
},
viewOptions: {
componentType: 'form'
},
formOptions: {
disabled: true,
defaultSpan: 12 // 默认的表单 span
},
indexRow: { // 或者直接传true,不显示title不居中
title: '序号',
align: 'center',
width: 70
},
columns: [
{
title: '关键词',
key: 'search',
show: false,
disabled: true,
search: {
disabled: false
},
form: {
show: false,
component: {
placeholder: '请输入关键词'
}
}
},
{
title: 'ID',
key: 'id',
width: 90,
disabled: true,
form: {
disabled: true
}
},
{
title: '登录用户名',
key: 'username',
search: {
disabled: false
},
width: 140,
type: 'input',
form: {
disabled: true,
component: {
placeholder: '请输入登录用户名'
}
}
},
{
title: '登录ip',
key: 'ip',
search: {
disabled: false
},
width: 130,
type: 'input',
form: {
disabled: true,
component: {
placeholder: '请输入登录ip'
}
}
}, {
title: '运营商',
key: 'isp',
search: {
disabled: true
},
disabled: true,
width: 180,
type: 'input',
form: {
component: {
placeholder: '请输入操作系统'
}
}
}, {
title: '大州',
key: 'continent',
width: 80,
type: 'input',
form: {
disabled: true,
component: {
placeholder: '请输入州'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '国家',
key: 'country',
width: 80,
type: 'input',
form: {
component: {
placeholder: '请输入国家'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '省份',
key: 'province',
width: 80,
type: 'input',
form: {
component: {
placeholder: '请输入省份'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '城市',
key: 'city',
width: 80,
type: 'input',
form: {
component: {
placeholder: '请输入城市'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '县区',
key: 'district',
width: 80,
type: 'input',
form: {
component: {
placeholder: '请输入县区'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '区域代码',
key: 'area_code',
width: 100,
type: 'input',
form: {
component: {
placeholder: '请输入区域代码'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '英文全称',
key: 'country_english',
width: 120,
type: 'input',
form: {
component: {
placeholder: '请输入英文全称'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '简称',
key: 'country_code',
width: 100,
type: 'input',
form: {
component: {
placeholder: '请输入简称'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '经度',
key: 'longitude',
width: 80,
type: 'input',
disabled: true,
form: {
component: {
placeholder: '请输入经度'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '纬度',
key: 'latitude',
width: 80,
type: 'input',
disabled: true,
form: {
component: {
placeholder: '请输入纬度'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '登录类型',
key: 'login_type',
width: 100,
type: 'select',
search: {
disabled: false
},
dict: {
data: [{ label: '普通登录', value: 1 }]
},
form: {
component: {
placeholder: '请选择登录类型'
}
},
component: { props: { color: 'auto' } } // 自动染色
}, {
title: '操作系统',
key: 'os',
width: 180,
type: 'input',
form: {
component: {
placeholder: '请输入操作系统'
}
}
}, {
title: '浏览器名',
key: 'browser',
width: 180,
type: 'input',
form: {
component: {
placeholder: '请输入操作系统'
}
}
}, {
title: 'agent信息',
key: 'agent',
disabled: true,
width: 180,
type: 'input',
form: {
component: {
placeholder: '请输入操作系统'
}
}
}, {
fixed: 'right',
title: '登录时间',
key: 'create_datetime',
width: 160,
type: 'datetime'
}
]
}
}

View File

@ -0,0 +1,48 @@
<template>
<d2-container :class="{ 'page-compact': crud.pageOptions.compact }">
<d2-crud-x
ref="d2Crud"
v-bind="_crudProps"
v-on="_crudListeners"
crud.options.tableType="vxe-table"
>
<div slot="header">
<crud-search
ref="search"
:options="crud.searchOptions"
@submit="handleSearch"
/>
<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>
</d2-container>
</template>
<script>
import * as api from './api'
import { crudOptions } from './crud'
import { d2CrudPlus } from 'd2-crud-plus'
export default {
name: 'loginLog',
mixins: [d2CrudPlus.crud],
data () {
return {}
},
methods: {
getCrudOptions () {
return crudOptions(this)
},
pageRequest (query) {
return api.GetList(query)
}
}
}
</script>

View File

@ -12,14 +12,15 @@ export const crudOptions = (vm) => {
}, },
rowHandle: { rowHandle: {
fixed: 'right',
view: { view: {
thin: true, thin: true,
text: '详情', text: '',
disabled () { disabled () {
return !vm.hasPermissions('Retrieve') return !vm.hasPermissions('Retrieve')
} }
}, },
width: 160, width: 70,
edit: { edit: {
thin: true, thin: true,
text: '', text: '',
@ -42,7 +43,12 @@ export const crudOptions = (vm) => {
}, },
formOptions: { formOptions: {
disabled: true, disabled: true,
defaultSpan: 24 // 默认的表单 span defaultSpan: 12 // 默认的表单 span
},
indexRow: { // 或者直接传true,不显示title不居中
title: '序号',
align: 'center',
width: 70
}, },
columns: [ columns: [
{ {
@ -106,11 +112,16 @@ export const crudOptions = (vm) => {
disabled: true disabled: true
}, },
disabled: true, disabled: true,
width: 180, type: 'textarea',
type: 'input',
form: { form: {
disabled: true, disabled: true,
component: { component: {
props: {
type: 'textarea'
},
autosize: {
minRows: 2, maxRows: 8
},
placeholder: '请输入关键词' placeholder: '请输入关键词'
} }
} }
@ -147,7 +158,7 @@ export const crudOptions = (vm) => {
search: { search: {
disabled: false disabled: false
}, },
width: 100, width: 130,
type: 'input', type: 'input',
form: { form: {
disabled: true, disabled: true,
@ -199,6 +210,7 @@ export const crudOptions = (vm) => {
search: { search: {
disabled: true disabled: true
}, },
minWidth: 240,
type: 'input', type: 'input',
form: { form: {
disabled: true disabled: true
@ -223,7 +235,8 @@ export const crudOptions = (vm) => {
} }
}, },
{ {
title: '创建时间', fixed: 'right',
title: '操作时间',
key: 'create_datetime', key: 'create_datetime',
width: 160, width: 160,
type: 'datetime', type: 'datetime',

View File

@ -57,11 +57,9 @@ export default {
return api.GetList(query) return api.GetList(query)
}, },
addRequest (row) { addRequest (row) {
console.log('api', api)
return api.AddObj(row) return api.AddObj(row)
}, },
updateRequest (row) { updateRequest (row) {
console.log('----', row)
return api.UpdateObj(row) return api.UpdateObj(row)
}, },
delRequest (row) { delRequest (row) {

View File

@ -164,7 +164,7 @@ export const crudOptions = (vm) => {
data: BUTTON_WHETHER_BOOL data: BUTTON_WHETHER_BOOL
}, },
form: { form: {
value: 0, value: false,
component: { component: {
placeholder: '请选择是否管理员' placeholder: '请选择是否管理员'
} }
@ -183,7 +183,7 @@ export const crudOptions = (vm) => {
data: BUTTON_STATUS_BOOL data: BUTTON_STATUS_BOOL
}, },
form: { form: {
value: 1, value: true,
component: { component: {
placeholder: '请选择状态' placeholder: '请选择状态'
} }

View File

@ -48,7 +48,7 @@
> >
<template slot="title"> <template slot="title">
<div> <div>
当前角色<el-tag>管理员</el-tag> 当前角色<el-tag>{{roleObj?roleObj.name:'无'}}</el-tag>
</div> </div>
</template> </template>
<div> <div>

View File

@ -6,7 +6,7 @@
* 联系Qq:1638245306 * 联系Qq:1638245306
* @文件介绍: 用户接口 * @文件介绍: 用户接口
*/ */
import { request, downloadFile } from '@/api/service' import { request } from '@/api/service'
export const urlPrefix = '/api/system/user/' export const urlPrefix = '/api/system/user/'
@ -43,13 +43,15 @@ export function DelObj (id) {
} }
/** /**
* 导出 * 重置密码
* @param params * @param id
* @returns {*}
* @constructor
*/ */
export function exportData (params) { export function ResetPwd (obj) {
return downloadFile({ return request({
url: urlPrefix + 'export/', url: urlPrefix + 'reset_password/' + obj.id + '/',
params: params, method: 'put',
method: 'post' data: obj
}) })
} }

View File

@ -2,7 +2,8 @@ import { request } from '@/api/service'
import { BUTTON_STATUS_BOOL } from '@/config/button' 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' import util from '@/libs/util'
const uploadUrl = util.baseURL() + 'api/system/img/'
const uploadUrl = util.baseURL() + 'api/system/file/'
export const crudOptions = (vm) => { export const crudOptions = (vm) => {
return { return {
pageOptions: { pageOptions: {
@ -12,8 +13,8 @@ export const crudOptions = (vm) => {
height: '100%' height: '100%'
}, },
rowHandle: { rowHandle: {
width: 140,
fixed: 'right', fixed: 'right',
width: 180,
view: { view: {
thin: true, thin: true,
text: '', text: '',
@ -34,7 +35,21 @@ export const crudOptions = (vm) => {
disabled () { disabled () {
return !vm.hasPermissions('Delete') return !vm.hasPermissions('Delete')
} }
},
custom: [
{
thin: true,
text: '',
size: 'small',
type: 'warning',
icon: 'el-icon-refresh-left',
show () {
return vm.hasPermissions('ResetPwd')
},
emit: 'resetPwd'
} }
]
}, },
viewOptions: { viewOptions: {
componentType: 'form' componentType: 'form'
@ -45,7 +60,7 @@ export const crudOptions = (vm) => {
indexRow: { // 或者直接传true,不显示title不居中 indexRow: { // 或者直接传true,不显示title不居中
title: '序号', title: '序号',
align: 'center', align: 'center',
width: 80 width: 70
}, },
columns: [ columns: [
{ {
@ -81,7 +96,7 @@ export const crudOptions = (vm) => {
search: { search: {
disabled: false disabled: false
}, },
width: 160, width: 140,
type: 'input', type: 'input',
form: { form: {
rules: [ // 表单校验规则 rules: [ // 表单校验规则
@ -105,7 +120,6 @@ export const crudOptions = (vm) => {
title: '姓名', title: '姓名',
key: 'name', key: 'name',
search: { search: {
key: 'name__icontains',
disabled: false disabled: false
}, },
type: 'input', type: 'input',
@ -124,18 +138,18 @@ export const crudOptions = (vm) => {
}, },
{ {
title: '部门', title: '部门',
width: 160,
key: 'dept', key: 'dept',
search: { search: {
disabled: true disabled: true
}, },
minWidth: 140,
type: 'table-selector', type: 'table-selector',
dict: { dict: {
cache: false, cache: false,
url: deptPrefix, url: deptPrefix,
value: 'id', // 数据字典中value字段的属性名 value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名 label: 'name', // 数据字典中label字段的属性名
getData: (url, dict, { _, component }) => { getData: (url, dict, { form, component }) => {
return request({ url: url, params: { page: 1, limit: 10, status: 1 } }).then(ret => { return request({ url: url, params: { page: 1, limit: 10, status: 1 } }).then(ret => {
component._elProps.page = ret.data.page component._elProps.page = ret.data.page
component._elProps.limit = ret.data.limit component._elProps.limit = ret.data.limit
@ -173,13 +187,14 @@ export const crudOptions = (vm) => {
} }
} }
} }
}, { },
{
title: '手机号码', title: '手机号码',
key: 'mobile', key: 'mobile',
width: 120,
search: { search: {
disabled: true disabled: true
}, },
minWidth: 110,
type: 'input', type: 'input',
form: { form: {
rules: [ rules: [
@ -196,7 +211,7 @@ export const crudOptions = (vm) => {
}, { }, {
title: '邮箱', title: '邮箱',
key: 'email', key: 'email',
width: 120, minWidth: 160,
form: { form: {
rules: [ rules: [
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] } { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
@ -209,49 +224,26 @@ export const crudOptions = (vm) => {
{ {
title: '性别', title: '性别',
key: 'gender', key: 'gender',
type: 'select', type: 'radio',
width: 70,
dict: { dict: {
data: [{ label: '', value: 1 }, { label: '', value: 0 }] data: [{ label: '', value: 1 }, { label: '', value: 0 }]
}, },
form: { form: {
value: 1, value: 1,
rules: [
{ required: true, message: '性别必填项' }
],
component: { component: {
span: 12 span: 12
},
itemProps: {
class: { yxtInput: true }
} }
}, },
component: { props: { color: 'auto' } } // 自动染色 component: { props: { color: 'auto' } } // 自动染色
}, },
{
title: '用户类型',
key: 'user_type',
type: 'select',
width: 120,
search: {
key: 'user_type',
value: 0,
disabled: false
},
dict: {
data: [{ label: '前台用户', value: 1 }, { label: '后台用户', value: 0 }]
},
form: {
disabled: true
},
component: { props: { color: 'auto' } } // 自动染色
},
{ {
title: '状态', title: '状态',
key: 'is_active', key: 'is_active',
search: { search: {
disabled: false disabled: false
}, },
width: 90, width: 70,
type: 'radio', type: 'radio',
dict: { dict: {
data: BUTTON_STATUS_BOOL data: BUTTON_STATUS_BOOL
@ -267,23 +259,22 @@ export const crudOptions = (vm) => {
title: '头像', title: '头像',
key: 'avatar', key: 'avatar',
type: 'avatar-uploader', type: 'avatar-uploader',
width: 80, width: 100,
align: 'left', align: 'left',
form: { form: {
component: { component: {
props: { props: {
uploader: { uploader: {
action: uploadUrl, action: uploadUrl,
name: 'url',
headers: { headers: {
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 }
} }
}, },
elProps: { // 与el-uploader 配置一致 elProps: { // 与el-uploader 配置一致
@ -309,9 +300,8 @@ export const crudOptions = (vm) => {
component: { component: {
props: { props: {
buildUrl (value, item) { buildUrl (value, item) {
console.log(11, value)
if (value && value.indexOf('http') !== 0) { if (value && value.indexOf('http') !== 0) {
return '/api/upload/form/download?key=' + value return util.baseURL() + value
} }
return value return value
} }
@ -321,10 +311,10 @@ export const crudOptions = (vm) => {
{ {
title: '角色', title: '角色',
key: 'role', key: 'role',
width: 160,
search: { search: {
disabled: true disabled: true
}, },
minWidth: 130,
type: 'table-selector', type: 'table-selector',
dict: { dict: {
cache: false, cache: false,
@ -370,6 +360,6 @@ export const crudOptions = (vm) => {
} }
} }
} }
].concat(vm.commonEndColumns({ update_datetime: { showTable: false } })) ].concat(vm.commonEndColumns({ update_datetime: { showForm: false, showTable: false }, create_datetime: { showForm: false, showTable: true } }))
} }
} }

View File

@ -1,11 +1,3 @@
<!--
* @创建文件时间: 2021-06-01 22:41:21
* @Auther: 猿小天
* @最后修改人: 猿小天
* @最后修改时间: 2021-07-29 19:27:10
* 联系Qq:1638245306
* @文件介绍: 用户管理
-->
<template> <template>
<d2-container :class="{ 'page-compact': crud.pageOptions.compact }"> <d2-container :class="{ 'page-compact': crud.pageOptions.compact }">
<d2-crud-x <d2-crud-x
@ -13,6 +5,7 @@
v-bind="_crudProps" v-bind="_crudProps"
v-on="_crudListeners" v-on="_crudListeners"
crud.options.tableType="vxe-table" crud.options.tableType="vxe-table"
@resetPwd="resetPwd"
> >
<div slot="header"> <div slot="header">
<crud-search <crud-search
@ -26,20 +19,8 @@
v-permission="'Create'" v-permission="'Create'"
type="primary" type="primary"
@click="addRow" @click="addRow"
><i class="el-icon-plus"/> 新增 ><i class="el-icon-plus" /> 新增</el-button
</el-button
> >
<el-button
size="small"
type="danger"
@click="onExport"
v-permission="'Export'"
><i class="el-icon-download"/> 导出
</el-button>
<importExcel
importApi="/api/system/user/import/"
v-permission="'Import'">导入
</importExcel>
</el-button-group> </el-button-group>
<crud-toolbar <crud-toolbar
:search.sync="crud.searchOptions.show" :search.sync="crud.searchOptions.show"
@ -50,6 +31,20 @@
/> />
</div> </div>
</d2-crud-x> </d2-crud-x>
<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>
</el-form-item>
<el-form-item label="再次输入密码" prop="pwd2">
<el-input v-model="resetPwdForm.pwd2" type="password" show-password clearable autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false"> </el-button>
<el-button type="primary" @click="resetPwdSubmit"> </el-button>
</div>
</el-dialog>
</d2-container> </d2-container>
</template> </template>
@ -60,10 +55,42 @@ import { d2CrudPlus } from 'd2-crud-plus'
export default { export default {
name: 'user', name: 'user',
mixins: [d2CrudPlus.crud], mixins: [d2CrudPlus.crud],
data () { data () {
return {} 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' }]
}
}
}, },
methods: { methods: {
getCrudOptions () { getCrudOptions () {
@ -76,19 +103,38 @@ export default {
return api.AddObj(row) return api.AddObj(row)
}, },
updateRequest (row) { updateRequest (row) {
console.log('----', row)
return api.UpdateObj(row) return api.UpdateObj(row)
}, },
delRequest (row) { delRequest (row) {
return api.DelObj(row.id) return api.DelObj(row.id)
}, },
onExport () { //
this.$confirm('是否确认导出所有数据项?', '警告', { resetPwd ({ row }) {
confirmButtonText: '确定', this.dialogFormVisible = true
cancelButtonText: '取消', this.resetPwdForm.id = row.id
type: 'warning' },
}).then(function () { //
return api.exportData() 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('表单校验失败,请检查')
}
}) })
} }
} }

View File

@ -45,7 +45,10 @@ export const crudOptions = (vm) => {
disabled: false disabled: false
}, },
form: { form: {
disabled: true disabled: true,
component: {
placeholder: '请输入关键词'
}
}, },
view: { // 查看对话框组件的单独配置 view: { // 查看对话框组件的单独配置
disabled: true disabled: true