diff --git a/backend/dvadmin/system/urls.py b/backend/dvadmin/system/urls.py index 6b8c95f..59e65f2 100644 --- a/backend/dvadmin/system/urls.py +++ b/backend/dvadmin/system/urls.py @@ -30,8 +30,6 @@ system_url.register(r'system_config', SystemConfigViewSet) system_url.register(r'message_center',MessageCenterViewSet) urlpatterns = [ - path('user/export/', UserViewSet.as_view({'post': 'export_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//', SystemConfigViewSet.as_view({'get': 'get_table_data'})), diff --git a/backend/dvadmin/system/views/login.py b/backend/dvadmin/system/views/login.py index faaa2ea..184dba9 100644 --- a/backend/dvadmin/system/views/login.py +++ b/backend/dvadmin/system/views/login.py @@ -54,25 +54,39 @@ class LoginSerializer(TokenObtainPairSerializer): 登录的序列化器: 重写djangorestframework-simplejwt的序列化器 """ - captcha = serializers.CharField( max_length=6, required=False, allow_null=True, allow_blank=True ) - class Meta: model = Users fields = "__all__" read_only_fields = ["id"] - default_error_messages = {"no_active_account": _("账号/密码错误")} +class LoginView(TokenObtainPairView): + """ + 登录接口 + """ + serializer_class = LoginSerializer + permission_classes = [] + + def post(self, request, *args, **kwargs): + # username可能携带的不止是用户名,可能还是用户的其它唯一标识 手机号 邮箱 + username = request.data.get('username',None) + if username is None: + return ErrorResponse(msg="账号不能为空") + password = request.data.get('password',None) + if password is None: + return ErrorResponse(msg="密码不能为空") - def validate(self, attrs): - captcha = self.initial_data.get("captcha", None) if dispatch.get_system_config_values("base.captcha_state"): + captcha = request.data.get('captcha', None) + captchaKey = request.data.get('captchaKey', None) + if captchaKey is None: + return ErrorResponse(msg="验证码不能为空") if captcha is None: raise CustomValidationError("验证码不能为空") self.image_code = CaptchaStore.objects.filter( - id=self.initial_data["captchaKey"] + id=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: @@ -87,34 +101,36 @@ class LoginSerializer(TokenObtainPairSerializer): 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 - dept = getattr(self.user, 'dept', None) + try: + # 手动通过 user 签发 jwt-token + user = Users.objects.get(username=username) + except: + return ErrorResponse(msg='该账号未注册') + # 获得用户后,校验密码并签发token + if not user.check_password(password): + return ErrorResponse(msg='密码错误') + result = { + "name":user.name, + "userId":user.id, + "avatar":user.avatar, + } + dept = getattr(user, 'dept', None) if dept: - data['dept_info'] = { + result['dept_info'] = { 'dept_id': dept.id, 'dept_name': dept.name, 'dept_key': dept.key } - role = getattr(self.user, 'role', None) + role = getattr(user, 'role', None) if role: - data['role_info'] = role.values('id', 'name', 'key') - request = self.context.get("request") - request.user = self.user + result['role_info'] = role.values('id', 'name', 'key') + refresh = LoginSerializer.get_token(user) + result["refresh"] = str(refresh) + result["access"] = str(refresh.access_token) # 记录登录日志 + request.user = user save_login_log(request=request) - return {"code": 2000, "msg": "请求成功", "data": data} - - -class LoginView(TokenObtainPairView): - """ - 登录接口 - """ - - serializer_class = LoginSerializer - permission_classes = [] + return DetailResponse(data=result,msg="获取成功") class LoginTokenSerializer(TokenObtainPairSerializer): diff --git a/backend/dvadmin/system/views/login_log.py b/backend/dvadmin/system/views/login_log.py index 4dc3617..5853ecb 100644 --- a/backend/dvadmin/system/views/login_log.py +++ b/backend/dvadmin/system/views/login_log.py @@ -34,3 +34,4 @@ class LoginLogViewSet(CustomModelViewSet): queryset = LoginLog.objects.all() serializer_class = LoginLogSerializer extra_filter_backends = [] + ordering_fields = ['create_datetime'] diff --git a/backend/dvadmin/system/views/menu.py b/backend/dvadmin/system/views/menu.py index 16d60f4..b00086a 100644 --- a/backend/dvadmin/system/views/menu.py +++ b/backend/dvadmin/system/views/menu.py @@ -185,11 +185,11 @@ class MenuViewSet(CustomModelViewSet): parent = params.get('parent', None) if params: if parent: - queryset = self.queryset.filter(status=1, parent=parent) + queryset = self.queryset.filter(parent=parent) else: - queryset = self.queryset.filter(status=1) + queryset = self.queryset else: - queryset = self.queryset.filter(status=1, parent__isnull=True) + queryset = self.queryset.filter(parent__isnull=True) queryset = self.filter_queryset(queryset) serializer = MenuSerializer(queryset, many=True, request=request) data = serializer.data diff --git a/backend/dvadmin/system/views/message_center.py b/backend/dvadmin/system/views/message_center.py index 072db85..ad3c680 100644 --- a/backend/dvadmin/system/views/message_center.py +++ b/backend/dvadmin/system/views/message_center.py @@ -86,7 +86,9 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer): user_id = self.request.user.id message_center_id = instance.id queryset = MessageCenterTargetUser.objects.filter(messagecenter__id=message_center_id,users_id=user_id).first() - return queryset.is_read + if queryset: + return queryset.is_read + return False class Meta: model = MessageCenter @@ -121,12 +123,12 @@ class MessageCenterCreateSerializer(CustomModelSerializer): users = initial_data.get('target_user', []) if target_type in [1]: # 按角色 target_role = initial_data.get('target_role',[]) - users = Users.objects.exclude(is_deleted=True).filter(role__id__in=target_role).values_list('id', flat=True) + users = Users.objects.filter(role__id__in=target_role).values_list('id', flat=True) if target_type in [2]: # 按部门 target_dept = initial_data.get('target_dept',[]) - users = Users.objects.exclude(is_deleted=True).filter(dept__id__in=target_dept).values_list('id', flat=True) + users = Users.objects.filter(dept__id__in=target_dept).values_list('id', flat=True) if target_type in [3]: # 系统通知 - users = Users.objects.exclude(is_deleted=True).values_list('id', flat=True) + users = Users.objects.values_list('id', flat=True) targetuser_data = [] for user in users: targetuser_data.append({ @@ -211,6 +213,6 @@ class MessageCenterViewSet(CustomModelViewSet): queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('create_datetime').last() data = None if queryset: - serializer = MessageCenterTargetUserListSerializer(queryset, many=False, request=request) + serializer = MessageCenterTargetUserListSerializer(queryset.messagecenter, many=False, request=request) data = serializer.data return DetailResponse(data=data, msg="获取成功") diff --git a/backend/dvadmin/system/views/user.py b/backend/dvadmin/system/views/user.py index 046c2b6..dcc4d80 100644 --- a/backend/dvadmin/system/views/user.py +++ b/backend/dvadmin/system/views/user.py @@ -219,10 +219,12 @@ class ExportUserProfileSerializer(CustomModelSerializer): class UserProfileImportSerializer(CustomModelSerializer): + password = serializers.CharField(required=True, max_length=50, error_messages={"required": "登录密码不能为空"}) + def save(self, **kwargs): data = super().save(**kwargs) password = hashlib.new( - "md5", str(self.initial_data.get("password", "")).encode(encoding="UTF-8") + "md5", str(self.initial_data.get("password", "admin123456")).encode(encoding="UTF-8") ).hexdigest() data.set_password(password) data.save() diff --git a/backend/dvadmin/utils/pagination.py b/backend/dvadmin/utils/pagination.py index 1884c06..b702884 100644 --- a/backend/dvadmin/utils/pagination.py +++ b/backend/dvadmin/utils/pagination.py @@ -10,7 +10,7 @@ from collections import OrderedDict from django.core import paginator -from django.core.paginator import Paginator as DjangoPaginator +from django.core.paginator import Paginator as DjangoPaginator, InvalidPage from rest_framework.pagination import PageNumberPagination from rest_framework.response import Response @@ -21,13 +21,52 @@ class CustomPagination(PageNumberPagination): max_page_size = 999 django_paginator_class = DjangoPaginator + def paginate_queryset(self, queryset, request, view=None): + """ + Paginate a queryset if required, either returning a + page object, or `None` if pagination is not configured for this view. + """ + empty = True + + page_size = self.get_page_size(request) + if not page_size: + return None + + paginator = self.django_paginator_class(queryset, page_size) + page_number = request.query_params.get(self.page_query_param, 1) + if page_number in self.last_page_strings: + page_number = paginator.num_pages + + try: + self.page = paginator.page(page_number) + except InvalidPage as exc: + + # msg = self.invalid_page_message.format( + # page_number=page_number, message=str(exc) + # ) + # raise NotFound(msg) + empty = False + pass + + if paginator.num_pages > 1 and self.template is not None: + # The browsable API should display pagination controls. + self.display_page_controls = True + + self.request = request + + if not empty: + self.page = [] + + return list(self.page) def get_paginated_response(self, data): code = 2000 msg = 'success' res = { "page": int(self.get_page_number(self.request, paginator)) or 1, - "total": self.page.paginator.count, + "total": self.page.paginator.count if self.page else 0, "limit": int(self.get_page_size(self.request)) or 10, + "is_next": self.page.has_next(), + "is_previous": self.page.has_previous(), "data": data } if not data: @@ -38,6 +77,6 @@ class CustomPagination(PageNumberPagination): return Response(OrderedDict([ ('code', code), ('msg', msg), - # ('total',self.page.paginator.count), ('data', res), ])) + diff --git a/web/package.json b/web/package.json index 336ae62..dc88cd4 100644 --- a/web/package.json +++ b/web/package.json @@ -20,9 +20,9 @@ "china-division": "^2.4.0", "core-js": "^3.4.3", "cropperjs": "^1.5.6", - "d2-crud-plus": "^2.13.1", - "d2-crud-x": "^2.13.1", - "d2p-extends": "^2.13.1", + "d2-crud-plus": "^2.17.9", + "d2-crud-x": "^2.17.9", + "d2p-extends": "^2.17.9", "dayjs": "^1.8.17", "echarts": "^5.1.2", "el-phone-number-input": "^1.1.5", @@ -42,6 +42,7 @@ "sortablejs": "^1.10.1", "ua-parser-js": "^0.7.20", "vue": "2.7.14", + "vue-grid-layout": "^2.4.0", "vue-i18n": "^8.15.1", "vue-infinite-scroll": "^2.0.2", "vue-router": "^3.6.5", diff --git a/web/src/components/importExcel/index.vue b/web/src/components/importExcel/index.vue index 1f5241e..06ef430 100644 --- a/web/src/components/importExcel/index.vue +++ b/web/src/components/importExcel/index.vue @@ -118,7 +118,7 @@ export default { that.$refs.upload.clearFiles() // 是否更新已经存在的用户数据 return request({ - url: util.baseURL() + that.api + 'import_data/', + url: that.api + 'import_data/', method: 'post', data: { url: response.data.url diff --git a/web/src/install.js b/web/src/install.js index b20fa54..3bfc11e 100644 --- a/web/src/install.js +++ b/web/src/install.js @@ -62,7 +62,15 @@ Vue.use(d2CrudPlus, { page: { // page接口返回的数据结构配置, request: { current: 'page', - size: 'limit' + size: 'limit', + orderAsc (query, value) { + const field = query.orderProp + if (value) { + query.ordering = field + } else { + query.ordering = `-${field}` + } + } }, response: { current: 'page', // 当前页码 ret.data.current diff --git a/web/src/layout/header-aside/layout.vue b/web/src/layout/header-aside/layout.vue index 469c25d..11e082f 100644 --- a/web/src/layout/header-aside/layout.vue +++ b/web/src/layout/header-aside/layout.vue @@ -97,6 +97,12 @@ + +
+ Powered by Django-Vue-Admin + + Copyright dvadmin团队 +
@@ -208,4 +214,24 @@ export default { diff --git a/web/src/views/dashboard/workbench/components/about.vue b/web/src/views/dashboard/workbench/components/about.vue new file mode 100644 index 0000000..e8571d8 --- /dev/null +++ b/web/src/views/dashboard/workbench/components/about.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/web/src/views/dashboard/workbench/components/index.js b/web/src/views/dashboard/workbench/components/index.js new file mode 100644 index 0000000..4d5867f --- /dev/null +++ b/web/src/views/dashboard/workbench/components/index.js @@ -0,0 +1,13 @@ +import { markRaw } from 'vue' + +const resultComps = {} +const requireComponent = require.context( + './', // 在当前目录下查找 + false, // 不遍历子文件夹 + /\.vue$/ // 正则匹配 以 .vue结尾的文件 +) +requireComponent.keys().forEach(fileName => { + const comp = requireComponent(fileName) + resultComps[fileName.replace(/^\.\/(.*)\.\w+$/, '$1')] = comp.default +}) +export default markRaw(resultComps) diff --git a/web/src/views/dashboard/workbench/components/time.vue b/web/src/views/dashboard/workbench/components/time.vue new file mode 100644 index 0000000..d9ae761 --- /dev/null +++ b/web/src/views/dashboard/workbench/components/time.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/web/src/views/dashboard/workbench/components/ver.vue b/web/src/views/dashboard/workbench/components/ver.vue new file mode 100644 index 0000000..841a2ee --- /dev/null +++ b/web/src/views/dashboard/workbench/components/ver.vue @@ -0,0 +1,44 @@ + + + diff --git a/web/src/views/dashboard/workbench/components/welcome.vue b/web/src/views/dashboard/workbench/components/welcome.vue new file mode 100644 index 0000000..87229fb --- /dev/null +++ b/web/src/views/dashboard/workbench/components/welcome.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/web/src/views/dashboard/workbench/index.vue b/web/src/views/dashboard/workbench/index.vue index 9c15a8d..88e7227 100644 --- a/web/src/views/dashboard/workbench/index.vue +++ b/web/src/views/dashboard/workbench/index.vue @@ -1,313 +1,382 @@ - diff --git a/web/src/views/system/areas/crud.js b/web/src/views/system/areas/crud.js index c7a0092..7f71b32 100644 --- a/web/src/views/system/areas/crud.js +++ b/web/src/views/system/areas/crud.js @@ -29,34 +29,35 @@ export const crudOptions = (vm) => { iconLoaded: 'el-icon-loading' // 美化loading图标 } }, - rowHandle: { - show: false, - width: 140, - view: { - thin: true, - text: '', - show: false, - disabled () { - return !vm.hasPermissions('Retrieve') - } - }, - edit: { - thin: true, - text: '', - show: false, - disabled () { - return !vm.hasPermissions('Update') - } - }, - remove: { - thin: true, - text: '', - show: false, - disabled () { - return !vm.hasPermissions('Delete') - } - } - }, + // rowHandle: { + // show: false, + // width: 140, + // view: { + // thin: true, + // text: '', + // show: false, + // disabled () { + // return !vm.hasPermissions('Retrieve') + // } + // }, + // edit: { + // thin: true, + // text: '', + // show: false, + // disabled () { + // return !vm.hasPermissions('Update') + // } + // }, + // remove: { + // thin: true, + // text: '', + // show: false, + // disabled () { + // return !vm.hasPermissions('Delete') + // } + // } + // }, + rowHandle: false, viewOptions: { componentType: 'form' }, diff --git a/web/src/views/system/fileList/crud.js b/web/src/views/system/fileList/crud.js index b4f009c..754b3e6 100644 --- a/web/src/views/system/fileList/crud.js +++ b/web/src/views/system/fileList/crud.js @@ -15,13 +15,7 @@ export const crudOptions = (vm) => { return !vm.hasPermissions('Retrieve') } }, - edit: { - thin: true, - text: '', - disabled () { - return !vm.hasPermissions('Update') - } - }, + edit: false, remove: { thin: true, text: '', diff --git a/web/src/views/system/log/loginLog/crud.js b/web/src/views/system/log/loginLog/crud.js index 08a33a4..94dd3ab 100644 --- a/web/src/views/system/log/loginLog/crud.js +++ b/web/src/views/system/log/loginLog/crud.js @@ -4,12 +4,11 @@ export const crudOptions = (vm) => { compact: true }, options: { - tableType: 'vxe-table', - rowKey: true, // 必须设置,true or false + // tableType: 'vxe-table', + // rowKey: true, // 必须设置,true or false rowId: 'id', height: '100%', // 表格高度100%, 使用toolbar必须设置 - highlightCurrentRow: false - + highlightCurrentRow: false, }, rowHandle: { fixed: 'right', @@ -284,7 +283,8 @@ export const crudOptions = (vm) => { title: '登录时间', key: 'create_datetime', width: 160, - type: 'datetime' + type: 'datetime', + sortable: true } ] } diff --git a/web/src/views/system/messageCenter/crud.js b/web/src/views/system/messageCenter/crud.js index a2824c0..115863c 100644 --- a/web/src/views/system/messageCenter/crud.js +++ b/web/src/views/system/messageCenter/crud.js @@ -19,9 +19,7 @@ export const crudOptions = (vm) => { edit: { thin: true, text: '', - show () { - return vm.tabActivted !== 'receive' - }, + show: false, disabled () { return !vm.hasPermissions('Update') } diff --git a/web/src/views/system/user/crud.js b/web/src/views/system/user/crud.js index 88a2f9c..fa29cfb 100644 --- a/web/src/views/system/user/crud.js +++ b/web/src/views/system/user/crud.js @@ -9,8 +9,8 @@ export const crudOptions = (vm) => { }, options: { height: '100%', - tableType: 'vxe-table', - rowKey: true, + // tableType: 'vxe-table', + //rowKey: true, rowId: 'id' }, selectionRow: { @@ -149,6 +149,7 @@ export const crudOptions = (vm) => { { title: '姓名', key: 'name', + sortable: 'custom', minWidth: 90, search: { disabled: false