diff --git a/backend/application/settings.py b/backend/application/settings.py index 7f8e7d5..8b2c8c2 100644 --- a/backend/application/settings.py +++ b/backend/application/settings.py @@ -409,5 +409,6 @@ PLUGINS_URL_PATTERNS = [] # from dvadmin_ak_sk.settings import * # 秘钥管理管理 # from dvadmin_tenants.settings import * # 租户管理 #from dvadmin_social_auth.settings import * +#from dvadmin_uniapp.settings import * # ... # ********** 一键导入插件配置结束 ********** diff --git a/backend/conf/env.example.py b/backend/conf/env.example.py index 03e8c1b..bf015db 100644 --- a/backend/conf/env.example.py +++ b/backend/conf/env.example.py @@ -7,30 +7,30 @@ from application.settings import BASE_DIR # ================================================= # # 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库 # sqlite3 设置 -DATABASE_ENGINE = "django.db.backends.sqlite3" -DATABASE_NAME = os.path.join(BASE_DIR, "db.sqlite3") +# DATABASE_ENGINE = "django.db.backends.sqlite3" +# DATABASE_NAME = os.path.join(BASE_DIR, "db.sqlite3") # 使用mysql时,改为此配置 -# DATABASE_ENGINE = "django.db.backends.mysql" -# DATABASE_NAME = 'django-vue-admin' # mysql 时使用 +DATABASE_ENGINE = "django.db.backends.mysql" +DATABASE_NAME = 'django-vue3-admin' # mysql 时使用 # 数据库地址 改为自己数据库地址 -DATABASE_HOST = "127.0.0.1" +DATABASE_HOST = '127.0.0.1' # # 数据库端口 DATABASE_PORT = 3306 # # 数据库用户名 DATABASE_USER = "root" # # 数据库密码 -DATABASE_PASSWORD = "123456" +DATABASE_PASSWORD = "DVADMIN3" # 表前缀 TABLE_PREFIX = "dvadmin_" # ================================================= # # ******** redis配置,无redis 可不进行配置 ******** # # ================================================= # -# REDIS_PASSWORD = '' -# REDIS_HOST = '127.0.0.1' -# REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6380' +REDIS_PASSWORD = 'DVADMIN3' +REDIS_HOST = '127.0.0.1' +REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379' # ================================================= # # ****************** 功能 启停 ******************* # # ================================================= # diff --git a/backend/dvadmin/system/tests.py b/backend/dvadmin/system/tests.py index 2e9cb5f..866d09d 100644 --- a/backend/dvadmin/system/tests.py +++ b/backend/dvadmin/system/tests.py @@ -1 +1,56 @@ +from functools import wraps + +from django.db.models import Func, F, OuterRef, Exists from django.test import TestCase +import django +import os +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "application.settings") +django.setup() +from dvadmin.system.models import Menu, RoleMenuPermission, RoleMenuButtonPermission, MenuButton + + +import time + +def timing_decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + run_time = end_time - start_time + print(f"{func.__name__} ran in {run_time:.6f} seconds") + return result + return wrapper + +@timing_decorator +def getMenu(): + data = [] + queryset = Menu.objects.filter(status=1, is_catalog=False).values('name', 'id') + for item in queryset: + parent_list = Menu.get_all_parent(item['id']) + names = [d["name"] for d in parent_list] + completeName = "/".join(names) + isCheck = RoleMenuPermission.objects.filter( + menu__id=item['id'], + role__id=1, + ).exists() + mbCheck = RoleMenuButtonPermission.objects.filter( + menu_button = OuterRef("pk"), + role__id=1, + ) + btns = MenuButton.objects.filter( + menu__id=item['id'], + ).annotate(isCheck=Exists(mbCheck)).values('id', 'name', 'value', 'isCheck',data_range=F('menu_button_permission__data_range')) + # print(b) + dicts = { + 'name': completeName, + 'id': item['id'], + 'isCheck': isCheck, + 'btns':btns + } + print(dicts) + data.append(dicts) + # print(data) + +if __name__ == '__main__': + getMenu() \ No newline at end of file diff --git a/backend/dvadmin/system/views/area.py b/backend/dvadmin/system/views/area.py index cd681d7..dfa2353 100644 --- a/backend/dvadmin/system/views/area.py +++ b/backend/dvadmin/system/views/area.py @@ -60,12 +60,9 @@ class AreaViewSet(CustomModelViewSet): del params['page'] if limit: del params['limit'] - if params: - if pcode: - queryset = self.queryset.filter(enable=True, pcode=pcode) - else: - queryset = self.queryset.filter(enable=True) + if params and pcode: + queryset = self.queryset.filter(enable=True, pcode=pcode) else: - queryset = self.queryset.filter(enable=True, pcode__isnull=True) + queryset = self.queryset.filter(enable=True) return queryset diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py index ccbe6d6..a5b5a6f 100644 --- a/backend/dvadmin/system/views/role.py +++ b/backend/dvadmin/system/views/role.py @@ -47,12 +47,12 @@ class RoleCreateUpdateSerializer(CustomModelSerializer): def validate(self, attrs: dict): return super().validate(attrs) - def save(self, **kwargs): - is_superuser = self.request.user.is_superuser - if not is_superuser: - self.validated_data.pop('admin') - data = super().save(**kwargs) - return data + # def save(self, **kwargs): + # is_superuser = self.request.user.is_superuser + # if not is_superuser: + # self.validated_data.pop('admin') + # data = super().save(**kwargs) + # return data class Meta: model = Role diff --git a/backend/dvadmin/system/views/role_menu_button_permission.py b/backend/dvadmin/system/views/role_menu_button_permission.py index 6f77931..745daee 100644 --- a/backend/dvadmin/system/views/role_menu_button_permission.py +++ b/backend/dvadmin/system/views/role_menu_button_permission.py @@ -171,14 +171,46 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet): if role is None: return ErrorResponse(msg="未获取到角色信息") is_superuser = request.user.is_superuser + # if is_superuser: + # queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all() + # else: + # role_id = request.user.role.values_list('id', flat=True) + # menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id',flat=True) + # queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id').all() + # serializer = RoleMenuPermissionSerializer(queryset,many=True,request=request) + # data = serializer.data + # return DetailResponse(data=data) + data = [] if is_superuser: queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all() else: role_id = request.user.role.values_list('id', flat=True) menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id',flat=True) - queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id').all() - serializer = RoleMenuPermissionSerializer(queryset,many=True,request=request) - data = serializer.data + queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id') + for item in queryset: + parent_list = Menu.get_all_parent(item['id']) + names = [d["name"] for d in parent_list] + completeName = "/".join(names) + isCheck = RoleMenuPermission.objects.filter( + menu__id=item['id'], + role__id=role, + ).exists() + mbCheck = RoleMenuButtonPermission.objects.filter( + menu_button=OuterRef("pk"), + role__id=role, + ) + btns = MenuButton.objects.filter( + menu__id=item['id'], + ).annotate(isCheck=Exists(mbCheck)).values('id', 'name', 'value', 'isCheck', + data_range=F('menu_button_permission__data_range')) + dicts = { + 'name': completeName, + 'id': item['id'], + 'isCheck': isCheck, + 'btns': btns, + + } + data.append(dicts) return DetailResponse(data=data) @action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated]) diff --git a/backend/dvadmin/utils/filters.py b/backend/dvadmin/utils/filters.py index 13ab42f..05c0dfb 100644 --- a/backend/dvadmin/utils/filters.py +++ b/backend/dvadmin/utils/filters.py @@ -12,16 +12,49 @@ from collections import OrderedDict from functools import reduce 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 -from django_filters.filters import CharFilter +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 rest_framework.filters import BaseFilterBackend - +from django_filters.conf import settings from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission +from dvadmin.utils.models import CoreModel +class CoreModelFilterBankend(BaseFilterBackend): + """ + 自定义时间范围过滤器 + """ + def filter_queryset(self, request, queryset, view): + create_datetime_after = request.query_params.get('create_datetime_after', None) + create_datetime_before = request.query_params.get('create_datetime_before', None) + update_datetime_after = request.query_params.get('update_datetime_after', None) + update_datetime_before = request.query_params.get('update_datetime_after', None) + if any([create_datetime_after, create_datetime_before, update_datetime_after, update_datetime_before]): + create_filter = Q() + if create_datetime_after and create_datetime_before: + create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=create_datetime_before) + elif create_datetime_after: + create_filter &= Q(create_datetime__gte=create_datetime_after) + elif create_datetime_before: + create_filter &= Q(create_datetime__lte=create_datetime_before) + + # 更新时间范围过滤条件 + update_filter = Q() + if update_datetime_after and update_datetime_before: + update_filter &= Q(update_datetime__gte=update_datetime_after) & Q(update_datetime__lte=update_datetime_before) + elif update_datetime_after: + update_filter &= Q(update_datetime__gte=update_datetime_after) + elif update_datetime_before: + update_filter &= Q(update_datetime__lte=update_datetime_before) + # 结合两个时间范围过滤条件 + queryset = queryset.filter(create_filter & update_filter) + return queryset + return queryset def get_dept(dept_id: int, dept_all_list=None, dept_list=None): """ @@ -172,6 +205,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): "$": "iregex", "~": "icontains", } + filter_fields = "__all__" def construct_search(self, field_name, lookup_expr=None): lookup = self.lookup_prefixes.get(field_name[0]) @@ -179,14 +213,16 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): field_name = field_name[1:] else: lookup = lookup_expr - if field_name.endswith(lookup): - return field_name - return LOOKUP_SEP.join([field_name, lookup]) + if lookup: + if field_name.endswith(lookup): + return field_name + return LOOKUP_SEP.join([field_name, lookup]) + return field_name def find_filter_lookups(self, orm_lookups, search_term_key): for lookup in orm_lookups: # if lookup.find(search_term_key) >= 0: - new_lookup = lookup.split("__")[0] + new_lookup = LOOKUP_SEP.join(lookup.split(LOOKUP_SEP)[:-1]) if len(lookup.split(LOOKUP_SEP)) > 1 else lookup # 修复条件搜索错误 bug if new_lookup == search_term_key: return lookup @@ -202,18 +238,22 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): # TODO: remove assertion in 2.1 if filterset_class is None and hasattr(view, "filter_class"): utils.deprecate( - "`%s.filter_class` attribute should be renamed `filterset_class`." - % view.__class__.__name__ + "`%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( - "`%s.filter_fields` attribute should be renamed `filterset_fields`." - % view.__class__.__name__ + "`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__ ) - filterset_fields = getattr(view, "filter_fields", None) + self.filter_fields = getattr(view, "filter_fields", None) + if isinstance(self.filter_fields, (list, tuple)): + filterset_fields = [ + field[1:] if field[0] in self.lookup_prefixes.keys() else field for field in self.filter_fields + ] + else: + filterset_fields = self.filter_fields if filterset_class: filterset_model = filterset_class._meta.model @@ -233,6 +273,51 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): MetaBase = getattr(self.filterset_base, "Meta", object) class AutoFilterSet(self.filterset_base): + @classmethod + def get_all_model_fields(cls, model): + opts = model._meta + + return [ + f.name + for f in sorted(opts.fields + opts.many_to_many) + if (f.name == "id") + or not isinstance(f, models.AutoField) + and not (getattr(f.remote_field, "parent_link", False)) + ] + + @classmethod + def get_fields(cls): + """ + Resolve the 'fields' argument that should be used for generating filters on the + filterset. This is 'Meta.fields' sans the fields in 'Meta.exclude'. + """ + model = cls._meta.model + fields = cls._meta.fields + exclude = cls._meta.exclude + + assert not (fields is None and exclude is None), ( + "Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' " + "has been deprecated since 0.15.0 and is now disallowed. Add an explicit " + "'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__ + ) + + # Setting exclude with no fields implies all other fields. + if exclude is not None and fields is None: + fields = ALL_FIELDS + + # Resolve ALL_FIELDS into all fields for the filterset's model. + if fields == ALL_FIELDS: + fields = cls.get_all_model_fields(model) + + # Remove excluded fields + exclude = exclude or [] + if not isinstance(fields, dict): + fields = [(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude] + else: + fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude] + + return OrderedDict(fields) + @classmethod def get_filters(cls): """ @@ -261,9 +346,12 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): if field is None: undefined.append(field_name) # 更新默认字符串搜索为模糊搜索 - if isinstance(field, (models.CharField)) and filterset_fields == '__all__' and lookups == [ - 'exact']: - lookups = ['icontains'] + if ( + isinstance(field, (models.CharField)) + and filterset_fields == "__all__" + and lookups == ["exact"] + ): + lookups = ["icontains"] for lookup_expr in lookups: filter_name = cls.get_filter_name(field_name, lookup_expr) @@ -273,20 +361,15 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): continue if field is not None: - filters[filter_name] = cls.filter_for_field( - field, field_name, lookup_expr - ) + filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr) # Allow Meta.fields to contain declared filters *only* when a list/tuple if isinstance(cls._meta.fields, (list, tuple)): - undefined = [ - f for f in undefined if f not in cls.declared_filters - ] + undefined = [f for f in undefined if f not in cls.declared_filters] if undefined: raise TypeError( - "'Meta.fields' must not contain non-model field names: %s" - % ", ".join(undefined) + "'Meta.fields' must not contain non-model field names: %s" % ", ".join(undefined) ) # Add in declared filters. This is necessary since we don't enforce adding @@ -308,22 +391,31 @@ class CustomDjangoFilterBackend(DjangoFilterBackend): return queryset if filterset.__class__.__name__ == "AutoFilterSet": queryset = filterset.queryset - orm_lookups = [] - for search_field in filterset.filters: - if isinstance(filterset.filters[search_field], CharFilter): - orm_lookups.append( - self.construct_search(six.text_type(search_field), filterset.filters[search_field].lookup_expr) - ) - else: - orm_lookups.append(search_field) + filter_fields = filterset.filters if self.filter_fields == "__all__" else self.filter_fields + orm_lookup_dict = dict( + zip( + [field for field in filter_fields], + [filterset.filters[lookup].lookup_expr for lookup in filterset.filters.keys()], + ) + ) + orm_lookups = [ + self.construct_search(lookup, lookup_expr) for lookup, lookup_expr in orm_lookup_dict.items() + ] + # print(orm_lookups) conditions = [] queries = [] for search_term_key in filterset.data.keys(): orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key) - if not orm_lookup: + if not orm_lookup or filterset.data.get(search_term_key) == '': continue - query = Q(**{orm_lookup: filterset.data[search_term_key]}) - queries.append(query) + filterset_data_len = len(filterset.data.getlist(search_term_key)) + if filterset_data_len == 1: + query = Q(**{orm_lookup: filterset.data[search_term_key]}) + queries.append(query) + elif filterset_data_len == 2: + orm_lookup += '__range' + query = Q(**{orm_lookup: filterset.data.getlist(search_term_key)}) + queries.append(query) if len(queries) > 0: conditions.append(reduce(operator.and_, queries)) queryset = queryset.filter(reduce(operator.and_, conditions)) diff --git a/backend/dvadmin/utils/serializers.py b/backend/dvadmin/utils/serializers.py index 698d9ee..5dd9527 100644 --- a/backend/dvadmin/utils/serializers.py +++ b/backend/dvadmin/utils/serializers.py @@ -26,7 +26,7 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer): # 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖 modifier_field_id = "modifier" modifier_name = serializers.SerializerMethodField(read_only=True) - dept_belong_id = serializers.IntegerField(required=False, default=None) + dept_belong_id = serializers.IntegerField(required=False, allow_null=True) def get_modifier_name(self, instance): if not hasattr(instance, "modifier"): diff --git a/backend/dvadmin/utils/viewset.py b/backend/dvadmin/utils/viewset.py index a20b9be..b85007a 100644 --- a/backend/dvadmin/utils/viewset.py +++ b/backend/dvadmin/utils/viewset.py @@ -7,16 +7,18 @@ @Remark: 自定义视图集 """ from django.db import transaction +from django_filters import DateTimeFromToRangeFilter +from django_filters.rest_framework import FilterSet from drf_yasg import openapi 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 +from dvadmin.utils.filters import DataLevelPermissionsFilter, CoreModelFilterBankend from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSerializerMixin from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse from dvadmin.utils.permission import CustomPermission -from dvadmin.utils.models import get_custom_app_models +from dvadmin.utils.models import get_custom_app_models, CoreModel from dvadmin.system.models import FieldPermission, MenuField from django_restql.mixins import QueryArgumentsMixin @@ -37,7 +39,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi update_serializer_class = None filter_fields = '__all__' search_fields = () - extra_filter_class = [DataLevelPermissionsFilter] + extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionsFilter] permission_classes = [CustomPermission] import_field_dict = {} export_field_label = {} diff --git a/backend/requirements.txt b/backend/requirements.txt index 95b96f8..4824df6 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -28,3 +28,4 @@ uvicorn==0.23.2 gunicorn==21.2.0 gevent==23.9.1 Pillow==10.1.0 +dvadmin-celery==1.0.5 diff --git a/docker-compose.yml b/docker-compose.yml index 13d8033..c08a0ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,7 @@ services: - ./docker_env/nginx/my.conf:/etc/nginx/conf.d/my.conf expose: - "8080" + restart: always networks: network: ipv4_address: 177.10.0.11 @@ -23,9 +24,8 @@ services: dockerfile: ./docker_env/django/Dockerfile container_name: dvadmin3-django working_dir: /backend -# 打开mysql 时,打开此选项 -# depends_on: -# - dvadmin3-mysql + depends_on: + - dvadmin3-mysql environment: PYTHONUNBUFFERED: 1 DATABASE_HOST: dvadmin3-mysql @@ -42,74 +42,70 @@ services: network: ipv4_address: 177.10.0.12 -# dvadmin3-mysql: -# image: mysql:5.7 -# container_name: dvadmin3-mysql -# #使用该参数,container内的root拥有真正的root权限,否则,container内的root只是外部的一个普通用户权限 -# #设置为true,不然数据卷可能挂载不了,启动不起 -## privileged: true -# restart: always -# ports: -# - "3306:3306" -# environment: -# MYSQL_ROOT_PASSWORD: "123456" -# MYSQL_DATABASE: "dvadmin3_pro" -# TZ: Asia/Shanghai -# command: -# --wait_timeout=31536000 -# --interactive_timeout=31536000 -# --max_connections=1000 -# --default-authentication-plugin=mysql_native_password -# volumes: -# - "./docker_env/mysql/data:/var/lib/mysql" -# - "./docker_env/mysql/conf.d:/etc/mysql/conf.d" -# - "./docker_env/mysql/logs:/logs" -# networks: -# network: -# ipv4_address: 177.10.0.13 + dvadmin3-mysql: + image: mysql:8.0 + container_name: dvadmin3-mysql + privileged: true + restart: always + ports: + - "3306:3306" + environment: + MYSQL_ROOT_PASSWORD: "DVADMIN3" + MYSQL_DATABASE: "django-vue3-admin" + TZ: Asia/Shanghai + command: + --wait_timeout=31536000 + --interactive_timeout=31536000 + --max_connections=1000 + --default-authentication-plugin=mysql_native_password + volumes: + - "./docker_env/mysql/data:/var/lib/mysql" + - "./docker_env/mysql/conf.d:/etc/mysql/conf.d" + - "./docker_env/mysql/logs:/logs" + networks: + network: + ipv4_address: 177.10.0.13 -# 如果使用celery 插件,请自行打开此注释 -# dvadmin3-celery: -# build: -# context: . -# dockerfile: ./docker_env/celery/Dockerfile -# # image: django:2.2 -# container_name: dvadmin3-celery -# working_dir: /backend -# depends_on: -# - dvadmin3-mysql -# environment: -# PYTHONUNBUFFERED: 1 -# DATABASE_HOST: dvadmin3-mysql -# TZ: Asia/Shanghai -# volumes: -# - ./backend:/backend -# - ./logs/log:/var/log -# restart: always -# networks: -# network: -# ipv4_address: 177.10.0.14 + dvadmin3-celery: + build: + context: . + dockerfile: ./docker_env/celery/Dockerfile + container_name: dvadmin3-celery + working_dir: /backend + depends_on: + - dvadmin3-mysql + environment: + PYTHONUNBUFFERED: 1 + DATABASE_HOST: dvadmin3-mysql + TZ: Asia/Shanghai + volumes: + - ./backend:/backend + - ./logs/log:/var/log + restart: always + networks: + network: + ipv4_address: 177.10.0.14 -# dvadmin3-redis: -# image: redis:6.2.6-alpine # 指定服务镜像,最好是与之前下载的redis配置文件保持一致 -# container_name: dvadmin3-redis # 容器名称 -# restart: on-failure # 重启方式 -# environment: -# - TZ=Asia/Shanghai # 设置时区 -# volumes: # 配置数据卷 -# - ./docker_env/redis/data:/data -# - ./docker_env/redis/redis.conf:/etc/redis/redis.conf -# ports: # 映射端口 -# - "6379:6379" -# sysctls: # 设置容器中的内核参数 -# - net.core.somaxconn=1024 -# command: /bin/sh -c "echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf && redis-server /etc/redis/redis.conf --appendonly yes" # 指定配置文件并开启持久化 -# privileged: true # 使用该参数,container内的root拥有真正的root权限。否则,container内的root只是外部的一个普通用户权限 -# networks: -# network: -# ipv4_address: 177.10.0.15 + dvadmin3-redis: + image: redis:6.2.6-alpine # 指定服务镜像,最好是与之前下载的redis配置文件保持一致 + container_name: dvadmin3-redis # 容器名称 + restart: always + environment: + - TZ=Asia/Shanghai # 设置时区 + volumes: # 配置数据卷 + - ./docker_env/redis/data:/data + - ./docker_env/redis/redis.conf:/etc/redis/redis.conf + ports: # 映射端口 + - "6379:6379" + sysctls: # 设置容器中的内核参数 + - net.core.somaxconn=1024 + command: /bin/sh -c "echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf && redis-server /etc/redis/redis.conf --appendonly yes --requirepass DVADMIN3" # 指定配置文件并开启持久化 + privileged: true # 使用该参数,container内的root拥有真正的root权限。否则,container内的root只是外部的一个普通用户权限 + networks: + network: + ipv4_address: 177.10.0.15 networks: diff --git a/docker_env/django/Dockerfile b/docker_env/django/Dockerfile index 3f8c93c..7acd383 100644 --- a/docker_env/django/Dockerfile +++ b/docker_env/django/Dockerfile @@ -1,6 +1,9 @@ FROM registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-backend:latest WORKDIR /backend COPY ./backend/ . +RUN ls ./conf/ RUN awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }' +RUN sed -i "s|DATABASE_HOST = "127.0.0.1"|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py +RUN sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py RUN python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt CMD ["/backend/docker_start.sh"] diff --git a/web/.env.development b/web/.env.development index dc36b29..1c3ca5d 100644 --- a/web/.env.development +++ b/web/.env.development @@ -2,7 +2,7 @@ ENV = 'development' # 本地环境接口地址 -VITE_API_URL = 'http://127.0.0.1:8001' +VITE_API_URL = 'http://127.0.0.1:8000' # 是否启用按钮权限 VITE_PM_ENABLED = true diff --git a/web/package.json b/web/package.json index 28b837f..956d682 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,7 @@ "@fast-crud/fast-extends": "^1.19.2", "@fast-crud/ui-element": "^1.19.2", "@fast-crud/ui-interface": "^1.19.2", + "@types/lodash": "^4.14.202", "@vitejs/plugin-vue-jsx": "^3.0.0", "@wangeditor/editor": "^5.1.23", "@wangeditor/editor-for-vue": "^5.1.12", @@ -25,7 +26,7 @@ "echarts": "^5.4.1", "echarts-gl": "^2.0.9", "echarts-wordcloud": "^2.1.0", - "element-plus": "^2.3.9", + "element-plus": "^2.5.5", "element-tree-line": "^0.2.1", "font-awesome": "^4.7.0", "js-cookie": "^3.0.1", diff --git a/web/src/components/dvaSelect/index.vue b/web/src/components/dvaSelect/index.vue new file mode 100644 index 0000000..8d58fbe --- /dev/null +++ b/web/src/components/dvaSelect/index.vue @@ -0,0 +1,141 @@ + + + diff --git a/web/src/components/tableSelector/index.vue b/web/src/components/tableSelector/index.vue index d98cff4..7ad3cad 100644 --- a/web/src/components/tableSelector/index.vue +++ b/web/src/components/tableSelector/index.vue @@ -43,7 +43,7 @@ import {defineProps, onMounted, reactive, ref, toRaw, watch} from 'vue' import {dict} from '@fast-crud/fast-crud' import XEUtils from 'xe-utils' - +import {request} from '/@/utils/service' const props = defineProps({ modelValue: {}, tableConfig: { @@ -71,6 +71,7 @@ watch(multipleSelection, // 监听multipleSelection的变化, if (!tableConfig.isMultiple) { data.value = value ? value[tableConfig.label] : null } else { + const result = value ? value.map((item: any) => { return item[tableConfig.label] }) : null @@ -123,12 +124,12 @@ const getDict = async () => { const params = { page: pageConfig.page, limit: pageConfig.limit, - search: search + search: search.value } - const dicts = dict({url: url, params: params}) - await dicts.reloadDict() - const dictData: any = dicts.data - const {data, page, limit, total} = dictData + const {data, page, limit, total} = await request({ + url:url, + params:params + }) pageConfig.page = page pageConfig.limit = limit pageConfig.total = total diff --git a/web/src/directive/authDirective.ts b/web/src/directive/authDirective.ts index 5971e64..44a8871 100644 --- a/web/src/directive/authDirective.ts +++ b/web/src/directive/authDirective.ts @@ -1,7 +1,6 @@ import type { App } from 'vue'; -import { useUserInfo } from '/@/stores/userInfo'; import { judementSameArr } from '/@/utils/arrayOperation'; - +import {BtnPermissionStore} from "/@/stores/btnPermission"; /** * 用户权限指令 * @directive 单个权限验证(v-auth="xxx") @@ -12,16 +11,16 @@ export function authDirective(app: App) { // 单个权限验证(v-auth="xxx") app.directive('auth', { mounted(el, binding) { - const stores = useUserInfo(); - if (!stores.userInfos.authBtnList.some((v: string) => v === binding.value)) el.parentNode.removeChild(el); + const stores = BtnPermissionStore(); + if (!stores.data.some((v: string) => v === binding.value)) el.parentNode.removeChild(el); }, }); // 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]") app.directive('auths', { mounted(el, binding) { let flag = false; - const stores = useUserInfo(); - stores.userInfos.authBtnList.map((val: string) => { + const stores = BtnPermissionStore(); + stores.data.map((val: string) => { binding.value.map((v: string) => { if (val === v) flag = true; }); @@ -32,8 +31,8 @@ export function authDirective(app: App) { // 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]") app.directive('auth-all', { mounted(el, binding) { - const stores = useUserInfo(); - const flag = judementSameArr(binding.value, stores.userInfos.authBtnList); + const stores = BtnPermissionStore(); + const flag = judementSameArr(binding.value, stores.data); if (!flag) el.parentNode.removeChild(el); }, }); diff --git a/web/src/directive/index.ts b/web/src/directive/index.ts index e2e1c57..899a303 100644 --- a/web/src/directive/index.ts +++ b/web/src/directive/index.ts @@ -1,7 +1,7 @@ import type { App } from 'vue'; import { authDirective } from '/@/directive/authDirective'; import { wavesDirective, dragDirective } from '/@/directive/customDirective'; - +import {resizeObDirective} from '/@/directive/sizeDirective' /** * 导出指令方法:v-xxx * @methods authDirective 用户权限指令,用法:v-auth @@ -15,4 +15,6 @@ export function directive(app: App) { wavesDirective(app); // 自定义拖动指令 dragDirective(app); + // 监听窗口大小变化 + resizeObDirective(app) } diff --git a/web/src/directive/sizeDirective.ts b/web/src/directive/sizeDirective.ts new file mode 100644 index 0000000..f39c689 --- /dev/null +++ b/web/src/directive/sizeDirective.ts @@ -0,0 +1,23 @@ +import {App} from "vue/dist/vue"; + +const map = new WeakMap() +const ob = new ResizeObserver((entries) => { + for(const entry of entries){ + const handler = map.get(entry.target); + handler && handler({ + width: entry.borderBoxSize[0].inlineSize, + height: entry.borderBoxSize[0].blockSize + }); + } +}); +export function resizeObDirective(app: App){ + app.directive('resizeOb', { + mounted(el,binding) { + map.set(el,binding.value); + ob.observe(el); // 监听目标元素 + }, + unmounted(el) { + ob.unobserve(el); // 停止监听 + }, + }) +} diff --git a/web/src/layout/logo/index.vue b/web/src/layout/logo/index.vue index d71010c..99bf7da 100644 --- a/web/src/layout/logo/index.vue +++ b/web/src/layout/logo/index.vue @@ -1,10 +1,10 @@ @@ -13,8 +13,8 @@ import { computed } from 'vue'; import { storeToRefs } from 'pinia'; import { useThemeConfig } from '/@/stores/themeConfig'; import logoMini from '/@/assets/logo-mini.svg'; -import {SystemConfigStore} from "/@/stores/systemConfig"; - +import { SystemConfigStore } from "/@/stores/systemConfig"; +import _ from "lodash"; // 定义变量内容 const storesThemeConfig = useThemeConfig(); const { themeConfig } = storeToRefs(storesThemeConfig); @@ -31,11 +31,18 @@ const onThemeConfigChange = () => { }; const systemConfigStore = SystemConfigStore() -const {systemConfig} = storeToRefs(systemConfigStore) -const getSystemConfig = computed(()=>{ - return systemConfig.value +const { systemConfig } = storeToRefs(systemConfigStore) +const getSystemConfig = computed(() => { + return systemConfig.value }) +const siteLogo = computed(() => { + if (!_.isEmpty(getSystemConfig.value['login.site_logo'])) { + return getSystemConfig.value['login.site_logo'] + } + return logoMini +}); + diff --git a/web/src/views/system/log/loginLog/crud.tsx b/web/src/views/system/log/loginLog/crud.tsx index e8a50be..a3fd92c 100644 --- a/web/src/views/system/log/loginLog/crud.tsx +++ b/web/src/views/system/log/loginLog/crud.tsx @@ -1,5 +1,6 @@ import * as api from './api'; import { UserPageQuery, AddReq, DelReq, EditReq, CreateCrudOptionsProps, CreateCrudOptionsRet, dict } from '@fast-crud/fast-crud'; +import {commonCrudConfig} from "/@/utils/commonCrud"; export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { const pageRequest = async (query: UserPageQuery) => { @@ -325,6 +326,11 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp }, }, }, + ...commonCrudConfig({ + create_datetime: { + search: true + } + }) }, }, }; diff --git a/web/src/views/system/login/index.vue b/web/src/views/system/login/index.vue index e57f316..7915b64 100644 --- a/web/src/views/system/login/index.vue +++ b/web/src/views/system/login/index.vue @@ -1,11 +1,12 @@