|
@ -31,9 +31,9 @@
|
|||
|
||||
👩👧👦演示地址:[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)
|
||||
|
||||
|
@ -130,7 +130,7 @@ npm run dev
|
|||
8. 启动项目
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
或使用 daphne :
|
||||
daphne -b 0.0.0.0 -8000 application.asgi:application
|
||||
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
||||
~~~
|
||||
|
||||
### 访问项目
|
||||
|
|
|
@ -28,7 +28,10 @@ from conf.env import *
|
|||
|
||||
# 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'
|
||||
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!
|
||||
DEBUG = locals().get('DEBUG', True)
|
||||
|
|
|
@ -5,19 +5,24 @@ from application.settings import BASE_DIR
|
|||
# ================================================= #
|
||||
# ************** 数据库 配置 ************** #
|
||||
# ================================================= #
|
||||
#
|
||||
|
||||
# 数据库 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_HOST = "127.0.0.1"
|
||||
# 数据库端口
|
||||
# # 数据库端口
|
||||
DATABASE_PORT = 3306
|
||||
# 数据库用户名
|
||||
# # 数据库用户名
|
||||
DATABASE_USER = "root"
|
||||
# 数据库密码
|
||||
# # 数据库密码
|
||||
DATABASE_PASSWORD = "123456"
|
||||
|
||||
# ================================================= #
|
||||
# ************** redis配置,无redis 可不进行配置 ************** #
|
||||
# ================================================= #
|
||||
|
@ -30,3 +35,4 @@ DATABASE_PASSWORD = "123456"
|
|||
DEBUG = True # 线上环境请设置为True
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
|
||||
ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址)
|
||||
|
|
|
@ -163,6 +163,17 @@ class Initialize(CoreInitialize):
|
|||
"name": "导出",
|
||||
"value": "Export",
|
||||
"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, "权限表标识")
|
||||
|
@ -507,6 +518,27 @@ class Initialize(CoreInitialize):
|
|||
"creator_id": 1,
|
||||
"parent_id": 15,
|
||||
"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, "菜单表")
|
||||
|
@ -517,19 +549,19 @@ class Initialize(CoreInitialize):
|
|||
"""
|
||||
self.menu_button_data = [
|
||||
{
|
||||
"id": 1,
|
||||
"description": None,
|
||||
"modifier": "1",
|
||||
"dept_belong_id": 1,
|
||||
"update_datetime": datetime.datetime.now(),
|
||||
"create_datetime": datetime.datetime.now(),
|
||||
"name": "查询",
|
||||
"value": "Search",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 0,
|
||||
"creator_id": 1,
|
||||
"menu_id": 1
|
||||
},
|
||||
"id": 1,
|
||||
"description": None,
|
||||
"modifier": "1",
|
||||
"dept_belong_id": 1,
|
||||
"update_datetime": datetime.datetime.now(),
|
||||
"create_datetime": datetime.datetime.now(),
|
||||
"name": "查询",
|
||||
"value": "Search",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 0,
|
||||
"creator_id": 1,
|
||||
"menu_id": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"description": None,
|
||||
|
@ -1243,6 +1275,47 @@ class Initialize(CoreInitialize):
|
|||
"method": 1,
|
||||
"creator_id": 1,
|
||||
"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, "菜单按钮表")
|
||||
|
|
|
@ -29,7 +29,7 @@ class Users(AbstractUser, CoreModel):
|
|||
(1, "前台用户"),
|
||||
)
|
||||
user_type = models.IntegerField(choices=USER_TYPE, default=0, verbose_name="用户类型", null=True, blank=True,
|
||||
help_text="用户类型")
|
||||
help_text="用户类型")
|
||||
post = models.ManyToManyField(to='Post', verbose_name='关联岗位', db_constraint=False, help_text="关联岗位")
|
||||
role = models.ManyToManyField(to='Role', verbose_name='关联角色', db_constraint=False, help_text="关联角色")
|
||||
dept = models.ForeignKey(to='Dept', verbose_name='所属部门', on_delete=models.PROTECT, db_constraint=False, null=True,
|
||||
|
@ -206,7 +206,7 @@ class OperationLog(CoreModel):
|
|||
def media_file_name(instance, filename):
|
||||
h = instance.md5sum
|
||||
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):
|
||||
|
@ -306,3 +306,32 @@ class SystemConfig(CoreModel):
|
|||
|
||||
def __str__(self):
|
||||
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',)
|
||||
|
|
|
@ -15,6 +15,7 @@ from dvadmin.system.views.button import ButtonViewSet
|
|||
from dvadmin.system.views.dept import DeptViewSet
|
||||
from dvadmin.system.views.dictionary import DictionaryViewSet
|
||||
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_button import MenuButtonViewSet
|
||||
from dvadmin.system.views.operation_log import OperationLogViewSet
|
||||
|
@ -41,11 +42,14 @@ urlpatterns = [
|
|||
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/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/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/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_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
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 角色管理
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
|
||||
from dvadmin.system.models import Dept
|
||||
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:
|
||||
model = Dept
|
||||
fields = "__all__"
|
||||
|
@ -55,11 +56,11 @@ class DeptViewSet(CustomModelViewSet):
|
|||
update_serializer_class = DeptCreateUpdateSerializer
|
||||
# extra_filter_backends = []
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True, request=request)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
# def list(self, request, *args, **kwargs):
|
||||
# queryset = self.filter_queryset(self.get_queryset())
|
||||
# page = self.paginate_queryset(queryset)
|
||||
# if page is not None:
|
||||
# serializer = self.get_serializer(page, many=True, request=request)
|
||||
# return self.get_paginated_response(serializer.data)
|
||||
# serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
# return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
|
|
|
@ -17,7 +17,7 @@ class FileSerializer(CustomModelSerializer):
|
|||
url = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
def get_url(self, instance):
|
||||
return str(instance.url)
|
||||
return 'media/'+str(instance.url)
|
||||
|
||||
class Meta:
|
||||
model = FileList
|
||||
|
@ -41,3 +41,4 @@ class FileViewSet(CustomModelViewSet):
|
|||
queryset = FileList.objects.all()
|
||||
serializer_class = FileSerializer
|
||||
filter_fields = ['name', ]
|
||||
permission_classes = []
|
||||
|
|
|
@ -25,6 +25,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.request_util import save_login_log
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.validator import CustomValidationError
|
||||
|
||||
|
@ -56,7 +57,7 @@ class LoginSerializer(TokenObtainPairSerializer):
|
|||
登录的序列化器:
|
||||
重写djangorestframework-simplejwt的序列化器
|
||||
"""
|
||||
captcha = serializers.CharField(max_length=6)
|
||||
captcha = serializers.CharField(max_length=6, required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
|
@ -67,24 +68,31 @@ class LoginSerializer(TokenObtainPairSerializer):
|
|||
'no_active_account': _('账号/密码不正确')
|
||||
}
|
||||
|
||||
def validate_captcha(self, captcha):
|
||||
self.image_code = CaptchaStore.objects.filter(
|
||||
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('验证码过期')
|
||||
else:
|
||||
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("图片验证码错误")
|
||||
|
||||
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(
|
||||
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('验证码过期')
|
||||
else:
|
||||
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')
|
||||
request.user = self.user
|
||||
# 记录登录日志
|
||||
save_login_log(request=request)
|
||||
return {
|
||||
"code": 2000,
|
||||
"msg": "请求成功",
|
||||
|
|
|
@ -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 = []
|
|
@ -11,6 +11,7 @@ import hashlib
|
|||
from django.contrib.auth.hashers import make_password
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Users
|
||||
from dvadmin.utils.json_response import ErrorResponse, DetailResponse
|
||||
|
@ -147,7 +148,7 @@ class UserViewSet(CustomModelViewSet):
|
|||
'gender': '用户性别(男/女/未知)',
|
||||
'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):
|
||||
"""获取当前用户信息"""
|
||||
user = request.user
|
||||
|
@ -155,18 +156,19 @@ class UserViewSet(CustomModelViewSet):
|
|||
"name": user.name,
|
||||
"mobile": user.mobile,
|
||||
"gender": user.gender,
|
||||
"email": user.email
|
||||
"email": user.email,
|
||||
'avatar':user.avatar
|
||||
}
|
||||
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):
|
||||
"""修改当前用户信息"""
|
||||
user = request.user
|
||||
Users.objects.filter(id=user.id).update(**request.data)
|
||||
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):
|
||||
"""密码修改"""
|
||||
instance = Users.objects.filter(id=kwargs.get('pk')).first()
|
||||
|
@ -185,3 +187,22 @@ class UserViewSet(CustomModelViewSet):
|
|||
return ErrorResponse(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="未获取到用户")
|
||||
|
|
|
@ -36,7 +36,7 @@ def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
|
|||
if dept_list is None:
|
||||
dept_list = [dept_id]
|
||||
for ele in dept_all_list:
|
||||
if ele.get('parentId') == dept_id:
|
||||
if ele.get('parent') == dept_id:
|
||||
dept_list.append(ele.get('id'))
|
||||
get_dept(ele.get('id'), dept_all_list, dept_list)
|
||||
return list(set(dept_list))
|
||||
|
@ -94,10 +94,15 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
|||
return queryset.filter(dept_belong_id=user_dept_id)
|
||||
|
||||
# 3. 根据所有角色 获取所有权限范围
|
||||
# (0, "仅本人数据权限"),
|
||||
# (1, "本部门及以下数据权限"),
|
||||
# (2, "本部门数据权限"),
|
||||
# (3, "全部数据权限"),
|
||||
# (4, "自定数据权限")
|
||||
role_list = request.user.role.filter(status=1).values('admin', 'data_range')
|
||||
dataScope_list = []
|
||||
dataScope_list = [] # 权限范围列表
|
||||
for ele in role_list:
|
||||
# 3.1 判断用户是否为超级管理员角色/如果有1(所有数据) 则返回所有数据
|
||||
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
|
||||
if 3 == ele.get('data_range') or ele.get('admin') == True:
|
||||
return queryset
|
||||
dataScope_list.append(ele.get('data_range'))
|
||||
|
|
|
@ -3,12 +3,16 @@ Request工具类
|
|||
"""
|
||||
import json
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractBaseUser
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.urls.resolvers import ResolverMatch
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from user_agents import parse
|
||||
|
||||
from dvadmin.system.models import LoginLog
|
||||
|
||||
|
||||
def get_request_user(request):
|
||||
"""
|
||||
|
@ -163,3 +167,50 @@ def get_verbose_name(queryset=None, view=None, model=None):
|
|||
except Exception as e:
|
||||
pass
|
||||
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)
|
||||
|
|
|
@ -68,7 +68,7 @@ class CustomModelSerializer(DynamicFieldsMixin,ModelSerializer):
|
|||
def update(self, instance, validated_data):
|
||||
if self.request:
|
||||
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)
|
||||
|
||||
def get_request_username(self):
|
||||
|
|
|
@ -7,6 +7,8 @@ server {
|
|||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
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;
|
||||
index index.html index.php index.htm;
|
||||
}
|
||||
|
@ -16,6 +18,8 @@ server {
|
|||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
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; #重写
|
||||
proxy_pass http://177.7.0.12:8000/; # 设置代理服务器的协议和地址
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
module.exports = [
|
||||
{ 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-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: '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: '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: '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: '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: '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: '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: '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: '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: '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-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: '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: '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: '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: '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: '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: '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: '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: '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: '' }
|
||||
]
|
||||
|
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 376 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 1.9 MiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 45 KiB |
|
@ -254,7 +254,7 @@ Vue.prototype.commonEndColumns = function (param = {}) {
|
|||
search: {
|
||||
disabled: true
|
||||
},
|
||||
type: 'cascader',
|
||||
type: 'table-selector',
|
||||
dict: {
|
||||
cache: true,
|
||||
url: '/api/system/dept/?limit=999&status=1',
|
||||
|
@ -262,9 +262,14 @@ Vue.prototype.commonEndColumns = function (param = {}) {
|
|||
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 }) }]
|
||||
getData: (url, dict, {
|
||||
_,
|
||||
component
|
||||
}) => {
|
||||
return request({
|
||||
url: url,
|
||||
}).then(ret => {
|
||||
return ret.data.data
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -273,13 +278,27 @@ Vue.prototype.commonEndColumns = function (param = {}) {
|
|||
component: {
|
||||
props: {
|
||||
elProps: {
|
||||
clearable: true,
|
||||
showAllLevels: false, // 仅显示最后一级
|
||||
props: {
|
||||
checkStrictly: true, // 可以不需要选到最后一级
|
||||
emitPath: false,
|
||||
clearable: true
|
||||
}
|
||||
treeConfig: {
|
||||
transform: true,
|
||||
rowField: 'id',
|
||||
parentField: 'parent',
|
||||
expandAll: 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 }) }]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
注销
|
||||
</el-dropdown-item>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -20,6 +20,21 @@
|
|||
:label-position="position"
|
||||
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-input v-model="userInfo.name" clearable></el-input>
|
||||
</el-form-item>
|
||||
|
@ -130,6 +145,11 @@ export default {
|
|||
return {
|
||||
position: 'left',
|
||||
activeName: 'userInfo',
|
||||
action: util.baseURL() + 'api/system/file/',
|
||||
headers: {
|
||||
Authorization: 'JWT ' + util.cookies.get('token')
|
||||
},
|
||||
fileList:[],
|
||||
userInfo: {
|
||||
name: '',
|
||||
gender: '',
|
||||
|
@ -177,6 +197,7 @@ export default {
|
|||
params: {}
|
||||
}).then((res) => {
|
||||
_self.userInfo = res.data
|
||||
_self.fileList = [{name:'avatar.png',url:res.data.avatar}]
|
||||
})
|
||||
},
|
||||
/**
|
||||
|
@ -251,6 +272,16 @@ export default {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ const router = new VueRouter({
|
|||
*/
|
||||
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
|
||||
await store.dispatch('d2admin/page/isLoaded')
|
||||
// 确认已经加载组件尺寸设置 https://github.com/d2-projects/d2-admin/issues/198
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import layoutHeaderAside from '@/layout/header-aside'
|
||||
|
||||
import { checkPlugins } from '@/views/plugins/index.js'
|
||||
// 由于懒加载页面太多的话会造成webpack热更新太慢,所以开发环境不使用懒加载,只有生产环境使用懒加载
|
||||
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')
|
||||
// },
|
||||
// // 系统 按钮配置
|
||||
{
|
||||
path: 'button',
|
||||
name: 'button',
|
||||
meta: {
|
||||
title: '按钮',
|
||||
auth: true
|
||||
},
|
||||
component: _import('system/button')
|
||||
},
|
||||
// // 系统 菜单权限
|
||||
{
|
||||
path: 'menuButton/:id',
|
||||
name: 'menuButton',
|
||||
meta: {
|
||||
title: '菜单按钮',
|
||||
auth: true
|
||||
},
|
||||
component: _import('system/menuButton')
|
||||
},
|
||||
// {
|
||||
// path: 'button',
|
||||
// name: 'button',
|
||||
// meta: {
|
||||
// title: '按钮',
|
||||
// auth: true
|
||||
// },
|
||||
// component: _import('system/button')
|
||||
// },
|
||||
// // // 系统 菜单权限
|
||||
// {
|
||||
// path: 'menuButton/:id',
|
||||
// name: 'menuButton',
|
||||
// meta: {
|
||||
// title: '菜单按钮',
|
||||
// auth: true
|
||||
// },
|
||||
// component: _import('system/menuButton')
|
||||
// },
|
||||
// // 系统 角色管理
|
||||
// {
|
||||
// path: 'role',
|
||||
|
@ -149,15 +149,15 @@ const frameIn = [{
|
|||
// component: _import('system/log/operationLog')
|
||||
// },
|
||||
// 系统 前端日志
|
||||
{
|
||||
path: 'frontendLog',
|
||||
name: 'frontendLog',
|
||||
meta: {
|
||||
title: '前端日志',
|
||||
auth: true
|
||||
},
|
||||
component: _import('system/log/frontendLog')
|
||||
},
|
||||
// {
|
||||
// path: 'frontendLog',
|
||||
// name: 'frontendLog',
|
||||
// meta: {
|
||||
// title: '前端日志',
|
||||
// auth: true
|
||||
// },
|
||||
// component: _import('system/log/frontendLog')
|
||||
// },
|
||||
// 刷新页面 必须保留
|
||||
{
|
||||
path: 'refresh',
|
||||
|
@ -186,7 +186,17 @@ const frameOut = [
|
|||
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')
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 错误页面
|
||||
*/
|
||||
|
|
|
@ -36,39 +36,39 @@ export default {
|
|||
title: 'd2admin 经典',
|
||||
name: 'd2',
|
||||
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'
|
||||
// }
|
||||
]
|
||||
},
|
||||
// 是否默认开启页面切换动画
|
||||
|
|
|
@ -18,22 +18,20 @@ export default {
|
|||
/**
|
||||
* @description 登录
|
||||
* @param {Object} context
|
||||
* @param {Object} payload username {String} 用户账号
|
||||
* @param {Object} payload password {String} 密码
|
||||
* @param {Object} payload route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
|
||||
*/
|
||||
async login ({ dispatch }, {
|
||||
username = '',
|
||||
password = '',
|
||||
captcha = '',
|
||||
captchaKey = ''
|
||||
} = {}) {
|
||||
let res = await SYS_USER_LOGIN({
|
||||
username,
|
||||
password,
|
||||
captcha,
|
||||
captchaKey
|
||||
})
|
||||
* @param {Object} data
|
||||
* @param {Object} data username {String} 用户账号
|
||||
* @param {Object} data password {String} 密码
|
||||
* @param {Object} data route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
|
||||
* @param {Object} data request function 请求方法
|
||||
*/
|
||||
async login ({ dispatch }, data) {
|
||||
let request = data.request
|
||||
if (request) {
|
||||
delete data.request
|
||||
} else {
|
||||
request = SYS_USER_LOGIN
|
||||
}
|
||||
let res = await request(data)
|
||||
// 设置 cookie 一定要存 uuid 和 token 两个 cookie
|
||||
// 整个系统依赖这两个数据进行校验和存储
|
||||
// uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复
|
||||
|
@ -44,7 +42,7 @@ export default {
|
|||
util.cookies.set('token', res.access)
|
||||
util.cookies.set('refresh', res.refresh)
|
||||
// 设置 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')
|
||||
},
|
||||
|
|
|
@ -21,6 +21,7 @@ export default {
|
|||
console.log('演示地址:https://demo.django-vue-admin.com')
|
||||
console.log('社区地址:https://bbs.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,谢谢 ~')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
function importAll (r) {
|
||||
const __modules = []
|
||||
r.keys().forEach(key => {
|
||||
|
@ -8,10 +10,42 @@ function importAll (r) {
|
|||
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) {
|
||||
// 查找 src/views/plugins 目录所有插件,插件目录下需有 index.js 文件
|
||||
// 再查找 node_modules/@great-dream/ 目录下所有插件
|
||||
// 进行去重并vue注册导入
|
||||
if (window.pluginsAll) return
|
||||
let components = []
|
||||
components = components.concat(importAll(require.context('./', true, /index\.js$/)))
|
||||
components = components.concat(importAll(require.context('@great-dream/', true, /index\.js$/)))
|
||||
|
|
|
@ -78,8 +78,12 @@ export const crudOptions = (vm) => {
|
|||
disabled: false
|
||||
},
|
||||
width: 160,
|
||||
type: 'input'
|
||||
|
||||
type: 'input',
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入文件名称'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '文件地址',
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -12,14 +12,15 @@ export const crudOptions = (vm) => {
|
|||
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: 'right',
|
||||
view: {
|
||||
thin: true,
|
||||
text: '详情',
|
||||
text: '',
|
||||
disabled () {
|
||||
return !vm.hasPermissions('Retrieve')
|
||||
}
|
||||
},
|
||||
width: 160,
|
||||
width: 70,
|
||||
edit: {
|
||||
thin: true,
|
||||
text: '',
|
||||
|
@ -42,7 +43,12 @@ export const crudOptions = (vm) => {
|
|||
},
|
||||
formOptions: {
|
||||
disabled: true,
|
||||
defaultSpan: 24 // 默认的表单 span
|
||||
defaultSpan: 12 // 默认的表单 span
|
||||
},
|
||||
indexRow: { // 或者直接传true,不显示title,不居中
|
||||
title: '序号',
|
||||
align: 'center',
|
||||
width: 70
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
|
@ -106,11 +112,16 @@ export const crudOptions = (vm) => {
|
|||
disabled: true
|
||||
},
|
||||
disabled: true,
|
||||
width: 180,
|
||||
type: 'input',
|
||||
type: 'textarea',
|
||||
form: {
|
||||
disabled: true,
|
||||
component: {
|
||||
props: {
|
||||
type: 'textarea'
|
||||
},
|
||||
autosize: {
|
||||
minRows: 2, maxRows: 8
|
||||
},
|
||||
placeholder: '请输入关键词'
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +158,7 @@ export const crudOptions = (vm) => {
|
|||
search: {
|
||||
disabled: false
|
||||
},
|
||||
width: 100,
|
||||
width: 130,
|
||||
type: 'input',
|
||||
form: {
|
||||
disabled: true,
|
||||
|
@ -199,6 +210,7 @@ export const crudOptions = (vm) => {
|
|||
search: {
|
||||
disabled: true
|
||||
},
|
||||
minWidth: 240,
|
||||
type: 'input',
|
||||
form: {
|
||||
disabled: true
|
||||
|
@ -223,7 +235,8 @@ export const crudOptions = (vm) => {
|
|||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
fixed: 'right',
|
||||
title: '操作时间',
|
||||
key: 'create_datetime',
|
||||
width: 160,
|
||||
type: 'datetime',
|
||||
|
|
|
@ -57,11 +57,9 @@ export default {
|
|||
return api.GetList(query)
|
||||
},
|
||||
addRequest (row) {
|
||||
console.log('api', api)
|
||||
return api.AddObj(row)
|
||||
},
|
||||
updateRequest (row) {
|
||||
console.log('----', row)
|
||||
return api.UpdateObj(row)
|
||||
},
|
||||
delRequest (row) {
|
||||
|
|
|
@ -164,7 +164,7 @@ export const crudOptions = (vm) => {
|
|||
data: BUTTON_WHETHER_BOOL
|
||||
},
|
||||
form: {
|
||||
value: 0,
|
||||
value: false,
|
||||
component: {
|
||||
placeholder: '请选择是否管理员'
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ export const crudOptions = (vm) => {
|
|||
data: BUTTON_STATUS_BOOL
|
||||
},
|
||||
form: {
|
||||
value: 1,
|
||||
value: true,
|
||||
component: {
|
||||
placeholder: '请选择状态'
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
>
|
||||
<template slot="title">
|
||||
<div>
|
||||
当前角色<el-tag>管理员</el-tag>
|
||||
当前角色<el-tag>{{roleObj?roleObj.name:'无'}}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* 联系Qq:1638245306
|
||||
* @文件介绍: 用户接口
|
||||
*/
|
||||
import { request, downloadFile } from '@/api/service'
|
||||
import { request } from '@/api/service'
|
||||
|
||||
export const urlPrefix = '/api/system/user/'
|
||||
|
||||
|
@ -43,13 +43,15 @@ export function DelObj (id) {
|
|||
}
|
||||
|
||||
/**
|
||||
* 导出
|
||||
* @param params
|
||||
* 重置密码
|
||||
* @param id
|
||||
* @returns {*}
|
||||
* @constructor
|
||||
*/
|
||||
export function exportData (params) {
|
||||
return downloadFile({
|
||||
url: urlPrefix + 'export/',
|
||||
params: params,
|
||||
method: 'post'
|
||||
export function ResetPwd (obj) {
|
||||
return request({
|
||||
url: urlPrefix + 'reset_password/' + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ import { request } from '@/api/service'
|
|||
import { BUTTON_STATUS_BOOL } from '@/config/button'
|
||||
import { urlPrefix as deptPrefix } from '../dept/api'
|
||||
import util from '@/libs/util'
|
||||
const uploadUrl = util.baseURL() + 'api/system/img/'
|
||||
|
||||
const uploadUrl = util.baseURL() + 'api/system/file/'
|
||||
export const crudOptions = (vm) => {
|
||||
return {
|
||||
pageOptions: {
|
||||
|
@ -12,8 +13,8 @@ export const crudOptions = (vm) => {
|
|||
height: '100%'
|
||||
},
|
||||
rowHandle: {
|
||||
width: 140,
|
||||
fixed: 'right',
|
||||
width: 180,
|
||||
view: {
|
||||
thin: true,
|
||||
text: '',
|
||||
|
@ -34,7 +35,21 @@ export const crudOptions = (vm) => {
|
|||
disabled () {
|
||||
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: {
|
||||
componentType: 'form'
|
||||
|
@ -45,7 +60,7 @@ export const crudOptions = (vm) => {
|
|||
indexRow: { // 或者直接传true,不显示title,不居中
|
||||
title: '序号',
|
||||
align: 'center',
|
||||
width: 80
|
||||
width: 70
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
|
@ -81,7 +96,7 @@ export const crudOptions = (vm) => {
|
|||
search: {
|
||||
disabled: false
|
||||
},
|
||||
width: 160,
|
||||
width: 140,
|
||||
type: 'input',
|
||||
form: {
|
||||
rules: [ // 表单校验规则
|
||||
|
@ -95,7 +110,7 @@ export const crudOptions = (vm) => {
|
|||
},
|
||||
helper: {
|
||||
render (h) {
|
||||
return (< el-alert title="密码默认为:admin123456" type="warning" />
|
||||
return (< el-alert title="密码默认为:admin123456" type="warning"/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +120,6 @@ export const crudOptions = (vm) => {
|
|||
title: '姓名',
|
||||
key: 'name',
|
||||
search: {
|
||||
key: 'name__icontains',
|
||||
disabled: false
|
||||
},
|
||||
type: 'input',
|
||||
|
@ -124,18 +138,18 @@ export const crudOptions = (vm) => {
|
|||
},
|
||||
{
|
||||
title: '部门',
|
||||
width: 160,
|
||||
key: 'dept',
|
||||
search: {
|
||||
disabled: true
|
||||
},
|
||||
minWidth: 140,
|
||||
type: 'table-selector',
|
||||
dict: {
|
||||
cache: false,
|
||||
url: deptPrefix,
|
||||
value: 'id', // 数据字典中value字段的属性名
|
||||
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 => {
|
||||
component._elProps.page = ret.data.page
|
||||
component._elProps.limit = ret.data.limit
|
||||
|
@ -173,13 +187,14 @@ export const crudOptions = (vm) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
key: 'mobile',
|
||||
width: 120,
|
||||
search: {
|
||||
disabled: true
|
||||
},
|
||||
minWidth: 110,
|
||||
type: 'input',
|
||||
form: {
|
||||
rules: [
|
||||
|
@ -196,7 +211,7 @@ export const crudOptions = (vm) => {
|
|||
}, {
|
||||
title: '邮箱',
|
||||
key: 'email',
|
||||
width: 120,
|
||||
minWidth: 160,
|
||||
form: {
|
||||
rules: [
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
|
||||
|
@ -209,49 +224,26 @@ export const crudOptions = (vm) => {
|
|||
{
|
||||
title: '性别',
|
||||
key: 'gender',
|
||||
type: 'select',
|
||||
type: 'radio',
|
||||
width: 70,
|
||||
dict: {
|
||||
data: [{ label: '男', value: 1 }, { label: '女', value: 0 }]
|
||||
},
|
||||
form: {
|
||||
value: 1,
|
||||
rules: [
|
||||
{ required: true, message: '性别必填项' }
|
||||
],
|
||||
component: {
|
||||
span: 12
|
||||
},
|
||||
itemProps: {
|
||||
class: { yxtInput: true }
|
||||
}
|
||||
},
|
||||
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: '状态',
|
||||
key: 'is_active',
|
||||
search: {
|
||||
disabled: false
|
||||
},
|
||||
width: 90,
|
||||
width: 70,
|
||||
type: 'radio',
|
||||
dict: {
|
||||
data: BUTTON_STATUS_BOOL
|
||||
|
@ -267,23 +259,22 @@ export const crudOptions = (vm) => {
|
|||
title: '头像',
|
||||
key: 'avatar',
|
||||
type: 'avatar-uploader',
|
||||
width: 80,
|
||||
width: 100,
|
||||
align: 'left',
|
||||
form: {
|
||||
component: {
|
||||
props: {
|
||||
uploader: {
|
||||
action: uploadUrl,
|
||||
name: 'url',
|
||||
headers: {
|
||||
Authorization: 'JWT ' + util.cookies.get('token')
|
||||
},
|
||||
type: 'form',
|
||||
successHandle (ret, option) {
|
||||
if (ret.data == null || ret.data === '') {
|
||||
if (ret.data === null || ret.data === '') {
|
||||
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 配置一致
|
||||
|
@ -309,9 +300,8 @@ export const crudOptions = (vm) => {
|
|||
component: {
|
||||
props: {
|
||||
buildUrl (value, item) {
|
||||
console.log(11, value)
|
||||
if (value && value.indexOf('http') !== 0) {
|
||||
return '/api/upload/form/download?key=' + value
|
||||
return util.baseURL() + value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
@ -321,10 +311,10 @@ export const crudOptions = (vm) => {
|
|||
{
|
||||
title: '角色',
|
||||
key: 'role',
|
||||
width: 160,
|
||||
search: {
|
||||
disabled: true
|
||||
},
|
||||
minWidth: 130,
|
||||
type: 'table-selector',
|
||||
dict: {
|
||||
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 } }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<!--
|
||||
* @创建文件时间: 2021-06-01 22:41:21
|
||||
* @Auther: 猿小天
|
||||
* @最后修改人: 猿小天
|
||||
* @最后修改时间: 2021-07-29 19:27:10
|
||||
* 联系Qq:1638245306
|
||||
* @文件介绍: 用户管理
|
||||
-->
|
||||
<template>
|
||||
<d2-container :class="{ 'page-compact': crud.pageOptions.compact }">
|
||||
<d2-crud-x
|
||||
|
@ -13,6 +5,7 @@
|
|||
v-bind="_crudProps"
|
||||
v-on="_crudListeners"
|
||||
crud.options.tableType="vxe-table"
|
||||
@resetPwd="resetPwd"
|
||||
>
|
||||
<div slot="header">
|
||||
<crud-search
|
||||
|
@ -26,20 +19,8 @@
|
|||
v-permission="'Create'"
|
||||
type="primary"
|
||||
@click="addRow"
|
||||
><i class="el-icon-plus"/> 新增
|
||||
</el-button
|
||||
><i class="el-icon-plus" /> 新增</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>
|
||||
<crud-toolbar
|
||||
:search.sync="crud.searchOptions.show"
|
||||
|
@ -50,6 +31,20 @@
|
|||
/>
|
||||
</div>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
@ -60,10 +55,42 @@ import { d2CrudPlus } from 'd2-crud-plus'
|
|||
|
||||
export default {
|
||||
name: 'user',
|
||||
|
||||
mixins: [d2CrudPlus.crud],
|
||||
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: {
|
||||
getCrudOptions () {
|
||||
|
@ -76,19 +103,38 @@ export default {
|
|||
return api.AddObj(row)
|
||||
},
|
||||
updateRequest (row) {
|
||||
console.log('----', row)
|
||||
return api.UpdateObj(row)
|
||||
},
|
||||
delRequest (row) {
|
||||
return api.DelObj(row.id)
|
||||
},
|
||||
onExport () {
|
||||
this.$confirm('是否确认导出所有数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(function () {
|
||||
return api.exportData()
|
||||
// 重置密码弹框
|
||||
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('表单校验失败,请检查')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,10 @@ export const crudOptions = (vm) => {
|
|||
disabled: false
|
||||
},
|
||||
form: {
|
||||
disabled: true
|
||||
disabled: true,
|
||||
component: {
|
||||
placeholder: '请输入关键词'
|
||||
}
|
||||
},
|
||||
view: { // 查看对话框组件的单独配置
|
||||
disabled: true
|
||||
|
|