9 Commits

Author SHA1 Message Date
猿小天
ff7333d882 1.删除旧代码 2023-10-29 21:02:53 +08:00
猿小天
52de562fac Merge remote-tracking branch 'origin/master' 2023-10-29 20:28:59 +08:00
猿小天
9cb3570f5a 1.完成初始化的配置 2023-10-29 20:28:45 +08:00
猿小天
75670f9a5e 1.完成初始化的配置 2023-10-29 20:09:56 +08:00
猿小天
212af88409 1.完成新版列字段授权 2023-10-29 11:16:34 +08:00
猿小天
1b71ba156b 1.完成新版接口授权 2023-10-29 00:43:44 +08:00
猿小天
10b159aa15 1.完成新版接口授权 2023-10-29 00:33:07 +08:00
猿小天
b7b589176a 1.完成新版菜单授权 2023-10-28 19:00:35 +08:00
猿小天
bd8fef9a04 1.完成新版菜单管理 2023-10-27 16:59:25 +08:00
54 changed files with 2552 additions and 3595 deletions

View File

@@ -178,6 +178,7 @@ CHANNEL_LAYERS = {
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}
REDIS_URL = locals().get('REDIS_URL', "")
# CHANNEL_LAYERS = {
# 'default': {
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
@@ -399,10 +400,11 @@ TENANT_SHARED_APPS = []
PLUGINS_URL_PATTERNS = []
# ********** 一键导入插件配置开始 **********
# 例如:
# from dvadmin_upgrade_center.settings import * # 升级中心
#from dvadmin3_upgrade_center.settings import * # 升级中心
# from dvadmin_celery.settings import * # celery 异步任务
# from dvadmin_third.settings import * # 第三方用户管理
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
# from dvadmin_tenants.settings import * # 租户管理
# from dvadmin_uniapp.settings import *
# ...
# ********** 一键导入插件配置结束 **********

View File

@@ -9,7 +9,7 @@ django.setup()
from dvadmin.system.models import (
Role, Dept, Users, Menu, MenuButton,
ApiWhiteList, Dictionary, SystemConfig,
RoleMenuPermission, RoleMenuButtonPermission
RoleMenuPermission, RoleApiPermission, Columns
)
from dvadmin.utils.serializers import CustomModelSerializer
@@ -42,24 +42,13 @@ class UsersInitSerializer(CustomModelSerializer):
}
class MenuButtonInitSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化器
"""
class Meta:
model = MenuButton
fields = ['id', 'name', 'value', 'api', 'method', 'menu']
read_only_fields = ["id"]
class MenuInitSerializer(CustomModelSerializer):
"""
递归深度获取数信息(用于生成初始化json文件)
"""
name = serializers.CharField(required=False)
name = serializers.CharField(required=True)
menu_type = serializers.IntegerField(required=True)
children = serializers.SerializerMethodField()
menu_button = serializers.SerializerMethodField()
def get_children(self, obj: Menu):
data = []
@@ -69,26 +58,18 @@ class MenuInitSerializer(CustomModelSerializer):
data = serializer.data
return data
def get_menu_button(self, obj: Menu):
data = []
instance = obj.menuPermission.order_by('method')
if instance:
data = list(instance.values('name', 'value', 'api', 'method'))
return data
def save(self, **kwargs):
instance = super().save(**kwargs)
children = self.initial_data.get('children')
menu_button = self.initial_data.get('menu_button')
# 菜单表
if children:
for menu_data in children:
menu_data['parent'] = instance.id
filter_data = {
"name": menu_data['name'],
"web_path": menu_data['web_path'],
"component": menu_data['component'],
"component_name": menu_data['component_name'],
"menu_type": menu_data['menu_type'],
}
instance_obj = Menu.objects.filter(**filter_data).first()
if instance_obj and not self.initial_data.get('reset'):
@@ -96,24 +77,12 @@ class MenuInitSerializer(CustomModelSerializer):
serializer = MenuInitSerializer(instance_obj, data=menu_data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
# 菜单按钮
if menu_button:
for menu_button_data in menu_button:
menu_button_data['menu'] = instance.id
filter_data = {
"menu": menu_button_data['menu'],
"value": menu_button_data['value']
}
instance_obj = MenuButton.objects.filter(**filter_data).first()
serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
return instance
class Meta:
model = Menu
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
'cache', 'visible', 'parent', 'children', 'menu_button', 'creator', 'dept_belong_id']
fields = ['name', 'icon', 'sort', 'is_link', 'menu_type', 'web_path', 'component', 'component_name', 'status',
'cache', 'visible', 'parent', 'children', 'creator', 'dept_belong_id']
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
@@ -128,7 +97,7 @@ class RoleInitSerializer(CustomModelSerializer):
class Meta:
model = Role
fields = ['name', 'key', 'sort', 'status', 'admin',
fields = ['name', 'key', 'sort', 'status',
'creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
@@ -166,37 +135,62 @@ class RoleMenuInitSerializer(CustomModelSerializer):
}
class RoleMenuButtonInitSerializer(CustomModelSerializer):
class RoleApiPermissionInitSerializer(CustomModelSerializer):
"""
初始化角色菜单按钮(用于生成初始化json文件)
初始化角色接口权限(用于生成初始化json文件)
"""
role_key = serializers.CharField(max_length=100, required=True)
menu_button_value = serializers.CharField(max_length=100, required=True)
name = serializers.CharField(max_length=255, required=True)
api = serializers.CharField(max_length=255, required=True)
method = serializers.IntegerField(required=True)
data_range = serializers.CharField(max_length=100, required=False)
def create(self, validated_data):
init_data = self.initial_data
validated_data.pop('menu_button_value')
validated_data.pop('role_key')
role_id = Role.objects.filter(key=init_data['role_key']).first()
menu_button_id = MenuButton.objects.filter(value=init_data['menu_button_value']).first()
validated_data['role'] = role_id
validated_data['menu_button'] = menu_button_id
instance = super().create(validated_data)
instance.dept.set([])
return instance
class Meta:
model = RoleMenuButtonPermission
fields = ['role_key', 'menu_button_value', 'data_range', 'dept', 'creator', 'dept_belong_id']
model = RoleApiPermission
fields = ['role_key', 'name','api','method', 'data_range', 'dept', 'creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
'role': {'required': False},
'menu': {'required': False},
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
}
class RoleColumnInitSerializer(CustomModelSerializer):
"""
初始化角色字段权限(用于生成初始化json文件)
"""
role_key = serializers.CharField(max_length=100, required=True)
app = serializers.CharField(max_length=255, required=True)
model = serializers.CharField(max_length=255, required=True)
field_name = serializers.CharField(max_length=255, required=True)
title = serializers.CharField(max_length=255, required=True)
def create(self, validated_data):
init_data = self.initial_data
validated_data.pop('role_key')
role_id = Role.objects.filter(key=init_data['role_key']).first()
validated_data['role'] = role_id
instance = super().create(validated_data)
return instance
class Meta:
model = Columns
fields = ['role_key', 'app','model','field_name', 'title']
read_only_fields = ["id"]
extra_kwargs = {
'role': {'required': False},
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
}
class ApiWhiteListInitSerializer(CustomModelSerializer):
"""

View File

@@ -0,0 +1,123 @@
[
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "id",
"title": "Id",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "description",
"title": "描述",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "creator",
"title": "创建人",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "modifier",
"title": "修改人",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "dept_belong_id",
"title": "数据归属部门",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "update_datetime",
"title": "修改时间",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "create_datetime",
"title": "创建时间",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "name",
"title": "角色名称",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "key",
"title": "权限字符",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "sort",
"title": "角色顺序",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
},
{
"dept_belong_id": "1",
"app": "dvadmin.system",
"model": "Role",
"field_name": "status",
"title": "角色状态",
"is_query": 1,
"is_create": 1,
"is_update": 1,
"role_key": "admin"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,6 @@
"key": "admin",
"sort": 1,
"status": true,
"admin": true,
"remark": null
},
{
@@ -12,7 +11,6 @@
"key": "public",
"sort": 2,
"status": true,
"admin": true,
"remark": null
}
]

View File

@@ -0,0 +1,14 @@
[
{
"role_key": "admin",
"name": "菜单列表查询",
"api": "/api/system/menu/",
"method": 0
},
{
"role_key": "public",
"name": "菜单列表查询",
"api": "/api/system/menu/",
"method": 0
}
]

View File

@@ -1,12 +0,0 @@
[
{
"role_key": "admin",
"menu_button_value": "menu:Search",
"data_range": 0
},
{
"role_key": "public",
"menu_button_value":"menu:Search",
"data_range": 0
}
]

View File

@@ -11,7 +11,7 @@ from dvadmin.utils.core_initialize import CoreInitialize
from dvadmin.system.fixtures.initSerializer import (
UsersInitSerializer, DeptInitSerializer, RoleInitSerializer,
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer,
SystemConfigInitSerializer, RoleMenuInitSerializer, RoleMenuButtonInitSerializer
SystemConfigInitSerializer, RoleMenuInitSerializer, RoleApiPermissionInitSerializer, RoleColumnInitSerializer
)
@@ -39,7 +39,7 @@ class Initialize(CoreInitialize):
"""
初始化菜单信息
"""
self.init_base(MenuInitSerializer, unique_fields=['name', 'web_path', 'component', 'component_name'])
self.init_base(MenuInitSerializer, unique_fields=['name'])
def init_role_menu(self):
"""
@@ -47,11 +47,17 @@ class Initialize(CoreInitialize):
"""
self.init_base(RoleMenuInitSerializer, unique_fields=['role', 'menu'])
def init_role_menu_button(self):
def init_role_api_permission(self):
"""
初始化角色菜单按钮信息
"""
self.init_base(RoleMenuButtonInitSerializer, unique_fields=['role', 'menu_button'])
self.init_base(RoleApiPermissionInitSerializer, unique_fields=['role', 'api','name'])
def init_role_column(self):
"""
初始化角色字段权限
"""
self.init_base(RoleColumnInitSerializer, unique_fields=['app','model','field_name'])
def init_api_white_list(self):
"""
@@ -77,7 +83,8 @@ class Initialize(CoreInitialize):
self.init_users()
self.init_menu()
self.init_role_menu()
self.init_role_menu_button()
self.init_role_api_permission()
self.init_role_column()
self.init_api_white_list()
self.init_dictionary()
self.init_system_config()

View File

@@ -13,7 +13,6 @@ class Role(CoreModel):
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
status = models.BooleanField(default=True, verbose_name="角色状态", help_text="角色状态")
admin = models.BooleanField(default=False, verbose_name="是否为admin", help_text="是否为admin")
class Meta:
db_table = table_prefix + "system_role"
@@ -156,22 +155,24 @@ class Menu(CoreModel):
help_text="上级菜单",
)
icon = models.CharField(max_length=64, verbose_name="菜单图标", null=True, blank=True, help_text="菜单图标")
name = models.CharField(max_length=64, verbose_name="菜单名称", help_text="菜单名称")
name = models.CharField(max_length=64, verbose_name="目录名称/菜单名称/按钮名称", help_text="目录名称/菜单名称/按钮名称")
sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序")
ISLINK_CHOICES = (
(0, ""),
(1, ""),
)
is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录")
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
MENU_TYPE_CHOICES =(
(0, "目录"),
(1, "菜单"),
(2, "按钮"),
)
menu_type = models.IntegerField(default=0, verbose_name="菜单类型", help_text="菜单类型")
web_path = models.CharField(max_length=128,default="/", verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
component = models.CharField(max_length=200, verbose_name="组件地址/按钮权限值", null=True, blank=True, help_text="组件地址/按钮权限值")
component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True,
help_text="组件名称")
status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态")
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示",
help_text="侧边栏中是否显示")
frame_out = models.BooleanField(default=False, blank=True, verbose_name="是否主框架外", help_text="是否主框架外")
class Meta:
db_table = table_prefix + "system_menu"
@@ -184,7 +185,6 @@ class Columns(CoreModel):
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
app = models.CharField(max_length=64, verbose_name='应用名')
model = models.CharField(max_length=64, verbose_name='表名')
menu = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name='菜单', db_constraint=False)
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
title = models.CharField(max_length=64, verbose_name='字段显示名')
is_query = models.BooleanField(default=1, verbose_name='是否可查询')
@@ -251,25 +251,25 @@ class RoleMenuPermission(CoreModel):
# ordering = ("-create_datetime",)
class RoleMenuButtonPermission(CoreModel):
class RoleApiPermission(CoreModel):
role = models.ForeignKey(
to="Role",
db_constraint=False,
related_name="role_menu_button",
related_name="role_api",
on_delete=models.CASCADE,
verbose_name="关联角色",
help_text="关联角色",
)
menu_button = models.ForeignKey(
to="MenuButton",
db_constraint=False,
related_name="menu_button_permission",
on_delete=models.CASCADE,
verbose_name="关联菜单按钮",
help_text="关联菜单按钮",
null=True,
blank=True
name = models.CharField(max_length=64, verbose_name="名称", help_text="名称")
api = models.CharField(max_length=200, verbose_name="接口地址", help_text="接口地址")
METHOD_CHOICES = (
(0, "GET"),
(1, "POST"),
(2, "PUT"),
(3, "DELETE"),
)
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
help_text="接口请求方法")
DATASCOPE_CHOICES = (
(0, "仅本人数据权限"),
(1, "本部门及以下数据权限"),
@@ -283,8 +283,8 @@ class RoleMenuButtonPermission(CoreModel):
help_text="数据权限-关联部门")
class Meta:
db_table = table_prefix + "role_menu_button_permission"
verbose_name = "角色按钮权限表"
db_table = table_prefix + "role_api_permission"
verbose_name = "角色接口权限表"
verbose_name_plural = verbose_name
ordering = ("-create_datetime",)
@@ -457,7 +457,7 @@ class SystemConfig(CoreModel):
help_text="父级",
)
title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")
key = models.CharField(max_length=20, verbose_name="", help_text="", db_index=True)
key = models.CharField(max_length=50, verbose_name="", help_text="", db_index=True)
value = models.JSONField(max_length=100, verbose_name="", help_text="", null=True, blank=True)
sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True)
status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态")

View File

@@ -8,19 +8,17 @@ from dvadmin.system.views.dictionary import DictionaryViewSet
from dvadmin.system.views.file_list import FileViewSet
from dvadmin.system.views.login_log import LoginLogViewSet
from dvadmin.system.views.menu import MenuViewSet
from dvadmin.system.views.menu_button import MenuButtonViewSet
from dvadmin.system.views.message_center import MessageCenterViewSet
from dvadmin.system.views.operation_log import OperationLogViewSet
from dvadmin.system.views.role import RoleViewSet
from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
from dvadmin.system.views.role_api_permission import RoleApiPermissionViewSet
from dvadmin.system.views.system_config import SystemConfigViewSet
from dvadmin.system.views.user import UserViewSet
from dvadmin.system.views.column import ColumnViewSet
system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet)
system_url.register(r'menu_button', MenuButtonViewSet)
system_url.register(r'role', RoleViewSet)
system_url.register(r'dept', DeptViewSet)
system_url.register(r'user', UserViewSet)
@@ -31,7 +29,7 @@ system_url.register(r'file', FileViewSet)
system_url.register(r'api_white_list', ApiWhiteListViewSet)
system_url.register(r'system_config', SystemConfigViewSet)
system_url.register(r'message_center', MessageCenterViewSet)
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
system_url.register(r'role_api_permission', RoleApiPermissionViewSet)
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
system_url.register(r'column', ColumnViewSet)

View File

@@ -32,15 +32,10 @@ class ColumnViewSet(CustomModelViewSet):
role_id = request.query_params.get('role')
app_name = request.query_params.get('app')
model_name = request.query_params.get('model')
menu = request.query_params.get('menu')
if not role_id or not model_name or not app_name or not menu:
return SuccessResponse([])
queryset = self.filter_queryset(self.get_queryset().filter(role_id=role_id, model=model_name, app=app_name,menu_id=menu))
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True, request=request)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True, request=request)
if not role_id or not model_name or not app_name:
return ErrorResponse(msg="参数错误")
queryset = Columns.objects.filter(role_id=role_id, model=model_name, app=app_name)
serializer = ColumnSerializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功")
def create(self, request, *args, **kwargs):
@@ -95,4 +90,4 @@ class ColumnViewSet(CustomModelViewSet):
serializer = self.get_serializer(data=data, request=request)
serializer.is_valid(raise_exception=True)
serializer.save()
return SuccessResponse(msg='匹配成功')
return DetailResponse(msg='匹配成功')

View File

@@ -9,7 +9,7 @@ from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Dept, RoleMenuButtonPermission, Users
from dvadmin.system.models import Dept, RoleApiPermission, Users
from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -135,7 +135,7 @@ class DeptViewSet(CustomModelViewSet):
queryset = Dept.objects.values('id', 'name', 'parent')
else:
role_ids = request.user.role.values_list('id', flat=True)
data_range = RoleMenuButtonPermission.objects.filter(role__in=role_ids).values_list('data_range', flat=True)
data_range = RoleApiPermission.objects.filter(role__in=role_ids).values_list('data_range', flat=True)
user_dept_id = request.user.dept.id
dept_list = [user_dept_id]
data_range_list = list(set(data_range))

View File

@@ -8,10 +8,10 @@
"""
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Menu, RoleMenuPermission
from dvadmin.system.views.menu_button import MenuButtonSerializer
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -21,7 +21,7 @@ class MenuSerializer(CustomModelSerializer):
菜单表的简单序列化器
"""
menuPermission = serializers.SerializerMethodField(read_only=True)
hasChild = serializers.SerializerMethodField()
hasChildren = serializers.SerializerMethodField()
def get_menuPermission(self, instance):
queryset = instance.menuPermission.order_by('-name').values('id', 'name', 'value')
@@ -31,7 +31,7 @@ class MenuSerializer(CustomModelSerializer):
else:
return None
def get_hasChild(self, instance):
def get_hasChildren(self, instance):
hasChild = Menu.objects.filter(parent=instance.id)
if hasChild:
return True
@@ -71,7 +71,7 @@ class WebRouterSerializer(CustomModelSerializer):
class Meta:
model = Menu
fields = (
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', 'web_path', 'component',
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'menu_type', 'web_path', 'component',
'component_name', 'cache', 'visible', 'status')
read_only_fields = ["id"]
@@ -90,46 +90,57 @@ class MenuViewSet(CustomModelViewSet):
create_serializer_class = MenuCreateSerializer
update_serializer_class = MenuCreateSerializer
search_fields = ['name', 'status']
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog']
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'menu_type']
def list(self, request):
@action(methods=['get'], detail=False)
def tree(self, request):
"""懒加载"""
request.query_params._mutable = True
params = request.query_params
parent = params.get('parent', None)
page = params.get('page', None)
limit = params.get('limit', None)
if page:
del params['page']
if limit:
del params['limit']
if params:
if parent:
queryset = self.queryset.filter(parent=parent)
else:
queryset = self.queryset.filter()
else:
queryset = self.queryset.filter(parent__isnull=True)
queryset = self.filter_queryset(queryset)
menu_type = params.get('menu_type', 0)
queryset = Menu.objects.filter(menu_type=menu_type).order_by('sort')
serializer = MenuSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data)
return DetailResponse(data=data)
@action(methods=['get'], detail=True)
def getChildren(self,request,pk):
"""
获取子菜单,用于菜单页面的懒加载
"""
queryset = Menu.objects.filter(parent=pk)
serializer = MenuSerializer(queryset, many=True, request=request)
data = serializer.data
return DetailResponse(data=data)
@action(methods=['GET'], detail=False, permission_classes=[])
def web_router(self, request):
"""用于前端获取当前角色的路由"""
user = request.user
is_admin = user.role.values_list('admin', flat=True)
if user.is_superuser or True in is_admin:
queryset = self.queryset.filter(status=1)
if user.is_superuser:
queryset = self.queryset.filter(status=1,menu_type__in=[0,1])
else:
role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id', flat=True)
queryset = Menu.objects.filter(id__in=menu_list)
queryset = Menu.objects.filter(id__in=menu_list,menu_type__in=[0,1])
serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功")
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def menu_button_all_permission(self, request):
"""
获取所有的按钮权限
:param request:
:return:
"""
is_superuser = request.user.is_superuser
if is_superuser:
queryset = Menu.objects.filter(menu_type=2).values_list('component', flat=True)
else:
role_id = request.user.role.values_list('id', flat=True)
queryset = Menu.objects.filter(role__in=role_id,menu_type=2).values_list('component',flat=True).distinct()
return DetailResponse(data=queryset)
@action(methods=['GET'], detail=False, permission_classes=[])
def get_all_menu(self, request):
"""用于菜单管理获取所有的菜单"""

View File

@@ -1,72 +0,0 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理
"""
from django.db.models import F
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import MenuButton, RoleMenuButtonPermission
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
class MenuButtonSerializer(CustomModelSerializer):
"""
菜单按钮-序列化器
"""
class Meta:
model = MenuButton
fields = ['id', 'name', 'value', 'api', 'method','menu']
read_only_fields = ["id"]
class MenuButtonCreateUpdateSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化器
"""
class Meta:
model = MenuButton
fields = "__all__"
read_only_fields = ["id"]
class MenuButtonViewSet(CustomModelViewSet):
"""
菜单按钮接口
list:查询
create:新增
update:修改
retrieve:单例
destroy:删除
"""
queryset = MenuButton.objects.all()
serializer_class = MenuButtonSerializer
create_serializer_class = MenuButtonCreateUpdateSerializer
update_serializer_class = MenuButtonCreateUpdateSerializer
extra_filter_class = []
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
def menu_button_all_permission(self,request):
"""
获取所有的按钮权限
:param request:
:return:
"""
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
queryset = MenuButton.objects.values_list('value',flat=True)
else:
role_id = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_id).values_list('menu_button__value',flat=True).distinct()
return DetailResponse(data=queryset)

View File

@@ -13,7 +13,6 @@ from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Role, Menu, MenuButton, Dept
from dvadmin.system.views.dept import DeptSerializer
from dvadmin.system.views.menu import MenuSerializer
from dvadmin.system.views.menu_button import MenuButtonSerializer
from dvadmin.utils.crud_mixin import FastCrudMixin
from dvadmin.utils.field_permission import FieldPermissionMixin
from dvadmin.utils.json_response import SuccessResponse, DetailResponse
@@ -39,7 +38,6 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
"""
menu = MenuSerializer(many=True, read_only=True)
dept = DeptSerializer(many=True, read_only=True)
permission = MenuButtonSerializer(many=True, read_only=True)
key = serializers.CharField(max_length=50,
validators=[CustomUniqueValidator(queryset=Role.objects.all(), message="权限字符必须唯一")])
name = serializers.CharField(max_length=50, validators=[CustomUniqueValidator(queryset=Role.objects.all())])
@@ -63,21 +61,11 @@ class MenuPermissionSerializer(CustomModelSerializer):
"""
菜单的按钮权限
"""
menuPermission = serializers.SerializerMethodField()
def get_menuPermission(self, instance):
is_superuser = self.request.user.is_superuser
if is_superuser:
queryset = MenuButton.objects.filter(menu__id=instance.id)
else:
menu_permission_id_list = self.request.user.role.values_list('permission', flat=True)
queryset = MenuButton.objects.filter(id__in=menu_permission_id_list, menu__id=instance.id)
serializer = MenuButtonSerializer(queryset, many=True, read_only=True)
return serializer.data
class Meta:
model = Menu
fields = ['id', 'parent', 'name', 'menuPermission']
fields = ['id', 'parent', 'name']
class MenuButtonPermissionSerializer(CustomModelSerializer):

View File

@@ -4,40 +4,46 @@
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理
@Remark: 接口权限管理
"""
from django.db.models import F, Subquery, OuterRef, Exists
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, Columns
from dvadmin.system.models import RoleApiPermission, Menu, MenuButton, Dept, RoleMenuPermission, Columns
from dvadmin.system.views.menu import MenuSerializer
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
class RoleApiPermissionSerializer(CustomModelSerializer):
"""
菜单按钮-序列化器
接口权限-序列化器
"""
dept_name = serializers.SerializerMethodField(help_text="部门名称")
def get_dept_name(self, instance):
dept_name_list = instance.dept.values_list("name",flat=True)
return ",".join(dept_name_list)
class Meta:
model = RoleMenuButtonPermission
model = RoleApiPermission
fields = "__all__"
read_only_fields = ["id"]
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
class RoleApiPermissionCreateUpdateSerializer(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)
class Meta:
model = RoleMenuButtonPermission
model = RoleApiPermission
fields = "__all__"
read_only_fields = ["id"]
@@ -51,14 +57,14 @@ class RoleButtonPermissionSerializer(CustomModelSerializer):
def get_isCheck(self, instance):
params = self.request.query_params
return RoleMenuButtonPermission.objects.filter(
return RoleApiPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
).exists()
def get_data_range(self, instance):
params = self.request.query_params
obj = RoleMenuButtonPermission.objects.filter(
obj = RoleApiPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
).first()
@@ -100,7 +106,7 @@ class RoleMenuPermissionSerializer(CustomModelSerializer):
def get_columns(self, instance):
params = self.request.query_params
col_list = Columns.objects.filter(role__id=params.get('role'),menu__id=instance['id'])
col_list = Columns.objects.filter(role__id=params.get('role'))
serializer = RoleColumnsSerializer(col_list,many=True,request=self.request)
return serializer.data
@@ -110,90 +116,21 @@ class RoleMenuPermissionSerializer(CustomModelSerializer):
model = Menu
fields = ['id','name','isCheck','btns','columns']
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
class RoleApiPermissionViewSet(CustomModelViewSet):
"""
菜单按钮接口
接口权限接口
list:查询
create:新增
update:修改
retrieve:单例
destroy:删除
"""
queryset = RoleMenuButtonPermission.objects.all()
serializer_class = RoleMenuButtonPermissionSerializer
create_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
queryset = RoleApiPermission.objects.all()
serializer_class = RoleApiPermissionSerializer
create_serializer_class = RoleApiPermissionCreateUpdateSerializer
update_serializer_class = RoleApiPermissionCreateUpdateSerializer
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
role = params.get('role',None)
if role is None:
return ErrorResponse(msg="未获取到角色信息")
is_superuser = request.user.is_superuser
if is_superuser:
queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all()
else:
role_id = request.user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id',flat=True)
queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id').all()
serializer = RoleMenuPermissionSerializer(queryset,many=True,request=request)
data = serializer.data
return DetailResponse(data=data)
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
def set_role_premission(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 menu in body:
if menu.get('isCheck'):
menu_parent = Menu.objects.filter(id=menu.get('id')).values('parent').first()
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu_parent.get('parent'))
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
for btn in menu.get('btns'):
if btn.get('isCheck'):
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),data_range=btn.get('data_range'))
instance.dept.set(btn.get('dept',[]))
for col in menu.get('columns'):
Columns.objects.filter(id=col.get('id')).update(is_query=col.get('is_query'),is_create=col.get('is_create'),is_update=col.get('is_update'))
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
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
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="参数错误")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def data_scope(self, request):
"""
@@ -231,7 +168,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
role_list = request.user.role.values_list('id', flat=True)
if params := request.query_params:
if menu_button_id := params.get('menu_button', None):
role_queryset = RoleMenuButtonPermission.objects.filter(
role_queryset = RoleApiPermission.objects.filter(
role__in=role_list, menu_button__id=menu_button_id
).values_list('data_range', flat=True)
data_range_list = list(set(role_queryset))
@@ -294,8 +231,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
"""
params = request.query_params
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
if is_superuser:
queryset = Dept.objects.values('id', 'name', 'parent')
else:
if not params:
@@ -304,62 +240,6 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
if menu_button is None:
return ErrorResponse(msg="参数错误")
role_list = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list, menu_button=None).values(
dept_id=F('dept__id'),
name=F('dept__name'),
parent=F('dept__parent')
)
return DetailResponse(data=queryset)
@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
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
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()
dept_ids = RoleApiPermission.objects.filter(role__in=role_list).values_list('dept__id',flat=True)
queryset = Dept.objects.filter(id__in=dept_ids).values('id', 'name', 'parent')
return DetailResponse(data=queryset)

View File

@@ -58,8 +58,38 @@ class RoleMenuPermissionViewSet(CustomModelViewSet):
update_serializer_class = RoleMenuPermissionCreateUpdateSerializer
extra_filter_class = []
@action(methods=['get'],detail=False)
def menu_permission_tree(self,request):
"""
获取菜单按钮树
"""
# params = request.query_params
# role_id = params.get('role',None)
# if role_id is None:
# return ErrorResponse(msg="未获取到角色")
if request.user.is_superuser:
queryset = Menu.objects.filter(status=1).values("id", "name", "parent_id")
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, id__in=menu_list).values('id','name', "parent_id").all()
return DetailResponse(data=queryset)
@action(methods=['get'],detail=False)
def get_menu_permission_checked(self,request):
"""
获取已授权的菜单ID
"""
params = request.query_params
role_id = params.get('role',None)
if role_id is None:
return ErrorResponse(msg="未获取到角色")
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('menu_id', flat=True)
queryset = Menu.objects.filter(status=1, id__in=menu_list).values_list('id',flat=True)
return DetailResponse(data=queryset)
@action(methods=['post'],detail=False)
def save_auth(self,request):
def save_menu_permission(self,request):
"""
保存页面菜单授权
:param request:

View File

@@ -20,7 +20,7 @@ from django_filters.rest_framework import DjangoFilterBackend
from django_filters.utils import get_model_field
from rest_framework.filters import BaseFilterBackend
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission
from dvadmin.system.models import Dept, ApiWhiteList, RoleApiPermission
def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
@@ -116,18 +116,17 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
if _pk: # 判断是否是单例查询
re_api = re.sub(_pk,'{id}', api)
role_id_list = request.user.role.values_list('id', flat=True)
role_permission_list=RoleMenuButtonPermission.objects.filter(
role_permission_list=RoleApiPermission.objects.filter(
role__in=role_id_list,
role__status=1,
menu_button__api=re_api,
menu_button__method=method).values(
api=re_api,
method=method).values(
'data_range',
role_admin=F('role__admin')
)
dataScope_list = [] # 权限范围列表
for ele in role_permission_list:
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
if ele.get("data_range") == 3 or ele.get("role_admin") == True:
if ele.get("data_range") == 3:
return queryset
dataScope_list.append(ele.get("data_range"))
dataScope_list = list(set(dataScope_list))

View File

@@ -79,6 +79,5 @@ class CustomPagination(PageNumberPagination):
('total', total),
('is_next', is_next),
('is_previous', is_previous),
('data', data),
('permission', self.request.permission_fields)
('data', data)
]))

View File

@@ -12,7 +12,7 @@ from django.contrib.auth.models import AnonymousUser
from django.db.models import F
from rest_framework.permissions import BasePermission
from dvadmin.system.models import ApiWhiteList, RoleMenuButtonPermission
from dvadmin.system.models import ApiWhiteList, RoleApiPermission
def ValidationApi(reqApi, validApi):
@@ -74,18 +74,18 @@ class CustomPermission(BasePermission):
methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH']
method = methodList.index(method)
# ***接口白名单***
api_white_list = ApiWhiteList.objects.values(permission__api=F('url'), permission__method=F('method'))
api_white_list = ApiWhiteList.objects.values('method',api=F('url'))
api_white_list = [
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('permission__method')) + '$' for item in api_white_list if item.get('permission__api')]
str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('method')) + '$' for item in api_white_list if item.get('api')]
# ********#
if not hasattr(request.user, "role"):
return False
role_id_list = request.user.role.values_list('id',flat=True)
userApiList = RoleMenuButtonPermission.objects.filter(role__in=role_id_list).values(permission__api=F('menu_button__api'), permission__method=F('menu_button__method')) # 获取当前用户的角色拥有的所有接口
userApiList = RoleApiPermission.objects.filter(role__in=role_id_list).values('api','method') # 获取当前用户的角色拥有的所有接口
ApiList = [
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('permission__method')) + '$' for item in userApiList if item.get('permission__api')]
str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('method')) + '$' for item in userApiList if item.get('api')]
new_api_ist = api_white_list + ApiList
new_api = api + ":" + str(method)
for item in new_api_ist:

View File

@@ -159,11 +159,14 @@ const initModeValueEcho = () => {
// 处理 icon 类型用于回显时tab 高亮与初始化数据
const initFontIconName = () => {
let name = 'ali';
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
// 初始化 tab 高亮回显
state.fontIconTabActive = name;
if(props.modelValue){
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
// 初始化 tab 高亮回显
state.fontIconTabActive = name;
}
return name;
};
// 初始化数据

View File

@@ -9,7 +9,7 @@ export const BtnPermissionStore = defineStore('BtnPermission', {
actions: {
async getBtnPermissionStore() {
request({
url: '/api/system/menu_button/menu_button_all_permission/',
url: '/api/system/menu/menu_button_all_permission/',
method: 'get',
}).then((ret: {
data: []

View File

@@ -9,7 +9,7 @@ import { request } from '/@/utils/service';
//扩展包
import { FsExtendsEditor,FsExtendsUploader } from '@fast-crud/fast-extends';
import '@fast-crud/fast-extends/dist/style.css';
import { successMessage, successNotification } from '/@/utils/message';
import { ElMessage } from "element-plus";
export default {
async install(app: any, options: any) {
// 先安装ui
@@ -18,9 +18,9 @@ export default {
app.use(FastCrud, {
//i18n, //i18n配置可选默认使用中文具体用法请看demo里的 src/i18n/index.js 文件
// 此处配置公共的dictRequest字典请求
async dictRequest({ dict }: any) {
async dictRequest({ url }: any) {
//根据dict的url异步返回一个字典数组
return await request({ url: dict.url, params: dict.params || {} }).then((res:any)=>{
return await request({ url: url, }).then((res:any)=>{
return res.data
});
},
@@ -41,14 +41,21 @@ export default {
transformRes: ({ res }: any) => {
//将pageRequest的返回数据转换为fast-crud所需要的格式
//return {records,currentPage,pageSize,total};
return { records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total };
if(res.page){
return { records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total };
}else{
return { records: res.data,currentPage: 1, pageSize: res.data.length, total: res.data.length };
}
},
},
form: {
afterSubmit(ctx: any) {
// 增加crud提示
if (ctx.res.code == 2000) {
successNotification(ctx.res.msg);
afterSubmit(ctx:any ) {
const {mode} = ctx
if (mode === "add") {
ElMessage.success({ message: "添加成功" });
} else if (mode === "edit") {
ElMessage.success({ message: "保存成功" });
}
},
},
@@ -100,10 +107,12 @@ export default {
});
},
successHandle(ret) {
console.log(111,ret)
// 上传完成后的结果处理, 此处应返回格式为{url:xxx,key:xxx}
return {
url: getBaseURL() + ret.data.url,
key: ret.data.id
url: ret.data.url,
key: ret.data.id,
...ret.data
};
}
}

View File

@@ -4,8 +4,16 @@ import { DictionaryStore } from '/@/stores/dictionary';
/**
* @method 获取指定name字典
*/
export const dictionary = (name: string) => {
export const dictionary = (key: string,value:string|number|null=null) => {
const dict = DictionaryStore()
const dictionary = toRaw(dict.data)
return dictionary[name]
if(value!==null){
for (let item of dictionary[key]) {
if (item.value === value) {
return item.label
}
}
return []
}
return dictionary[key]
}

View File

@@ -12,5 +12,5 @@ export const scanAndInstallPlugins = (app: any) => {
pluginNames.add(pluginsName);
}
pluginsAll = Array.from(pluginNames);
console.log('已发现插件:', pluginsAll);
console.table('已注册插件:', pluginsAll);
};

View File

@@ -1,8 +1,6 @@
<template>
<el-drawer size="70%" v-model="drawer" direction="rtl" destroy-on-close :before-close="handleClose">
<fs-page>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</el-drawer>
</template>
@@ -25,6 +23,7 @@ const handleClose = (done: () => void) => {
})
.then(() => {
done();
})
.catch(() => {
// catch error

View File

@@ -5,12 +5,19 @@ export const apiPrefix = '/api/system/menu/';
export function GetList(query: UserPageQuery) {
return request({
url: apiPrefix,
url: apiPrefix+"tree/",
method: 'get',
params: query,
});
}
export function GetChildren(id: InfoReq) {
return request({
url: apiPrefix + id + '/getChildren/',
method: 'get',
});
}
export function GetObj(id: InfoReq) {
return request({
url: apiPrefix + id + '/',

View File

@@ -1,41 +0,0 @@
import { request } from '/@/utils/service';
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
export const apiPrefix = '/api/system/menu_button/';
export function GetList(query: PageQuery) {
return request({
url: apiPrefix,
method: 'get',
params: query,
});
}
export function GetObj(id: InfoReq) {
return request({
url: apiPrefix + id,
method: 'get',
});
}
export function AddObj(obj: AddReq) {
return request({
url: apiPrefix,
method: 'post',
data: obj,
});
}
export function UpdateObj(obj: any) {
return request({
url: apiPrefix + obj.id + '/',
method: 'put',
data: obj,
});
}
export function DelObj(id: DelReq) {
return request({
url: apiPrefix + id + '/',
method: 'delete',
data: { id },
});
}

View File

@@ -1,202 +0,0 @@
import { AddReq, DelReq, EditReq, dict, CreateCrudOptionsRet, CreateCrudOptionsProps } from '@fast-crud/fast-crud';
import * as api from './api';
import { request } from '/@/utils/service';
//此处为crudOptions配置
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async () => {
if (context!.selectOptions.value.id) {
return await api.GetList({ menu: context!.selectOptions.value.id } as any);
} else {
return undefined;
}
};
const editRequest = async ({ form, row }: EditReq) => {
return await api.UpdateObj({ ...form, menu: row.menu });
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }: AddReq) => {
return await api.AddObj({ ...form, ...{ menu: context!.selectOptions.value.id } });
};
return {
crudOptions: {
search: {
container: {
action: {
//按钮栏配置
col: {
span: 8,
},
},
},
},
rowHandle: {
//固定右侧
fixed: 'right',
width: 200,
buttons: {
view: {
show: false,
},
edit: {
icon: '',
type: 'primary',
},
remove: {
icon: '',
type: 'primary',
},
},
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
form: {
col: { span: 24 },
labelWidth: '100px',
wrapper: {
is: 'el-dialog',
width: '600px',
},
},
columns: {
_index: {
title: '序号',
form: { show: false },
column: {
type: 'index',
align: 'center',
width: '70px',
columnSetDisabled: true, //禁止在列设置中选择
},
},
search: {
title: '关键词',
column: { show: false },
type: 'text',
search: { show: true },
form: {
show: false,
component: {
placeholder: '输入关键词搜索',
},
},
},
id: {
title: 'ID',
type: 'text',
column: { show: false },
search: { show: false },
form: { show: false },
},
name: {
title: '权限名称',
type: 'text',
search: { show: true },
column: {
minWidth: 120,
sortable: true,
},
form: {
rules: [{ required: true, message: '权限名称必填' }],
component: {
placeholder: '输入权限名称搜索',
props: {
clearable: true,
allowCreate: true,
filterable: true,
},
},
helper: {
render() {
return <el-alert title="手动输入" type="warning" description="页面中按钮的名称或者自定义一个名称" />;
},
},
},
},
value: {
title: '权限值',
type: 'text',
search: { show: false },
column: {
width: 120,
sortable: true,
},
form: {
rules: [{ required: true, message: '权限标识必填' }],
placeholder: '输入权限标识',
helper: {
render() {
return <el-alert title="唯一值" type="warning" description="用于判断前端按钮权限或接口权限" />;
},
},
},
},
method: {
title: '请求方式',
search: { show: false },
type: 'dict-select',
column: {
width: 120,
sortable: true,
},
dict: dict({
data: [
{ label: 'GET', value: 0 },
{ label: 'POST', value: 1, color: 'success' },
{ label: 'PUT', value: 2, color: 'warning' },
{ label: 'DELETE', value: 3, color: 'danger' },
],
}),
form: {
rules: [{ required: true, message: '必填项' }],
},
},
api: {
title: '接口地址',
search: { show: false },
type: 'dict-select',
dict: dict({
getData() {
return request({ url: '/swagger.json' }).then((res: any) => {
const ret = Object.keys(res.paths);
const data = [];
for (const item of ret) {
const obj: any = {};
obj.label = item;
obj.value = item;
data.push(obj);
}
return data;
});
},
}),
column: {
minWidth: 250,
sortable: true,
},
form: {
rules: [{ required: true, message: '必填项' }],
component: {
props: {
allowCreate: true,
filterable: true,
clearable: true,
},
},
helper: {
render() {
return <el-alert title="请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/" type="warning" />;
},
},
},
},
},
},
};
};

View File

@@ -1,28 +0,0 @@
<template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useFs } from '@fast-crud/fast-crud';
import { createCrudOptions } from './crud';
import { MenuTreeItemType } from '../../types';
// 当前选择的菜单信息
let selectOptions: any = ref({ name: null });
const { crudRef, crudBinding, crudExpose, context } = useFs({ createCrudOptions, context: { selectOptions } });
const { doRefresh, setTableData } = crudExpose;
const handleRefreshTable = (record: MenuTreeItemType) => {
if (!record.is_catalog && record.id) {
selectOptions.value = record;
doRefresh();
} else {
//清空表格数据
setTableData([]);
}
};
defineExpose({ selectOptions, handleRefreshTable });
</script>

View File

@@ -1,279 +0,0 @@
<template>
<div class="menu-form-com">
<div class="menu-form-alert">
1.红色星号表示必填;<br />
2.添加菜单如果是目录组件地址为空即可;<br />
3.添加根节点菜单父级菜单为空即可;
</div>
<el-form ref="formRef" :rules="rules" :model="menuFormData" label-width="80px" label-position="right">
<el-form-item label="菜单名称" prop="name">
<el-input v-model="menuFormData.name" placeholder="请输入菜单名称" />
</el-form-item>
<el-form-item label="父级菜单" prop="parent">
<el-tree-select
v-model="menuFormData.parent"
:props="defaultTreeProps"
:data="deptDefaultList"
:cache-data="props.cacheData"
lazy
check-strictly
clearable
:load="handleTreeLoad"
placeholder="请选择父级菜单"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="路由地址" prop="web_path">
<el-input v-model="menuFormData.web_path" placeholder="请输入路由地址,请以/开头" />
</el-form-item>
<el-form-item label="图标" prop="icon">
<IconSelector clearable v-model="menuFormData.icon" />
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item required label="状态">
<el-switch v-model="menuFormData.status" width="60" inline-prompt active-text="启用" inactive-text="禁用" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item v-if="menuFormData.status" required label="侧边显示">
<el-switch v-model="menuFormData.visible" width="60" inline-prompt active-text="显示" inactive-text="隐藏" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item required label="是否目录">
<el-switch v-model="menuFormData.is_catalog" width="60" inline-prompt active-text="是" inactive-text="否" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item v-if="!menuFormData.is_catalog" required label="外链接">
<el-switch v-model="menuFormData.is_link" width="60" inline-prompt active-text="是" inactive-text="否" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注">
<el-input v-model="menuFormData.description" maxlength="200" show-word-limit type="textarea" placeholder="请输入备注" />
</el-form-item>
<el-divider></el-divider>
<div style="min-height: 184px">
<el-form-item v-if="!menuFormData.is_catalog && !menuFormData.is_link" label="组件地址" prop="component">
<el-autocomplete
class="w-full"
v-model="menuFormData.component"
:fetch-suggestions="querySearch"
:trigger-on-focus="false"
clearable
:debounce="100"
placeholder="输入组件地址"
/>
</el-form-item>
<el-form-item v-if="!menuFormData.is_catalog && !menuFormData.is_link" label="组件名称" prop="component_name">
<el-input v-model="menuFormData.component_name" placeholder="请输入组件名称" />
</el-form-item>
<el-form-item v-if="!menuFormData.is_catalog && menuFormData.is_link" label="Url" prop="web_path">
<el-input v-model="menuFormData.web_path" placeholder="请输入Url" />
</el-form-item>
<el-form-item v-if="!menuFormData.is_catalog" label="缓存">
<el-switch v-model="menuFormData.cache" width="60" inline-prompt active-text="启用" inactive-text="禁用" />
</el-form-item>
</div>
<el-divider></el-divider>
</el-form>
<div class="menu-form-btns">
<el-button @click="handleSubmit" type="primary" :loading="menuBtnLoading">保存</el-button>
<el-button @click="handleCancel">取消</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from 'vue';
import { ElForm, FormRules } from 'element-plus';
import IconSelector from '/@/components/iconSelector/index.vue';
import { lazyLoadMenu, AddObj, UpdateObj } from '../../api';
import { successNotification } from '/@/utils/message';
import { MenuFormDataType, MenuTreeItemType, ComponentFileItem, APIResponseData } from '../../types';
import type Node from 'element-plus/es/components/tree/src/model/node';
interface IProps {
initFormData: Partial<MenuTreeItemType> | null;
treeData: MenuTreeItemType[];
cacheData: MenuTreeItemType[];
}
const defaultTreeProps: any = {
children: 'children',
label: 'name',
value: 'id',
isLeaf: (data: MenuTreeItemType[], node: Node) => {
if (node?.data.hasChild) {
return false;
} else {
return true;
}
},
};
const validateWebPath = (rule: any, value: string, callback: Function) => {
let pattern = /^\/.*?/;
let patternUrl = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
const reg = menuFormData.is_link ? patternUrl.test(value) : pattern.test(value);
if (reg) {
callback();
} else {
callback(new Error('请输入正确的地址'));
}
};
const props = withDefaults(defineProps<IProps>(), {
initFormData: () => null,
treeData: () => [],
cacheData: () => [],
});
const emit = defineEmits(['drawerClose']);
const formRef = ref<InstanceType<typeof ElForm>>();
const rules = reactive<FormRules>({
web_path: [{ required: true, message: '请输入正确的地址', validator: validateWebPath, trigger: 'blur' }],
name: [{ required: true, message: '菜单名称必填', trigger: 'blur' }],
component: [{ required: true, message: '请输入组件地址', trigger: 'blur' }],
component_name: [{ required: true, message: '请输入组件名称', trigger: 'blur' }],
});
let deptDefaultList = ref<MenuTreeItemType[]>([]);
let menuFormData = reactive<MenuFormDataType>({
parent: '',
name: '',
component: '',
web_path: '',
icon: '',
cache: true,
status: true,
visible: true,
component_name: '',
description: '',
is_catalog: false,
is_link: false,
});
let menuBtnLoading = ref(false);
const setMenuFormData = () => {
if (props.initFormData?.id) {
menuFormData.id = props.initFormData?.id || '';
menuFormData.name = props.initFormData?.name || '';
menuFormData.parent = props.initFormData?.parent || '';
menuFormData.component = props.initFormData?.component || '';
menuFormData.web_path = props.initFormData?.web_path || '';
menuFormData.icon = props.initFormData?.icon || '';
menuFormData.status = props.initFormData?.status || true;
menuFormData.visible = props.initFormData?.visible || true;
menuFormData.cache = props.initFormData?.cache || true;
menuFormData.component_name = props.initFormData?.component_name || '';
menuFormData.description = props.initFormData?.description || '';
menuFormData.is_catalog = props.initFormData?.is_catalog || false;
menuFormData.is_link = props.initFormData?.is_link || false;
}
};
const querySearch = (queryString: string, cb: any) => {
const files: any = import.meta.glob('@views/**/*.vue');
let fileLists: Array<any> = [];
Object.keys(files).forEach((queryString: string) => {
fileLists.push({
label: queryString.replace(/(\.\/|\.vue)/g, ''),
value: queryString.replace(/(\.\/|\.vue)/g, ''),
});
});
const results = queryString ? fileLists.filter(createFilter(queryString)) : fileLists;
// 统一去掉/src/views/前缀
results.forEach((val) => {
val.label = val.label.replace('/src/views/', '');
val.value = val.value.replace('/src/views/', '');
});
cb(results);
};
const createFilter = (queryString: string) => {
return (file: ComponentFileItem) => {
return file.value.toLowerCase().indexOf(queryString.toLowerCase()) !== -1;
};
};
/**
* 树的懒加载
*/
const handleTreeLoad = (node: Node, resolve: Function) => {
if (node.level !== 0) {
lazyLoadMenu({ parent: node.data.id }).then((res: APIResponseData) => {
resolve(res.data);
});
}
};
const handleSubmit = () => {
if (!formRef.value) return;
formRef.value.validate(async (valid) => {
if (!valid) return;
try {
let res;
menuBtnLoading.value = true;
if (menuFormData.id) {
res = await UpdateObj(menuFormData);
} else {
res = await AddObj(menuFormData);
}
if (res?.code === 2000) {
successNotification(res.msg as string);
handleCancel('submit');
}
} finally {
menuBtnLoading.value = false;
}
});
};
const handleCancel = (type: string = '') => {
emit('drawerClose', type);
formRef.value?.resetFields();
};
onMounted(async () => {
props.treeData.map((item) => {
deptDefaultList.value.push(item);
});
setMenuFormData();
});
</script>
<style lang="scss" scoped>
.menu-form-com {
margin: 10px;
overflow-y: auto;
.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-btns {
padding-bottom: 10px;
box-sizing: border-box;
}
}
</style>

View File

@@ -1,318 +0,0 @@
<template>
<el-input v-model="filterVal" :prefix-icon="Search" placeholder="请输入菜单名称" />
<div class="menu-tree-com">
<div class="mtc-head">
<el-icon size="16" color="#606266" class="mtc-head-icon">
<Menu />
</el-icon>
菜单列表
<el-tooltip
effect="dark"
placement="right"
content="1.红色菜单代表状态禁用; 2.添加菜单,如果是目录,组件地址为空即可; 3.添加根节点菜单父级ID为空即可; 4.支持拖拽菜单;"
>
<el-icon size="16" color="var(--el-color-primary)" class="mtc-tooltip">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
<el-tree
ref="treeRef"
:data="treeData"
:props="defaultTreeProps"
:filter-node-method="filterNode"
:load="handleTreeLoad"
lazy
:indent="45"
@node-click="handleNodeClick"
highlight-current
>
<template #default="{ node, data }">
<element-tree-line :node="node" :showLabelLine="false" :indent="32">
<span v-if="data.status" class="text-center font-black font-normal">
<SvgIcon :name="node.data.icon" color="var(--el-color-primary)" />
&nbsp;{{ node.label }}
</span>
<span v-else class="text-center font-black text-red-700 font-normal"> <SvgIcon :name="node.data.icon" />&nbsp;{{ node.label }} </span>
</element-tree-line>
</template>
</el-tree>
<div class="mtc-tags">
<el-tooltip effect="dark" content="新增">
<el-icon size="16" @click="handleUpdateMenu('create')" class="mtc-tags-icon">
<Plus />
</el-icon>
</el-tooltip>
<el-tooltip effect="dark" content="编辑">
<el-icon size="16" @click="handleUpdateMenu('update')" class="mtc-tags-icon">
<Edit />
</el-icon>
</el-tooltip>
<el-tooltip effect="dark" content="上移">
<el-icon size="16" @click="handleSort('up')" class="mtc-tags-icon">
<Top />
</el-icon>
</el-tooltip>
<el-tooltip effect="dark" content="下移">
<el-icon size="16" @click="handleSort('down')" class="mtc-tags-icon">
<Bottom />
</el-icon>
</el-tooltip>
<el-tooltip effect="dark" content="删除">
<el-icon size="16" @click="handleDeleteMenu()" class="mtc-tags-icon">
<Delete />
</el-icon>
</el-tooltip>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, toRaw, watch, h } from 'vue';
import { ElTree } from 'element-plus';
import { getElementLabelLine } from 'element-tree-line';
import { Search } from '@element-plus/icons-vue';
import SvgIcon from '/@/components/svgIcon/index.vue';
import { lazyLoadMenu, menuMoveUp, menuMoveDown } from '../../api';
import { warningNotification } from '/@/utils/message';
import { TreeTypes, MenuTreeItemType } from '../../types';
import type Node from 'element-plus/es/components/tree/src/model/node';
interface IProps {
treeData: TreeTypes[];
}
const ElementTreeLine = getElementLabelLine(h);
const defaultTreeProps: any = {
children: 'children',
label: 'name',
icon: 'icon',
isLeaf: (data: TreeTypes[], node: Node) => {
if (node.data.is_catalog) {
return false;
} else {
return true;
}
},
};
const treeRef = ref<InstanceType<typeof ElTree>>();
withDefaults(defineProps<IProps>(), {
treeData: () => [],
});
const emit = defineEmits(['treeClick', 'deleteDept', 'updateDept']);
let filterVal = ref('');
let sortDisable = ref(false);
let treeSelectMenu = ref<Partial<MenuTreeItemType>>({});
let treeSelectNode = ref<Node | null>(null);
watch(filterVal, (val) => {
treeRef.value!.filter(val);
});
/**
* 树的搜索事件
*/
const filterNode = (value: string, data: any) => {
if (!value) return true;
return toRaw(data).name.indexOf(value) !== -1;
};
/**
* 树的懒加载
*/
const handleTreeLoad = (node: Node, resolve: Function) => {
if (node.level !== 0) {
lazyLoadMenu({ parent: node.data.id }).then((res: APIResponseData) => {
resolve(res.data);
});
}
};
/**
* 树的点击事件
*/
const handleNodeClick = (record: MenuTreeItemType, node: Node) => {
treeSelectMenu.value = record;
treeSelectNode.value = node;
emit('treeClick', record);
};
/**
* 点击左侧编辑按钮
*/
const handleUpdateMenu = (type: string) => {
if (type === 'update') {
if (!treeSelectMenu.value.id) {
warningNotification('请选择菜单!');
return;
}
emit('updateDept', type, treeSelectMenu.value);
} else {
emit('updateDept', type);
}
};
/**
* 删除菜单
*/
const handleDeleteMenu = () => {
if (!treeSelectMenu.value.id) {
warningNotification('请选择菜单!');
return;
}
emit('deleteDept', treeSelectMenu.value.id, () => {
treeSelectMenu.value = {};
});
};
/**
* 移动操作
*/
const handleSort = async (type: string) => {
if (!treeSelectMenu.value.id) {
warningNotification('请选择菜单!');
return;
}
if (sortDisable.value) return;
const parentList = treeSelectNode.value?.parent.childNodes || [];
const index = parentList.findIndex((i) => i.data.id === treeSelectMenu.value.id);
const record = parentList.find((i) => i.data.id === treeSelectMenu.value.id);
if (type === 'up') {
if (index === 0) return;
parentList.splice(index - 1, 0, record as any);
parentList.splice(index + 1, 1);
sortDisable.value = true;
await menuMoveUp({ menu_id: treeSelectMenu.value.id });
sortDisable.value = false;
}
if (type === 'down') {
parentList.splice(index + 2, 0, record as any);
parentList.splice(index, 1);
sortDisable.value = true;
await menuMoveDown({ menu_id: treeSelectMenu.value.id });
sortDisable.value = false;
}
};
defineExpose({
treeRef,
});
</script>
<style lang="scss" scoped>
.menu-tree-com {
.mtc-head {
display: flex;
align-items: center;
margin-left: -8px;
color: #606266;
font-weight: 600;
.mtc-head-icon {
margin-right: 8px;
position: relative;
top: -1px;
}
.mtc-tooltip {
margin-left: 5px;
position: relative;
top: -1px;
}
}
.mtc-tags {
height: 40px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-around;
box-sizing: border-box;
.mtc-tags-icon {
cursor: pointer;
color: var(--el-color-primary);
}
}
}
</style>
<style lang="scss">
.menu-tree-com {
height: calc(100% - 60px);
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
.el-tree-node__content {
height: 32px !important;
}
.el-tree .el-tree-node__expand-icon svg {
display: none !important;
height: 0;
width: 0;
}
.el-tree-node__expand-icon {
font-size: 16px;
}
.el-tree-node__content > .el-tree-node__expand-icon {
padding: 0;
box-sizing: border-box;
margin-right: 5px;
margin-left: 24px;
}
.el-tree .el-tree-node__expand-icon.expanded {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
.el-tree .el-tree-node__expand-icon.is-leaf {
margin-left: 0;
}
.el-tree .el-tree-node__expand-icon:before {
background: url('../../../../../assets/img/menu-tree-show-icon.png') no-repeat center / 100%;
content: '';
display: block;
width: 24px;
height: 24px;
}
.el-tree .el-tree-node__expand-icon.expanded:before {
background: url('../../../../../assets/img/menu-tree-hidden-icon.png') no-repeat center / 100%;
content: '';
display: block;
width: 24px;
height: 24px;
}
.el-tree .is-leaf.el-tree-node__expand-icon::before {
display: block;
background: none !important;
content: '';
width: 18px;
height: 18px;
border: none;
}
}
</style>

View File

@@ -0,0 +1,371 @@
import * as api from './api';
import { CreateCrudOptionsProps, CreateCrudOptionsRet, dict, useCompute } from '@fast-crud/fast-crud';
const { compute } = useCompute();
import { shallowRef } from "vue";
import IconSelector from "/@/components/IconSelector/index.vue"
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }) => {
form.id = row.id;
await api.UpdateObj(form);
if (row.parent) {
//刷新父节点的状态
reloadTreeChildren(row.parent);
}
};
const delRequest = async ({ row }:any) => {
await api.DelObj(row.id);
if (row.parent) {
//刷新父节点的状态
reloadTreeChildren(row.parent);
}
};
const addRequest = async (context:any) => {
const {form} = context;
if(form.web_path===undefined||form.web_path===null){
form.web_path='/'
}
return await api.AddObj(form);
};
//刷新父节点状态
function reloadTreeChildren(parent:string|number) {
const data = crudExpose.getBaseTableRef().store.states.treeData;
if (data.value != null) {
const item = data.value[parent];
if (item != null) {
item.loaded = false;
item.expanded = false;
}
}
}
const createFilter = (queryString: string) => {
return (file: any) => {
return file.value.toLowerCase().indexOf(queryString.toLowerCase()) !== -1;
};
};
// 获取组件地址
const getCompoent = (queryString: string, cb: any) => {
const files: any = import.meta.glob('@views/**/*.vue');
let fileLists: Array<any> = [];
Object.keys(files).forEach((queryString: string) => {
fileLists.push({
label: queryString.replace(/(\.\/|\.vue)/g, ''),
value: queryString.replace(/(\.\/|\.vue)/g, ''),
});
});
const results = queryString ? fileLists.filter(createFilter(queryString)) : fileLists;
// 统一去掉/src/views/前缀
results.forEach((val) => {
val.label = val.label.replace('/src/views/', '');
val.value = val.value.replace('/src/views/', '');
});
cb(results);
};
// 验证路由地址
const { getFormData } = crudExpose;
const validateWebPath = (rule: any, value: string, callback: Function) => {
let pattern = /^\/.*?/;
let patternUrl = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
const reg = getFormData().is_link ? patternUrl.test(value) : pattern.test(value);
if (reg) {
callback();
} else {
callback(new Error('请输入正确的地址'));
}
};
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
pagination: {
show: false,
},
form: {
labelWidth: '120px',
row: { gutter: 20 },
},
table: {
lazy: true,
load: async (row: any, treeNode: unknown, resolve: (date: any[]) => void) => {
//懒加载,更新和删除后,需要刷新父节点的状态,见上方
const obj = await api.GetChildren(row.id);
resolve([...obj.data]);
},
},
columns: {
id: {
title: 'ID',
key: 'id',
type: 'number',
column: {
width: 100,
},
form: {
show: false,
},
},
menu_type: {
title: '类型',
type: 'dict-radio',
dict: dict({
data: [
{ label: '目录', value: 0 },
{ label: '菜单', value: 1 },
{ label: '按钮', value: 2 },
],
}),
form: {
value:0,
component:{
optionName: "el-radio-button"
},
valueChange({ form, value, getComponentRef }) {
if (value) {
getComponentRef("parent")?.reloadDict(); // 执行city的select组件的reloadDict()方法触发“city”重新加载字典
}
}
},
column: {
show: false,
},
},
parent: {
title: '父级',
dict: dict({
prototype: true,
url({form}){
if(form && form.menu_type===1){
return '/api/system/menu/tree/?menu_type=0'
}else{
return `/api/system/menu/tree/?menu_type=1`
}
return undefined
},
label: 'name',
value: 'id',
}),
type: 'dict-select',
form: {
show:compute(({form})=>{
return [1,2].includes(form.menu_type);
}),
rules: [{ required: true, message: '必填项' }],
component: {},
},
column: {
show: false,
},
},
name: {
title: '名称',
search: { show: true },
type: 'text',
form: {
rules: [{ required: true, message: '请输入名称' }],
component: {
placeholder: '请输入名称',
},
},
},
icon: {
title: '图标',
form:{
show:compute(({form})=>{
return [0,1].includes(form.menu_type);
}),
component:{
name: shallowRef(IconSelector),
vModel: "modelValue",
}
},
column: {
component: {
style: 'font-size:18px',
},
},
},
sort: {
title: '排序',
type: 'number',
form: {
value: 1,
show:compute(({form})=>{
return [0,1].includes(form.menu_type);
}),
},
},
is_link: {
title: '外链接',
type: 'dict-switch',
dict: dict({
data: [
{ label: '是', value: true },
{ label: '否', value: false },
],
}),
form: {
value: false,
show:compute(({form})=>{
return [1].includes(form.menu_type);
}),
},
},
web_path: {
title: '路由地址',
form: {
show:compute(({form})=>{
return [1].includes(form.menu_type);
}),
helper: compute(({ form }) => {
return form.is_link ? '请输入http开头的地址' : '浏览器中url的地址,请以/开头';
}),
rules: [{ required: true, message: '请输入路由地址', validator: validateWebPath, trigger: 'blur' }],
component: {
placeholder: '请输入路由地址',
},
},
column: {
show: false,
},
},
component_name: {
title: '组件名称',
form: {
show:compute(({form})=>{
return [1].includes(form.menu_type);
}),
rules: [{ required: true, message: '请输入组件名称', trigger: 'blur' }],
component: {
placeholder: '请输入组件名称',
},
},
column: {
show: false,
},
},
component: {
title: compute(({ form }) => {
return form.menu_type === 1 ? '组件地址' : '按钮权限值';
}),
form: {
col:{
span:24,
},
show: compute(({ form }) => {
return [1,2].includes(form.menu_type)
}),
helper: compute(({ form }) => {
return form.menu_type === 1 ? 'src/views下的文件夹地址' : '按钮权限值是唯一的标识';
}),
rules: [{ required: true, message: '请输入组件地址', trigger: 'blur' }],
component: {
style: {
width: '100%',
},
disabled: compute(({ form }) => {
if(form.is_link&&form.menu_type===1){
form.component ="无"
return form.is_link
}
form.component =null
return false
}),
name: compute(({ form }) => {
return [1,2].includes(form.menu_type)? 'el-autocomplete' : 'el-input';
}),
triggerOnFocus: false,
fetchSuggestions: (query, cb) => {
return getCompoent(query, cb);
},
},
},
column: {
show: false,
},
},
visible: {
title: '侧边可见',
search: { show: true },
type: 'dict-radio',
dict: dict({
data: [
{ label: '是', value: true },
{ label: '否', value: false },
],
}),
form: {
value: true,
show:compute(({form})=>{
return [1].includes(form.menu_type)
}),
},
},
cache: {
title: '是否缓存',
search: { show: true },
type: 'dict-radio',
dict: dict({
data: [
{ label: '是', value: true },
{ label: '否', value: false },
],
}),
form: {
value: false,
show:compute(({form})=>{
return [1].includes(form.menu_type)
}),
},
},
frame_out: {
title: '主框架外展示',
search: { show: true },
type: 'dict-radio',
dict: dict({
data: [
{ label: '是', value: true },
{ label: '否', value: false },
],
}),
form: {
value: false,
show:compute(({form})=>{
return [1].includes(form.menu_type)
}),
},
},
status: {
title: '状态',
search: { show: true },
type: 'dict-radio',
dict: dict({
data: [
{ label: '启用', value: true },
{ label: '禁用', value: false },
],
}),
form: {
value: true,
show:compute(({form})=>{
return [0,1].includes(form.menu_type);
}),
},
},
},
},
};
}

View File

@@ -1,142 +1,41 @@
<template>
<fs-page>
<el-row class="menu-el-row">
<el-col :span="6">
<div class="menu-box menu-left-box">
<MenuTreeCom
ref="menuTreeRef"
:treeData="menuTreeData"
@treeClick="handleTreeClick"
@updateDept="handleUpdateMenu"
@deleteDept="handleDeleteMenu"
/>
</div>
</el-col>
<fs-page>
<fs-crud ref="crudRef" v-bind="crudBinding">
<el-col :span="18">
<div class="menu-box menu-right-box">
<MenuButtonCom ref="menuButtonRef" />
</div>
</el-col>
</el-row>
<el-drawer v-model="drawerVisible" title="菜单配置" direction="rtl" size="500px" :close-on-click-modal="false" :before-close="handleDrawerClose">
<MenuFormCom
v-if="drawerVisible"
:initFormData="drawerFormData"
:cacheData="menuTreeCacheData"
:treeData="menuTreeData"
@drawerClose="handleDrawerClose"
/>
</el-drawer>
</fs-page>
</fs-crud>
<fs-form-wrapper ref="addChildrenRef" />
</fs-page>
</template>
<script lang="ts" setup name="menuPages">
import { ref, onMounted } from 'vue';
import XEUtils from 'xe-utils';
import { ElMessageBox } from 'element-plus';
import MenuTreeCom from './components/MenuTreeCom/index.vue';
import MenuButtonCom from './components/MenuButtonCom/index.vue';
import MenuFormCom from './components/MenuFormCom/index.vue';
import { GetList, DelObj } from './api';
import { successNotification } from '/@/utils/message';
import { APIResponseData, MenuTreeItemType } from './types';
<script lang="ts" setup>
import {ref, onMounted, reactive, computed} from "vue";
import createCrudOptions from "./crud";
import {useExpose, useCrud, useCompute} from "@fast-crud/fast-crud";
import {AddObj} from "./api";
import {ElMessage } from "element-plus"
let menuTreeData = ref([]);
let menuTreeCacheData = ref<MenuTreeItemType[]>([]);
let drawerVisible = ref(false);
let drawerFormData = ref<Partial<MenuTreeItemType>>({});
let menuTreeRef = ref<InstanceType<typeof MenuTreeCom> | null>(null);
let menuButtonRef = ref<InstanceType<typeof MenuButtonCom> | null>(null);
const getData = () => {
GetList({}).then((ret: APIResponseData) => {
const responseData = ret.data;
const result = XEUtils.toArrayTree(responseData, {
parentKey: 'parent',
children: 'children',
strict: true,
});
menuTreeData.value = result;
});
};
/**
* 菜单的点击事件
*/
const handleTreeClick = (record: MenuTreeItemType) => {
menuButtonRef.value?.handleRefreshTable(record);
};
// crud组件的ref
const crudRef = ref();
// crud 配置的ref
const crudBinding = ref();
// 暴露的方法
const { crudExpose } = useExpose({ crudRef, crudBinding });
/**
* 部门的 新增 or 编辑 事件
*/
const handleUpdateMenu = (type: string, record?: MenuTreeItemType) => {
if (type === 'update' && record) {
const parentData = menuTreeRef.value?.treeRef?.currentNode.parent.data || {};
menuTreeCacheData.value = [parentData];
drawerFormData.value = record;
}
drawerVisible.value = true;
};
const handleDrawerClose = (type?: string) => {
if (type === 'submit') {
getData();
}
drawerVisible.value = false;
drawerFormData.value = {};
};
/**
* 部门的删除事件
*/
const handleDeleteMenu = (id: string, callback: Function) => {
ElMessageBox.confirm('您确认删除该菜单项吗?', '温馨提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
const res: APIResponseData = await DelObj(id);
callback();
if (res?.code === 2000) {
successNotification(res.msg as string);
getData();
}
});
};
// 你的crud配置
const { crudOptions } = createCrudOptions({ crudExpose });
// 初始化crud配置
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
// 你可以调用此方法重新初始化crud配置
// resetCrudOptions(options)
// 页面打开后获取列表数据
onMounted(() => {
getData();
crudExpose.doRefresh();
});
</script>
<style lang="scss" scoped>
.menu-el-row {
height: 100%;
overflow: hidden;
.el-col {
height: 100%;
padding: 10px 0;
box-sizing: border-box;
}
}
.menu-box {
height: 100%;
padding: 10px;
background-color: #fff;
box-sizing: border-box;
}
.menu-left-box {
position: relative;
border-radius: 0 8px 8px 0;
margin-right: 10px;
}
.menu-right-box {
border-radius: 8px 0 0 8px;
}
</style>

View File

@@ -0,0 +1,57 @@
import { request } from '/@/utils/service';
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
import XEUtils from "xe-utils";
export const apiPrefix = '/api/system/role_api_permission/';
export function GetList(query: UserPageQuery) {
return request({
url: apiPrefix,
method: 'get',
params: query,
});
}
export function GetObj(id: InfoReq) {
return request({
url: apiPrefix + id,
method: 'get',
});
}
export function AddObj(obj: AddReq) {
return request({
url: apiPrefix,
method: 'post',
data: obj,
});
}
export function UpdateObj(obj: EditReq) {
return request({
url: apiPrefix + obj.id + '/',
method: 'put',
data: obj,
});
}
export function DelObj(id: DelReq) {
return request({
url: apiPrefix + id + '/',
method: 'delete',
data: { id },
});
}
/**
* 获取数据范围授权的所有部门
* @param query
* @constructor
*/
export function GetAllDeptData(query: UserPageQuery) {
return request({
url: apiPrefix+'role_to_dept_all/',
method: 'get',
params: query,
}).then((res:any)=>{
return XEUtils.toArrayTree(res.data,{ parentKey: 'parent', key: 'id', children: 'children',})
})
}

View File

@@ -0,0 +1,211 @@
import * as api from './api';
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
import { request } from '/@/utils/service';
import { dictionary } from '/@/utils/dictionary';
import { successMessage } from '/@/utils/message';
import {inject} from "vue";
export const createCrudOptions = function ({ crudExpose,propsContext }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
form.role = row.role;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }: AddReq) => {
form.role = propsContext.roleId
return await api.AddObj(form);
};
//权限判定
const hasPermissions = inject("$hasPermissions")
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
rowHandle: {
//固定右侧
fixed: 'right',
},
form: {
col: { span: 24 },
labelWidth: '110px',
wrapper: {
is: 'el-dialog',
width: '600px',
},
},
columns: {
_index: {
title: '序号',
form: { show: false },
column: {
//type: 'index',
align: 'center',
width: '70px',
columnSetDisabled: true, //禁止在列设置中选择
//@ts-ignore
formatter: (context) => {
//计算序号,你可以自定义计算规则,此处为翻页累加
let index = context.index ?? 1;
let pagination: any = crudExpose!.crudBinding.value.pagination;
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
},
},
},
name:{
title: '接口名称',
type: 'text',
search:{
show:true
},
column:{
width:150
}
},
method: {
title: '请求方式',
sortable: 'custom',
search: {
show:true
},
type: 'dict-select',
dict: dict({
data: [
{
label: 'GET',
value: 0,
},
{
label: 'POST',
value: 1,
},
{
label: 'PUT',
value: 2,
},
{
label: 'DELETE',
value: 3,
},
{
label: 'PATCH',
value: 4,
},
],
}),
column:{
width: 100,
},
form: {
rules: [
// 表单校验规则
{
required: true,
message: '必填项',
},
],
component: {
span: 12,
},
},
},
api: {
title: '接口地址',
sortable: 'custom',
search: {
disabled: true,
},
type: 'dict-select',
dict: dict({
async getData(dict: any) {
return request('/swagger.json').then((ret: any) => {
const res = Object.keys(ret.paths);
const data = [];
for (const item of res) {
const obj = { label: '', value: '' };
obj.label = item;
obj.value = item;
data.push(obj);
}
return data;
});
},
}),
column:{
minWidth: 200,
},
form: {
rules: [
// 表单校验规则
{
required: true,
message: '必填项',
},
],
component: {
span: 24,
props: {
allowCreate: true,
filterable: true,
clearable: true,
},
},
helper: {
position: 'label',
tooltip: {
placement: 'top-start',
},
text: '请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/',
},
},
},
data_range: {
title: '数据权限范围',
search: {
show:true
},
type: 'dict-select',
dict:dict({
url:'/api/system/role_api_permission/data_scope/'
}),
column: {
minWidth:120,
},
},
dept:{
title:'数据权限部门',
column:{
minWidth:120,
cellRender(scope){
return <div>{scope.row.dept_name}</div>
}
},
form:{
show: compute(({form})=>{
return form.data_range===4
})
}
},
description:{
title:'描述',
type:'textarea',
column:{
width:300
}
}
},
},
};
};

View File

@@ -0,0 +1,59 @@
<template>
<div style="height: 86vh">
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #form_dept="scope">
<div>
<el-tree-select
v-model="scope.form.dept"
:data="allDeptData"
:props="treeProps"
node-key="id"
multiple
:render-after-expand="false"
show-checkbox
check-strictly
check-on-click-node
>
<template #default="{ data: { name } }">
{{ name }}
</template
>
</el-tree-select>
</div>
</template>
</fs-crud>
</div>
</template>
<script lang="ts" setup>
import {ref, onMounted, reactive} from 'vue';
import {useFs} from '@fast-crud/fast-crud';
import {createCrudOptions} from './crud';
import {GetAllDeptData} from './api'
const propsContext = defineProps({
roleId:{
type:String||Number,
required:true
}
})
const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions,propsContext});
// 获取所有部门数据
const allDeptData = ref<any[]>([]);
const treeProps ={
label: 'name',
value: 'id',
children: 'children',
}
onMounted(async () => {
const res = await GetAllDeptData({role:propsContext.roleId});
allDeptData.value = res;
crudExpose.doSearch({form:{role:propsContext.roleId}})
});
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,65 @@
import { request } from '/@/utils/service';
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
import XEUtils from "xe-utils";
import {CurrentInfoType} from "/@/views/system/columns/types";
export const apiPrefix = '/api/system/column/';
export function GetList(query: UserPageQuery) {
return request({
url: apiPrefix,
method: 'get',
params: query,
});
}
export function GetObj(id: InfoReq) {
return request({
url: apiPrefix + id,
method: 'get',
});
}
export function AddObj(obj: AddReq) {
return request({
url: apiPrefix,
method: 'post',
data: obj,
});
}
export function UpdateObj(obj: EditReq) {
return request({
url: apiPrefix + obj.id + '/',
method: 'put',
data: obj,
});
}
export function DelObj(id: DelReq) {
return request({
url: apiPrefix + id + '/',
method: 'delete',
data: { id },
});
}
/**
* 获取所有model
*/
export function getModelList() {
return request({
url: '/api/system/column/get_models/',
method: 'get',
});
}
/**
* 自动匹配field
* @param data
*/
export function automatchColumnsData(data: CurrentInfoType) {
return request({
url: '/api/system/column/auto_match_fields/',
method: 'post',
data,
});
}

View File

@@ -0,0 +1,208 @@
import * as api from './api';
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
import { request } from '/@/utils/service';
import { dictionary } from '/@/utils/dictionary';
import { successMessage, successNotification, warningNotification } from '/@/utils/message';
import { inject } from 'vue';
import { automatchColumnsData } from '/@/views/system/columns/components/ColumnsTableCom/api';
export const createCrudOptions = function ({ crudExpose, props }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery) => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
return await api.UpdateObj(form);
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }: AddReq) => {
form.role = props.role;
form.model = props.model;
form.model = props.app;
return await api.AddObj(form);
};
/**
* 自动匹配列
*/
const handleAutomatch = async () => {
if (props.role && props.model && props.app) {
const res = await automatchColumnsData(props);
if (res?.code === 2000) {
successNotification('匹配成功');
}
crudExpose.doSearch({ form: { role: props.role, model: props.model, app: props.app } });
}
warningNotification('请选择角色和模型表!');
};
//权限判定
const hasPermissions = inject('$hasPermissions');
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
pagination: {
show: false,
},
actionbar: {
buttons: {
auto: {
text: '自动匹配',
type: 'success',
click: () => {
return handleAutomatch();
},
},
},
},
rowHandle: {
//固定右侧
fixed: 'right',
},
form: {
col: { span: 24 },
labelWidth: '110px',
wrapper: {
is: 'el-dialog',
width: '600px',
},
},
columns: {
_index: {
title: '序号',
form: { show: false },
column: {
//type: 'index',
align: 'center',
width: '70px',
columnSetDisabled: true, //禁止在列设置中选择
//@ts-ignore
formatter: (context) => {
//计算序号,你可以自定义计算规则,此处为翻页累加
let index = context.index ?? 1;
let pagination: any = crudExpose!.crudBinding.value.pagination;
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
},
},
},
field_name: {
title: '字段名',
type: 'text',
search: {
show: true,
},
column: {
width: 150,
},
},
title: {
title: '中文名',
sortable: 'custom',
search: {
show: true,
},
type: 'text',
column: {
width: 100,
},
form: {
rules: [
// 表单校验规则
{
required: true,
message: '必填项',
},
],
component: {
span: 12,
},
},
},
is_create: {
title: '创建时显示',
sortable: 'custom',
search: {
disabled: true,
},
type: 'dict-switch',
dict: dict({
data: [
{ value: true, label: '启用' },
{ value: false, label: '禁用' },
],
}),
form: {
value: true,
},
column: {
valueChange(context){
return api.UpdateObj(context.row)
},
component: {
name: 'fs-dict-switch',
},
},
},
is_update: {
title: '编辑时显示',
search: {
show: true,
},
type: 'dict-switch',
dict: dict({
data: [
{ value: true, label: '启用' },
{ value: false, label: '禁用' },
],
}),
form: {
value: true,
},
column: {
component: {
name: 'fs-dict-switch',
onChange: compute((context) => {
//动态onChange方法测试
return () => {
console.log('onChange', context.row.switch);
};
}),
},
},
},
is_query: {
title: '列表中显示',
type: 'dict-switch',
dict: dict({
data: [
{ value: true, label: '启用' },
{ value: false, label: '禁用' },
],
}),
form: {
value: true,
},
column: {
component: {
name: 'fs-dict-switch',
onChange: compute((context) => {
//动态onChange方法测试
return () => {
console.log('onChange', context.row.switch);
};
}),
},
},
},
},
},
};
};

View File

@@ -0,0 +1,69 @@
<template>
<div>
<el-divider>模型表</el-divider>
<div class="model-card">
<div v-for="(item,index) in allModelData" :key="index">
<el-text @click="onModelChecked(item,index)" :type="modelCheckIndex===index?'primary':''">{{item.app + '--'+item.title + '('+item.key+')' }}</el-text>
</div>
</div>
<el-divider>字段权限</el-divider>
<div style="height: 50vh">
<fs-crud ref="crudRef" v-bind="crudBinding">
</fs-crud>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref, onMounted, reactive} from 'vue';
import {useFs} from '@fast-crud/fast-crud';
import {createCrudOptions} from './crud';
import {getModelList} from './api'
const propsContext = defineProps({
roleId:{
type:String||Number,
required:true
}
})
const props = reactive({
role: '',
model: '',
app: '',
menu:''
})
// 获取所有model
const allModelData = ref<any[]>([]);
const modelCheckIndex=ref(-1)
const onModelChecked = (row,index)=>{
modelCheckIndex.value = index
props.model = row.key
props.app = row.app
props.role=propsContext.roleId
crudExpose.setTableData([])
crudExpose.doSearch({form:{role:propsContext.roleId,model:row.key,app:row.app}})
}
const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions,props});
onMounted(async () => {
const res = await getModelList();
allModelData.value = res.data;
});
</script>
<style scoped lang="scss">
.model-card{
height: 20vh;
overflow-y: scroll;
div{
margin: 15px 0;
cursor: pointer;
}
}
</style>

View File

@@ -1,18 +1,49 @@
import { request } from "/@/utils/service";
import XEUtils from 'xe-utils';
/**
*
*
* @param roleId
* @param query
*/
export function getRolePremission(query:object) {
export function getMenuPremissionTree(query:object) {
return request({
url: '/api/system/role_menu_button_permission/get_role_premission/',
url: '/api/system/role_menu_permission/menu_permission_tree/',
method: 'get',
params:query
}).then((res:any)=>{
return XEUtils.toArrayTree(res.data,{ parentKey: 'parent_id', key: 'id', children: 'children',})
})
}
/**
*
* @param query
*/
export function getMenuPremissionChecked(query:object) {
return request({
url: '/api/system/role_menu_permission/get_menu_permission_checked/',
method: 'get',
params:query
}).then((res:any)=>{
return res.data
})
}
/**
*
*/
export function saveMenuPremission(data:object) {
return request({
url:'/api/system/role_menu_permission/save_menu_permission/',
method:'post',
data
})
}
/***
*
* @param roleId

View File

@@ -0,0 +1,212 @@
<template>
<div>
<MenuPermissionTree
ref="permissionTreeRef"
:tree="menuPermissionTreeData"
:default-expand-all="true"
:editable="false"
node-key="id"
show-checkbox
:props="{ label: 'title' }"></MenuPermissionTree>
<div style="margin-top: 2em">
<el-button type="primary" @click="updatePermission">确定</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import {ref, onMounted, defineProps, watch, computed, reactive,nextTick} from 'vue';
import XEUtils from 'xe-utils';
import {errorNotification} from '/@/utils/message';
import {getDataPermissionRange, getDataPermissionDept, getMenuPremissionTree, saveMenuPremission,getMenuPremissionChecked} from './api';
import {MenuDataType, DataPermissionRangeType, CustomDataPermissionDeptType} from './types';
import {ElMessage} from 'element-plus'
import MenuPermissionTree from "./menuPermissionTree.vue";
const props = defineProps({
roleId: {
type: Number,
default: -1
}
})
//获取菜单/按钮权限
const permissionTreeRef = ref();
let menuPermissionTreeData = ref<MenuDataType[]>([]);
const getMenuPremissionTreeData = async () => {
const resMenu = await getMenuPremissionTree({role: props.roleId})
menuPermissionTreeData.value = resMenu
nextTick(() => {
updateChecked(props.roleId);
});
}
// 如果勾选节点中存在非叶子节点tree组件会将其所有子节点全部勾选
// 所以要找出所有叶子节点仅勾选叶子节点tree组件会将父节点同步勾选
function getAllCheckedLeafNodeId(tree, checkedIds, temp) {
for (let i = 0; i < tree.length; i++) {
const item = tree[i];
if (item.children && item.children.length !== 0) {
getAllCheckedLeafNodeId(item.children, checkedIds, temp);
} else {
if (checkedIds.indexOf(item.id) !== -1) {
temp.push(item.id);
}
}
}
return temp;
}
async function updateChecked(roleId:string|number) {
let checkedIds = await getMenuPremissionChecked({role: roleId});
// 找出所有的叶子节点
checkedIds = getAllCheckedLeafNodeId(menuPermissionTreeData.value, checkedIds, []);
permissionTreeRef.value.setCheckedKeys(checkedIds);
}
/**
* 更新菜单权限
*/
async function updatePermission() {
const roleId = props.roleId;
const { checked, halfChecked } = permissionTreeRef.value.getChecked();
const allChecked = [...checked, ...halfChecked];
const menuIds = allChecked.filter(item=>item !== -1)
await saveMenuPremission({role: roleId, menu: menuIds})
handleDrawerClose();
ElMessage.success("授权成功");
}
const emit = defineEmits(['handleDrawerClose']);
const handleDrawerClose = () => {
emit('handleDrawerClose')
}
defineExpose({getMenuPremissionTreeData})
</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;
span {
font-size: 16px;
}
}
.pc-collapse-main {
padding-top: 15px;
box-sizing: border-box;
.pccm-item {
margin-bottom: 10px;
.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;
}
.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

@@ -0,0 +1,205 @@
<template>
<el-tree
v-if="computedTree"
ref="treeRef"
class="fs-permission-tree"
:class="{ 'is-editable': editable }"
:data="computedTree"
:props="computedProps"
@check="onChecked"
>
<template #default="{ data }">
<div :class="'node-title-pane'">
<div class="node-title">{{ data.name }}</div>
<div v-if="editable === true" class="node-suffix">
<fs-icon v-if="actions.add !== false" :icon="ui.icons.add" @click.stop="add(data)" />
<fs-icon v-if="actions.edit !== false && data.id !== -1" :icon="ui.icons.edit" @click.stop="edit(data)" />
<fs-icon
v-if="actions.remove !== false && data.id !== -1"
:icon="ui.icons.remove"
@click.stop="remove(data)"
/>
</div>
</div>
</template>
</el-tree>
</template>
<script lang="ts">
import _ from "lodash-es";
import { useUi, utils } from "@fast-crud/fast-crud";
import { defineComponent, ref, computed, nextTick, onMounted } from "vue";
export default defineComponent({
name: "FsPermissionTree",
props: {
/**
* 树形数据
* */
tree: {},
/**
* 是否可编辑
*/
editable: {
default: true
},
actions: {
default: {}
},
props: {}
} as any,
emits: ["add", "edit", "remove"],
setup(props: any, ctx) {
const treeRef = ref();
const { ui } = useUi();
const computedTree = computed(() => {
if (props.tree == null) {
return null;
}
const clone = _.cloneDeep(props.tree);
utils.deepdash.forEachDeep(clone, (value, key, pNode, context) => {
if (value == null) {
return;
}
if (!(value instanceof Object) || value instanceof Array) {
return;
}
if (value.class === "is-leaf-node") {
//处理过,无需再次处理
return;
}
if (value.children != null && value.children.length > 0) {
return;
}
const parents = context.parents;
if (parents.length < 2) {
return;
}
const parent = parents[parents.length - 2].value;
//看parent下面的children是否全部都没有children
for (const child of parent.children) {
if (child.children != null && child.children.length > 0) {
//存在child有children
return;
}
}
// 所有的子节点都没有children
parent.class = "is-twig-node"; // 连接叶子节点的末梢枝杈节点
for (const child of parent.children) {
child.class = "is-leaf-node";
}
});
console.log("nodes ", clone);
return [
{
name: "根节点",
id: -1,
children: clone
}
];
});
function add(data) {
ctx.emit("add", data);
}
function edit(data) {
ctx.emit("edit", data);
}
function remove(data) {
ctx.emit("remove", data);
}
function onChecked(a, b, c) {
console.log("chedcked", a, b, c);
}
function getChecked() {
const checked = treeRef.value.getCheckedKeys();
const halfChecked = treeRef.value.getHalfCheckedKeys();
return {
checked,
halfChecked
};
}
function setCheckedKeys(ids) {
treeRef.value.setCheckedKeys(ids);
}
function customNodeClass(data) {
if (data.class) {
return data.class;
}
return null;
}
const computedProps = computed(() => {
return _.merge({ class: customNodeClass }, props.props);
});
return {
computedTree,
add,
edit,
remove,
treeRef,
onChecked,
getChecked,
computedProps,
setCheckedKeys,
ui
};
}
});
</script>
<style lang="scss">
.fs-permission-tree {
height: 80vh;
overflow-y: scroll;
.el-tree-node.is-expanded.is-twig-node > .el-tree-node__children {
display: flex;
flex-wrap: wrap;
}
.is-twig-node > .el-tree-node__children > :not(:first-child) .el-tree-node__content {
padding-left: 0 !important;
}
.el-tree-node__content {
box-sizing: content-box;
padding: 5px;
}
.is-leaf-node {
//&::before {
// display: none;
//}
}
.node-title-pane {
display: flex;
.node-title {
width: 100px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
&.is-editable {
.el-tree-node__content {
&:hover {
.node-suffix {
visibility: visible;
}
}
}
.node-suffix {
margin-right: 5px;
visibility: hidden;
i {
width: 20px;
height: 20px;
}
> * {
margin-left: 5px;
}
}
}
}
</style>

View File

@@ -1,117 +0,0 @@
import { request } from "/@/utils/service";
/**
* 获取角色所拥有的菜单
* @param params
*/
export function GetMenu(params:any) {
return request({
url: '/api/system/role_menu_button_permission/role_get_menu/',
method: 'get',
params:params
});
}
/***
* 新增权限
* @param data
* @constructor
*/
export function SaveMenuPermission(data:any) {
return request({
url: '/api/system/role_menu_permission/save_auth/',
method: 'post',
data:data
});
}
/**
* 获取菜单下的按钮
* @param params
* @constructor
*/
export function GetMenuButton(params:any) {
return request({
url: '/api/system/role_menu_button_permission/role_menu_get_button/',
method: 'get',
params:params
});
}
/***
* 根据角色获取已授权的菜单
* @param params
* @constructor
*/
export function role_to_menu (params:any={}) {
return request({
url: '/api/system/role_menu_button_permission/role_to_menu/',
method: 'get',
params: params
})
}
/***
* 根据角色获取数据权限范围
* @constructor
*/
export function GetDataScope (params:any={}) {
return request({
url: '/api/system/role_menu_button_permission/data_scope/',
method: 'get',
params: params
})
}
/***
* 获取权限部门
* @constructor
*/
export function GetDataScopeDept (params:any) {
return request({
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
method: 'get',
params: params
})
}
/***
* 新增权限
* @param data
* @constructor
*/
export function CreatePermission(data:any) {
return request({
url: '/api/system/role_menu_button_permission/',
method: 'post',
data:data
});
}
/***
* 根据菜单获取菜单下按钮
* @param params
*/
export function getObj(params:any) {
return request({
url: '/api/system/role_menu_button_permission/menu_to_button/',
method: 'get',
params:params
});
}
/**
* 删除按钮权限
* @param data
* @constructor
*/
export function DeletePermission(data:any) {
return request({
url: `/api/system/role_menu_button_permission/${data.id}/`,
method: 'delete',
data:{}
});
}

View File

@@ -1,350 +0,0 @@
<template>
<el-drawer size="70%" v-model="drawer" direction="rtl" destroy-on-close :before-close="handleClose">
<template #header>
<div>
<el-tag>当前角色:{{ editedRoleInfo.name }}</el-tag>
</div>
</template>
<div style="padding: 1em">
<div style="margin-bottom: 10px">
<el-button type="primary" @click="onSaveAuth">保存菜单授权</el-button>
</div>
<vxe-table
ref="tableRef"
border
resizable
:row-config="{ keyField: 'menu_id' }"
:tree-config="{ transform: true, rowField: 'menu_id', parentField: 'parent' }"
:checkbox-config="{ labelField: 'menu_id', checkRowKeys: multipleTableData, checkStrictly: true }"
:expand-config="{ accordion: true }"
@toggle-row-expand="menuNodeClick"
:data="menuData"
>
<vxe-column type="checkbox" title="ID" width="200" tree-node></vxe-column>
<vxe-column field="name" title="目录/菜单"></vxe-column>
<vxe-column type="expand" title="已授予权限" width="120">
<template #content="{ row, rowIndex }">
<div style="padding: 10px 0px" v-if="!row.is_catalog">
<el-button type="primary" size="small" style="margin-bottom: 0.5em" @click="createBtnPermission">新增 </el-button>
<el-table size="small" :data="buttonPermissionData" border style="width: 100%">
<el-table-column prop="menu_button" label="权限名称" width="100">
<template #default="scope">
<div>{{ scope.row.menu_button__name }}</div>
</template>
</el-table-column>
<el-table-column prop="menu_button__value" label="权限值" width="150"> </el-table-column>
<el-table-column prop="data_range" label="权限范围" width="140">
<template #default="scope">
<div>{{ formatDataRange(scope.row.data_range) }}</div>
</template>
</el-table-column>
<el-table-column prop="dept" label="权限涉及部门" />
<el-table-column fixed="right" label="操作" width="120">
<template #default="scope">
<el-button type="danger" size="small" @click="onDeleteBtn(scope)">删除 </el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
</vxe-column>
</vxe-table>
<!-- 弹窗-->
<el-dialog v-model="dialogFormVisible" append-to-body width="400px" title="配置按钮权限">
<el-form ref="buttonFormRef" :model="buttonForm" :rules="buttonRules" label-width="120px">
<el-form-item label="按钮" prop="menu_button">
<el-select v-model="buttonForm.menu_button" placeholder="请选择按钮" @change="onChangeButton">
<el-option v-for="(item, index) in buttonOptions" :key="index" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="权限范围" prop="data_range">
<el-select v-model="buttonForm.data_range" placeholder="请选择按钮">
<el-option v-for="(item, index) in dataScopeOptions" :key="index" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="数据部门" prop="dept" v-show="buttonForm.data_range === 4">
<div class="dept-tree">
<el-tree
:data="deptOptions"
show-checkbox
default-expand-all
:default-checked-keys="deptCheckedKeys"
ref="deptTree"
node-key="dept_id"
:check-strictly="true"
:props="{ label: 'name' }"
></el-tree>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="onSaveButtonForm"> 确定 </el-button>
</span>
</template>
</el-dialog>
</div>
</el-drawer>
</template>
<script lang="ts" setup>
import { ref, defineExpose, reactive, toRefs } from 'vue';
import { ElMessageBox, ElTable } from 'element-plus';
import * as api from './api.ts';
import type { FormRules, FormInstance } from 'element-plus';
import { ElMessage } from 'element-plus';
import XEUtils from 'xe-utils';
import { VXETable, VxeTableInstance, VxeTableEvents } from 'vxe-table';
interface tableRow {
menu_id: number;
name: string;
}
//抽屉是否显示
const drawer = ref(false);
//当前编辑的角色信息
const editedRoleInfo = ref({});
//抽屉关闭确认
const handleClose = (done: () => void) => {
ElMessageBox.confirm('您确定要关闭?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
done();
})
.catch(() => {
// catch error
});
};
/*****菜单的配置项***/
const defaultProps = {
children: 'children',
label: 'name',
isLeaf: 'hasChild',
};
interface Tree {
name: string;
children?: Tree[];
id: number;
}
let menuData = ref<Tree>();
//获取菜单
const getMenuData = () => {
api.GetMenu({}).then((res: any) => {
const { data } = res;
menuData.value = data;
});
};
//获取已授权的菜单
const tableRef = ref<VxeTableInstance<tableRow>>();
const multipleTableData = ref();
const getRoleToMenu = () => {
api.role_to_menu({ role: editedRoleInfo.value.id }).then((res: any) => {
const { data } = res;
multipleTableData.value = data;
});
};
let isBtnPermissionShow = ref(false);
let buttonOptions = ref<[]>();
let editedMenuInfo = ref();
//菜单节点点击事件
const menuNodeClick: VxeTableEvents.ToggleRowExpand<tableRow> = ({ expanded, row }) => {
// isBtnPermissionShow.value = !node.is_catalog
if (!row.is_catalog) {
buttonOptions.value = [];
editedMenuInfo.value = row;
api.GetMenuButton({ menu: row.menu_id }).then((res: any) => {
const { data } = res;
buttonOptions.value = data;
});
api.getObj({ menu: row.menu_id, role: editedRoleInfo.value.id }).then((res: any) => {
const { data } = res;
buttonPermissionData.value = data;
});
}
};
const menuTree = ref();
/*****菜单的配置项***/
/***按钮授权的弹窗****/
//是否显示新增表单
const dialogFormVisible = ref(false);
//部门树
const deptTree = ref();
//自定义部门数据
const deptOptions = ref();
//选中的部门数据
const deptCheckedKeys = [];
//按钮表单
const buttonForm = reactive({
menu_button: null,
role: null,
menu: null,
data_range: null,
dept: [],
});
//按钮表格数据
let buttonPermissionData = ref([]);
//按钮表单验证
const buttonRules = reactive<FormRules>({
menu_button: [{ required: true, message: '必填项' }],
data_range: [{ required: true, message: '必填项' }],
});
//新增按钮
const buttonFormRef = ref<FormInstance>();
const createBtnPermission = () => {
dialogFormVisible.value = true;
buttonForm.menu_button = null;
buttonForm.menu = null;
buttonForm.role = null;
buttonForm.data_range = null;
buttonForm.dept = [];
};
//权限范围数据
const dataScopeOptions = ref<[]>();
//按钮值变化事件
const onChangeButton = (val: any) => {
dataScopeOptions.value = [];
//获取权限值范围
api.GetDataScope({ menu_button: val }).then((res: any) => {
dataScopeOptions.value = res.data;
});
//获取权限部门值
api.GetDataScopeDept({ menu_button: val }).then((res: any) => {
deptOptions.value = XEUtils.toArrayTree(res.data, { parentKey: 'parent', strict: false });
});
};
//过滤按钮名称
const formatMenuBtn = (val: any) => {
let obj: any = buttonOptions.value?.find((item: any) => {
return item.id === val;
});
return obj ? obj.name : null;
};
//过滤权限范围
const formatDataRange = (val: any) => {
let obj: any = [
{
value: 0,
label: '仅本人数据权限',
},
{
value: 1,
label: '本部门及以下数据权限',
},
{
value: 2,
label: '本部门数据权限',
},
{
value: 3,
label: '全部数据权限',
},
{
value: 4,
label: '自定义数据权限',
},
].find((item: any) => {
return item.value === val;
});
return obj ? obj.label : null;
};
//保存按钮表单
const onSaveButtonForm = async () => {
const { id: roleId } = editedRoleInfo.value;
const { id: menuId } = editedMenuInfo.value;
const form: any = Object.assign({}, buttonForm);
form.role = roleId;
form.menu = menuId;
//选中的部门
const checkedList = deptTree.value.getCheckedKeys();
form.dept = checkedList;
if (!buttonFormRef.value) return;
await buttonFormRef.value.validate((valid, fields) => {
if (valid) {
api.CreatePermission(form).then((res: any) => {
const { data } = res;
buttonPermissionData.value.push(data);
dialogFormVisible.value = false;
ElMessage({
type: 'success',
message: res.msg,
});
});
} else {
ElMessage({
type: 'error',
title: '提交错误',
message: 'F12控制台看详情',
});
console.log('提交错误', fields);
}
});
};
//删除按钮权限
const onDeleteBtn = (scope: any) => {
const { row, $index } = scope;
ElMessageBox.confirm('您是否要删除数据?', '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
api.DeletePermission({ id: row.id }).then((res: any) => {
buttonPermissionData.value.splice($index, 1);
ElMessage({
type: 'success',
message: res.msg,
});
});
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
});
});
};
/***按钮授权的弹窗****/
//初始化数据
const initGet = () => {
getMenuData();
getRoleToMenu();
};
/**
* 保存授权
*/
const onSaveAuth = () => {
const $table = tableRef.value;
if ($table) {
const selectRecords = $table.getCheckboxRecords();
const menuIdList = selectRecords.map((record: any) => record.menu_id);
const { id: roleId } = editedRoleInfo.value;
const data = {
role: roleId,
menu: menuIdList,
};
api.SaveMenuPermission(data).then((res: any) => {
ElMessage({
message: res.msg,
type: 'success',
});
});
}
};
defineExpose({ drawer, editedRoleInfo, initGet });
</script>
<style scoped></style>

View File

@@ -1,21 +0,0 @@
import { request } from "/@/utils/service";
export function getDataPermissionRange() {
return request({
url: '/api/system/role_menu_button_permission/data_scope/',
method: 'get',
})
}
export function getDataPermissionDept() {
return request({
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
method: 'get'
})
}
export function getDataPermissionMenu() {
return request({
url: '/api/system/role_menu_button_permission/get_role_permissions/',
method: 'get'
})
}

View File

@@ -1,235 +0,0 @@
<template>
<div class="permission-com">
<div class="pc-item">
<p class="pc-title">数据授权</p>
<div class="pc-cell">
<el-radio-group v-model="dataPermission" class="pc-data-permission">
<el-radio v-for="item in dataPermissionRange" :key="item.label" :label="item.value" @change="handleChange">{{ item.label }}</el-radio>
</el-radio-group>
<el-tree-select
v-if="dataPermission === 4"
node-key="id"
v-model="customDataPermission"
:props="defaultTreeProps"
:data="deptData"
multiple
check-strictly
:render-after-expand="false"
show-checkbox
class="pc-custom-dept"
/>
</div>
</div>
<div class="pc-item pc-menu">
<p class="pc-title">菜单授权</p>
<div>
<el-tree
:props="defaultTreeProps"
:data="menuData"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
class="dc-menu-tree"
>
<template #default="{ node, data }">
<div class="pc-tree-node" :class="{ 'tree-node-label-border': !data.is_catalog }">
<p class="tree-node-label">{{ node.label }}</p>
<div v-if="!data.is_catalog">
<ul class="menu-permission-list">
<li v-for="m in data.menuPermission" :key="m.id" class="menu-permission-item">
<el-checkbox v-model="m.id" :label="m.name" />
</li>
</ul>
<ul class="menu-permission-list">
<li v-for="m in data.columns" :key="m.id" class="menu-permission-item">
<el-checkbox v-model="m.id" :label="m.title" />
</li>
</ul>
</div>
</div>
</template>
</el-tree>
</div>
</div>
<div class="pc-btn">
<el-button type="primary">确定</el-button>
<el-button>取消</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import XEUtils from 'xe-utils';
import { getDataPermissionRange, getDataPermissionDept, getDataPermissionMenu } from './api';
import { DataPermissionRangeType, CustomDataPermissionDeptType, CustomDataPermissionMenuType } from './types';
const defaultTreeProps = {
children: 'children',
label: 'name',
value: 'id',
};
const data: any[] = [
{
id: 1,
label: 'Level one 1',
children: [
{
id: 4,
label: 'Level two 1-1',
isPenultimate: true,
children: [
{
id: 9,
label: 'Level three 1-1-1',
},
{
id: 10,
label: 'Level three 1-1-2',
},
],
},
],
},
{
id: 2,
label: 'Level one 2',
isPenultimate: true,
children: [
{
id: 5,
label: 'Level two 2-1',
},
{
id: 6,
label: 'Level two 2-2',
},
],
},
{
id: 3,
label: 'Level one 3',
isPenultimate: true,
children: [
{
id: 7,
label: 'Level two 3-1',
},
{
id: 8,
label: 'Level two 3-2',
},
],
},
];
let dataPermission = ref();
let dataPermissionRange = ref<DataPermissionRangeType[]>([]);
let customDataPermission = ref();
let deptData = ref<CustomDataPermissionDeptType[]>([]);
let menuData = ref<CustomDataPermissionMenuType[]>([]);
const fetchData = async () => {
try {
const resRange = await getDataPermissionRange();
const resMenu = await getDataPermissionMenu();
if (resRange?.code === 2000) {
dataPermissionRange.value = resRange.data;
}
if (resMenu?.code === 2000) {
console.log(resMenu.data);
menuData.value = resMenu.data;
}
} catch {
return;
}
};
const handleChange = async () => {
if (dataPermission.value === 4) {
const res = await getDataPermissionDept();
const data = XEUtils.toArrayTree(res.data, { parentKey: 'parent', strict: false });
deptData.value = data;
}
};
const handleTestClick = (node: any, data: any) => {
console.log(node, data);
};
onMounted(() => {
fetchData();
});
</script>
<style lang="scss" scoped>
.permission-com {
width: 100%;
height: 100%;
padding: 15px;
box-sizing: border-box;
.pc-item {
width: 100%;
margin-bottom: 15px;
border-bottom: 1px #dcdfe6 solid;
}
.pc-title {
font-weight: 600;
}
.pc-cell {
display: flex;
padding: 10px;
overflow-x: auto;
.pc-data-permission {
min-width: 800px;
}
.pc-custom-dept {
min-width: 200px;
}
}
.pc-menu {
height: calc(100% - 140px);
overflow-y: auto;
}
.pc-tree-node {
width: 100%;
display: flex;
align-items: center;
.tree-node-label {
font-size: 16px;
margin-right: 20px;
}
.menu-permission-list {
display: flex;
align-items: center;
.menu-permission-item {
margin-right: 10px;
}
}
}
.tree-node-label-border {
border-bottom: 1px #dcdfe6 solid;
}
.pc-btn {
padding-bottom: 15px;
}
}
</style>
<style lang="scss">
.dc-menu-tree {
.el-tree-node__content {
height: auto;
}
}
</style>

View File

@@ -1,20 +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[]
}

View File

@@ -1,5 +1,7 @@
<template>
<el-drawer v-model="drawerVisible" title="权限配置" direction="rtl" size="60%" :close-on-click-modal="false"
<el-drawer v-model="drawerVisible" title="权限配置" direction="rtl" size="60%"
:close-on-click-modal="false"
destroy-on-close
:before-close="handleDrawerClose">
<template #header>
<el-row>
@@ -8,111 +10,32 @@
<el-tag>{{ props.roleName }}</el-tag>
</div>
</el-col>
<el-col :span="6" :offset="8">
<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-collapse v-model="collapseCurrent" @change="handleCollapseChange" accordion>
<el-collapse-item v-for="(item,mIndex) in menuData" :key="mIndex" :name="mIndex">
<template #title>
<div @click.stop="null">
<p class="pc-collapse-title">
<el-checkbox v-model="item.isCheck">
<span>{{ item.name }}</span>
</el-checkbox>
</p>
<div v-show="!collapseCurrent.includes(mIndex)">
<el-checkbox v-for="btn in item.btns" :key="btn.value" :label="btn.value" v-model="btn.isCheck">
{{ btn.name }}
</el-checkbox>
</div>
</div>
</template>
<div class="pc-collapse-main">
<div class="pccm-item">
<p>允许对这些数据有以下操作</p>
<el-checkbox v-for="(btn,bIndex) in item.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(item, btn.id)">
<el-icon><Setting/></el-icon>
</span>
</div>
</el-checkbox>
</div>
<div class="pccm-item">
<p>对这些数据有以下字段权限</p>
<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, item, head.value)">
<span>{{head.label}}</span>
</el-checkbox>
</div>
</li>
<li v-for="(c_item, c_index) in item.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"></el-checkbox>
</div>
</li>
</ul>
</div>
</div>
</el-collapse-item>
</el-collapse>
<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>
<div>
<el-tabs type="border-card" v-model="permissionTab">
<el-tab-pane label="菜单/按钮授权" name="menu">
<MenuPermission ref="menuPermissionRef" :role-id="props.roleId" @handleDrawerClose="handleDrawerClose"></MenuPermission>
</el-tab-pane>
<el-tab-pane label="请求接口授权" name="api">
<ApiPermission v-if="permissionTab==='api'" :role-id="props.roleId"></ApiPermission>
</el-tab-pane>
<el-tab-pane label="表单字段授权" name="field">
<FieldPermission v-if="permissionTab==='field'" :role-id="props.roleId"></FieldPermission>
</el-tab-pane>
</el-tabs>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import {ref, onMounted, defineProps, watch, computed, reactive} from 'vue';
import {ref, onMounted, defineProps, watch, computed, reactive,nextTick} from 'vue';
import XEUtils from 'xe-utils';
import {errorNotification} from '/@/utils/message';
import {getDataPermissionRange, getDataPermissionDept, getRolePremission, setRolePremission,setBtnDatarange} from './api';
import {MenuDataType, DataPermissionRangeType, CustomDataPermissionDeptType} from './types';
import {ElMessage} from 'element-plus'
import MenuPermission from "./MenuPermission/index.vue";
import ApiPermission from "./ApiPermission/index.vue";
import FieldPermission from "./FieldPermission/index.vue";
const props = defineProps({
roleId: {
type: Number,
@@ -128,28 +51,27 @@ const props = defineProps({
}
})
const emit = defineEmits(['update:drawerVisible'])
const menuPermissionRef = ref()
const permissionTab = ref('menu')
const drawerVisible = ref(false)
watch(
() => props.drawerVisible,
(val) => {
drawerVisible.value = val;
getMenuBtnPermission()
fetchData()
nextTick(()=>{
menuPermissionRef.value.getMenuPremissionTreeData()
})
// fetchData()
}
);
const handleDrawerClose = () => {
permissionTab.value ='menu'
emit('update:drawerVisible', false);
}
const defaultTreeProps = {
children: 'children',
label: 'name',
value: 'id',
};
let menuData = ref<MenuDataType[]>([]);
let collapseCurrent = ref(['1']);
let menuCurrent = ref<Partial<MenuDataType>>({});
let menuBtnCurrent = ref<number>(-1);
@@ -164,10 +86,52 @@ const formatDataRange = computed(() => {
let deptData = ref<CustomDataPermissionDeptType[]>([]);
let dataPermission = ref();
let customDataPermission = ref([]);
//,,
const getMenuBtnPermission = async () => {
const resMenu = await getRolePremission({role: props.roleId})
menuData.value = resMenu.data
///
const permissionTreeRef = ref();
let menuPermissionTreeData = ref<MenuDataType[]>([]);
const getMenuPremissionTreeData = async () => {
const resMenu = await getMenuPremissionTree({role: props.roleId})
menuPermissionTreeData.value = resMenu
nextTick(() => {
updateChecked(props.roleId);
});
}
// tree
// tree
function getAllCheckedLeafNodeId(tree, checkedIds, temp) {
for (let i = 0; i < tree.length; i++) {
const item = tree[i];
if (item.children && item.children.length !== 0) {
getAllCheckedLeafNodeId(item.children, checkedIds, temp);
} else {
if (checkedIds.indexOf(item.id) !== -1) {
temp.push(item.id);
}
}
}
return temp;
}
async function updateChecked(roleId:string|number) {
let checkedIds = await getMenuPremissionChecked({role: roleId});
//
checkedIds = getAllCheckedLeafNodeId(menuPermissionTreeData.value, checkedIds, []);
permissionTreeRef.value.setCheckedKeys(checkedIds);
}
/**
* 更新菜单权限
*/
async function updatePermission() {
const roleId = props.roleId;
const { checked, halfChecked } = permissionTreeRef.value.getChecked();
const allChecked = [...checked, ...halfChecked];
const menuIds = allChecked.filter(item=>item !== -1)
await saveMenuPremission({role: roleId, menu: menuIds})
handleDrawerClose();
//await updateChecked(roleId);
ElMessage.success("授权成功");
}
const fetchData = async () => {

View File

@@ -56,35 +56,15 @@ export const createCrudOptions = function ({
show: true,
},
edit: {
show: hasPermissions('role:Update'),
show: hasPermissions('role:update'),
},
remove: {
show: hasPermissions('role:Delete'),
show: hasPermissions('role:delete'),
},
/* custom: {
type: 'primary',
text: '权限配置',
show: hasPermissions('role:Update'),
tooltip: {
placement: 'top',
content: '权限配置',
},
click: (context: any): void => {
const { row } = context;
// eslint-disable-next-line no-mixed-spaces-and-tabs
rolePermission.value.drawer = true;
rolePermission.value.editedRoleInfo = row;
rolePermission.value.initGet();
},
}, */
customNew: {
type: 'primary',
text: '权限配置',
show: hasPermissions('role:Update'),
tooltip: {
placement: 'top',
content: '权限配置',
},
text: '权',
// show: hasPermissions('role:Update'),
click: (context: any): void => {
const { row } = context;
handleDrawerOpen(row);
@@ -183,44 +163,13 @@ export const createCrudOptions = function ({
value: 1,
},
},
admin: {
title: '是否管理员',
search: { show: false },
type: 'dict-radio',
dict: dict({
data: [
{
label: '是',
value: true,
color: 'success',
},
{
label: '否',
value: false,
color: 'danger',
},
],
}),
column: {
minWidth: 130,
sortable: 'custom',
show: columnPermission('admin', 'is_query'),
},
addForm: {
show: columnPermission('admin', 'is_create'),
},
editForm: {
show: columnPermission('admin', 'is_update'),
},
form: {
rules: [{ required: true, message: '是否管理员必填' }],
value: false,
},
},
status: {
title: '状态',
search: { show: true },
type: 'dict-radio',
dict: dict({
data: dictionary('button_status_bool'),
}),
column: {
width: 100,
component: {
@@ -243,10 +192,7 @@ export const createCrudOptions = function ({
},
editForm: {
show: columnPermission('status', 'is_update'),
},
dict: dict({
data: dictionary('button_status_bool'),
}),
}
},
update_datetime: {
title: '更新时间',

View File

@@ -6,8 +6,6 @@
</template>
</fs-crud>
<permission ref="rolePermission"></permission>
<PermissionComNew v-model:drawerVisible="drawerVisible" :roleId="roleId" :roleName="roleName" @drawerClose="handleDrawerClose" />
</fs-page>
</template>
@@ -19,7 +17,7 @@ import { GetPermission } from './api';
import { useExpose, useCrud } from '@fast-crud/fast-crud';
import { createCrudOptions } from './crud';
import permission from './components/PermissionCom/index.vue';
import PermissionComNew from './components/PermissionComNew/index.vue';
import PermissionComNew from './components/index.vue';
let drawerVisible = ref(false);
let roleId = ref(null);
@@ -67,5 +65,4 @@ onMounted(async () => {
crudExpose.doRefresh();
});
defineExpose(rolePermission);
</script>

View File

@@ -1,107 +0,0 @@
import { request } from "/@/utils/service";
/**
* 获取角色所拥有的菜单
* @param params
*/
export function GetMenu(params:any) {
return request({
url: '/api/system/role_menu_button_permission/role_get_menu/',
method: 'get',
params:params
});
}
/***
* 新增权限
* @param data
* @constructor
*/
export function SaveMenuPermission(data:any) {
return request({
url: '/api/system/role_menu_permission/save_auth/',
method: 'post',
data:data
});
}
/**
* 获取菜单下的按钮
* @param params
* @constructor
*/
export function GetMenuButton(params:any) {
return request({
url: '/api/system/role_menu_button_permission/role_menu_get_button/',
method: 'get',
params:params
});
}
/***
* 根据角色获取数据权限范围
* @constructor
*/
export function GetDataScope (params:any={}) {
return request({
url: '/api/system/role_menu_button_permission/data_scope/',
method: 'get',
params: params
})
}
/***
* 获取权限部门
* @constructor
*/
export function GetDataScopeDept (params:any) {
return request({
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
method: 'get',
params: params
})
}
/***
* 新增权限
* @param data
* @constructor
*/
export function CreatePermission(data:any) {
return request({
url: '/api/system/role_menu_button_permission/',
method: 'post',
data:data
});
}
/***
* 根据菜单获取菜单下按钮
* @param params
*/
export function getObj(params:any) {
return request({
url: '/api/system/role_menu_button_permission/menu_to_button/',
method: 'get',
params:params
});
}
/**
* 删除按钮权限
* @param data
* @constructor
*/
export function DeletePermission(data:any) {
return request({
url: `/api/system/role_menu_button_permission/${data.id}/`,
method: 'delete',
data:{}
});
}

View File

@@ -1,432 +0,0 @@
<template>
<el-drawer
size="70%"
v-model="drawer"
direction="rtl"
destroy-on-close
:before-close="handleClose"
>
<template #header>
<div>
<el-tag size="large" type="primary">当前角色:{{ editedRoleInfo.name }}</el-tag>
</div>
</template>
<div style="padding: 1em">
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="8" :lg="6" :xl="6">
<el-card header="菜单页面授权">
<template #header>
<div class="card-header">
<el-tooltip effect="dark" content="点击菜单项,可对菜单下的按钮/接口授权"
placement="right">
<div>
<span>菜单页面</span>
<el-icon>
<QuestionFilled/>
</el-icon>
</div>
</el-tooltip>
<el-button size="mini" type="primary" @click="onSaveAuth">保存菜单授权</el-button>
</div>
</template>
<el-tree :data="menuData"
ref="menuTree"
show-checkbox
node-key="id"
highlight-current
:expand-on-click-node="false"
:check-on-click-node="true"
:props="defaultProps"
@node-click="menuNodeClick"
/>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="16" :lg="18" :xl="18">
<!-- <el-alert title="对页面菜单下按钮授权" description="新增或删除对菜单下的按钮/接口授权" type="warning" />-->
<el-card v-if="isBtnPermissionShow">
<template #header>
<div class="card-header">
<el-tooltip effect="dark" content="新增或删除对菜单下的按钮/接口授权" placement="right">
<div>
<span>按钮/接口授权</span>
<el-icon>
<QuestionFilled/>
</el-icon>
</div>
</el-tooltip>
</div>
</template>
<div>
<el-divider content-position="left">{{ editedMenuInfo.name }}</el-divider>
<el-button type="primary" size="small" style="margin-bottom: 0.5em"
@click="createBtnPermission">新增
</el-button>
<el-table size="small" :data="buttonPermissionData" border style="width: 100%">
<el-table-column prop="menu_button" label="权限名称" width="100">
<template #default="scope">
<div>{{ formatMenuBtn(scope.row.menu_button) }}</div>
</template>
</el-table-column>
<el-table-column prop="data_range" label="权限范围" width="140">
<template #default="scope">
<div>{{ formatDataRange(scope.row.data_range) }}</div>
</template>
</el-table-column>
<el-table-column prop="dept" label="权限涉及部门"/>
<el-table-column fixed="right" label="操作" width="120">
<template #default="scope">
<el-button type="danger" size="small" @click="onDeleteBtn(scope)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- <el-divider content-position="left">字段授权</el-divider>-->
<!-- <el-table size="small" :data="crudPermissionData" border style="width: 100%">-->
<!-- <el-table-column prop="field" label="字段"></el-table-column>-->
<!-- <el-table-column prop="table" label="列表显示">-->
<!-- <template #default="scope">-->
<!-- <div>-->
<!-- <el-switch size="mini" v-model="scope.row.table"/>-->
<!-- </div>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column prop="view" label="表单查看">-->
<!-- <template #default="scope">-->
<!-- <div>-->
<!-- <el-switch size="mini" v-model="scope.row.view"/>-->
<!-- </div>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column prop="edit" label="表单编辑">-->
<!-- <template #default="scope">-->
<!-- <div>-->
<!-- <el-switch size="mini" v-model="scope.row.edit"/>-->
<!-- </div>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
</el-card>
</el-col>
</el-row>
<el-dialog v-model="dialogFormVisible" width="400px" title="配置按钮权限">
<el-form ref="buttonFormRef" :model="buttonForm" :rules="buttonRules" label-width="120px">
<el-form-item label="按钮" prop="menu_button">
<el-select v-model="buttonForm.menu_button" placeholder="请选择按钮" @change="onChangeButton">
<el-option v-for="(item,index) in buttonOptions" :key="index" :label="item.name"
:value="item.id"/>
</el-select>
</el-form-item>
<el-form-item label="权限范围" prop="data_range">
<el-select v-model="buttonForm.data_range" placeholder="请选择按钮">
<el-option v-for="(item,index) in dataScopeOptions" :key="index" :label="item.label"
:value="item.value"/>
</el-select>
</el-form-item>
<el-form-item label="数据部门" prop="dept" v-show="buttonForm.data_range === 4">
<div class="dept-tree">
<el-tree
:data="deptOptions"
show-checkbox
default-expand-all
:default-checked-keys="deptCheckedKeys"
ref="deptTree"
node-key="id"
:check-strictly="true"
:props="{ label: 'name' }"
></el-tree>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="onSaveButtonForm">
确定
</el-button>
</span>
</template>
</el-dialog>
</div>
</el-drawer>
</template>
<script lang="ts" setup name="rolePermission">
import {ref, defineExpose, reactive, toRefs} from 'vue'
import {ElMessageBox} from 'element-plus'
import * as api from './api'
import type {FormRules, FormInstance} from 'element-plus'
import {ElMessage} from 'element-plus'
import XEUtils from 'xe-utils'
//抽屉是否显示
const drawer = ref(false)
//当前编辑的角色信息
const editedRoleInfo = ref({})
//抽屉关闭确认
const handleClose = (done: () => void) => {
ElMessageBox.confirm('您确定要关闭?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
done()
})
.catch(() => {
// catch error
})
}
/*****菜单的配置项***/
const defaultProps = {
children: 'children',
label: 'name',
isLeaf: 'hasChild'
}
interface Tree {
name: string
children?: Tree[],
id: number
}
let menuData = ref<Tree>()
//获取菜单
const getMenuData = () => {
api.GetMenu({}).then((res: any) => {
const {data} = res
const list = XEUtils.toArrayTree(data, {parentKey: "parent", key:'menu_id',strict: true})
menuData.value = list
})
}
let isBtnPermissionShow = ref(false)
let buttonOptions = ref<[]>()
let editedMenuInfo = ref()
//菜单节点点击事件
const menuNodeClick = (node: any, obj: any) => {
isBtnPermissionShow.value = !node.is_catalog
if (!node.is_catalog) {
buttonOptions.value = []
editedMenuInfo.value = node
api.GetMenuButton({menu: node.menu_id}).then((res: any) => {
const {data} = res
buttonOptions.value = data
})
api.getObj({menu: node.menu_id, role: editedRoleInfo.value.id}).then((res: any) => {
const {data} = res
buttonPermissionData.value = data
})
}
}
const menuTree = ref()
/*****菜单的配置项***/
/***按钮授权的弹窗****/
//是否显示新增表单
const dialogFormVisible = ref(false)
//部门树
const deptTree = ref()
//自定义部门数据
const deptOptions = ref()
//选中的部门数据
const deptCheckedKeys = []
//按钮表单
const buttonForm = reactive({
menu_button: null,
role: null,
menu: null,
data_range: null,
dept: []
})
//按钮表格数据
let buttonPermissionData = ref([])
//按钮表单验证
const buttonRules = reactive<FormRules>({
menu_button: [
{required: true, message: '必填项'}
],
data_range: [
{required: true, message: '必填项'}
]
})
//新增按钮
const buttonFormRef = ref<FormInstance>()
const createBtnPermission = () => {
dialogFormVisible.value = true
buttonForm.menu_button = null
buttonForm.menu = null
buttonForm.role = null
buttonForm.data_range = null
buttonForm.dept = []
}
//权限范围数据
const dataScopeOptions = ref<[]>()
//按钮值变化事件
const onChangeButton = (val: any) => {
dataScopeOptions.value = []
//获取权限值范围
api.GetDataScope({menu_button: val}).then((res: any) => {
dataScopeOptions.value = res.data
})
//获取权限部门值
api.GetDataScopeDept({menu_button: val}).then((res: any) => {
deptOptions.value = XEUtils.toArrayTree(res.data, {parentKey: 'parent', strict: false})
})
}
//过滤按钮名称
const formatMenuBtn = (val: any) => {
let obj: any = buttonOptions.value?.find((item: any) => {
return item.id === val
})
return obj ? obj.name : null
}
//过滤权限范围
const formatDataRange = (val: any) => {
let obj: any = [
{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 1,
"label": '本部门及以下数据权限'
},
{
"value": 2,
"label": '本部门数据权限'
},
{
"value": 3,
"label": '全部数据权限'
},
{
"value": 4,
"label": '自定义数据权限'
}
].find((item: any) => {
return item.value === val
})
return obj ? obj.label : null
}
//保存按钮表单
const onSaveButtonForm = async () => {
const {id: roleId} = editedRoleInfo.value
const {id: menuId} = editedMenuInfo.value
const form: any = Object.assign({}, buttonForm)
form.role = roleId
form.menu = menuId
//选中的部门
const checkedList = deptTree.value.getCheckedKeys()
form.dept = checkedList
if (!buttonFormRef.value) return
await buttonFormRef.value.validate((valid, fields) => {
if (valid) {
api.CreatePermission(form).then((res: any) => {
const {data} = res
buttonPermissionData.value.push(data)
dialogFormVisible.value = false
ElMessage({
type: 'success',
message: res.msg,
})
})
} else {
ElMessage({
type: 'error',
title: '提交错误',
message: 'F12控制台看详情',
})
console.log('提交错误', fields)
}
})
}
//删除按钮权限
const onDeleteBtn = (scope: any) => {
const {row, $index} = scope
ElMessageBox.confirm(
'您是否要删除数据?',
'温馨提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
api.DeletePermission({id: row.id}).then((res: any) => {
buttonPermissionData.value.splice($index, 1)
ElMessage({
type: 'success',
message: res.msg,
})
})
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
})
})
}
/***按钮授权的弹窗****/
//初始化数据
const initGet = () => {
getMenuData()
}
/**
* 保存授权
*/
const onSaveAuth = () => {
//选中的菜单
const checkedList = menuTree.value.getCheckedKeys()
//半选中的菜单
const halfCheckedList = menuTree.value.getHalfCheckedKeys()
//合并的菜单数据
const menuIdList = [...checkedList, ...halfCheckedList]
// console.log(menuIdList)
const {id: roleId} = editedRoleInfo.value
const data = {
role: roleId,
menu: menuIdList
}
api.SaveMenuPermission(data).then((res: any) => {
ElMessage({
message: res.msg,
type: 'success',
})
})
}
defineExpose({drawer, editedRoleInfo, initGet})
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.dept-tree::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
.dept-tree {
height: 160px;
overflow-y: scroll;
scrollbar-width: none; /* firefox */
-ms-overflow-style: none; /* IE 10+ */
border: 1px solid #e1e1e1;
width: 16em;
border-radius: 2px;
}
</style>