diff --git a/.gitignore b/.gitignore index 95d65ce2b9a71f4bb29af2f1f9c13231bc69e3b2..12bc2b3ee3e90884a6db97506c55488fbcb98dc4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ .history/ .vscode/ -web/package-lock.json \ No newline at end of file +web/package-lock.json + +*.bat \ No newline at end of file diff --git a/backend/dvadmin/system/fixtures/init_menu.json b/backend/dvadmin/system/fixtures/init_menu.json index 835a41e2bb214bea6bce6d2097b20e538fe43195..ec9742aa7ee793dfef84736611f102781cba0610 100644 --- a/backend/dvadmin/system/fixtures/init_menu.json +++ b/backend/dvadmin/system/fixtures/init_menu.json @@ -11,12 +11,164 @@ "status": true, "cache": false, "visible": true, - "parent": null, "children": [ + { + "name": "用户管理", + "icon": "iconfont icon-icon-", + "sort": 1, + "is_link": false, + "is_catalog": false, + "web_path": "/user", + "component": "system/user/index", + "component_name": "user", + "status": true, + "cache": false, + "visible": true, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "user:Search", + "api": "/api/system/user/", + "method": 0 + }, + { + "name": "新增", + "value": "user:Create", + "api": "/api/system/user/", + "method": 1 + }, + { + "name": "编辑", + "value": "user:Update", + "api": "/api/system/user/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "user:Delete", + "api": "/api/system/user/{id}/", + "method": 3 + }, + { + "name": "导出", + "value": "user:Export", + "api": "/api/system/user/export/", + "method": 1 + }, + { + "name": "导入", + "value": "user:Import", + "api": "/api/system/user/import/", + "method": 1 + }, + { + "name": "获取导入模板", + "value": "user:ImportTemplate", + "api": "/api/system/user/import/", + "method": 0 + }, + { + "name": "批量更新模板", + "value": "user:BatchUpdateTemplate", + "api": "/api/system/user/update_template/", + "method": 0 + }, + { + "name": "重设密码", + "value": "user:ResetPassword", + "api": "/api/system/user/{id}/reset_password/", + "method": 2 + }, + { + "name": "重置密码", + "value": "user:ResetDefaultPassword", + "api": "/api/system/user/{id}/reset_to_default_password/", + "method": 2 + } + ], + "menu_field": [ + { + "field_name": "avatar", + "title": "头像", + "model": "Users" + }, + { + "field_name": "create_datetime", + "title": "创建时间", + "model": "Users" + }, + { + "field_name": "creator", + "title": "创建人", + "model": "Users" + }, + { + "field_name": "dept", + "title": "所属部门", + "model": "Users" + }, + { + "field_name": "dept_belong_id", + "title": "数据归属部门", + "model": "Users" + }, + { + "field_name": "description", + "title": "描述", + "model": "Users" + }, + { + "field_name": "email", + "title": "邮箱", + "model": "Users" + }, + { + "field_name": "gender", + "title": "性别", + "model": "Users" + }, + { + "field_name": "id", + "title": "Id", + "model": "Users" + }, + { + "field_name": "mobile", + "title": "电话", + "model": "Users" + }, + { + "field_name": "modifier", + "title": "修改人", + "model": "Users" + }, + { + "field_name": "name", + "title": "姓名", + "model": "Users" + }, + { + "field_name": "update_datetime", + "title": "修改时间", + "model": "Users" + }, + { + "field_name": "username", + "title": "用户账号", + "model": "Users" + }, + { + "field_name": "user_type", + "title": "用户类型", + "model": "Users" + } + ] + }, { "name": "菜单管理", "icon": "iconfont icon-caidan", - "sort": 1, + "sort": 2, "is_link": false, "is_catalog": false, "web_path": "/menu", @@ -25,7 +177,6 @@ "status": true, "cache": false, "visible": true, - "parent": 1, "children": [], "menu_button": [ { @@ -35,11 +186,29 @@ "method": 0 }, { - "name": "详情", + "name": "单例", "value": "menu:Retrieve", "api": "/api/system/menu/{id}/", "method": 0 }, + { + "name": "新增", + "value": "menu:Create", + "api": "/api/system/menu/", + "method": 1 + }, + { + "name": "编辑", + "value": "menu:Update", + "api": "/api/system/menu/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "menu:Delete", + "api": "/api/system/menu/{id}/", + "method": 3 + }, { "name": "查询所有", "value": "menu:SearchAll", @@ -53,22 +222,28 @@ "method": 0 }, { - "name": "查询按钮权限", - "value": "btn:Search", + "name": "查询按钮", + "value": "menu:SearchButton", "api": "/api/system/menu_button/", "method": 0 }, { - "name": "查询列权限", - "value": "column:Search", - "api": "/api/system/column/", - "method": 0 + "name": "新增按钮", + "value": "menu:CreateButton", + "api": "/api/system/menu_button/", + "method": 1 }, { - "name": "新增", - "value": "menu:Create", - "api": "/api/system/menu/", - "method": 1 + "name": "编辑按钮", + "value": "menu:UpdateButton", + "api": "/api/system/menu_button/{id}/", + "method": 2 + }, + { + "name": "删除按钮", + "value": "menu:DeleteButton", + "api": "/api/system/menu_button/{id}/", + "method": 3 }, { "name": "上移", @@ -83,10 +258,10 @@ "method": 1 }, { - "name": "新增按钮权限", - "value": "btn:Create", - "api": "/api/system/menu_button/", - "method": 1 + "name": "查询列权限", + "value": "column:Search", + "api": "/api/system/column/", + "method": 0 }, { "name": "新增列权限", @@ -94,47 +269,23 @@ "api": "/api/system/column/", "method": 1 }, - { - "name": "自动匹配列权限", - "value": "column:Match", - "api": "/api/system/column/auto_match_fields/", - "method": 1 - }, - { - "name": "编辑", - "value": "menu:Update", - "api": "/api/system/menu/{id}/", - "method": 2 - }, - { - "name": "修改按钮权限", - "value": "btn:Update", - "api": "/api/system/menu_button/{id}/", - "method": 2 - }, { "name": "编辑列权限", "value": "column:Update", "api": "/api/system/column/{id}/", "method": 2 }, - { - "name": "删除", - "value": "menu:Delete", - "api": "/api/system/menu/{id}/", - "method": 3 - }, - { - "name": "删除按钮权限", - "value": "btn:Delete", - "api": "/api/system/menu_button/{id}/", - "method": 3 - }, { "name": "删除列权限", "value": "column:Delete", "api": "/api/system/column/{id}/", "method": 3 + }, + { + "name": "自动匹配列权限", + "value": "column:Match", + "api": "/api/system/column/auto_match_fields/", + "method": 1 } ], "menu_field": [] @@ -151,7 +302,6 @@ "status": true, "cache": false, "visible": true, - "parent": 1, "children": [], "menu_button": [ { @@ -223,7 +373,6 @@ "status": true, "cache": false, "visible": true, - "parent": 1, "children": [], "menu_button": [ { @@ -233,17 +382,11 @@ "method": 0 }, { - "name": "详情", + "name": "单例", "value": "role:Retrieve", "api": "/api/system/role/{id}/", "method": 0 }, - { - "name": "权限配置", - "value": "role:Permission", - "api": "/api/system/role/{id}/", - "method": 0 - }, { "name": "新增", "value": "role:Create", @@ -256,222 +399,122 @@ "api": "/api/system/role/{id}/", "method": 2 }, - { - "name": "保存", - "value": "role:Save", - "api": "/api/system/role/{id}/", - "method": 2 - }, { "name": "删除", "value": "role:Delete", "api": "/api/system/role/{id}/", "method": 3 - } - ], - "menu_field": [ - { - "field_name": "create_datetime", - "title": "创建时间", - "model": "Role" - }, - { - "field_name": "creator", - "title": "创建人", - "model": "Role" - }, - { - "field_name": "dept_belong_id", - "title": "数据归属部门", - "model": "Role" - }, - { - "field_name": "description", - "title": "描述", - "model": "Role" - }, - { - "field_name": "id", - "title": "Id", - "model": "Role" - }, - { - "field_name": "key", - "title": "权限字符", - "model": "Role" - }, - { - "field_name": "modifier", - "title": "修改人", - "model": "Role" }, { - "field_name": "name", - "title": "角色名称", - "model": "Role" - }, - { - "field_name": "sort", - "title": "角色顺序", - "model": "Role" - }, - { - "field_name": "status", - "title": "角色状态", - "model": "Role" - }, - { - "field_name": "update_datetime", - "title": "修改时间", - "model": "Role" - } - ] - }, - { - "name": "用户管理", - "icon": "iconfont icon-icon-", - "sort": 6, - "is_link": false, - "is_catalog": false, - "web_path": "/user", - "component": "system/user/index", - "component_name": "user", - "status": true, - "cache": false, - "visible": true, - "parent": 1, - "children": [], - "menu_button": [ - { - "name": "查询", - "value": "user:Search", - "api": "/api/system/user/", + "name": "获取所有可授权数据范围的部门", + "value": "role:AllDataRangeDept", + "api": "/api/system/role_menu_button_permision/role_to_dept_all/", "method": 0 }, { - "name": "详情", - "value": "user:Retrieve", - "api": "/api/system/user/{id}/", + "name": "获取所有可授权菜单", + "value": "role:AllCanMenu", + "api": "/api/system/role_menu_button_permision/get_role_menu/", "method": 0 }, { - "name": "新增", - "value": "user:Create", - "api": "/api/system/user/", - "method": 1 + "name": "获取所有已授权用户", + "value": "role:AllAuthorizedUser", + "api": "/api/system/role/get_role_users/", + "method": 0 }, { - "name": "导出", - "value": "user:Export", - "api": "/api/system/user/export/", - "method": 1 + "name": "获取菜单所有可授权按钮", + "value": "role:AllMenuButton", + "api": "/api/system/role_menu_button_permision/get_role_menu_btn_field/", + "method": 0 }, { - "name": "导入", - "value": "user:Import", - "api": "/api/system/user/import/", - "method": 1 + "name": "授权菜单", + "value": "role:SetMenu", + "api": "/api/system/role_menu_button_permision/set_role_menu/", + "method": 2 }, { - "name": "编辑", - "value": "user:Update", - "api": "/api/system/user/{id}/", + "name": "授权菜单按钮", + "value": "role:SetMenuButton", + "api": "/api/system/role_menu_button_permision/set_role_menu_btn/", "method": 2 }, { - "name": "重设密码", - "value": "user:ResetPassword", - "api": "/api/system/user/{id}/reset_password/", + "name": "授权数据范围", + "value": "role:SetDataRange", + "api": "/api/system/role_menu_button_permision/set_role_menu_btn_data_range/", "method": 2 }, { - "name": "重置密码", - "value": "user:DefaultPassword", - "api": "/api/system/user/{id}/reset_to_default_password/", - "method": 2 + "name": "获取所有用户", + "value": "role:AllUser", + "api": "/api/system/user/", + "method": 0 }, { - "name": "删除", - "value": "user:Delete", - "api": "/api/system/user/{id}/", - "method": 3 + "name": "授权用户予角色", + "value": "role:SetUserRole", + "api": "/api/system/role/{id}/set_role_users/", + "method": 2 } ], "menu_field": [ - { - "field_name": "avatar", - "title": "头像", - "model": "Users" - }, { "field_name": "create_datetime", "title": "创建时间", - "model": "Users" + "model": "Role" }, { "field_name": "creator", "title": "创建人", - "model": "Users" - }, - { - "field_name": "dept", - "title": "所属部门", - "model": "Users" + "model": "Role" }, { "field_name": "dept_belong_id", "title": "数据归属部门", - "model": "Users" + "model": "Role" }, { "field_name": "description", "title": "描述", - "model": "Users" - }, - { - "field_name": "email", - "title": "邮箱", - "model": "Users" - }, - { - "field_name": "gender", - "title": "性别", - "model": "Users" + "model": "Role" }, { "field_name": "id", "title": "Id", - "model": "Users" + "model": "Role" }, { - "field_name": "mobile", - "title": "电话", - "model": "Users" + "field_name": "key", + "title": "权限字符", + "model": "Role" }, { "field_name": "modifier", "title": "修改人", - "model": "Users" + "model": "Role" }, { "field_name": "name", - "title": "姓名", - "model": "Users" + "title": "角色名称", + "model": "Role" }, { - "field_name": "update_datetime", - "title": "修改时间", - "model": "Users" + "field_name": "sort", + "title": "角色顺序", + "model": "Role" }, { - "field_name": "username", - "title": "用户账号", - "model": "Users" + "field_name": "status", + "title": "角色状态", + "model": "Role" }, { - "field_name": "user_type", - "title": "用户类型", - "model": "Users" + "field_name": "update_datetime", + "title": "修改时间", + "model": "Role" } ] }, @@ -690,35 +733,11 @@ "menu_button": [ { "name": "查询", - "value": "Search", - "api": "/api/system/downloadCenter/", - "method": 0 - }, - { - "name": "详情", - "value": "Retrieve", - "api": "/api/system/downloadCenter/{id}/", - "method": 0 - }, - { - "name": "新增", - "value": "Create", - "api": "/api/system/downloadCenter/", - "method": 1 - }, - { - "name": "编辑", - "value": "Update", - "api": "/api/system/downloadCenter/{id}/", - "method": 2 - }, - { - "name": "删除", - "value": "Delete", - "api": "/api/system/downloadCenter/{id}/", - "method": 3 + "value": "downloadCenter:Search", + "api": "/api/system/download_center/" } - ] + ], + "menu_field": [] } ], "menu_button": [], diff --git a/backend/dvadmin/system/models.py b/backend/dvadmin/system/models.py index b6fe27980ea63fdf6ddf8b179ba03313e5a2138b..7a71c67ed8d2b7e9c651731d71777b26b6107869 100644 --- a/backend/dvadmin/system/models.py +++ b/backend/dvadmin/system/models.py @@ -63,6 +63,8 @@ class Users(CoreModel, AbstractUser): help_text="关联岗位") role = models.ManyToManyField(to="Role", blank=True, verbose_name="关联角色", db_constraint=False, help_text="关联角色") + current_role = models.ForeignKey(to=Role, null=True, blank=True, db_constraint=False, on_delete=models.SET_NULL, + verbose_name="当前登录角色", help_text="当前登录角色", related_name='current_role_set') dept = models.ForeignKey( to="Dept", verbose_name="所属部门", @@ -72,6 +74,14 @@ class Users(CoreModel, AbstractUser): blank=True, help_text="关联部门", ) + manage_dept = models.ManyToManyField( + to="Dept", + verbose_name="管理部门", + db_constraint=False, + blank=True, + help_text="管理部门", + related_name='manage_dept_set' + ) login_error_count = models.IntegerField(default=0, verbose_name="登录错误次数", help_text="登录错误次数") pwd_change_count = models.IntegerField(default=0,blank=True, verbose_name="密码修改次数", help_text="密码修改次数") objects = CustomUserManager() diff --git a/backend/dvadmin/system/views/user.py b/backend/dvadmin/system/views/user.py index c31540c22c98a1497a7c684b2608e33a1945f6cf..4e09f90da9c46067c348ffacef3641506f3ec9e0 100644 --- a/backend/dvadmin/system/views/user.py +++ b/backend/dvadmin/system/views/user.py @@ -90,6 +90,8 @@ class UserCreateSerializer(CustomModelSerializer): data = super().save(**kwargs) data.dept_belong_id = data.dept_id data.save() + if not self.validated_data.get('manage_dept', None): + data.manage_dept.add(data.dept_id) data.post.set(self.initial_data.get("post", [])) return data @@ -127,6 +129,8 @@ class UserUpdateSerializer(CustomModelSerializer): data = super().save(**kwargs) data.dept_belong_id = data.dept_id data.save() + if not self.validated_data.get('manage_dept', None): + data.manage_dept.add(data.dept_id) data.post.set(self.initial_data.get("post", [])) return data @@ -426,12 +430,9 @@ class UserViewSet(CustomModelViewSet): queryset = self.filter_queryset(self.get_queryset()) else: queryset = self.filter_queryset(self.get_queryset()) - # print(queryset.values('id','name','dept__id')) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True, request=request) - # print(serializer.data) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True, request=request) - return SuccessResponse(data=serializer.data, msg="获取成功") diff --git a/backend/dvadmin/utils/filters.py b/backend/dvadmin/utils/filters.py index f61fc62ed214d878693ee6f936b07209e331b101..f62de0c211fd0d583c3da8864d5b541676ced328 100644 --- a/backend/dvadmin/utils/filters.py +++ b/backend/dvadmin/utils/filters.py @@ -15,15 +15,16 @@ import six from django.db import models from django.db.models import Q, F from django.db.models.constants import LOOKUP_SEP -from django_filters import utils, FilterSet from django_filters.constants import ALL_FIELDS -from django_filters.filters import CharFilter, DateTimeFromToRangeFilter from django_filters.rest_framework import DjangoFilterBackend -from django_filters.utils import get_model_field +from django_filters.utils import get_model_field, translate_validation, deprecate +from rest_framework.request import Request from rest_framework.filters import BaseFilterBackend from django_filters.conf import settings -from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission, MenuButton -from dvadmin.utils.models import CoreModel + +from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission, MenuButton, Users +from util.currency import recursion_down_fast + class CoreModelFilterBankend(BaseFilterBackend): """ @@ -200,6 +201,34 @@ class DataLevelPermissionsFilter(BaseFilterBackend): return queryset.filter(dept_belong_id__in=list(set(dept_list))) +class DataLevelPermissionsSubFilter(DataLevelPermissionsFilter): + """数据级权限过滤的子过滤器,过滤管理部门字段manage_dept""" + + def _extracted_from_filter_queryset_33(self, request:Request, queryset, api, method): + u:Users = request.user + if u.is_superuser: + return queryset + dept_list = [] + # 自己部门 交 管理部门 + if u.manage_dept.exists(): # 兼容旧数据 + for dept in u.manage_dept.all(): + dept_list.extend(recursion_down_fast(dept, 'parent', 'id')) + else: + dept_list = recursion_down_fast(u.dept, 'parent', 'id') + dept_list = set(recursion_down_fast(u.dept)) & set(dept_list) + # 自己创建的数据要能看到 + # 应对归属a管b、c等情况,如果自己创建数据则是a,不显式指定自己的数据就查不到 + if queryset.model._meta.model_name == 'dept': + return queryset.filter(Q(id__in=dept_list) | Q(creator=u)) + return queryset.filter(Q(dept_belong_id__in=dept_list) | Q(creator=u)) + + +class DataLevelPermissionMargeFilter(DataLevelPermissionsFilter): + def _extracted_from_filter_queryset_33(self, request, queryset, api, method): + queryset = super()._extracted_from_filter_queryset_33(request, queryset, api, method) + return DataLevelPermissionsSubFilter._extracted_from_filter_queryset_33(self, request, queryset, api, method) + + class CustomDjangoFilterBackend(DjangoFilterBackend): lookup_prefixes = { "^": "istartswith", @@ -240,14 +269,14 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): # TODO: remove assertion in 2.1 if filterset_class is None and hasattr(view, "filter_class"): - utils.deprecate( + deprecate( "`%s.filter_class` attribute should be renamed `filterset_class`." % view.__class__.__name__ ) filterset_class = getattr(view, "filter_class", None) # TODO: remove assertion in 2.1 if filterset_fields is None and hasattr(view, "filter_fields"): - utils.deprecate( + deprecate( "`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__ ) self.filter_fields = getattr(view, "filter_fields", None) @@ -427,5 +456,5 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): return queryset if not filterset.is_valid() and self.raise_exception: - raise utils.translate_validation(filterset.errors) + raise translate_validation(filterset.errors) return filterset.qs diff --git a/backend/dvadmin/utils/viewset.py b/backend/dvadmin/utils/viewset.py index 42948b1afb7a80fbe6fd1e8175a6345157365598..43160e696c7d36ac9f629e985a5fe19d3402a63b 100644 --- a/backend/dvadmin/utils/viewset.py +++ b/backend/dvadmin/utils/viewset.py @@ -16,7 +16,7 @@ from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework.viewsets import ModelViewSet -from dvadmin.utils.filters import DataLevelPermissionsFilter, CoreModelFilterBankend +from dvadmin.utils.filters import CoreModelFilterBankend, DataLevelPermissionMargeFilter from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSerializerMixin from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse from dvadmin.utils.permission import CustomPermission @@ -41,7 +41,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi update_serializer_class = None filter_fields = '__all__' search_fields = () - extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionsFilter] + extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionMargeFilter] permission_classes = [CustomPermission] import_field_dict = {} export_field_label = {} @@ -152,3 +152,13 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi return SuccessResponse(data=[], msg="删除成功") else: return ErrorResponse(msg="未获取到keys字段") + + @action(methods=['post'], detail=False) + def get_by_ids(self, request): + """通过IDS列表获取数据""" + ids = request.data.get('ids', []) + if ids and ids != ['']: + queryset = self.get_queryset().filter(id__in=ids) + serializer = self.get_serializer(queryset, many=True) + return DetailResponse(data=serializer.data) + return DetailResponse(data=None) diff --git a/backend/templates/terms_service.html b/backend/templates/terms_service.html index cef7e386f498364ef67303a1b64ac2803b4c4b5a..566cd8663a1fe3c9e419c30040e2102146cd6448 100644 --- a/backend/templates/terms_service.html +++ b/backend/templates/terms_service.html @@ -82,7 +82,7 @@

五、巨梦科技企业客户服务说明

- 1、巨梦科技平台提供给多家企业客户使用,企业客户通过巨梦科技平台进行发布用户活动等。如果功夫企火星企业用户违反了隐私政策或发生其它侵犯用户权益的行为,用户可要求巨梦科技提供必要的配合与巨梦科技企业用户进行沟通和维权,维权过程中产生的费用由用户自行承担。

+ 1、巨梦科技平台提供给多家企业客户使用,企业客户通过巨梦科技平台进行发布用户活动等。如果企业用户违反了隐私政策或发生其它侵犯用户权益的行为,用户可要求巨梦科技提供必要的配合与巨梦科技企业用户进行沟通和维权,维权过程中产生的费用由用户自行承担。

六、其他事宜

diff --git a/backend/util/currency.py b/backend/util/currency.py new file mode 100644 index 0000000000000000000000000000000000000000..fcd1c28a6acab38b3de1aa07dd941fd0f355d30c --- /dev/null +++ b/backend/util/currency.py @@ -0,0 +1,78 @@ +from uuid import uuid4 +from datetime import datetime + +from django.db import connection +from django.db.models import Model +from django.core.cache import cache + + +def create_code(model,prefix): + current_date = datetime.now().strftime('%Y%m%d%H%M%S') + code = f"{prefix}{current_date}" + str(uuid4().int)[:6] + return code + +def lock(key): + if callable(key): # @lock + def inner(*args, **kwargs): + with cache.lock(key='lock'): + return key(*args, **kwargs) + inner.__name__ = key.__name__ + else: # @lock(key='aaa') + def inner(func): + def _inner(*args, **kwargs): + with cache.lock(key=key): + return func(*args, **kwargs) + _inner.__name__ = func.__name__ + return _inner + return inner + +def recursion_down_fast(instance:Model, parent='parent', key='id') -> list[int]: + """向下递归instance的所有子级,且返回一维列表,使用sql优化,速度非常快""" + if not instance: + return [] + sql = f""" + WITH RECURSIVE children AS ( + SELECT id, {key} AS param_{key} FROM {instance.__class__._meta.db_table} WHERE {parent}_id = %s UNION ALL + SELECT a.id, a.{key} AS param_{key} FROM {instance.__class__._meta.db_table} a + INNER JOIN children b ON a.{parent}_id = b.id + ) SELECT param_{key} FROM children; + """ + with connection.cursor() as cursor: + cursor.execute(sql, [getattr(instance, key)]) + data = cursor.fetchall() + return [getattr(instance, key), *[i[0] for i in data]] + +def recursion_up_fast(instance: Model, parent='parent', key='id') -> list[int]: + """向上递归instance的所有父级,使用sql优化,速度非常快""" + if not instance: + return [] + sql = f""" + WITH RECURSIVE parents AS ( + SELECT id, {key} as param_{key}, {parent}_id FROM {instance.__class__._meta.db_table} WHERE id = %s UNION ALL + SELECT a.id, a.{key} as param_{key}, a.{parent}_id FROM {instance.__class__._meta.db_table} a + INNER JOIN parents b ON a.id = b.{parent}_id + ) SELECT param_{key} FROM parents; + """ + with connection.cursor() as cursor: + cursor.execute(sql, [getattr(instance, key)]) + data = cursor.fetchall() + return [i[0] for i in data] + +def recursion_up_joint(instance: Model, parent='parent', key='name', joint='/') -> str: + """向上递归instance所有父级并拼接需要的值,返回完整路径,使用sql优化,速度非常快""" + if instance is None: + return '' + sql = f""" + WITH RECURSIVE parents AS ( + SELECT id, {parent}_id, {key}::TEXT AS path FROM {instance.__class__._meta.db_table} WHERE {key} = %s AND id = %s UNION ALL + SELECT a.id, a.{parent}_id, (a.{key} || '{joint}' || b.path)::TEXT FROM {instance.__class__._meta.db_table} a + INNER JOIN parents b ON a.id = b.{parent}_id + ) SELECT path FROM parents where {parent}_id IS NULL; + """ + with connection.cursor() as cursor: + cursor.execute(sql, [getattr(instance, key), instance.pk]) + data = cursor.fetchall() + try: + return data[0][0] + except IndexError: + raise Exception('找不到初始数据') \ No newline at end of file diff --git a/web/package.json b/web/package.json index 73981a4e18f4820125fb072a265e6fc740f6fcb9..2a57a86c02822559e4bc734052f2e61533f52495 100644 --- a/web/package.json +++ b/web/package.json @@ -8,7 +8,8 @@ "build:dev": "vite build --mode development", "build": "vite build", "build:local": "vite build --mode local_prod", - "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/" + "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/", + "build:flowH5": "vite build --config flowH5.config.ts" }, "dependencies": { "@element-plus/icons-vue": "^2.3.1", @@ -18,6 +19,7 @@ "@fast-crud/ui-interface": "^1.21.2", "@great-dream/dvadmin3-celery-web": "^3.1.3", "@iconify/vue": "^4.1.2", + "@meetjs/vant4-kit": "^1.0.1", "@types/lodash": "^4.17.7", "@vitejs/plugin-vue-jsx": "^4.0.1", "@wangeditor/editor": "^5.1.23", @@ -48,6 +50,8 @@ "print-js": "^1.6.0", "qrcodejs2-fixes": "^0.0.2", "qs": "^6.11.0", + "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-terser": "^7.0.2", "screenfull": "^6.0.2", "sortablejs": "^1.15.0", "splitpanes": "^3.1.5", @@ -62,6 +66,7 @@ "vue-draggable-plus": "^0.6.0", "vue-grid-layout": "^3.0.0-beta1", "vue-i18n": "^9.14.0", + "vue-qr": "^4.0.9", "vue-router": "^4.4.3", "vxe-table": "^4.6.18", "xe-utils": "^3.5.30" diff --git a/web/src/components/calendar/index.vue b/web/src/components/calendar/index.vue index 9c44e60174037981be2c52b2f71d829007f1d26a..911051aea1ce3d64be58d7c361004b143ba76527 100644 --- a/web/src/components/calendar/index.vue +++ b/web/src/components/calendar/index.vue @@ -1,403 +1,402 @@ - - - - - \ No newline at end of file +} + \ No newline at end of file diff --git a/web/src/layout/navBars/breadcrumb/user.vue b/web/src/layout/navBars/breadcrumb/user.vue index 92e68c6c7a7dfc4c46fcba1f815797f34f516432..5f167dc4f2182eb520b719b8279acd200cb1a9bf 100644 --- a/web/src/layout/navBars/breadcrumb/user.vue +++ b/web/src/layout/navBars/breadcrumb/user.vue @@ -60,11 +60,9 @@
- - - - - + + + {{ userInfos.username === '' ? 'common' : userInfos.username }} @@ -125,9 +123,6 @@ const layoutUserFlexNum = computed(() => { return num; }); -// 定义变量内容 -const { isSocketOpen } = storeToRefs(useUserInfo()); - // 全屏点击时 const onScreenfullClick = () => { if (!screenfull.isEnabled) { @@ -226,12 +221,21 @@ import { getBaseURL } from '/@/utils/baseUrl'; const messageCenter = messageCenterStore(); let eventSource: EventSource | null = null; // 存储 EventSource 实例 const token = Session.get('token'); +const isConnected = ref(false); // 标志变量,记录是否已连接过 const getMessageCenterCount = () => { // 创建 EventSource 实例并连接到后端 SSE 端点 - eventSource = new EventSource(`${getBaseURL()}/sse/?token=${token}`); // 替换为你的后端地址 - + eventSource = new EventSource(`${getBaseURL()}sse/?token=${token}`); // 替换为你的后端地址 + // 首次连接成功时打印一次 + eventSource.onopen = function () { + if (!isConnected.value) { + console.log('SSE 首次连接成功'); + isConnected.value = true; // 设置标志为已连接 + } + }; // 监听消息事件 eventSource.onmessage = function (event) { + console.log(event.data); + messageCenter.setUnread(+event.data); // 更新总记录数 }; diff --git a/web/src/layout/navBars/breadcrumb/userNews.vue b/web/src/layout/navBars/breadcrumb/userNews.vue index aa1b067d4a97fb871e7ba1d2f17088f70c833036..c51d477732bc99fa8a57ea8122689fa94701c7a3 100644 --- a/web/src/layout/navBars/breadcrumb/userNews.vue +++ b/web/src/layout/navBars/breadcrumb/userNews.vue @@ -48,9 +48,8 @@ const getLastMsg = () => { params: {}, }).then((res: any) => { const { data } = res; - console.log(data); + if (data) state.newsList = [data]; - state.newsList = [data]; }); }; onMounted(() => { diff --git a/web/src/stores/interface/index.ts b/web/src/stores/interface/index.ts index 9f73c9212b370fc1118808a5d05c6f24590dfd7f..5083cf45b02138041181045d0a93dd07e2fba24f 100644 --- a/web/src/stores/interface/index.ts +++ b/web/src/stores/interface/index.ts @@ -23,7 +23,6 @@ export interface UserInfosState { } export interface UserInfosStates { userInfos: UserInfosState; - isSocketOpen: boolean } // 路由缓存列表 diff --git a/web/src/stores/userInfo.ts b/web/src/stores/userInfo.ts index a33d8cb79395bcc2a373699b018defd6f9fcb22c..bdaf4bc2a436173027fec2925437cef386b10e17 100644 --- a/web/src/stores/userInfo.ts +++ b/web/src/stores/userInfo.ts @@ -32,7 +32,6 @@ export const useUserInfo = defineStore('userInfo', { }, ], }, - isSocketOpen: false }), actions: { async setPwdChangeCount(count: number) { diff --git a/web/src/theme/element.scss b/web/src/theme/element.scss index 6cdebc9ac34229a9da32b2ac9a3c7bb7d508c4f1..741d731ca2acf3fa0d407783fd999c244c822c59 100644 --- a/web/src/theme/element.scss +++ b/web/src/theme/element.scss @@ -1,4 +1,4 @@ -@import 'mixins/index.scss'; +@use 'mixins/index.scss' as index; /* Button 按钮 ------------------------------- */ @@ -100,7 +100,7 @@ .el-sub-menu .iconfont, .el-menu-item .fa, .el-sub-menu .fa { - @include generalIcon; + @include index.generalIcon; } // 水平菜单、横向菜单高亮 背景色,鼠标 hover 时,有子级菜单的背景色 .el-menu-item.is-active, diff --git a/web/src/theme/index.scss b/web/src/theme/index.scss index a94595ce57970141f45c30856b5d70e3d1b0e4d3..80b7ce00000155bd28f71cadbbb87cda05e45e81 100644 --- a/web/src/theme/index.scss +++ b/web/src/theme/index.scss @@ -1,8 +1,8 @@ -@import './app.scss'; -@import 'common/transition.scss'; -@import './other.scss'; -@import './element.scss'; -@import './media/media.scss'; -@import './waves.scss'; -@import './dark.scss'; -@import './fa/css/font-awesome.min.css'; +@use './app.scss'; +@use 'common/transition.scss'; +@use './other.scss'; +@use './element.scss'; +@use './media/media.scss'; +@use './waves.scss'; +@use './dark.scss'; +@use './fa/css/font-awesome.min.css'; diff --git a/web/src/theme/media/chart.scss b/web/src/theme/media/chart.scss index 8485e39c21dcc115a5cff5ec3a6d9fefe488bb5e..79883390c6f99fd174d0216960669f807fdf2e7b 100644 --- a/web/src/theme/media/chart.scss +++ b/web/src/theme/media/chart.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于768px ------------------------------- */ -@media screen and (max-width: $sm) { +@media screen and (max-width: index.$sm) { .big-data-down-left { width: 100% !important; flex-direction: unset !important; @@ -51,7 +51,7 @@ /* 页面宽度大于768px小于1200px ------------------------------- */ -@media screen and (min-width: $sm) and (max-width: $lg) { +@media screen and (min-width: index.$sm) and (max-width: index.$lg) { .chart-warp-bottom { .big-data-down-left { width: 50% !important; @@ -72,7 +72,7 @@ /* 页面宽度小于1200px ------------------------------- */ -@media screen and (max-width: $lg) { +@media screen and (max-width: index.$lg) { .chart-warp-top { .up-left { display: none; diff --git a/web/src/theme/media/cityLinkage.scss b/web/src/theme/media/cityLinkage.scss index 1394156ee1d9a3f7d595b8ab3f056b5a1f8704d8..edc6d110ce600133b301c6482e92b5853d0b5bee 100644 --- a/web/src/theme/media/cityLinkage.scss +++ b/web/src/theme/media/cityLinkage.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于576px ------------------------------- */ -@media screen and (max-width: $xs) { +@media screen and (max-width: index.$xs) { .el-cascader__dropdown.el-popper { overflow: auto; max-width: 100%; diff --git a/web/src/theme/media/date.scss b/web/src/theme/media/date.scss index 1a503970683aa0c271d3c318b3b11d8821d16c2a..4b3c5e888c5c7114bccbce386f4f7c2d7676bee4 100644 --- a/web/src/theme/media/date.scss +++ b/web/src/theme/media/date.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于768px ------------------------------- */ -@media screen and (max-width: $sm) { +@media screen and (max-width: index.$sm) { // 时间选择器适配 .el-date-range-picker { width: 100vw; diff --git a/web/src/theme/media/dialog.scss b/web/src/theme/media/dialog.scss index 023ccae0eb6439ba7b43cba414c0dd0692bb268f..64355037b65ba4852abee9dbd5285792ffe729d2 100644 --- a/web/src/theme/media/dialog.scss +++ b/web/src/theme/media/dialog.scss @@ -1,4 +1,4 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于800px ------------------------------- */ diff --git a/web/src/theme/media/error.scss b/web/src/theme/media/error.scss index f35015fda673cd46a7e03dfc1a76e672f011bc8c..3ee65db472a4bcc7ca0c3e3bea0d56fc0ce072fa 100644 --- a/web/src/theme/media/error.scss +++ b/web/src/theme/media/error.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于768px ------------------------------- */ -@media screen and (max-width: $sm) { +@media screen and (max-width: index.$sm) { .error { .error-flex { flex-direction: column-reverse !important; @@ -26,7 +26,7 @@ /* 页面宽度大于768px小于992px ------------------------------- */ -@media screen and (min-width: $sm) and (max-width: $md) { +@media screen and (min-width: index.$sm) and (max-width: index.$md) { .error { .error-flex { padding-left: 30px !important; @@ -36,7 +36,7 @@ /* 页面宽度小于1200px ------------------------------- */ -@media screen and (max-width: $lg) { +@media screen and (max-width: index.$lg) { .error { .error-flex { padding: 0 30px; diff --git a/web/src/theme/media/form.scss b/web/src/theme/media/form.scss index eb1d883072914ba5accf74c5fda8608e304e01d6..ab382f2df7916737a4e8770d25222a8479fac295 100644 --- a/web/src/theme/media/form.scss +++ b/web/src/theme/media/form.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于576px ------------------------------- */ -@media screen and (max-width: $xs) { +@media screen and (max-width: index.$xs) { .el-form-item__label { width: 100% !important; text-align: left !important; diff --git a/web/src/theme/media/home.scss b/web/src/theme/media/home.scss index 5a2417e18d38d7cb47064cf5392ee7726ddfe66a..88a966988a610c611f9579e546e8d15d314face4 100644 --- a/web/src/theme/media/home.scss +++ b/web/src/theme/media/home.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于768px ------------------------------- */ -@media screen and (max-width: $sm) { +@media screen and (max-width: index.$sm) { .home-media, .home-media-sm { margin-top: 15px; @@ -11,7 +11,7 @@ /* 页面宽度小于1200px ------------------------------- */ -@media screen and (max-width: $lg) { +@media screen and (max-width: index.$lg) { .home-media-lg { margin-top: 15px; } diff --git a/web/src/theme/media/layout.scss b/web/src/theme/media/layout.scss index df8ce56adc69eea4dc199218b1a0ed718f3fbbc0..620991b9f7b312900cb3e27a282ffc3145a25c74 100644 --- a/web/src/theme/media/layout.scss +++ b/web/src/theme/media/layout.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于576px ------------------------------- */ -@media screen and (max-width: $xs) { +@media screen and (max-width: index.$xs) { // MessageBox 弹框 .el-message-box { width: 80% !important; @@ -11,7 +11,7 @@ /* 页面宽度小于768px ------------------------------- */ -@media screen and (max-width: $sm) { +@media screen and (max-width: index.$sm) { // Breadcrumb 面包屑 .layout-navbars-breadcrumb-hide { display: none; diff --git a/web/src/theme/media/login.scss b/web/src/theme/media/login.scss index 29cdbb01917bae22c9a019608e4e2f7432e2bb35..f041ffa3bd32fdebc5d90a30ca40adda56842400 100644 --- a/web/src/theme/media/login.scss +++ b/web/src/theme/media/login.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于1200px ------------------------------- */ -@media screen and (max-width: $lg) and (min-width: $xs) { +@media screen and (max-width: index.$lg) and (min-width: index.$xs) { .login-container { .login-left { .login-left-img { @@ -23,7 +23,7 @@ /* 页面宽度小于576px ------------------------------- */ -@media screen and (max-width: $xs) { +@media screen and (max-width: index.$xs) { .login-container { .login-left { display: none; @@ -59,7 +59,7 @@ /* 页面宽度小于375px ------------------------------- */ -@media screen and (max-width: $us) { +@media screen and (max-width: index.$us) { .login-container { .login-right { .login-right-warp { diff --git a/web/src/theme/media/media.scss b/web/src/theme/media/media.scss index bed1c356a2193e185be340cd3351dd7b163a732e..f5f00c744da00881439345008932632938494bb5 100644 --- a/web/src/theme/media/media.scss +++ b/web/src/theme/media/media.scss @@ -1,13 +1,13 @@ -@import './login.scss'; -@import './error.scss'; -@import './layout.scss'; -@import './personal.scss'; -@import './tagsView.scss'; -@import './home.scss'; -@import './chart.scss'; -@import './form.scss'; -@import './scrollbar.scss'; -@import './pagination.scss'; -@import './dialog.scss'; -@import './cityLinkage.scss'; -@import './date.scss'; +@use './login.scss'; +@use './error.scss'; +@use './layout.scss'; +@use './personal.scss'; +@use './tagsView.scss'; +@use './home.scss'; +@use './chart.scss'; +@use './form.scss'; +@use './scrollbar.scss'; +@use './pagination.scss'; +@use './dialog.scss'; +@use './cityLinkage.scss'; +@use './date.scss'; diff --git a/web/src/theme/media/pagination.scss b/web/src/theme/media/pagination.scss index 37af75f265ccbe752f2f09c37e6d23b39a381b2b..107703997bad226927f8980c5a104d30d87b8683 100644 --- a/web/src/theme/media/pagination.scss +++ b/web/src/theme/media/pagination.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于576px ------------------------------- */ -@media screen and (max-width: $xs) { +@media screen and (max-width: index.$xs) { .el-pager, .el-pagination__jump { display: none !important; diff --git a/web/src/theme/media/personal.scss b/web/src/theme/media/personal.scss index 7ec0d4abcc85b515fcb86c4f264670191887ae02..d5c1d8e3c12869006f2b38c4e7f84b0d1d1a16de 100644 --- a/web/src/theme/media/personal.scss +++ b/web/src/theme/media/personal.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于768px ------------------------------- */ -@media screen and (max-width: $sm) { +@media screen and (max-width: index.$sm) { .personal-info { padding-left: 0 !important; margin-top: 15px; diff --git a/web/src/theme/media/scrollbar.scss b/web/src/theme/media/scrollbar.scss index 968a79d59520558d40ca4ffaf6c1a4fee5929fca..9a36d8018911708ab7bb40400d980f2d9c72dc08 100644 --- a/web/src/theme/media/scrollbar.scss +++ b/web/src/theme/media/scrollbar.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于768px ------------------------------- */ -@media screen and (max-width: $sm) { +@media screen and (max-width: index.$sm) { // 滚动条的宽度 ::-webkit-scrollbar { width: 3px !important; diff --git a/web/src/theme/media/tagsView.scss b/web/src/theme/media/tagsView.scss index b71674ef43f6c877d9cc48705e8fd299910de0a4..ad531daca3cd3715800116eb4280553ae16a0ff9 100644 --- a/web/src/theme/media/tagsView.scss +++ b/web/src/theme/media/tagsView.scss @@ -1,8 +1,8 @@ -@import './index.scss'; +@use './index.scss' as index; /* 页面宽度小于768px ------------------------------- */ -@media screen and (max-width: $sm) { +@media screen and (max-width: index.$sm) { .tags-view-form { .tags-view-form-col { margin-bottom: 20px; diff --git a/web/src/utils/service.ts b/web/src/utils/service.ts index 4207572a0cb4c2fafc5290b51dbc9289d4574cb2..0080f78c65d8be5b8e68a54d6e60ddebf501f2c1 100644 --- a/web/src/utils/service.ts +++ b/web/src/utils/service.ts @@ -172,20 +172,20 @@ function createRequestFunction(service: any) { return function (config: any) { const configDefault = { headers: { - 'Content-Type': get(config, 'headers.Content-Type', 'application/json'), + 'Content-Type': 'application/json', }, timeout: 5000, baseURL: getBaseURL(), data: {}, }; - + Object.assign(configDefault, config); // const token = userStore.getToken; const token = Session.get('token'); if (token != null) { // @ts-ignore configDefault.headers.Authorization = 'JWT ' + token; } - return service(Object.assign(configDefault, config)); + return service(configDefault); }; } diff --git a/web/src/views/system/login/component/oauth2.vue b/web/src/views/system/login/component/oauth2.vue index 1b0c0c68b86bb7978de4a408d0504d046205373c..1a2c6cb286013502835c8972ebc7c4a0d9fd999c 100644 --- a/web/src/views/system/login/component/oauth2.vue +++ b/web/src/views/system/login/component/oauth2.vue @@ -40,7 +40,7 @@ export default defineComponent({ // }; onMounted(() => { - getBackends(); + // getBackends(); }); return { ...toRefs(state), diff --git a/web/src/views/system/login/index.vue b/web/src/views/system/login/index.vue index 897ddc4121cf00fabb1900f0be02265ef9c37814..36acbe6f20be3a9cf1c741aa65b599e698df91bc 100644 --- a/web/src/views/system/login/index.vue +++ b/web/src/views/system/login/index.vue @@ -47,10 +47,10 @@