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