|
@ -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
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
### 访问项目
|
### 访问项目
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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详细地址)
|
||||||
|
|
|
@ -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, "菜单按钮表")
|
||||||
|
|
|
@ -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',)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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="获取成功")
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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": "请求成功",
|
||||||
|
|
|
@ -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 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="未获取到用户")
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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/; # 设置代理服务器的协议和地址
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: '' }
|
||||||
]
|
]
|
||||||
|
|
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: {
|
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 }) }]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 错误页面
|
* 错误页面
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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'
|
|
||||||
// }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// 是否默认开启页面切换动画
|
// 是否默认开启页面切换动画
|
||||||
|
|
|
@ -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')
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,谢谢 ~')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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$/)))
|
||||||
|
|
|
@ -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: '文件地址',
|
||||||
|
|
|
@ -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: {
|
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',
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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: '请选择状态'
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 } }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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('表单校验失败,请检查')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|