22 Commits

Author SHA1 Message Date
dvadmin-开发-李强
82a16545b4 Accept Merge Request #5: (develop -> master)
1. 新增: 大数据选择器组件
2. 新增: docker默认一键启动参数
3. 新增: 集成celery和Redis
4. 新增: 封装时间范围的搜索,示例在登录日志页面
5. 优化: 角色授权中的获取授权列表接口
6. 优化: 角色授权中字段权限显示判断
7. 优化: 个人中心修改密码成功后,强制退出登录状态
8. 优化: getBaseURL函数
9. 修复: 页面缓存无效问题
10. 修复: 登录页背景logo设置无效问题
11. 修复: 全局过滤器导致过滤无效问题
12. 修复: 当dept_belong_id为null时,触发接口报错
2024-03-12 22:45:36 +08:00
猿小天
39638e2e6a 功能变化: 优化个人中心修改密码后强制退出登录状态 2024-02-28 15:27:03 +08:00
猿小天
d3e5f258e5 修复BUG: 修复页面缓存问题 2024-02-25 16:30:49 +08:00
猿小天
77a27cba14 Merge remote-tracking branch 'origin/develop' into develop 2024-02-25 15:03:26 +08:00
猿小天
26bbf67da8 新功能: 角色授权字段权限判断 2024-02-25 15:03:07 +08:00
李强
d4f754976e Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	backend/application/settings.py
2024-02-21 17:49:38 +08:00
猿小天
85805e0d4b 功能变化: 优化角色授权中的获取授权列表 2024-02-16 22:41:15 +08:00
猿小天
8a646e5ef7 新功能: 新增大数据选择组件 2024-02-16 20:13:20 +08:00
猿小天
71eec9cfbd 修复BUG:
1.消息中心不加载目标数据问题;
2.地区数据加载问题
2024-01-18 22:12:55 +08:00
猿小天
9bd79b9dc6 修复BUG:
1.角色管理没有分页问题;
2.自定义指令引用问题
2024-01-18 20:25:02 +08:00
李强
e210b7dd17 Merge remote-tracking branch 'origin/develop' into develop 2024-01-12 00:28:56 +08:00
李强
4d00661163 feat: 优化getBaseURL函数 2024-01-12 00:28:52 +08:00
H0nGzA1
6798fca362 fix: 修复登录页背景logo设置 2024-01-04 19:49:40 +08:00
猿小天
6f5bbb045d 修复BUG: 全局过滤器导致的过滤无效 2024-01-04 16:53:12 +08:00
猿小天
bbf3018b20 Merge remote-tracking branch 'origin/develop' into develop 2024-01-03 23:23:24 +08:00
猿小天
5510a18280 修复BUG: 全局过滤器导致的过滤无效 2024-01-03 23:23:13 +08:00
李强
97737c3ef1 feat: 更新docker默认一键启动参数 2024-01-02 23:17:39 +08:00
李强
0b554f3669 feat: 默认集成celery与redis 2024-01-02 22:10:57 +08:00
猿小天
63453450ad 修复BUG: 当dept_belong_id为null时,触发接口报错 2024-01-02 14:39:16 +08:00
猿小天
fea6ebd98e Merge remote-tracking branch 'origin/develop' into develop 2024-01-02 13:50:52 +08:00
猿小天
d4e43e28e8 新功能: 封装时间范围的搜索,示例在登录日志页面 2024-01-02 13:50:43 +08:00
dvadmin-开发-李强
9973688675 Accept Merge Request #4: (develop -> master)
Merge Request: 正式发布v3.0.1版本

Created By: @dvadmin-开发-李强
Accepted By: @dvadmin-开发-李强
URL: https://dvadmin-private.coding.net/p/code/d/dvadmin3/git/merge/4?initial=true
2024-01-01 22:56:43 +08:00
36 changed files with 971 additions and 709 deletions

View File

@@ -409,5 +409,6 @@ PLUGINS_URL_PATTERNS = []
# from dvadmin_ak_sk.settings import * # 秘钥管理管理 # from dvadmin_ak_sk.settings import * # 秘钥管理管理
# from dvadmin_tenants.settings import * # 租户管理 # from dvadmin_tenants.settings import * # 租户管理
#from dvadmin_social_auth.settings import * #from dvadmin_social_auth.settings import *
#from dvadmin_uniapp.settings import *
# ... # ...
# ********** 一键导入插件配置结束 ********** # ********** 一键导入插件配置结束 **********

View File

@@ -7,30 +7,30 @@ from application.settings import BASE_DIR
# ================================================= # # ================================================= #
# 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库 # 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库
# sqlite3 设置 # 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时改为此配置 # 使用mysql时改为此配置
# DATABASE_ENGINE = "django.db.backends.mysql" DATABASE_ENGINE = "django.db.backends.mysql"
# DATABASE_NAME = 'django-vue-admin' # mysql 时使用 DATABASE_NAME = 'django-vue3-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 = "DVADMIN3"
# 表前缀 # 表前缀
TABLE_PREFIX = "dvadmin_" TABLE_PREFIX = "dvadmin_"
# ================================================= # # ================================================= #
# ******** redis配置无redis 可不进行配置 ******** # # ******** redis配置无redis 可不进行配置 ******** #
# ================================================= # # ================================================= #
# REDIS_PASSWORD = '' REDIS_PASSWORD = 'DVADMIN3'
# REDIS_HOST = '127.0.0.1' REDIS_HOST = '127.0.0.1'
# REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6380' REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379'
# ================================================= # # ================================================= #
# ****************** 功能 启停 ******************* # # ****************** 功能 启停 ******************* #
# ================================================= # # ================================================= #

View File

@@ -1 +1,56 @@
from functools import wraps
from django.db.models import Func, F, OuterRef, Exists
from django.test import TestCase 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()

View File

@@ -60,12 +60,9 @@ class AreaViewSet(CustomModelViewSet):
del params['page'] del params['page']
if limit: if limit:
del params['limit'] del params['limit']
if params: if params and pcode:
if pcode:
queryset = self.queryset.filter(enable=True, pcode=pcode) queryset = self.queryset.filter(enable=True, pcode=pcode)
else: else:
queryset = self.queryset.filter(enable=True) queryset = self.queryset.filter(enable=True)
else:
queryset = self.queryset.filter(enable=True, pcode__isnull=True)
return queryset return queryset

View File

@@ -47,12 +47,12 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
def validate(self, attrs: dict): def validate(self, attrs: dict):
return super().validate(attrs) return super().validate(attrs)
def save(self, **kwargs): # def save(self, **kwargs):
is_superuser = self.request.user.is_superuser # is_superuser = self.request.user.is_superuser
if not is_superuser: # if not is_superuser:
self.validated_data.pop('admin') # self.validated_data.pop('admin')
data = super().save(**kwargs) # data = super().save(**kwargs)
return data # return data
class Meta: class Meta:
model = Role model = Role

View File

@@ -171,14 +171,46 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
if role is None: if role is None:
return ErrorResponse(msg="未获取到角色信息") return ErrorResponse(msg="未获取到角色信息")
is_superuser = request.user.is_superuser 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: if is_superuser:
queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all() queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all()
else: else:
role_id = request.user.role.values_list('id', flat=True) role_id = request.user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).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() queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id')
serializer = RoleMenuPermissionSerializer(queryset,many=True,request=request) for item in queryset:
data = serializer.data 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) return DetailResponse(data=data)
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated]) @action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])

View File

@@ -12,16 +12,49 @@ from collections import OrderedDict
from functools import reduce from functools import reduce
import six import six
from django.db import models
from django.db.models import Q, F from django.db.models import Q, F
from django.db.models.constants import LOOKUP_SEP from django.db.models.constants import LOOKUP_SEP
from django_filters import utils from django_filters import utils, FilterSet
from django_filters.filters import CharFilter from django_filters.constants import ALL_FIELDS
from django_filters.filters import CharFilter, DateTimeFromToRangeFilter
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from django_filters.utils import get_model_field from django_filters.utils import get_model_field
from rest_framework.filters import BaseFilterBackend from rest_framework.filters import BaseFilterBackend
from django_filters.conf import settings
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission 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): def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
""" """
@@ -172,6 +205,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
"$": "iregex", "$": "iregex",
"~": "icontains", "~": "icontains",
} }
filter_fields = "__all__"
def construct_search(self, field_name, lookup_expr=None): def construct_search(self, field_name, lookup_expr=None):
lookup = self.lookup_prefixes.get(field_name[0]) lookup = self.lookup_prefixes.get(field_name[0])
@@ -179,14 +213,16 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
field_name = field_name[1:] field_name = field_name[1:]
else: else:
lookup = lookup_expr lookup = lookup_expr
if lookup:
if field_name.endswith(lookup): if field_name.endswith(lookup):
return field_name return field_name
return LOOKUP_SEP.join([field_name, lookup]) return LOOKUP_SEP.join([field_name, lookup])
return field_name
def find_filter_lookups(self, orm_lookups, search_term_key): def find_filter_lookups(self, orm_lookups, search_term_key):
for lookup in orm_lookups: for lookup in orm_lookups:
# if lookup.find(search_term_key) >= 0: # 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 # 修复条件搜索错误 bug
if new_lookup == search_term_key: if new_lookup == search_term_key:
return lookup return lookup
@@ -202,18 +238,22 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
# TODO: remove assertion in 2.1 # TODO: remove assertion in 2.1
if filterset_class is None and hasattr(view, "filter_class"): if filterset_class is None and hasattr(view, "filter_class"):
utils.deprecate( utils.deprecate(
"`%s.filter_class` attribute should be renamed `filterset_class`." "`%s.filter_class` attribute should be renamed `filterset_class`." % view.__class__.__name__
% view.__class__.__name__
) )
filterset_class = getattr(view, "filter_class", None) filterset_class = getattr(view, "filter_class", None)
# TODO: remove assertion in 2.1 # TODO: remove assertion in 2.1
if filterset_fields is None and hasattr(view, "filter_fields"): if filterset_fields is None and hasattr(view, "filter_fields"):
utils.deprecate( utils.deprecate(
"`%s.filter_fields` attribute should be renamed `filterset_fields`." "`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__
% 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: if filterset_class:
filterset_model = filterset_class._meta.model filterset_model = filterset_class._meta.model
@@ -233,6 +273,51 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
MetaBase = getattr(self.filterset_base, "Meta", object) MetaBase = getattr(self.filterset_base, "Meta", object)
class AutoFilterSet(self.filterset_base): 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 @classmethod
def get_filters(cls): def get_filters(cls):
""" """
@@ -261,9 +346,12 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
if field is None: if field is None:
undefined.append(field_name) undefined.append(field_name)
# 更新默认字符串搜索为模糊搜索 # 更新默认字符串搜索为模糊搜索
if isinstance(field, (models.CharField)) and filterset_fields == '__all__' and lookups == [ if (
'exact']: isinstance(field, (models.CharField))
lookups = ['icontains'] and filterset_fields == "__all__"
and lookups == ["exact"]
):
lookups = ["icontains"]
for lookup_expr in lookups: for lookup_expr in lookups:
filter_name = cls.get_filter_name(field_name, lookup_expr) filter_name = cls.get_filter_name(field_name, lookup_expr)
@@ -273,20 +361,15 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
continue continue
if field is not None: if field is not None:
filters[filter_name] = cls.filter_for_field( filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr)
field, field_name, lookup_expr
)
# Allow Meta.fields to contain declared filters *only* when a list/tuple # Allow Meta.fields to contain declared filters *only* when a list/tuple
if isinstance(cls._meta.fields, (list, tuple)): if isinstance(cls._meta.fields, (list, tuple)):
undefined = [ undefined = [f for f in undefined if f not in cls.declared_filters]
f for f in undefined if f not in cls.declared_filters
]
if undefined: if undefined:
raise TypeError( raise TypeError(
"'Meta.fields' must not contain non-model field names: %s" "'Meta.fields' must not contain non-model field names: %s" % ", ".join(undefined)
% ", ".join(undefined)
) )
# Add in declared filters. This is necessary since we don't enforce adding # Add in declared filters. This is necessary since we don't enforce adding
@@ -308,22 +391,31 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
return queryset return queryset
if filterset.__class__.__name__ == "AutoFilterSet": if filterset.__class__.__name__ == "AutoFilterSet":
queryset = filterset.queryset queryset = filterset.queryset
orm_lookups = [] filter_fields = filterset.filters if self.filter_fields == "__all__" else self.filter_fields
for search_field in filterset.filters: orm_lookup_dict = dict(
if isinstance(filterset.filters[search_field], CharFilter): zip(
orm_lookups.append( [field for field in filter_fields],
self.construct_search(six.text_type(search_field), filterset.filters[search_field].lookup_expr) [filterset.filters[lookup].lookup_expr for lookup in filterset.filters.keys()],
) )
else: )
orm_lookups.append(search_field) orm_lookups = [
self.construct_search(lookup, lookup_expr) for lookup, lookup_expr in orm_lookup_dict.items()
]
# print(orm_lookups)
conditions = [] conditions = []
queries = [] queries = []
for search_term_key in filterset.data.keys(): for search_term_key in filterset.data.keys():
orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key) 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 continue
filterset_data_len = len(filterset.data.getlist(search_term_key))
if filterset_data_len == 1:
query = Q(**{orm_lookup: filterset.data[search_term_key]}) query = Q(**{orm_lookup: filterset.data[search_term_key]})
queries.append(query) 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: if len(queries) > 0:
conditions.append(reduce(operator.and_, queries)) conditions.append(reduce(operator.and_, queries))
queryset = queryset.filter(reduce(operator.and_, conditions)) queryset = queryset.filter(reduce(operator.and_, conditions))

View File

@@ -26,7 +26,7 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
# 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖 # 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖
modifier_field_id = "modifier" modifier_field_id = "modifier"
modifier_name = serializers.SerializerMethodField(read_only=True) 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): def get_modifier_name(self, instance):
if not hasattr(instance, "modifier"): if not hasattr(instance, "modifier"):

View File

@@ -7,16 +7,18 @@
@Remark: 自定义视图集 @Remark: 自定义视图集
""" """
from django.db import transaction 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 import openapi
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.viewsets import ModelViewSet 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.import_export_mixin import ExportSerializerMixin, ImportSerializerMixin
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
from dvadmin.utils.permission import CustomPermission 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 dvadmin.system.models import FieldPermission, MenuField
from django_restql.mixins import QueryArgumentsMixin from django_restql.mixins import QueryArgumentsMixin
@@ -37,7 +39,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
update_serializer_class = None update_serializer_class = None
filter_fields = '__all__' filter_fields = '__all__'
search_fields = () search_fields = ()
extra_filter_class = [DataLevelPermissionsFilter] extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionsFilter]
permission_classes = [CustomPermission] permission_classes = [CustomPermission]
import_field_dict = {} import_field_dict = {}
export_field_label = {} export_field_label = {}

View File

@@ -28,3 +28,4 @@ uvicorn==0.23.2
gunicorn==21.2.0 gunicorn==21.2.0
gevent==23.9.1 gevent==23.9.1
Pillow==10.1.0 Pillow==10.1.0
dvadmin-celery==1.0.5

View File

@@ -13,6 +13,7 @@ services:
- ./docker_env/nginx/my.conf:/etc/nginx/conf.d/my.conf - ./docker_env/nginx/my.conf:/etc/nginx/conf.d/my.conf
expose: expose:
- "8080" - "8080"
restart: always
networks: networks:
network: network:
ipv4_address: 177.10.0.11 ipv4_address: 177.10.0.11
@@ -23,9 +24,8 @@ services:
dockerfile: ./docker_env/django/Dockerfile dockerfile: ./docker_env/django/Dockerfile
container_name: dvadmin3-django container_name: dvadmin3-django
working_dir: /backend working_dir: /backend
# 打开mysql 时,打开此选项 depends_on:
# depends_on: - dvadmin3-mysql
# - dvadmin3-mysql
environment: environment:
PYTHONUNBUFFERED: 1 PYTHONUNBUFFERED: 1
DATABASE_HOST: dvadmin3-mysql DATABASE_HOST: dvadmin3-mysql
@@ -42,74 +42,70 @@ services:
network: network:
ipv4_address: 177.10.0.12 ipv4_address: 177.10.0.12
# dvadmin3-mysql: dvadmin3-mysql:
# image: mysql:5.7 image: mysql:8.0
# container_name: dvadmin3-mysql container_name: dvadmin3-mysql
# #使用该参数container内的root拥有真正的root权限否则container内的root只是外部的一个普通用户权限 privileged: true
# #设置为true不然数据卷可能挂载不了启动不起 restart: always
## privileged: true ports:
# restart: always - "3306:3306"
# ports: environment:
# - "3306:3306" MYSQL_ROOT_PASSWORD: "DVADMIN3"
# environment: MYSQL_DATABASE: "django-vue3-admin"
# MYSQL_ROOT_PASSWORD: "123456" TZ: Asia/Shanghai
# MYSQL_DATABASE: "dvadmin3_pro" command:
# TZ: Asia/Shanghai --wait_timeout=31536000
# command: --interactive_timeout=31536000
# --wait_timeout=31536000 --max_connections=1000
# --interactive_timeout=31536000 --default-authentication-plugin=mysql_native_password
# --max_connections=1000 volumes:
# --default-authentication-plugin=mysql_native_password - "./docker_env/mysql/data:/var/lib/mysql"
# volumes: - "./docker_env/mysql/conf.d:/etc/mysql/conf.d"
# - "./docker_env/mysql/data:/var/lib/mysql" - "./docker_env/mysql/logs:/logs"
# - "./docker_env/mysql/conf.d:/etc/mysql/conf.d" networks:
# - "./docker_env/mysql/logs:/logs" network:
# networks: ipv4_address: 177.10.0.13
# network:
# ipv4_address: 177.10.0.13
# 如果使用celery 插件,请自行打开此注释 dvadmin3-celery:
# dvadmin3-celery: build:
# build: context: .
# context: . dockerfile: ./docker_env/celery/Dockerfile
# dockerfile: ./docker_env/celery/Dockerfile container_name: dvadmin3-celery
# # image: django:2.2 working_dir: /backend
# container_name: dvadmin3-celery depends_on:
# working_dir: /backend - dvadmin3-mysql
# depends_on: environment:
# - dvadmin3-mysql PYTHONUNBUFFERED: 1
# environment: DATABASE_HOST: dvadmin3-mysql
# PYTHONUNBUFFERED: 1 TZ: Asia/Shanghai
# DATABASE_HOST: dvadmin3-mysql volumes:
# TZ: Asia/Shanghai - ./backend:/backend
# volumes: - ./logs/log:/var/log
# - ./backend:/backend restart: always
# - ./logs/log:/var/log networks:
# restart: always network:
# networks: ipv4_address: 177.10.0.14
# network:
# ipv4_address: 177.10.0.14
# dvadmin3-redis: dvadmin3-redis:
# image: redis:6.2.6-alpine # 指定服务镜像最好是与之前下载的redis配置文件保持一致 image: redis:6.2.6-alpine # 指定服务镜像最好是与之前下载的redis配置文件保持一致
# container_name: dvadmin3-redis # 容器名称 container_name: dvadmin3-redis # 容器名称
# restart: on-failure # 重启方式 restart: always
# environment: environment:
# - TZ=Asia/Shanghai # 设置时区 - TZ=Asia/Shanghai # 设置时区
# volumes: # 配置数据卷 volumes: # 配置数据卷
# - ./docker_env/redis/data:/data - ./docker_env/redis/data:/data
# - ./docker_env/redis/redis.conf:/etc/redis/redis.conf - ./docker_env/redis/redis.conf:/etc/redis/redis.conf
# ports: # 映射端口 ports: # 映射端口
# - "6379:6379" - "6379:6379"
# sysctls: # 设置容器中的内核参数 sysctls: # 设置容器中的内核参数
# - net.core.somaxconn=1024 - net.core.somaxconn=1024
# command: /bin/sh -c "echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf && redis-server /etc/redis/redis.conf --appendonly yes" # 指定配置文件并开启持久化 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只是外部的一个普通用户权限 privileged: true # 使用该参数container内的root拥有真正的root权限。否则container内的root只是外部的一个普通用户权限
# networks: networks:
# network: network:
# ipv4_address: 177.10.0.15 ipv4_address: 177.10.0.15
networks: networks:

View File

@@ -1,6 +1,9 @@
FROM registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-backend:latest FROM registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-backend:latest
WORKDIR /backend WORKDIR /backend
COPY ./backend/ . COPY ./backend/ .
RUN ls ./conf/
RUN awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }' 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 RUN python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt
CMD ["/backend/docker_start.sh"] CMD ["/backend/docker_start.sh"]

View File

@@ -2,7 +2,7 @@
ENV = 'development' ENV = 'development'
# 本地环境接口地址 # 本地环境接口地址
VITE_API_URL = 'http://127.0.0.1:8001' VITE_API_URL = 'http://127.0.0.1:8000'
# 是否启用按钮权限 # 是否启用按钮权限
VITE_PM_ENABLED = true VITE_PM_ENABLED = true

View File

@@ -14,6 +14,7 @@
"@fast-crud/fast-extends": "^1.19.2", "@fast-crud/fast-extends": "^1.19.2",
"@fast-crud/ui-element": "^1.19.2", "@fast-crud/ui-element": "^1.19.2",
"@fast-crud/ui-interface": "^1.19.2", "@fast-crud/ui-interface": "^1.19.2",
"@types/lodash": "^4.14.202",
"@vitejs/plugin-vue-jsx": "^3.0.0", "@vitejs/plugin-vue-jsx": "^3.0.0",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
@@ -25,7 +26,7 @@
"echarts": "^5.4.1", "echarts": "^5.4.1",
"echarts-gl": "^2.0.9", "echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.1.0", "echarts-wordcloud": "^2.1.0",
"element-plus": "^2.3.9", "element-plus": "^2.5.5",
"element-tree-line": "^0.2.1", "element-tree-line": "^0.2.1",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",

View File

@@ -0,0 +1,141 @@
<template>
<!-- 你的自定义受控组件-->
<el-select-v2
v-model="data"
:options="options"
style="width: 100%;"
:clearable="true"
:props="selectProps"
@change="onDataChange"
/>
</template>
<script lang="ts" setup>
import {ref, defineComponent, watch, computed, toRefs, toRaw, onMounted} from 'vue'
import {useUi} from "@fast-crud/fast-crud";
import {request} from "/@/utils/service";
const props = defineProps({
dict: { // 接收来自FastCrud配置中的dict数据
type: Array,
required: true,
},
modelValue: {}
})
const emit = defineEmits(['update:modelValue'])
// 获取数据
const dataList = ref([])
function getData(params) {
request({
url: props.dict.url,
params: params
}).then(res => {
dataList.value = res.data
})
}
// template上使用data
const data = ref()
// const data = computed({
// get: () => {
// console.log("有默认值", props.modelValue)
// //getData({id:props.modelValue})
//
// console.log(11, dataList)
// // const {data} = res
// // console.log("get",data[0][selectProps.value.label])
// if (dataList && dataList.length === 1) {
// return dataList[0][selectProps.value.value]
// } else {
// // console.log("aa",res.data)
// return props.modelValue
// }
// // return props.modelValue
// },
// set: (val) => {
// //data.value = val
// return val
// }
// })
const options = ref([])
const selectProps = ref({
label: 'label',
value: 'value'
})
watch(
() => {
return props.modelValue
}, // 监听modelValue的变化
(value) => {
// data.value = value
request({
url: props.dict.url,
params: {
id: props.modelValue
}
}).then(res => {
const dataList = res.data
console.log(dataList)
if (dataList && dataList.length === 1) {
data.value = dataList[0][selectProps.value.label]
}else{
data.value = null
}
})
}, // 当modelValue值触发后同步修改data.value的值
{immediate: true} // 立即触发一次给data赋值初始值
)
//获取表单校验上下文
const {ui} = useUi()
const formValidator = ui.formItem.injectFormItemContext();
// 当data需要变化时上报给父组件
// 父组件监听到update:modelValue事件后会更新props.modelValue的值
// 然后watch会被触发修改data.value的值。
function onDataChange(value) {
emit('update:modelValue', value)
data.value = value
//触发校验
formValidator.onChange()
formValidator.onBlur()
}
if (props.dict.url instanceof Function) {
request(props.dict.url).then((res) => {
options.value = res.data
})
} else {
selectProps.value.label = props.dict.label
selectProps.value.value = props.dict.value
request({
url: props.dict.url
}).then((res) => {
options.value = res.data
})
}
// onMounted(() => {
// getData({id: props.modelValue})
// })
</script>
<style scoped lang="scss">
.el-select .el-input__wrapper .el-input__inner::placeholder {
//color: #a8abb2;
color: #0d84ff;
}
.el-select-v2 {
.el-select-v2__wrapper {
.el-select-v2__placeholder.is-transparent {
//color: #a8abb2;
color: #0d84ff;
}
}
}
</style>

View File

@@ -43,7 +43,7 @@
import {defineProps, onMounted, reactive, ref, toRaw, watch} from 'vue' import {defineProps, onMounted, reactive, ref, toRaw, watch} from 'vue'
import {dict} from '@fast-crud/fast-crud' import {dict} from '@fast-crud/fast-crud'
import XEUtils from 'xe-utils' import XEUtils from 'xe-utils'
import {request} from '/@/utils/service'
const props = defineProps({ const props = defineProps({
modelValue: {}, modelValue: {},
tableConfig: { tableConfig: {
@@ -71,6 +71,7 @@ watch(multipleSelection, // 监听multipleSelection的变化
if (!tableConfig.isMultiple) { if (!tableConfig.isMultiple) {
data.value = value ? value[tableConfig.label] : null data.value = value ? value[tableConfig.label] : null
} else { } else {
const result = value ? value.map((item: any) => { const result = value ? value.map((item: any) => {
return item[tableConfig.label] return item[tableConfig.label]
}) : null }) : null
@@ -123,12 +124,12 @@ const getDict = async () => {
const params = { const params = {
page: pageConfig.page, page: pageConfig.page,
limit: pageConfig.limit, limit: pageConfig.limit,
search: search search: search.value
} }
const dicts = dict({url: url, params: params}) const {data, page, limit, total} = await request({
await dicts.reloadDict() url:url,
const dictData: any = dicts.data params:params
const {data, page, limit, total} = dictData })
pageConfig.page = page pageConfig.page = page
pageConfig.limit = limit pageConfig.limit = limit
pageConfig.total = total pageConfig.total = total

View File

@@ -1,7 +1,6 @@
import type { App } from 'vue'; import type { App } from 'vue';
import { useUserInfo } from '/@/stores/userInfo';
import { judementSameArr } from '/@/utils/arrayOperation'; import { judementSameArr } from '/@/utils/arrayOperation';
import {BtnPermissionStore} from "/@/stores/btnPermission";
/** /**
* 用户权限指令 * 用户权限指令
* @directive 单个权限验证v-auth="xxx" * @directive 单个权限验证v-auth="xxx"
@@ -12,16 +11,16 @@ export function authDirective(app: App) {
// 单个权限验证v-auth="xxx" // 单个权限验证v-auth="xxx"
app.directive('auth', { app.directive('auth', {
mounted(el, binding) { mounted(el, binding) {
const stores = useUserInfo(); const stores = BtnPermissionStore();
if (!stores.userInfos.authBtnList.some((v: string) => v === binding.value)) el.parentNode.removeChild(el); if (!stores.data.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
}, },
}); });
// 多个权限验证满足一个则显示v-auths="[xxx,xxx]" // 多个权限验证满足一个则显示v-auths="[xxx,xxx]"
app.directive('auths', { app.directive('auths', {
mounted(el, binding) { mounted(el, binding) {
let flag = false; let flag = false;
const stores = useUserInfo(); const stores = BtnPermissionStore();
stores.userInfos.authBtnList.map((val: string) => { stores.data.map((val: string) => {
binding.value.map((v: string) => { binding.value.map((v: string) => {
if (val === v) flag = true; if (val === v) flag = true;
}); });
@@ -32,8 +31,8 @@ export function authDirective(app: App) {
// 多个权限验证全部满足则显示v-auth-all="[xxx,xxx]" // 多个权限验证全部满足则显示v-auth-all="[xxx,xxx]"
app.directive('auth-all', { app.directive('auth-all', {
mounted(el, binding) { mounted(el, binding) {
const stores = useUserInfo(); const stores = BtnPermissionStore();
const flag = judementSameArr(binding.value, stores.userInfos.authBtnList); const flag = judementSameArr(binding.value, stores.data);
if (!flag) el.parentNode.removeChild(el); if (!flag) el.parentNode.removeChild(el);
}, },
}); });

View File

@@ -1,7 +1,7 @@
import type { App } from 'vue'; import type { App } from 'vue';
import { authDirective } from '/@/directive/authDirective'; import { authDirective } from '/@/directive/authDirective';
import { wavesDirective, dragDirective } from '/@/directive/customDirective'; import { wavesDirective, dragDirective } from '/@/directive/customDirective';
import {resizeObDirective} from '/@/directive/sizeDirective'
/** /**
* 导出指令方法v-xxx * 导出指令方法v-xxx
* @methods authDirective 用户权限指令用法v-auth * @methods authDirective 用户权限指令用法v-auth
@@ -15,4 +15,6 @@ export function directive(app: App) {
wavesDirective(app); wavesDirective(app);
// 自定义拖动指令 // 自定义拖动指令
dragDirective(app); dragDirective(app);
// 监听窗口大小变化
resizeObDirective(app)
} }

View File

@@ -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); // 停止监听
},
})
}

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange"> <div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
<img :src="logoMini" class="layout-logo-medium-img" /> <img :src="siteLogo" class="layout-logo-medium-img" />
<span style="font-size: x-large">{{ getSystemConfig['login.site_title'] || themeConfig.globalTitle }}</span> <span style="font-size: x-large">{{ getSystemConfig['login.site_title'] || themeConfig.globalTitle }}</span>
</div> </div>
<div class="layout-logo-size" v-else @click="onThemeConfigChange"> <div class="layout-logo-size" v-else @click="onThemeConfigChange">
<img :src="logoMini" class="layout-logo-size-img" /> <img :src="siteLogo" class="layout-logo-size-img" />
</div> </div>
</template> </template>
@@ -14,7 +14,7 @@ import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig'; import { useThemeConfig } from '/@/stores/themeConfig';
import logoMini from '/@/assets/logo-mini.svg'; import logoMini from '/@/assets/logo-mini.svg';
import { SystemConfigStore } from "/@/stores/systemConfig"; import { SystemConfigStore } from "/@/stores/systemConfig";
import _ from "lodash";
// 定义变量内容 // 定义变量内容
const storesThemeConfig = useThemeConfig(); const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig); const { themeConfig } = storeToRefs(storesThemeConfig);
@@ -36,6 +36,13 @@ const getSystemConfig = computed(()=>{
return systemConfig.value return systemConfig.value
}) })
const siteLogo = computed(() => {
if (!_.isEmpty(getSystemConfig.value['login.site_logo'])) {
return getSystemConfig.value['login.site_logo']
}
return logoMini
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -50,30 +57,36 @@ const getSystemConfig = computed(()=>{
font-size: 16px; font-size: 16px;
cursor: pointer; cursor: pointer;
animation: logoAnimation 0.3s ease-in-out; animation: logoAnimation 0.3s ease-in-out;
span { span {
white-space: nowrap; white-space: nowrap;
display: inline-block; display: inline-block;
} }
&:hover { &:hover {
span { span {
color: var(--color-primary-light-2); color: var(--color-primary-light-2);
} }
} }
&-medium-img { &-medium-img {
width: 40px; width: 40px;
margin-right: 5px; margin-right: 5px;
} }
} }
.layout-logo-size { .layout-logo-size {
width: 100%; width: 100%;
height: 50px; height: 50px;
display: flex; display: flex;
cursor: pointer; cursor: pointer;
animation: logoAnimation 0.3s ease-in-out; animation: logoAnimation 0.3s ease-in-out;
&-img { &-img {
width: 40px; width: 40px;
margin: auto; margin: auto;
} }
&:hover { &:hover {
img { img {
animation: logoAnimation 0.3s ease-in-out; animation: logoAnimation 0.3s ease-in-out;

View File

@@ -3,9 +3,7 @@
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition :name="setTransitionName" mode="out-in"> <transition :name="setTransitionName" mode="out-in">
<keep-alive :include="getKeepAliveNames" v-if="showView"> <keep-alive :include="getKeepAliveNames" v-if="showView">
<div>
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" v-show="!isIframePage" /> <component :is="Component" :key="state.refreshRouterViewKey" class="w100" v-show="!isIframePage" />
</div>
</keep-alive> </keep-alive>
</transition> </transition>
</router-view> </router-view>
@@ -61,6 +59,7 @@ const setTransitionName = computed(() => {
}); });
// 获取组件缓存列表(name值) // 获取组件缓存列表(name值)
const getKeepAliveNames = computed(() => { const getKeepAliveNames = computed(() => {
console.log(cachedViews.value)
return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList; return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
}); });
// 设置 iframe 显示/隐藏 // 设置 iframe 显示/隐藏

View File

@@ -1,7 +1,7 @@
import { createApp } from 'vue'; import { createApp } from 'vue';
import App from './App.vue'; import App from './App.vue';
import router from './router'; import router from './router';
import { directive } from '/@/utils/directive'; import { directive } from '/@/directive/index';
import { i18n } from '/@/i18n'; import { i18n } from '/@/i18n';
import other from '/@/utils/other'; import other from '/@/utils/other';
import '/@/assets/style/tailwind.css'; // 先引入tailwind css, 以免element-plus冲突 import '/@/assets/style/tailwind.css'; // 先引入tailwind css, 以免element-plus冲突

View File

@@ -1,39 +0,0 @@
import type { App } from 'vue';
import { judementSameArr } from '/@/utils/arrayOperation';
import {BtnPermissionStore} from "/@/stores/btnPermission";
/**
* 用户权限指令
* @directive 单个权限验证v-auth="xxx"
* @directive 多个权限验证满足一个则显示v-auths="[xxx,xxx]"
* @directive 多个权限验证全部满足则显示v-auth-all="[xxx,xxx]"
*/
export function authDirective(app: App) {
// 单个权限验证v-auth="xxx"
app.directive('auth', {
mounted(el, binding) {
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 = BtnPermissionStore();
stores.data.map((val: string) => {
binding.value.map((v: string) => {
if (val === v) flag = true;
});
});
if (!flag) el.parentNode.removeChild(el);
},
});
// 多个权限验证全部满足则显示v-auth-all="[xxx,xxx]"
app.directive('auth-all', {
mounted(el, binding) {
const stores = BtnPermissionStore();
const flag = judementSameArr(binding.value, stores.data);
if (!flag) el.parentNode.removeChild(el);
},
});
}

View File

@@ -3,8 +3,12 @@ import { pluginsAll } from '/@/views/plugins/index';
/** /**
* @description 校验是否为租户模式。租户模式把域名替换成 域名 加端口 * @description 校验是否为租户模式。租户模式把域名替换成 域名 加端口
*/ */
export const getBaseURL = function (url:string) { export const getBaseURL = function (url: null | string = null, isHost: null | boolean = null) {
let baseURL = import.meta.env.VITE_API_URL as any; let baseURL = import.meta.env.VITE_API_URL as any;
// 如果需要host返回返回地址前缀加http地址
if (isHost && !baseURL.startsWith('http')) {
baseURL = window.location.protocol + '//' + window.location.host + baseURL
}
let param = baseURL.split('/')[3] || ''; let param = baseURL.split('/')[3] || '';
// @ts-ignore // @ts-ignore
if (pluginsAll && pluginsAll.indexOf('dvadmin3-tenants-web') !== -1 && (!param || baseURL.startsWith('/'))) { if (pluginsAll && pluginsAll.indexOf('dvadmin3-tenants-web') !== -1 && (!param || baseURL.startsWith('/'))) {
@@ -31,9 +35,8 @@ export const getBaseURL = function (url:string) {
if (regex.test(url)) { if (regex.test(url)) {
return url return url
} else { } else {
if(url.startsWith('/')){ // js判断是否是斜杠结尾
return baseURL + url; return baseURL.replace(/\/$/, '') + '/' + url.replace(/^\//, '');
}
} }
} }
if (!baseURL.endsWith('/')) { if (!baseURL.endsWith('/')) {

View File

@@ -1,6 +1,7 @@
import {dict} from "@fast-crud/fast-crud"; import {dict} from "@fast-crud/fast-crud";
import {shallowRef} from 'vue' import {shallowRef} from 'vue'
import deptFormat from "/@/components/dept-format/index.vue"; import deptFormat from "/@/components/dept-format/index.vue";
export const commonCrudConfig = (options = { export const commonCrudConfig = (options = {
create_datetime: { create_datetime: {
form: false, form: false,
@@ -132,7 +133,53 @@ export const commonCrudConfig = (options = {
title: '更新时间', title: '更新时间',
type: 'datetime', type: 'datetime',
search: { search: {
show: options.update_datetime?.search || false show: options.update_datetime?.search || false,
col: {span: 8},
component: {
type: 'datetimerange',
props: {
'start-placeholder': '开始时间',
'end-placeholder': '结束时间',
'value-format': 'YYYY-MM-DD HH:mm:ss',
'picker-options': {
shortcuts: [{
text: '最近一周',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近一个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近三个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
}
}
},
valueResolve(context: any) {
const {key, value} = context
//value解析就是把组件的值转化为后台所需要的值
//在form表单点击保存按钮后提交到后台之前执行转化
if (value) {
context.form.update_datetime_after = value[0]
context.form.update_datetime_before = value[1]
}
// ↑↑↑↑↑ 注意这里是form不是row
}
}, },
column: { column: {
width: 160, width: 160,
@@ -149,14 +196,60 @@ export const commonCrudConfig = (options = {
title: '创建时间', title: '创建时间',
type: 'datetime', type: 'datetime',
search: { search: {
show: options.create_datetime?.search || false show: options.create_datetime?.search || false,
col: {span: 8},
component: {
type: 'datetimerange',
props: {
'start-placeholder': '开始时间',
'end-placeholder': '结束时间',
'value-format': 'YYYY-MM-DD HH:mm:ss',
'picker-options': {
shortcuts: [{
text: '最近一周',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近一个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近三个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
}
}
},
valueResolve(context: any) {
const {key, value} = context
//value解析就是把组件的值转化为后台所需要的值
//在form表单点击保存按钮后提交到后台之前执行转化
if (value) {
context.form.create_datetime_after = value[0]
context.form.create_datetime_before = value[1]
}
// ↑↑↑↑↑ 注意这里是form不是row
}
}, },
column: { column: {
width: 160, width: 160,
show: options.create_datetime?.table || false, show: options.create_datetime?.table || false,
}, },
form: { form: {
show: false, show: false
}, },
viewForm: { viewForm: {
show: true show: true

View File

@@ -1,178 +0,0 @@
import type { App } from 'vue';
/**
* 按钮波浪指令
* @directive 默认方式v-waves如 `<div v-waves></div>`
* @directive 参数方式v-waves=" |light|red|orange|purple|green|teal",如 `<div v-waves="'light'"></div>`
*/
export function wavesDirective(app: App) {
app.directive('waves', {
mounted(el, binding) {
el.classList.add('waves-effect');
binding.value && el.classList.add(`waves-${binding.value}`);
function setConvertStyle(obj: { [key: string]: unknown }) {
let style: string = '';
for (let i in obj) {
if (obj.hasOwnProperty(i)) style += `${i}:${obj[i]};`;
}
return style;
}
function onCurrentClick(e: { [key: string]: unknown }) {
let elDiv = document.createElement('div');
elDiv.classList.add('waves-ripple');
el.appendChild(elDiv);
let styles = {
left: `${e.layerX}px`,
top: `${e.layerY}px`,
opacity: 1,
transform: `scale(${(el.clientWidth / 100) * 10})`,
'transition-duration': `750ms`,
'transition-timing-function': `cubic-bezier(0.250, 0.460, 0.450, 0.940)`,
};
elDiv.setAttribute('style', setConvertStyle(styles));
setTimeout(() => {
elDiv.setAttribute(
'style',
setConvertStyle({
opacity: 0,
transform: styles.transform,
left: styles.left,
top: styles.top,
})
);
setTimeout(() => {
elDiv && el.removeChild(elDiv);
}, 750);
}, 450);
}
el.addEventListener('mousedown', onCurrentClick, false);
},
unmounted(el) {
el.addEventListener('mousedown', () => {});
},
});
}
/**
* 自定义拖动指令
* @description 使用方式v-drag="[dragDom,dragHeader]",如 `<div v-drag="['.drag-container .el-dialog', '.drag-container .el-dialog__header']"></div>`
* @description dragDom 要拖动的元素dragHeader 要拖动的 Header 位置
* @link 注意https://github.com/element-plus/element-plus/issues/522
* @lick 参考https://blog.csdn.net/weixin_46391323/article/details/105228020?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-10&spm=1001.2101.3001.4242
*/
export function dragDirective(app: App) {
app.directive('drag', {
mounted(el, binding) {
if (!binding.value) return false;
const dragDom = document.querySelector(binding.value[0]) as HTMLElement;
const dragHeader = document.querySelector(binding.value[1]) as HTMLElement;
dragHeader.onmouseover = () => (dragHeader.style.cursor = `move`);
function down(e: any, type: string) {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = type === 'pc' ? e.clientX - dragHeader.offsetLeft : e.touches[0].clientX - dragHeader.offsetLeft;
const disY = type === 'pc' ? e.clientY - dragHeader.offsetTop : e.touches[0].clientY - dragHeader.offsetTop;
// body当前宽度
const screenWidth = document.body.clientWidth;
// 可见区域高度(应为body高度可某些环境下无法获取)
const screenHeight = document.documentElement.clientHeight;
// 对话框宽度
const dragDomWidth = dragDom.offsetWidth;
// 对话框高度
const dragDomheight = dragDom.offsetHeight;
const minDragDomLeft = dragDom.offsetLeft;
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
const minDragDomTop = dragDom.offsetTop;
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
// 获取到的值带px 正则匹配替换
let styL: any = getComputedStyle(dragDom).left;
let styT: any = getComputedStyle(dragDom).top;
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100);
styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100);
} else {
styL = +styL.replace(/\px/g, '');
styT = +styT.replace(/\px/g, '');
}
return {
disX,
disY,
minDragDomLeft,
maxDragDomLeft,
minDragDomTop,
maxDragDomTop,
styL,
styT,
};
}
function move(e: any, type: string, obj: any) {
let { disX, disY, minDragDomLeft, maxDragDomLeft, minDragDomTop, maxDragDomTop, styL, styT } = obj;
// 通过事件委托,计算移动的距离
let left = type === 'pc' ? e.clientX - disX : e.touches[0].clientX - disX;
let top = type === 'pc' ? e.clientY - disY : e.touches[0].clientY - disY;
// 边界处理
if (-left > minDragDomLeft) {
left = -minDragDomLeft;
} else if (left > maxDragDomLeft) {
left = maxDragDomLeft;
}
if (-top > minDragDomTop) {
top = -minDragDomTop;
} else if (top > maxDragDomTop) {
top = maxDragDomTop;
}
// 移动当前元素
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
}
/**
* pc端
* onmousedown 鼠标按下触发事件
* onmousemove 鼠标按下时持续触发事件
* onmouseup 鼠标抬起触发事件
*/
dragHeader.onmousedown = (e) => {
const obj = down(e, 'pc');
document.onmousemove = (e) => {
move(e, 'pc', obj);
};
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
};
};
/**
* 移动端
* ontouchstart 当按下手指时触发ontouchstart
* ontouchmove 当移动手指时触发ontouchmove
* ontouchend 当移走手指时触发ontouchend
*/
dragHeader.ontouchstart = (e) => {
const obj = down(e, 'app');
document.ontouchmove = (e) => {
move(e, 'app', obj);
};
document.ontouchend = () => {
document.ontouchmove = null;
document.ontouchend = null;
};
};
},
});
}

View File

@@ -1,18 +0,0 @@
import type { App } from 'vue';
import { authDirective } from '/@/utils/authDirective';
import { wavesDirective, dragDirective } from '/@/utils/customDirective';
/**
* 导出指令方法v-xxx
* @methods authDirective 用户权限指令用法v-auth
* @methods wavesDirective 按钮波浪指令用法v-waves
* @methods dragDirective 自定义拖动指令用法v-drag
*/
export function directive(app: App) {
// 用户权限指令
authDirective(app);
// 按钮波浪指令
wavesDirective(app);
// 自定义拖动指令
dragDirective(app);
}

View File

@@ -1,13 +1,80 @@
<template> <template>
<div> <fs-page>
测试框架外显示 <fs-crud ref="crudRef" v-bind="crudBinding">
</div> <template #header-top>
<div id="myEcharts" v-show="isEcharts" v-resize-ob="handleResize" :style="{width: '100%', height: '300px'}"></div>
</template>
</fs-crud>
</fs-page>
</template> </template>
<script setup lang="ts" name="demo"> <script lang="ts" setup name="loginLog">
import { ref, onMounted } from 'vue';
import { useFs } from '@fast-crud/fast-crud';
import { createCrudOptions } from './crud';
import * as echarts from "echarts";
const isEcharts = ref(true)
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions,isEcharts,initChart });
const myEcharts = echarts
function initChart() {
let chart = myEcharts.init(document.getElementById("myEcharts"), "purple-passion");
// 在这里请求API,例如:
/***
* request({url:'xxxx'}).then(res=>{
* // 把chart.setOption写在这里面
*
* })
*
*/
chart.setOption({
title: {
text: "2021年各月份销售量单位",
left: "center",
},
xAxis: {
type: "category",
data: [
"一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"
]
},
tooltip: {
trigger: "axis"
},
yAxis: {
type: "value"
},
series: [
{
data: [
606, 542, 985, 687, 501, 787, 339, 706, 383, 684, 669, 737
],
type: "line",
smooth: true,
itemStyle: {
normal: {
label: {
show: true,
position: "top",
formatter: "{c}"
}
}
}
}
]
});
window.onresize = function () {
chart.resize();
};
}
function handleResize(size) {
console.log(size)
}
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
initChart()
});
</script> </script>
<style scoped>
</style>

View File

@@ -1,5 +1,6 @@
import * as api from './api'; import * as api from './api';
import { UserPageQuery, AddReq, DelReq, EditReq, CreateCrudOptionsProps, CreateCrudOptionsRet, dict } from '@fast-crud/fast-crud'; 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 { export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery) => { const pageRequest = async (query: UserPageQuery) => {
@@ -325,6 +326,11 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
}, },
}, },
}, },
...commonCrudConfig({
create_datetime: {
search: true
}
})
}, },
}, },
}; };

View File

@@ -1,11 +1,12 @@
<template> <template>
<div class="login-container flex"> <div class="login-container flex z-10">
<div class="login-left"> <div class="login-left">
<div class="login-left-logo"> <div class="login-left-logo">
<img :src="logoMini" /> <img :src="siteLogo" />
<div class="login-left-logo-text"> <div class="login-left-logo-text">
<span>{{ getSystemConfig['login.site_title'] || getThemeConfig.globalViceTitle }}</span> <span>{{ getSystemConfig['login.site_title'] || getThemeConfig.globalViceTitle }}</span>
<span class="login-left-logo-text-msg">{{ getSystemConfig['login.site_name']||getThemeConfig.globalViceTitleMsg }}</span> <span class="login-left-logo-text-msg">{{
getSystemConfig['login.site_name'] || getThemeConfig.globalViceTitleMsg }}</span>
</div> </div>
</div> </div>
<div class="login-left-img"> <div class="login-left-img">
@@ -13,12 +14,13 @@
</div> </div>
<img :src="loginBg" class="login-left-waves" /> <img :src="loginBg" class="login-left-waves" />
</div> </div>
<div class="login-right flex"> <div class="login-right flex z-10">
<div class="login-right-warp flex-margin"> <div class="login-right-warp flex-margin">
<span class="login-right-warp-one"></span> <span class="login-right-warp-one"></span>
<span class="login-right-warp-two"></span> <span class="login-right-warp-two"></span>
<div class="login-right-warp-mian"> <div class="login-right-warp-mian">
<div class="login-right-warp-main-title">{{ getSystemConfig['login.site_title'] || getThemeConfig.globalTitle }} 欢迎您</div> <div class="login-right-warp-main-title">{{ getSystemConfig['login.site_title'] ||
getThemeConfig.globalTitle }} 欢迎您</div>
<div class="login-right-warp-main-form"> <div class="login-right-warp-main-form">
<div v-if="!state.isScan"> <div v-if="!state.isScan">
<el-tabs v-model="state.tabsActiveName"> <el-tabs v-model="state.tabsActiveName">
@@ -41,19 +43,26 @@
</div> </div>
</div> </div>
<div class="login-authorization"> <div class="login-authorization z-10">
<p>Copyright © {{ getSystemConfig['login.copyright'] || '2021-2024 django-vue-admin.com' }} 版权所有</p> <p>Copyright © {{ getSystemConfig['login.copyright'] || '2021-2024 django-vue-admin.com' }} 版权所有</p>
<p class="la-other"> <p class="la-other">
<a href="https://beian.miit.gov.cn" target="_blank">{{getSystemConfig['login.keep_record'] || '晋ICP备18005113号-3'}}</a> <a href="https://beian.miit.gov.cn" target="_blank">{{ getSystemConfig['login.keep_record'] ||
'晋ICP备18005113号-3' }}</a>
| |
<a :href="getSystemConfig['login.help_url']?getSystemConfig['login.help_url']:'https://django-vue-admin.com'" target="_blank">帮助</a> <a :href="getSystemConfig['login.help_url'] ? getSystemConfig['login.help_url'] : 'https://django-vue-admin.com'"
target="_blank">帮助</a>
| |
<a :href="getSystemConfig['login.privacy_url']?getBaseURL(getSystemConfig['login.privacy_url']):'#'">隐私</a> <a
:href="getSystemConfig['login.privacy_url'] ? getBaseURL(getSystemConfig['login.privacy_url']) : '#'">隐私</a>
| |
<a :href="getSystemConfig['login.clause_url']?getBaseURL(getSystemConfig['login.clause_url']):'#'">条款</a> <a
:href="getSystemConfig['login.clause_url'] ? getBaseURL(getSystemConfig['login.clause_url']) : '#'">条款</a>
</p> </p>
</div> </div>
</div> </div>
<div v-if="siteBg">
<img :src="siteBg" class="fixed inset-0 z-1 w-full h-full" />
</div>
</template> </template>
<script setup lang="ts" name="loginIndex"> <script setup lang="ts" name="loginIndex">
@@ -70,6 +79,7 @@ import {getBaseURL} from "/@/utils/baseUrl";
const Account = defineAsyncComponent(() => import('/@/views/system/login/component/account.vue')); const Account = defineAsyncComponent(() => import('/@/views/system/login/component/account.vue'));
const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue')); const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
const Scan = defineAsyncComponent(() => import('/@/views/system/login/component/scan.vue')); const Scan = defineAsyncComponent(() => import('/@/views/system/login/component/scan.vue'));
import _ from "lodash";
// 定义变量内容 // 定义变量内容
const storesThemeConfig = useThemeConfig(); const storesThemeConfig = useThemeConfig();
@@ -90,6 +100,18 @@ const getSystemConfig = computed(()=>{
return systemConfig.value return systemConfig.value
}) })
const siteLogo = computed(() => {
if (!_.isEmpty(getSystemConfig.value['login.site_logo'])) {
return getSystemConfig.value['login.site_logo']
}
return logoMini
});
const siteBg = computed(() => {
if (!_.isEmpty(getSystemConfig.value['login.login_background'])) {
return getSystemConfig.value['login.login_background']
}
});
// 页面加载时 // 页面加载时
onMounted(() => { onMounted(() => {

View File

@@ -4,13 +4,18 @@ import tableSelector from '/@/components/tableSelector/index.vue';
import {shallowRef, computed, ref, inject} from 'vue'; import {shallowRef, computed, ref, inject} from 'vue';
import manyToMany from '/@/components/manyToMany/index.vue'; import manyToMany from '/@/components/manyToMany/index.vue';
import {auth} from '/@/utils/authFunction' import {auth} from '/@/utils/authFunction'
import {createCrudOptions as userCrudOptions } from "/@/views/system/user/crud";
import {request} from '/@/utils/service'
const {compute} = useCompute(); const {compute} = useCompute();
interface CreateCrudOptionsTypes { interface CreateCrudOptionsTypes {
crudOptions: CrudOptions; crudOptions: CrudOptions;
} }
export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudExpose: CrudExpose; tabActivted: any }): CreateCrudOptionsTypes { export const createCrudOptions = function ({
crudExpose,
tabActivted
}: { crudExpose: CrudExpose; tabActivted: any }): CreateCrudOptionsTypes {
const pageRequest = async (query: PageQuery) => { const pageRequest = async (query: PageQuery) => {
if (tabActivted.value === 'receive') { if (tabActivted.value === 'receive') {
return await api.GetSelfReceive(query); return await api.GetSelfReceive(query);
@@ -292,6 +297,7 @@ export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudEx
{ {
prop: 'name', prop: 'name',
label: '部门名称', label: '部门名称',
width: 150,
}, },
{ {
prop: 'status_label', prop: 'status_label',

View File

@@ -182,6 +182,7 @@ import { useRouter } from 'vue-router';
import { useUserInfo } from '/@/stores/userInfo'; import { useUserInfo } from '/@/stores/userInfo';
import { successMessage } from '/@/utils/message'; import { successMessage } from '/@/utils/message';
import {dictionary} from "/@/utils/dictionary"; import {dictionary} from "/@/utils/dictionary";
const router = useRouter();
// 头像裁剪组件 // 头像裁剪组件
const avatarSelector = defineAsyncComponent(() => import('/@/components/avatarSelector/index.vue')); const avatarSelector = defineAsyncComponent(() => import('/@/components/avatarSelector/index.vue'));
@@ -333,6 +334,10 @@ const settingPassword = () => {
if (valid) { if (valid) {
api.UpdatePassword(userPasswordInfo).then((res: any) => { api.UpdatePassword(userPasswordInfo).then((res: any) => {
ElMessage.success('密码修改成功'); ElMessage.success('密码修改成功');
setTimeout(() => {
Session.remove('token');
router.push('/login');
}, 1000);
}); });
} else { } else {
// 校验失败 // 校验失败

View File

@@ -6,7 +6,7 @@
<template #header> <template #header>
<el-row> <el-row>
<el-col :span="4"> <el-col :span="4">
<div>当前角色: <div>当前授权角色:
<el-tag>{{ props.roleName }}</el-tag> <el-tag>{{ props.roleName }}</el-tag>
</div> </div>
</el-col> </el-col>
@@ -49,7 +49,7 @@
</el-checkbox> </el-checkbox>
</div> </div>
<div class="pccm-item"> <div class="pccm-item" v-if="item.columns&&item.columns.length>0">
<p>对这些数据有以下字段权限</p> <p>对这些数据有以下字段权限</p>
<ul class="columns-list"> <ul class="columns-list">

View File

@@ -4,6 +4,7 @@ import { dictionary } from '/@/utils/dictionary';
import {columnPermission} from '../../../utils/columnPermission'; import {columnPermission} from '../../../utils/columnPermission';
import {successMessage} from '../../../utils/message'; import {successMessage} from '../../../utils/message';
import {auth} from '/@/utils/authFunction' import {auth} from '/@/utils/authFunction'
interface CreateCrudOptionsTypes { interface CreateCrudOptionsTypes {
output: any; output: any;
crudOptions: CrudOptions; crudOptions: CrudOptions;
@@ -45,6 +46,9 @@ export const createCrudOptions = function ({
editRequest, editRequest,
delRequest, delRequest,
}, },
pagination: {
show: true
},
actionbar: { actionbar: {
buttons: { buttons: {
add: { add: {
@@ -114,13 +118,6 @@ export const createCrudOptions = function ({
column: { column: {
minWidth: 120, minWidth: 120,
sortable: 'custom', sortable: 'custom',
show: columnPermission('name', 'is_query'),
},
// addForm: {
// show: columnPermission('name', 'is_create'),
// },
editForm: {
show: columnPermission('name', 'is_update'),
}, },
form: { form: {
rules: [{required: true, message: '角色名称必填'}], rules: [{required: true, message: '角色名称必填'}],
@@ -136,15 +133,8 @@ export const createCrudOptions = function ({
column: { column: {
minWidth: 120, minWidth: 120,
sortable: 'custom', sortable: 'custom',
show: columnPermission('key', 'is_query'),
columnSetDisabled: true, columnSetDisabled: true,
}, },
addForm: {
show: columnPermission('key', 'is_create'),
},
editForm: {
show: columnPermission('key', 'is_update'),
},
form: { form: {
rules: [{required: true, message: '权限标识必填'}], rules: [{required: true, message: '权限标识必填'}],
component: { component: {
@@ -164,12 +154,6 @@ export const createCrudOptions = function ({
minWidth: 90, minWidth: 90,
sortable: 'custom', sortable: 'custom',
}, },
addForm: {
show: columnPermission('sort', 'is_create'),
},
editForm: {
show: columnPermission('sort', 'is_update'),
},
form: { form: {
rules: [{required: true, message: '排序必填'}], rules: [{required: true, message: '排序必填'}],
value: 1, value: 1,
@@ -194,61 +178,11 @@ export const createCrudOptions = function ({
}; };
}), }),
}, },
show: columnPermission('status', 'is_query'),
},
addForm: {
show: columnPermission('status', 'is_create'),
},
editForm: {
show: columnPermission('status', 'is_update'),
}, },
dict: dict({ dict: dict({
data: dictionary('button_status_bool'), data: dictionary('button_status_bool'),
}), }),
}, }
update_datetime: {
title: '更新时间',
type: 'text',
search: { show: false },
column: {
minWidth: 170,
sortable: 'custom',
show: columnPermission('update_datetime', 'is_query'),
},
form: {
show: false,
component: {
placeholder: '输入关键词搜索',
},
},
},
create_datetime: {
title: '创建时间',
type: 'text',
search: { show: false },
column: {
sortable: 'custom',
minWidth: 170,
show: columnPermission('create_datetime', 'is_query'),
},
form: {
show: false,
component: {
placeholder: '输入关键词搜索',
},
},
},
// description: {
// title: '备注',
// type: 'textarea',
// search: {show: false},
// form: {
// component: {
// maxlength: 200,
// placeholder: '输入备注',
// },
// },
// },
}, },
}, },
}; };

View File

@@ -47,21 +47,21 @@ const { crudExpose } = useExpose({ crudRef, crudBinding });
// 你的crud配置 // 你的crud配置
const { crudOptions } = createCrudOptions({ crudExpose, rolePermission, handleDrawerOpen }); const { crudOptions } = createCrudOptions({ crudExpose, rolePermission, handleDrawerOpen });
// 页面打开后获取列表数据
onMounted( async () => {
const newOptions = await handleColumnPermission(GetPermission,crudOptions)
// 初始化crud配置 // 初始化crud配置
const { resetCrudOptions } = useCrud({ const { resetCrudOptions } = useCrud({
crudExpose, crudExpose,
crudOptions, crudOptions,
context: {}, context: {},
}); });
// 页面打开后获取列表数据
onMounted( async () => {
const newOptions = await handleColumnPermission(GetPermission,crudOptions)
//重置crudBinding //重置crudBinding
resetCrudOptions(newOptions); //resetCrudOptions(newOptions);
crudExpose.doRefresh(); crudExpose.doRefresh();
}); });

View File

@@ -372,6 +372,9 @@ export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps)
form: { form: {
show: false, show: false,
}, },
column: {
minWidth: 400, //最小列宽
},
}, },
...commonCrudConfig({ ...commonCrudConfig({
dept_belong_id: { dept_belong_id: {