Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
@@ -114,7 +114,7 @@ cd web
|
|||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
npm install yarn
|
npm install yarn
|
||||||
yarn install --registry=https://registry.npm.taobao.org
|
yarn install --registry=https://registry.npmmirror.com
|
||||||
|
|
||||||
# 启动服务
|
# 启动服务
|
||||||
yarn build
|
yarn build
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# This will make sure the app is always imported when
|
||||||
|
# Django starts so that shared_task will use this app.
|
||||||
|
from .celery import app as celery_app
|
||||||
|
|
||||||
|
__all__ = ('celery_app',)
|
||||||
@@ -15,7 +15,7 @@ else:
|
|||||||
from celery import Celery
|
from celery import Celery
|
||||||
|
|
||||||
app = Celery(f"application")
|
app = Celery(f"application")
|
||||||
app.config_from_object('django.conf:settings')
|
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||||
platforms.C_FORCE_ROOT = True
|
platforms.C_FORCE_ROOT = True
|
||||||
|
|
||||||
|
|||||||
1
backend/dvadmin/__init__.py
Normal file
1
backend/dvadmin/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
@@ -167,19 +167,13 @@
|
|||||||
"method": 0
|
"method": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "查询所有",
|
"name": "获取所有部门",
|
||||||
"value": "dept:SearchAll",
|
"value": "dept:SearchAll",
|
||||||
"api": "/api/system/dept/all_dept/",
|
"api": "/api/system/dept/all_dept/",
|
||||||
"method": 0
|
"method": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "懒加载查询所有",
|
"name": "部门顶部信息",
|
||||||
"value": "dept:LazySearchAll",
|
|
||||||
"api": "/api/system/dept/dept_lazy_tree/",
|
|
||||||
"method": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "头信息",
|
|
||||||
"value": "dept:HeaderInfo",
|
"value": "dept:HeaderInfo",
|
||||||
"api": "/api/system/dept/dept_info/",
|
"api": "/api/system/dept/dept_info/",
|
||||||
"method": 0
|
"method": 0
|
||||||
|
|||||||
12
backend/dvadmin/system/signals.py
Normal file
12
backend/dvadmin/system/signals.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.dispatch import Signal
|
||||||
|
# 初始化信号
|
||||||
|
pre_init_complete = Signal()
|
||||||
|
detail_init_complete = Signal()
|
||||||
|
post_init_complete = Signal()
|
||||||
|
# 租户初始化信号
|
||||||
|
pre_tenants_init_complete = Signal()
|
||||||
|
detail_tenants_init_complete = Signal()
|
||||||
|
post_tenants_init_complete = Signal()
|
||||||
|
post_tenants_all_init_complete = Signal()
|
||||||
|
# 租户创建完成信号
|
||||||
|
tenants_create_complete = Signal()
|
||||||
@@ -35,6 +35,7 @@ system_url.register(r'message_center', MessageCenterViewSet)
|
|||||||
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
|
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
|
||||||
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
||||||
system_url.register(r'column', MenuFieldViewSet)
|
system_url.register(r'column', MenuFieldViewSet)
|
||||||
|
system_url.register(r'login_log', LoginLogViewSet)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -44,8 +45,8 @@ urlpatterns = [
|
|||||||
path('system_config/get_association_table/', SystemConfigViewSet.as_view({'get': 'get_association_table'})),
|
path('system_config/get_association_table/', SystemConfigViewSet.as_view({'get': 'get_association_table'})),
|
||||||
path('system_config/get_table_data/<int:pk>/', SystemConfigViewSet.as_view({'get': 'get_table_data'})),
|
path('system_config/get_table_data/<int:pk>/', SystemConfigViewSet.as_view({'get': 'get_table_data'})),
|
||||||
path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})),
|
path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})),
|
||||||
path('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
|
# path('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
|
||||||
path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})),
|
# path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})),
|
||||||
path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})),
|
path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})),
|
||||||
path('clause/privacy.html', PrivacyView.as_view()),
|
path('clause/privacy.html', PrivacyView.as_view()),
|
||||||
path('clause/terms_service.html', TermsServiceView.as_view()),
|
path('clause/terms_service.html', TermsServiceView.as_view()),
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import pypinyin
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from dvadmin.system.models import Area
|
from dvadmin.system.models import Area
|
||||||
|
from dvadmin.utils.field_permission import FieldPermissionMixin
|
||||||
from dvadmin.utils.json_response import SuccessResponse
|
from dvadmin.utils.json_response import SuccessResponse
|
||||||
from dvadmin.utils.serializers import CustomModelSerializer
|
from dvadmin.utils.serializers import CustomModelSerializer
|
||||||
from dvadmin.utils.viewset import CustomModelViewSet
|
from dvadmin.utils.viewset import CustomModelViewSet
|
||||||
@@ -14,13 +16,21 @@ class AreaSerializer(CustomModelSerializer):
|
|||||||
"""
|
"""
|
||||||
pcode_count = serializers.SerializerMethodField(read_only=True)
|
pcode_count = serializers.SerializerMethodField(read_only=True)
|
||||||
hasChild = serializers.SerializerMethodField()
|
hasChild = serializers.SerializerMethodField()
|
||||||
|
pcode_info = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_pcode_info(self, instance):
|
||||||
|
pcode = Area.objects.filter(code=instance.pcode_id).values("name", "code")
|
||||||
|
return pcode
|
||||||
|
|
||||||
def get_pcode_count(self, instance: Area):
|
def get_pcode_count(self, instance: Area):
|
||||||
return Area.objects.filter(pcode=instance).count()
|
return Area.objects.filter(pcode=instance).count()
|
||||||
|
|
||||||
def get_hasChild(self, instance):
|
def get_hasChild(self, instance):
|
||||||
hasChild = Area.objects.filter(pcode=instance.code)
|
hasChild = Area.objects.filter(pcode=instance.code)
|
||||||
if hasChild:
|
if hasChild:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Area
|
model = Area
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
@@ -32,12 +42,24 @@ class AreaCreateUpdateSerializer(CustomModelSerializer):
|
|||||||
地区管理 创建/更新时的列化器
|
地区管理 创建/更新时的列化器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
pinyin = ''.join([''.join(i) for i in pypinyin.pinyin(data["name"], style=pypinyin.NORMAL)])
|
||||||
|
data["level"] = 1
|
||||||
|
data["pinyin"] = pinyin
|
||||||
|
data["initials"] = pinyin[0].upper() if pinyin else "#"
|
||||||
|
pcode = data["pcode"] if 'pcode' in data else None
|
||||||
|
if pcode:
|
||||||
|
pcode = Area.objects.get(pk=pcode)
|
||||||
|
data["pcode"] = pcode.code
|
||||||
|
data["level"] = pcode.level + 1
|
||||||
|
return super().to_internal_value(data)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Area
|
model = Area
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class AreaViewSet(CustomModelViewSet):
|
class AreaViewSet(CustomModelViewSet, FieldPermissionMixin):
|
||||||
"""
|
"""
|
||||||
地区管理接口
|
地区管理接口
|
||||||
list:查询
|
list:查询
|
||||||
@@ -48,21 +70,28 @@ class AreaViewSet(CustomModelViewSet):
|
|||||||
"""
|
"""
|
||||||
queryset = Area.objects.all()
|
queryset = Area.objects.all()
|
||||||
serializer_class = AreaSerializer
|
serializer_class = AreaSerializer
|
||||||
|
create_serializer_class = AreaCreateUpdateSerializer
|
||||||
|
update_serializer_class = AreaCreateUpdateSerializer
|
||||||
extra_filter_class = []
|
extra_filter_class = []
|
||||||
|
|
||||||
def get_queryset(self):
|
def list(self, request, *args, **kwargs):
|
||||||
self.request.query_params._mutable = True
|
self.request.query_params._mutable = True
|
||||||
params = self.request.query_params
|
params = self.request.query_params
|
||||||
pcode = params.get('pcode', None)
|
known_params = {'page', 'limit', 'pcode'}
|
||||||
page = params.get('page', None)
|
# 使用集合操作检查是否有未知参数
|
||||||
limit = params.get('limit', None)
|
other_params_exist = any(param not in known_params for param in params)
|
||||||
if page:
|
if other_params_exist:
|
||||||
del params['page']
|
|
||||||
if limit:
|
|
||||||
del params['limit']
|
|
||||||
if params and pcode:
|
|
||||||
queryset = self.queryset.filter(enable=True, pcode=pcode)
|
|
||||||
else:
|
|
||||||
queryset = self.queryset.filter(enable=True)
|
queryset = self.queryset.filter(enable=True)
|
||||||
return queryset
|
else:
|
||||||
|
pcode = params.get('pcode', None)
|
||||||
|
params['limit'] = 999
|
||||||
|
if params and pcode:
|
||||||
|
queryset = self.queryset.filter(enable=True, pcode=pcode)
|
||||||
|
else:
|
||||||
|
queryset = self.queryset.filter(enable=True, level=1)
|
||||||
|
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="获取成功")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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, RoleMenuButtonPermission, Users
|
||||||
|
from dvadmin.utils.filters import DataLevelPermissionsFilter
|
||||||
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
|
||||||
@@ -125,17 +126,6 @@ class DeptViewSet(CustomModelViewSet):
|
|||||||
return SuccessResponse(data=data)
|
return SuccessResponse(data=data)
|
||||||
|
|
||||||
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||||
def dept_lazy_tree(self, request, *args, **kwargs):
|
|
||||||
parent = self.request.query_params.get('parent')
|
|
||||||
is_superuser = request.user.is_superuser
|
|
||||||
if is_superuser:
|
|
||||||
queryset = Dept.objects.values('id', 'name', 'parent')
|
|
||||||
else:
|
|
||||||
queryset = Dept.objects.values('id', 'name', 'parent')
|
|
||||||
queryset = self.filter_queryset(queryset)
|
|
||||||
return DetailResponse(data=queryset, msg="获取成功")
|
|
||||||
|
|
||||||
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated], extra_filter_class=[])
|
|
||||||
def all_dept(self, request, *args, **kwargs):
|
def all_dept(self, request, *args, **kwargs):
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent')
|
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent')
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ class FileSerializer(CustomModelSerializer):
|
|||||||
url = serializers.SerializerMethodField(read_only=True)
|
url = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
def get_url(self, instance):
|
def get_url(self, instance):
|
||||||
# return 'media/' + str(instance.url)
|
base_url = f"{self.request.scheme}://{self.request.get_host()}/"
|
||||||
return instance.file_url or (f'media/{str(instance.url)}')
|
return base_url + (instance.file_url or (f'media/{str(instance.url)}'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FileList
|
model = FileList
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
|
|||||||
from captcha.views import CaptchaStore, captcha_image
|
from captcha.views import CaptchaStore, captcha_image
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
|
from django.db.models import Q
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
@@ -83,11 +84,18 @@ class LoginSerializer(TokenObtainPairSerializer):
|
|||||||
else:
|
else:
|
||||||
self.image_code and self.image_code.delete()
|
self.image_code and self.image_code.delete()
|
||||||
raise CustomValidationError("图片验证码错误")
|
raise CustomValidationError("图片验证码错误")
|
||||||
|
try:
|
||||||
user = Users.objects.get(username=attrs['username'])
|
user = Users.objects.get(
|
||||||
|
Q(username=attrs['username']) | Q(email=attrs['username']) | Q(mobile=attrs['username']))
|
||||||
|
except Users.DoesNotExist:
|
||||||
|
raise CustomValidationError("您登录的账号不存在")
|
||||||
|
except Users.MultipleObjectsReturned:
|
||||||
|
raise CustomValidationError("您登录的账号存在多个,请联系管理员检查登录账号唯一性")
|
||||||
if not user.is_active:
|
if not user.is_active:
|
||||||
raise CustomValidationError("账号已被锁定,联系管理员解锁")
|
raise CustomValidationError("账号已被锁定,联系管理员解锁")
|
||||||
try:
|
try:
|
||||||
|
# 必须重置用户名为username,否则使用邮箱手机号登录会提示密码错误
|
||||||
|
attrs['username'] = user.username
|
||||||
data = super().validate(attrs)
|
data = super().validate(attrs)
|
||||||
data["name"] = self.user.name
|
data["name"] = self.user.name
|
||||||
data["userId"] = self.user.id
|
data["userId"] = self.user.id
|
||||||
@@ -114,6 +122,7 @@ class LoginSerializer(TokenObtainPairSerializer):
|
|||||||
user.login_error_count += 1
|
user.login_error_count += 1
|
||||||
if user.login_error_count >= 5:
|
if user.login_error_count >= 5:
|
||||||
user.is_active = False
|
user.is_active = False
|
||||||
|
user.save()
|
||||||
raise CustomValidationError("账号已被锁定,联系管理员解锁")
|
raise CustomValidationError("账号已被锁定,联系管理员解锁")
|
||||||
user.save()
|
user.save()
|
||||||
count = 5 - user.login_error_count
|
count = 5 - user.login_error_count
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
@Remark: 按钮权限管理
|
@Remark: 按钮权限管理
|
||||||
"""
|
"""
|
||||||
from dvadmin.system.models import LoginLog
|
from dvadmin.system.models import LoginLog
|
||||||
|
from dvadmin.utils.field_permission import FieldPermissionMixin
|
||||||
from dvadmin.utils.serializers import CustomModelSerializer
|
from dvadmin.utils.serializers import CustomModelSerializer
|
||||||
from dvadmin.utils.viewset import CustomModelViewSet
|
from dvadmin.utils.viewset import CustomModelViewSet
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ class LoginLogSerializer(CustomModelSerializer):
|
|||||||
read_only_fields = ["id"]
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
class LoginLogViewSet(CustomModelViewSet):
|
class LoginLogViewSet(CustomModelViewSet, FieldPermissionMixin):
|
||||||
"""
|
"""
|
||||||
登录日志接口
|
登录日志接口
|
||||||
list:查询
|
list:查询
|
||||||
@@ -33,4 +34,4 @@ class LoginLogViewSet(CustomModelViewSet):
|
|||||||
"""
|
"""
|
||||||
queryset = LoginLog.objects.all()
|
queryset = LoginLog.objects.all()
|
||||||
serializer_class = LoginLogSerializer
|
serializer_class = LoginLogSerializer
|
||||||
extra_filter_class = []
|
# extra_filter_class = []
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ from dvadmin.utils.serializers import CustomModelSerializer
|
|||||||
from dvadmin.utils.viewset import CustomModelViewSet
|
from dvadmin.utils.viewset import CustomModelViewSet
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MenuButtonSerializer(CustomModelSerializer):
|
class MenuButtonSerializer(CustomModelSerializer):
|
||||||
"""
|
"""
|
||||||
菜单按钮-序列化器
|
菜单按钮-序列化器
|
||||||
@@ -92,16 +94,14 @@ class MenuButtonViewSet(CustomModelViewSet):
|
|||||||
"""
|
"""
|
||||||
menu_obj = Menu.objects.filter(id=request.data['menu']).first()
|
menu_obj = Menu.objects.filter(id=request.data['menu']).first()
|
||||||
result_list = [
|
result_list = [
|
||||||
{'menu': menu_obj.id, 'name': '新增', 'value': f'{menu_obj.component_name}:Create', 'api': f'/api{menu_obj.web_path}/',
|
{'menu': menu_obj.id, 'name': '新增', 'value': f'{menu_obj.component_name}:Create', 'api': f'/api/{menu_obj.component_name}/', 'method': 1},
|
||||||
'method': 1},
|
{'menu': menu_obj.id, 'name': '删除', 'value': f'{menu_obj.component_name}:Delete', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 3},
|
||||||
{'menu': menu_obj.id, 'name': '删除', 'value': f'{menu_obj.component_name}:Delete', 'api': f'/api{menu_obj.web_path}/{{id}}/',
|
{'menu': menu_obj.id, 'name': '编辑', 'value': f'{menu_obj.component_name}:Update', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 2},
|
||||||
'method': 3},
|
{'menu': menu_obj.id, 'name': '查询', 'value': f'{menu_obj.component_name}:Search', 'api': f'/api/{menu_obj.component_name}/', 'method': 0},
|
||||||
{'menu': menu_obj.id, 'name': '修改', 'value': f'{menu_obj.component_name}:Update', 'api': f'/api{menu_obj.web_path}/{{id}}/',
|
{'menu': menu_obj.id, 'name': '详情', 'value': f'{menu_obj.component_name}:Retrieve', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 0},
|
||||||
'method': 2},
|
{'menu': menu_obj.id, 'name': '复制', 'value': f'{menu_obj.component_name}:Copy', 'api': f'/api/{menu_obj.component_name}/', 'method': 1},
|
||||||
{'menu': menu_obj.id, 'name': '查询', 'value': f'{menu_obj.component_name}:Search', 'api': f'/api{menu_obj.web_path}/',
|
{'menu': menu_obj.id, 'name': '导入', 'value': f'{menu_obj.component_name}:Import', 'api': f'/api/{menu_obj.component_name}/import_data/', 'method': 1},
|
||||||
'method': 0},
|
{'menu': menu_obj.id, 'name': '导出', 'value': f'{menu_obj.component_name}:Export', 'api': f'/api{menu_obj.component_name}/export_data/', 'method': 1},]
|
||||||
{'menu': menu_obj.id, 'name': '详情', 'value': f'{menu_obj.component_name}:Retrieve', 'api': f'/api{menu_obj.web_path}/{{id}}/',
|
|
||||||
'method': 0}]
|
|
||||||
serializer = self.get_serializer(data=result_list, many=True)
|
serializer = self.get_serializer(data=result_list, many=True)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
serializer.save()
|
serializer.save()
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class MessageCenterSerializer(CustomModelSerializer):
|
|||||||
return serializer.data
|
return serializer.data
|
||||||
|
|
||||||
def get_user_info(self, instance, parsed_query):
|
def get_user_info(self, instance, parsed_query):
|
||||||
|
if instance.target_type in (1,2,3):
|
||||||
|
return []
|
||||||
users = instance.target_user.all()
|
users = instance.target_user.all()
|
||||||
# You can do what ever you want in here
|
# You can do what ever you want in here
|
||||||
# `parsed_query` param is passed to BookSerializer to allow further querying
|
# `parsed_query` param is passed to BookSerializer to allow further querying
|
||||||
@@ -80,6 +82,9 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
|
|||||||
"""
|
"""
|
||||||
目标用户序列化器-序列化器
|
目标用户序列化器-序列化器
|
||||||
"""
|
"""
|
||||||
|
role_info = DynamicSerializerMethodField()
|
||||||
|
user_info = DynamicSerializerMethodField()
|
||||||
|
dept_info = DynamicSerializerMethodField()
|
||||||
is_read = serializers.SerializerMethodField()
|
is_read = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_is_read(self, instance):
|
def get_is_read(self, instance):
|
||||||
@@ -90,6 +95,44 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
|
|||||||
return queryset.is_read
|
return queryset.is_read
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_role_info(self, instance, parsed_query):
|
||||||
|
roles = instance.target_role.all()
|
||||||
|
# You can do what ever you want in here
|
||||||
|
# `parsed_query` param is passed to BookSerializer to allow further querying
|
||||||
|
from dvadmin.system.views.role import RoleSerializer
|
||||||
|
serializer = RoleSerializer(
|
||||||
|
roles,
|
||||||
|
many=True,
|
||||||
|
parsed_query=parsed_query
|
||||||
|
)
|
||||||
|
return serializer.data
|
||||||
|
|
||||||
|
def get_user_info(self, instance, parsed_query):
|
||||||
|
if instance.target_type in (1,2,3):
|
||||||
|
return []
|
||||||
|
users = instance.target_user.all()
|
||||||
|
# You can do what ever you want in here
|
||||||
|
# `parsed_query` param is passed to BookSerializer to allow further querying
|
||||||
|
from dvadmin.system.views.user import UserSerializer
|
||||||
|
serializer = UserSerializer(
|
||||||
|
users,
|
||||||
|
many=True,
|
||||||
|
parsed_query=parsed_query
|
||||||
|
)
|
||||||
|
return serializer.data
|
||||||
|
|
||||||
|
def get_dept_info(self, instance, parsed_query):
|
||||||
|
dept = instance.target_dept.all()
|
||||||
|
# You can do what ever you want in here
|
||||||
|
# `parsed_query` param is passed to BookSerializer to allow further querying
|
||||||
|
from dvadmin.system.views.dept import DeptSerializer
|
||||||
|
serializer = DeptSerializer(
|
||||||
|
dept,
|
||||||
|
many=True,
|
||||||
|
parsed_query=parsed_query
|
||||||
|
)
|
||||||
|
return serializer.data
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MessageCenter
|
model = MessageCenter
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
@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, BooleanField, Q, Case, Value, When
|
||||||
|
from django.db.models.functions import Coalesce
|
||||||
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.fields import ListField
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, FieldPermission, \
|
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, FieldPermission, \
|
||||||
@@ -172,59 +174,103 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
|||||||
update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
|
update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
|
||||||
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
|
||||||
|
# is_superuser = request.user.is_superuser
|
||||||
|
# if is_superuser:
|
||||||
|
# queryset = Menu.objects.filter(status=1, is_catalog=True).values('name', 'id').all()
|
||||||
|
# else:
|
||||||
|
# role_id = request.user.role.values_list('id', flat=True)
|
||||||
|
# menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('menu__id', flat=True)
|
||||||
|
# queryset = Menu.objects.filter(status=1, is_catalog=True, id__in=menu_list).values('name', 'id').all()
|
||||||
|
# serializer = RoleMenuSerializer(queryset, many=True, request=request)
|
||||||
|
# data = serializer.data
|
||||||
|
# return DetailResponse(data=data)
|
||||||
|
|
||||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||||
def get_role_premission(self, request):
|
def get_role_permission(self, request):
|
||||||
"""
|
|
||||||
角色授权获取:
|
|
||||||
:param request: role
|
|
||||||
:return: menu,btns,columns
|
|
||||||
"""
|
|
||||||
params = request.query_params
|
params = request.query_params
|
||||||
role = params.get('role', None)
|
# 需要授权的角色信息
|
||||||
if role is None:
|
current_role = params.get('role', None)
|
||||||
return ErrorResponse(msg="未获取到角色信息")
|
# 当前登录用户的角色
|
||||||
|
role_list = request.user.role.values_list('id', flat=True)
|
||||||
|
if current_role is None:
|
||||||
|
return ErrorResponse(msg='参数错误')
|
||||||
is_superuser = request.user.is_superuser
|
is_superuser = request.user.is_superuser
|
||||||
if is_superuser:
|
if is_superuser:
|
||||||
queryset = Menu.objects.filter(status=1, is_catalog=True).values('name', 'id').all()
|
menu_queryset = Menu.objects.prefetch_related('menuPermission').prefetch_related(
|
||||||
|
'menufield_set')
|
||||||
else:
|
else:
|
||||||
role_id = request.user.role.values_list('id', flat=True)
|
role_id_list = request.user.role.values_list('id', flat=True)
|
||||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id', flat=True)
|
menu_list = RoleMenuPermission.objects.filter(role__in=role_id_list).values_list('menu__id', flat=True)
|
||||||
queryset = Menu.objects.filter(status=1, is_catalog=True, id__in=menu_list).values('name', 'id').all()
|
|
||||||
serializer = RoleMenuSerializer(queryset, many=True, request=request)
|
# 当前角色已授权的菜单
|
||||||
data = serializer.data
|
menu_queryset = Menu.objects.filter(id__in=menu_list).prefetch_related('menuPermission').prefetch_related(
|
||||||
return DetailResponse(data=data)
|
'menufield_set')
|
||||||
# data = []
|
result = []
|
||||||
# if is_superuser:
|
for menu_item in menu_queryset:
|
||||||
# queryset = Menu.objects.filter(status=1, is_catalog=False).values('name', 'id').all()
|
isCheck = RoleMenuPermission.objects.filter(
|
||||||
# else:
|
menu_id=menu_item.id,
|
||||||
# role_id = request.user.role.values_list('id', flat=True)
|
role_id=current_role
|
||||||
# menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id', flat=True)
|
).exists()
|
||||||
# queryset = Menu.objects.filter(status=1, is_catalog=False, id__in=menu_list).values('name', 'id')
|
dicts = {
|
||||||
# for item in queryset:
|
'name': menu_item.name,
|
||||||
# parent_list = Menu.get_all_parent(item['id'])
|
'id': menu_item.id,
|
||||||
# names = [d["name"] for d in parent_list]
|
'parent': menu_item.parent.id if menu_item.parent else None,
|
||||||
# completeName = "/".join(names)
|
'isCheck': isCheck,
|
||||||
# isCheck = RoleMenuPermission.objects.filter(
|
'btns': [],
|
||||||
# menu__id=item['id'],
|
'columns': []
|
||||||
# role__id=role,
|
}
|
||||||
# ).exists()
|
for mb_item in menu_item.menuPermission.all():
|
||||||
# mbCheck = RoleMenuButtonPermission.objects.filter(
|
rolemenubuttonpermission_queryset = RoleMenuButtonPermission.objects.filter(
|
||||||
# menu_button=OuterRef("pk"),
|
menu_button_id=mb_item.id,
|
||||||
# role__id=role,
|
role_id=current_role
|
||||||
# )
|
).first()
|
||||||
# btns = MenuButton.objects.filter(
|
dicts['btns'].append(
|
||||||
# menu__id=item['id'],
|
{
|
||||||
# ).annotate(isCheck=Exists(mbCheck)).values('id', 'name', 'value', 'isCheck',
|
'id': mb_item.id,
|
||||||
# data_range=F('menu_button_permission__data_range'))
|
'name': mb_item.name,
|
||||||
# dicts = {
|
'value': mb_item.value,
|
||||||
# 'name': completeName,
|
'data_range': rolemenubuttonpermission_queryset.data_range
|
||||||
# 'id': item['id'],
|
if rolemenubuttonpermission_queryset
|
||||||
# 'isCheck': isCheck,
|
else None,
|
||||||
# 'btns': btns,
|
'isCheck': bool(rolemenubuttonpermission_queryset),
|
||||||
#
|
'dept': rolemenubuttonpermission_queryset.dept.all().values_list('id', flat=True)
|
||||||
# }
|
if rolemenubuttonpermission_queryset
|
||||||
# data.append(dicts)
|
else [],
|
||||||
# return DetailResponse(data=data)
|
}
|
||||||
|
)
|
||||||
|
for column_item in menu_item.menufield_set.all():
|
||||||
|
# 需要授权角色已拥有的列权限
|
||||||
|
fieldpermission_queryset = column_item.menu_field.filter(role_id=current_role).first()
|
||||||
|
is_query = fieldpermission_queryset.is_query if fieldpermission_queryset else False
|
||||||
|
is_create = fieldpermission_queryset.is_create if fieldpermission_queryset else False
|
||||||
|
is_update = fieldpermission_queryset.is_update if fieldpermission_queryset else False
|
||||||
|
# 当前登录用户角色可分配的列权限
|
||||||
|
fieldpermission_queryset_disabled = column_item.menu_field.filter(role_id__in=role_list).first()
|
||||||
|
disabled_query = fieldpermission_queryset_disabled.is_query if fieldpermission_queryset_disabled else True
|
||||||
|
disabled_create = fieldpermission_queryset_disabled.is_create if fieldpermission_queryset_disabled else True
|
||||||
|
disabled_update = fieldpermission_queryset_disabled.is_update if fieldpermission_queryset_disabled else True
|
||||||
|
|
||||||
|
dicts['columns'].append({
|
||||||
|
'id': column_item.id,
|
||||||
|
'field_name': column_item.field_name,
|
||||||
|
'title': column_item.title,
|
||||||
|
'is_query': is_query,
|
||||||
|
'is_create': is_create,
|
||||||
|
'is_update': is_update,
|
||||||
|
'disabled_query': False if is_superuser else not disabled_query,
|
||||||
|
'disabled_create': False if is_superuser else not disabled_create,
|
||||||
|
'disabled_update': False if is_superuser else not disabled_update,
|
||||||
|
})
|
||||||
|
result.append(dicts)
|
||||||
|
return DetailResponse(data=result)
|
||||||
|
|
||||||
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
|
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
|
||||||
def set_role_premission(self, request, pk):
|
def set_role_premission(self, request, pk):
|
||||||
@@ -238,27 +284,21 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
|||||||
RoleMenuPermission.objects.filter(role=pk).delete()
|
RoleMenuPermission.objects.filter(role=pk).delete()
|
||||||
RoleMenuButtonPermission.objects.filter(role=pk).delete()
|
RoleMenuButtonPermission.objects.filter(role=pk).delete()
|
||||||
for item in body:
|
for item in body:
|
||||||
for menu in item["menus"]:
|
if item.get('isCheck'):
|
||||||
if menu.get('isCheck'):
|
RoleMenuPermission.objects.create(role_id=pk, menu_id=item["id"])
|
||||||
menu_parent = Menu.get_all_parent(menu.get('id'))
|
for btn in item.get('btns'):
|
||||||
role_menu_permission_list = []
|
if btn.get('isCheck'):
|
||||||
for d in menu_parent:
|
data_range = btn.get('data_range', 0) or 0
|
||||||
role_menu_permission_list.append(RoleMenuPermission(role_id=pk, menu_id=d["id"]))
|
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),
|
||||||
RoleMenuPermission.objects.bulk_create(role_menu_permission_list)
|
data_range=data_range)
|
||||||
# RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
|
instance.dept.set(btn.get('dept', []))
|
||||||
for btn in menu.get('btns'):
|
for col in item.get('columns'):
|
||||||
if btn.get('isCheck'):
|
FieldPermission.objects.update_or_create(role_id=pk, field_id=col.get('id'),
|
||||||
data_range = btn.get('data_range', 0) or 0
|
defaults={
|
||||||
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),
|
'is_query': col.get('is_query'),
|
||||||
data_range=data_range)
|
'is_create': col.get('is_create'),
|
||||||
instance.dept.set(btn.get('dept', []))
|
'is_update': col.get('is_update')
|
||||||
for col in menu.get('columns'):
|
})
|
||||||
FieldPermission.objects.update_or_create(role_id=pk, field_id=col.get('id'),
|
|
||||||
defaults={
|
|
||||||
'is_query': col.get('is_query'),
|
|
||||||
'is_create': col.get('is_create'),
|
|
||||||
'is_update': col.get('is_update')
|
|
||||||
})
|
|
||||||
return DetailResponse(msg="授权成功")
|
return DetailResponse(msg="授权成功")
|
||||||
|
|
||||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||||
@@ -291,86 +331,45 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
|||||||
is_superuser = request.user.is_superuser
|
is_superuser = request.user.is_superuser
|
||||||
if is_superuser:
|
if is_superuser:
|
||||||
data = [
|
data = [
|
||||||
{
|
{"value": 0, "label": '仅本人数据权限'},
|
||||||
"value": 0,
|
{"value": 1, "label": '本部门及以下数据权限'},
|
||||||
"label": '仅本人数据权限'
|
{"value": 2, "label": '本部门数据权限'},
|
||||||
},
|
{"value": 3, "label": '全部数据权限'},
|
||||||
{
|
{"value": 4, "label": '自定义数据权限'}
|
||||||
"value": 1,
|
|
||||||
"label": '本部门及以下数据权限'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 2,
|
|
||||||
"label": '本部门数据权限'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 3,
|
|
||||||
"label": '全部数据权限'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 4,
|
|
||||||
"label": '自定义数据权限'
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
return DetailResponse(data=data)
|
return DetailResponse(data=data)
|
||||||
else:
|
else:
|
||||||
data = []
|
params = request.query_params
|
||||||
|
data = [{"value": 0, "label": '仅本人数据权限'}]
|
||||||
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 menu_button_id := params.get('menu_button', None):
|
role_queryset = RoleMenuButtonPermission.objects.filter(
|
||||||
role_queryset = RoleMenuButtonPermission.objects.filter(
|
role__in=role_list
|
||||||
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))
|
if menu_button_id := params.get('menu_button', None):
|
||||||
for item in data_range_list:
|
role_queryset = RoleMenuButtonPermission.objects.filter(
|
||||||
if item == 0:
|
role__in=role_list, menu_button__id=menu_button_id
|
||||||
data = [{
|
).values_list('data_range', flat=True)
|
||||||
"value": 0,
|
|
||||||
"label": '仅本人数据权限'
|
data_range_list = list(set(role_queryset))
|
||||||
}]
|
for item in data_range_list:
|
||||||
elif item == 1:
|
if item == 0:
|
||||||
data = [{
|
data = data
|
||||||
"value": 0,
|
elif item == 1:
|
||||||
"label": '仅本人数据权限'
|
data.extend([
|
||||||
}, {
|
{"value": 1, "label": '本部门及以下数据权限'},
|
||||||
"value": 1,
|
{"value": 2, "label": '本部门数据权限'}
|
||||||
"label": '本部门及以下数据权限'
|
])
|
||||||
},
|
elif item == 2:
|
||||||
{
|
data.extend([{"value": 2, "label": '本部门数据权限'}])
|
||||||
"value": 2,
|
elif item == 3:
|
||||||
"label": '本部门数据权限'
|
data.extend([{"value": 3, "label": '全部数据权限'}])
|
||||||
}]
|
elif item == 4:
|
||||||
elif item == 2:
|
data.extend([{"value": 4, "label": '自定义数据权限'}])
|
||||||
data = [{
|
else:
|
||||||
"value": 0,
|
data = []
|
||||||
"label": '仅本人数据权限'
|
return DetailResponse(data=data)
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 2,
|
|
||||||
"label": '本部门数据权限'
|
|
||||||
}]
|
|
||||||
elif item == 3:
|
|
||||||
data = [{
|
|
||||||
"value": 0,
|
|
||||||
"label": '仅本人数据权限'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 3,
|
|
||||||
"label": '全部数据权限'
|
|
||||||
}, ]
|
|
||||||
elif item == 4:
|
|
||||||
data = [{
|
|
||||||
"value": 0,
|
|
||||||
"label": '仅本人数据权限'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": 4,
|
|
||||||
"label": '自定义数据权限'
|
|
||||||
}]
|
|
||||||
else:
|
|
||||||
data = []
|
|
||||||
return DetailResponse(data=data)
|
|
||||||
return ErrorResponse(msg="参数错误")
|
|
||||||
|
|
||||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||||
def role_to_dept_all(self, request):
|
def role_to_dept_all(self, request):
|
||||||
@@ -379,23 +378,23 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
|||||||
:param request:
|
:param request:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
params = request.query_params
|
|
||||||
is_superuser = request.user.is_superuser
|
is_superuser = request.user.is_superuser
|
||||||
if is_superuser:
|
params = request.query_params
|
||||||
queryset = Dept.objects.values('id', 'name', 'parent')
|
# 当前登录用户的角色
|
||||||
else:
|
role_list = request.user.role.values_list('id', flat=True)
|
||||||
if not params:
|
|
||||||
return ErrorResponse(msg="参数错误")
|
menu_button_id = params.get('menu_button')
|
||||||
menu_button = params.get('menu_button')
|
# 当前登录用户角色可以分配的自定义部门权限
|
||||||
if menu_button is None:
|
dept_checked_disabled = RoleMenuButtonPermission.objects.filter(
|
||||||
return ErrorResponse(msg="参数错误")
|
role_id__in=role_list, menu_button_id=menu_button_id
|
||||||
role_list = request.user.role.values_list('id', flat=True)
|
).values_list('dept', flat=True)
|
||||||
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list, menu_button=None).values(
|
dept_list = Dept.objects.values('id', 'name', 'parent')
|
||||||
dept_id=F('dept__id'),
|
|
||||||
name=F('dept__name'),
|
data = []
|
||||||
parent=F('dept__parent')
|
for dept in dept_list:
|
||||||
)
|
dept["disabled"] = False if is_superuser else dept["id"] not in dept_checked_disabled
|
||||||
return DetailResponse(data=queryset)
|
data.append(dept)
|
||||||
|
return DetailResponse(data=data)
|
||||||
|
|
||||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||||
def menu_to_button(self, request):
|
def menu_to_button(self, request):
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ class UserUpdateSerializer(CustomModelSerializer):
|
|||||||
"""
|
"""
|
||||||
更改激活状态
|
更改激活状态
|
||||||
"""
|
"""
|
||||||
print(111, value)
|
|
||||||
if value:
|
if value:
|
||||||
self.initial_data["login_error_count"] = 0
|
self.initial_data["login_error_count"] = 0
|
||||||
return value
|
return value
|
||||||
@@ -331,6 +330,10 @@ class UserViewSet(CustomModelViewSet):
|
|||||||
if not verify_password:
|
if not verify_password:
|
||||||
old_pwd_md5 = hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest()
|
old_pwd_md5 = hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest()
|
||||||
verify_password = check_password(str(old_pwd_md5), request.user.password)
|
verify_password = check_password(str(old_pwd_md5), request.user.password)
|
||||||
|
# 创建用户时、自定义密码无法修改问题
|
||||||
|
if not verify_password:
|
||||||
|
old_pwd_md5 = hashlib.md5(old_pwd_md5.encode(encoding='UTF-8')).hexdigest()
|
||||||
|
verify_password = check_password(str(old_pwd_md5), request.user.password)
|
||||||
if verify_password:
|
if verify_password:
|
||||||
request.user.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest())
|
request.user.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest())
|
||||||
request.user.save()
|
request.user.save()
|
||||||
@@ -407,11 +410,11 @@ class UserViewSet(CustomModelViewSet):
|
|||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
else:
|
else:
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
print(queryset.values('id','name','dept__id'))
|
# print(queryset.values('id','name','dept__id'))
|
||||||
page = self.paginate_queryset(queryset)
|
page = self.paginate_queryset(queryset)
|
||||||
if page is not None:
|
if page is not None:
|
||||||
serializer = self.get_serializer(page, many=True, request=request)
|
serializer = self.get_serializer(page, many=True, request=request)
|
||||||
print(serializer.data)
|
# print(serializer.data)
|
||||||
return self.get_paginated_response(serializer.data)
|
return self.get_paginated_response(serializer.data)
|
||||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from itertools import groupby
|
||||||
|
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
@@ -35,4 +38,34 @@ class FieldPermissionMixin:
|
|||||||
data= FieldPermission.objects.filter(
|
data= FieldPermission.objects.filter(
|
||||||
field__model=model['model'],role__in=roles
|
field__model=model['model'],role__in=roles
|
||||||
).values( 'is_create', 'is_query', 'is_update',field_name=F('field__field_name'))
|
).values( 'is_create', 'is_query', 'is_update',field_name=F('field__field_name'))
|
||||||
|
|
||||||
|
"""
|
||||||
|
合并权限
|
||||||
|
|
||||||
|
这段代码首先根据 field_name 对列表进行排序,
|
||||||
|
然后使用 groupby 按 field_name 进行分组。
|
||||||
|
对于每个组,它创建一个新的字典 merged,
|
||||||
|
并遍历组中的每个字典,将布尔值字段使用逻辑或(or)操作符进行合并(如果 merged 中还没有该字段,则默认为 False),
|
||||||
|
其他字段(如 field_name)则直接取组的关键字(即 key)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 使用field_name对列表进行分组, # groupby 需要先对列表进行排序,因为它只能对连续相同的元素进行分组。
|
||||||
|
grouped = groupby(sorted(list(data), key=lambda x: x['field_name']), key=lambda x: x['field_name'])
|
||||||
|
|
||||||
|
data = []
|
||||||
|
|
||||||
|
# 遍历分组,合并权限
|
||||||
|
for key, group in grouped:
|
||||||
|
|
||||||
|
# 初始化一个空字典来存储合并后的结果
|
||||||
|
merged = {}
|
||||||
|
for item in group:
|
||||||
|
# 合并权限, True值优先
|
||||||
|
merged['is_create'] = merged.get('is_create', False) or item['is_create']
|
||||||
|
merged['is_query'] = merged.get('is_query', False) or item['is_query']
|
||||||
|
merged['is_update'] = merged.get('is_update', False) or item['is_update']
|
||||||
|
merged['field_name'] = key
|
||||||
|
|
||||||
|
data.append(merged)
|
||||||
|
|
||||||
return DetailResponse(data=data)
|
return DetailResponse(data=data)
|
||||||
@@ -37,11 +37,11 @@ class CoreModelFilterBankend(BaseFilterBackend):
|
|||||||
if any([create_datetime_after, create_datetime_before, update_datetime_after, update_datetime_before]):
|
if any([create_datetime_after, create_datetime_before, update_datetime_after, update_datetime_before]):
|
||||||
create_filter = Q()
|
create_filter = Q()
|
||||||
if create_datetime_after and create_datetime_before:
|
if create_datetime_after and create_datetime_before:
|
||||||
create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=create_datetime_before)
|
create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=f'{create_datetime_before} 23:59:59')
|
||||||
elif create_datetime_after:
|
elif create_datetime_after:
|
||||||
create_filter &= Q(create_datetime__gte=create_datetime_after)
|
create_filter &= Q(create_datetime__gte=create_datetime_after)
|
||||||
elif create_datetime_before:
|
elif create_datetime_before:
|
||||||
create_filter &= Q(create_datetime__lte=create_datetime_before)
|
create_filter &= Q(create_datetime__lte=f'{create_datetime_before} 23:59:59')
|
||||||
|
|
||||||
# 更新时间范围过滤条件
|
# 更新时间范围过滤条件
|
||||||
update_filter = Q()
|
update_filter = Q()
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ class ApiLoggingMiddleware(MiddlewareMixin):
|
|||||||
request.request_path = get_request_path(request)
|
request.request_path = get_request_path(request)
|
||||||
|
|
||||||
def __handle_response(self, request, response):
|
def __handle_response(self, request, response):
|
||||||
|
|
||||||
|
# 判断有无log_id属性,使用All记录时,会出现此情况
|
||||||
|
if request.request_data.get('log_id', None) is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 移除log_id,不记录此ID
|
||||||
|
log_id = request.request_data.pop('log_id')
|
||||||
|
|
||||||
# request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性
|
# request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性
|
||||||
body = getattr(request, 'request_data', {})
|
body = getattr(request, 'request_data', {})
|
||||||
# 请求含有password则用*替换掉(暂时先用于所有接口的password请求参数)
|
# 请求含有password则用*替换掉(暂时先用于所有接口的password请求参数)
|
||||||
@@ -60,7 +68,7 @@ class ApiLoggingMiddleware(MiddlewareMixin):
|
|||||||
'status': True if response.data.get('code') in [2000, ] else False,
|
'status': True if response.data.get('code') in [2000, ] else False,
|
||||||
'json_result': {"code": response.data.get('code'), "msg": response.data.get('msg')},
|
'json_result': {"code": response.data.get('code'), "msg": response.data.get('msg')},
|
||||||
}
|
}
|
||||||
operation_log, creat = OperationLog.objects.update_or_create(defaults=info, id=self.operation_log_id)
|
operation_log, creat = OperationLog.objects.update_or_create(defaults=info, id=log_id)
|
||||||
if not operation_log.request_modular and settings.API_MODEL_MAP.get(request.request_path, None):
|
if not operation_log.request_modular and settings.API_MODEL_MAP.get(request.request_path, None):
|
||||||
operation_log.request_modular = settings.API_MODEL_MAP[request.request_path]
|
operation_log.request_modular = settings.API_MODEL_MAP[request.request_path]
|
||||||
operation_log.save()
|
operation_log.save()
|
||||||
@@ -71,7 +79,8 @@ class ApiLoggingMiddleware(MiddlewareMixin):
|
|||||||
if self.methods == 'ALL' or request.method in self.methods:
|
if self.methods == 'ALL' or request.method in self.methods:
|
||||||
log = OperationLog(request_modular=get_verbose_name(view_func.cls.queryset))
|
log = OperationLog(request_modular=get_verbose_name(view_func.cls.queryset))
|
||||||
log.save()
|
log.save()
|
||||||
self.operation_log_id = log.id
|
# self.operation_log_id = log.id
|
||||||
|
request.request_data['log_id'] = log.id
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,14 @@
|
|||||||
@Created on: 2021/5/31 031 22:08
|
@Created on: 2021/5/31 031 22:08
|
||||||
@Remark: 公共基础model类
|
@Remark: 公共基础model类
|
||||||
"""
|
"""
|
||||||
|
from datetime import datetime
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.apps import apps
|
|
||||||
from django.db import models
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from application import settings
|
from application import settings
|
||||||
|
from django.apps import apps
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import models
|
||||||
|
from rest_framework.request import Request
|
||||||
|
|
||||||
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
||||||
|
|
||||||
@@ -60,8 +61,24 @@ class SoftDeleteModel(models.Model):
|
|||||||
"""
|
"""
|
||||||
重写删除方法,直接开启软删除
|
重写删除方法,直接开启软删除
|
||||||
"""
|
"""
|
||||||
self.is_deleted = True
|
if soft_delete:
|
||||||
self.save(using=using)
|
self.is_deleted = True
|
||||||
|
self.save(using=using)
|
||||||
|
# 级联软删除关联对象
|
||||||
|
for related_object in self._meta.related_objects:
|
||||||
|
related_model = getattr(self, related_object.get_accessor_name())
|
||||||
|
# 处理一对多和多对多的关联对象
|
||||||
|
if related_object.one_to_many or related_object.many_to_many:
|
||||||
|
related_objects = related_model.all()
|
||||||
|
elif related_object.one_to_one:
|
||||||
|
related_objects = [related_model]
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for obj in related_objects:
|
||||||
|
obj.delete(soft_delete=True)
|
||||||
|
else:
|
||||||
|
super().delete(using=using, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class CoreModel(models.Model):
|
class CoreModel(models.Model):
|
||||||
@@ -87,6 +104,111 @@ class CoreModel(models.Model):
|
|||||||
verbose_name = '核心模型'
|
verbose_name = '核心模型'
|
||||||
verbose_name_plural = verbose_name
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def get_request_user(self, request: Request):
|
||||||
|
if getattr(request, "user", None):
|
||||||
|
return request.user
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_request_user_id(self, request: Request):
|
||||||
|
if getattr(request, "user", None):
|
||||||
|
return getattr(request.user, "id", None)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_request_user_name(self, request: Request):
|
||||||
|
if getattr(request, "user", None):
|
||||||
|
return getattr(request.user, "name", None)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_request_user_username(self, request: Request):
|
||||||
|
if getattr(request, "user", None):
|
||||||
|
return getattr(request.user, "username", None)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def common_insert_data(self, request: Request):
|
||||||
|
data = {
|
||||||
|
'create_datetime': datetime.now(),
|
||||||
|
'creator': self.get_request_user(request)
|
||||||
|
}
|
||||||
|
return {**data, **self.common_update_data(request)}
|
||||||
|
|
||||||
|
def common_update_data(self, request: Request):
|
||||||
|
return {
|
||||||
|
'update_datetime': datetime.now(),
|
||||||
|
'modifier': self.get_request_user_username(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
exclude_fields = [
|
||||||
|
'_state',
|
||||||
|
'pk',
|
||||||
|
'id',
|
||||||
|
'create_datetime',
|
||||||
|
'update_datetime',
|
||||||
|
'creator',
|
||||||
|
'creator_id',
|
||||||
|
'creator_pk',
|
||||||
|
'creator_name',
|
||||||
|
'modifier',
|
||||||
|
'modifier_id',
|
||||||
|
'modifier_pk',
|
||||||
|
'modifier_name',
|
||||||
|
'dept_belong_id',
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_exclude_fields(self):
|
||||||
|
return self.exclude_fields
|
||||||
|
|
||||||
|
def get_all_fields(self):
|
||||||
|
return self._meta.fields
|
||||||
|
|
||||||
|
def get_all_fields_names(self):
|
||||||
|
return [field.name for field in self.get_all_fields()]
|
||||||
|
|
||||||
|
def get_need_fields_names(self):
|
||||||
|
return [field.name for field in self.get_all_fields() if field.name not in self.exclude_fields]
|
||||||
|
|
||||||
|
def to_data(self):
|
||||||
|
"""将模型转化为字典(去除不包含字段)(注意与to_dict_data区分)。
|
||||||
|
"""
|
||||||
|
res = {}
|
||||||
|
for field in self.get_need_fields_names():
|
||||||
|
field_value = getattr(self, field)
|
||||||
|
res[field] = field_value.id if (issubclass(field_value.__class__, CoreModel)) else field_value
|
||||||
|
return res
|
||||||
|
|
||||||
|
@property
|
||||||
|
def DATA(self):
|
||||||
|
return self.to_data()
|
||||||
|
|
||||||
|
def to_dict_data(self):
|
||||||
|
"""需要导出的字段(去除不包含字段)(注意与to_data区分)
|
||||||
|
"""
|
||||||
|
return {field: getattr(self, field) for field in self.get_need_fields_names()}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def DICT_DATA(self):
|
||||||
|
return self.to_dict_data()
|
||||||
|
|
||||||
|
def insert(self, request):
|
||||||
|
"""插入模型
|
||||||
|
"""
|
||||||
|
assert self.pk is None, f'模型{self.__class__.__name__}还没有保存到数据中,不能手动指定ID'
|
||||||
|
validated_data = {**self.common_insert_data(request), **self.DICT_DATA}
|
||||||
|
return self.__class__._default_manager.create(**validated_data)
|
||||||
|
|
||||||
|
def update(self, request, update_data: dict[str, any] = None):
|
||||||
|
"""更新模型
|
||||||
|
"""
|
||||||
|
assert isinstance(update_data, dict), 'update_data必须为字典'
|
||||||
|
validated_data = {**self.common_insert_data(request), **update_data}
|
||||||
|
for key, value in validated_data.items():
|
||||||
|
# 不允许修改id,pk,uuid字段
|
||||||
|
if key in ['id', 'pk', 'uuid']:
|
||||||
|
continue
|
||||||
|
if hasattr(self, key):
|
||||||
|
setattr(self, key, value)
|
||||||
|
self.save()
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
def get_all_models_objects(model_name=None):
|
def get_all_models_objects(model_name=None):
|
||||||
"""
|
"""
|
||||||
@@ -97,16 +219,9 @@ def get_all_models_objects(model_name=None):
|
|||||||
if not settings.ALL_MODELS_OBJECTS:
|
if not settings.ALL_MODELS_OBJECTS:
|
||||||
all_models = apps.get_models()
|
all_models = apps.get_models()
|
||||||
for item in list(all_models):
|
for item in list(all_models):
|
||||||
table = {
|
table = {"tableName": item._meta.verbose_name, "table": item.__name__, "tableFields": []}
|
||||||
"tableName": item._meta.verbose_name,
|
|
||||||
"table": item.__name__,
|
|
||||||
"tableFields": []
|
|
||||||
}
|
|
||||||
for field in item._meta.fields:
|
for field in item._meta.fields:
|
||||||
fields = {
|
fields = {"title": field.verbose_name, "field": field.name}
|
||||||
"title": field.verbose_name,
|
|
||||||
"field": field.name
|
|
||||||
}
|
|
||||||
table['tableFields'].append(fields)
|
table['tableFields'].append(fields)
|
||||||
settings.ALL_MODELS_OBJECTS.setdefault(item.__name__, {"table": table, "object": item})
|
settings.ALL_MODELS_OBJECTS.setdefault(item.__name__, {"table": table, "object": item})
|
||||||
if model_name:
|
if model_name:
|
||||||
@@ -117,25 +232,20 @@ def get_all_models_objects(model_name=None):
|
|||||||
def get_model_from_app(app_name):
|
def get_model_from_app(app_name):
|
||||||
"""获取模型里的字段"""
|
"""获取模型里的字段"""
|
||||||
model_module = import_module(app_name + '.models')
|
model_module = import_module(app_name + '.models')
|
||||||
|
exclude_models = getattr(model_module, 'exclude_models', [])
|
||||||
filter_model = [
|
filter_model = [
|
||||||
getattr(model_module, item) for item in dir(model_module)
|
value for key, value in model_module.__dict__.items()
|
||||||
if item != 'CoreModel' and issubclass(getattr(model_module, item).__class__, models.base.ModelBase)
|
if key != 'CoreModel'
|
||||||
|
and isinstance(value, type)
|
||||||
|
and issubclass(value, models.Model)
|
||||||
|
and key not in exclude_models
|
||||||
]
|
]
|
||||||
model_list = []
|
model_list = []
|
||||||
for model in filter_model:
|
for model in filter_model:
|
||||||
if model.__name__ == 'AbstractUser':
|
if model.__name__ == 'AbstractUser':
|
||||||
continue
|
continue
|
||||||
fields = [
|
fields = [{'title': field.verbose_name, 'name': field.name, 'object': field} for field in model._meta.fields]
|
||||||
{'title': field.verbose_name, 'name': field.name, 'object': field}
|
model_list.append({'app': app_name, 'verbose': model._meta.verbose_name, 'model': model.__name__, 'object': model, 'fields': fields})
|
||||||
for field in model._meta.fields
|
|
||||||
]
|
|
||||||
model_list.append({
|
|
||||||
'app': app_name,
|
|
||||||
'verbose': model._meta.verbose_name,
|
|
||||||
'model': model.__name__,
|
|
||||||
'object': model,
|
|
||||||
'fields': fields
|
|
||||||
})
|
|
||||||
return model_list
|
return model_list
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,35 @@ class AnonymousUserPermission(BasePermission):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class SuperuserPermission(BasePermission):
|
||||||
|
"""
|
||||||
|
超级管理员权限类
|
||||||
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
if isinstance(request.user, AnonymousUser):
|
||||||
|
return False
|
||||||
|
# 判断是否是超级管理员
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class AdminPermission(BasePermission):
|
||||||
|
"""
|
||||||
|
普通管理员权限类
|
||||||
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
if isinstance(request.user, AnonymousUser):
|
||||||
|
return False
|
||||||
|
# 判断是否是超级管理员
|
||||||
|
is_superuser = request.user.is_superuser
|
||||||
|
# 判断是否是管理员角色
|
||||||
|
is_admin = request.user.role.values_list('admin', flat=True)
|
||||||
|
if is_superuser or True in is_admin:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def ReUUID(api):
|
def ReUUID(api):
|
||||||
"""
|
"""
|
||||||
将接口的uuid替换掉
|
将接口的uuid替换掉
|
||||||
@@ -81,8 +110,9 @@ class CustomPermission(BasePermission):
|
|||||||
# ********#
|
# ********#
|
||||||
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 = RoleMenuButtonPermission.objects.filter(role__in=role_id_list).values(
|
||||||
|
permission__api=F('menu_button__api'), permission__method=F('menu_button__method')) # 获取当前用户的角色拥有的所有接口
|
||||||
ApiList = [
|
ApiList = [
|
||||||
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||||
item.get('permission__method')) + '$' for item in userApiList if item.get('permission__api')]
|
item.get('permission__method')) + '$' for item in userApiList if item.get('permission__api')]
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
|
|||||||
# 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖
|
# 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖
|
||||||
modifier_field_id = "modifier"
|
modifier_field_id = "modifier"
|
||||||
modifier_name = serializers.SerializerMethodField(read_only=True)
|
modifier_name = serializers.SerializerMethodField(read_only=True)
|
||||||
dept_belong_id = serializers.IntegerField(required=False, allow_null=True)
|
|
||||||
|
|
||||||
def get_modifier_name(self, instance):
|
def get_modifier_name(self, instance):
|
||||||
if not hasattr(instance, "modifier"):
|
if not hasattr(instance, "modifier"):
|
||||||
@@ -52,7 +51,7 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
|
|||||||
format="%Y-%m-%d %H:%M:%S", required=False, read_only=True
|
format="%Y-%m-%d %H:%M:%S", required=False, read_only=True
|
||||||
)
|
)
|
||||||
update_datetime = serializers.DateTimeField(
|
update_datetime = serializers.DateTimeField(
|
||||||
format="%Y-%m-%d %H:%M:%S", required=False
|
format="%Y-%m-%d %H:%M:%S", required=False, read_only=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, instance=None, data=empty, request=None, **kwargs):
|
def __init__(self, instance=None, data=empty, request=None, **kwargs):
|
||||||
@@ -71,11 +70,11 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
|
|||||||
validated_data[self.creator_field_id] = self.request.user
|
validated_data[self.creator_field_id] = self.request.user
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.dept_belong_id_field_name in self.fields.fields
|
self.dept_belong_id_field_name in self.fields.fields
|
||||||
and validated_data.get(self.dept_belong_id_field_name, None) is None
|
and validated_data.get(self.dept_belong_id_field_name, None) is None
|
||||||
):
|
):
|
||||||
validated_data[self.dept_belong_id_field_name] = getattr(
|
validated_data[self.dept_belong_id_field_name] = getattr(
|
||||||
self.request.user, "dept_id", None
|
self.request.user, "dept_id", validated_data.get(self.dept_belong_id_field_name, None)
|
||||||
)
|
)
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,9 @@ from application.settings import LOGGING
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
multiprocessing.freeze_support()
|
multiprocessing.freeze_support()
|
||||||
uvicorn.run("application.asgi:application", reload=False, host="0.0.0.0", port=8000, workers=4,
|
workers = 4
|
||||||
|
if os.sys.platform.startswith('win'):
|
||||||
|
# Windows操作系统
|
||||||
|
workers = None
|
||||||
|
uvicorn.run("application.asgi:application", reload=False, host="0.0.0.0", port=8000, workers=workers,
|
||||||
log_config=LOGGING)
|
log_config=LOGGING)
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
Django==4.2.7
|
Django==4.2.14
|
||||||
django-comment-migrate==0.1.7
|
django-comment-migrate==0.1.7
|
||||||
django-cors-headers==4.3.0
|
django-cors-headers==4.4.0
|
||||||
django-filter==23.3
|
django-filter==24.2
|
||||||
django-ranged-response==0.2.0
|
django-ranged-response==0.2.0
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.15.2
|
||||||
django-restql==0.15.3
|
django-restql==0.15.4
|
||||||
django-simple-captcha==0.5.20
|
django-simple-captcha==0.6.0
|
||||||
django-timezone-field==6.0.1
|
django-timezone-field==7.0
|
||||||
djangorestframework-simplejwt==5.3.0
|
djangorestframework-simplejwt==5.3.1
|
||||||
drf-yasg==1.21.7
|
drf-yasg==1.21.7
|
||||||
mysqlclient==2.2.0
|
mysqlclient==2.2.0
|
||||||
pypinyin==0.49.0
|
pypinyin==0.51.0
|
||||||
ua-parser==0.18.0
|
ua-parser==0.18.0
|
||||||
pyparsing==3.1.1
|
pyparsing==3.1.2
|
||||||
openpyxl==3.1.2
|
openpyxl==3.1.5
|
||||||
requests==2.31.0
|
requests==2.32.3
|
||||||
typing-extensions==4.8.0
|
typing-extensions==4.12.2
|
||||||
tzlocal==5.1
|
tzlocal==5.2
|
||||||
channels==3.0.5
|
channels==4.1.0
|
||||||
channels-redis==4.1.0
|
channels-redis==4.2.0
|
||||||
websockets==11.0.3
|
websockets==11.0.3
|
||||||
user-agents==2.2.0
|
user-agents==2.2.0
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
whitenoise==6.6.0
|
whitenoise==6.7.0
|
||||||
psycopg2==2.9.9
|
psycopg2==2.9.9
|
||||||
uvicorn==0.23.2
|
uvicorn==0.30.3
|
||||||
gunicorn==21.2.0
|
gunicorn==22.0.0
|
||||||
gevent==23.9.1
|
gevent==24.2.1
|
||||||
Pillow==10.1.0
|
Pillow==10.4.0
|
||||||
dvadmin-celery==1.0.5
|
dvadmin-celery==1.0.5
|
||||||
pyinstaller==6.8.0
|
pyinstaller==6.9.0
|
||||||
BIN
backend/static/logo.icns
Normal file
BIN
backend/static/logo.icns
Normal file
Binary file not shown.
@@ -6,4 +6,4 @@ RUN awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |
|
|||||||
RUN sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py
|
RUN sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py
|
||||||
RUN sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py
|
RUN sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py
|
||||||
RUN python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt
|
RUN python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt
|
||||||
CMD ["/backend/docker_start.sh"]
|
CMD ["sh","docker_start.sh"]
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ server {
|
|||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
|
# 禁止缓存html文件,避免前端页面不及时更新,需要用户手动刷新的情况
|
||||||
|
if ($request_uri ~* "^/$|^/index.html|^/index.htm") {
|
||||||
|
add_header Cache-Control "no-store";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/api/ {
|
location ~ ^/api/ {
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ server {
|
|||||||
real_ip_header X-Forwarded-For;
|
real_ip_header X-Forwarded-For;
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html index.php index.htm;
|
index index.html index.php index.htm;
|
||||||
|
# 禁止缓存html文件,避免前端页面不及时更新,需要用户手动刷新的情况
|
||||||
|
if ($request_uri ~* "^/$|^/index.html|^/index.htm") {
|
||||||
|
add_header Cache-Control "no-store";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
|
|||||||
2
web/.gitignore
vendored
2
web/.gitignore
vendored
@@ -21,3 +21,5 @@ pnpm-debug.log*
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
# 构建版本文件,无需上传git
|
||||||
|
public/version-build
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "django-vue3-admin",
|
"name": "django-vue3-admin",
|
||||||
"version": "3.0.3",
|
"version": "3.0.4",
|
||||||
"description": "是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。\n django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus",
|
"description": "是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。\n django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -10,32 +10,32 @@
|
|||||||
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
|
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.0.10",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"@fast-crud/fast-crud": "^1.20.1",
|
"@fast-crud/fast-crud": "^1.21.2",
|
||||||
"@fast-crud/fast-extends": "^1.20.1",
|
"@fast-crud/fast-extends": "^1.21.2",
|
||||||
"@fast-crud/ui-element": "^1.20.1",
|
"@fast-crud/ui-element": "^1.21.2",
|
||||||
"@fast-crud/ui-interface": "^1.20.1",
|
"@fast-crud/ui-interface": "^1.21.2",
|
||||||
"@iconify/vue": "^4.1.1",
|
"@iconify/vue": "^4.1.2",
|
||||||
"@types/lodash": "^4.14.202",
|
"@types/lodash": "^4.17.7",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.20",
|
||||||
"axios": "^1.2.1",
|
"axios": "^1.7.4",
|
||||||
"countup.js": "^2.3.2",
|
"countup.js": "^2.8.0",
|
||||||
"cropperjs": "^1.5.13",
|
"cropperjs": "^1.6.2",
|
||||||
"e-icon-picker": "2.1.1",
|
"e-icon-picker": "2.1.1",
|
||||||
"echarts": "^5.4.1",
|
"echarts": "^5.5.1",
|
||||||
"echarts-gl": "^2.0.9",
|
"echarts-gl": "^2.0.9",
|
||||||
"echarts-wordcloud": "^2.1.0",
|
"echarts-wordcloud": "^2.1.0",
|
||||||
"element-plus": "^2.5.5",
|
"element-plus": "^2.8.0",
|
||||||
"element-tree-line": "^0.2.1",
|
"element-tree-line": "^0.2.1",
|
||||||
"font-awesome": "^4.7.0",
|
"font-awesome": "^4.7.0",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.5",
|
||||||
"js-table2excel": "^1.0.3",
|
"js-table2excel": "^1.1.2",
|
||||||
"jsplumb": "^2.15.6",
|
"jsplumb": "^2.15.6",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^2.0.28",
|
"pinia": "^2.0.28",
|
||||||
"pinia-plugin-persist": "^1.0.0",
|
"pinia-plugin-persist": "^1.0.0",
|
||||||
@@ -49,31 +49,31 @@
|
|||||||
"tailwindcss": "^3.2.7",
|
"tailwindcss": "^3.2.7",
|
||||||
"ts-md5": "^1.3.1",
|
"ts-md5": "^1.3.1",
|
||||||
"upgrade": "^1.1.0",
|
"upgrade": "^1.1.0",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.4.38",
|
||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-cropper": "^1.0.8",
|
"vue-cropper": "^1.0.8",
|
||||||
"vue-grid-layout": "^3.0.0-beta1",
|
"vue-grid-layout": "^3.0.0-beta1",
|
||||||
"vue-i18n": "^9.2.2",
|
"vue-i18n": "^9.14.0",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.4.3",
|
||||||
"vxe-table": "^4.4.1",
|
"vxe-table": "^4.6.18",
|
||||||
"xe-utils": "^3.5.7"
|
"xe-utils": "^3.5.30"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.11.13",
|
"@types/node": "^18.19.42",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.3",
|
||||||
"@types/sortablejs": "^1.15.0",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.46.0",
|
"@typescript-eslint/eslint-plugin": "^5.46.0",
|
||||||
"@typescript-eslint/parser": "^5.46.0",
|
"@typescript-eslint/parser": "^5.46.0",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^5.1.2",
|
||||||
"@vue/compiler-sfc": "^3.2.45",
|
"@vue/compiler-sfc": "^3.4.38",
|
||||||
"eslint": "^8.54.0",
|
"eslint": "^9.9.0",
|
||||||
"eslint-plugin-vue": "^9.8.0",
|
"eslint-plugin-vue": "^9.27.0",
|
||||||
"prettier": "^2.8.1",
|
"prettier": "^2.8.1",
|
||||||
"sass": "^1.56.2",
|
"sass": "^1.56.2",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^4.0.0",
|
"vite": "^5.4.1",
|
||||||
"vite-plugin-vue-setup-extend": "^0.4.0",
|
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||||
"vue-eslint-parser": "^9.1.0"
|
"vue-eslint-parser": "^9.4.3"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|||||||
@@ -1,203 +1,211 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-select popper-class="popperClass" class="tableSelector" :multiple="props.tableConfig.isMultiple"
|
<el-select
|
||||||
@remove-tag="removeTag" v-model="data" placeholder="请选择" @visible-change="visibleChange">
|
popper-class="popperClass"
|
||||||
<template #empty>
|
class="tableSelector"
|
||||||
<div class="option">
|
multiple
|
||||||
<el-input style="margin-bottom: 10px" v-model="search" clearable placeholder="请输入关键词" @change="getDict"
|
@remove-tag="removeTag"
|
||||||
@clear="getDict">
|
v-model="data"
|
||||||
<template #append>
|
placeholder="请选择"
|
||||||
<el-button type="primary" icon="Search"/>
|
@visible-change="visibleChange"
|
||||||
</template>
|
>
|
||||||
</el-input>
|
<template #empty>
|
||||||
<el-table
|
<div class="option">
|
||||||
ref="tableRef"
|
<el-input style="margin-bottom: 10px" v-model="search" clearable placeholder="请输入关键词" @change="getDict" @clear="getDict">
|
||||||
:data="tableData"
|
<template #append>
|
||||||
size="mini"
|
<el-button type="primary" icon="Search" />
|
||||||
border
|
</template>
|
||||||
row-key="id"
|
</el-input>
|
||||||
style="width: 400px"
|
<el-table
|
||||||
max-height="200"
|
ref="tableRef"
|
||||||
height="200"
|
:data="tableData"
|
||||||
:highlight-current-row="!props.tableConfig.isMultiple"
|
size="mini"
|
||||||
@selection-change="handleSelectionChange"
|
border
|
||||||
@current-change="handleCurrentChange"
|
row-key="id"
|
||||||
>
|
:lazy="props.tableConfig.lazy"
|
||||||
<el-table-column v-if="props.tableConfig.isMultiple" fixed type="selection" width="55"/>
|
:load="props.tableConfig.load"
|
||||||
<el-table-column fixed type="index" label="#" width="50"/>
|
:tree-props="props.tableConfig.treeProps"
|
||||||
<el-table-column :prop="item.prop" :label="item.label" :width="item.width"
|
style="width: 400px"
|
||||||
v-for="(item,index) in props.tableConfig.columns" :key="index"/>
|
max-height="200"
|
||||||
</el-table>
|
height="200"
|
||||||
<el-pagination style="margin-top: 10px" background
|
:highlight-current-row="!props.tableConfig.isMultiple"
|
||||||
v-model:current-page="pageConfig.page"
|
@selection-change="handleSelectionChange"
|
||||||
v-model:page-size="pageConfig.limit"
|
@current-change="handleCurrentChange"
|
||||||
layout="prev, pager, next"
|
>
|
||||||
:total="pageConfig.total"
|
<el-table-column v-if="props.tableConfig.isMultiple" fixed type="selection" width="55" />
|
||||||
@current-change="handlePageChange"
|
<el-table-column fixed type="index" label="#" width="50" />
|
||||||
/>
|
<el-table-column
|
||||||
</div>
|
:prop="item.prop"
|
||||||
</template>
|
:label="item.label"
|
||||||
</el-select>
|
:width="item.width"
|
||||||
|
v-for="(item, index) in props.tableConfig.columns"
|
||||||
|
:key="index"
|
||||||
|
/>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
style="margin-top: 10px"
|
||||||
|
background
|
||||||
|
v-model:current-page="pageConfig.page"
|
||||||
|
v-model:page-size="pageConfig.limit"
|
||||||
|
layout="prev, pager, next"
|
||||||
|
:total="pageConfig.total"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {defineProps, onMounted, reactive, ref, toRaw, watch} from 'vue'
|
import { defineProps, reactive, ref, watch } from 'vue';
|
||||||
import {dict} from '@fast-crud/fast-crud'
|
import XEUtils from 'xe-utils';
|
||||||
import XEUtils from 'xe-utils'
|
import { request } from '/@/utils/service';
|
||||||
import {request} from '/@/utils/service'
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {},
|
modelValue: {},
|
||||||
tableConfig: {
|
tableConfig: {
|
||||||
url: null,
|
url: null,
|
||||||
label: null, //显示值
|
label: null, //显示值
|
||||||
value: null, //数据值
|
value: null, //数据值
|
||||||
isTree: false,
|
isTree: false,
|
||||||
data: [],//默认数据
|
lazy: true,
|
||||||
isMultiple: false, //是否多选
|
load: () => {},
|
||||||
columns: [], //每一项对应的列表项
|
data: [], //默认数据
|
||||||
},
|
isMultiple: false, //是否多选
|
||||||
displayLabel: {}
|
treeProps: { children: 'children', hasChildren: 'hasChildren' },
|
||||||
} as any)
|
columns: [], //每一项对应的列表项
|
||||||
const emit = defineEmits(['update:modelValue'])
|
},
|
||||||
|
displayLabel: {},
|
||||||
|
} as any);
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
// tableRef
|
// tableRef
|
||||||
const tableRef = ref()
|
const tableRef = ref();
|
||||||
// template上使用data
|
// template上使用data
|
||||||
const data = ref()
|
const data = ref();
|
||||||
// 多选值
|
// 多选值
|
||||||
const multipleSelection = ref()
|
const multipleSelection = ref();
|
||||||
watch(multipleSelection, // 监听multipleSelection的变化,
|
|
||||||
(value) => {
|
|
||||||
const {tableConfig} = props
|
|
||||||
//是否多选
|
|
||||||
if (!tableConfig.isMultiple) {
|
|
||||||
data.value = value ? value[tableConfig.label] : null
|
|
||||||
} else {
|
|
||||||
|
|
||||||
const result = value ? value.map((item: any) => {
|
|
||||||
return item[tableConfig.label]
|
|
||||||
}) : null
|
|
||||||
data.value = result
|
|
||||||
}
|
|
||||||
}, // 当multipleSelection值触发后,同步修改data.value的值
|
|
||||||
{immediate: true} // 立即触发一次,给data赋值初始值
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
// 搜索值
|
// 搜索值
|
||||||
const search = ref(undefined)
|
const search = ref(undefined);
|
||||||
//表格数据
|
//表格数据
|
||||||
const tableData = ref()
|
const tableData = ref();
|
||||||
// 分页的配置
|
// 分页的配置
|
||||||
const pageConfig = reactive({
|
const pageConfig = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
total: 0
|
total: 0,
|
||||||
})
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表格多选
|
* 表格多选
|
||||||
* @param val:Array
|
* @param val:Array
|
||||||
*/
|
*/
|
||||||
const handleSelectionChange = (val: any) => {
|
const handleSelectionChange = (val: any) => {
|
||||||
multipleSelection.value = val
|
multipleSelection.value = val;
|
||||||
const {tableConfig} = props
|
const { tableConfig } = props;
|
||||||
const result = val.map((item: any) => {
|
const result = val.map((item: any) => {
|
||||||
return item[tableConfig.value]
|
return item[tableConfig.value];
|
||||||
})
|
});
|
||||||
emit('update:modelValue', result)
|
data.value = val.map((item: any) => {
|
||||||
}
|
return item[tableConfig.label];
|
||||||
|
});
|
||||||
|
|
||||||
|
emit('update:modelValue', result);
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* 表格单选
|
* 表格单选
|
||||||
* @param val:Object
|
* @param val:Object
|
||||||
*/
|
*/
|
||||||
const handleCurrentChange = (val: any) => {
|
const handleCurrentChange = (val: any) => {
|
||||||
multipleSelection.value = val
|
const { tableConfig } = props;
|
||||||
const {tableConfig} = props
|
if (!tableConfig.isMultiple && val) {
|
||||||
emit('update:modelValue', val[tableConfig.value])
|
data.value = [val[tableConfig.label]];
|
||||||
}
|
emit('update:modelValue', val[tableConfig.value]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取字典值
|
* 获取字典值
|
||||||
*/
|
*/
|
||||||
const getDict = async () => {
|
const getDict = async () => {
|
||||||
const url = props.tableConfig.url
|
const url = props.tableConfig.url;
|
||||||
const params = {
|
const params = {
|
||||||
page: pageConfig.page,
|
page: pageConfig.page,
|
||||||
limit: pageConfig.limit,
|
limit: pageConfig.limit,
|
||||||
search: search.value
|
search: search.value,
|
||||||
}
|
};
|
||||||
const {data, page, limit, total} = await request({
|
const { data, page, limit, total } = await request({
|
||||||
url:url,
|
url: url,
|
||||||
params:params
|
params: params,
|
||||||
})
|
});
|
||||||
pageConfig.page = page
|
pageConfig.page = page;
|
||||||
pageConfig.limit = limit
|
pageConfig.limit = limit;
|
||||||
pageConfig.total = total
|
pageConfig.total = total;
|
||||||
if (props.tableConfig.data === undefined || props.tableConfig.data.length === 0) {
|
if (props.tableConfig.data === undefined || props.tableConfig.data.length === 0) {
|
||||||
if (props.tableConfig.isTree) {
|
if (props.tableConfig.isTree) {
|
||||||
tableData.value = XEUtils.toArrayTree(data, {parentKey: 'parent', key: 'id', children: 'children'})
|
tableData.value = XEUtils.toArrayTree(data, { parentKey: 'parent', key: 'id', children: 'children' });
|
||||||
} else {
|
} else {
|
||||||
tableData.value = data
|
tableData.value = data;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tableData.value = props.tableConfig.data
|
tableData.value = props.tableConfig.data;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下拉框展开/关闭
|
* 下拉框展开/关闭
|
||||||
* @param bool
|
* @param bool
|
||||||
*/
|
*/
|
||||||
const visibleChange = (bool: any) => {
|
const visibleChange = (bool: any) => {
|
||||||
if (bool) {
|
if (bool) {
|
||||||
getDict()
|
getDict();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页
|
* 分页
|
||||||
* @param page
|
* @param page
|
||||||
*/
|
*/
|
||||||
const handlePageChange = (page: any) => {
|
const handlePageChange = (page: any) => {
|
||||||
pageConfig.page = page
|
pageConfig.page = page;
|
||||||
getDict()
|
getDict();
|
||||||
}
|
};
|
||||||
|
|
||||||
// 监听displayLabel的变化,更新数据
|
// 监听displayLabel的变化,更新数据
|
||||||
watch(() => {
|
watch(
|
||||||
return props.displayLabel
|
() => {
|
||||||
}, (value) => {
|
return props.displayLabel;
|
||||||
const {tableConfig} = props
|
},
|
||||||
const result = value ? value.map((item: any) => {
|
(value) => {
|
||||||
return item[tableConfig.label]
|
const { tableConfig } = props;
|
||||||
}) : null
|
const result = value
|
||||||
data.value = result
|
? value.map((item: any) => { return item[tableConfig.label];})
|
||||||
}, {immediate: true})
|
: null;
|
||||||
|
data.value = result;
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.option {
|
.option {
|
||||||
height: auto;
|
height: auto;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.popperClass {
|
.popperClass {
|
||||||
height: 320px;
|
height: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-select-dropdown__wrap {
|
.el-select-dropdown__wrap {
|
||||||
max-height: 310px !important;
|
max-height: 310px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tableSelector {
|
.tableSelector {
|
||||||
.el-icon, .el-tag__close {
|
.el-icon,
|
||||||
display: none;
|
.el-tag__close {
|
||||||
}
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default {
|
|||||||
two4: '友情链接',
|
two4: '友情链接',
|
||||||
},
|
},
|
||||||
account: {
|
account: {
|
||||||
accountPlaceholder1: '请输入登录账号',
|
accountPlaceholder1: '请输入登录账号/邮箱/手机号',
|
||||||
accountPlaceholder2: '请输入登录密码',
|
accountPlaceholder2: '请输入登录密码',
|
||||||
accountPlaceholder3: '请输入验证码',
|
accountPlaceholder3: '请输入验证码',
|
||||||
accountBtnText: '登 录',
|
accountBtnText: '登 录',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {initBackEndControlRoutes, setRouters} from '/@/router/backEnd';
|
|||||||
import {useFrontendMenuStore} from "/@/stores/frontendMenu";
|
import {useFrontendMenuStore} from "/@/stores/frontendMenu";
|
||||||
import {useTagsViewRoutes} from "/@/stores/tagsViewRoutes";
|
import {useTagsViewRoutes} from "/@/stores/tagsViewRoutes";
|
||||||
import {toRaw} from "vue";
|
import {toRaw} from "vue";
|
||||||
|
import {checkVersion} from "/@/utils/upgrade";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1、前端控制路由时:isRequestRoutes 为 false,需要写 roles,需要走 setFilterRoute 方法。
|
* 1、前端控制路由时:isRequestRoutes 为 false,需要写 roles,需要走 setFilterRoute 方法。
|
||||||
@@ -97,6 +98,8 @@ const frameOutRoutes = staticRoutes.map(item => item.path)
|
|||||||
|
|
||||||
// 路由加载前
|
// 路由加载前
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
// 检查浏览器本地版本与线上版本是否一致,判断是否需要刷新页面进行更新
|
||||||
|
await checkVersion()
|
||||||
NProgress.configure({showSpinner: false});
|
NProgress.configure({showSpinner: false});
|
||||||
if (to.meta.title) NProgress.start();
|
if (to.meta.title) NProgress.start();
|
||||||
const token = Session.get('token');
|
const token = Session.get('token');
|
||||||
|
|||||||
@@ -22,33 +22,18 @@ export const handleColumnPermission = async (func: Function, crudOptions: any,ex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const columns = crudOptions.columns;
|
const columns = crudOptions.columns;
|
||||||
const excludeColumns = ['_index','id', 'create_datetime', 'update_datetime'].concat(excludeColumn)
|
const excludeColumns = ['checked','_index','id', 'create_datetime', 'update_datetime'].concat(excludeColumn)
|
||||||
for (let col in columns) {
|
for (let col in columns) {
|
||||||
if (excludeColumns.includes(col)) {
|
|
||||||
continue
|
|
||||||
}else{
|
|
||||||
if (columns[col].column) {
|
|
||||||
columns[col].column.show = false
|
|
||||||
} else {
|
|
||||||
columns[col]['column'] = {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
columns[col].addForm = {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
columns[col].editForm = {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let item of res.data) {
|
for (let item of res.data) {
|
||||||
if (excludeColumns.includes(item.field_name)) {
|
if (excludeColumns.includes(item.field_name)) {
|
||||||
continue
|
continue
|
||||||
} else if(item.field_name === col) {
|
} else if(item.field_name === col) {
|
||||||
columns[col].column.show = item['is_query']
|
|
||||||
// 如果列表不可见,则禁止在列设置中选择
|
// 如果列表不可见,则禁止在列设置中选择
|
||||||
if(!item['is_query'])columns[col].column.columnSetDisabled = true
|
// 只有列表不可见,才修改列配置,这样才不影响默认的配置
|
||||||
|
if(!item['is_query']){
|
||||||
|
columns[col].column.show = false
|
||||||
|
columns[col].column.columnSetDisabled = true
|
||||||
|
}
|
||||||
columns[col].addForm = {
|
columns[col].addForm = {
|
||||||
show: item['is_create']
|
show: item['is_create']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import '/@/theme/loading.scss';
|
import '/@/theme/loading.scss';
|
||||||
|
import { showUpgrade } from "/@/utils/upgrade";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 页面全局 Loading
|
* 页面全局 Loading
|
||||||
@@ -9,6 +11,8 @@ import '/@/theme/loading.scss';
|
|||||||
export const NextLoading = {
|
export const NextLoading = {
|
||||||
// 创建 loading
|
// 创建 loading
|
||||||
start: () => {
|
start: () => {
|
||||||
|
// 显示升级提示
|
||||||
|
showUpgrade()
|
||||||
const bodys: Element = document.body;
|
const bodys: Element = document.body;
|
||||||
const div = <HTMLElement>document.createElement('div');
|
const div = <HTMLElement>document.createElement('div');
|
||||||
div.setAttribute('class', 'loading-next');
|
div.setAttribute('class', 'loading-next');
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ function createService() {
|
|||||||
ElMessageBox.alert(dataAxios.msg, '提示', {
|
ElMessageBox.alert(dataAxios.msg, '提示', {
|
||||||
confirmButtonText: 'OK',
|
confirmButtonText: 'OK',
|
||||||
callback: (action: Action) => {
|
callback: (action: Action) => {
|
||||||
window.location.reload();
|
// window.location.reload();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
|
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
|
||||||
|
|||||||
55
web/src/utils/upgrade.ts
Normal file
55
web/src/utils/upgrade.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import * as process from "process";
|
||||||
|
import {Local, Session} from '/@/utils/storage';
|
||||||
|
import {ElNotification} from "element-plus";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
// 是否显示升级提示信息框
|
||||||
|
const IS_SHOW_UPGRADE_SESSION_KEY = 'isShowUpgrade';
|
||||||
|
const VERSION_KEY = 'DVADMIN3_VERSION'
|
||||||
|
const VERSION_FILE_NAME = 'version-build'
|
||||||
|
|
||||||
|
export function showUpgrade () {
|
||||||
|
const isShowUpgrade = Session.get(IS_SHOW_UPGRADE_SESSION_KEY) ?? false
|
||||||
|
if (isShowUpgrade) {
|
||||||
|
Session.remove(IS_SHOW_UPGRADE_SESSION_KEY)
|
||||||
|
ElNotification({
|
||||||
|
title: '新版本升级',
|
||||||
|
message: "检测到系统新版本,正在更新中!不用担心,更新很快的哦!",
|
||||||
|
type: 'success',
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生产环境前端版本校验,
|
||||||
|
export async function checkVersion(){
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
// 开发环境无需校验前端版本
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取线上版本号 t为时间戳,防止缓存
|
||||||
|
await axios.get(`${import.meta.env.VITE_PUBLIC_PATH}${VERSION_FILE_NAME}?t=${new Date().getTime()}`).then(res => {
|
||||||
|
const {status, data} = res || {}
|
||||||
|
if (status === 200) {
|
||||||
|
// 获取当前版本号
|
||||||
|
const localVersion = Local.get(VERSION_KEY)
|
||||||
|
// 将当前版本号持久缓存至本地
|
||||||
|
Local.set(VERSION_KEY, data)
|
||||||
|
// 当用户本地存在版本号并且和线上版本号不一致时,进行页面刷新操作
|
||||||
|
if (localVersion && localVersion !== data) {
|
||||||
|
// 本地缓存版本号和线上版本号不一致,弹出升级提示框
|
||||||
|
// 此处无法直接使用消息框进行提醒,因为 window.location.reload()会导致消息框消失,将在loading页面判断是否需要显示升级提示框
|
||||||
|
Session.set(IS_SHOW_UPGRADE_SESSION_KEY, true)
|
||||||
|
window.location.reload()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateVersionFile (){
|
||||||
|
// 生成版本文件到public目录下version文件中
|
||||||
|
const version = `${process.env.npm_package_version}.${new Date().getTime()}`;
|
||||||
|
fs.writeFileSync(`public/${VERSION_FILE_NAME}`, version);
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import VFormRender from '@/components/form-render/index.vue'
|
|
||||||
import ContainerItems from '@/components/form-render/container-item/index'
|
|
||||||
|
|
||||||
import {registerIcon} from '@/utils/el-icons'
|
|
||||||
import 'virtual:svg-icons-register'
|
|
||||||
import '@/iconfont/iconfont.css'
|
|
||||||
|
|
||||||
import { installI18n } from '@/utils/i18n'
|
|
||||||
import { loadExtension } from '@/extension/extension-loader'
|
|
||||||
|
|
||||||
VFormRender.install = function (app) {
|
|
||||||
installI18n(app)
|
|
||||||
loadExtension(app)
|
|
||||||
|
|
||||||
app.use(ContainerItems)
|
|
||||||
registerIcon(app)
|
|
||||||
app.component(VFormRender.name, VFormRender)
|
|
||||||
}
|
|
||||||
|
|
||||||
const components = [
|
|
||||||
VFormRender
|
|
||||||
]
|
|
||||||
|
|
||||||
const install = (app) => {
|
|
||||||
installI18n(app)
|
|
||||||
loadExtension(app)
|
|
||||||
|
|
||||||
app.use(ContainerItems)
|
|
||||||
registerIcon(app)
|
|
||||||
components.forEach(component => {
|
|
||||||
app.component(component.name, component)
|
|
||||||
})
|
|
||||||
|
|
||||||
window.axios = axios
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof window !== 'undefined' && window.Vue) { /* script<EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD>ֵaxios<EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
|
|
||||||
//window.axios = axios
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
install,
|
|
||||||
VFormRender
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import VFormDesigner from '@/components/form-designer/index.vue'
|
|
||||||
import VFormRender from '@/components/form-render/index.vue'
|
|
||||||
|
|
||||||
import Draggable from '@/../lib/vuedraggable/dist/vuedraggable.umd.js'
|
|
||||||
import {registerIcon} from '@/utils/el-icons'
|
|
||||||
import 'virtual:svg-icons-register'
|
|
||||||
import '@/iconfont/iconfont.css'
|
|
||||||
|
|
||||||
import ContainerWidgets from '@/components/form-designer/form-widget/container-widget/index'
|
|
||||||
import ContainerItems from '@/components/form-render/container-item/index'
|
|
||||||
|
|
||||||
import { addDirective } from '@/utils/directive'
|
|
||||||
import { installI18n } from '@/utils/i18n'
|
|
||||||
import { loadExtension } from '@/extension/extension-loader'
|
|
||||||
|
|
||||||
|
|
||||||
VFormDesigner.install = function (app) {
|
|
||||||
addDirective(app)
|
|
||||||
installI18n(app)
|
|
||||||
loadExtension(app)
|
|
||||||
|
|
||||||
app.use(ContainerWidgets)
|
|
||||||
app.use(ContainerItems)
|
|
||||||
|
|
||||||
registerIcon(app)
|
|
||||||
app.component('draggable', Draggable)
|
|
||||||
app.component(VFormDesigner.name, VFormDesigner)
|
|
||||||
}
|
|
||||||
|
|
||||||
VFormRender.install = function (app) {
|
|
||||||
installI18n(app)
|
|
||||||
loadExtension(app)
|
|
||||||
|
|
||||||
app.use(ContainerItems)
|
|
||||||
|
|
||||||
registerIcon(app)
|
|
||||||
app.component(VFormRender.name, VFormRender)
|
|
||||||
}
|
|
||||||
|
|
||||||
const components = [
|
|
||||||
VFormDesigner,
|
|
||||||
VFormRender
|
|
||||||
]
|
|
||||||
|
|
||||||
const install = (app) => {
|
|
||||||
addDirective(app)
|
|
||||||
installI18n(app)
|
|
||||||
loadExtension(app)
|
|
||||||
|
|
||||||
app.use(ContainerWidgets)
|
|
||||||
app.use(ContainerItems)
|
|
||||||
|
|
||||||
registerIcon(app)
|
|
||||||
app.component('draggable', Draggable)
|
|
||||||
|
|
||||||
components.forEach(component => {
|
|
||||||
app.component(component.name, component)
|
|
||||||
})
|
|
||||||
|
|
||||||
window.axios = axios
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof window !== 'undefined' && window.Vue) { /* script<EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD>ֵaxios<EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
|
|
||||||
//window.axios = axios
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
install,
|
|
||||||
VFormDesigner,
|
|
||||||
VFormRender
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
import DVAFormDesigner from './components/DVAFormDesigner.vue'
|
|
||||||
|
|
||||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>浽һ<E6B5BD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
const components = [
|
|
||||||
DVAFormDesigner
|
|
||||||
]
|
|
||||||
|
|
||||||
// <20><><EFBFBD><EFBFBD> install <20><><EFBFBD><EFBFBD>
|
|
||||||
const install = function (Vue) {
|
|
||||||
|
|
||||||
if (install.installed) return
|
|
||||||
install.installed = true
|
|
||||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><D0B1><EFBFBD>ע<EFBFBD><D7A2>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
components.map(component => {
|
|
||||||
Vue.component(component.name, component) //component.name <20>˴<EFBFBD>ʹ<EFBFBD>õ<EFBFBD><C3B5><EFBFBD><EFBFBD><EFBFBD>vue<75>ļ<EFBFBD><C4BC>е<EFBFBD> name <20><><EFBFBD><EFBFBD>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof window !== 'undefined' && window.Vue) {
|
|
||||||
install(window.Vue)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
// <20><><EFBFBD><EFBFBD><EFBFBD>Ķ<EFBFBD><C4B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD>һ<EFBFBD><D2BB> install <20><><EFBFBD><EFBFBD>
|
|
||||||
install,
|
|
||||||
// <20><><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>
|
|
||||||
...components
|
|
||||||
}
|
|
||||||
@@ -39,3 +39,9 @@ export function DelObj(id: DelReq) {
|
|||||||
data: { id },
|
data: { id },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
export function GetPermission() {
|
||||||
|
return request({
|
||||||
|
url: apiPrefix + 'field_permission/',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,244 +1,202 @@
|
|||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
import {
|
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||||
dict,
|
import { dictionary } from '/@/utils/dictionary';
|
||||||
UserPageQuery,
|
import { successMessage } from '/@/utils/message';
|
||||||
AddReq,
|
import { auth } from '/@/utils/authFunction';
|
||||||
DelReq,
|
import tableSelector from '/@/components/tableSelector/index.vue';
|
||||||
EditReq,
|
import { shallowRef } from 'vue';
|
||||||
compute,
|
|
||||||
CreateCrudOptionsProps,
|
|
||||||
CreateCrudOptionsRet
|
|
||||||
} from '@fast-crud/fast-crud';
|
|
||||||
import {dictionary} from '/@/utils/dictionary';
|
|
||||||
import {successMessage} from '/@/utils/message';
|
|
||||||
import {auth} from "/@/utils/authFunction";
|
|
||||||
|
|
||||||
export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const pageRequest = async (query: UserPageQuery) => {
|
const pageRequest = async (query: UserPageQuery) => {
|
||||||
return await api.GetList(query);
|
return await api.GetList(query);
|
||||||
};
|
};
|
||||||
const editRequest = async ({form, row}: EditReq) => {
|
const editRequest = async ({ form, row }: EditReq) => {
|
||||||
form.id = row.id;
|
form.id = row.id;
|
||||||
return await api.UpdateObj(form);
|
return await api.UpdateObj(form);
|
||||||
};
|
};
|
||||||
const delRequest = async ({row}: DelReq) => {
|
const delRequest = async ({ row }: DelReq) => {
|
||||||
return await api.DelObj(row.id);
|
return await api.DelObj(row.id);
|
||||||
};
|
};
|
||||||
const addRequest = async ({form}: AddReq) => {
|
const addRequest = async ({ form }: AddReq) => {
|
||||||
return await api.AddObj(form);
|
return await api.AddObj(form);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 懒加载
|
* 懒加载
|
||||||
* @param row
|
* @param row
|
||||||
* @returns {Promise<unknown>}
|
* @returns {Promise<unknown>}
|
||||||
*/
|
*/
|
||||||
const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
|
const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
|
||||||
pageRequest({pcode: tree.code}).then((res: APIResponseData) => {
|
pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
|
||||||
resolve(res.data);
|
resolve(res.data);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
request: {
|
request: {
|
||||||
pageRequest,
|
pageRequest,
|
||||||
addRequest,
|
addRequest,
|
||||||
editRequest,
|
editRequest,
|
||||||
delRequest,
|
delRequest,
|
||||||
},
|
},
|
||||||
actionbar: {
|
actionbar: {
|
||||||
buttons: {
|
buttons: {
|
||||||
add: {
|
add: {
|
||||||
show: auth('area:Create'),
|
show: auth('area:Create'),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
//固定右侧
|
//固定右侧
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 200,
|
width: 200,
|
||||||
buttons: {
|
buttons: {
|
||||||
view: {
|
view: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
iconRight: 'Edit',
|
iconRight: 'Edit',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
show: auth('area:Update')
|
show: auth('area:Update'),
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
iconRight: 'Delete',
|
iconRight: 'Delete',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
show: auth('area:Delete')
|
show: auth('area:Delete'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pagination: {
|
pagination: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
table: {
|
table: {
|
||||||
rowKey: 'id',
|
rowKey: 'id',
|
||||||
lazy: true,
|
lazy: true,
|
||||||
load: loadContentMethod,
|
load: loadContentMethod,
|
||||||
treeProps: {children: 'children', hasChildren: 'hasChild'},
|
treeProps: { children: 'children', hasChildren: 'hasChild' },
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
_index: {
|
_index: {
|
||||||
title: '序号',
|
title: '序号',
|
||||||
form: {show: false},
|
form: { show: false },
|
||||||
column: {
|
column: {
|
||||||
type: 'index',
|
type: 'index',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: '70px',
|
width: '70px',
|
||||||
columnSetDisabled: true, //禁止在列设置中选择
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// pcode: {
|
name: {
|
||||||
// title: '父级地区',
|
title: '名称',
|
||||||
// show: false,
|
search: {
|
||||||
// search: {
|
show: true,
|
||||||
// show: true,
|
},
|
||||||
// },
|
treeNode: true,
|
||||||
// type: 'dict-tree',
|
type: 'input',
|
||||||
// form: {
|
column: {
|
||||||
// component: {
|
minWidth: 120,
|
||||||
// showAllLevels: false, // 仅显示最后一级
|
},
|
||||||
// props: {
|
form: {
|
||||||
// elProps: {
|
rules: [
|
||||||
// clearable: true,
|
// 表单校验规则
|
||||||
// showAllLevels: false, // 仅显示最后一级
|
{ required: true, message: '名称必填项' },
|
||||||
// props: {
|
],
|
||||||
// checkStrictly: true, // 可以不需要选到最后一级
|
component: {
|
||||||
// emitPath: false,
|
placeholder: '请输入名称',
|
||||||
// clearable: true,
|
},
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// },
|
pcode: {
|
||||||
// },
|
title: '父级地区',
|
||||||
// },
|
search: {
|
||||||
// },
|
disabled: true,
|
||||||
name: {
|
},
|
||||||
title: '名称',
|
width: 130,
|
||||||
search: {
|
type: 'table-selector',
|
||||||
show: true,
|
form: {
|
||||||
},
|
component: {
|
||||||
treeNode: true,
|
name: shallowRef(tableSelector),
|
||||||
type: 'input',
|
vModel: 'modelValue',
|
||||||
column: {
|
displayLabel: compute(({ row }) => {
|
||||||
minWidth: 120,
|
if (row) {
|
||||||
},
|
return row.pcode_info;
|
||||||
form: {
|
}
|
||||||
rules: [
|
return null;
|
||||||
// 表单校验规则
|
}),
|
||||||
{required: true, message: '名称必填项'},
|
tableConfig: {
|
||||||
],
|
url: '/api/system/area/',
|
||||||
component: {
|
label: 'name',
|
||||||
placeholder: '请输入名称',
|
value: 'id',
|
||||||
},
|
isTree: true,
|
||||||
},
|
isMultiple: false,
|
||||||
},
|
lazy: true,
|
||||||
code: {
|
load: loadContentMethod,
|
||||||
title: '地区编码',
|
treeProps: { children: 'children', hasChildren: 'hasChild' },
|
||||||
search: {
|
columns: [
|
||||||
show: true,
|
{
|
||||||
},
|
prop: 'name',
|
||||||
type: 'input',
|
label: '地区',
|
||||||
column: {
|
width: 150,
|
||||||
minWidth: 90,
|
},
|
||||||
},
|
{
|
||||||
form: {
|
prop: 'code',
|
||||||
rules: [
|
label: '地区编码',
|
||||||
// 表单校验规则
|
},
|
||||||
{required: true, message: '地区编码必填项'},
|
],
|
||||||
],
|
},
|
||||||
component: {
|
},
|
||||||
placeholder: '请输入地区编码',
|
},
|
||||||
},
|
column: {
|
||||||
},
|
show: false,
|
||||||
},
|
},
|
||||||
pinyin: {
|
},
|
||||||
title: '拼音',
|
code: {
|
||||||
search: {
|
title: '地区编码',
|
||||||
disabled: true,
|
search: {
|
||||||
},
|
show: true,
|
||||||
type: 'input',
|
},
|
||||||
column: {
|
type: 'input',
|
||||||
minWidth: 120,
|
column: {
|
||||||
},
|
minWidth: 90,
|
||||||
form: {
|
},
|
||||||
rules: [
|
form: {
|
||||||
// 表单校验规则
|
rules: [
|
||||||
{required: true, message: '拼音必填项'},
|
// 表单校验规则
|
||||||
],
|
{ required: true, message: '地区编码必填项' },
|
||||||
component: {
|
],
|
||||||
placeholder: '请输入拼音',
|
component: {
|
||||||
},
|
placeholder: '请输入地区编码',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
level: {
|
},
|
||||||
title: '地区层级',
|
enable: {
|
||||||
search: {
|
title: '是否启用',
|
||||||
disabled: true,
|
search: {
|
||||||
},
|
show: true,
|
||||||
type: 'input',
|
},
|
||||||
column: {
|
type: 'dict-radio',
|
||||||
minWidth: 100,
|
column: {
|
||||||
},
|
minWidth: 90,
|
||||||
form: {
|
component: {
|
||||||
disabled: false,
|
name: 'fs-dict-switch',
|
||||||
rules: [
|
activeText: '',
|
||||||
// 表单校验规则
|
inactiveText: '',
|
||||||
{required: true, message: '拼音必填项'},
|
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
||||||
],
|
onChange: compute((context) => {
|
||||||
component: {
|
return () => {
|
||||||
placeholder: '请输入拼音',
|
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||||
},
|
successMessage(res.msg as string);
|
||||||
},
|
});
|
||||||
},
|
};
|
||||||
initials: {
|
}),
|
||||||
title: '首字母',
|
},
|
||||||
column: {
|
},
|
||||||
minWidth: 100,
|
dict: dict({
|
||||||
},
|
data: dictionary('button_status_bool'),
|
||||||
form: {
|
}),
|
||||||
rules: [
|
},
|
||||||
// 表单校验规则
|
},
|
||||||
{required: true, message: '首字母必填项'},
|
},
|
||||||
],
|
};
|
||||||
|
|
||||||
component: {
|
|
||||||
placeholder: '请输入首字母',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
enable: {
|
|
||||||
title: '是否启用',
|
|
||||||
search: {
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
type: 'dict-radio',
|
|
||||||
column: {
|
|
||||||
minWidth: 90,
|
|
||||||
component: {
|
|
||||||
name: 'fs-dict-switch',
|
|
||||||
activeText: '',
|
|
||||||
inactiveText: '',
|
|
||||||
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
|
||||||
onChange: compute((context) => {
|
|
||||||
return () => {
|
|
||||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
|
||||||
successMessage(res.msg as string);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dict: dict({
|
|
||||||
data: dictionary('button_status_bool'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,14 +5,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="areas">
|
<script lang="ts" setup name="areas">
|
||||||
import { ref, onMounted } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import { useFs } from '@fast-crud/fast-crud';
|
import { useFs } from '@fast-crud/fast-crud';
|
||||||
import { createCrudOptions } from './crud';
|
import { createCrudOptions } from './crud';
|
||||||
|
import { GetPermission } from './api';
|
||||||
|
import { handleColumnPermission } from '/@/utils/columnPermission';
|
||||||
|
|
||||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ createCrudOptions });
|
||||||
|
|
||||||
// 页面打开后获取列表数据
|
// 页面打开后获取列表数据
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
|
// 设置列权限
|
||||||
|
const newOptions = await handleColumnPermission(GetPermission, crudOptions);
|
||||||
|
//重置crudBinding
|
||||||
|
resetCrudOptions(newOptions);
|
||||||
|
// 刷新
|
||||||
crudExpose.doRefresh();
|
crudExpose.doRefresh();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ type GetListType = PageQuery & { show_all: string };
|
|||||||
|
|
||||||
export const apiPrefix = '/api/system/user/';
|
export const apiPrefix = '/api/system/user/';
|
||||||
|
|
||||||
export function GetDept(query: PageQuery) {
|
// export function GetDept(query: PageQuery) {
|
||||||
return request({
|
// return request({
|
||||||
url: '/api/system/dept/dept_lazy_tree/',
|
// url: '/api/system/dept/dept_all/',
|
||||||
method: 'get',
|
// method: 'get',
|
||||||
params: query,
|
// params: query,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function GetList(query: GetListType) {
|
export function GetList(query: GetListType) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -220,7 +220,10 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
label: 'name',
|
label: 'name',
|
||||||
}),
|
}),
|
||||||
column: {
|
column: {
|
||||||
minWidth: 150, //最小列宽
|
minWidth: 200, //最小列宽
|
||||||
|
formatter({value,row,index}){
|
||||||
|
return row.dept_name_all
|
||||||
|
}
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -259,7 +262,11 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
label: 'name',
|
label: 'name',
|
||||||
}),
|
}),
|
||||||
column: {
|
column: {
|
||||||
minWidth: 100, //最小列宽
|
minWidth: 200, //最小列宽
|
||||||
|
formatter({value,row,index}){
|
||||||
|
const values = row.role_info.map((item:any) => item.name);
|
||||||
|
return values.join(',')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -382,6 +389,10 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
form: {
|
form: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
|
column:{
|
||||||
|
width:150,
|
||||||
|
showOverflowTooltip: true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -277,7 +277,8 @@ const { resetCrudOptions } = useCrud({
|
|||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
border-radius: 8px 0 0 8px;
|
border-radius: 8px 0 0 8px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: #fff;
|
color: var(--next-bg-topBarColor);
|
||||||
|
background-color: var(--el-fill-color-blank);;
|
||||||
}
|
}
|
||||||
.dept-user-com-table {
|
.dept-user-com-table {
|
||||||
height: calc(100% - 200px);
|
height: calc(100% - 200px);
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dept-left {
|
.dept-left {
|
||||||
background-color: #fff;
|
background-color: var(--el-fill-color-blank);;
|
||||||
border-radius: 0 8px 8px 0;
|
border-radius: 0 8px 8px 0;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,3 +39,10 @@ export function DelObj(id: DelReq) {
|
|||||||
data: { id },
|
data: { id },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetPermission() {
|
||||||
|
return request({
|
||||||
|
url: apiPrefix + 'field_permission/',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,14 +5,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="loginLog">
|
<script lang="ts" setup name="loginLog">
|
||||||
import { ref, onMounted } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import { useFs } from '@fast-crud/fast-crud';
|
import { useFs } from '@fast-crud/fast-crud';
|
||||||
import { createCrudOptions } from './crud';
|
import { createCrudOptions } from './crud';
|
||||||
|
import { GetPermission } from './api';
|
||||||
|
import { handleColumnPermission } from '/@/utils/columnPermission';
|
||||||
|
|
||||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ createCrudOptions });
|
||||||
|
|
||||||
// 页面打开后获取列表数据
|
// 页面打开后获取列表数据
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
|
// 设置列权限
|
||||||
|
const newOptions = await handleColumnPermission(GetPermission, crudOptions);
|
||||||
|
//重置crudBinding
|
||||||
|
resetCrudOptions(newOptions);
|
||||||
|
// 刷新
|
||||||
crudExpose.doRefresh();
|
crudExpose.doRefresh();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -48,3 +48,10 @@ export function BatchAdd(obj: AddReq) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function BatchDelete(keys: any) {
|
||||||
|
return request({
|
||||||
|
url: apiPrefix + 'multiple_delete/',
|
||||||
|
method: 'delete',
|
||||||
|
data: { keys },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {auth} from '/@/utils/authFunction'
|
|||||||
import {request} from '/@/utils/service';
|
import {request} from '/@/utils/service';
|
||||||
import { successNotification } from '/@/utils/message';
|
import { successNotification } from '/@/utils/message';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { nextTick, ref } from 'vue';
|
||||||
|
import XEUtils from 'xe-utils';
|
||||||
//此处为crudOptions配置
|
//此处为crudOptions配置
|
||||||
export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const pageRequest = async () => {
|
const pageRequest = async () => {
|
||||||
@@ -22,7 +24,42 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
|
|||||||
const addRequest = async ({form}: AddReq) => {
|
const addRequest = async ({form}: AddReq) => {
|
||||||
return await api.AddObj({...form, ...{menu: context!.selectOptions.value.id}});
|
return await api.AddObj({...form, ...{menu: context!.selectOptions.value.id}});
|
||||||
};
|
};
|
||||||
|
// 记录选中的行
|
||||||
|
const selectedRows = ref<any>([]);
|
||||||
|
|
||||||
|
const onSelectionChange = (changed: any) => {
|
||||||
|
const tableData = crudExpose.getTableData();
|
||||||
|
const unChanged = tableData.filter((row: any) => !changed.includes(row));
|
||||||
|
// 添加已选择的行
|
||||||
|
XEUtils.arrayEach(changed, (item: any) => {
|
||||||
|
const ids = XEUtils.pluck(selectedRows.value, 'id');
|
||||||
|
if (!ids.includes(item.id)) {
|
||||||
|
selectedRows.value = XEUtils.union(selectedRows.value, [item]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 剔除未选择的行
|
||||||
|
XEUtils.arrayEach(unChanged, (unItem: any) => {
|
||||||
|
selectedRows.value = XEUtils.remove(selectedRows.value, (item: any) => item.id !== unItem.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const toggleRowSelection = () => {
|
||||||
|
// 多选后,回显默认勾选
|
||||||
|
const tableRef = crudExpose.getBaseTableRef();
|
||||||
|
const tableData = crudExpose.getTableData();
|
||||||
|
const selected = XEUtils.filter(tableData, (item: any) => {
|
||||||
|
const ids = XEUtils.pluck(selectedRows.value, 'id');
|
||||||
|
return ids.includes(item.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
XEUtils.arrayEach(selected, (item) => {
|
||||||
|
tableRef.toggleRowSelection(item, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
selectedRows,
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
pagination:{
|
pagination:{
|
||||||
show:false
|
show:false
|
||||||
@@ -84,6 +121,11 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
|
|||||||
editRequest,
|
editRequest,
|
||||||
delRequest,
|
delRequest,
|
||||||
},
|
},
|
||||||
|
table: {
|
||||||
|
rowKey: 'id', //设置你的主键id, 默认rowKey=id
|
||||||
|
onSelectionChange,
|
||||||
|
onRefreshed: () => toggleRowSelection(),
|
||||||
|
},
|
||||||
form: {
|
form: {
|
||||||
col: {span: 24},
|
col: {span: 24},
|
||||||
labelWidth: '100px',
|
labelWidth: '100px',
|
||||||
@@ -93,6 +135,16 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
|
$checked: {
|
||||||
|
title: '选择',
|
||||||
|
form: { show: false },
|
||||||
|
column: {
|
||||||
|
type: 'selection',
|
||||||
|
align: 'center',
|
||||||
|
width: '70px',
|
||||||
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
|
},
|
||||||
|
},
|
||||||
_index: {
|
_index: {
|
||||||
title: '序号',
|
title: '序号',
|
||||||
form: {show: false},
|
form: {show: false},
|
||||||
|
|||||||
@@ -1,19 +1,72 @@
|
|||||||
<template>
|
<template>
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
|
<template #pagination-left>
|
||||||
|
<el-tooltip content="批量删除">
|
||||||
|
<el-button text type="danger" :disabled="selectedRowsCount === 0" :icon="Delete" circle @click="handleBatchDelete" />
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<template #pagination-right>
|
||||||
|
<el-popover placement="top" :width="400" trigger="click">
|
||||||
|
<template #reference>
|
||||||
|
<el-button text :type="selectedRowsCount > 0 ? 'primary' : ''">已选中{{ selectedRowsCount }}条数据</el-button>
|
||||||
|
</template>
|
||||||
|
<el-table :data="selectedRows" size="small">
|
||||||
|
<el-table-column width="150" property="id" label="id" />
|
||||||
|
<el-table-column fixed="right" label="操作" min-width="60">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button text type="info" :icon="Close" @click="removeSelectedRows(scope.row)" circle />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
</fs-crud>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useFs } from '@fast-crud/fast-crud';
|
import { useFs } from '@fast-crud/fast-crud';
|
||||||
import { createCrudOptions } from './crud';
|
import { createCrudOptions } from './crud';
|
||||||
import { MenuTreeItemType } from '../../types';
|
import { MenuTreeItemType } from '../../types';
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
import XEUtils from 'xe-utils';
|
||||||
|
import { BatchDelete } from './api';
|
||||||
|
import { Close, Delete } from '@element-plus/icons-vue';
|
||||||
// 当前选择的菜单信息
|
// 当前选择的菜单信息
|
||||||
let selectOptions: any = ref({ name: null });
|
let selectOptions: any = ref({ name: null });
|
||||||
|
|
||||||
const { crudRef, crudBinding, crudExpose, context } = useFs({ createCrudOptions, context: { selectOptions } });
|
const { crudRef, crudBinding, crudExpose, context,selectedRows } = useFs({ createCrudOptions, context: { selectOptions } });
|
||||||
const { doRefresh, setTableData } = crudExpose;
|
const { doRefresh, setTableData } = crudExpose;
|
||||||
|
|
||||||
|
// 选中行的条数
|
||||||
|
const selectedRowsCount = computed(() => {
|
||||||
|
return selectedRows.value.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = async () => {
|
||||||
|
await ElMessageBox.confirm(`确定要批量删除这${selectedRows.value.length}条记录吗`, '确认', {
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
closeOnClickModal: false,
|
||||||
|
});
|
||||||
|
await BatchDelete(XEUtils.pluck(selectedRows.value, 'id'));
|
||||||
|
ElMessage.info('删除成功');
|
||||||
|
selectedRows.value = [];
|
||||||
|
await crudExpose.doRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除已选中的行
|
||||||
|
const removeSelectedRows = (row: any) => {
|
||||||
|
const tableRef = crudExpose.getBaseTableRef();
|
||||||
|
const tableData = crudExpose.getTableData();
|
||||||
|
if (XEUtils.pluck(tableData, 'id').includes(row.id)) {
|
||||||
|
tableRef.toggleRowSelection(row, false);
|
||||||
|
} else {
|
||||||
|
selectedRows.value = XEUtils.remove(selectedRows.value, (item: any) => item.id !== row.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
const handleRefreshTable = (record: MenuTreeItemType) => {
|
const handleRefreshTable = (record: MenuTreeItemType) => {
|
||||||
if (!record.is_catalog && record.id) {
|
if (!record.is_catalog && record.id) {
|
||||||
selectOptions.value = record;
|
selectOptions.value = record;
|
||||||
|
|||||||
@@ -42,6 +42,13 @@ export function DelObj(id: DelReq) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function BatchDelete(keys: any) {
|
||||||
|
return request({
|
||||||
|
url: apiPrefix + 'multiple_delete/',
|
||||||
|
method: 'delete',
|
||||||
|
data: { keys },
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 获取所有model
|
* 获取所有model
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import * as api from './api';
|
|||||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||||
import { request } from '/@/utils/service';
|
import { request } from '/@/utils/service';
|
||||||
import { dictionary } from '/@/utils/dictionary';
|
import { dictionary } from '/@/utils/dictionary';
|
||||||
import { inject } from 'vue';
|
import { inject, nextTick, ref } from 'vue';
|
||||||
import {auth} from "/@/utils/authFunction";
|
import {auth} from "/@/utils/authFunction";
|
||||||
|
import XEUtils from 'xe-utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -27,8 +28,41 @@ export const createCrudOptions = function ({ crudExpose, props,modelDialog,selec
|
|||||||
form.menu = selectOptions.value.id;
|
form.menu = selectOptions.value.id;
|
||||||
return await api.AddObj(form);
|
return await api.AddObj(form);
|
||||||
};
|
};
|
||||||
|
// 记录选中的行
|
||||||
|
const selectedRows = ref<any>([]);
|
||||||
|
|
||||||
|
const onSelectionChange = (changed: any) => {
|
||||||
|
const tableData = crudExpose.getTableData();
|
||||||
|
const unChanged = tableData.filter((row: any) => !changed.includes(row));
|
||||||
|
// 添加已选择的行
|
||||||
|
XEUtils.arrayEach(changed, (item: any) => {
|
||||||
|
const ids = XEUtils.pluck(selectedRows.value, 'id');
|
||||||
|
if (!ids.includes(item.id)) {
|
||||||
|
selectedRows.value = XEUtils.union(selectedRows.value, [item]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 剔除未选择的行
|
||||||
|
XEUtils.arrayEach(unChanged, (unItem: any) => {
|
||||||
|
selectedRows.value = XEUtils.remove(selectedRows.value, (item: any) => item.id !== unItem.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const toggleRowSelection = () => {
|
||||||
|
// 多选后,回显默认勾选
|
||||||
|
const tableRef = crudExpose.getBaseTableRef();
|
||||||
|
const tableData = crudExpose.getTableData();
|
||||||
|
const selected = XEUtils.filter(tableData, (item: any) => {
|
||||||
|
const ids = XEUtils.pluck(selectedRows.value, 'id');
|
||||||
|
return ids.includes(item.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
XEUtils.arrayEach(selected, (item) => {
|
||||||
|
tableRef.toggleRowSelection(item, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
|
selectedRows,
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
request: {
|
request: {
|
||||||
pageRequest,
|
pageRequest,
|
||||||
@@ -77,7 +111,22 @@ export const createCrudOptions = function ({ crudExpose, props,modelDialog,selec
|
|||||||
width: '600px',
|
width: '600px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
table: {
|
||||||
|
rowKey: 'id', //设置你的主键id, 默认rowKey=id
|
||||||
|
onSelectionChange,
|
||||||
|
onRefreshed: () => toggleRowSelection(),
|
||||||
|
},
|
||||||
columns: {
|
columns: {
|
||||||
|
$checked: {
|
||||||
|
title: '选择',
|
||||||
|
form: { show: false },
|
||||||
|
column: {
|
||||||
|
type: 'selection',
|
||||||
|
align: 'center',
|
||||||
|
width: '70px',
|
||||||
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
|
},
|
||||||
|
},
|
||||||
_index: {
|
_index: {
|
||||||
title: '序号',
|
title: '序号',
|
||||||
form: { show: false },
|
form: { show: false },
|
||||||
|
|||||||
@@ -1,137 +1,177 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-dialog ref="modelRef" v-model="modelDialog" title="选择model">
|
<el-dialog ref="modelRef" v-model="modelDialog" title="选择model">
|
||||||
<div v-show="props.model">
|
<div v-show="props.model">
|
||||||
<el-tag>已选择:{{ props.model }}</el-tag>
|
<el-tag>已选择:{{ props.model }}</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<!-- 搜索输入框 -->
|
<!-- 搜索输入框 -->
|
||||||
<el-input
|
<el-input v-model="searchQuery" placeholder="搜索模型..." style="margin-bottom: 10px"></el-input>
|
||||||
v-model="searchQuery"
|
<div class="model-card">
|
||||||
placeholder="搜索模型..."
|
<!--注释编号:django-vue3-admin-index483211: 对请求回来的allModelData进行computed计算,返加搜索框匹配到的内容-->
|
||||||
style="margin-bottom: 10px;"
|
<div v-for="(item, index) in filteredModelData" :value="item.key" :key="index">
|
||||||
></el-input>
|
<el-text :type="modelCheckIndex === index ? 'primary' : ''" @click="onModelChecked(item, index)">
|
||||||
<div class="model-card">
|
{{ item.app + '--' + item.title + '(' + item.key + ')' }}
|
||||||
<!--注释编号:django-vue3-admin-index483211: 对请求回来的allModelData进行computed计算,返加搜索框匹配到的内容-->
|
</el-text>
|
||||||
<div v-for="(item,index) in filteredModelData" :value="item.key" :key="index">
|
</div>
|
||||||
<el-text :type="modelCheckIndex===index?'primary':''" @click="onModelChecked(item,index)">
|
</div>
|
||||||
{{ item.app + '--' + item.title + '(' + item.key + ')' }}
|
<template #footer>
|
||||||
</el-text>
|
<span class="dialog-footer">
|
||||||
</div>
|
<el-button @click="modelDialog = false">取消</el-button>
|
||||||
</div>
|
<el-button type="primary" @click="handleAutomatch"> 确定 </el-button>
|
||||||
<template #footer>
|
</span>
|
||||||
<span class="dialog-footer">
|
</template>
|
||||||
<el-button @click="modelDialog = false">取消</el-button>
|
</el-dialog>
|
||||||
<el-button type="primary" @click="handleAutomatch">
|
<div style="height: 72vh">
|
||||||
确定
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
</el-button>
|
<template #pagination-left>
|
||||||
</span>
|
<el-tooltip content="批量删除">
|
||||||
</template>
|
<el-button text type="danger" :disabled="selectedRowsCount === 0" :icon="Delete" circle @click="handleBatchDelete" />
|
||||||
</el-dialog>
|
</el-tooltip>
|
||||||
<div style="height: 80vh">
|
</template>
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
<template #pagination-right>
|
||||||
</fs-crud>
|
<el-popover placement="top" :width="400" trigger="click">
|
||||||
|
<template #reference>
|
||||||
</div>
|
<el-button text :type="selectedRowsCount > 0 ? 'primary' : ''">已选中{{ selectedRowsCount }}条数据</el-button>
|
||||||
</div>
|
</template>
|
||||||
|
<el-table :data="selectedRows" size="small">
|
||||||
|
<el-table-column width="150" property="id" label="id" />
|
||||||
|
<el-table-column fixed="right" label="操作" min-width="60">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button text type="info" :icon="Close" @click="removeSelectedRows(scope.row)" circle />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
</fs-crud>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {ref, onMounted, reactive, computed } from 'vue';
|
import { ref, onMounted, reactive, computed } from 'vue';
|
||||||
import {useFs} from '@fast-crud/fast-crud';
|
import { useFs } from '@fast-crud/fast-crud';
|
||||||
import {createCrudOptions} from './crud';
|
import { createCrudOptions } from './crud';
|
||||||
import {getModelList} from './api'
|
import { BatchDelete, getModelList } from './api';
|
||||||
import {MenuTreeItemType} from "/@/views/system/menu/types";
|
import { Close, Delete } from '@element-plus/icons-vue';
|
||||||
import {successMessage, successNotification, warningNotification} from '/@/utils/message';
|
import { MenuTreeItemType } from '/@/views/system/menu/types';
|
||||||
import {automatchColumnsData} from '/@/views/system/columns/components/ColumnsTableCom/api';
|
import { successMessage, successNotification, warningNotification } from '/@/utils/message';
|
||||||
|
import { automatchColumnsData } from '/@/views/system/columns/components/ColumnsTableCom/api';
|
||||||
|
import XEUtils from 'xe-utils';
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
// 当前选择的菜单信息
|
// 当前选择的菜单信息
|
||||||
let selectOptions: any = ref({name: null});
|
let selectOptions: any = ref({ name: null });
|
||||||
|
|
||||||
const props = reactive({
|
const props = reactive({
|
||||||
model: '',
|
model: '',
|
||||||
app: '',
|
app: '',
|
||||||
menu: ''
|
menu: '',
|
||||||
})
|
});
|
||||||
|
|
||||||
//model弹窗
|
//model弹窗
|
||||||
const modelDialog = ref(false)
|
const modelDialog = ref(false);
|
||||||
// 获取所有model
|
// 获取所有model
|
||||||
const allModelData = ref<any[]>([]);
|
const allModelData = ref<any[]>([]);
|
||||||
const modelCheckIndex = ref(null)
|
const modelCheckIndex = ref(null);
|
||||||
const onModelChecked = (row, index) => {
|
const onModelChecked = (row, index) => {
|
||||||
modelCheckIndex.value = index
|
modelCheckIndex.value = index;
|
||||||
props.model = row.key
|
props.model = row.key;
|
||||||
props.app = row.app
|
props.app = row.app;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
// 注释编号:django-vue3-admin-index083311:代码开始行
|
// 注释编号:django-vue3-admin-index083311:代码开始行
|
||||||
// 功能说明:搭配搜索的处理,返回搜索结果
|
// 功能说明:搭配搜索的处理,返回搜索结果
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
|
|
||||||
const filteredModelData = computed(() => {
|
const filteredModelData = computed(() => {
|
||||||
if (!searchQuery.value) {
|
if (!searchQuery.value) {
|
||||||
return allModelData.value;
|
return allModelData.value;
|
||||||
}
|
}
|
||||||
const query = searchQuery.value.toLowerCase();
|
const query = searchQuery.value.toLowerCase();
|
||||||
return allModelData.value.filter(item =>
|
return allModelData.value.filter(
|
||||||
item.app.toLowerCase().includes(query) ||
|
(item) => item.app.toLowerCase().includes(query) || item.title.toLowerCase().includes(query) || item.key.toLowerCase().includes(query)
|
||||||
item.title.toLowerCase().includes(query) ||
|
);
|
||||||
item.key.toLowerCase().includes(query)
|
});
|
||||||
);
|
|
||||||
});
|
|
||||||
// 注释编号:django-vue3-admin-index083311:代码结束行
|
// 注释编号:django-vue3-admin-index083311:代码结束行
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜单选中时,加载表格数据
|
* 菜单选中时,加载表格数据
|
||||||
* @param record
|
* @param record
|
||||||
*/
|
*/
|
||||||
const handleRefreshTable = (record: MenuTreeItemType) => {
|
const handleRefreshTable = (record: MenuTreeItemType) => {
|
||||||
if (!record.is_catalog && record.id) {
|
if (!record.is_catalog && record.id) {
|
||||||
selectOptions.value = record;
|
selectOptions.value = record;
|
||||||
crudExpose.doRefresh();
|
crudExpose.doRefresh();
|
||||||
} else {
|
} else {
|
||||||
//清空表格数据
|
//清空表格数据
|
||||||
crudExpose.setTableData([]);
|
crudExpose.setTableData([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 自动匹配列
|
* 自动匹配列
|
||||||
*/
|
*/
|
||||||
const handleAutomatch = async () => {
|
const handleAutomatch = async () => {
|
||||||
props.menu = selectOptions.value.id
|
props.menu = selectOptions.value.id;
|
||||||
modelDialog.value = false
|
modelDialog.value = false;
|
||||||
if (props.menu && props.model) {
|
if (props.menu && props.model) {
|
||||||
const res = await automatchColumnsData(props);
|
const res = await automatchColumnsData(props);
|
||||||
if (res?.code === 2000) {
|
if (res?.code === 2000) {
|
||||||
successNotification('匹配成功');
|
successNotification('匹配成功');
|
||||||
}
|
}
|
||||||
crudExpose.doSearch({form: {menu: props.menu, model: props.model}});
|
crudExpose.doSearch({ form: { menu: props.menu, model: props.model } });
|
||||||
}else {
|
} else {
|
||||||
warningNotification('请选择角色和模型表!');
|
warningNotification('请选择角色和模型表!');
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 选中行的条数
|
||||||
const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, props, modelDialog, selectOptions,allModelData});
|
const selectedRowsCount = computed(() => {
|
||||||
onMounted(async () => {
|
return selectedRows.value.length;
|
||||||
const res = await getModelList();
|
|
||||||
allModelData.value = res.data;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({selectOptions, handleRefreshTable});
|
// 批量删除
|
||||||
|
const handleBatchDelete = async () => {
|
||||||
|
await ElMessageBox.confirm(`确定要批量删除这${selectedRows.value.length}条记录吗`, '确认', {
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
closeOnClickModal: false,
|
||||||
|
});
|
||||||
|
await BatchDelete(XEUtils.pluck(selectedRows.value, 'id'));
|
||||||
|
ElMessage.info('删除成功');
|
||||||
|
selectedRows.value = [];
|
||||||
|
await crudExpose.doRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除已选中的行
|
||||||
|
const removeSelectedRows = (row: any) => {
|
||||||
|
const tableRef = crudExpose.getBaseTableRef();
|
||||||
|
const tableData = crudExpose.getTableData();
|
||||||
|
if (XEUtils.pluck(tableData, 'id').includes(row.id)) {
|
||||||
|
tableRef.toggleRowSelection(row, false);
|
||||||
|
} else {
|
||||||
|
selectedRows.value = XEUtils.remove(selectedRows.value, (item: any) => item.id !== row.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { crudBinding, crudRef, crudExpose, selectedRows } = useFs({ createCrudOptions, props, modelDialog, selectOptions, allModelData });
|
||||||
|
onMounted(async () => {
|
||||||
|
const res = await getModelList();
|
||||||
|
allModelData.value = res.data;
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({ selectOptions, handleRefreshTable });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.model-card {
|
.model-card {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
height: 30vh;
|
height: 30vh;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,21 +10,12 @@
|
|||||||
<el-input v-model="menuFormData.name" placeholder="请输入菜单名称" />
|
<el-input v-model="menuFormData.name" placeholder="请输入菜单名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="父级菜单" prop="parent">
|
<el-form-item label="父级菜单" prop="parent">
|
||||||
<el-tree-select
|
<el-tree-select v-model="menuFormData.parent" :props="defaultTreeProps" :data="deptDefaultList"
|
||||||
v-model="menuFormData.parent"
|
:cache-data="props.cacheData" lazy check-strictly clearable :load="handleTreeLoad"
|
||||||
:props="defaultTreeProps"
|
placeholder="请选择父级菜单" style="width: 100%" />
|
||||||
:data="deptDefaultList"
|
|
||||||
:cache-data="props.cacheData"
|
|
||||||
lazy
|
|
||||||
check-strictly
|
|
||||||
clearable
|
|
||||||
:load="handleTreeLoad"
|
|
||||||
placeholder="请选择父级菜单"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="路由地址" prop="web_path">
|
<el-form-item label="路由地址" prop="web_path">
|
||||||
<el-input v-model="menuFormData.web_path" placeholder="请输入路由地址,请以/开头" />
|
<el-input v-model="menuFormData.web_path" placeholder="请输入路由地址,请以/开头" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -35,12 +26,14 @@
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item required label="状态">
|
<el-form-item required label="状态">
|
||||||
<el-switch v-model="menuFormData.status" width="60" inline-prompt active-text="启用" inactive-text="禁用" />
|
<el-switch v-model="menuFormData.status" width="60" inline-prompt active-text="启用"
|
||||||
|
inactive-text="禁用" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item v-if="menuFormData.status" required label="侧边显示">
|
<el-form-item v-if="menuFormData.status" required label="侧边显示">
|
||||||
<el-switch v-model="menuFormData.visible" width="60" inline-prompt active-text="显示" inactive-text="隐藏" />
|
<el-switch v-model="menuFormData.visible" width="60" inline-prompt active-text="显示"
|
||||||
|
inactive-text="隐藏" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -48,46 +41,45 @@
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item required label="是否目录">
|
<el-form-item required label="是否目录">
|
||||||
<el-switch v-model="menuFormData.is_catalog" width="60" inline-prompt active-text="是" inactive-text="否" />
|
<el-switch v-model="menuFormData.is_catalog" width="60" inline-prompt active-text="是"
|
||||||
|
inactive-text="否" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item v-if="!menuFormData.is_catalog" required label="外链接">
|
<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-switch v-model="menuFormData.is_link" width="60" inline-prompt active-text="是"
|
||||||
|
inactive-text="否" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item required v-if="!menuFormData.is_catalog" label="是否固定">
|
||||||
|
<el-switch v-model="menuFormData.is_affix" 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 && menuFormData.is_link" required label="是否内嵌">
|
||||||
|
<el-switch v-model="menuFormData.is_iframe" width="60" inline-prompt active-text="是"
|
||||||
|
inactive-text="否" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item required v-if="!menuFormData.is_catalog" label="是否固定">
|
|
||||||
<el-switch v-model="menuFormData.is_affix" 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 && menuFormData.is_link" required label="是否内嵌">
|
|
||||||
<el-switch v-model="menuFormData.is_iframe" width="60" inline-prompt active-text="是" inactive-text="否" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-form-item label="备注">
|
<el-form-item label="备注">
|
||||||
<el-input v-model="menuFormData.description" maxlength="200" show-word-limit type="textarea" placeholder="请输入备注" />
|
<el-input v-model="menuFormData.description" maxlength="200" show-word-limit type="textarea"
|
||||||
|
placeholder="请输入备注" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-divider></el-divider>
|
<el-divider></el-divider>
|
||||||
|
|
||||||
<div style="min-height: 184px">
|
<div style="min-height: 184px">
|
||||||
<el-form-item v-if="!menuFormData.is_catalog && !menuFormData.is_link" label="组件地址" prop="component">
|
<el-form-item v-if="!menuFormData.is_catalog && !menuFormData.is_link" label="组件地址" prop="component">
|
||||||
<el-autocomplete
|
<el-autocomplete class="w-full" v-model="menuFormData.component" :fetch-suggestions="querySearch"
|
||||||
class="w-full"
|
:trigger-on-focus="false" clearable :debounce="100" placeholder="输入组件地址" />
|
||||||
v-model="menuFormData.component"
|
|
||||||
:fetch-suggestions="querySearch"
|
|
||||||
:trigger-on-focus="false"
|
|
||||||
clearable
|
|
||||||
:debounce="100"
|
|
||||||
placeholder="输入组件地址"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item v-if="!menuFormData.is_catalog && !menuFormData.is_link" label="组件名称" prop="component_name">
|
<el-form-item v-if="!menuFormData.is_catalog && !menuFormData.is_link" label="组件名称"
|
||||||
|
prop="component_name">
|
||||||
<el-input v-model="menuFormData.component_name" placeholder="请输入组件名称" />
|
<el-input v-model="menuFormData.component_name" placeholder="请输入组件名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -96,7 +88,8 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item v-if="!menuFormData.is_catalog" label="缓存">
|
<el-form-item v-if="!menuFormData.is_catalog" label="缓存">
|
||||||
<el-switch v-model="menuFormData.cache" width="60" inline-prompt active-text="启用" inactive-text="禁用" />
|
<el-switch v-model="menuFormData.cache" width="60" inline-prompt active-text="启用"
|
||||||
|
inactive-text="禁用" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -111,6 +104,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import XEUtils from 'xe-utils';
|
||||||
import { ref, onMounted, reactive } from 'vue';
|
import { ref, onMounted, reactive } from 'vue';
|
||||||
import { ElForm, FormRules } from 'element-plus';
|
import { ElForm, FormRules } from 'element-plus';
|
||||||
import IconSelector from '/@/components/iconSelector/index.vue';
|
import IconSelector from '/@/components/iconSelector/index.vue';
|
||||||
@@ -148,14 +142,14 @@ const validateWebPath = (rule: any, value: string, callback: Function) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const validateLinkUrl = (rule: any, value: string, callback: Function) => {
|
const validateLinkUrl = (rule: any, value: string, callback: Function) => {
|
||||||
let pattern = /^\/.*?/;
|
let pattern = /^\/.*?/;
|
||||||
let patternUrl = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
|
let patternUrl = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
|
||||||
const reg = pattern.test(value) || patternUrl.test(value)
|
const reg = pattern.test(value) || patternUrl.test(value)
|
||||||
if (reg) {
|
if (reg) {
|
||||||
callback();
|
callback();
|
||||||
} else {
|
} else {
|
||||||
callback(new Error('请输入正确的地址'));
|
callback(new Error('请输入正确的地址'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = withDefaults(defineProps<IProps>(), {
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
@@ -172,7 +166,7 @@ const rules = reactive<FormRules>({
|
|||||||
name: [{ required: true, message: '菜单名称必填', trigger: 'blur' }],
|
name: [{ required: true, message: '菜单名称必填', trigger: 'blur' }],
|
||||||
component: [{ required: true, message: '请输入组件地址', trigger: 'blur' }],
|
component: [{ required: true, message: '请输入组件地址', trigger: 'blur' }],
|
||||||
component_name: [{ required: true, message: '请输入组件名称', trigger: 'blur' }],
|
component_name: [{ required: true, message: '请输入组件名称', trigger: 'blur' }],
|
||||||
link_url: [{ required: true, message: '请输入外链接地址',validator:validateLinkUrl, trigger: 'blur' }],
|
link_url: [{ required: true, message: '请输入外链接地址', validator: validateLinkUrl, trigger: 'blur' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
let deptDefaultList = ref<MenuTreeItemType[]>([]);
|
let deptDefaultList = ref<MenuTreeItemType[]>([]);
|
||||||
@@ -189,9 +183,9 @@ let menuFormData = reactive<MenuFormDataType>({
|
|||||||
description: '',
|
description: '',
|
||||||
is_catalog: false,
|
is_catalog: false,
|
||||||
is_link: false,
|
is_link: false,
|
||||||
is_iframe: false,
|
is_iframe: false,
|
||||||
is_affix: false,
|
is_affix: false,
|
||||||
link_url:''
|
link_url: ''
|
||||||
});
|
});
|
||||||
let menuBtnLoading = ref(false);
|
let menuBtnLoading = ref(false);
|
||||||
|
|
||||||
@@ -210,9 +204,9 @@ const setMenuFormData = () => {
|
|||||||
menuFormData.description = props.initFormData?.description || '';
|
menuFormData.description = props.initFormData?.description || '';
|
||||||
menuFormData.is_catalog = !!props.initFormData.is_catalog;
|
menuFormData.is_catalog = !!props.initFormData.is_catalog;
|
||||||
menuFormData.is_link = !!props.initFormData.is_link;
|
menuFormData.is_link = !!props.initFormData.is_link;
|
||||||
menuFormData.is_iframe =!!props.initFormData.is_iframe;
|
menuFormData.is_iframe = !!props.initFormData.is_iframe;
|
||||||
menuFormData.is_affix =!!props.initFormData.is_affix;
|
menuFormData.is_affix = !!props.initFormData.is_affix;
|
||||||
menuFormData.link_url =props.initFormData.link_url;
|
menuFormData.link_url = props.initFormData.link_url;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -246,7 +240,7 @@ const createFilter = (queryString: string) => {
|
|||||||
const handleTreeLoad = (node: Node, resolve: Function) => {
|
const handleTreeLoad = (node: Node, resolve: Function) => {
|
||||||
if (node.level !== 0) {
|
if (node.level !== 0) {
|
||||||
lazyLoadMenu({ parent: node.data.id }).then((res: APIResponseData) => {
|
lazyLoadMenu({ parent: node.data.id }).then((res: APIResponseData) => {
|
||||||
resolve(res.data);
|
resolve(XEUtils.filter(res.data, (i: MenuTreeItemType) => i.is_catalog));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -278,9 +272,14 @@ const handleCancel = (type: string = '') => {
|
|||||||
formRef.value?.resetFields();
|
formRef.value?.resetFields();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
props.treeData.map((item) => {
|
props.treeData.map((item) => {
|
||||||
deptDefaultList.value.push(item);
|
if (item.is_catalog) {
|
||||||
|
deptDefaultList.value.push(item);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
setMenuFormData();
|
setMenuFormData();
|
||||||
});
|
});
|
||||||
@@ -290,6 +289,7 @@ onMounted(async () => {
|
|||||||
.menu-form-com {
|
.menu-form-com {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
.menu-form-alert {
|
.menu-form-alert {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
@@ -298,6 +298,7 @@ onMounted(async () => {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--el-color-primary);
|
background-color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-form-btns {
|
.menu-form-btns {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
@@ -16,12 +16,12 @@
|
|||||||
<el-col :span="18">
|
<el-col :span="18">
|
||||||
<el-tabs type="border-card">
|
<el-tabs type="border-card">
|
||||||
<el-tab-pane label="按钮权限配置" >
|
<el-tab-pane label="按钮权限配置" >
|
||||||
<div style="height: 80vh">
|
<div style="height: 72vh">
|
||||||
<MenuButtonCom ref="menuButtonRef" />
|
<MenuButtonCom ref="menuButtonRef" />
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="列权限配置">
|
<el-tab-pane label="列权限配置">
|
||||||
<div style="height: 80vh">
|
<div style="height: 72vh">
|
||||||
<MenuFieldCom ref="menuFieldRef"></MenuFieldCom>
|
<MenuFieldCom ref="menuFieldRef"></MenuFieldCom>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@@ -138,7 +138,7 @@ onMounted(() => {
|
|||||||
.menu-box {
|
.menu-box {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: #fff;
|
background-color: var(--el-fill-color-blank);;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,5 +65,5 @@ export interface MenuFormDataType {
|
|||||||
is_link: boolean;
|
is_link: boolean;
|
||||||
is_iframe:boolean;
|
is_iframe:boolean;
|
||||||
is_affix:boolean;
|
is_affix:boolean;
|
||||||
link_url: string;
|
link_url: string|undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,38 @@
|
|||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
import {dict, useCompute, PageQuery, AddReq, DelReq, EditReq, CrudExpose, CrudOptions} from '@fast-crud/fast-crud';
|
import { dict, useCompute, PageQuery, AddReq, DelReq, EditReq, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||||
import tableSelector from '/@/components/tableSelector/index.vue';
|
import tableSelector from '/@/components/tableSelector/index.vue';
|
||||||
import {shallowRef, computed, ref, inject} from 'vue';
|
import { shallowRef, computed } from 'vue';
|
||||||
import manyToMany from '/@/components/manyToMany/index.vue';
|
import manyToMany from '/@/components/manyToMany/index.vue';
|
||||||
import {auth} from '/@/utils/authFunction'
|
import { auth } from '/@/utils/authFunction';
|
||||||
import {createCrudOptions as userCrudOptions } from "/@/views/system/user/crud";
|
const { compute } = useCompute();
|
||||||
import {request} from '/@/utils/service'
|
|
||||||
const {compute} = useCompute();
|
|
||||||
|
|
||||||
interface CreateCrudOptionsTypes {
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
crudOptions: CrudOptions;
|
const { tabActivted } = context; //从context中获取tabActivted
|
||||||
}
|
|
||||||
|
|
||||||
export const createCrudOptions = function ({
|
const pageRequest = async (query: PageQuery) => {
|
||||||
crudExpose,
|
if (tabActivted.value === 'receive') {
|
||||||
tabActivted
|
return await api.GetSelfReceive(query);
|
||||||
}: { crudExpose: CrudExpose; tabActivted: any }): CreateCrudOptionsTypes {
|
}
|
||||||
const pageRequest = async (query: PageQuery) => {
|
return await api.GetList(query);
|
||||||
if (tabActivted.value === 'receive') {
|
};
|
||||||
return await api.GetSelfReceive(query);
|
const editRequest = async ({ form, row }: EditReq) => {
|
||||||
}
|
form.id = row.id;
|
||||||
return await api.GetList(query);
|
return await api.UpdateObj(form);
|
||||||
};
|
};
|
||||||
const editRequest = async ({form, row}: EditReq) => {
|
const delRequest = async ({ row }: DelReq) => {
|
||||||
form.id = row.id;
|
return await api.DelObj(row.id);
|
||||||
return await api.UpdateObj(form);
|
};
|
||||||
};
|
const addRequest = async ({ form }: AddReq) => {
|
||||||
const delRequest = async ({row}: DelReq) => {
|
return await api.AddObj(form);
|
||||||
return await api.DelObj(row.id);
|
};
|
||||||
};
|
|
||||||
const addRequest = async ({form}: AddReq) => {
|
|
||||||
return await api.AddObj(form);
|
|
||||||
};
|
|
||||||
|
|
||||||
const viewRequest = async ({row}: { row: any }) => {
|
const viewRequest = async ({ row }: { row: any }) => {
|
||||||
return await api.GetObj(row.id);
|
return await api.GetObj(row.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const IsReadFunc = computed(() => {
|
|
||||||
return tabActivted.value === 'receive';
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const IsReadFunc = computed(() => {
|
||||||
|
return tabActivted.value === 'receive';
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
@@ -50,27 +42,27 @@ export const createCrudOptions = function ({
|
|||||||
editRequest,
|
editRequest,
|
||||||
delRequest,
|
delRequest,
|
||||||
},
|
},
|
||||||
actionbar:{
|
actionbar: {
|
||||||
buttons:{
|
buttons: {
|
||||||
add:{
|
add: {
|
||||||
show:computed(() =>{
|
show: computed(() => {
|
||||||
return tabActivted.value !== 'receive' && auth('messageCenter:Create');
|
return tabActivted.value !== 'receive' && auth('messageCenter:Create');
|
||||||
})
|
}),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
fixed:'right',
|
fixed: 'right',
|
||||||
width:150,
|
width: 150,
|
||||||
buttons: {
|
buttons: {
|
||||||
edit: {
|
edit: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
text:"查看",
|
text: '查看',
|
||||||
type:'text',
|
type: 'text',
|
||||||
iconRight:'View',
|
iconRight: 'View',
|
||||||
show:auth("messageCenter:Search"),
|
show: auth('messageCenter:Search'),
|
||||||
click({ index, row }) {
|
click({ index, row }) {
|
||||||
crudExpose.openView({ index, row });
|
crudExpose.openView({ index, row });
|
||||||
if (tabActivted.value === 'receive') {
|
if (tabActivted.value === 'receive') {
|
||||||
@@ -82,7 +74,7 @@ export const createCrudOptions = function ({
|
|||||||
remove: {
|
remove: {
|
||||||
iconRight: 'Delete',
|
iconRight: 'Delete',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
show:auth('messageCenter:Delete')
|
show: auth('messageCenter:Delete'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -99,7 +91,7 @@ export const createCrudOptions = function ({
|
|||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
type: ['text', 'colspan'],
|
type: ['text', 'colspan'],
|
||||||
column:{
|
column: {
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
@@ -132,7 +124,7 @@ export const createCrudOptions = function ({
|
|||||||
target_type: {
|
target_type: {
|
||||||
title: '目标类型',
|
title: '目标类型',
|
||||||
type: ['dict-radio', 'colspan'],
|
type: ['dict-radio', 'colspan'],
|
||||||
column:{
|
column: {
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
dict: dict({
|
dict: dict({
|
||||||
@@ -285,7 +277,7 @@ export const createCrudOptions = function ({
|
|||||||
name: shallowRef(tableSelector),
|
name: shallowRef(tableSelector),
|
||||||
vModel: 'modelValue',
|
vModel: 'modelValue',
|
||||||
displayLabel: compute(({ form }) => {
|
displayLabel: compute(({ form }) => {
|
||||||
return form.target_dept_name;
|
return form.dept_info;
|
||||||
}),
|
}),
|
||||||
tableConfig: {
|
tableConfig: {
|
||||||
url: '/api/system/dept/all_dept/',
|
url: '/api/system/dept/all_dept/',
|
||||||
@@ -297,7 +289,7 @@ export const createCrudOptions = function ({
|
|||||||
{
|
{
|
||||||
prop: 'name',
|
prop: 'name',
|
||||||
label: '部门名称',
|
label: '部门名称',
|
||||||
width: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'status_label',
|
prop: 'status_label',
|
||||||
@@ -349,7 +341,7 @@ export const createCrudOptions = function ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
component: {
|
component: {
|
||||||
disabled: true,
|
disabled: false,
|
||||||
id: '1', // 当同一个页面有多个editor时,需要配置不同的id
|
id: '1', // 当同一个页面有多个editor时,需要配置不同的id
|
||||||
editorConfig: {
|
editorConfig: {
|
||||||
// 是否只读
|
// 是否只读
|
||||||
@@ -373,4 +365,4 @@ export const createCrudOptions = function ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,43 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<fs-page>
|
<fs-page>
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
<template #header-middle>
|
||||||
<template #header-middle>
|
<el-tabs v-model="tabActivted" @tab-click="onTabClick">
|
||||||
<el-tabs v-model="tabActivted" @tab-click="onTabClick">
|
<el-tab-pane label="我的发布" name="send"></el-tab-pane>
|
||||||
<el-tab-pane label="我的发布" name="send"></el-tab-pane>
|
<el-tab-pane label="我的接收" name="receive"></el-tab-pane>
|
||||||
<el-tab-pane label="我的接收" name="receive"></el-tab-pane>
|
</el-tabs>
|
||||||
</el-tabs>
|
</template>
|
||||||
</template>
|
</fs-crud>
|
||||||
</fs-crud>
|
</fs-page>
|
||||||
</fs-page>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="messageCenter">
|
<script lang="ts" setup name="messageCenter">
|
||||||
import {ref, onMounted} from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import {useExpose, useCrud} from '@fast-crud/fast-crud';
|
import { useFs } from '@fast-crud/fast-crud';
|
||||||
import {createCrudOptions} from './crud';
|
import createCrudOptions from './crud';
|
||||||
// crud组件的ref
|
|
||||||
const crudRef = ref();
|
|
||||||
// crud 配置的ref
|
|
||||||
const crudBinding = ref();
|
|
||||||
// 暴露的方法
|
|
||||||
const {crudExpose} = useExpose({crudRef, crudBinding});
|
|
||||||
//tab选择
|
//tab选择
|
||||||
const tabActivted = ref('send')
|
const tabActivted = ref('send');
|
||||||
const onTabClick= (tab:any)=> {
|
const onTabClick = (tab: any) => {
|
||||||
const { paneName } = tab
|
const { paneName } = tab;
|
||||||
tabActivted.value = paneName
|
tabActivted.value = paneName;
|
||||||
crudExpose.doRefresh();
|
crudExpose.doRefresh();
|
||||||
}
|
};
|
||||||
// 你的crud配置
|
|
||||||
const {crudOptions} = createCrudOptions({crudExpose,tabActivted});
|
const context: any = { tabActivted }; //将 tabActivted 通过context传递给crud.tsx
|
||||||
// 初始化crud配置
|
// 初始化crud配置
|
||||||
const {resetCrudOptions} = useCrud({crudExpose, crudOptions});
|
const { crudRef, crudBinding, crudExpose } = useFs({ createCrudOptions, context });
|
||||||
|
|
||||||
// 页面打开后获取列表数据
|
// 页面打开后获取列表数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
crudExpose.doRefresh();
|
crudExpose.doRefresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -153,7 +153,7 @@
|
|||||||
center
|
center
|
||||||
>
|
>
|
||||||
<el-form-item label="原密码" required prop="oldPassword">
|
<el-form-item label="原密码" required prop="oldPassword">
|
||||||
<el-input v-model="userPasswordInfo.oldPassword" placeholder="请输入原始密码" clearable></el-input>
|
<el-input type="password" v-model="userPasswordInfo.oldPassword" placeholder="请输入原始密码" show-password clearable></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item required prop="newPassword" label="新密码">
|
<el-form-item required prop="newPassword" label="新密码">
|
||||||
<el-input type="password" v-model="userPasswordInfo.newPassword" placeholder="请输入新密码" show-password clearable></el-input>
|
<el-input type="password" v-model="userPasswordInfo.newPassword" placeholder="请输入新密码" show-password clearable></el-input>
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
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 getRolePermission(query:object) {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/system/role_menu_button_permission/get_role_premission/',
|
url: '/api/system/role_menu_button_permission/get_role_permission/',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params:query
|
params:query
|
||||||
|
}).then((res:any)=>{
|
||||||
|
return XEUtils.toArrayTree(res.data, {key: 'id', parentKey: 'parent',children: 'children',strict: false})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,16 +28,25 @@ export function setRolePremission(roleId:any,data:object) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDataPermissionRange() {
|
export function getDataPermissionRange(query:object) {
|
||||||
|
return request({
|
||||||
|
url: '/api/system/role_menu_button_permission/data_scope/',
|
||||||
|
method: 'get',
|
||||||
|
params:query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDataPermissionRangeAll() {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/system/role_menu_button_permission/data_scope/',
|
url: '/api/system/role_menu_button_permission/data_scope/',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export function getDataPermissionDept() {
|
export function getDataPermissionDept(query:object) {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
|
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
|
||||||
method: 'get'
|
method: 'get',
|
||||||
|
params:query
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,392 +1,451 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer v-model="drawerVisible" title="权限配置" direction="rtl" size="60%" :close-on-click-modal="false"
|
<el-drawer
|
||||||
:before-close="handleDrawerClose" :destroy-on-close="true">
|
v-model="drawerVisibleNew"
|
||||||
<template #header>
|
title="权限配置"
|
||||||
<el-row>
|
direction="rtl"
|
||||||
<el-col :span="4">
|
size="60%"
|
||||||
<div>当前授权角色:
|
:close-on-click-modal="false"
|
||||||
<el-tag>{{ props.roleName }}</el-tag>
|
:before-close="handleDrawerClose"
|
||||||
</div>
|
:destroy-on-close="true"
|
||||||
</el-col>
|
>
|
||||||
<el-col :span="6">
|
<template #header>
|
||||||
<div>
|
<el-row>
|
||||||
<el-button size="small" type="primary" class="pc-save-btn" @click="handleSavePermission">保存菜单授权
|
<el-col :span="4">
|
||||||
</el-button>
|
<div>
|
||||||
</div>
|
当前授权角色:
|
||||||
</el-col>
|
<el-tag>{{ props.roleName }}</el-tag>
|
||||||
</el-row>
|
</div>
|
||||||
</template>
|
</el-col>
|
||||||
<div class="permission-com">
|
<el-col :span="6">
|
||||||
<el-tabs>
|
<div>
|
||||||
<el-tab-pane v-for="(item, mIndex) in menuData" :key="mIndex" :label="item.name">
|
<el-button size="small" type="primary" class="pc-save-btn" @click="handleSavePermission">保存菜单授权 </el-button>
|
||||||
<el-tabs tab-position="left">
|
</div>
|
||||||
<el-tab-pane v-for="(menu, mIndex) in item.menus" :key="mIndex" :label="menu.name" >
|
</el-col>
|
||||||
<el-checkbox v-model="menu.isCheck">页面显示权限</el-checkbox>
|
</el-row>
|
||||||
<div class="pc-collapse-main">
|
</template>
|
||||||
<div class="pccm-item">
|
<div class="permission-com">
|
||||||
<div class="menu-form-alert"> 配置操作功能接口权限,配置数据权限点击小齿轮 </div>
|
<el-row class="menu-el-row" :gutter="20">
|
||||||
<el-checkbox v-for="(btn, bIndex) in menu.btns" :key="bIndex" v-model="btn.isCheck"
|
<el-col :span="6">
|
||||||
:label="btn.value">
|
<div class="menu-box menu-left-box">
|
||||||
<div class="btn-item">
|
<el-tree
|
||||||
{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
|
ref="treeRef"
|
||||||
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(menu, btn.id)">
|
:data="menuData"
|
||||||
<el-icon>
|
:props="defaultTreeProps"
|
||||||
<Setting />
|
:default-checked-keys="menuDefaultCheckedKeys"
|
||||||
</el-icon>
|
@check="handleMenuCheck"
|
||||||
</span>
|
@node-click="handleMenuClick"
|
||||||
</div>
|
node-key="id"
|
||||||
</el-checkbox>
|
check-strictly
|
||||||
</div>
|
highlight-current
|
||||||
|
show-checkbox
|
||||||
|
default-expand-all
|
||||||
|
>
|
||||||
|
</el-tree>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
<div class="pccm-item" v-if="menu.columns && menu.columns.length > 0">
|
<el-col :span="18">
|
||||||
<div class="menu-form-alert"> 配置数据列字段权限 </div>
|
<div class="pc-collapse-main" v-if="menuCurrent.btns && menuCurrent.btns.length > 0">
|
||||||
<ul class="columns-list">
|
<div class="pccm-item">
|
||||||
<li class="columns-head">
|
<div class="menu-form-alert">配置操作功能接口权限,配置数据权限点击小齿轮</div>
|
||||||
<div class="width-txt">
|
<el-checkbox v-for="(btn, bIndex) in menuCurrent.btns" :key="bIndex" v-model="btn.isCheck" :label="btn.value">
|
||||||
<span>字段</span>
|
<div class="btn-item">
|
||||||
</div>
|
{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
|
||||||
<div v-for="(head, hIndex) in column.header" :key="hIndex" class="width-check">
|
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(menuCurrent, btn)">
|
||||||
<el-checkbox :label="head.value" @change="handleColumnChange($event, menu, head.value)">
|
<el-icon>
|
||||||
<span>{{ head.label }}</span>
|
<Setting />
|
||||||
</el-checkbox>
|
</el-icon>
|
||||||
</div>
|
</span>
|
||||||
</li>
|
</div>
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
<li v-for="(c_item, c_index) in menu.columns" :key="c_index" class="columns-item">
|
<div class="pccm-item" v-if="menuCurrent.columns && menuCurrent.columns.length > 0">
|
||||||
<div class="width-txt">{{ c_item.title }}</div>
|
<div class="menu-form-alert">配置数据列字段权限</div>
|
||||||
<div v-for="(col, cIndex) in column.header" :key="cIndex" class="width-check">
|
<ul class="columns-list">
|
||||||
<el-checkbox v-model="c_item[col.value]" class="ci-checkout"></el-checkbox>
|
<li class="columns-head">
|
||||||
</div>
|
<div class="width-txt">
|
||||||
</li>
|
<span>字段</span>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
<div v-for="(head, hIndex) in column.header" :key="hIndex" class="width-check">
|
||||||
</div>
|
<el-checkbox :label="head.value" @change="handleColumnChange($event, menuCurrent, head.value, head.disabled)">
|
||||||
</el-tab-pane>
|
<span>{{ head.label }}</span>
|
||||||
</el-tabs>
|
</el-checkbox>
|
||||||
</el-tab-pane>
|
</div>
|
||||||
</el-tabs>
|
</li>
|
||||||
|
|
||||||
<el-dialog v-model="dialogVisible" title="数据权限配置" width="400px" :close-on-click-modal="false"
|
<li v-for="(c_item, c_index) in menuCurrent.columns" :key="c_index" class="columns-item">
|
||||||
:before-close="handleDialogClose">
|
<div class="width-txt">{{ c_item.title }}</div>
|
||||||
<div class="pc-dialog">
|
<div v-for="(col, cIndex) in column.header" :key="cIndex" class="width-check">
|
||||||
<el-select v-model="dataPermission" @change="handlePermissionRangeChange" class="dialog-select"
|
<el-checkbox v-model="c_item[col.value]" class="ci-checkout" :disabled="c_item[col.disabled]"></el-checkbox>
|
||||||
placeholder="请选择">
|
</div>
|
||||||
<el-option v-for="item in dataPermissionRange" :key="item.value" :label="item.label" :value="item.value" />
|
</li>
|
||||||
</el-select>
|
</ul>
|
||||||
<el-tree-select v-show="dataPermission === 4" node-key="id" v-model="customDataPermission"
|
</div>
|
||||||
:props="defaultTreeProps" :data="deptData" multiple check-strictly :render-after-expand="false"
|
</div>
|
||||||
show-checkbox class="dialog-tree" />
|
</el-col>
|
||||||
</div>
|
</el-row>
|
||||||
<template #footer>
|
|
||||||
<div>
|
<el-dialog v-model="dialogVisible" title="数据权限配置" width="400px" :close-on-click-modal="false" :before-close="handleDialogClose">
|
||||||
<el-button type="primary" @click="handleDialogConfirm"> 确定</el-button>
|
<div class="pc-dialog">
|
||||||
<el-button @click="handleDialogClose"> 取消</el-button>
|
<el-select v-model="dataPermission" @change="handlePermissionRangeChange" class="dialog-select" placeholder="请选择">
|
||||||
</div>
|
<el-option v-for="item in dataPermissionRange" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
</template>
|
</el-select>
|
||||||
</el-dialog>
|
<el-tree-select
|
||||||
</div>
|
v-show="dataPermission === 4"
|
||||||
</el-drawer>
|
node-key="id"
|
||||||
|
v-model="customDataPermission"
|
||||||
|
:props="defaultTreeProps"
|
||||||
|
:data="deptData"
|
||||||
|
multiple
|
||||||
|
check-strictly
|
||||||
|
:render-after-expand="false"
|
||||||
|
show-checkbox
|
||||||
|
class="dialog-tree"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="handleDialogConfirm"> 确定</el-button>
|
||||||
|
<el-button @click="handleDialogClose"> 取消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
</template>
|
</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 } from 'vue';
|
||||||
import XEUtils from 'xe-utils';
|
import XEUtils from 'xe-utils';
|
||||||
import { errorNotification } from '/@/utils/message';
|
import { errorNotification } from '/@/utils/message';
|
||||||
import {
|
import { getDataPermissionRange, getDataPermissionDept, getRolePermission, setRolePremission, setBtnDatarange } from './api';
|
||||||
getDataPermissionRange,
|
import { MenuDataType, DataPermissionRangeType, CustomDataPermissionDeptType } from './types';
|
||||||
getDataPermissionDept,
|
import { ElMessage, ElTree } from 'element-plus';
|
||||||
getRolePremission,
|
|
||||||
setRolePremission,
|
|
||||||
setBtnDatarange
|
|
||||||
} from './api';
|
|
||||||
import { MenuDataType, MenusType, DataPermissionRangeType, CustomDataPermissionDeptType } from './types';
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
roleId: {
|
roleId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: -1
|
default: -1,
|
||||||
},
|
},
|
||||||
roleName: {
|
roleName: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: '',
|
||||||
},
|
},
|
||||||
drawerVisible: {
|
drawerVisible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
const emit = defineEmits(['update:drawerVisible'])
|
const emit = defineEmits(['update:drawerVisible']);
|
||||||
|
|
||||||
const drawerVisible = ref(false)
|
const drawerVisibleNew = ref(false);
|
||||||
watch(
|
watch(
|
||||||
() => props.drawerVisible,
|
() => props.drawerVisible,
|
||||||
(val) => {
|
(val) => {
|
||||||
drawerVisible.value = val;
|
drawerVisibleNew .value = val;
|
||||||
getMenuBtnPermission()
|
getMenuBtnPermission();
|
||||||
fetchData()
|
getDataPermissionRangeLable();
|
||||||
}
|
menuCurrent.value = {};
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const handleDrawerClose = () => {
|
const handleDrawerClose = () => {
|
||||||
emit('update:drawerVisible', false);
|
emit('update:drawerVisible', false);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const defaultTreeProps = {
|
|
||||||
children: 'children',
|
|
||||||
label: 'name',
|
|
||||||
value: 'id',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let menuData = ref<MenuDataType[]>([]);
|
const defaultTreeProps = {
|
||||||
let collapseCurrent = ref<number[]>([]);
|
children: 'children',
|
||||||
let menuCurrent = ref<Partial<MenuDataType>>({});
|
label: 'name',
|
||||||
|
value: 'id',
|
||||||
|
};
|
||||||
|
|
||||||
|
let menuData = ref<MenuDataType[]>([]); // 菜单列表数据
|
||||||
|
let menuDefaultCheckedKeys = ref<number[]>([]); // 默认选中的菜单列表
|
||||||
|
let menuCurrent = ref<Partial<MenuDataType>>({}); // 当前选中的菜单
|
||||||
|
|
||||||
let menuBtnCurrent = ref<number>(-1);
|
let menuBtnCurrent = ref<number>(-1);
|
||||||
let dialogVisible = ref(false);
|
let dialogVisible = ref(false);
|
||||||
let dataPermissionRange = ref<DataPermissionRangeType[]>([]);
|
let dataPermissionRange = ref<DataPermissionRangeType[]>([]);
|
||||||
|
let dataPermissionRangeLabel = ref<DataPermissionRangeType[]>([]);
|
||||||
|
|
||||||
const formatDataRange = computed(() => {
|
const formatDataRange = computed(() => {
|
||||||
return function (datarange: number) {
|
return function (datarange: number) {
|
||||||
const findItem = dataPermissionRange.value.find((i) => i.value === datarange);
|
const findItem = dataPermissionRangeLabel.value.find((i) => i.value === datarange);
|
||||||
return findItem?.label || ''
|
return findItem?.label || '';
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
let deptData = ref<CustomDataPermissionDeptType[]>([]);
|
let deptData = ref<CustomDataPermissionDeptType[]>([]);
|
||||||
let dataPermission = ref();
|
let dataPermission = ref();
|
||||||
let customDataPermission = ref([]);
|
let customDataPermission = ref([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单复选框选中
|
||||||
|
* @param node
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
const handleMenuCheck = (node: any, data: any) => {
|
||||||
|
XEUtils.eachTree(menuData.value, (item) => {
|
||||||
|
item.isCheck = data.checkedKeys.includes(item.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 菜单点击
|
||||||
|
* @param node
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
const handleMenuClick = (selectNode: MenuDataType) => {
|
||||||
|
menuCurrent.value = selectNode;
|
||||||
|
};
|
||||||
//获取菜单,按钮,权限
|
//获取菜单,按钮,权限
|
||||||
const getMenuBtnPermission = async () => {
|
const getMenuBtnPermission = async () => {
|
||||||
const resMenu = await getRolePremission({ role: props.roleId })
|
const resMenu = await getRolePermission({ role: props.roleId });
|
||||||
menuData.value = resMenu.data
|
menuData.value = resMenu;
|
||||||
}
|
menuDefaultCheckedKeys.value = XEUtils.toTreeArray(resMenu)
|
||||||
|
.filter((i) => i.isCheck)
|
||||||
const fetchData = async () => {
|
.map((i) => i.id);
|
||||||
try {
|
};
|
||||||
const resRange = await getDataPermissionRange();
|
// 获取按钮的数据权限下拉选项
|
||||||
if (resRange?.code === 2000) {
|
const getDataPermissionRangeLable = async () => {
|
||||||
dataPermissionRange.value = resRange.data;
|
const resRange = await getDataPermissionRange({ role: props.roleId });
|
||||||
}
|
dataPermissionRangeLabel.value = resRange.data;
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCollapseChange = (val: number) => {
|
/**
|
||||||
collapseCurrent.value = [val];
|
* 获取按钮数据权限下拉选项
|
||||||
|
* @param btnId 按钮id
|
||||||
|
*/
|
||||||
|
const fetchData = async (btnId: number) => {
|
||||||
|
try {
|
||||||
|
const resRange = await getDataPermissionRange({ menu_button: btnId });
|
||||||
|
if (resRange?.code === 2000) {
|
||||||
|
dataPermissionRange.value = resRange.data;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置按钮数据权限
|
* 设置按钮数据权限
|
||||||
* @param record 当前菜单
|
* @param record 当前菜单
|
||||||
* @param btnType 按钮类型
|
* @param btnType 按钮类型
|
||||||
*/
|
*/
|
||||||
const handleSettingClick = (record: MenusType, btnId: number) => {
|
const handleSettingClick = (record: any, btn: MenuDataType['btns'][number]) => {
|
||||||
menuCurrent.value = record;
|
menuCurrent.value = record;
|
||||||
menuBtnCurrent.value = btnId;
|
menuBtnCurrent.value = btn.id;
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
|
dataPermission.value = btn.data_range;
|
||||||
|
handlePermissionRangeChange(btn.data_range);
|
||||||
|
fetchData(btn.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleColumnChange = (val: boolean, record: MenusType, btnType: string) => {
|
/**
|
||||||
for (const iterator of record.columns) {
|
* 设置列权限
|
||||||
iterator[btnType] = val;
|
* @param val 是否选中
|
||||||
}
|
* @param record 当前菜单
|
||||||
|
* @param btnType 按钮类型
|
||||||
|
* @param disabledType 禁用类型
|
||||||
|
*/
|
||||||
|
const handleColumnChange = (val: boolean, record: any, btnType: string, disabledType: string) => {
|
||||||
|
for (const iterator of record.columns) {
|
||||||
|
iterator[btnType] = iterator[disabledType] ? iterator[btnType] : val;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限设置
|
||||||
|
*/
|
||||||
const handlePermissionRangeChange = async (val: number) => {
|
const handlePermissionRangeChange = async (val: number) => {
|
||||||
if (val === 4) {
|
if (val === 4) {
|
||||||
const res = await getDataPermissionDept();
|
const res = await getDataPermissionDept({ role: props.roleId, menu_button: menuBtnCurrent.value });
|
||||||
const data = XEUtils.toArrayTree(res.data, { parentKey: 'parent', strict: false });
|
const depts = XEUtils.toArrayTree(res.data, { parentKey: 'parent', strict: false });
|
||||||
deptData.value = data;
|
deptData.value = depts;
|
||||||
}
|
const btnObj = XEUtils.find(menuCurrent.value.btns, (item) => item.id === menuBtnCurrent.value);
|
||||||
|
customDataPermission.value = btnObj.dept;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据权限设置确认
|
* 数据权限设置确认
|
||||||
*/
|
*/
|
||||||
const handleDialogConfirm = () => {
|
const handleDialogConfirm = () => {
|
||||||
if (dataPermission.value !== 0 && !dataPermission.value) {
|
if (dataPermission.value !== 0 && !dataPermission.value) {
|
||||||
errorNotification('请选择');
|
errorNotification('请选择');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
for (const btn of menuCurrent.value?.btns || []) {
|
||||||
//if (dataPermission.value !== 4) {}
|
if (btn.id === menuBtnCurrent.value) {
|
||||||
for (const item of menuData.value) {
|
const findItem = dataPermissionRange.value.find((i) => i.value === dataPermission.value);
|
||||||
for (const iterator of item.menus) {
|
btn.data_range = findItem?.value || 0;
|
||||||
if (iterator.id === menuCurrent.value.id) {
|
if (btn.data_range === 4) {
|
||||||
for (const btn of iterator.btns) {
|
btn.dept = customDataPermission.value;
|
||||||
if (btn.id === menuBtnCurrent.value) {
|
}
|
||||||
const findItem = dataPermissionRange.value.find((i) => i.value === dataPermission.value);
|
}
|
||||||
btn.data_range = findItem?.value || 0;
|
}
|
||||||
if (btn.data_range === 4) {
|
handleDialogClose();
|
||||||
btn.dept = customDataPermission.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handleDialogClose();
|
|
||||||
};
|
};
|
||||||
const handleDialogClose = () => {
|
const handleDialogClose = () => {
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
customDataPermission.value = [];
|
customDataPermission.value = [];
|
||||||
dataPermission.value = null;
|
dataPermission.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
//保存权限
|
//保存菜单授权
|
||||||
const handleSavePermission = () => {
|
const handleSavePermission = () => {
|
||||||
setRolePremission(props.roleId, menuData.value).then((res: any) => {
|
setRolePremission(props.roleId, XEUtils.toTreeArray(menuData.value)).then((res: any) => {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: res.msg,
|
message: res.msg,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const column = reactive({
|
const column = reactive({
|
||||||
header: [{ value: 'is_create', label: '新增可见' }, { value: 'is_update', label: '编辑可见' }, {
|
header: [
|
||||||
value: 'is_query',
|
{ value: 'is_create', label: '新增可见', disabled: 'disabled_create' },
|
||||||
label: '列表可见'
|
{ value: 'is_update', label: '编辑可见', disabled: 'disabled_update' },
|
||||||
}]
|
{ value: 'is_query', label: '列表可见', disabled: 'disabled_query' },
|
||||||
})
|
],
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMounted(() => {});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.permission-com {
|
.permission-com {
|
||||||
margin: 15px;
|
margin: 15px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.pc-save-btn {
|
.pc-save-btn {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pc-collapse-title {
|
.pc-collapse-title {
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pc-collapse-main {
|
.pc-collapse-main {
|
||||||
padding-top: 15px;
|
box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.pccm-item {
|
.pccm-item {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
.menu-form-alert {
|
.menu-form-alert {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--el-color-primary);
|
background-color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-item {
|
.btn-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-list {
|
.columns-list {
|
||||||
.width-txt {
|
.width-txt {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.width-check {
|
.width-check {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.width-icon {
|
.width-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-head {
|
.columns-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
border-bottom: 1px solid #ebeef5;
|
border-bottom: 1px solid #ebeef5;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-item {
|
.columns-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.ci-checkout {
|
.ci-checkout {
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pc-dialog {
|
.pc-dialog {
|
||||||
.dialog-select {
|
.dialog-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-tree {
|
.dialog-tree {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.permission-com {
|
.permission-com {
|
||||||
.el-collapse {
|
.el-collapse {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-collapse-item {
|
.el-collapse-item {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-collapse-item__header {
|
.el-collapse-item__header {
|
||||||
height: auto;
|
height: auto;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border-top: 1px solid #ebeef5;
|
border-top: 1px solid #ebeef5;
|
||||||
border-left: 1px solid #ebeef5;
|
border-left: 1px solid #ebeef5;
|
||||||
border-right: 1px solid #ebeef5;
|
border-right: 1px solid #ebeef5;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-collapse-item__header.is-active {
|
.el-collapse-item__header.is-active {
|
||||||
border-radius: 8px 8px 0 0;
|
border-radius: 8px 8px 0 0;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-collapse-item__wrap {
|
.el-collapse-item__wrap {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-left: 1px solid #ebeef5;
|
border-left: 1px solid #ebeef5;
|
||||||
border-right: 1px solid #ebeef5;
|
border-right: 1px solid #ebeef5;
|
||||||
border-top: 1px solid #ebeef5;
|
border-top: 1px solid #ebeef5;
|
||||||
border-radius: 0 0 8px 8px;
|
border-radius: 0 0 8px 8px;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.el-collapse-item__content {
|
.el-collapse-item__content {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,36 +1,29 @@
|
|||||||
export interface DataPermissionRangeType {
|
export interface DataPermissionRangeType {
|
||||||
label: string;
|
label: string;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomDataPermissionDeptType {
|
export interface CustomDataPermissionDeptType {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
patent: number;
|
patent: number;
|
||||||
children: CustomDataPermissionDeptType[]
|
children: CustomDataPermissionDeptType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomDataPermissionMenuType {
|
export interface CustomDataPermissionMenuType {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
is_catalog: boolean;
|
is_catalog: boolean;
|
||||||
menuPermission: { id: number; name: string; value: string }[] | null;
|
menuPermission: { id: number; name: string; value: string }[] | null;
|
||||||
columns: { id: number; name: string; title: string }[] | null;
|
columns: { id: number; name: string; title: string }[] | null;
|
||||||
children: CustomDataPermissionMenuType[]
|
children: CustomDataPermissionMenuType[];
|
||||||
}
|
|
||||||
|
|
||||||
export interface MenusType{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
isCheck: boolean;
|
|
||||||
radio: string;
|
|
||||||
btns: { id:number,name: string; value: string; isCheck: boolean; data_range: number; dept:object; name:string }[];
|
|
||||||
columns: { [key: string]: boolean | string; }[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MenuDataType {
|
export interface MenuDataType {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
menus:MenusType[];
|
isCheck: boolean;
|
||||||
|
btns: { id: number; name: string; value: string; isCheck: boolean; data_range: number; dept: object }[];
|
||||||
|
columns: { [key: string]: boolean | string; }[];
|
||||||
|
children: MenuDataType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const apiPrefix = '/api/system/user/';
|
|||||||
|
|
||||||
export function GetDept(query: PageQuery) {
|
export function GetDept(query: PageQuery) {
|
||||||
return request({
|
return request({
|
||||||
url: "/api/system/dept/dept_lazy_tree/",
|
url: "/api/system/dept/all_dept/",
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: query,
|
params: query,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -218,7 +218,10 @@ export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps)
|
|||||||
label: 'name'
|
label: 'name'
|
||||||
}),
|
}),
|
||||||
column: {
|
column: {
|
||||||
minWidth: 150, //最小列宽
|
minWidth: 200, //最小列宽
|
||||||
|
formatter({value,row,index}){
|
||||||
|
return row.dept_name_all
|
||||||
|
}
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -253,7 +256,11 @@ export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps)
|
|||||||
label: 'name',
|
label: 'name',
|
||||||
}),
|
}),
|
||||||
column: {
|
column: {
|
||||||
minWidth: 100, //最小列宽
|
minWidth: 200, //最小列宽
|
||||||
|
formatter({value,row,index}){
|
||||||
|
const values = row.role_info.map((item:any) => item.name);
|
||||||
|
return values.join(',')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
rules: [
|
rules: [
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ const getData = () => {
|
|||||||
const result = XEUtils.toArrayTree(responseData, {
|
const result = XEUtils.toArrayTree(responseData, {
|
||||||
parentKey: 'parent',
|
parentKey: 'parent',
|
||||||
children: 'children',
|
children: 'children',
|
||||||
strict: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
data.value = result;
|
data.value = result;
|
||||||
|
|||||||
@@ -200,11 +200,9 @@ export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps)
|
|||||||
component: {
|
component: {
|
||||||
span: 24,
|
span: 24,
|
||||||
props: {
|
props: {
|
||||||
elProps: {
|
allowCreate: true,
|
||||||
allowCreate: true,
|
filterable: true,
|
||||||
filterable: true,
|
clearable: true,
|
||||||
clearable: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
itemProps: {
|
itemProps: {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { resolve } from 'path';
|
|||||||
import { defineConfig, loadEnv, ConfigEnv } from 'vite';
|
import { defineConfig, loadEnv, ConfigEnv } from 'vite';
|
||||||
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
|
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
|
||||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
|
import { generateVersionFile } from "/@/utils/upgrade";
|
||||||
|
|
||||||
const pathResolve = (dir: string) => {
|
const pathResolve = (dir: string) => {
|
||||||
return resolve(__dirname, '.', dir);
|
return resolve(__dirname, '.', dir);
|
||||||
@@ -17,6 +18,8 @@ const alias: Record<string, string> = {
|
|||||||
|
|
||||||
const viteConfig = defineConfig((mode: ConfigEnv) => {
|
const viteConfig = defineConfig((mode: ConfigEnv) => {
|
||||||
const env = loadEnv(mode.mode, process.cwd());
|
const env = loadEnv(mode.mode, process.cwd());
|
||||||
|
// 当Vite构建时,生成版本文件
|
||||||
|
generateVersionFile()
|
||||||
return {
|
return {
|
||||||
plugins: [vue(), vueJsx(), vueSetupExtend()],
|
plugins: [vue(), vueJsx(), vueSetupExtend()],
|
||||||
root: process.cwd(),
|
root: process.cwd(),
|
||||||
|
|||||||
Reference in New Issue
Block a user