!100 正式发布v3.1.0版本

1.[新增] 文件选择器添加插槽功能
2.[新增] 初次登录必须修改密码功能
3.[新增] 下载中心模块和异步导出功能
4.[新增] 批量删除功能(示例代码在菜单管理里面)
5.[新增] 角色批量授权用户功能
6.[优化] 菜单排序逻辑
7.[优化] 优化列权限逻辑
8.[修复] 允许路由在框架外显示 bug
9.[修复] 用户管理crud无法显示用户头像问题
10.[修复] 字段筛选排除 file文件字段
11.[修复] 更新import_export.py已过滤空数据表
12.[修复] 日期期间条件过滤不包含截止日期当天数据的bug

后续规划
1.审批流插件正在测试中
2.报表插件规划中
3.新版文档规划中
This commit is contained in:
dvadmin
2025-01-07 13:44:34 +00:00
committed by Gitee
36 changed files with 1640 additions and 1563 deletions

View File

@@ -404,7 +404,7 @@ PLUGINS_URL_PATTERNS = []
# ********** 一键导入插件配置开始 **********
# 例如:
# from dvadmin_upgrade_center.settings import * # 升级中心
from dvadmin3_celery.settings import * # celery 异步任务
# from dvadmin3_celery.settings import * # celery 异步任务
# from dvadmin_third.settings import * # 第三方用户管理
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
# from dvadmin_tenants.settings import * # 租户管理

View File

@@ -41,6 +41,9 @@ class DownloadCenterViewSet(CustomModelViewSet):
serializer_class = DownloadCenterSerializer
filter_class = DownloadCenterFilterSet
permission_classes = []
extra_filter_class = []
def get_queryset(self):
if self.request.user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(creator=self.request.user)

View File

@@ -34,10 +34,6 @@ class FileSerializer(CustomModelSerializer):
model = FileList
fields = "__all__"
class Meta:
model = FileList
fields = "__all__"
def create(self, validated_data):
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local'
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup")

View File

@@ -26,6 +26,12 @@ class RoleSerializer(CustomModelSerializer):
"""
角色-序列化器
"""
users = serializers.SerializerMethodField()
@staticmethod
def get_users(instance):
users = instance.users_set.exclude(id=1).values('id', 'name', 'dept__name')
return users
class Meta:
model = Role
@@ -116,3 +122,23 @@ class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin):
create_serializer_class = RoleCreateUpdateSerializer
update_serializer_class = RoleCreateUpdateSerializer
search_fields = ['name', 'key']
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
def set_role_users(self, request, pk):
"""
设置 角色-用户
:param request:
:return:
"""
data = request.data
direction = data.get('direction')
movedKeys = data.get('movedKeys')
role = Role.objects.get(pk=pk)
if direction == "left":
# left : 移除用户权限
role.users_set.remove(*movedKeys)
else:
# right : 添加用户权限
role.users_set.add(*movedKeys)
serializer = RoleSerializer(role)
return DetailResponse(data=serializer.data, msg="更新成功")

View File

@@ -6,24 +6,20 @@
@Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理
"""
from django.db.models import F, Subquery, OuterRef, Exists, BooleanField, Q, Case, Value, When
from django.db.models.functions import Coalesce
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.fields import ListField
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, FieldPermission, \
MenuField
from dvadmin.system.views.menu import MenuSerializer
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
from dvadmin.system.models import RoleMenuButtonPermission, Menu, Dept, MenuButton, RoleMenuPermission, \
MenuField, FieldPermission
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
"""
菜单按钮-序列化
角色-菜单-按钮-权限 查询序列化
"""
class Meta:
@@ -34,7 +30,7 @@ class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化
角色-菜单-按钮-权限 创建/修改序列化
"""
menu_button__name = serializers.CharField(source='menu_button.name', read_only=True)
menu_button__value = serializers.CharField(source='menu_button.value', read_only=True)
@@ -45,63 +41,99 @@ class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class RoleButtonPermissionSerializer(CustomModelSerializer):
class RoleMenuSerializer(CustomModelSerializer):
"""
角色按钮权限
角色-菜单 序列化
"""
isCheck = serializers.SerializerMethodField()
data_range = serializers.SerializerMethodField()
def get_isCheck(self, instance):
params = self.request.query_params
data = self.request.data
return RoleMenuPermission.objects.filter(
menu_id=instance.id,
role_id=params.get('roleId', data.get('roleId')),
).exists()
class Meta:
model = Menu
fields = ["id", "name", "parent", "is_catalog", "isCheck"]
class RoleMenuButtonSerializer(CustomModelSerializer):
"""
角色-菜单-按钮 序列化
"""
isCheck = serializers.SerializerMethodField()
data_range = serializers.SerializerMethodField()
role_menu_btn_perm_id = serializers.SerializerMethodField()
dept = serializers.SerializerMethodField()
def get_isCheck(self, instance):
params = self.request.query_params
data = self.request.data
return RoleMenuButtonPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
menu_button_id=instance.id,
role_id=params.get('roleId', data.get('roleId')),
).exists()
def get_data_range(self, instance):
params = self.request.query_params
obj = RoleMenuButtonPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
).first()
obj = self.get_role_menu_btn_prem(instance)
if obj is None:
return None
return obj.data_range
def get_role_menu_btn_perm_id(self, instance):
obj = self.get_role_menu_btn_prem(instance)
if obj is None:
return None
return obj.id
def get_dept(self, instance):
obj = self.get_role_menu_btn_prem(instance)
if obj is None:
return None
return obj.dept.all().values_list('id', flat=True)
def get_role_menu_btn_prem(self, instance):
params = self.request.query_params
data = self.request.data
obj = RoleMenuButtonPermission.objects.filter(
menu_button_id=instance.id,
role_id=params.get('roleId', data.get('roleId')),
).first()
return obj
class Meta:
model = MenuButton
fields = ['id', 'name', 'value', 'isCheck', 'data_range']
class RoleFieldPermissionSerializer(CustomModelSerializer):
class Meta:
model = FieldPermission
fields = "__all__"
fields = ['id', 'menu', 'name', 'isCheck', 'data_range', 'role_menu_btn_perm_id', 'dept']
class RoleMenuFieldSerializer(CustomModelSerializer):
"""
角色-菜单-字段 序列化
"""
is_query = serializers.SerializerMethodField()
is_create = serializers.SerializerMethodField()
is_update = serializers.SerializerMethodField()
def get_is_query(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
queryset = instance.menu_field.filter(role=params.get('roleId')).first()
if queryset:
return queryset.is_query
return False
def get_is_create(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
queryset = instance.menu_field.filter(role=params.get('roleId')).first()
if queryset:
return queryset.is_create
return False
def get_is_update(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
queryset = instance.menu_field.filter(role=params.get('roleId')).first()
if queryset:
return queryset.is_update
return False
@@ -111,54 +143,6 @@ class RoleMenuFieldSerializer(CustomModelSerializer):
fields = ['id', 'field_name', 'title', 'is_query', 'is_create', 'is_update']
class RoleMenuSerializer(CustomModelSerializer):
menus = serializers.SerializerMethodField()
def get_menus(self, instance):
menu_list = Menu.objects.filter(parent=instance['id']).values('id', 'name')
serializer = RoleMenuPermissionSerializer(menu_list, many=True, request=self.request)
return serializer.data
class Meta:
model = Menu
fields = ['id', 'name', 'menus']
class RoleMenuPermissionSerializer(CustomModelSerializer):
"""
菜单和按钮权限
"""
# name = serializers.SerializerMethodField()
isCheck = serializers.SerializerMethodField()
btns = serializers.SerializerMethodField()
columns = serializers.SerializerMethodField()
# def get_name(self, instance):
# parent_list = Menu.get_all_parent(instance['id'])
# names = [d["name"] for d in parent_list]
# return "/".join(names)
def get_isCheck(self, instance):
params = self.request.query_params
return RoleMenuPermission.objects.filter(
menu__id=instance['id'],
role__id=params.get('role'),
).exists()
def get_btns(self, instance):
btn_list = MenuButton.objects.filter(menu__id=instance['id']).values('id', 'name', 'value')
serializer = RoleButtonPermissionSerializer(btn_list, many=True, request=self.request)
return serializer.data
def get_columns(self, instance):
col_list = MenuField.objects.filter(menu=instance['id'])
serializer = RoleMenuFieldSerializer(col_list, many=True, request=self.request)
return serializer.data
class Meta:
model = Menu
fields = ['id', 'name', 'isCheck', 'btns', 'columns']
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
"""
菜单按钮接口
@@ -174,202 +158,103 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
extra_filter_class = []
# @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
# def get_role_premission(self, request):
# """
# 角色授权获取:
# :param request: role
# :return: menu,btns,columns
# """
# params = request.query_params
# is_superuser = request.user.is_superuser
# if is_superuser:
# queryset = Menu.objects.filter(status=1, is_catalog=True).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('menu__id', flat=True)
# queryset = Menu.objects.filter(status=1, is_catalog=True, id__in=menu_list).values('name', 'id').all()
# serializer = RoleMenuSerializer(queryset, many=True, request=request)
# data = serializer.data
# return DetailResponse(data=data)
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def get_role_menu(self, request):
"""
获取 角色-菜单
:param request:
:return:
"""
menu_queryset = Menu.objects.all()
serializer = RoleMenuSerializer(menu_queryset, many=True, request=request)
return DetailResponse(data=serializer.data)
@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])
def set_role_menu(self, request):
"""
设置 角色-菜单
:param request:
:return:
"""
data = request.data
roleId = data.get('roleId')
menuId = data.get('menuId')
isCheck = data.get('isCheck')
if isCheck:
# 添加权限:创建关联记录
instance = RoleMenuPermission.objects.create(role_id=roleId, menu_id=menuId)
else:
# 删除权限:移除关联记录
RoleMenuPermission.objects.filter(role_id=roleId, menu_id=menuId).delete()
menu_instance = Menu.objects.get(id=menuId)
serializer = RoleMenuSerializer(menu_instance, request=request)
return DetailResponse(data=serializer.data, msg="更新成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def get_role_permission(self, request):
def get_role_menu_btn_field(self, request):
"""
获取 角色-菜单-按钮-列字段
:param request:
:return:
"""
params = request.query_params
# 需要授权的角色信息
current_role = params.get('role', None)
# 当前登录用户的角色
role_list = request.user.role.values_list('id', flat=True)
if current_role is None:
return ErrorResponse(msg='参数错误')
is_superuser = request.user.is_superuser
if is_superuser:
menu_queryset = Menu.objects.prefetch_related('menuPermission').prefetch_related(
'menufield_set')
else:
role_id_list = request.user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_id_list).values_list('menu__id', flat=True)
# 当前角色已授权的菜单
menu_queryset = Menu.objects.filter(id__in=menu_list).prefetch_related('menuPermission').prefetch_related(
'menufield_set')
result = []
for menu_item in menu_queryset:
isCheck = RoleMenuPermission.objects.filter(
menu_id=menu_item.id,
role_id=current_role
).exists()
dicts = {
'name': menu_item.name,
'id': menu_item.id,
'parent': menu_item.parent.id if menu_item.parent else None,
'isCheck': isCheck,
'btns': [],
'columns': []
}
for mb_item in menu_item.menuPermission.all():
rolemenubuttonpermission_queryset = RoleMenuButtonPermission.objects.filter(
menu_button_id=mb_item.id,
role_id=current_role
).first()
dicts['btns'].append(
{
'id': mb_item.id,
'name': mb_item.name,
'value': mb_item.value,
'data_range': rolemenubuttonpermission_queryset.data_range
if rolemenubuttonpermission_queryset
else None,
'isCheck': bool(rolemenubuttonpermission_queryset),
'dept': rolemenubuttonpermission_queryset.dept.all().values_list('id', flat=True)
if rolemenubuttonpermission_queryset
else [],
}
)
for column_item in menu_item.menufield_set.all():
# 需要授权角色已拥有的列权限
fieldpermission_queryset = column_item.menu_field.filter(role_id=current_role).first()
is_query = fieldpermission_queryset.is_query if fieldpermission_queryset else False
is_create = fieldpermission_queryset.is_create if fieldpermission_queryset else False
is_update = fieldpermission_queryset.is_update if fieldpermission_queryset else False
# 当前登录用户角色可分配的列权限
fieldpermission_queryset_disabled = column_item.menu_field.filter(role_id__in=role_list).first()
disabled_query = fieldpermission_queryset_disabled.is_query if fieldpermission_queryset_disabled else True
disabled_create = fieldpermission_queryset_disabled.is_create if fieldpermission_queryset_disabled else True
disabled_update = fieldpermission_queryset_disabled.is_update if fieldpermission_queryset_disabled else True
dicts['columns'].append({
'id': column_item.id,
'field_name': column_item.field_name,
'title': column_item.title,
'is_query': is_query,
'is_create': is_create,
'is_update': is_update,
'disabled_query': False if is_superuser else not disabled_query,
'disabled_create': False if is_superuser else not disabled_create,
'disabled_update': False if is_superuser else not disabled_update,
})
result.append(dicts)
return DetailResponse(data=result)
menuId = params.get('menuId', None)
menu_btn_queryset = MenuButton.objects.filter(menu_id=menuId)
menu_btn_serializer = RoleMenuButtonSerializer(menu_btn_queryset, many=True, request=request)
menu_field_queryset = MenuField.objects.filter(menu_id=menuId)
menu_field_serializer = RoleMenuFieldSerializer(menu_field_queryset, many=True, request=request)
return DetailResponse(data={'menu_btn': menu_btn_serializer.data, 'menu_field': menu_field_serializer.data})
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
def set_role_premission(self, request, pk):
def set_role_menu_field(self, request, pk):
"""
角色菜单和按钮及按钮范围授权:
:param request:
:param pk: role
:return:
设置 角色-菜单-列字段
"""
body = request.data
RoleMenuPermission.objects.filter(role=pk).delete()
RoleMenuButtonPermission.objects.filter(role=pk).delete()
for item in body:
if item.get('isCheck'):
RoleMenuPermission.objects.create(role_id=pk, menu_id=item["id"])
for btn in item.get('btns'):
if btn.get('isCheck'):
data_range = btn.get('data_range', 0) or 0
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),
data_range=data_range)
instance.dept.set(btn.get('dept', []))
for col in item.get('columns'):
FieldPermission.objects.update_or_create(role_id=pk, field_id=col.get('id'),
data = request.data
for col in data:
FieldPermission.objects.update_or_create(
role_id=pk, field_id=col.get('id'),
defaults={
'is_query': col.get('is_query'),
'is_create': col.get('is_create'),
'is_update': col.get('is_update')
'is_update': col.get('is_update'),
'is_query': col.get('is_query'),
})
return DetailResponse(msg="授权成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def role_menu_get_button(self, request):
"""
当前用户角色和菜单获取可下拉选项的按钮:角色授权页面使用
:param request:
:return:
"""
if params := request.query_params:
if menu_id := params.get('menu', None):
is_superuser = request.user.is_superuser
if is_superuser:
queryset = MenuButton.objects.filter(menu=menu_id).values('id', 'name')
else:
role_list = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(
role__in=role_list, menu_button__menu=menu_id
).values(btn_id=F('menu_button__id'), name=F('menu_button__name'))
return DetailResponse(data=queryset)
return ErrorResponse(msg="参数错误")
return DetailResponse(data=[], msg="更新成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def data_scope(self, request):
@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])
def set_role_menu_btn(self, request):
"""
获取数据权限范围:角色授权页面使用
:param request:
:return:
设置 角色-菜单-按钮
"""
is_superuser = request.user.is_superuser
if is_superuser:
data = [
{"value": 0, "label": '仅本人数据权限'},
{"value": 1, "label": '本部门及以下数据权限'},
{"value": 2, "label": '本部门数据权限'},
{"value": 3, "label": '全部数据权限'},
{"value": 4, "label": '自定义数据权限'}
]
return DetailResponse(data=data)
data = request.data
isCheck = data.get('isCheck', None)
roleId = data.get('roleId', None)
btnId = data.get('btnId', None)
if isCheck:
# 添加权限:创建关联记录
RoleMenuButtonPermission.objects.create(role_id=roleId, menu_button_id=btnId)
else:
params = request.query_params
data = [{"value": 0, "label": '仅本人数据权限'}]
role_list = request.user.role.values_list('id', flat=True)
# 权限页面进入初始化获取所有的数据权限范围
role_queryset = RoleMenuButtonPermission.objects.filter(
role__in=role_list
).values_list('data_range', flat=True)
# 通过按钮小齿轮获取指定按钮的权限
if menu_button_id := params.get('menu_button', None):
role_queryset = RoleMenuButtonPermission.objects.filter(
role__in=role_list, menu_button__id=menu_button_id
).values_list('data_range', flat=True)
# 删除权限:移除关联记录
RoleMenuButtonPermission.objects.filter(role_id=roleId, menu_button_id=btnId).delete()
menu_btn_instance = MenuButton.objects.get(id=btnId)
serializer = RoleMenuButtonSerializer(menu_btn_instance, request=request)
return DetailResponse(data=serializer.data, msg="更新成功")
data_range_list = list(set(role_queryset))
for item in data_range_list:
if item == 0:
data = data
elif item == 1:
data.extend([
{"value": 1, "label": '本部门及以下数据权限'},
{"value": 2, "label": '本部门数据权限'}
])
elif item == 2:
data.extend([{"value": 2, "label": '本部门数据权限'}])
elif item == 3:
data.extend([{"value": 3, "label": '全部数据权限'}])
elif item == 4:
data.extend([{"value": 4, "label": '自定义数据权限'}])
else:
data = []
return DetailResponse(data=data)
@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])
def set_role_menu_btn_data_range(self, request):
"""
设置 角色-菜单-按钮-权限
"""
data = request.data
instance = RoleMenuButtonPermission.objects.get(id=data.get('role_menu_btn_perm_id'))
instance.data_range = data.get('data_range')
instance.dept.add(*data.get('dept'))
if not data.get('dept'):
instance.dept.clear()
instance.save()
serializer = RoleMenuButtonPermissionSerializer(instance, request=request)
return DetailResponse(data=serializer.data, msg="更新成功")
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def role_to_dept_all(self, request):
@@ -395,55 +280,3 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
dept["disabled"] = False if is_superuser else dept["id"] not in dept_checked_disabled
data.append(dept)
return DetailResponse(data=data)
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def menu_to_button(self, request):
"""
根据所选择菜单获取已配置的按钮/接口权限:角色授权页面使用
:param request:
:return:
"""
params = request.query_params
menu_id = params.get('menu', None)
if menu_id is None:
return ErrorResponse(msg="未获取到参数")
is_superuser = request.user.is_superuser
if is_superuser:
queryset = RoleMenuButtonPermission.objects.filter(menu_button__menu=menu_id).values(
'id',
'data_range',
'menu_button',
'menu_button__name',
'menu_button__value'
)
return DetailResponse(data=queryset)
else:
if params:
role_id = params.get('role', None)
if role_id is None:
return ErrorResponse(msg="未获取到参数")
queryset = RoleMenuButtonPermission.objects.filter(role=role_id, menu_button__menu=menu_id).values(
'id',
'data_range',
'menu_button',
'menu_button__name',
'menu_button__value'
)
return DetailResponse(data=queryset)
return ErrorResponse(msg="未获取到参数")
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def role_to_menu(self, request):
"""
获取角色对应的按钮权限
:param request:
:return:
"""
params = request.query_params
role_id = params.get('role', None)
if role_id is None:
return ErrorResponse(msg="未获取到参数")
queryset = RoleMenuPermission.objects.filter(role_id=role_id).values_list('menu_id', flat=True).distinct()
return DetailResponse(data=queryset)

View File

@@ -1,14 +1,25 @@
# -*- coding: utf-8 -*-
from itertools import groupby
from django.db.models import F
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import FieldPermission, MenuField
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.models import get_custom_app_models
def merge_permission(data):
"""
合并权限
"""
result = {}
for item in data:
field_name = item.pop('field_name')
if field_name not in result:
result[field_name] = item
else:
for key, value in item.items():
result[field_name][key] = result[field_name][key] or value
return result
class FieldPermissionMixin:
@@ -17,55 +28,16 @@ class FieldPermissionMixin:
"""
获取字段权限
"""
finded = False
for model in get_custom_app_models():
if model['object'] is self.serializer_class.Meta.model:
finded = True
break
if finded:
break
if finded is False:
return []
model = self.serializer_class.Meta.model.__name__
user = request.user
# 创建一个默认字典来存储最终的结果
if user.is_superuser == 1:
data = MenuField.objects.filter( model=model['model']).values('field_name')
for item in data:
item['is_create'] = True
item['is_query'] = True
item['is_update'] = True
data = MenuField.objects.filter(model=model).values('field_name')
result = {item['field_name']: {"is_create": True, "is_query": True, "is_update": True} for item in data}
else:
roles = request.user.role.values_list('id', flat=True)
data = FieldPermission.objects.filter(
field__model=model['model'],role__in=roles
field__model=model, role__in=roles
).values('is_create', 'is_query', 'is_update', field_name=F('field__field_name'))
"""
合并权限
这段代码首先根据 field_name 对列表进行排序,
然后使用 groupby 按 field_name 进行分组。
对于每个组,它创建一个新的字典 merged
并遍历组中的每个字典将布尔值字段使用逻辑或or操作符进行合并如果 merged 中还没有该字段,则默认为 False
其他字段(如 field_name则直接取组的关键字即 key
"""
# 使用field_name对列表进行分组, # groupby 需要先对列表进行排序,因为它只能对连续相同的元素进行分组。
grouped = groupby(sorted(list(data), key=lambda x: x['field_name']), key=lambda x: x['field_name'])
data = []
# 遍历分组,合并权限
for key, group in grouped:
# 初始化一个空字典来存储合并后的结果
merged = {}
for item in group:
# 合并权限, True值优先
merged['is_create'] = merged.get('is_create', False) or item['is_create']
merged['is_query'] = merged.get('is_query', False) or item['is_query']
merged['is_update'] = merged.get('is_update', False) or item['is_update']
merged['field_name'] = key
data.append(merged)
return DetailResponse(data=data)
result = merge_permission(data)
return DetailResponse(data=result)

View File

@@ -340,7 +340,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
from timezone_field import TimeZoneField
# 不进行 过滤的model 类
if isinstance(field, (models.JSONField, TimeZoneField)):
if isinstance(field, (models.JSONField, TimeZoneField, models.FileField)):
continue
# warn if the field doesn't exist.
if field is None:

View File

@@ -86,4 +86,5 @@ def import_to_data(file_url, field_data, m2m_fields=None):
else:
array[key] = cell_value
tables.append(array)
return tables
data = [i for i in tables if len(i) != 0]
return data

View File

@@ -305,11 +305,10 @@ class ExportSerializerMixin:
assert self.export_serializer_class, "'%s' 请配置对应的导出序列化器。" % self.__class__.__name__
data = self.export_serializer_class(queryset, many=True, request=request).data
try:
from dvadmin3_celery import settings
async_export_data.delay(
data,
str(f"导出{get_verbose_name(queryset)}-{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.xlsx"),
DownloadCenter.objects.create(creator=request.user, task_name=f'{get_verbose_name(queryset)}数据导出任务').pk,
DownloadCenter.objects.create(creator=request.user, task_name=f'{get_verbose_name(queryset)}数据导出任务', dept_belong_id=request.user.dept_id).pk,
self.export_field_label
)
return SuccessResponse(msg="导入任务已创建,请前往‘下载中心’等待下载")

View File

@@ -66,7 +66,7 @@
</div>
</div>
</slot>
<el-dialog v-model="selectVisiable" :draggable="false" width="50%" :align-center="false" :append-to-body="true"
<el-dialog v-model="selectVisiable" :draggable="true" width="50%" :align-center="false" :append-to-body="true"
@open="if (listData.length === 0) listRequest();" @close="onClose" @closed="onClosed" modal-class="_overlay">
<template #header>
<span class="el-dialog__title">文件选择</span>
@@ -248,7 +248,7 @@ const listRequest = async () => {
});
listData.value = [];
await nextTick();
listData.value = res.data;
listData.value = (res.data as any[]).map((item: any) => ({ ...item, url: getBaseURL(item.url) }));
pageForm.total = res.total;
pageForm.page = res.page;
pageForm.limit = res.limit;
@@ -277,8 +277,10 @@ const onItemClick = async (e: MouseEvent) => {
if (target.classList.contains('active')) { target.classList.remove('active'); flat = -1; }
else { target.classList.add('active'); flat = 1; }
if (data.value.length) {
if (flat === 1) data.value.push(fileId);
else data.value.splice(data.value.indexOf(fileId), 1);
let _l = JSON.parse(JSON.stringify(data.value));
if (flat === 1) _l.push(fileId);
else _l.splice(_l.indexOf(fileId), 1);
data.value = _l;
} else data.value = [fileId];
// 去重排序,<降序,>升序
data.value = Array.from(new Set(data.value)).sort();
@@ -378,7 +380,7 @@ const data = ref<any>(null);
const emit = defineEmits(['update:modelValue', 'onSave', 'onClose', 'onClosed']);
watch(
() => props.modelValue,
(val) => data.value = val,
(val) => data.value = props.multiple ? JSON.parse(JSON.stringify(val)) : val,
{ immediate: true }
);
const { ui } = useUi();

View File

@@ -2,6 +2,9 @@ import { defineStore } from 'pinia';
import { UserInfosStates } from './interface';
import { Session } from '/@/utils/storage';
import { request } from '../utils/service';
import { getBaseURL } from '../utils/baseUrl';
import headerImage from '/@/assets/img/headerImage.png';
/**
* 用户信息
* @methods setUserInfos 设置用户信息
@@ -72,7 +75,7 @@ export const useUserInfo = defineStore('userInfo', {
method: 'get',
}).then((res:any)=>{
this.userInfos.username = res.data.name;
this.userInfos.avatar = res.data.avatar;
this.userInfos.avatar = (res.data.avatar && getBaseURL(res.data.avatar)) || headerImage;
this.userInfos.name = res.data.name;
this.userInfos.email = res.data.email;
this.userInfos.mobile = res.data.mobile;

View File

@@ -1,3 +1,4 @@
import XEUtils from 'xe-utils';
import {useColumnPermission} from '/@/stores/columnPermission';
type permissionType = 'is_create' | 'is_query' | 'is_update';
@@ -23,25 +24,17 @@ export const handleColumnPermission = async (func: Function, crudOptions: any,ex
}
const columns = crudOptions.columns;
const excludeColumns = ['checked','_index','id', 'create_datetime', 'update_datetime'].concat(excludeColumn)
for (let col in columns) {
for (let item of res.data) {
if (excludeColumns.includes(item.field_name)) {
continue
} else if(item.field_name === col) {
XEUtils.eachTree(columns, (item, key) => {
if (!excludeColumns.includes(String(key)) && key in res.data) {
// 如果列表不可见,则禁止在列设置中选择
// 只有列表不可见,才修改列配置,这样才不影响默认的配置
if(!item['is_query']){
columns[col].column.show = false
columns[col].column.columnSetDisabled = true
}
columns[col].addForm = {
show: item['is_create']
}
columns[col].editForm = {
show: item['is_update']
}
}
if (!res.data[key]['is_query']) {
item.column.show = false;
item.column.columnSetDisabled = true;
}
item.addForm = { show: res.data[key]['is_create'] };
item.editForm = { show: res.data[key]['is_update'] };
}
});
return crudOptions
}

View File

@@ -100,10 +100,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
placement: 'top',
content: '重设密码',
},
click: (ctx: any) => {
const { row } = ctx;
context?.handleResetPwdOpen(row);
},
click: (ctx: any) => context?.handleResetPwdOpen(ctx.row),
},
},
},
@@ -263,10 +260,10 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
}),
column: {
minWidth: 200, //最小列宽
formatter({value,row,index}){
const values = row.role_info.map((item:any) => item.name);
return values.join(',')
}
// formatter({ value, row, index }) {
// const values = row.role_info.map((item: any) => item.name);
// return values.join(',')
// }
},
form: {
rules: [
@@ -385,12 +382,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
},
avatar: {
title: '头像',
type: 'avatar-cropper',
type: 'avatar-uploader',
form: {
show: false,
},
column: {
width:150,
width: 100,
showOverflowTooltip: true,
}
},

View File

@@ -42,6 +42,15 @@
<template #actionbar-right>
<importExcel api="api/system/user/" v-auth="'user:Import'">导入 </importExcel>
</template>
<template #cell_avatar="scope">
<div v-if="scope.row.avatar" style="display: flex; justify-content: center; align-items: center;">
<el-image
style="width: 50px; height: 50px; border-radius: 50%; aspect-ratio: 1 /1 ; "
:src="getBaseURL(scope.row.avatar)"
:preview-src-list="[getBaseURL(scope.row.avatar)]"
:preview-teleported="true" />
</div>
</template>
</fs-crud>
<el-dialog v-model="resetPwdVisible" title="重设密码" width="400px" draggable :before-close="handleResetPwdClose">
@@ -69,6 +78,7 @@ import { ECharts, EChartsOption, init } from 'echarts';
import { getDeptInfoById, resetPwd } from './api';
import { warningNotification, successNotification } from '/@/utils/message';
import { HeadDeptInfoType } from '../../types';
import {getBaseURL} from '/@/utils/baseUrl';
let deptCountChart: ECharts;
let deptSexChart: ECharts;

View File

@@ -303,6 +303,16 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
},
},
},
is_value: {
title: '是否值',
column: {
show: false
},
form: {
show: false,
value: true
}
}
},
},
};

View File

@@ -1,7 +1,7 @@
<template>
<fs-page>
<FileSelector v-model="selected" :showInput="false" ref="fileSelectorRef" :tabsShow="SHOW.ALL" :itemSize="120"
:multiple="true" :selectable="false">
:multiple="false" :selectable="true" valueKey="url" inputType="image">
<!-- <template #input="scope">
input{{ scope }}
</template> -->

View File

@@ -32,7 +32,7 @@
<el-col :xs="24" :sm="24" class="personal-item mb6">
<div class="personal-item-label">角色</div>
<div class="personal-item-value">
<el-tag v-for="(item, index) in state.personalForm.role_info" :key="index">{{ item.name }}</el-tag>
<el-tag v-for="(item, index) in state.personalForm.role_info" :key="index" style="margin-right: 5px;">{{ item.name }}</el-tag>
</div>
</el-col>
</el-row>

View File

@@ -1,70 +0,0 @@
import { request } from "/@/utils/service";
import XEUtils from "xe-utils";
/**
* 获取角色的授权列表
* @param roleId
* @param query
*/
export function getRolePermission(query:object) {
return request({
url: '/api/system/role_menu_button_permission/get_role_permission/',
method: 'get',
params:query
}).then((res:any)=>{
return XEUtils.toArrayTree(res.data, {key: 'id', parentKey: 'parent',children: 'children',strict: false})
})
}
/***
* 设置角色的权限
* @param roleId
* @param data
*/
export function setRolePremission(roleId:any,data:object) {
return request({
url: `/api/system/role_menu_button_permission/${roleId}/set_role_premission/`,
method: 'put',
data
})
}
export function getDataPermissionRange(query:object) {
return request({
url: '/api/system/role_menu_button_permission/data_scope/',
method: 'get',
params:query
})
}
export function getDataPermissionRangeAll() {
return request({
url: '/api/system/role_menu_button_permission/data_scope/',
method: 'get',
})
}
export function getDataPermissionDept(query:object) {
return request({
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
method: 'get',
params:query
})
}
export function getDataPermissionMenu() {
return request({
url: '/api/system/role_menu_button_permission/get_role_permissions/',
method: 'get'
})
}
/**
* 设置按钮的数据范围
*/
export function setBtnDatarange(roleId:number,data:object) {
return request({
url: `/api/system/role_menu_button_permission/${roleId}/set_btn_datarange/`,
method: 'put',
data
})
}

View File

@@ -1,451 +0,0 @@
<template>
<el-drawer
v-model="drawerVisibleNew"
title="权限配置"
direction="rtl"
size="60%"
:close-on-click-modal="false"
:before-close="handleDrawerClose"
:destroy-on-close="true"
>
<template #header>
<el-row>
<el-col :span="4">
<div>
当前授权角色
<el-tag>{{ props.roleName }}</el-tag>
</div>
</el-col>
<el-col :span="6">
<div>
<el-button size="small" type="primary" class="pc-save-btn" @click="handleSavePermission">保存菜单授权 </el-button>
</div>
</el-col>
</el-row>
</template>
<div class="permission-com">
<el-row class="menu-el-row" :gutter="20">
<el-col :span="6">
<div class="menu-box menu-left-box">
<el-tree
ref="treeRef"
:data="menuData"
:props="defaultTreeProps"
:default-checked-keys="menuDefaultCheckedKeys"
@check="handleMenuCheck"
@node-click="handleMenuClick"
node-key="id"
check-strictly
highlight-current
show-checkbox
default-expand-all
>
</el-tree>
</div>
</el-col>
<el-col :span="18">
<div class="pc-collapse-main" v-if="menuCurrent.btns && menuCurrent.btns.length > 0">
<div class="pccm-item">
<div class="menu-form-alert">配置操作功能接口权限,配置数据权限点击小齿轮</div>
<el-checkbox v-for="(btn, bIndex) in menuCurrent.btns" :key="bIndex" v-model="btn.isCheck" :label="btn.value">
<div class="btn-item">
{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(menuCurrent, btn)">
<el-icon>
<Setting />
</el-icon>
</span>
</div>
</el-checkbox>
</div>
<div class="pccm-item" v-if="menuCurrent.columns && menuCurrent.columns.length > 0">
<div class="menu-form-alert">配置数据列字段权限</div>
<ul class="columns-list">
<li class="columns-head">
<div class="width-txt">
<span>字段</span>
</div>
<div v-for="(head, hIndex) in column.header" :key="hIndex" class="width-check">
<el-checkbox :label="head.value" @change="handleColumnChange($event, menuCurrent, head.value, head.disabled)">
<span>{{ head.label }}</span>
</el-checkbox>
</div>
</li>
<li v-for="(c_item, c_index) in menuCurrent.columns" :key="c_index" class="columns-item">
<div class="width-txt">{{ c_item.title }}</div>
<div v-for="(col, cIndex) in column.header" :key="cIndex" class="width-check">
<el-checkbox v-model="c_item[col.value]" class="ci-checkout" :disabled="c_item[col.disabled]"></el-checkbox>
</div>
</li>
</ul>
</div>
</div>
</el-col>
</el-row>
<el-dialog v-model="dialogVisible" title="数据权限配置" width="400px" :close-on-click-modal="false" :before-close="handleDialogClose">
<div class="pc-dialog">
<el-select v-model="dataPermission" @change="handlePermissionRangeChange" class="dialog-select" placeholder="请选择">
<el-option v-for="item in dataPermissionRange" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-tree-select
v-show="dataPermission === 4"
node-key="id"
v-model="customDataPermission"
:props="defaultTreeProps"
:data="deptData"
multiple
check-strictly
:render-after-expand="false"
show-checkbox
class="dialog-tree"
/>
</div>
<template #footer>
<div>
<el-button type="primary" @click="handleDialogConfirm"> 确定</el-button>
<el-button @click="handleDialogClose"> 取消</el-button>
</div>
</template>
</el-dialog>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, onMounted, defineProps, watch, computed, reactive } from 'vue';
import XEUtils from 'xe-utils';
import { errorNotification } from '/@/utils/message';
import { getDataPermissionRange, getDataPermissionDept, getRolePermission, setRolePremission, setBtnDatarange } from './api';
import { MenuDataType, DataPermissionRangeType, CustomDataPermissionDeptType } from './types';
import { ElMessage, ElTree } from 'element-plus';
const props = defineProps({
roleId: {
type: Number,
default: -1,
},
roleName: {
type: String,
default: '',
},
drawerVisible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['update:drawerVisible']);
const drawerVisibleNew = ref(false);
watch(
() => props.drawerVisible,
(val) => {
drawerVisibleNew .value = val;
getMenuBtnPermission();
getDataPermissionRangeLable();
menuCurrent.value = {};
}
);
const handleDrawerClose = () => {
emit('update:drawerVisible', false);
};
const defaultTreeProps = {
children: 'children',
label: 'name',
value: 'id',
};
let menuData = ref<MenuDataType[]>([]); // 菜单列表数据
let menuDefaultCheckedKeys = ref<number[]>([]); // 默认选中的菜单列表
let menuCurrent = ref<Partial<MenuDataType>>({}); // 当前选中的菜单
let menuBtnCurrent = ref<number>(-1);
let dialogVisible = ref(false);
let dataPermissionRange = ref<DataPermissionRangeType[]>([]);
let dataPermissionRangeLabel = ref<DataPermissionRangeType[]>([]);
const formatDataRange = computed(() => {
return function (datarange: number) {
const findItem = dataPermissionRangeLabel.value.find((i) => i.value === datarange);
return findItem?.label || '';
};
});
let deptData = ref<CustomDataPermissionDeptType[]>([]);
let dataPermission = ref();
let customDataPermission = ref([]);
/**
* 菜单复选框选中
* @param node
* @param data
*/
const handleMenuCheck = (node: any, data: any) => {
XEUtils.eachTree(menuData.value, (item) => {
item.isCheck = data.checkedKeys.includes(item.id);
});
};
/**
* 菜单点击
* @param node
* @param data
*/
const handleMenuClick = (selectNode: MenuDataType) => {
menuCurrent.value = selectNode;
};
//获取菜单,按钮,权限
const getMenuBtnPermission = async () => {
const resMenu = await getRolePermission({ role: props.roleId });
menuData.value = resMenu;
menuDefaultCheckedKeys.value = XEUtils.toTreeArray(resMenu)
.filter((i) => i.isCheck)
.map((i) => i.id);
};
// 获取按钮的数据权限下拉选项
const getDataPermissionRangeLable = async () => {
const resRange = await getDataPermissionRange({ role: props.roleId });
dataPermissionRangeLabel.value = resRange.data;
};
/**
* 获取按钮数据权限下拉选项
* @param btnId 按钮id
*/
const fetchData = async (btnId: number) => {
try {
const resRange = await getDataPermissionRange({ menu_button: btnId });
if (resRange?.code === 2000) {
dataPermissionRange.value = resRange.data;
}
} catch {
return;
}
};
/**
* 设置按钮数据权限
* @param record 当前菜单
* @param btnType 按钮类型
*/
const handleSettingClick = (record: any, btn: MenuDataType['btns'][number]) => {
menuCurrent.value = record;
menuBtnCurrent.value = btn.id;
dialogVisible.value = true;
dataPermission.value = btn.data_range;
handlePermissionRangeChange(btn.data_range);
fetchData(btn.id);
};
/**
* 设置列权限
* @param val 是否选中
* @param record 当前菜单
* @param btnType 按钮类型
* @param disabledType 禁用类型
*/
const handleColumnChange = (val: boolean, record: any, btnType: string, disabledType: string) => {
for (const iterator of record.columns) {
iterator[btnType] = iterator[disabledType] ? iterator[btnType] : val;
}
};
/**
* 数据权限设置
*/
const handlePermissionRangeChange = async (val: number) => {
if (val === 4) {
const res = await getDataPermissionDept({ role: props.roleId, menu_button: menuBtnCurrent.value });
const depts = XEUtils.toArrayTree(res.data, { parentKey: 'parent', strict: false });
deptData.value = depts;
const btnObj = XEUtils.find(menuCurrent.value.btns, (item) => item.id === menuBtnCurrent.value);
customDataPermission.value = btnObj.dept;
}
};
/**
* 数据权限设置确认
*/
const handleDialogConfirm = () => {
if (dataPermission.value !== 0 && !dataPermission.value) {
errorNotification('请选择');
return;
}
for (const btn of menuCurrent.value?.btns || []) {
if (btn.id === menuBtnCurrent.value) {
const findItem = dataPermissionRange.value.find((i) => i.value === dataPermission.value);
btn.data_range = findItem?.value || 0;
if (btn.data_range === 4) {
btn.dept = customDataPermission.value;
}
}
}
handleDialogClose();
};
const handleDialogClose = () => {
dialogVisible.value = false;
customDataPermission.value = [];
dataPermission.value = null;
};
//保存菜单授权
const handleSavePermission = () => {
setRolePremission(props.roleId, XEUtils.toTreeArray(menuData.value)).then((res: any) => {
ElMessage({
message: res.msg,
type: 'success',
});
});
};
const column = reactive({
header: [
{ value: 'is_create', label: '新增可见', disabled: 'disabled_create' },
{ value: 'is_update', label: '编辑可见', disabled: 'disabled_update' },
{ value: 'is_query', label: '列表可见', disabled: 'disabled_query' },
],
});
onMounted(() => {});
</script>
<style lang="scss" scoped>
.permission-com {
margin: 15px;
box-sizing: border-box;
.pc-save-btn {
margin-bottom: 15px;
}
.pc-collapse-title {
line-height: 32px;
text-align: left;
span {
font-size: 16px;
}
}
.pc-collapse-main {
box-sizing: border-box;
.pccm-item {
margin-bottom: 10px;
.menu-form-alert {
color: #fff;
line-height: 24px;
padding: 8px 16px;
margin-bottom: 20px;
border-radius: 4px;
background-color: var(--el-color-primary);
}
.btn-item {
display: flex;
align-items: center;
span {
margin-left: 5px;
}
}
.columns-list {
.width-txt {
width: 200px;
}
.width-check {
width: 100px;
}
.width-icon {
cursor: pointer;
}
.columns-head {
display: flex;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid #ebeef5;
box-sizing: border-box;
span {
font-weight: 900;
}
}
.columns-item {
display: flex;
align-items: center;
padding: 6px 0;
box-sizing: border-box;
.ci-checkout {
height: auto !important;
}
}
}
}
}
.pc-dialog {
.dialog-select {
width: 100%;
}
.dialog-tree {
width: 100%;
margin-top: 20px;
}
}
}
</style>
<style lang="scss">
.permission-com {
.el-collapse {
border-top: none;
border-bottom: none;
}
.el-collapse-item {
margin-bottom: 15px;
}
.el-collapse-item__header {
height: auto;
padding: 15px;
border-radius: 8px;
border-top: 1px solid #ebeef5;
border-left: 1px solid #ebeef5;
border-right: 1px solid #ebeef5;
box-sizing: border-box;
background-color: #fafafa;
}
.el-collapse-item__header.is-active {
border-radius: 8px 8px 0 0;
background-color: #fafafa;
}
.el-collapse-item__wrap {
padding: 15px;
border-left: 1px solid #ebeef5;
border-right: 1px solid #ebeef5;
border-top: 1px solid #ebeef5;
border-radius: 0 0 8px 8px;
background-color: #fafafa;
box-sizing: border-box;
.el-collapse-item__content {
padding-bottom: 0;
}
}
}
</style>

View File

@@ -1,29 +0,0 @@
export interface DataPermissionRangeType {
label: string;
value: number;
}
export interface CustomDataPermissionDeptType {
id: number;
name: string;
patent: number;
children: CustomDataPermissionDeptType[];
}
export interface CustomDataPermissionMenuType {
id: number;
name: string;
is_catalog: boolean;
menuPermission: { id: number; name: string; value: string }[] | null;
columns: { id: number; name: string; title: string }[] | null;
children: CustomDataPermissionMenuType[];
}
export interface MenuDataType {
id: string;
name: string;
isCheck: boolean;
btns: { id: number; name: string; value: string; isCheck: boolean; data_range: number; dept: object }[];
columns: { [key: string]: boolean | string; }[];
children: MenuDataType[];
}

View File

@@ -0,0 +1,76 @@
<template>
<el-drawer
v-model="RoleDrawer.drawerVisible"
title="权限配置"
direction="rtl"
size="80%"
:close-on-click-modal="false"
:before-close="RoleDrawer.handleDrawerClose"
:destroy-on-close="true"
>
<template #header>
<div>
当前授权角色
<el-tag style="margin-right: 20px">{{ RoleDrawer.roleName }}</el-tag>
授权人员
<el-button size="small" :icon="UserFilled" @click="handleUsers">{{ RoleDrawer.users.length }}</el-button>
</div>
</template>
<splitpanes class="default-theme" style="height: 100%">
<pane min-size="20" size="22">
<div class="pane-box">
<MenuTreeCom />
</div>
</pane>
<pane min-size="20">
<div class="pane-box">
<el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane label="接口权限" name="first"><MenuBtnCom /></el-tab-pane>
<el-tab-pane label="列字段权限" name="second"><MenuFieldCom /></el-tab-pane>
</el-tabs>
</div>
</pane>
</splitpanes>
</el-drawer>
<el-dialog v-model="dialogVisible" title="授权用户" width="700px" :close-on-click-modal="false">
<RoleUsersCom />
</el-dialog>
</template>
<script setup lang="ts">
import { Splitpanes, Pane } from 'splitpanes';
import 'splitpanes/dist/splitpanes.css';
import { UserFilled } from '@element-plus/icons-vue';
import { RoleDrawerStores } from '../stores/RoleDrawerStores';
import { defineAsyncComponent, ref } from 'vue';
import { RoleUsersStores } from '../stores/RoleUsersStores';
const MenuTreeCom = defineAsyncComponent(() => import('./RoleMenuTree.vue'));
const MenuBtnCom = defineAsyncComponent(() => import('./RoleMenuBtn.vue'));
const MenuFieldCom = defineAsyncComponent(() => import('./RoleMenuField.vue'));
const RoleUsersCom = defineAsyncComponent(() => import('./RoleUsers.vue'));
const RoleDrawer = RoleDrawerStores(); // 抽屉参数
const RoleUsers = RoleUsersStores(); // 角色-用户
const activeName = ref('first');
const dialogVisible = ref(false);
const handleUsers = () => {
dialogVisible.value = true;
RoleUsers.get_all_users(); // 获取所有用户
RoleUsers.set_right_users(RoleDrawer.$state.users); // 设置已选中用户
};
</script>
<style lang="scss" scoped>
.pane-box {
width: 100vw; /* 视口宽度 */
height: 100vh; /* 视口高度 */
max-width: 100%; /* 确保不超过父元素的宽度 */
max-height: 100%; /* 确保不超过父元素的高度 */
overflow: auto; /* 当内容超出容器尺寸时显示滚动条 */
padding: 10px;
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,186 @@
<template>
<div class="pccm-item" v-if="RoleMenuBtn.$state.length > 0">
<div class="menu-form-alert">配置操作功能接口权限配置数据权限点击小齿轮</div>
<el-checkbox v-for="btn in RoleMenuBtn.$state" :key="btn.id" v-model="btn.isCheck" @change="handleCheckChange(btn)">
<div class="btn-item">
{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(btn)">
<el-icon>
<Setting />
</el-icon>
</span>
</div>
</el-checkbox>
</div>
<el-dialog v-model="dialogVisible" title="数据权限配置" width="400px" :close-on-click-modal="false" :before-close="handleDialogClose">
<div class="pc-dialog">
<el-select v-model="selectBtn.data_range" @change="handlePermissionRangeChange" placeholder="请选择">
<el-option v-for="item in dataPermissionRange" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-tree-select
v-show="selectBtn.data_range === 4"
node-key="id"
v-model="selectBtn.dept"
:props="defaultTreeProps"
:data="deptData"
multiple
check-strictly
:render-after-expand="false"
show-checkbox
class="dialog-tree"
/>
</div>
<template #footer>
<div>
<el-button type="primary" @click="handleDialogConfirm"> 确定</el-button>
<el-button @click="handleDialogClose"> 取消</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { RoleDrawerStores } from '../stores/RoleDrawerStores';
import { RoleMenuBtnStores } from '../stores/RoleMenuBtnStores';
import { RoleMenuTreeStores } from '../stores/RoleMenuTreeStores';
import { RoleMenuBtnType } from '../types';
import { getRoleToDeptAll, setRoleMenuBtn, setRoleMenuBtnDataRange } from './api';
import XEUtils from 'xe-utils';
import { ElMessage } from 'element-plus';
const RoleDrawer = RoleDrawerStores(); // 角色-菜单
const RoleMenuTree = RoleMenuTreeStores(); // 角色-菜单
const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单-按钮
const dialogVisible = ref(false);
// 选中的按钮
const selectBtn = ref<RoleMenuBtnType>({
id: 0,
menu_btn_pre_id: 0,
/** 是否选中 */
isCheck: false,
/** 按钮名称 */
name: '',
/** 数据权限范围 */
data_range: 0,
dept: [],
});
/**
* 数据权限范围
*/
const dataPermissionRange = ref([
{ label: '仅本人数据权限', value: 0 },
{ label: '本部门及以下数据权限', value: 1 },
{ label: '本部门数据权限', value: 2 },
{ label: '全部数据权限', value: 3 },
{ label: '自定数据权限', value: 4 },
]);
/**
* 自定义数据权限的部门树配置
*/
const defaultTreeProps = {
children: 'children',
label: 'name',
value: 'id',
};
/**
* 自定数据权限下拉选择事件
*/
const handlePermissionRangeChange = async (val: number) => {
if (val < 4) {
selectBtn.value.dept = [];
}
};
/**
* 格式化按钮数据范围
*/
const formatDataRange = computed(() => {
return function (datarange: number) {
const datarangeitem = XEUtils.find(dataPermissionRange.value, (item: any) => {
if (item.value === datarange) {
return item.label;
}
});
return datarangeitem.label;
};
});
/**
* 勾选按钮
*/
const handleCheckChange = async (btn: RoleMenuBtnType) => {
const put_data = {
isCheck: btn.isCheck,
roleId: RoleDrawer.roleId,
menuId: RoleMenuTree.id,
btnId: btn.id,
};
const { data, msg } = await setRoleMenuBtn(put_data);
RoleMenuBtn.updateState(data);
ElMessage({ message: msg, type: 'success' });
};
/**
* 按钮-数据范围确定
*/
const handleDialogConfirm = async () => {
const { data, msg } = await setRoleMenuBtnDataRange(selectBtn.value);
selectBtn.value = data;
dialogVisible.value = false;
ElMessage({ message: msg, type: 'success' });
};
/**
* 数据范围关闭
*/
const handleDialogClose = () => {
dialogVisible.value = false;
};
/**
* 齿轮点击
*/
const handleSettingClick = async (btn: RoleMenuBtnType) => {
selectBtn.value = btn;
dialogVisible.value = true;
};
/**
* 部门数据
*
*/
const deptData = ref<number[]>([]);
// 页面打开后获取列表数据
onMounted(async () => {
const res = await getRoleToDeptAll({ role: RoleDrawer.roleId, menu_button: selectBtn.value.id });
const depts = XEUtils.toArrayTree(res.data, { parentKey: 'parent', strict: false });
deptData.value = depts;
});
</script>
<style lang="scss" scoped>
.pccm-item {
margin-bottom: 10px;
.menu-form-alert {
color: #fff;
line-height: 24px;
padding: 8px 16px;
margin-bottom: 20px;
border-radius: 4px;
background-color: var(--el-color-primary);
}
}
// .el-checkbox {
// width: 200px;
// }
.btn-item {
display: flex;
align-items: center;
justify-content: center; /* 水平居中 */
.el-icon {
margin-left: 5px;
}
}
.dialog-tree {
width: 100%;
margin-top: 20px;
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<div class="pccm-item" v-if="RoleMenuField.$state.length > 0">
<div class="menu-form-alert">
<el-button size="small" @click="handleSaveField">保存 </el-button>
配置数据列字段权限
</div>
<ul class="columns-list">
<li class="columns-head">
<div class="width-txt">
<span>字段</span>
</div>
<div v-for="(head, hIndex) in RoleMenuFieldHeader.$state" :key="hIndex" class="width-check">
<el-checkbox v-model="head.checked" @change="handleColumnChange($event, head.value, head.disabled)">
<span>{{ head.label }}</span>
</el-checkbox>
</div>
</li>
<div class="columns-content">
<li v-for="(c_item, c_index) in RoleMenuField.$state" :key="c_index" class="columns-item">
<div class="width-txt">{{ c_item.title }}</div>
<div v-for="(col, cIndex) in RoleMenuFieldHeader.$state" :key="cIndex" class="width-check">
<el-checkbox v-model="c_item[col.value]" class="ci-checkout" :disabled="c_item[col.disabled]"></el-checkbox>
</div>
</li>
</div>
</ul>
</div>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus';
import { RoleDrawerStores } from '../stores/RoleDrawerStores';
import { RoleMenuFieldStores, RoleMenuFieldHeaderStores } from '../stores/RoleMenuFieldStores';
import { setRoleMenuField } from './api';
const RoleMenuField = RoleMenuFieldStores();
const RoleMenuFieldHeader = RoleMenuFieldHeaderStores();
const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
/** 全选 */
const handleColumnChange = (val: boolean, btnType: string, disabledType: string) => {
for (const iterator of RoleMenuField.$state) {
iterator[btnType] = iterator[disabledType] ? iterator[btnType] : val;
}
};
const handleSaveField = async () => {
const res = await setRoleMenuField(RoleDrawer.$state.roleId, RoleMenuField.$state);
ElMessage({ message: res.msg, type: 'success' });
};
</script>
<style lang="scss" scoped>
.pccm-item {
margin-bottom: 10px;
.menu-form-alert {
color: #fff;
line-height: 24px;
padding: 8px 16px;
margin-bottom: 20px;
border-radius: 4px;
background-color: var(--el-color-primary);
}
.menu-form-btn {
margin-left: 10px;
height: 40px;
padding: 8px 16px;
margin-bottom: 20px;
}
.btn-item {
display: flex;
align-items: center;
span {
margin-left: 5px;
}
}
.columns-list {
.width-txt {
width: 200px;
}
.width-check {
width: 100px;
}
.width-icon {
cursor: pointer;
}
.columns-head {
display: flex;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid #ebeef5;
box-sizing: border-box;
span {
font-weight: 900;
}
}
.columns-content {
max-height: calc(100vh - 240px); /* 视口高度 */
overflow-y: auto; /* 当内容超出高度时显示垂直滚动条 */
.columns-item {
display: flex;
align-items: center;
padding: 6px 0;
box-sizing: border-box;
.ci-checkout {
height: auto !important;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<el-tree
ref="treeRef"
:data="menuData"
:props="defaultTreeProps"
:default-checked-keys="menuDefaultCheckedKeys"
@check-change="handleMenuChange"
@node-click="handleMenuClick"
node-key="id"
check-strictly
highlight-current
show-checkbox
default-expand-all
>
</el-tree>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { RoleDrawerStores } from '../stores/RoleDrawerStores';
import { RoleMenuTreeStores } from '../stores/RoleMenuTreeStores';
import { RoleMenuBtnStores } from '../stores/RoleMenuBtnStores';
import { RoleMenuFieldStores } from '../stores/RoleMenuFieldStores';
import { RoleMenuFieldHeaderStores } from '../stores/RoleMenuFieldStores';
import { getRoleMenu, getRoleMenuBtnField, setRoleMenu } from './api';
import { ElMessage } from 'element-plus';
import XEUtils from 'xe-utils';
import { RoleMenuTreeType } from '../types';
const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
const RoleMenuTree = RoleMenuTreeStores(); // 角色-菜单
const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单-按钮
const RoleMenuField = RoleMenuFieldStores(); // 角色-菜单-列字段
const RoleMenuFieldHeader = RoleMenuFieldHeaderStores();// 角色-菜单-列字段
const menuData = ref<RoleMenuTreeType[]>([]); // 菜单列表数据
const menuDefaultCheckedKeys = ref<(number | string | undefined)[]>([]); // 默认选中的菜单列表
// 菜单配置
const defaultTreeProps = {
children: 'children',
label: 'name',
value: 'id',
};
/**
* 菜单复选框选中
* @param node当前节点的 Node 对象
* @param checked布尔值表示当前节点是否被选中
*/
const handleMenuChange = (node: any, checked: boolean) => {
setRoleMenu({ roleId: RoleDrawer.roleId, menuId: node.id, isCheck: checked }).then((res: any) => {
ElMessage({ message: res.msg, type: 'success' });
});
};
/**
* 菜单点击事件
*/
const handleMenuClick = async (selectNode: RoleMenuTreeType) => {
if (!selectNode.is_catalog) {
RoleMenuTree.setRoleMenuTree(selectNode); // 更新当前选中的菜单
// 获取当前菜单的按钮列表
const { data } = await getRoleMenuBtnField({
roleId: RoleDrawer.roleId,
menuId: selectNode.id,
});
RoleMenuBtn.setState(data.menu_btn); // 更新按钮列表
RoleMenuField.setState(data.menu_field); // 更新列字段列表
} else {
RoleMenuBtn.setState([]); // 更新按钮列表
RoleMenuField.setState([]); // 更新列字段列表
}
RoleMenuFieldHeader.$reset()
};
// 页面打开后获取列表数据
onMounted(async () => {
menuData.value = await getRoleMenu({ roleId: RoleDrawer.roleId });
menuDefaultCheckedKeys.value = XEUtils.toTreeArray(menuData.value)
.filter((i) => i.isCheck)
.map((i) => i.id);
});
</script>

View File

@@ -0,0 +1,35 @@
<template>
<el-transfer
v-model="RoleUsers.$state.right_users"
filterable
:titles="['未授权用户', '已授权用户']"
:data="RoleUsers.$state.all_users"
:props="{
key: 'id',
label: 'name',
}"
@change="handleChange"
/>
</template>
<script lang="ts" setup>
import { ElMessage } from 'element-plus';
import { RoleDrawerStores } from '../stores/RoleDrawerStores';
import { RoleUsersStores } from '../stores/RoleUsersStores';
import { setRoleUsers } from './api';
const RoleDrawer = RoleDrawerStores(); // 抽屉参数
const RoleUsers = RoleUsersStores(); // 角色-用户
/**
*
* @param value 当前右侧选中的用户
* @param direction 移动的方向
* @param movedKeys 移动的用户
*/
const handleChange = (value: number[] | string[], direction: 'left' | 'right', movedKeys: string[] | number[]) => {
setRoleUsers(RoleDrawer.$state.roleId, { direction, movedKeys }).then((res:any) => {
RoleDrawer.set_state(res.data)
ElMessage({ message: res.msg, type: 'success' });
});
};
</script>

View File

@@ -0,0 +1,121 @@
import { request } from '/@/utils/service';
import XEUtils from 'xe-utils';
/**
* 获取 角色-菜单
* @param query
*/
export function getRoleMenu(query: object) {
return request({
url: '/api/system/role_menu_button_permission/get_role_menu/',
method: 'get',
params: query,
}).then((res: any) => {
return XEUtils.toArrayTree(res.data, { key: 'id', parentKey: 'parent', children: 'children', strict: false });
});
}
/**
* 设置 角色-菜单
* @param data
* @returns
*/
export function setRoleMenu(data: object) {
return request({
url: '/api/system/role_menu_button_permission/set_role_menu/',
method: 'put',
data,
});
}
/**
* 获取 角色-菜单-按钮-列字段
* @param query
*/
export function getRoleMenuBtnField(query: object) {
return request({
url: '/api/system/role_menu_button_permission/get_role_menu_btn_field/',
method: 'get',
params: query,
});
}
/**
* 设置 角色-菜单-按钮
* @param data
*/
export function setRoleMenuBtn(data: object) {
return request({
url: '/api/system/role_menu_button_permission/set_role_menu_btn/',
method: 'put',
data,
});
}
/**
* 设置 角色-菜单-列字段
* @param data
*/
export function setRoleMenuField(roleId: string | number | undefined, data: object) {
return request({
url: `/api/system/role_menu_button_permission/${roleId}/set_role_menu_field/`,
method: 'put',
data,
});
}
/**
* 设置 角色-菜单-按钮-数据权限
* @param query
* @returns
*/
export function setRoleMenuBtnDataRange(data: object) {
return request({
url: '/api/system/role_menu_button_permission/set_role_menu_btn_data_range/',
method: 'put',
data,
});
}
/**
* 获取当前用户角色下所能授权的部门
* @param query
* @returns
*/
export function getRoleToDeptAll(query: object) {
return request({
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
method: 'get',
params: query,
});
}
/**
* 获取所有用户
* @param query
* @returns
*/
export function getAllUsers() {
return request({
url: '/api/system/user/',
method: 'get',
params: { limit: 999 },
}).then((res: any) => {
return XEUtils.map(res.data, (item: any) => {
return {
id: item.id,
name: item.name,
};
});
});
}
/**
* 设置角色-用户
* @param query
* @returns
*/
export function setRoleUsers(roleId: string | number | undefined, data: object) {
return request({
url: `/api/system/role/${roleId}/set_role_users/`,
method: 'put',
data,
});
}

View File

@@ -1,25 +1,16 @@
import {CrudOptions, AddReq, DelReq, EditReq, dict, CrudExpose, compute} from '@fast-crud/fast-crud';
import { CreateCrudOptionsProps, CreateCrudOptionsRet, AddReq, DelReq, EditReq, dict, compute } from '@fast-crud/fast-crud';
import * as api from './api';
import { dictionary } from '/@/utils/dictionary';
import {columnPermission} from '../../../utils/columnPermission';
import { successMessage } from '../../../utils/message';
import {auth} from '/@/utils/authFunction'
import { auth } from '/@/utils/authFunction';
interface CreateCrudOptionsTypes {
output: any;
crudOptions: CrudOptions;
}
//此处为crudOptions配置
export const createCrudOptions = function ({
crudExpose,
rolePermission,
handleDrawerOpen,
}: {
crudExpose: CrudExpose;
rolePermission: any;
handleDrawerOpen: Function;
}): CreateCrudOptionsTypes {
/**
*
* @param crudExposeindex传递过来的示例
* @param contextindex传递过来的自定义参数
* @returns
*/
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: any) => {
return await api.GetList(query);
};
@@ -34,10 +25,6 @@ export const createCrudOptions = function ({
return await api.AddObj(form);
};
//权限判定
// @ts-ignore
// @ts-ignore
return {
crudOptions: {
request: {
@@ -47,14 +34,14 @@ export const createCrudOptions = function ({
delRequest,
},
pagination: {
show: true
show: true,
},
actionbar: {
buttons: {
add: {
show: auth('role:Create')
}
}
show: auth('role:Create'),
},
},
},
rowHandle: {
//固定右侧
@@ -74,13 +61,11 @@ export const createCrudOptions = function ({
type: 'primary',
text: '权限配置',
show: auth('role:Permission'),
tooltip: {
placement: 'top',
content: '权限配置',
},
click: (context: any): void => {
const {row} = context;
handleDrawerOpen(row);
click: (clickContext: any): void => {
const { row } = clickContext;
context.RoleDrawer.handleDrawerOpen(row);
context.RoleMenuBtn.setState([]);
context.RoleMenuField.setState([]);
},
},
},
@@ -106,14 +91,12 @@ export const createCrudOptions = function ({
},
id: {
title: 'ID',
type: 'text',
column: { show: false },
search: { show: false },
form: { show: false },
},
name: {
title: '角色名称',
type: 'text',
search: { show: true },
column: {
minWidth: 120,
@@ -128,7 +111,6 @@ export const createCrudOptions = function ({
},
key: {
title: '权限标识',
type: 'text',
search: { show: false },
column: {
minWidth: 120,
@@ -142,9 +124,9 @@ export const createCrudOptions = function ({
},
},
valueBuilder(context) {
const {row, key} = context
return row[key]
}
const { row, key } = context;
return row[key];
},
},
sort: {
title: '排序',
@@ -180,9 +162,9 @@ export const createCrudOptions = function ({
},
},
dict: dict({
data: dictionary('button_status_bool'),
value: dictionary('button_status_bool'),
}),
}
},
},
},
};

View File

@@ -1,69 +1,36 @@
<template>
<fs-page>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #cell_url="scope">
<el-tag size="small">{{ scope.row.url }}</el-tag>
</template>
</fs-crud>
<permission ref="rolePermission"></permission>
<PermissionComNew v-model:drawerVisible="drawerVisible" :roleId="roleId" :roleName="roleName" @drawerClose="handleDrawerClose" />
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
<PermissionDrawerCom />
</fs-page>
</template>
<script lang="ts" setup name="role">
import {ref, onMounted, inject, onBeforeUpdate} from 'vue';
import { GetPermission } from './api';
import { useExpose, useCrud } from '@fast-crud/fast-crud';
import { defineAsyncComponent, onMounted } from 'vue';
import { useFs } from '@fast-crud/fast-crud';
import { createCrudOptions } from './crud';
import PermissionComNew from './components/PermissionComNew/index.vue';
import _ from "lodash-es";
import {handleColumnPermission} from "/@/utils/columnPermission";
let drawerVisible = ref(false);
let roleId = ref(null);
let roleName = ref(null);
import { RoleDrawerStores } from './stores/RoleDrawerStores';
import { RoleMenuBtnStores } from './stores/RoleMenuBtnStores';
import { RoleMenuFieldStores } from './stores/RoleMenuFieldStores';
import { RoleUsersStores } from './stores/RoleUsersStores';
const rolePermission = ref();
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
const PermissionDrawerCom = defineAsyncComponent(() => import('./components/RoleDrawer.vue'));
const handleDrawerOpen = (row: any) => {
roleId.value = row.id;
roleName.value = row.name;
drawerVisible.value = true;
};
const handleDrawerClose = () => {
drawerVisible.value = false;
};
const { crudExpose } = useExpose({ crudRef, crudBinding });
// 你的crud配置
const { crudOptions } = createCrudOptions({ crudExpose, rolePermission, handleDrawerOpen });
// 初始化crud配置
const { resetCrudOptions } = useCrud({
crudExpose,
crudOptions,
context: {},
const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单
const RoleMenuField = RoleMenuFieldStores();// 角色-菜单-字段
const RoleUsers = RoleUsersStores();// 角色-用户
const { crudBinding, crudRef, crudExpose } = useFs({
createCrudOptions,
context: { RoleDrawer, RoleMenuBtn, RoleMenuField },
});
// 页面打开后获取列表数据
onMounted(async () => {
const newOptions = await handleColumnPermission(GetPermission,crudOptions)
//重置crudBinding
//resetCrudOptions(newOptions);
// 刷新
crudExpose.doRefresh();
});
// 获取全部用户
RoleUsers.get_all_users();
defineExpose(rolePermission);
});
</script>

View File

@@ -0,0 +1,37 @@
import { defineStore } from 'pinia';
import { RoleDrawerType } from '../types';
/**
* 权限配置:抽屉
*/
const initialState: RoleDrawerType = {
drawerVisible: false,
roleId: undefined,
roleName: undefined,
users: [],
};
export const RoleDrawerStores = defineStore('RoleDrawerStores', {
state: (): RoleDrawerType => ({
...initialState,
}),
actions: {
/**
* 打开权限修改抽屉
*/
handleDrawerOpen(row: any) {
this.drawerVisible = true;
this.set_state(row);
},
set_state(row: any) {
this.roleName = row.name;
this.roleId = row.id;
this.users = row.users;
},
/**
* 关闭权限修改抽屉
*/
handleDrawerClose() {
Object.assign(this.$state, initialState);
},
},
});

View File

@@ -0,0 +1,24 @@
import { defineStore } from 'pinia';
import { RoleMenuBtnType } from '../types';
/**
* 权限配置:角色-菜单-按钮
*/
export const RoleMenuBtnStores = defineStore('RoleMenuBtnStores', {
state: (): RoleMenuBtnType[] => [],
actions: {
/**
* 初始化
*/
setState(data: RoleMenuBtnType[]) {
this.$state = data;
this.$state.length = data.length;
},
updateState(data: RoleMenuBtnType) {
const index = this.$state.findIndex((item) => item.id === data.id);
if (index !== -1) {
this.$state[index] = data;
}
},
},
});

View File

@@ -0,0 +1,24 @@
import { defineStore } from 'pinia';
import { RoleMenuFieldType, RoleMenuFieldHeaderType } from '../types';
/**
* 权限配置:角色-菜单-列字段
*/
export const RoleMenuFieldStores = defineStore('RoleMenuFieldStores', {
state: (): RoleMenuFieldType[] => [],
actions: {
/** 重置 */
setState(data: RoleMenuFieldType[]) {
this.$state = data;
this.$state.length = data.length;
},
},
});
export const RoleMenuFieldHeaderStores = defineStore('RoleMenuFieldHeaderStores', {
state: (): RoleMenuFieldHeaderType[] => [
{ value: 'is_create', label: '新增可见', disabled: 'disabled_create', checked: false },
{ value: 'is_update', label: '编辑可见', disabled: 'disabled_update', checked: false },
{ value: 'is_query', label: '列表可见', disabled: 'disabled_query', checked: false },
],
});

View File

@@ -0,0 +1,21 @@
import { defineStore } from 'pinia';
import { RoleMenuTreeType } from '../types';
/**
* 权限抽屉:角色-菜单
*/
export const RoleMenuTreeStores = defineStore('RoleMenuTreeStores', {
state: (): RoleMenuTreeType => ({
id: 0,
parent: 0,
name: '',
isCheck: false,
is_catalog: false,
}),
actions: {
/** 赋值 */
setRoleMenuTree(data: RoleMenuTreeType) {
this.$state = data;
},
},
});

View File

@@ -0,0 +1,24 @@
import { defineStore } from 'pinia';
import { RoleUsersType } from '../types';
import { getAllUsers } from '../components/api';
import XEUtils from 'xe-utils';
/**
* 权限抽屉:角色-用户
*/
export const RoleUsersStores = defineStore('RoleUsersStores', {
state: (): RoleUsersType => ({
all_users: [],
right_users: [],
}),
actions: {
get_all_users() {
getAllUsers().then((res: any) => {
this.$state.all_users = res;
});
},
set_right_users(users: any) {
this.$state.right_users = XEUtils.map(users, (item: any) => item.id);
},
},
});

View File

@@ -1,14 +1,4 @@
import { CrudOptions, CrudExpose } from '@fast-crud/fast-crud';
export interface CreateCrudReturnTypes {
crudOptions: CrudOptions;
}
export interface CreateCrudOptionsTypes {
crudExpose: CrudExpose;
configPermission: Function;
}
/**角色列表数据类型 */
export interface RoleItemType {
id: string | number;
modifier_name: string;
@@ -25,3 +15,85 @@ export interface RoleItemType {
admin: boolean;
creator: string;
}
export interface UsersType {
id: string | number;
name: string;
}
export interface RoleUsersType {
all_users: UsersType[];
right_users: UsersType[];
}
/**
* 权限配置 抽屉组件参数数据类型
*/
export interface RoleDrawerType {
/** 是否显示抽屉*/
drawerVisible: boolean;
/** 角色id*/
roleId: string | number | undefined;
/** 角色名称*/
roleName: string | undefined;
/** 用户*/
users: UsersType[];
}
/**
* 菜单数据类型
*/
export interface RoleMenuTreeType {
id: number | string | undefined;
/** 父级id */
parent: number | string | undefined;
name: string;
/** 是否选中 */
isCheck: boolean;
/** 是否是目录 */
is_catalog: boolean;
}
/**
* 菜单-按钮数据类型
*/
export interface RoleMenuBtnType {
id: string | number;
menu_btn_pre_id: string | number;
/** 是否选中 */
isCheck: boolean;
/** 按钮名称 */
name: string;
/** 数据权限范围 */
data_range: number | null;
/** 自定义部门 */
dept: number[];
}
/**
* 菜单-列字段数据类型
*/
export interface RoleMenuFieldType {
id: string | number | boolean;
/** 模型表字段名 */
field_name: string;
/** 字段显示名 */
title: string;
/** 是否可查询 */
is_query: boolean;
/** 是否可创建 */
is_create: boolean;
/** 是否可更新 */
is_update: boolean;
[key: string]: string | number | boolean;
}
/**
* 菜单-列字段-标题数据类型
*/
export interface RoleMenuFieldHeaderType {
value: string;
/** 模型表字段名 */
label: string;
/** 字段显示名 */
disabled: string;
/** 是否可查询 */
checked: boolean;
}

View File

@@ -18,6 +18,7 @@ import {storeToRefs} from "pinia";
import { computed } from "vue";
import { Md5 } from 'ts-md5';
import { commonCrudConfig } from "/@/utils/commonCrud";
import { ElMessageBox } from 'element-plus';
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery) => {
return await api.GetList(query);
@@ -79,9 +80,10 @@ export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps)
text: "导出",//按钮文字
title: "导出",//鼠标停留显示的信息
show: auth('user:Export'),
click() {
return exportRequest(crudExpose!.getSearchFormData())
}
click: (ctx: any) => ElMessageBox.confirm(
'确定重设密码吗?', '提示',
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
).then(() => resetToDefaultPasswordRequest(ctx.row))
}
}
},
@@ -257,10 +259,10 @@ export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps)
}),
column: {
minWidth: 200, //最小列宽
formatter({value,row,index}){
const values = row.role_info.map((item:any) => item.name);
return values.join(',')
}
// formatter({ value, row, index }) {
// const values = row.role_info.map((item: any) => item.name);
// return values.join(',')
// }
},
form: {
rules: [
@@ -382,12 +384,13 @@ export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps)
},
avatar: {
title: '头像',
type: 'avatar-cropper',
type: 'avatar-uploader',
align: 'center',
form: {
show: false,
},
column: {
minWidth: 400, //最小列宽
minWidth: 100, //最小列宽
},
},
...commonCrudConfig({

View File

@@ -33,6 +33,15 @@
<template #actionbar-right>
<importExcel api="api/system/user/" v-auth="'user:Import'">导入</importExcel>
</template>
<template #cell_avatar="scope">
<div v-if="scope.row.avatar" style="display: flex; justify-content: center; align-items: center;">
<el-image
style="width: 50px; height: 50px; border-radius: 50%; aspect-ratio: 1 /1 ; "
:src="getBaseURL(scope.row.avatar)"
:preview-src-list="[getBaseURL(scope.row.avatar)]"
:preview-teleported="true" />
</div>
</template>
</fs-crud>
</el-card>
</el-col>
@@ -50,6 +59,8 @@ import {ref, onMounted, watch, toRaw, h} from 'vue';
import XEUtils from 'xe-utils';
import {getElementLabelLine} from 'element-tree-line';
import importExcel from '/@/components/importExcel/index.vue'
import {getBaseURL} from '/@/utils/baseUrl';
const ElementTreeLine = getElementLabelLine(h);