Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff7333d882 | ||
|
|
52de562fac | ||
|
|
9cb3570f5a | ||
|
|
75670f9a5e | ||
|
|
212af88409 | ||
|
|
1b71ba156b | ||
|
|
10b159aa15 | ||
|
|
b7b589176a | ||
|
|
bd8fef9a04 |
@@ -178,6 +178,7 @@ CHANNEL_LAYERS = {
|
||||
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||
}
|
||||
}
|
||||
REDIS_URL = locals().get('REDIS_URL', "")
|
||||
# CHANNEL_LAYERS = {
|
||||
# 'default': {
|
||||
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
|
||||
@@ -399,10 +400,11 @@ TENANT_SHARED_APPS = []
|
||||
PLUGINS_URL_PATTERNS = []
|
||||
# ********** 一键导入插件配置开始 **********
|
||||
# 例如:
|
||||
# from dvadmin_upgrade_center.settings import * # 升级中心
|
||||
#from dvadmin3_upgrade_center.settings import * # 升级中心
|
||||
# from dvadmin_celery.settings import * # celery 异步任务
|
||||
# from dvadmin_third.settings import * # 第三方用户管理
|
||||
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
|
||||
# from dvadmin_tenants.settings import * # 租户管理
|
||||
# from dvadmin_uniapp.settings import *
|
||||
# ...
|
||||
# ********** 一键导入插件配置结束 **********
|
||||
|
||||
@@ -9,7 +9,7 @@ django.setup()
|
||||
from dvadmin.system.models import (
|
||||
Role, Dept, Users, Menu, MenuButton,
|
||||
ApiWhiteList, Dictionary, SystemConfig,
|
||||
RoleMenuPermission, RoleMenuButtonPermission
|
||||
RoleMenuPermission, RoleApiPermission, Columns
|
||||
)
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
|
||||
@@ -42,24 +42,13 @@ class UsersInitSerializer(CustomModelSerializer):
|
||||
}
|
||||
|
||||
|
||||
class MenuButtonInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id', 'name', 'value', 'api', 'method', 'menu']
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MenuInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
递归深度获取数信息(用于生成初始化json文件)
|
||||
"""
|
||||
name = serializers.CharField(required=False)
|
||||
name = serializers.CharField(required=True)
|
||||
menu_type = serializers.IntegerField(required=True)
|
||||
children = serializers.SerializerMethodField()
|
||||
menu_button = serializers.SerializerMethodField()
|
||||
|
||||
def get_children(self, obj: Menu):
|
||||
data = []
|
||||
@@ -69,26 +58,18 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
data = serializer.data
|
||||
return data
|
||||
|
||||
def get_menu_button(self, obj: Menu):
|
||||
data = []
|
||||
instance = obj.menuPermission.order_by('method')
|
||||
if instance:
|
||||
data = list(instance.values('name', 'value', 'api', 'method'))
|
||||
return data
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
children = self.initial_data.get('children')
|
||||
menu_button = self.initial_data.get('menu_button')
|
||||
# 菜单表
|
||||
if children:
|
||||
for menu_data in children:
|
||||
menu_data['parent'] = instance.id
|
||||
filter_data = {
|
||||
"name": menu_data['name'],
|
||||
"web_path": menu_data['web_path'],
|
||||
"component": menu_data['component'],
|
||||
"component_name": menu_data['component_name'],
|
||||
"menu_type": menu_data['menu_type'],
|
||||
}
|
||||
instance_obj = Menu.objects.filter(**filter_data).first()
|
||||
if instance_obj and not self.initial_data.get('reset'):
|
||||
@@ -96,24 +77,12 @@ class MenuInitSerializer(CustomModelSerializer):
|
||||
serializer = MenuInitSerializer(instance_obj, data=menu_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
# 菜单按钮
|
||||
if menu_button:
|
||||
for menu_button_data in menu_button:
|
||||
menu_button_data['menu'] = instance.id
|
||||
filter_data = {
|
||||
"menu": menu_button_data['menu'],
|
||||
"value": menu_button_data['value']
|
||||
}
|
||||
instance_obj = MenuButton.objects.filter(**filter_data).first()
|
||||
serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
|
||||
'cache', 'visible', 'parent', 'children', 'menu_button', 'creator', 'dept_belong_id']
|
||||
fields = ['name', 'icon', 'sort', 'is_link', 'menu_type', 'web_path', 'component', 'component_name', 'status',
|
||||
'cache', 'visible', 'parent', 'children', 'creator', 'dept_belong_id']
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
@@ -128,7 +97,7 @@ class RoleInitSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = ['name', 'key', 'sort', 'status', 'admin',
|
||||
fields = ['name', 'key', 'sort', 'status',
|
||||
'creator', 'dept_belong_id']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
@@ -166,37 +135,62 @@ class RoleMenuInitSerializer(CustomModelSerializer):
|
||||
}
|
||||
|
||||
|
||||
class RoleMenuButtonInitSerializer(CustomModelSerializer):
|
||||
class RoleApiPermissionInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化角色菜单按钮(用于生成初始化json文件)
|
||||
初始化角色接口权限(用于生成初始化json文件)
|
||||
"""
|
||||
role_key = serializers.CharField(max_length=100, required=True)
|
||||
menu_button_value = serializers.CharField(max_length=100, required=True)
|
||||
name = serializers.CharField(max_length=255, required=True)
|
||||
api = serializers.CharField(max_length=255, required=True)
|
||||
method = serializers.IntegerField(required=True)
|
||||
data_range = serializers.CharField(max_length=100, required=False)
|
||||
|
||||
def create(self, validated_data):
|
||||
init_data = self.initial_data
|
||||
validated_data.pop('menu_button_value')
|
||||
validated_data.pop('role_key')
|
||||
role_id = Role.objects.filter(key=init_data['role_key']).first()
|
||||
menu_button_id = MenuButton.objects.filter(value=init_data['menu_button_value']).first()
|
||||
validated_data['role'] = role_id
|
||||
validated_data['menu_button'] = menu_button_id
|
||||
instance = super().create(validated_data)
|
||||
instance.dept.set([])
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
fields = ['role_key', 'menu_button_value', 'data_range', 'dept', 'creator', 'dept_belong_id']
|
||||
model = RoleApiPermission
|
||||
fields = ['role_key', 'name','api','method', 'data_range', 'dept', 'creator', 'dept_belong_id']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
'role': {'required': False},
|
||||
'menu': {'required': False},
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
|
||||
class RoleColumnInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化角色字段权限(用于生成初始化json文件)
|
||||
"""
|
||||
role_key = serializers.CharField(max_length=100, required=True)
|
||||
app = serializers.CharField(max_length=255, required=True)
|
||||
model = serializers.CharField(max_length=255, required=True)
|
||||
field_name = serializers.CharField(max_length=255, required=True)
|
||||
title = serializers.CharField(max_length=255, required=True)
|
||||
|
||||
def create(self, validated_data):
|
||||
init_data = self.initial_data
|
||||
validated_data.pop('role_key')
|
||||
role_id = Role.objects.filter(key=init_data['role_key']).first()
|
||||
validated_data['role'] = role_id
|
||||
instance = super().create(validated_data)
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Columns
|
||||
fields = ['role_key', 'app','model','field_name', 'title']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
'role': {'required': False},
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
|
||||
class ApiWhiteListInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
|
||||
123
backend/dvadmin/system/fixtures/init_columns.json
Normal file
123
backend/dvadmin/system/fixtures/init_columns.json
Normal 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
@@ -4,7 +4,6 @@
|
||||
"key": "admin",
|
||||
"sort": 1,
|
||||
"status": true,
|
||||
"admin": true,
|
||||
"remark": null
|
||||
},
|
||||
{
|
||||
@@ -12,7 +11,6 @@
|
||||
"key": "public",
|
||||
"sort": 2,
|
||||
"status": true,
|
||||
"admin": true,
|
||||
"remark": null
|
||||
}
|
||||
]
|
||||
|
||||
14
backend/dvadmin/system/fixtures/init_roleapipermission.json
Normal file
14
backend/dvadmin/system/fixtures/init_roleapipermission.json
Normal 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
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
@@ -11,7 +11,7 @@ from dvadmin.utils.core_initialize import CoreInitialize
|
||||
from dvadmin.system.fixtures.initSerializer import (
|
||||
UsersInitSerializer, DeptInitSerializer, RoleInitSerializer,
|
||||
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer,
|
||||
SystemConfigInitSerializer, RoleMenuInitSerializer, RoleMenuButtonInitSerializer
|
||||
SystemConfigInitSerializer, RoleMenuInitSerializer, RoleApiPermissionInitSerializer, RoleColumnInitSerializer
|
||||
)
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class Initialize(CoreInitialize):
|
||||
"""
|
||||
初始化菜单信息
|
||||
"""
|
||||
self.init_base(MenuInitSerializer, unique_fields=['name', 'web_path', 'component', 'component_name'])
|
||||
self.init_base(MenuInitSerializer, unique_fields=['name'])
|
||||
|
||||
def init_role_menu(self):
|
||||
"""
|
||||
@@ -47,11 +47,17 @@ class Initialize(CoreInitialize):
|
||||
"""
|
||||
self.init_base(RoleMenuInitSerializer, unique_fields=['role', 'menu'])
|
||||
|
||||
def init_role_menu_button(self):
|
||||
def init_role_api_permission(self):
|
||||
"""
|
||||
初始化角色菜单按钮信息
|
||||
"""
|
||||
self.init_base(RoleMenuButtonInitSerializer, unique_fields=['role', 'menu_button'])
|
||||
self.init_base(RoleApiPermissionInitSerializer, unique_fields=['role', 'api','name'])
|
||||
|
||||
def init_role_column(self):
|
||||
"""
|
||||
初始化角色字段权限
|
||||
"""
|
||||
self.init_base(RoleColumnInitSerializer, unique_fields=['app','model','field_name'])
|
||||
|
||||
def init_api_white_list(self):
|
||||
"""
|
||||
@@ -77,7 +83,8 @@ class Initialize(CoreInitialize):
|
||||
self.init_users()
|
||||
self.init_menu()
|
||||
self.init_role_menu()
|
||||
self.init_role_menu_button()
|
||||
self.init_role_api_permission()
|
||||
self.init_role_column()
|
||||
self.init_api_white_list()
|
||||
self.init_dictionary()
|
||||
self.init_system_config()
|
||||
|
||||
@@ -13,7 +13,6 @@ class Role(CoreModel):
|
||||
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
|
||||
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
|
||||
status = models.BooleanField(default=True, verbose_name="角色状态", help_text="角色状态")
|
||||
admin = models.BooleanField(default=False, verbose_name="是否为admin", help_text="是否为admin")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_role"
|
||||
@@ -156,22 +155,24 @@ class Menu(CoreModel):
|
||||
help_text="上级菜单",
|
||||
)
|
||||
icon = models.CharField(max_length=64, verbose_name="菜单图标", null=True, blank=True, help_text="菜单图标")
|
||||
name = models.CharField(max_length=64, verbose_name="菜单名称", help_text="菜单名称")
|
||||
name = models.CharField(max_length=64, verbose_name="目录名称/菜单名称/按钮名称", help_text="目录名称/菜单名称/按钮名称")
|
||||
sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序")
|
||||
ISLINK_CHOICES = (
|
||||
(0, "否"),
|
||||
(1, "是"),
|
||||
)
|
||||
is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
|
||||
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录")
|
||||
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
|
||||
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
|
||||
MENU_TYPE_CHOICES =(
|
||||
(0, "目录"),
|
||||
(1, "菜单"),
|
||||
(2, "按钮"),
|
||||
)
|
||||
menu_type = models.IntegerField(default=0, verbose_name="菜单类型", help_text="菜单类型")
|
||||
web_path = models.CharField(max_length=128,default="/", verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
|
||||
component = models.CharField(max_length=200, verbose_name="组件地址/按钮权限值", null=True, blank=True, help_text="组件地址/按钮权限值")
|
||||
component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True,
|
||||
help_text="组件名称")
|
||||
status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态")
|
||||
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
|
||||
visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示",
|
||||
help_text="侧边栏中是否显示")
|
||||
frame_out = models.BooleanField(default=False, blank=True, verbose_name="是否主框架外", help_text="是否主框架外")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_menu"
|
||||
@@ -184,7 +185,6 @@ class Columns(CoreModel):
|
||||
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
|
||||
app = models.CharField(max_length=64, verbose_name='应用名')
|
||||
model = models.CharField(max_length=64, verbose_name='表名')
|
||||
menu = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name='菜单', db_constraint=False)
|
||||
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
|
||||
title = models.CharField(max_length=64, verbose_name='字段显示名')
|
||||
is_query = models.BooleanField(default=1, verbose_name='是否可查询')
|
||||
@@ -251,25 +251,25 @@ class RoleMenuPermission(CoreModel):
|
||||
# ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
class RoleMenuButtonPermission(CoreModel):
|
||||
class RoleApiPermission(CoreModel):
|
||||
role = models.ForeignKey(
|
||||
to="Role",
|
||||
db_constraint=False,
|
||||
related_name="role_menu_button",
|
||||
related_name="role_api",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="关联角色",
|
||||
help_text="关联角色",
|
||||
)
|
||||
menu_button = models.ForeignKey(
|
||||
to="MenuButton",
|
||||
db_constraint=False,
|
||||
related_name="menu_button_permission",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="关联菜单按钮",
|
||||
help_text="关联菜单按钮",
|
||||
null=True,
|
||||
blank=True
|
||||
name = models.CharField(max_length=64, verbose_name="名称", help_text="名称")
|
||||
api = models.CharField(max_length=200, verbose_name="接口地址", help_text="接口地址")
|
||||
METHOD_CHOICES = (
|
||||
(0, "GET"),
|
||||
(1, "POST"),
|
||||
(2, "PUT"),
|
||||
(3, "DELETE"),
|
||||
)
|
||||
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
|
||||
help_text="接口请求方法")
|
||||
DATASCOPE_CHOICES = (
|
||||
(0, "仅本人数据权限"),
|
||||
(1, "本部门及以下数据权限"),
|
||||
@@ -283,8 +283,8 @@ class RoleMenuButtonPermission(CoreModel):
|
||||
help_text="数据权限-关联部门")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "role_menu_button_permission"
|
||||
verbose_name = "角色按钮权限表"
|
||||
db_table = table_prefix + "role_api_permission"
|
||||
verbose_name = "角色接口权限表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
@@ -457,7 +457,7 @@ class SystemConfig(CoreModel):
|
||||
help_text="父级",
|
||||
)
|
||||
title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")
|
||||
key = models.CharField(max_length=20, verbose_name="键", help_text="键", db_index=True)
|
||||
key = models.CharField(max_length=50, verbose_name="键", help_text="键", db_index=True)
|
||||
value = models.JSONField(max_length=100, verbose_name="值", help_text="值", null=True, blank=True)
|
||||
sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True)
|
||||
status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态")
|
||||
|
||||
@@ -8,19 +8,17 @@ from dvadmin.system.views.dictionary import DictionaryViewSet
|
||||
from dvadmin.system.views.file_list import FileViewSet
|
||||
from dvadmin.system.views.login_log import LoginLogViewSet
|
||||
from dvadmin.system.views.menu import MenuViewSet
|
||||
from dvadmin.system.views.menu_button import MenuButtonViewSet
|
||||
from dvadmin.system.views.message_center import MessageCenterViewSet
|
||||
from dvadmin.system.views.operation_log import OperationLogViewSet
|
||||
from dvadmin.system.views.role import RoleViewSet
|
||||
from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet
|
||||
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
|
||||
from dvadmin.system.views.role_api_permission import RoleApiPermissionViewSet
|
||||
from dvadmin.system.views.system_config import SystemConfigViewSet
|
||||
from dvadmin.system.views.user import UserViewSet
|
||||
from dvadmin.system.views.column import ColumnViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
system_url.register(r'menu', MenuViewSet)
|
||||
system_url.register(r'menu_button', MenuButtonViewSet)
|
||||
system_url.register(r'role', RoleViewSet)
|
||||
system_url.register(r'dept', DeptViewSet)
|
||||
system_url.register(r'user', UserViewSet)
|
||||
@@ -31,7 +29,7 @@ system_url.register(r'file', FileViewSet)
|
||||
system_url.register(r'api_white_list', ApiWhiteListViewSet)
|
||||
system_url.register(r'system_config', SystemConfigViewSet)
|
||||
system_url.register(r'message_center', MessageCenterViewSet)
|
||||
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
|
||||
system_url.register(r'role_api_permission', RoleApiPermissionViewSet)
|
||||
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
||||
system_url.register(r'column', ColumnViewSet)
|
||||
|
||||
|
||||
@@ -32,15 +32,10 @@ class ColumnViewSet(CustomModelViewSet):
|
||||
role_id = request.query_params.get('role')
|
||||
app_name = request.query_params.get('app')
|
||||
model_name = request.query_params.get('model')
|
||||
menu = request.query_params.get('menu')
|
||||
if not role_id or not model_name or not app_name or not menu:
|
||||
return SuccessResponse([])
|
||||
queryset = self.filter_queryset(self.get_queryset().filter(role_id=role_id, model=model_name, app=app_name,menu_id=menu))
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True, request=request)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
if not role_id or not model_name or not app_name:
|
||||
return ErrorResponse(msg="参数错误")
|
||||
queryset = Columns.objects.filter(role_id=role_id, model=model_name, app=app_name)
|
||||
serializer = ColumnSerializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
@@ -95,4 +90,4 @@ class ColumnViewSet(CustomModelViewSet):
|
||||
serializer = self.get_serializer(data=data, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return SuccessResponse(msg='匹配成功')
|
||||
return DetailResponse(msg='匹配成功')
|
||||
|
||||
@@ -9,7 +9,7 @@ from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Dept, RoleMenuButtonPermission, Users
|
||||
from dvadmin.system.models import Dept, RoleApiPermission, Users
|
||||
from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
@@ -135,7 +135,7 @@ class DeptViewSet(CustomModelViewSet):
|
||||
queryset = Dept.objects.values('id', 'name', 'parent')
|
||||
else:
|
||||
role_ids = request.user.role.values_list('id', flat=True)
|
||||
data_range = RoleMenuButtonPermission.objects.filter(role__in=role_ids).values_list('data_range', flat=True)
|
||||
data_range = RoleApiPermission.objects.filter(role__in=role_ids).values_list('data_range', flat=True)
|
||||
user_dept_id = request.user.dept.id
|
||||
dept_list = [user_dept_id]
|
||||
data_range_list = list(set(data_range))
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Menu, RoleMenuPermission
|
||||
from dvadmin.system.views.menu_button import MenuButtonSerializer
|
||||
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse
|
||||
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
@@ -21,7 +21,7 @@ class MenuSerializer(CustomModelSerializer):
|
||||
菜单表的简单序列化器
|
||||
"""
|
||||
menuPermission = serializers.SerializerMethodField(read_only=True)
|
||||
hasChild = serializers.SerializerMethodField()
|
||||
hasChildren = serializers.SerializerMethodField()
|
||||
|
||||
def get_menuPermission(self, instance):
|
||||
queryset = instance.menuPermission.order_by('-name').values('id', 'name', 'value')
|
||||
@@ -31,7 +31,7 @@ class MenuSerializer(CustomModelSerializer):
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_hasChild(self, instance):
|
||||
def get_hasChildren(self, instance):
|
||||
hasChild = Menu.objects.filter(parent=instance.id)
|
||||
if hasChild:
|
||||
return True
|
||||
@@ -71,7 +71,7 @@ class WebRouterSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = (
|
||||
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', 'web_path', 'component',
|
||||
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'menu_type', 'web_path', 'component',
|
||||
'component_name', 'cache', 'visible', 'status')
|
||||
read_only_fields = ["id"]
|
||||
|
||||
@@ -90,46 +90,57 @@ class MenuViewSet(CustomModelViewSet):
|
||||
create_serializer_class = MenuCreateSerializer
|
||||
update_serializer_class = MenuCreateSerializer
|
||||
search_fields = ['name', 'status']
|
||||
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog']
|
||||
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'menu_type']
|
||||
|
||||
def list(self, request):
|
||||
@action(methods=['get'], detail=False)
|
||||
def tree(self, request):
|
||||
"""懒加载"""
|
||||
request.query_params._mutable = True
|
||||
params = request.query_params
|
||||
parent = params.get('parent', None)
|
||||
page = params.get('page', None)
|
||||
limit = params.get('limit', None)
|
||||
if page:
|
||||
del params['page']
|
||||
if limit:
|
||||
del params['limit']
|
||||
if params:
|
||||
if parent:
|
||||
queryset = self.queryset.filter(parent=parent)
|
||||
else:
|
||||
queryset = self.queryset.filter()
|
||||
else:
|
||||
queryset = self.queryset.filter(parent__isnull=True)
|
||||
queryset = self.filter_queryset(queryset)
|
||||
menu_type = params.get('menu_type', 0)
|
||||
queryset = Menu.objects.filter(menu_type=menu_type).order_by('sort')
|
||||
serializer = MenuSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return SuccessResponse(data=data)
|
||||
return DetailResponse(data=data)
|
||||
|
||||
@action(methods=['get'], detail=True)
|
||||
def getChildren(self,request,pk):
|
||||
"""
|
||||
获取子菜单,用于菜单页面的懒加载
|
||||
"""
|
||||
queryset = Menu.objects.filter(parent=pk)
|
||||
serializer = MenuSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return DetailResponse(data=data)
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[])
|
||||
def web_router(self, request):
|
||||
"""用于前端获取当前角色的路由"""
|
||||
user = request.user
|
||||
is_admin = user.role.values_list('admin', flat=True)
|
||||
if user.is_superuser or True in is_admin:
|
||||
queryset = self.queryset.filter(status=1)
|
||||
if user.is_superuser:
|
||||
queryset = self.queryset.filter(status=1,menu_type__in=[0,1])
|
||||
else:
|
||||
role_list = user.role.values_list('id', flat=True)
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id', flat=True)
|
||||
queryset = Menu.objects.filter(id__in=menu_list)
|
||||
queryset = Menu.objects.filter(id__in=menu_list,menu_type__in=[0,1])
|
||||
serializer = WebRouterSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return SuccessResponse(data=data, total=len(data), msg="获取成功")
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def menu_button_all_permission(self, request):
|
||||
"""
|
||||
获取所有的按钮权限
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = Menu.objects.filter(menu_type=2).values_list('component', flat=True)
|
||||
else:
|
||||
role_id = request.user.role.values_list('id', flat=True)
|
||||
queryset = Menu.objects.filter(role__in=role_id,menu_type=2).values_list('component',flat=True).distinct()
|
||||
return DetailResponse(data=queryset)
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[])
|
||||
def get_all_menu(self, request):
|
||||
"""用于菜单管理获取所有的菜单"""
|
||||
|
||||
@@ -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)
|
||||
@@ -13,7 +13,6 @@ from rest_framework.permissions import IsAuthenticated
|
||||
from dvadmin.system.models import Role, Menu, MenuButton, Dept
|
||||
from dvadmin.system.views.dept import DeptSerializer
|
||||
from dvadmin.system.views.menu import MenuSerializer
|
||||
from dvadmin.system.views.menu_button import MenuButtonSerializer
|
||||
from dvadmin.utils.crud_mixin import FastCrudMixin
|
||||
from dvadmin.utils.field_permission import FieldPermissionMixin
|
||||
from dvadmin.utils.json_response import SuccessResponse, DetailResponse
|
||||
@@ -39,7 +38,6 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
menu = MenuSerializer(many=True, read_only=True)
|
||||
dept = DeptSerializer(many=True, read_only=True)
|
||||
permission = MenuButtonSerializer(many=True, read_only=True)
|
||||
key = serializers.CharField(max_length=50,
|
||||
validators=[CustomUniqueValidator(queryset=Role.objects.all(), message="权限字符必须唯一")])
|
||||
name = serializers.CharField(max_length=50, validators=[CustomUniqueValidator(queryset=Role.objects.all())])
|
||||
@@ -63,21 +61,11 @@ class MenuPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单的按钮权限
|
||||
"""
|
||||
menuPermission = serializers.SerializerMethodField()
|
||||
|
||||
def get_menuPermission(self, instance):
|
||||
is_superuser = self.request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = MenuButton.objects.filter(menu__id=instance.id)
|
||||
else:
|
||||
menu_permission_id_list = self.request.user.role.values_list('permission', flat=True)
|
||||
queryset = MenuButton.objects.filter(id__in=menu_permission_id_list, menu__id=instance.id)
|
||||
serializer = MenuButtonSerializer(queryset, many=True, read_only=True)
|
||||
return serializer.data
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['id', 'parent', 'name', 'menuPermission']
|
||||
fields = ['id', 'parent', 'name']
|
||||
|
||||
|
||||
class MenuButtonPermissionSerializer(CustomModelSerializer):
|
||||
|
||||
@@ -4,40 +4,46 @@
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 菜单按钮管理
|
||||
@Remark: 接口权限管理
|
||||
"""
|
||||
from django.db.models import F, Subquery, OuterRef, Exists
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, Columns
|
||||
from dvadmin.system.models import RoleApiPermission, Menu, MenuButton, Dept, RoleMenuPermission, Columns
|
||||
from dvadmin.system.views.menu import MenuSerializer
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
|
||||
class RoleApiPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单按钮-序列化器
|
||||
接口权限-序列化器
|
||||
"""
|
||||
dept_name = serializers.SerializerMethodField(help_text="部门名称")
|
||||
|
||||
def get_dept_name(self, instance):
|
||||
dept_name_list = instance.dept.values_list("name",flat=True)
|
||||
return ",".join(dept_name_list)
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
model = RoleApiPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
|
||||
class RoleApiPermissionCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
初始化接口权限-序列化器
|
||||
"""
|
||||
menu_button__name = serializers.CharField(source='menu_button.name', read_only=True)
|
||||
menu_button__value = serializers.CharField(source='menu_button.value', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
model = RoleApiPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
@@ -51,14 +57,14 @@ class RoleButtonPermissionSerializer(CustomModelSerializer):
|
||||
|
||||
def get_isCheck(self, instance):
|
||||
params = self.request.query_params
|
||||
return RoleMenuButtonPermission.objects.filter(
|
||||
return RoleApiPermission.objects.filter(
|
||||
menu_button__id=instance['id'],
|
||||
role__id=params.get('role'),
|
||||
).exists()
|
||||
|
||||
def get_data_range(self, instance):
|
||||
params = self.request.query_params
|
||||
obj = RoleMenuButtonPermission.objects.filter(
|
||||
obj = RoleApiPermission.objects.filter(
|
||||
menu_button__id=instance['id'],
|
||||
role__id=params.get('role'),
|
||||
).first()
|
||||
@@ -100,7 +106,7 @@ class RoleMenuPermissionSerializer(CustomModelSerializer):
|
||||
|
||||
def get_columns(self, instance):
|
||||
params = self.request.query_params
|
||||
col_list = Columns.objects.filter(role__id=params.get('role'),menu__id=instance['id'])
|
||||
col_list = Columns.objects.filter(role__id=params.get('role'))
|
||||
serializer = RoleColumnsSerializer(col_list,many=True,request=self.request)
|
||||
return serializer.data
|
||||
|
||||
@@ -110,90 +116,21 @@ class RoleMenuPermissionSerializer(CustomModelSerializer):
|
||||
model = Menu
|
||||
fields = ['id','name','isCheck','btns','columns']
|
||||
|
||||
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
class RoleApiPermissionViewSet(CustomModelViewSet):
|
||||
"""
|
||||
菜单按钮接口
|
||||
接口权限接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = RoleMenuButtonPermission.objects.all()
|
||||
serializer_class = RoleMenuButtonPermissionSerializer
|
||||
create_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
|
||||
update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
|
||||
queryset = RoleApiPermission.objects.all()
|
||||
serializer_class = RoleApiPermissionSerializer
|
||||
create_serializer_class = RoleApiPermissionCreateUpdateSerializer
|
||||
update_serializer_class = RoleApiPermissionCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def get_role_premission(self, request):
|
||||
"""
|
||||
角色授权获取:
|
||||
:param request: role
|
||||
:return: menu,btns,columns
|
||||
"""
|
||||
params = request.query_params
|
||||
role = params.get('role',None)
|
||||
if role is None:
|
||||
return ErrorResponse(msg="未获取到角色信息")
|
||||
is_superuser = request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all()
|
||||
else:
|
||||
role_id = request.user.role.values_list('id', flat=True)
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id',flat=True)
|
||||
queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id').all()
|
||||
serializer = RoleMenuPermissionSerializer(queryset,many=True,request=request)
|
||||
data = serializer.data
|
||||
return DetailResponse(data=data)
|
||||
|
||||
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
|
||||
def set_role_premission(self,request,pk):
|
||||
"""
|
||||
对角色的菜单和按钮及按钮范围授权:
|
||||
:param request:
|
||||
:param pk: role
|
||||
:return:
|
||||
"""
|
||||
body = request.data
|
||||
RoleMenuPermission.objects.filter(role=pk).delete()
|
||||
RoleMenuButtonPermission.objects.filter(role=pk).delete()
|
||||
for menu in body:
|
||||
if menu.get('isCheck'):
|
||||
menu_parent = Menu.objects.filter(id=menu.get('id')).values('parent').first()
|
||||
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu_parent.get('parent'))
|
||||
RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
|
||||
for btn in menu.get('btns'):
|
||||
if btn.get('isCheck'):
|
||||
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),data_range=btn.get('data_range'))
|
||||
instance.dept.set(btn.get('dept',[]))
|
||||
for col in menu.get('columns'):
|
||||
Columns.objects.filter(id=col.get('id')).update(is_query=col.get('is_query'),is_create=col.get('is_create'),is_update=col.get('is_update'))
|
||||
return DetailResponse(msg="授权成功")
|
||||
|
||||
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def role_menu_get_button(self, request):
|
||||
"""
|
||||
当前用户角色和菜单获取可下拉选项的按钮:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
if params := request.query_params:
|
||||
if menu_id := params.get('menu', None):
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
queryset = MenuButton.objects.filter(menu=menu_id).values('id', 'name')
|
||||
else:
|
||||
role_list = request.user.role.values_list('id', flat=True)
|
||||
queryset = RoleMenuButtonPermission.objects.filter(
|
||||
role__in=role_list, menu_button__menu=menu_id
|
||||
).values(btn_id=F('menu_button__id'), name=F('menu_button__name'))
|
||||
return DetailResponse(data=queryset)
|
||||
return ErrorResponse(msg="参数错误")
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def data_scope(self, request):
|
||||
"""
|
||||
@@ -231,7 +168,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
role_list = request.user.role.values_list('id', flat=True)
|
||||
if params := request.query_params:
|
||||
if menu_button_id := params.get('menu_button', None):
|
||||
role_queryset = RoleMenuButtonPermission.objects.filter(
|
||||
role_queryset = RoleApiPermission.objects.filter(
|
||||
role__in=role_list, menu_button__id=menu_button_id
|
||||
).values_list('data_range', flat=True)
|
||||
data_range_list = list(set(role_queryset))
|
||||
@@ -294,8 +231,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
"""
|
||||
params = request.query_params
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
if is_superuser:
|
||||
queryset = Dept.objects.values('id', 'name', 'parent')
|
||||
else:
|
||||
if not params:
|
||||
@@ -304,62 +240,6 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
if menu_button is None:
|
||||
return ErrorResponse(msg="参数错误")
|
||||
role_list = request.user.role.values_list('id', flat=True)
|
||||
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list, menu_button=None).values(
|
||||
dept_id=F('dept__id'),
|
||||
name=F('dept__name'),
|
||||
parent=F('dept__parent')
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def menu_to_button(self, request):
|
||||
"""
|
||||
根据所选择菜单获取已配置的按钮/接口权限:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
params = request.query_params
|
||||
menu_id = params.get('menu', None)
|
||||
if menu_id is None:
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
queryset = RoleMenuButtonPermission.objects.filter(menu_button__menu=menu_id).values(
|
||||
'id',
|
||||
'data_range',
|
||||
'menu_button',
|
||||
'menu_button__name',
|
||||
'menu_button__value'
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
else:
|
||||
if params:
|
||||
|
||||
role_id = params.get('role', None)
|
||||
if role_id is None:
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
queryset = RoleMenuButtonPermission.objects.filter(role=role_id, menu_button__menu=menu_id).values(
|
||||
'id',
|
||||
'data_range',
|
||||
'menu_button',
|
||||
'menu_button__name',
|
||||
'menu_button__value'
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def role_to_menu(self, request):
|
||||
"""
|
||||
获取角色对应的按钮权限
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
params = request.query_params
|
||||
role_id = params.get('role', None)
|
||||
if role_id is None:
|
||||
return ErrorResponse(msg="未获取到参数")
|
||||
queryset = RoleMenuPermission.objects.filter(role_id=role_id).values_list('menu_id', flat=True).distinct()
|
||||
|
||||
dept_ids = RoleApiPermission.objects.filter(role__in=role_list).values_list('dept__id',flat=True)
|
||||
queryset = Dept.objects.filter(id__in=dept_ids).values('id', 'name', 'parent')
|
||||
return DetailResponse(data=queryset)
|
||||
@@ -58,8 +58,38 @@ class RoleMenuPermissionViewSet(CustomModelViewSet):
|
||||
update_serializer_class = RoleMenuPermissionCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
@action(methods=['get'],detail=False)
|
||||
def menu_permission_tree(self,request):
|
||||
"""
|
||||
获取菜单按钮树
|
||||
"""
|
||||
# params = request.query_params
|
||||
# role_id = params.get('role',None)
|
||||
# if role_id is None:
|
||||
# return ErrorResponse(msg="未获取到角色")
|
||||
if request.user.is_superuser:
|
||||
queryset = Menu.objects.filter(status=1).values("id", "name", "parent_id")
|
||||
else:
|
||||
role_id = request.user.role.values_list('id', flat=True)
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('menu_id', flat=True)
|
||||
queryset = Menu.objects.filter(status=1, id__in=menu_list).values('id','name', "parent_id").all()
|
||||
return DetailResponse(data=queryset)
|
||||
|
||||
@action(methods=['get'],detail=False)
|
||||
def get_menu_permission_checked(self,request):
|
||||
"""
|
||||
获取已授权的菜单ID
|
||||
"""
|
||||
params = request.query_params
|
||||
role_id = params.get('role',None)
|
||||
if role_id is None:
|
||||
return ErrorResponse(msg="未获取到角色")
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('menu_id', flat=True)
|
||||
queryset = Menu.objects.filter(status=1, id__in=menu_list).values_list('id',flat=True)
|
||||
return DetailResponse(data=queryset)
|
||||
|
||||
@action(methods=['post'],detail=False)
|
||||
def save_auth(self,request):
|
||||
def save_menu_permission(self,request):
|
||||
"""
|
||||
保存页面菜单授权
|
||||
:param request:
|
||||
|
||||
@@ -20,7 +20,7 @@ from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters.utils import get_model_field
|
||||
from rest_framework.filters import BaseFilterBackend
|
||||
|
||||
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission
|
||||
from dvadmin.system.models import Dept, ApiWhiteList, RoleApiPermission
|
||||
|
||||
|
||||
def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
|
||||
@@ -116,18 +116,17 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
||||
if _pk: # 判断是否是单例查询
|
||||
re_api = re.sub(_pk,'{id}', api)
|
||||
role_id_list = request.user.role.values_list('id', flat=True)
|
||||
role_permission_list=RoleMenuButtonPermission.objects.filter(
|
||||
role_permission_list=RoleApiPermission.objects.filter(
|
||||
role__in=role_id_list,
|
||||
role__status=1,
|
||||
menu_button__api=re_api,
|
||||
menu_button__method=method).values(
|
||||
api=re_api,
|
||||
method=method).values(
|
||||
'data_range',
|
||||
role_admin=F('role__admin')
|
||||
)
|
||||
dataScope_list = [] # 权限范围列表
|
||||
for ele in role_permission_list:
|
||||
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
|
||||
if ele.get("data_range") == 3 or ele.get("role_admin") == True:
|
||||
if ele.get("data_range") == 3:
|
||||
return queryset
|
||||
dataScope_list.append(ele.get("data_range"))
|
||||
dataScope_list = list(set(dataScope_list))
|
||||
|
||||
@@ -79,6 +79,5 @@ class CustomPagination(PageNumberPagination):
|
||||
('total', total),
|
||||
('is_next', is_next),
|
||||
('is_previous', is_previous),
|
||||
('data', data),
|
||||
('permission', self.request.permission_fields)
|
||||
('data', data)
|
||||
]))
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.contrib.auth.models import AnonymousUser
|
||||
from django.db.models import F
|
||||
from rest_framework.permissions import BasePermission
|
||||
|
||||
from dvadmin.system.models import ApiWhiteList, RoleMenuButtonPermission
|
||||
from dvadmin.system.models import ApiWhiteList, RoleApiPermission
|
||||
|
||||
|
||||
def ValidationApi(reqApi, validApi):
|
||||
@@ -74,18 +74,18 @@ class CustomPermission(BasePermission):
|
||||
methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH']
|
||||
method = methodList.index(method)
|
||||
# ***接口白名单***
|
||||
api_white_list = ApiWhiteList.objects.values(permission__api=F('url'), permission__method=F('method'))
|
||||
api_white_list = ApiWhiteList.objects.values('method',api=F('url'))
|
||||
api_white_list = [
|
||||
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('permission__method')) + '$' for item in api_white_list if item.get('permission__api')]
|
||||
str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('method')) + '$' for item in api_white_list if item.get('api')]
|
||||
# ********#
|
||||
if not hasattr(request.user, "role"):
|
||||
return False
|
||||
role_id_list = request.user.role.values_list('id',flat=True)
|
||||
userApiList = RoleMenuButtonPermission.objects.filter(role__in=role_id_list).values(permission__api=F('menu_button__api'), permission__method=F('menu_button__method')) # 获取当前用户的角色拥有的所有接口
|
||||
userApiList = RoleApiPermission.objects.filter(role__in=role_id_list).values('api','method') # 获取当前用户的角色拥有的所有接口
|
||||
ApiList = [
|
||||
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('permission__method')) + '$' for item in userApiList if item.get('permission__api')]
|
||||
str(item.get('api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('method')) + '$' for item in userApiList if item.get('api')]
|
||||
new_api_ist = api_white_list + ApiList
|
||||
new_api = api + ":" + str(method)
|
||||
for item in new_api_ist:
|
||||
|
||||
@@ -159,11 +159,14 @@ const initModeValueEcho = () => {
|
||||
// 处理 icon 类型,用于回显时,tab 高亮与初始化数据
|
||||
const initFontIconName = () => {
|
||||
let name = 'ali';
|
||||
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
|
||||
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
|
||||
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
|
||||
// 初始化 tab 高亮回显
|
||||
state.fontIconTabActive = name;
|
||||
if(props.modelValue){
|
||||
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
|
||||
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
|
||||
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
|
||||
// 初始化 tab 高亮回显
|
||||
state.fontIconTabActive = name;
|
||||
}
|
||||
|
||||
return name;
|
||||
};
|
||||
// 初始化数据
|
||||
|
||||
@@ -9,7 +9,7 @@ export const BtnPermissionStore = defineStore('BtnPermission', {
|
||||
actions: {
|
||||
async getBtnPermissionStore() {
|
||||
request({
|
||||
url: '/api/system/menu_button/menu_button_all_permission/',
|
||||
url: '/api/system/menu/menu_button_all_permission/',
|
||||
method: 'get',
|
||||
}).then((ret: {
|
||||
data: []
|
||||
|
||||
@@ -9,7 +9,7 @@ import { request } from '/@/utils/service';
|
||||
//扩展包
|
||||
import { FsExtendsEditor,FsExtendsUploader } from '@fast-crud/fast-extends';
|
||||
import '@fast-crud/fast-extends/dist/style.css';
|
||||
import { successMessage, successNotification } from '/@/utils/message';
|
||||
import { ElMessage } from "element-plus";
|
||||
export default {
|
||||
async install(app: any, options: any) {
|
||||
// 先安装ui
|
||||
@@ -18,9 +18,9 @@ export default {
|
||||
app.use(FastCrud, {
|
||||
//i18n, //i18n配置,可选,默认使用中文,具体用法请看demo里的 src/i18n/index.js 文件
|
||||
// 此处配置公共的dictRequest(字典请求)
|
||||
async dictRequest({ dict }: any) {
|
||||
async dictRequest({ url }: any) {
|
||||
//根据dict的url,异步返回一个字典数组
|
||||
return await request({ url: dict.url, params: dict.params || {} }).then((res:any)=>{
|
||||
return await request({ url: url, }).then((res:any)=>{
|
||||
return res.data
|
||||
});
|
||||
},
|
||||
@@ -41,14 +41,21 @@ export default {
|
||||
transformRes: ({ res }: any) => {
|
||||
//将pageRequest的返回数据,转换为fast-crud所需要的格式
|
||||
//return {records,currentPage,pageSize,total};
|
||||
return { records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total };
|
||||
if(res.page){
|
||||
return { records: res.data, currentPage: res.page, pageSize: res.limit, total: res.total };
|
||||
}else{
|
||||
return { records: res.data,currentPage: 1, pageSize: res.data.length, total: res.data.length };
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
form: {
|
||||
afterSubmit(ctx: any) {
|
||||
// 增加crud提示
|
||||
if (ctx.res.code == 2000) {
|
||||
successNotification(ctx.res.msg);
|
||||
afterSubmit(ctx:any ) {
|
||||
const {mode} = ctx
|
||||
if (mode === "add") {
|
||||
ElMessage.success({ message: "添加成功" });
|
||||
} else if (mode === "edit") {
|
||||
ElMessage.success({ message: "保存成功" });
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -100,10 +107,12 @@ export default {
|
||||
});
|
||||
},
|
||||
successHandle(ret) {
|
||||
console.log(111,ret)
|
||||
// 上传完成后的结果处理, 此处应返回格式为{url:xxx,key:xxx}
|
||||
return {
|
||||
url: getBaseURL() + ret.data.url,
|
||||
key: ret.data.id
|
||||
url: ret.data.url,
|
||||
key: ret.data.id,
|
||||
...ret.data
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { toRaw } from 'vue';
|
||||
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 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]
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@ export const scanAndInstallPlugins = (app: any) => {
|
||||
pluginNames.add(pluginsName);
|
||||
}
|
||||
pluginsAll = Array.from(pluginNames);
|
||||
console.log('已发现插件:', pluginsAll);
|
||||
console.table('已注册插件:', pluginsAll);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<el-drawer size="70%" v-model="drawer" direction="rtl" destroy-on-close :before-close="handleClose">
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
@@ -25,6 +23,7 @@ const handleClose = (done: () => void) => {
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
// catch error
|
||||
|
||||
@@ -5,12 +5,19 @@ export const apiPrefix = '/api/system/menu/';
|
||||
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
url: apiPrefix+"tree/",
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function GetChildren(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/getChildren/',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
}
|
||||
@@ -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" />;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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)" />
|
||||
{{ node.label }}
|
||||
</span>
|
||||
<span v-else class="text-center font-black text-red-700 font-normal"> <SvgIcon :name="node.data.icon" /> {{ 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>
|
||||
371
web/src/views/system/menu/crud.ts
Normal file
371
web/src/views/system/menu/crud.ts
Normal 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);
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,142 +1,41 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<el-row class="menu-el-row">
|
||||
<el-col :span="6">
|
||||
<div class="menu-box menu-left-box">
|
||||
<MenuTreeCom
|
||||
ref="menuTreeRef"
|
||||
:treeData="menuTreeData"
|
||||
@treeClick="handleTreeClick"
|
||||
@updateDept="handleUpdateMenu"
|
||||
@deleteDept="handleDeleteMenu"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
|
||||
<el-col :span="18">
|
||||
<div class="menu-box menu-right-box">
|
||||
<MenuButtonCom ref="menuButtonRef" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-drawer v-model="drawerVisible" title="菜单配置" direction="rtl" size="500px" :close-on-click-modal="false" :before-close="handleDrawerClose">
|
||||
<MenuFormCom
|
||||
v-if="drawerVisible"
|
||||
:initFormData="drawerFormData"
|
||||
:cacheData="menuTreeCacheData"
|
||||
:treeData="menuTreeData"
|
||||
@drawerClose="handleDrawerClose"
|
||||
/>
|
||||
</el-drawer>
|
||||
</fs-page>
|
||||
</fs-crud>
|
||||
<fs-form-wrapper ref="addChildrenRef" />
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="menuPages">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import MenuTreeCom from './components/MenuTreeCom/index.vue';
|
||||
import MenuButtonCom from './components/MenuButtonCom/index.vue';
|
||||
import MenuFormCom from './components/MenuFormCom/index.vue';
|
||||
import { GetList, DelObj } from './api';
|
||||
import { successNotification } from '/@/utils/message';
|
||||
import { APIResponseData, MenuTreeItemType } from './types';
|
||||
<script lang="ts" setup>
|
||||
import {ref, onMounted, reactive, computed} from "vue";
|
||||
import createCrudOptions from "./crud";
|
||||
import {useExpose, useCrud, useCompute} from "@fast-crud/fast-crud";
|
||||
import {AddObj} from "./api";
|
||||
import {ElMessage } from "element-plus"
|
||||
|
||||
let menuTreeData = ref([]);
|
||||
let menuTreeCacheData = ref<MenuTreeItemType[]>([]);
|
||||
let drawerVisible = ref(false);
|
||||
let drawerFormData = ref<Partial<MenuTreeItemType>>({});
|
||||
let menuTreeRef = ref<InstanceType<typeof MenuTreeCom> | null>(null);
|
||||
let menuButtonRef = ref<InstanceType<typeof MenuButtonCom> | null>(null);
|
||||
|
||||
const getData = () => {
|
||||
GetList({}).then((ret: APIResponseData) => {
|
||||
const responseData = ret.data;
|
||||
const result = XEUtils.toArrayTree(responseData, {
|
||||
parentKey: 'parent',
|
||||
children: 'children',
|
||||
strict: true,
|
||||
});
|
||||
menuTreeData.value = result;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 菜单的点击事件
|
||||
*/
|
||||
const handleTreeClick = (record: MenuTreeItemType) => {
|
||||
menuButtonRef.value?.handleRefreshTable(record);
|
||||
};
|
||||
// crud组件的ref
|
||||
const crudRef = ref();
|
||||
// crud 配置的ref
|
||||
const crudBinding = ref();
|
||||
// 暴露的方法
|
||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||
|
||||
/**
|
||||
* 部门的 新增 or 编辑 事件
|
||||
*/
|
||||
const handleUpdateMenu = (type: string, record?: MenuTreeItemType) => {
|
||||
if (type === 'update' && record) {
|
||||
const parentData = menuTreeRef.value?.treeRef?.currentNode.parent.data || {};
|
||||
menuTreeCacheData.value = [parentData];
|
||||
drawerFormData.value = record;
|
||||
}
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const handleDrawerClose = (type?: string) => {
|
||||
if (type === 'submit') {
|
||||
getData();
|
||||
}
|
||||
drawerVisible.value = false;
|
||||
drawerFormData.value = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* 部门的删除事件
|
||||
*/
|
||||
const handleDeleteMenu = (id: string, callback: Function) => {
|
||||
ElMessageBox.confirm('您确认删除该菜单项吗?', '温馨提示', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(async () => {
|
||||
const res: APIResponseData = await DelObj(id);
|
||||
callback();
|
||||
if (res?.code === 2000) {
|
||||
successNotification(res.msg as string);
|
||||
getData();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose });
|
||||
// 初始化crud配置
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
|
||||
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
|
||||
// 你可以调用此方法,重新初始化crud配置
|
||||
// resetCrudOptions(options)
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
getData();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-el-row {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.el-col {
|
||||
height: 100%;
|
||||
padding: 10px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-box {
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.menu-left-box {
|
||||
position: relative;
|
||||
border-radius: 0 8px 8px 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.menu-right-box {
|
||||
border-radius: 8px 0 0 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
57
web/src/views/system/role/components/ApiPermission/api.ts
Normal file
57
web/src/views/system/role/components/ApiPermission/api.ts
Normal 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',})
|
||||
})
|
||||
}
|
||||
211
web/src/views/system/role/components/ApiPermission/crud.tsx
Normal file
211
web/src/views/system/role/components/ApiPermission/crud.tsx
Normal 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
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
59
web/src/views/system/role/components/ApiPermission/index.vue
Normal file
59
web/src/views/system/role/components/ApiPermission/index.vue
Normal 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>
|
||||
65
web/src/views/system/role/components/FieldPermission/api.ts
Normal file
65
web/src/views/system/role/components/FieldPermission/api.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
208
web/src/views/system/role/components/FieldPermission/crud.tsx
Normal file
208
web/src/views/system/role/components/FieldPermission/crud.tsx
Normal 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);
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -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>
|
||||
@@ -1,18 +1,49 @@
|
||||
import { request } from "/@/utils/service";
|
||||
|
||||
import XEUtils from 'xe-utils';
|
||||
/**
|
||||
* 获取角色的授权列表
|
||||
* 获取菜单树
|
||||
* @param roleId
|
||||
* @param query
|
||||
*/
|
||||
export function getRolePremission(query:object) {
|
||||
export function getMenuPremissionTree(query:object) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/get_role_premission/',
|
||||
url: '/api/system/role_menu_permission/menu_permission_tree/',
|
||||
method: 'get',
|
||||
params:query
|
||||
}).then((res:any)=>{
|
||||
return XEUtils.toArrayTree(res.data,{ parentKey: 'parent_id', key: 'id', children: 'children',})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已授权的菜单
|
||||
* @param query
|
||||
*/
|
||||
export function getMenuPremissionChecked(query:object) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_permission/get_menu_permission_checked/',
|
||||
method: 'get',
|
||||
params:query
|
||||
}).then((res:any)=>{
|
||||
return res.data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存菜单权限
|
||||
*/
|
||||
export function saveMenuPremission(data:object) {
|
||||
return request({
|
||||
url:'/api/system/role_menu_permission/save_menu_permission/',
|
||||
method:'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***
|
||||
* 设置角色的权限
|
||||
* @param roleId
|
||||
212
web/src/views/system/role/components/MenuPermission/index.vue
Normal file
212
web/src/views/system/role/components/MenuPermission/index.vue
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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:{}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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'
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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[]
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" title="权限配置" direction="rtl" size="60%" :close-on-click-modal="false"
|
||||
<el-drawer v-model="drawerVisible" title="权限配置" direction="rtl" size="60%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
:before-close="handleDrawerClose">
|
||||
<template #header>
|
||||
<el-row>
|
||||
@@ -8,111 +10,32 @@
|
||||
<el-tag>{{ props.roleName }}</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" :offset="8">
|
||||
<div>
|
||||
<el-button size="small" type="primary" class="pc-save-btn" @click="handleSavePermission">保存菜单授权
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<div class="permission-com">
|
||||
<el-collapse v-model="collapseCurrent" @change="handleCollapseChange" accordion>
|
||||
<el-collapse-item v-for="(item,mIndex) in menuData" :key="mIndex" :name="mIndex">
|
||||
<template #title>
|
||||
<div @click.stop="null">
|
||||
<p class="pc-collapse-title">
|
||||
<el-checkbox v-model="item.isCheck">
|
||||
<span>{{ item.name }}</span>
|
||||
</el-checkbox>
|
||||
</p>
|
||||
<div v-show="!collapseCurrent.includes(mIndex)">
|
||||
<el-checkbox v-for="btn in item.btns" :key="btn.value" :label="btn.value" v-model="btn.isCheck">
|
||||
{{ btn.name }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="pc-collapse-main">
|
||||
<div class="pccm-item">
|
||||
<p>允许对这些数据有以下操作</p>
|
||||
<el-checkbox v-for="(btn,bIndex) in item.btns" :key="bIndex" v-model="btn.isCheck" :label="btn.value">
|
||||
<div class="btn-item">
|
||||
{{ btn.data_range!==null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
|
||||
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(item, btn.id)">
|
||||
<el-icon><Setting/></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="pccm-item">
|
||||
<p>对这些数据有以下字段权限</p>
|
||||
|
||||
<ul class="columns-list">
|
||||
<li class="columns-head">
|
||||
<div class="width-txt">
|
||||
<span>字段</span>
|
||||
</div>
|
||||
|
||||
<div v-for="(head,hIndex) in column.header" :key="hIndex" class="width-check">
|
||||
<el-checkbox :label="head.value" @change="handleColumnChange($event, item, head.value)">
|
||||
<span>{{head.label}}</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li v-for="(c_item, c_index) in item.columns" :key="c_index" class="columns-item">
|
||||
<div class="width-txt">{{ c_item.title }}</div>
|
||||
<div v-for="(col,cIndex) in column.header" :key="cIndex" class="width-check">
|
||||
<el-checkbox v-model="c_item[col.value]" class="ci-checkout"></el-checkbox>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="数据权限配置" width="400px" :close-on-click-modal="false"
|
||||
:before-close="handleDialogClose">
|
||||
<div class="pc-dialog">
|
||||
<el-select v-model="dataPermission" @change="handlePermissionRangeChange" class="dialog-select"
|
||||
placeholder="请选择">
|
||||
<el-option v-for="item in dataPermissionRange" :key="item.value" :label="item.label" :value="item.value"/>
|
||||
</el-select>
|
||||
<el-tree-select
|
||||
v-show="dataPermission === 4"
|
||||
node-key="id"
|
||||
v-model="customDataPermission"
|
||||
:props="defaultTreeProps"
|
||||
:data="deptData"
|
||||
multiple
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
show-checkbox
|
||||
class="dialog-tree"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div>
|
||||
<el-button type="primary" @click="handleDialogConfirm"> 确定</el-button>
|
||||
<el-button @click="handleDialogClose"> 取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<div>
|
||||
<el-tabs type="border-card" v-model="permissionTab">
|
||||
<el-tab-pane label="菜单/按钮授权" name="menu">
|
||||
<MenuPermission ref="menuPermissionRef" :role-id="props.roleId" @handleDrawerClose="handleDrawerClose"></MenuPermission>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="请求接口授权" name="api">
|
||||
<ApiPermission v-if="permissionTab==='api'" :role-id="props.roleId"></ApiPermission>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="表单字段授权" name="field">
|
||||
<FieldPermission v-if="permissionTab==='field'" :role-id="props.roleId"></FieldPermission>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, onMounted, defineProps, watch, computed, reactive} from 'vue';
|
||||
import {ref, onMounted, defineProps, watch, computed, reactive,nextTick} from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import {errorNotification} from '/@/utils/message';
|
||||
import {getDataPermissionRange, getDataPermissionDept, getRolePremission, setRolePremission,setBtnDatarange} from './api';
|
||||
import {MenuDataType, DataPermissionRangeType, CustomDataPermissionDeptType} from './types';
|
||||
import {ElMessage} from 'element-plus'
|
||||
|
||||
import MenuPermission from "./MenuPermission/index.vue";
|
||||
import ApiPermission from "./ApiPermission/index.vue";
|
||||
import FieldPermission from "./FieldPermission/index.vue";
|
||||
const props = defineProps({
|
||||
roleId: {
|
||||
type: Number,
|
||||
@@ -128,28 +51,27 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:drawerVisible'])
|
||||
|
||||
const menuPermissionRef = ref()
|
||||
const permissionTab = ref('menu')
|
||||
const drawerVisible = ref(false)
|
||||
watch(
|
||||
() => props.drawerVisible,
|
||||
(val) => {
|
||||
drawerVisible.value = val;
|
||||
getMenuBtnPermission()
|
||||
fetchData()
|
||||
nextTick(()=>{
|
||||
menuPermissionRef.value.getMenuPremissionTreeData()
|
||||
})
|
||||
// fetchData()
|
||||
}
|
||||
);
|
||||
const handleDrawerClose = () => {
|
||||
permissionTab.value ='menu'
|
||||
emit('update:drawerVisible', false);
|
||||
}
|
||||
|
||||
|
||||
const defaultTreeProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
};
|
||||
|
||||
let menuData = ref<MenuDataType[]>([]);
|
||||
|
||||
let collapseCurrent = ref(['1']);
|
||||
let menuCurrent = ref<Partial<MenuDataType>>({});
|
||||
let menuBtnCurrent = ref<number>(-1);
|
||||
@@ -164,10 +86,52 @@ const formatDataRange = computed(() => {
|
||||
let deptData = ref<CustomDataPermissionDeptType[]>([]);
|
||||
let dataPermission = ref();
|
||||
let customDataPermission = ref([]);
|
||||
//获取菜单,按钮,权限
|
||||
const getMenuBtnPermission = async () => {
|
||||
const resMenu = await getRolePremission({role: props.roleId})
|
||||
menuData.value = resMenu.data
|
||||
//获取菜单/按钮权限
|
||||
const permissionTreeRef = ref();
|
||||
let menuPermissionTreeData = ref<MenuDataType[]>([]);
|
||||
const getMenuPremissionTreeData = async () => {
|
||||
const resMenu = await getMenuPremissionTree({role: props.roleId})
|
||||
menuPermissionTreeData.value = resMenu
|
||||
nextTick(() => {
|
||||
updateChecked(props.roleId);
|
||||
});
|
||||
}
|
||||
|
||||
// 如果勾选节点中存在非叶子节点,tree组件会将其所有子节点全部勾选
|
||||
// 所以要找出所有叶子节点,仅勾选叶子节点,tree组件会将父节点同步勾选
|
||||
function getAllCheckedLeafNodeId(tree, checkedIds, temp) {
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const item = tree[i];
|
||||
if (item.children && item.children.length !== 0) {
|
||||
getAllCheckedLeafNodeId(item.children, checkedIds, temp);
|
||||
} else {
|
||||
if (checkedIds.indexOf(item.id) !== -1) {
|
||||
temp.push(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
async function updateChecked(roleId:string|number) {
|
||||
let checkedIds = await getMenuPremissionChecked({role: roleId});
|
||||
// 找出所有的叶子节点
|
||||
checkedIds = getAllCheckedLeafNodeId(menuPermissionTreeData.value, checkedIds, []);
|
||||
permissionTreeRef.value.setCheckedKeys(checkedIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新菜单权限
|
||||
*/
|
||||
async function updatePermission() {
|
||||
const roleId = props.roleId;
|
||||
const { checked, halfChecked } = permissionTreeRef.value.getChecked();
|
||||
const allChecked = [...checked, ...halfChecked];
|
||||
const menuIds = allChecked.filter(item=>item !== -1)
|
||||
await saveMenuPremission({role: roleId, menu: menuIds})
|
||||
handleDrawerClose();
|
||||
//await updateChecked(roleId);
|
||||
|
||||
ElMessage.success("授权成功");
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
@@ -56,35 +56,15 @@ export const createCrudOptions = function ({
|
||||
show: true,
|
||||
},
|
||||
edit: {
|
||||
show: hasPermissions('role:Update'),
|
||||
show: hasPermissions('role:update'),
|
||||
},
|
||||
remove: {
|
||||
show: hasPermissions('role:Delete'),
|
||||
show: hasPermissions('role:delete'),
|
||||
},
|
||||
/* custom: {
|
||||
type: 'primary',
|
||||
text: '权限配置',
|
||||
show: hasPermissions('role:Update'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '权限配置',
|
||||
},
|
||||
click: (context: any): void => {
|
||||
const { row } = context;
|
||||
// eslint-disable-next-line no-mixed-spaces-and-tabs
|
||||
rolePermission.value.drawer = true;
|
||||
rolePermission.value.editedRoleInfo = row;
|
||||
rolePermission.value.initGet();
|
||||
},
|
||||
}, */
|
||||
customNew: {
|
||||
type: 'primary',
|
||||
text: '权限配置',
|
||||
show: hasPermissions('role:Update'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '权限配置',
|
||||
},
|
||||
text: '授权',
|
||||
// show: hasPermissions('role:Update'),
|
||||
click: (context: any): void => {
|
||||
const { row } = context;
|
||||
handleDrawerOpen(row);
|
||||
@@ -183,44 +163,13 @@ export const createCrudOptions = function ({
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
title: '是否管理员',
|
||||
search: { show: false },
|
||||
type: 'dict-radio',
|
||||
dict: dict({
|
||||
data: [
|
||||
{
|
||||
label: '是',
|
||||
value: true,
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
label: '否',
|
||||
value: false,
|
||||
color: 'danger',
|
||||
},
|
||||
],
|
||||
}),
|
||||
column: {
|
||||
minWidth: 130,
|
||||
sortable: 'custom',
|
||||
show: columnPermission('admin', 'is_query'),
|
||||
},
|
||||
addForm: {
|
||||
show: columnPermission('admin', 'is_create'),
|
||||
},
|
||||
editForm: {
|
||||
show: columnPermission('admin', 'is_update'),
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '是否管理员必填' }],
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: '状态',
|
||||
search: { show: true },
|
||||
type: 'dict-radio',
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
@@ -243,10 +192,7 @@ export const createCrudOptions = function ({
|
||||
},
|
||||
editForm: {
|
||||
show: columnPermission('status', 'is_update'),
|
||||
},
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
}
|
||||
},
|
||||
update_datetime: {
|
||||
title: '更新时间',
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
</template>
|
||||
</fs-crud>
|
||||
|
||||
<permission ref="rolePermission"></permission>
|
||||
|
||||
<PermissionComNew v-model:drawerVisible="drawerVisible" :roleId="roleId" :roleName="roleName" @drawerClose="handleDrawerClose" />
|
||||
</fs-page>
|
||||
</template>
|
||||
@@ -19,7 +17,7 @@ import { GetPermission } from './api';
|
||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
import permission from './components/PermissionCom/index.vue';
|
||||
import PermissionComNew from './components/PermissionComNew/index.vue';
|
||||
import PermissionComNew from './components/index.vue';
|
||||
|
||||
let drawerVisible = ref(false);
|
||||
let roleId = ref(null);
|
||||
@@ -67,5 +65,4 @@ onMounted(async () => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
defineExpose(rolePermission);
|
||||
</script>
|
||||
|
||||
@@ -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:{}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user