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" "BACKEND": "channels.layers.InMemoryChannelLayer"
} }
} }
REDIS_URL = locals().get('REDIS_URL', "")
# CHANNEL_LAYERS = { # CHANNEL_LAYERS = {
# 'default': { # 'default': {
# 'BACKEND': 'channels_redis.core.RedisChannelLayer', # 'BACKEND': 'channels_redis.core.RedisChannelLayer',
@@ -399,10 +400,11 @@ TENANT_SHARED_APPS = []
PLUGINS_URL_PATTERNS = [] PLUGINS_URL_PATTERNS = []
# ********** 一键导入插件配置开始 ********** # ********** 一键导入插件配置开始 **********
# 例如: # 例如:
# from dvadmin_upgrade_center.settings import * # 升级中心 #from dvadmin3_upgrade_center.settings import * # 升级中心
# from dvadmin_celery.settings import * # celery 异步任务 # from dvadmin_celery.settings import * # celery 异步任务
# from dvadmin_third.settings import * # 第三方用户管理 # from dvadmin_third.settings import * # 第三方用户管理
# from dvadmin_ak_sk.settings import * # 秘钥管理管理 # from dvadmin_ak_sk.settings import * # 秘钥管理管理
# from dvadmin_tenants.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 ( from dvadmin.system.models import (
Role, Dept, Users, Menu, MenuButton, Role, Dept, Users, Menu, MenuButton,
ApiWhiteList, Dictionary, SystemConfig, ApiWhiteList, Dictionary, SystemConfig,
RoleMenuPermission, RoleMenuButtonPermission RoleMenuPermission, RoleApiPermission, Columns
) )
from dvadmin.utils.serializers import CustomModelSerializer 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): class MenuInitSerializer(CustomModelSerializer):
""" """
递归深度获取数信息(用于生成初始化json文件) 递归深度获取数信息(用于生成初始化json文件)
""" """
name = serializers.CharField(required=False) name = serializers.CharField(required=True)
menu_type = serializers.IntegerField(required=True)
children = serializers.SerializerMethodField() children = serializers.SerializerMethodField()
menu_button = serializers.SerializerMethodField()
def get_children(self, obj: Menu): def get_children(self, obj: Menu):
data = [] data = []
@@ -69,26 +58,18 @@ class MenuInitSerializer(CustomModelSerializer):
data = serializer.data data = serializer.data
return 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): def save(self, **kwargs):
instance = super().save(**kwargs) instance = super().save(**kwargs)
children = self.initial_data.get('children') children = self.initial_data.get('children')
menu_button = self.initial_data.get('menu_button')
# 菜单表 # 菜单表
if children: if children:
for menu_data in children: for menu_data in children:
menu_data['parent'] = instance.id menu_data['parent'] = instance.id
filter_data = { filter_data = {
"name": menu_data['name'], "name": menu_data['name'],
"web_path": menu_data['web_path'],
"component": menu_data['component'], "component": menu_data['component'],
"component_name": menu_data['component_name'], "menu_type": menu_data['menu_type'],
} }
instance_obj = Menu.objects.filter(**filter_data).first() instance_obj = Menu.objects.filter(**filter_data).first()
if instance_obj and not self.initial_data.get('reset'): 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 = MenuInitSerializer(instance_obj, data=menu_data, request=self.request)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
serializer.save() 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 return instance
class Meta: class Meta:
model = Menu model = Menu
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status', fields = ['name', 'icon', 'sort', 'is_link', 'menu_type', 'web_path', 'component', 'component_name', 'status',
'cache', 'visible', 'parent', 'children', 'menu_button', 'creator', 'dept_belong_id'] 'cache', 'visible', 'parent', 'children', 'creator', 'dept_belong_id']
extra_kwargs = { extra_kwargs = {
'creator': {'write_only': True}, 'creator': {'write_only': True},
'dept_belong_id': {'write_only': True} 'dept_belong_id': {'write_only': True}
@@ -128,7 +97,7 @@ class RoleInitSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Role model = Role
fields = ['name', 'key', 'sort', 'status', 'admin', fields = ['name', 'key', 'sort', 'status',
'creator', 'dept_belong_id'] 'creator', 'dept_belong_id']
read_only_fields = ["id"] read_only_fields = ["id"]
extra_kwargs = { 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) 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) data_range = serializers.CharField(max_length=100, required=False)
def create(self, validated_data): def create(self, validated_data):
init_data = self.initial_data init_data = self.initial_data
validated_data.pop('menu_button_value')
validated_data.pop('role_key') validated_data.pop('role_key')
role_id = Role.objects.filter(key=init_data['role_key']).first() 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['role'] = role_id
validated_data['menu_button'] = menu_button_id
instance = super().create(validated_data) instance = super().create(validated_data)
instance.dept.set([]) instance.dept.set([])
return instance return instance
class Meta: class Meta:
model = RoleMenuButtonPermission model = RoleApiPermission
fields = ['role_key', 'menu_button_value', 'data_range', 'dept', 'creator', 'dept_belong_id'] fields = ['role_key', 'name','api','method', 'data_range', 'dept', 'creator', 'dept_belong_id']
read_only_fields = ["id"] read_only_fields = ["id"]
extra_kwargs = { extra_kwargs = {
'role': {'required': False}, 'role': {'required': False},
'menu': {'required': False},
'creator': {'write_only': True}, 'creator': {'write_only': True},
'dept_belong_id': {'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): 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", "key": "admin",
"sort": 1, "sort": 1,
"status": true, "status": true,
"admin": true,
"remark": null "remark": null
}, },
{ {
@@ -12,7 +11,6 @@
"key": "public", "key": "public",
"sort": 2, "sort": 2,
"status": true, "status": true,
"admin": true,
"remark": null "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 ( from dvadmin.system.fixtures.initSerializer import (
UsersInitSerializer, DeptInitSerializer, RoleInitSerializer, UsersInitSerializer, DeptInitSerializer, RoleInitSerializer,
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer, 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): def init_role_menu(self):
""" """
@@ -47,11 +47,17 @@ class Initialize(CoreInitialize):
""" """
self.init_base(RoleMenuInitSerializer, unique_fields=['role', 'menu']) 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): def init_api_white_list(self):
""" """
@@ -77,7 +83,8 @@ class Initialize(CoreInitialize):
self.init_users() self.init_users()
self.init_menu() self.init_menu()
self.init_role_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_api_white_list()
self.init_dictionary() self.init_dictionary()
self.init_system_config() 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="权限字符") key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序") sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
status = models.BooleanField(default=True, 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: class Meta:
db_table = table_prefix + "system_role" db_table = table_prefix + "system_role"
@@ -156,22 +155,24 @@ class Menu(CoreModel):
help_text="上级菜单", help_text="上级菜单",
) )
icon = models.CharField(max_length=64, verbose_name="菜单图标", null=True, blank=True, 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="显示排序") 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_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录") MENU_TYPE_CHOICES =(
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址") (0, "目录"),
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址") (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, component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True,
help_text="组件名称") help_text="组件名称")
status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态") status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态")
cache = models.BooleanField(default=False, 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="侧边栏中是否显示", visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示",
help_text="侧边栏中是否显示") help_text="侧边栏中是否显示")
frame_out = models.BooleanField(default=False, blank=True, verbose_name="是否主框架外", help_text="是否主框架外")
class Meta: class Meta:
db_table = table_prefix + "system_menu" 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) role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
app = models.CharField(max_length=64, verbose_name='应用名') app = models.CharField(max_length=64, verbose_name='应用名')
model = 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='模型表字段名') field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
title = models.CharField(max_length=64, verbose_name='字段显示名') title = models.CharField(max_length=64, verbose_name='字段显示名')
is_query = models.BooleanField(default=1, verbose_name='是否可查询') is_query = models.BooleanField(default=1, verbose_name='是否可查询')
@@ -251,25 +251,25 @@ class RoleMenuPermission(CoreModel):
# ordering = ("-create_datetime",) # ordering = ("-create_datetime",)
class RoleMenuButtonPermission(CoreModel): class RoleApiPermission(CoreModel):
role = models.ForeignKey( role = models.ForeignKey(
to="Role", to="Role",
db_constraint=False, db_constraint=False,
related_name="role_menu_button", related_name="role_api",
on_delete=models.CASCADE, on_delete=models.CASCADE,
verbose_name="关联角色", verbose_name="关联角色",
help_text="关联角色", help_text="关联角色",
) )
menu_button = models.ForeignKey( name = models.CharField(max_length=64, verbose_name="名称", help_text="名称")
to="MenuButton", api = models.CharField(max_length=200, verbose_name="接口地址", help_text="接口地址")
db_constraint=False, METHOD_CHOICES = (
related_name="menu_button_permission", (0, "GET"),
on_delete=models.CASCADE, (1, "POST"),
verbose_name="关联菜单按钮", (2, "PUT"),
help_text="关联菜单按钮", (3, "DELETE"),
null=True,
blank=True
) )
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
help_text="接口请求方法")
DATASCOPE_CHOICES = ( DATASCOPE_CHOICES = (
(0, "仅本人数据权限"), (0, "仅本人数据权限"),
(1, "本部门及以下数据权限"), (1, "本部门及以下数据权限"),
@@ -283,8 +283,8 @@ class RoleMenuButtonPermission(CoreModel):
help_text="数据权限-关联部门") help_text="数据权限-关联部门")
class Meta: class Meta:
db_table = table_prefix + "role_menu_button_permission" db_table = table_prefix + "role_api_permission"
verbose_name = "角色按钮权限表" verbose_name = "角色接口权限表"
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
ordering = ("-create_datetime",) ordering = ("-create_datetime",)
@@ -457,7 +457,7 @@ class SystemConfig(CoreModel):
help_text="父级", help_text="父级",
) )
title = models.CharField(max_length=50, verbose_name="标题", 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) 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) sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True)
status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态") 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.file_list import FileViewSet
from dvadmin.system.views.login_log import LoginLogViewSet from dvadmin.system.views.login_log import LoginLogViewSet
from dvadmin.system.views.menu import MenuViewSet 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.message_center import MessageCenterViewSet
from dvadmin.system.views.operation_log import OperationLogViewSet from dvadmin.system.views.operation_log import OperationLogViewSet
from dvadmin.system.views.role import RoleViewSet from dvadmin.system.views.role import RoleViewSet
from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet 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.system_config import SystemConfigViewSet
from dvadmin.system.views.user import UserViewSet from dvadmin.system.views.user import UserViewSet
from dvadmin.system.views.column import ColumnViewSet from dvadmin.system.views.column import ColumnViewSet
system_url = routers.SimpleRouter() system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet) system_url.register(r'menu', MenuViewSet)
system_url.register(r'menu_button', MenuButtonViewSet)
system_url.register(r'role', RoleViewSet) system_url.register(r'role', RoleViewSet)
system_url.register(r'dept', DeptViewSet) system_url.register(r'dept', DeptViewSet)
system_url.register(r'user', UserViewSet) 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'api_white_list', ApiWhiteListViewSet)
system_url.register(r'system_config', SystemConfigViewSet) system_url.register(r'system_config', SystemConfigViewSet)
system_url.register(r'message_center', MessageCenterViewSet) 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'role_menu_permission', RoleMenuPermissionViewSet)
system_url.register(r'column', ColumnViewSet) system_url.register(r'column', ColumnViewSet)

View File

@@ -32,15 +32,10 @@ class ColumnViewSet(CustomModelViewSet):
role_id = request.query_params.get('role') role_id = request.query_params.get('role')
app_name = request.query_params.get('app') app_name = request.query_params.get('app')
model_name = request.query_params.get('model') 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:
if not role_id or not model_name or not app_name or not menu: return ErrorResponse(msg="参数错误")
return SuccessResponse([]) queryset = Columns.objects.filter(role_id=role_id, model=model_name, app=app_name)
queryset = self.filter_queryset(self.get_queryset().filter(role_id=role_id, model=model_name, app=app_name,menu_id=menu)) serializer = ColumnSerializer(queryset, many=True, request=request)
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)
return SuccessResponse(data=serializer.data, msg="获取成功") return SuccessResponse(data=serializer.data, msg="获取成功")
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
@@ -95,4 +90,4 @@ class ColumnViewSet(CustomModelViewSet):
serializer = self.get_serializer(data=data, request=request) serializer = self.get_serializer(data=data, request=request)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
serializer.save() 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.decorators import action
from rest_framework.permissions import IsAuthenticated 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.json_response import DetailResponse, SuccessResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@@ -135,7 +135,7 @@ class DeptViewSet(CustomModelViewSet):
queryset = Dept.objects.values('id', 'name', 'parent') queryset = Dept.objects.values('id', 'name', 'parent')
else: else:
role_ids = request.user.role.values_list('id', flat=True) 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 user_dept_id = request.user.dept.id
dept_list = [user_dept_id] dept_list = [user_dept_id]
data_range_list = list(set(data_range)) data_range_list = list(set(data_range))

View File

@@ -8,10 +8,10 @@
""" """
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Menu, RoleMenuPermission from dvadmin.system.models import Menu, RoleMenuPermission
from dvadmin.system.views.menu_button import MenuButtonSerializer from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@@ -21,7 +21,7 @@ class MenuSerializer(CustomModelSerializer):
菜单表的简单序列化器 菜单表的简单序列化器
""" """
menuPermission = serializers.SerializerMethodField(read_only=True) menuPermission = serializers.SerializerMethodField(read_only=True)
hasChild = serializers.SerializerMethodField() hasChildren = serializers.SerializerMethodField()
def get_menuPermission(self, instance): def get_menuPermission(self, instance):
queryset = instance.menuPermission.order_by('-name').values('id', 'name', 'value') queryset = instance.menuPermission.order_by('-name').values('id', 'name', 'value')
@@ -31,7 +31,7 @@ class MenuSerializer(CustomModelSerializer):
else: else:
return None return None
def get_hasChild(self, instance): def get_hasChildren(self, instance):
hasChild = Menu.objects.filter(parent=instance.id) hasChild = Menu.objects.filter(parent=instance.id)
if hasChild: if hasChild:
return True return True
@@ -71,7 +71,7 @@ class WebRouterSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Menu model = Menu
fields = ( 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') 'component_name', 'cache', 'visible', 'status')
read_only_fields = ["id"] read_only_fields = ["id"]
@@ -90,46 +90,57 @@ class MenuViewSet(CustomModelViewSet):
create_serializer_class = MenuCreateSerializer create_serializer_class = MenuCreateSerializer
update_serializer_class = MenuCreateSerializer update_serializer_class = MenuCreateSerializer
search_fields = ['name', 'status'] 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 params = request.query_params
parent = params.get('parent', None) menu_type = params.get('menu_type', 0)
page = params.get('page', None) queryset = Menu.objects.filter(menu_type=menu_type).order_by('sort')
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)
serializer = MenuSerializer(queryset, many=True, request=request) serializer = MenuSerializer(queryset, many=True, request=request)
data = serializer.data 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=[]) @action(methods=['GET'], detail=False, permission_classes=[])
def web_router(self, request): def web_router(self, request):
"""用于前端获取当前角色的路由""" """用于前端获取当前角色的路由"""
user = request.user user = request.user
is_admin = user.role.values_list('admin', flat=True) if user.is_superuser:
if user.is_superuser or True in is_admin: queryset = self.queryset.filter(status=1,menu_type__in=[0,1])
queryset = self.queryset.filter(status=1)
else: else:
role_list = user.role.values_list('id', flat=True) role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_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) serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功") 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=[]) @action(methods=['GET'], detail=False, permission_classes=[])
def get_all_menu(self, request): 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.models import Role, Menu, MenuButton, Dept
from dvadmin.system.views.dept import DeptSerializer from dvadmin.system.views.dept import DeptSerializer
from dvadmin.system.views.menu import MenuSerializer 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.crud_mixin import FastCrudMixin
from dvadmin.utils.field_permission import FieldPermissionMixin from dvadmin.utils.field_permission import FieldPermissionMixin
from dvadmin.utils.json_response import SuccessResponse, DetailResponse from dvadmin.utils.json_response import SuccessResponse, DetailResponse
@@ -39,7 +38,6 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
""" """
menu = MenuSerializer(many=True, read_only=True) menu = MenuSerializer(many=True, read_only=True)
dept = DeptSerializer(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, key = serializers.CharField(max_length=50,
validators=[CustomUniqueValidator(queryset=Role.objects.all(), message="权限字符必须唯一")]) validators=[CustomUniqueValidator(queryset=Role.objects.all(), message="权限字符必须唯一")])
name = serializers.CharField(max_length=50, validators=[CustomUniqueValidator(queryset=Role.objects.all())]) 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: class Meta:
model = Menu model = Menu
fields = ['id', 'parent', 'name', 'menuPermission'] fields = ['id', 'parent', 'name']
class MenuButtonPermissionSerializer(CustomModelSerializer): class MenuButtonPermissionSerializer(CustomModelSerializer):

View File

@@ -4,40 +4,46 @@
@author: 猿小天 @author: 猿小天
@contact: QQ:1638245306 @contact: QQ:1638245306
@Created on: 2021/6/3 003 0:30 @Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理 @Remark: 接口权限管理
""" """
from django.db.models import F, Subquery, OuterRef, Exists from django.db.models import F, Subquery, OuterRef, Exists
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated 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.system.views.menu import MenuSerializer
from dvadmin.utils.json_response import DetailResponse, ErrorResponse from dvadmin.utils.json_response import DetailResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet 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: class Meta:
model = RoleMenuButtonPermission model = RoleApiPermission
fields = "__all__" fields = "__all__"
read_only_fields = ["id"] read_only_fields = ["id"]
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer): class RoleApiPermissionCreateUpdateSerializer(CustomModelSerializer):
""" """
初始化菜单按钮-序列化器 初始化接口权限-序列化器
""" """
menu_button__name = serializers.CharField(source='menu_button.name', read_only=True) menu_button__name = serializers.CharField(source='menu_button.name', read_only=True)
menu_button__value = serializers.CharField(source='menu_button.value', read_only=True) menu_button__value = serializers.CharField(source='menu_button.value', read_only=True)
class Meta: class Meta:
model = RoleMenuButtonPermission model = RoleApiPermission
fields = "__all__" fields = "__all__"
read_only_fields = ["id"] read_only_fields = ["id"]
@@ -51,14 +57,14 @@ class RoleButtonPermissionSerializer(CustomModelSerializer):
def get_isCheck(self, instance): def get_isCheck(self, instance):
params = self.request.query_params params = self.request.query_params
return RoleMenuButtonPermission.objects.filter( return RoleApiPermission.objects.filter(
menu_button__id=instance['id'], menu_button__id=instance['id'],
role__id=params.get('role'), role__id=params.get('role'),
).exists() ).exists()
def get_data_range(self, instance): def get_data_range(self, instance):
params = self.request.query_params params = self.request.query_params
obj = RoleMenuButtonPermission.objects.filter( obj = RoleApiPermission.objects.filter(
menu_button__id=instance['id'], menu_button__id=instance['id'],
role__id=params.get('role'), role__id=params.get('role'),
).first() ).first()
@@ -100,7 +106,7 @@ class RoleMenuPermissionSerializer(CustomModelSerializer):
def get_columns(self, instance): def get_columns(self, instance):
params = self.request.query_params 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) serializer = RoleColumnsSerializer(col_list,many=True,request=self.request)
return serializer.data return serializer.data
@@ -110,90 +116,21 @@ class RoleMenuPermissionSerializer(CustomModelSerializer):
model = Menu model = Menu
fields = ['id','name','isCheck','btns','columns'] fields = ['id','name','isCheck','btns','columns']
class RoleMenuButtonPermissionViewSet(CustomModelViewSet): class RoleApiPermissionViewSet(CustomModelViewSet):
""" """
菜单按钮接口 接口权限接口
list:查询 list:查询
create:新增 create:新增
update:修改 update:修改
retrieve:单例 retrieve:单例
destroy:删除 destroy:删除
""" """
queryset = RoleMenuButtonPermission.objects.all() queryset = RoleApiPermission.objects.all()
serializer_class = RoleMenuButtonPermissionSerializer serializer_class = RoleApiPermissionSerializer
create_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer create_serializer_class = RoleApiPermissionCreateUpdateSerializer
update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer update_serializer_class = RoleApiPermissionCreateUpdateSerializer
extra_filter_class = [] 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]) @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def data_scope(self, request): def data_scope(self, request):
""" """
@@ -231,7 +168,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
role_list = request.user.role.values_list('id', flat=True) role_list = request.user.role.values_list('id', flat=True)
if params := request.query_params: if params := request.query_params:
if menu_button_id := params.get('menu_button', None): 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 role__in=role_list, menu_button__id=menu_button_id
).values_list('data_range', flat=True) ).values_list('data_range', flat=True)
data_range_list = list(set(role_queryset)) data_range_list = list(set(role_queryset))
@@ -294,8 +231,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
""" """
params = request.query_params params = request.query_params
is_superuser = request.user.is_superuser is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True) if is_superuser:
if is_superuser or True in is_admin:
queryset = Dept.objects.values('id', 'name', 'parent') queryset = Dept.objects.values('id', 'name', 'parent')
else: else:
if not params: if not params:
@@ -304,62 +240,6 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
if menu_button is None: if menu_button is None:
return ErrorResponse(msg="参数错误") return ErrorResponse(msg="参数错误")
role_list = request.user.role.values_list('id', flat=True) role_list = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list, menu_button=None).values( dept_ids = RoleApiPermission.objects.filter(role__in=role_list).values_list('dept__id',flat=True)
dept_id=F('dept__id'), queryset = Dept.objects.filter(id__in=dept_ids).values('id', 'name', 'parent')
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()
return DetailResponse(data=queryset) return DetailResponse(data=queryset)

View File

@@ -58,8 +58,38 @@ class RoleMenuPermissionViewSet(CustomModelViewSet):
update_serializer_class = RoleMenuPermissionCreateUpdateSerializer update_serializer_class = RoleMenuPermissionCreateUpdateSerializer
extra_filter_class = [] 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) @action(methods=['post'],detail=False)
def save_auth(self,request): def save_menu_permission(self,request):
""" """
保存页面菜单授权 保存页面菜单授权
:param 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 django_filters.utils import get_model_field
from rest_framework.filters import BaseFilterBackend 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): def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
@@ -116,18 +116,17 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
if _pk: # 判断是否是单例查询 if _pk: # 判断是否是单例查询
re_api = re.sub(_pk,'{id}', api) re_api = re.sub(_pk,'{id}', api)
role_id_list = request.user.role.values_list('id', flat=True) 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__in=role_id_list,
role__status=1, role__status=1,
menu_button__api=re_api, api=re_api,
menu_button__method=method).values( method=method).values(
'data_range', 'data_range',
role_admin=F('role__admin')
) )
dataScope_list = [] # 权限范围列表 dataScope_list = [] # 权限范围列表
for ele in role_permission_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 return queryset
dataScope_list.append(ele.get("data_range")) dataScope_list.append(ele.get("data_range"))
dataScope_list = list(set(dataScope_list)) dataScope_list = list(set(dataScope_list))

View File

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

View File

@@ -12,7 +12,7 @@ from django.contrib.auth.models import AnonymousUser
from django.db.models import F from django.db.models import F
from rest_framework.permissions import BasePermission from rest_framework.permissions import BasePermission
from dvadmin.system.models import ApiWhiteList, RoleMenuButtonPermission from dvadmin.system.models import ApiWhiteList, RoleApiPermission
def ValidationApi(reqApi, validApi): def ValidationApi(reqApi, validApi):
@@ -74,18 +74,18 @@ class CustomPermission(BasePermission):
methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'] methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH']
method = methodList.index(method) 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 = [ api_white_list = [
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str( str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('permission__method')) + '$' for item in api_white_list if item.get('permission__api')] item.get('method')) + '$' for item in api_white_list if item.get('api')]
# ********# # ********#
if not hasattr(request.user, "role"): if not hasattr(request.user, "role"):
return False return False
role_id_list = request.user.role.values_list('id',flat=True) 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 = [ ApiList = [
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str( str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('permission__method')) + '$' for item in userApiList if item.get('permission__api')] item.get('method')) + '$' for item in userApiList if item.get('api')]
new_api_ist = api_white_list + ApiList new_api_ist = api_white_list + ApiList
new_api = api + ":" + str(method) new_api = api + ":" + str(method)
for item in new_api_ist: for item in new_api_ist:

View File

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

View File

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

View File

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

View File

@@ -4,8 +4,16 @@ import { DictionaryStore } from '/@/stores/dictionary';
/** /**
* @method 获取指定name字典 * @method 获取指定name字典
*/ */
export const dictionary = (name: string) => { export const dictionary = (key: string,value:string|number|null=null) => {
const dict = DictionaryStore() const dict = DictionaryStore()
const dictionary = toRaw(dict.data) 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); pluginNames.add(pluginsName);
} }
pluginsAll = Array.from(pluginNames); pluginsAll = Array.from(pluginNames);
console.log('已发现插件:', pluginsAll); console.table('已注册插件:', pluginsAll);
}; };

View File

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

View File

@@ -5,12 +5,19 @@ export const apiPrefix = '/api/system/menu/';
export function GetList(query: UserPageQuery) { export function GetList(query: UserPageQuery) {
return request({ return request({
url: apiPrefix, url: apiPrefix+"tree/",
method: 'get', method: 'get',
params: query, params: query,
}); });
} }
export function GetChildren(id: InfoReq) {
return request({
url: apiPrefix + id + '/getChildren/',
method: 'get',
});
}
export function GetObj(id: InfoReq) { export function GetObj(id: InfoReq) {
return request({ return request({
url: apiPrefix + id + '/', 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> <template>
<fs-page> <fs-page>
<el-row class="menu-el-row"> <fs-crud ref="crudRef" v-bind="crudBinding">
<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>
<el-col :span="18"> </fs-crud>
<div class="menu-box menu-right-box"> <fs-form-wrapper ref="addChildrenRef" />
<MenuButtonCom ref="menuButtonRef" /> </fs-page>
</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>
</template> </template>
<script lang="ts" setup name="menuPages"> <script lang="ts" setup>
import { ref, onMounted } from 'vue'; import {ref, onMounted, reactive, computed} from "vue";
import XEUtils from 'xe-utils'; import createCrudOptions from "./crud";
import { ElMessageBox } from 'element-plus'; import {useExpose, useCrud, useCompute} from "@fast-crud/fast-crud";
import MenuTreeCom from './components/MenuTreeCom/index.vue'; import {AddObj} from "./api";
import MenuButtonCom from './components/MenuButtonCom/index.vue'; import {ElMessage } from "element-plus"
import MenuFormCom from './components/MenuFormCom/index.vue';
import { GetList, DelObj } from './api';
import { successNotification } from '/@/utils/message';
import { APIResponseData, MenuTreeItemType } from './types';
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;
});
};
/** // crud组件的ref
* 菜单的点击事件 const crudRef = ref();
*/ // crud 配置的ref
const handleTreeClick = (record: MenuTreeItemType) => { const crudBinding = ref();
menuButtonRef.value?.handleRefreshTable(record); // 暴露的方法
}; 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(() => { onMounted(() => {
getData(); crudExpose.doRefresh();
}); });
</script> </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 { request } from "/@/utils/service";
import XEUtils from 'xe-utils';
/** /**
* *
* @param roleId * @param roleId
* @param query * @param query
*/ */
export function getRolePremission(query:object) { export function getMenuPremissionTree(query:object) {
return request({ return request({
url: '/api/system/role_menu_button_permission/get_role_premission/', url: '/api/system/role_menu_permission/menu_permission_tree/',
method: 'get', method: 'get',
params:query 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 * @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> <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"> :before-close="handleDrawerClose">
<template #header> <template #header>
<el-row> <el-row>
@@ -8,111 +10,32 @@
<el-tag>{{ props.roleName }}</el-tag> <el-tag>{{ props.roleName }}</el-tag>
</div> </div>
</el-col> </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> </el-row>
</template> </template>
<div class="permission-com"> <div>
<el-collapse v-model="collapseCurrent" @change="handleCollapseChange" accordion> <el-tabs type="border-card" v-model="permissionTab">
<el-collapse-item v-for="(item,mIndex) in menuData" :key="mIndex" :name="mIndex"> <el-tab-pane label="菜单/按钮授权" name="menu">
<template #title> <MenuPermission ref="menuPermissionRef" :role-id="props.roleId" @handleDrawerClose="handleDrawerClose"></MenuPermission>
<div @click.stop="null"> </el-tab-pane>
<p class="pc-collapse-title"> <el-tab-pane label="请求接口授权" name="api">
<el-checkbox v-model="item.isCheck"> <ApiPermission v-if="permissionTab==='api'" :role-id="props.roleId"></ApiPermission>
<span>{{ item.name }}</span> </el-tab-pane>
</el-checkbox> <el-tab-pane label="表单字段授权" name="field">
</p> <FieldPermission v-if="permissionTab==='field'" :role-id="props.roleId"></FieldPermission>
<div v-show="!collapseCurrent.includes(mIndex)"> </el-tab-pane>
<el-checkbox v-for="btn in item.btns" :key="btn.value" :label="btn.value" v-model="btn.isCheck"> </el-tabs>
{{ btn.name }} </div>
</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>
</el-drawer> </el-drawer>
</template> </template>
<script setup lang="ts"> <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 XEUtils from 'xe-utils';
import {errorNotification} from '/@/utils/message'; 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 {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({ const props = defineProps({
roleId: { roleId: {
type: Number, type: Number,
@@ -128,28 +51,27 @@ const props = defineProps({
} }
}) })
const emit = defineEmits(['update:drawerVisible']) const emit = defineEmits(['update:drawerVisible'])
const menuPermissionRef = ref()
const permissionTab = ref('menu')
const drawerVisible = ref(false) const drawerVisible = ref(false)
watch( watch(
() => props.drawerVisible, () => props.drawerVisible,
(val) => { (val) => {
drawerVisible.value = val; drawerVisible.value = val;
getMenuBtnPermission() nextTick(()=>{
fetchData() menuPermissionRef.value.getMenuPremissionTreeData()
})
// fetchData()
} }
); );
const handleDrawerClose = () => { const handleDrawerClose = () => {
permissionTab.value ='menu'
emit('update:drawerVisible', false); emit('update:drawerVisible', false);
} }
const defaultTreeProps = {
children: 'children',
label: 'name',
value: 'id',
};
let menuData = ref<MenuDataType[]>([]);
let collapseCurrent = ref(['1']); let collapseCurrent = ref(['1']);
let menuCurrent = ref<Partial<MenuDataType>>({}); let menuCurrent = ref<Partial<MenuDataType>>({});
let menuBtnCurrent = ref<number>(-1); let menuBtnCurrent = ref<number>(-1);
@@ -164,10 +86,52 @@ const formatDataRange = computed(() => {
let deptData = ref<CustomDataPermissionDeptType[]>([]); let deptData = ref<CustomDataPermissionDeptType[]>([]);
let dataPermission = ref(); let dataPermission = ref();
let customDataPermission = ref([]); let customDataPermission = ref([]);
//,, ///
const getMenuBtnPermission = async () => { const permissionTreeRef = ref();
const resMenu = await getRolePremission({role: props.roleId}) let menuPermissionTreeData = ref<MenuDataType[]>([]);
menuData.value = resMenu.data 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 () => { const fetchData = async () => {

View File

@@ -56,35 +56,15 @@ export const createCrudOptions = function ({
show: true, show: true,
}, },
edit: { edit: {
show: hasPermissions('role:Update'), show: hasPermissions('role:update'),
}, },
remove: { 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: { customNew: {
type: 'primary', type: 'primary',
text: '权限配置', text: '权',
show: hasPermissions('role:Update'), // show: hasPermissions('role:Update'),
tooltip: {
placement: 'top',
content: '权限配置',
},
click: (context: any): void => { click: (context: any): void => {
const { row } = context; const { row } = context;
handleDrawerOpen(row); handleDrawerOpen(row);
@@ -183,44 +163,13 @@ export const createCrudOptions = function ({
value: 1, 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: { status: {
title: '状态', title: '状态',
search: { show: true }, search: { show: true },
type: 'dict-radio', type: 'dict-radio',
dict: dict({
data: dictionary('button_status_bool'),
}),
column: { column: {
width: 100, width: 100,
component: { component: {
@@ -243,10 +192,7 @@ export const createCrudOptions = function ({
}, },
editForm: { editForm: {
show: columnPermission('status', 'is_update'), show: columnPermission('status', 'is_update'),
}, }
dict: dict({
data: dictionary('button_status_bool'),
}),
}, },
update_datetime: { update_datetime: {
title: '更新时间', title: '更新时间',

View File

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