Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -98,5 +98,4 @@ media/
|
|||||||
__pypackages__/
|
__pypackages__/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
gunicorn.pid
|
gunicorn.pid
|
||||||
plugins/*
|
|
||||||
!plugins/__init__.py
|
!plugins/__init__.py
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from celery.signals import task_postrun
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -38,3 +40,12 @@ def retry_base_task_error():
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return wraps
|
return wraps
|
||||||
|
|
||||||
|
|
||||||
|
@task_postrun.connect
|
||||||
|
def add_periodic_task_name(sender, task_id, task, args, kwargs, **extras):
|
||||||
|
periodic_task_name = kwargs.get('periodic_task_name')
|
||||||
|
if periodic_task_name:
|
||||||
|
from django_celery_results.models import TaskResult
|
||||||
|
# 更新 TaskResult 表中的 periodic_task_name 字段
|
||||||
|
TaskResult.objects.filter(task_id=task_id).update(periodic_task_name=periodic_task_name)
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ PLUGINS_URL_PATTERNS = []
|
|||||||
# ********** 一键导入插件配置开始 **********
|
# ********** 一键导入插件配置开始 **********
|
||||||
# 例如:
|
# 例如:
|
||||||
# from dvadmin_upgrade_center.settings import * # 升级中心
|
# from dvadmin_upgrade_center.settings import * # 升级中心
|
||||||
# from dvadmin3_celery.settings import * # celery 异步任务
|
from dvadmin3_celery.settings import * # celery 异步任务
|
||||||
# from dvadmin_third.settings import * # 第三方用户管理
|
# from dvadmin_third.settings import * # 第三方用户管理
|
||||||
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
|
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
|
||||||
# from dvadmin_tenants.settings import * # 租户管理
|
# from dvadmin_tenants.settings import * # 租户管理
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ urlpatterns = [
|
|||||||
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()),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,16 +10,17 @@ from rest_framework import serializers
|
|||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
from dvadmin.system.models import Role, Menu, MenuButton, Dept
|
from dvadmin.system.models import Role, Menu, MenuButton, Dept, Users
|
||||||
from dvadmin.system.views.dept import DeptSerializer
|
from dvadmin.system.views.dept import DeptSerializer
|
||||||
from dvadmin.system.views.menu import MenuSerializer
|
from dvadmin.system.views.menu import MenuSerializer
|
||||||
from dvadmin.system.views.menu_button import MenuButtonSerializer
|
from dvadmin.system.views.menu_button import MenuButtonSerializer
|
||||||
from dvadmin.utils.crud_mixin import FastCrudMixin
|
from dvadmin.utils.crud_mixin import FastCrudMixin
|
||||||
from dvadmin.utils.field_permission import FieldPermissionMixin
|
from dvadmin.utils.field_permission import FieldPermissionMixin
|
||||||
from dvadmin.utils.json_response import SuccessResponse, DetailResponse
|
from dvadmin.utils.json_response import SuccessResponse, DetailResponse, ErrorResponse
|
||||||
from dvadmin.utils.serializers import CustomModelSerializer
|
from dvadmin.utils.serializers import CustomModelSerializer
|
||||||
from dvadmin.utils.validator import CustomUniqueValidator
|
from dvadmin.utils.validator import CustomUniqueValidator
|
||||||
from dvadmin.utils.viewset import CustomModelViewSet
|
from dvadmin.utils.viewset import CustomModelViewSet
|
||||||
|
from dvadmin.utils.permission import CustomPermission
|
||||||
|
|
||||||
|
|
||||||
class RoleSerializer(CustomModelSerializer):
|
class RoleSerializer(CustomModelSerializer):
|
||||||
@@ -107,7 +108,6 @@ class MenuButtonPermissionSerializer(CustomModelSerializer):
|
|||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin):
|
class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin):
|
||||||
"""
|
"""
|
||||||
角色管理接口
|
角色管理接口
|
||||||
@@ -142,3 +142,62 @@ class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin):
|
|||||||
role.users_set.add(*movedKeys)
|
role.users_set.add(*movedKeys)
|
||||||
serializer = RoleSerializer(role)
|
serializer = RoleSerializer(role)
|
||||||
return DetailResponse(data=serializer.data, msg="更新成功")
|
return DetailResponse(data=serializer.data, msg="更新成功")
|
||||||
|
|
||||||
|
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated, CustomPermission])
|
||||||
|
def get_role_users(self, request):
|
||||||
|
"""
|
||||||
|
获取角色已授权、未授权的用户
|
||||||
|
已授权的用户:1
|
||||||
|
未授权的用户:0
|
||||||
|
"""
|
||||||
|
role_id = request.query_params.get('role_id', None)
|
||||||
|
|
||||||
|
if not role_id:
|
||||||
|
return ErrorResponse(msg="请选择角色")
|
||||||
|
|
||||||
|
if request.query_params.get('authorized', 0) == "1":
|
||||||
|
queryset = Users.objects.filter(role__id=role_id).exclude(is_superuser=True)
|
||||||
|
else:
|
||||||
|
queryset = Users.objects.exclude(role__id=role_id).exclude(is_superuser=True)
|
||||||
|
|
||||||
|
if name := request.query_params.get('name', None):
|
||||||
|
queryset = queryset.filter(name__icontains=name)
|
||||||
|
|
||||||
|
if dept := request.query_params.get('dept', None):
|
||||||
|
queryset = queryset.filter(dept=dept)
|
||||||
|
|
||||||
|
page = self.paginate_queryset(queryset.values('id', 'name', 'dept__name'))
|
||||||
|
if page is not None:
|
||||||
|
return self.get_paginated_response(page)
|
||||||
|
|
||||||
|
return SuccessResponse(data=page)
|
||||||
|
|
||||||
|
@action(methods=['DELETE'], detail=True, permission_classes=[IsAuthenticated, CustomPermission])
|
||||||
|
def remove_role_user(self, request, pk):
|
||||||
|
"""
|
||||||
|
角色-删除用户
|
||||||
|
"""
|
||||||
|
user_id = request.data.get('user_id', None)
|
||||||
|
|
||||||
|
if not user_id:
|
||||||
|
return ErrorResponse(msg="请选择用户")
|
||||||
|
|
||||||
|
role = self.get_object()
|
||||||
|
role.users_set.remove(*user_id)
|
||||||
|
|
||||||
|
return SuccessResponse(msg="删除成功")
|
||||||
|
|
||||||
|
@action(methods=['POST'], detail=True, permission_classes=[IsAuthenticated, CustomPermission])
|
||||||
|
def add_role_users(self, request, pk):
|
||||||
|
"""
|
||||||
|
角色-添加用户
|
||||||
|
"""
|
||||||
|
users_id = request.data.get('users_id', None)
|
||||||
|
|
||||||
|
if not users_id:
|
||||||
|
return ErrorResponse(msg="请选择用户")
|
||||||
|
|
||||||
|
role = self.get_object()
|
||||||
|
role.users_set.add(*users_id)
|
||||||
|
|
||||||
|
return DetailResponse(msg="添加成功")
|
||||||
|
|||||||
@@ -231,9 +231,17 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
|||||||
isCheck = data.get('isCheck', None)
|
isCheck = data.get('isCheck', None)
|
||||||
roleId = data.get('roleId', None)
|
roleId = data.get('roleId', None)
|
||||||
btnId = data.get('btnId', None)
|
btnId = data.get('btnId', None)
|
||||||
|
data_range = data.get('data_range', None) or 0 # 默认仅本人权限
|
||||||
|
dept = data.get('dept', None) or [] # 默认空部门
|
||||||
|
|
||||||
if isCheck:
|
if isCheck:
|
||||||
# 添加权限:创建关联记录
|
# 添加权限:创建关联记录
|
||||||
RoleMenuButtonPermission.objects.create(role_id=roleId, menu_button_id=btnId)
|
instance = RoleMenuButtonPermission.objects.create(role_id=roleId,
|
||||||
|
menu_button_id=btnId,
|
||||||
|
data_range=data_range)
|
||||||
|
# 自定义部门权限
|
||||||
|
if data_range == 4 and dept:
|
||||||
|
instance.dept.set(dept)
|
||||||
else:
|
else:
|
||||||
# 删除权限:移除关联记录
|
# 删除权限:移除关联记录
|
||||||
RoleMenuButtonPermission.objects.filter(role_id=roleId, menu_button_id=btnId).delete()
|
RoleMenuButtonPermission.objects.filter(role_id=roleId, menu_button_id=btnId).delete()
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class CoreModelFilterBankend(BaseFilterBackend):
|
|||||||
create_datetime_after = request.query_params.get('create_datetime_after', None)
|
create_datetime_after = request.query_params.get('create_datetime_after', None)
|
||||||
create_datetime_before = request.query_params.get('create_datetime_before', None)
|
create_datetime_before = request.query_params.get('create_datetime_before', None)
|
||||||
update_datetime_after = request.query_params.get('update_datetime_after', None)
|
update_datetime_after = request.query_params.get('update_datetime_after', None)
|
||||||
update_datetime_before = request.query_params.get('update_datetime_after', None)
|
update_datetime_before = request.query_params.get('update_datetime_before', None)
|
||||||
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:
|
||||||
|
|||||||
@@ -29,3 +29,4 @@ gunicorn==22.0.0
|
|||||||
gevent==24.2.1
|
gevent==24.2.1
|
||||||
Pillow==10.4.0
|
Pillow==10.4.0
|
||||||
pyinstaller==6.9.0
|
pyinstaller==6.9.0
|
||||||
|
dvadmin3-celery==3.1.6
|
||||||
58
init.sh
58
init.sh
@@ -1,5 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
ENV_FILE=".env"
|
ENV_FILE=".env"
|
||||||
|
HOST="177.10.0.13"
|
||||||
# 检查 .env 文件是否存在
|
# 检查 .env 文件是否存在
|
||||||
if [ -f "$ENV_FILE" ]; then
|
if [ -f "$ENV_FILE" ]; then
|
||||||
echo "$ENV_FILE 文件已存在。"
|
echo "$ENV_FILE 文件已存在。"
|
||||||
@@ -15,17 +16,60 @@ else
|
|||||||
echo "REDIS随机密码已生成并写入 $ENV_FILE 文件。"
|
echo "REDIS随机密码已生成并写入 $ENV_FILE 文件。"
|
||||||
|
|
||||||
awk 'BEGIN { cmd="cp -i ./backend/conf/env.example.py ./backend/conf/env.py "; print "n" |cmd; }'
|
awk 'BEGIN { cmd="cp -i ./backend/conf/env.example.py ./backend/conf/env.py "; print "n" |cmd; }'
|
||||||
sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.13'|g" ./backend/conf/env.py
|
sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '$HOST'|g" ./backend/conf/env.py
|
||||||
sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.15'|g" ./backend/conf/env.py
|
sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.15'|g" ./backend/conf/env.py
|
||||||
sed -i "s|DATABASE_PASSWORD = 'DVADMIN3'|DATABASE_PASSWORD = '$MYSQL_PASSWORD'|g" ./backend/conf/env.py
|
sed -i "s|DATABASE_PASSWORD = 'DVADMIN3'|DATABASE_PASSWORD = '$MYSQL_PASSWORD'|g" ./backend/conf/env.py
|
||||||
sed -i "s|REDIS_PASSWORD = 'DVADMIN3'|REDIS_PASSWORD = '$REDIS_PASSWORD'|g" ./backend/conf/env.py
|
sed -i "s|REDIS_PASSWORD = 'DVADMIN3'|REDIS_PASSWORD = '$REDIS_PASSWORD'|g" ./backend/conf/env.py
|
||||||
echo "初始化密码创建成功"
|
echo "初始化密码创建成功"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "正在启动容器..."
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
docker exec dvadmin3-django python manage.py makemigrations
|
|
||||||
docker exec dvadmin3-django python manage.py migrate
|
if [ $? -ne 0 ]; then
|
||||||
docker exec dvadmin3-django python manage.py init
|
echo "docker-compose up -d 执行失败!"
|
||||||
echo "欢迎使用dvadmin3项目"
|
exit 1
|
||||||
echo "登录地址:http://ip:8080"
|
fi
|
||||||
echo "如访问不到,请检查防火墙配置"
|
|
||||||
|
MYSQL_PORT=3306
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
check_mysql() {
|
||||||
|
if nc -z "$HOST" "$MYSQL_PORT" >/dev/null 2>&1; then
|
||||||
|
echo "MySQL 服务正在运行在 $HOST:$MYSQL_PORT"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_redis() {
|
||||||
|
if nc -z "$HOST" "$REDIS_PORT" >/dev/null 2>&1; then
|
||||||
|
echo "Redis 服务正在运行在 $HOST:$REDIS_PORT"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
i=1
|
||||||
|
while [ $i -le 8 ]; do
|
||||||
|
if check_mysql || check_redis; then
|
||||||
|
echo "正在迁移数据..."
|
||||||
|
docker exec dvadmin3-django python3 manage.py makemigrations
|
||||||
|
docker exec dvadmin3-django python3 manage.py migrate
|
||||||
|
echo "正在初始化数据..."
|
||||||
|
docker exec dvadmin3-django python3 manage.py init
|
||||||
|
echo "欢迎使用dvadmin3项目"
|
||||||
|
echo "登录地址:http://ip:8080"
|
||||||
|
echo "如访问不到,请检查防火墙配置"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "第 $i 次尝试:MySQL 或 REDIS服务未运行,等待 2 秒后重试..."
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "尝试 5 次后,MySQL 或 REDIS服务仍未运行"
|
||||||
|
exit 1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "django-vue3-admin",
|
"name": "django-vue3-admin",
|
||||||
"version": "3.0.4",
|
"version": "3.1.0",
|
||||||
"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": {
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"@fast-crud/fast-extends": "^1.21.2",
|
"@fast-crud/fast-extends": "^1.21.2",
|
||||||
"@fast-crud/ui-element": "^1.21.2",
|
"@fast-crud/ui-element": "^1.21.2",
|
||||||
"@fast-crud/ui-interface": "^1.21.2",
|
"@fast-crud/ui-interface": "^1.21.2",
|
||||||
|
"@great-dream/dvadmin3-celery-web": "^3.1.3",
|
||||||
"@iconify/vue": "^4.1.2",
|
"@iconify/vue": "^4.1.2",
|
||||||
"@types/lodash": "^4.17.7",
|
"@types/lodash": "^4.17.7",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="user-info-head" @click="editCropper()">
|
<div class="user-info-head" @click="editCropper()">
|
||||||
<el-avatar :size="100" :src="options.img" />
|
<el-avatar :size="100" :src="getBaseURL(options.img)" />
|
||||||
<el-dialog :title="title" v-model="dialogVisiable" width="600px" append-to-body @opened="modalOpened" @close="closeDialog">
|
<el-dialog :title="title" v-model="dialogVisiable" width="600px" append-to-body @opened="modalOpened" @close="closeDialog">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col class="flex justify-center">
|
<el-col class="flex justify-center">
|
||||||
@@ -50,10 +50,11 @@ import { VueCropper } from 'vue-cropper';
|
|||||||
import { useUserInfo } from '/@/stores/userInfo';
|
import { useUserInfo } from '/@/stores/userInfo';
|
||||||
import { getCurrentInstance, nextTick, reactive, ref, computed, onMounted, defineExpose } from 'vue';
|
import { getCurrentInstance, nextTick, reactive, ref, computed, onMounted, defineExpose } from 'vue';
|
||||||
import { base64ToFile } from '/@/utils/tools';
|
import { base64ToFile } from '/@/utils/tools';
|
||||||
|
import headerImage from "/@/assets/img/headerImage.png";
|
||||||
|
import {getBaseURL} from "/@/utils/baseUrl";
|
||||||
const userStore = useUserInfo();
|
const userStore = useUserInfo();
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
const open = ref(false);
|
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const title = ref('修改头像');
|
const title = ref('修改头像');
|
||||||
const emit = defineEmits(['uploadImg']);
|
const emit = defineEmits(['uploadImg']);
|
||||||
@@ -75,7 +76,7 @@ const dialogVisiable = computed({
|
|||||||
|
|
||||||
//图片裁剪数据
|
//图片裁剪数据
|
||||||
const options = reactive({
|
const options = reactive({
|
||||||
img: userStore.userInfos.avatar, // 裁剪图片的地址
|
img: userStore.userInfos.avatar || headerImage, // 裁剪图片的地址
|
||||||
fileName: '',
|
fileName: '',
|
||||||
autoCrop: true, // 是否默认生成截图框
|
autoCrop: true, // 是否默认生成截图框
|
||||||
autoCropWidth: 200, // 默认生成截图框宽度
|
autoCropWidth: 200, // 默认生成截图框宽度
|
||||||
@@ -165,6 +166,7 @@ const updateAvatar = (img) => {
|
|||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
updateAvatar,
|
updateAvatar,
|
||||||
|
editCropper
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -172,7 +174,6 @@ defineExpose({
|
|||||||
.user-info-head {
|
.user-info-head {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 120px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info-head:hover:after {
|
.user-info-head:hover:after {
|
||||||
|
|||||||
@@ -8,7 +8,27 @@
|
|||||||
<el-option v-for="item, index in listAllData" :key="index" :value="String(item[props.valueKey])"
|
<el-option v-for="item, index in listAllData" :key="index" :value="String(item[props.valueKey])"
|
||||||
:label="item.name" />
|
:label="item.name" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<div v-if="props.inputType === 'image'" style="position: relative;" class="form-display"
|
|
||||||
|
<div v-if="props.inputType === 'image' && props.multiple"
|
||||||
|
style="width: 100%; display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 4px;">
|
||||||
|
<el-image v-for="item, index in (data || [])" :src="item" :key="index" fit="scale-down" class="itemList"
|
||||||
|
:style="{ width: props.inputSize + 'px', aspectRatio: '1 / 1' }" />
|
||||||
|
<div style="position: relative;" :style="{ width: props.inputSize + 'px', height: props.inputSize + 'px' }">
|
||||||
|
<div
|
||||||
|
style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center;">
|
||||||
|
<el-icon :size="24">
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div @click="selectVisiable = true && !props.disabled" class="addControllorHover"
|
||||||
|
:style="{ cursor: props.disabled ? 'not-allowed' : 'pointer' }"></div>
|
||||||
|
<el-icon v-show="(!!data && !props.disabled) && !props.multiple" class="closeHover" :size="16"
|
||||||
|
@click="clear">
|
||||||
|
<Close />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="props.inputType === 'image' && !props.multiple" class="form-display" style="position: relative;"
|
||||||
@mouseenter="formDisplayEnter" @mouseleave="formDisplayLeave"
|
@mouseenter="formDisplayEnter" @mouseleave="formDisplayLeave"
|
||||||
:style="{ width: props.inputSize + 'px', height: props.inputSize + 'px' }">
|
:style="{ width: props.inputSize + 'px', height: props.inputSize + 'px' }">
|
||||||
<el-image :src="data" fit="scale-down" :style="{ width: props.inputSize + 'px', aspectRatio: '1 / 1' }">
|
<el-image :src="data" fit="scale-down" :style="{ width: props.inputSize + 'px', aspectRatio: '1 / 1' }">
|
||||||
@@ -24,10 +44,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div @click="selectVisiable = true && !props.disabled" class="addControllorHover"
|
<div @click="selectVisiable = true && !props.disabled" class="addControllorHover"
|
||||||
:style="{ cursor: props.disabled ? 'not-allowed' : 'pointer' }"></div>
|
:style="{ cursor: props.disabled ? 'not-allowed' : 'pointer' }"></div>
|
||||||
<el-icon v-show="!!data && !props.disabled" class="closeHover" :size="16" @click="clear">
|
<el-icon v-show="(!!data && !props.disabled) && !props.multiple" class="closeHover" :size="16" @click="clear">
|
||||||
<Close />
|
<Close />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="props.inputType === 'video'" class="form-display" @mouseenter="formDisplayEnter"
|
<div v-if="props.inputType === 'video'" class="form-display" @mouseenter="formDisplayEnter"
|
||||||
@mouseleave="formDisplayLeave"
|
@mouseleave="formDisplayLeave"
|
||||||
style="position: relative; display: flex; align-items: center; justify-items: center;"
|
style="position: relative; display: flex; align-items: center; justify-items: center;"
|
||||||
@@ -46,6 +67,7 @@
|
|||||||
<Close />
|
<Close />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="props.inputType === 'audio'" class="form-display" @mouseenter="formDisplayEnter"
|
<div v-if="props.inputType === 'audio'" class="form-display" @mouseenter="formDisplayEnter"
|
||||||
@mouseleave="formDisplayLeave"
|
@mouseleave="formDisplayLeave"
|
||||||
style="position: relative; display: flex; align-items: center; justify-items: center;"
|
style="position: relative; display: flex; align-items: center; justify-items: center;"
|
||||||
@@ -199,7 +221,7 @@ const props = defineProps({
|
|||||||
tabsShow: { type: Number, default: SHOW.ALL },
|
tabsShow: { type: Number, default: SHOW.ALL },
|
||||||
|
|
||||||
// 是否可以多选,默认单选
|
// 是否可以多选,默认单选
|
||||||
// 该值为true时inputType必须是selector(暂不支持其他type的多选)
|
// 该值为true时inputType必须是selector或image(暂不支持其他type的多选)
|
||||||
multiple: { type: Boolean, default: false },
|
multiple: { type: Boolean, default: false },
|
||||||
|
|
||||||
// 是否可选,该参数用于只上传和展示而不选择和绑定model的情况
|
// 是否可选,该参数用于只上传和展示而不选择和绑定model的情况
|
||||||
@@ -274,6 +296,7 @@ const onItemClick = async (e: MouseEvent) => {
|
|||||||
while (!target.dataset.id) target = target.parentElement as HTMLElement;
|
while (!target.dataset.id) target = target.parentElement as HTMLElement;
|
||||||
let fileId = target.dataset.id;
|
let fileId = target.dataset.id;
|
||||||
if (props.multiple) {
|
if (props.multiple) {
|
||||||
|
if (!!!data.value) data.value = [];
|
||||||
if (target.classList.contains('active')) { target.classList.remove('active'); flat = -1; }
|
if (target.classList.contains('active')) { target.classList.remove('active'); flat = -1; }
|
||||||
else { target.classList.add('active'); flat = 1; }
|
else { target.classList.add('active'); flat = 1; }
|
||||||
if (data.value.length) {
|
if (data.value.length) {
|
||||||
@@ -394,7 +417,8 @@ const onDataChange = (value: any) => {
|
|||||||
defineExpose({ data, onDataChange, selectVisiable, clearState, clear });
|
defineExpose({ data, onDataChange, selectVisiable, clearState, clear });
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.multiple && props.inputType !== 'selector')
|
|
||||||
|
if (props.multiple && !['selector', 'image'].includes(props.inputType))
|
||||||
throw new Error('FileSelector组件属性multiple为true时inputType必须为selector');
|
throw new Error('FileSelector组件属性multiple为true时inputType必须为selector');
|
||||||
listRequestAll();
|
listRequestAll();
|
||||||
console.log('fileselector tenentmdoe', isTenentMode);
|
console.log('fileselector tenentmdoe', isTenentMode);
|
||||||
@@ -475,4 +499,9 @@ onMounted(() => {
|
|||||||
top: 2px;
|
top: 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.itemList {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
popper-class="popperClass"
|
popper-class="popperClass"
|
||||||
class="tableSelector"
|
class="tableSelector"
|
||||||
multiple
|
multiple
|
||||||
|
:collapseTags="props.tableConfig.collapseTags"
|
||||||
@remove-tag="removeTag"
|
@remove-tag="removeTag"
|
||||||
v-model="data"
|
v-model="data"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
@@ -18,20 +19,22 @@
|
|||||||
<el-table
|
<el-table
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
size="mini"
|
:size="props.tableConfig.size"
|
||||||
border
|
border
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:lazy="props.tableConfig.lazy"
|
:lazy="props.tableConfig.lazy"
|
||||||
:load="props.tableConfig.load"
|
:load="props.tableConfig.load"
|
||||||
:tree-props="props.tableConfig.treeProps"
|
:tree-props="props.tableConfig.treeProps"
|
||||||
style="width: 400px"
|
style="width: 600px"
|
||||||
max-height="200"
|
max-height="200"
|
||||||
height="200"
|
height="200"
|
||||||
:highlight-current-row="!props.tableConfig.isMultiple"
|
:highlight-current-row="!props.tableConfig.isMultiple"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
|
@select="handleSelectionChange"
|
||||||
|
@selectAll="handleSelectionChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
>
|
>
|
||||||
<el-table-column v-if="props.tableConfig.isMultiple" fixed type="selection" width="55" />
|
<el-table-column v-if="props.tableConfig.isMultiple" fixed type="selection" reserve-selection width="55" />
|
||||||
<el-table-column fixed type="index" label="#" width="50" />
|
<el-table-column fixed type="index" label="#" width="50" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:prop="item.prop"
|
:prop="item.prop"
|
||||||
@@ -56,24 +59,32 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, reactive, ref, watch } from 'vue';
|
import {computed, defineProps, onMounted, reactive, ref, watch} from 'vue';
|
||||||
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: {
|
||||||
|
type: Array || String || Number,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
tableConfig: {
|
tableConfig: {
|
||||||
url: null,
|
type: Object,
|
||||||
label: null, //显示值
|
default:{
|
||||||
value: null, //数据值
|
url: null,
|
||||||
isTree: false,
|
label: null, //显示值
|
||||||
lazy: true,
|
value: null, //数据值
|
||||||
load: () => {},
|
isTree: false,
|
||||||
data: [], //默认数据
|
lazy: true,
|
||||||
isMultiple: false, //是否多选
|
size:'default',
|
||||||
treeProps: { children: 'children', hasChildren: 'hasChildren' },
|
load: () => {},
|
||||||
columns: [], //每一项对应的列表项
|
data: [], //默认数据
|
||||||
},
|
isMultiple: false, //是否多选
|
||||||
|
collapseTags:false,
|
||||||
|
treeProps: { children: 'children', hasChildren: 'hasChildren' },
|
||||||
|
columns: [], //每一项对应的列表项
|
||||||
|
},
|
||||||
|
},
|
||||||
displayLabel: {},
|
displayLabel: {},
|
||||||
} as any);
|
} as any);
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
@@ -86,7 +97,7 @@ const multipleSelection = ref();
|
|||||||
// 搜索值
|
// 搜索值
|
||||||
const search = ref(undefined);
|
const search = ref(undefined);
|
||||||
//表格数据
|
//表格数据
|
||||||
const tableData = ref();
|
const tableData = ref([]);
|
||||||
// 分页的配置
|
// 分页的配置
|
||||||
const pageConfig = reactive({
|
const pageConfig = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -99,7 +110,6 @@ const pageConfig = reactive({
|
|||||||
* @param val:Array
|
* @param val:Array
|
||||||
*/
|
*/
|
||||||
const handleSelectionChange = (val: any) => {
|
const handleSelectionChange = (val: any) => {
|
||||||
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];
|
||||||
@@ -117,7 +127,7 @@ const handleSelectionChange = (val: any) => {
|
|||||||
const handleCurrentChange = (val: any) => {
|
const handleCurrentChange = (val: any) => {
|
||||||
const { tableConfig } = props;
|
const { tableConfig } = props;
|
||||||
if (!tableConfig.isMultiple && val) {
|
if (!tableConfig.isMultiple && val) {
|
||||||
data.value = [val[tableConfig.label]];
|
// data.value = [val[tableConfig.label]];
|
||||||
emit('update:modelValue', val[tableConfig.value]);
|
emit('update:modelValue', val[tableConfig.value]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -150,6 +160,32 @@ const getDict = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取节点值
|
||||||
|
const getNodeValues = () => {
|
||||||
|
request({
|
||||||
|
url:props.tableConfig.valueUrl,
|
||||||
|
method:'post',
|
||||||
|
data:{ids:props.modelValue}
|
||||||
|
}).then(res=>{
|
||||||
|
if(res.data.length>0){
|
||||||
|
data.value = res.data.map((item:any)=>{
|
||||||
|
return item[props.tableConfig.label]
|
||||||
|
})
|
||||||
|
|
||||||
|
tableRef.value!.clearSelection()
|
||||||
|
res.data.forEach((row) => {
|
||||||
|
tableRef.value!.toggleRowSelection(
|
||||||
|
row,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下拉框展开/关闭
|
* 下拉框展开/关闭
|
||||||
* @param bool
|
* @param bool
|
||||||
@@ -169,20 +205,12 @@ const handlePageChange = (page: any) => {
|
|||||||
getDict();
|
getDict();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听displayLabel的变化,更新数据
|
onMounted(()=>{
|
||||||
watch(
|
setTimeout(()=>{
|
||||||
() => {
|
getNodeValues()
|
||||||
return props.displayLabel;
|
},1000)
|
||||||
},
|
})
|
||||||
(value) => {
|
|
||||||
const { tableConfig } = props;
|
|
||||||
const result = value
|
|
||||||
? value.map((item: any) => { return item[tableConfig.label];})
|
|
||||||
: null;
|
|
||||||
data.value = result;
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -17,13 +17,15 @@ import { useRoutesList } from '/@/stores/routesList';
|
|||||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||||
import mittBus from '/@/utils/mitt';
|
import mittBus from '/@/utils/mitt';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
const route = useRoute();
|
||||||
// 引入组件
|
// 引入组件
|
||||||
const Logo = defineAsyncComponent(() => import('/@/layout/logo/index.vue'));
|
const Logo = defineAsyncComponent(() => import('/@/layout/logo/index.vue'));
|
||||||
const Vertical = defineAsyncComponent(() => import('/@/layout/navMenu/vertical.vue'));
|
const Vertical = defineAsyncComponent(() => import('/@/layout/navMenu/vertical.vue'));
|
||||||
|
|
||||||
// 定义变量内容
|
// 定义变量内容
|
||||||
const layoutAsideScrollbarRef = ref();
|
const layoutAsideScrollbarRef = ref();
|
||||||
|
const routesIndex = ref(0);
|
||||||
const stores = useRoutesList();
|
const stores = useRoutesList();
|
||||||
const storesThemeConfig = useThemeConfig();
|
const storesThemeConfig = useThemeConfig();
|
||||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||||
@@ -83,10 +85,36 @@ const closeLayoutAsideMobileMode = () => {
|
|||||||
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
|
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
|
||||||
document.body.setAttribute('class', '');
|
document.body.setAttribute('class', '');
|
||||||
};
|
};
|
||||||
|
const findFirstLevelIndex = (data, path) => {
|
||||||
|
for (let index = 0; index < data.length; index++) {
|
||||||
|
const item = data[index];
|
||||||
|
// 检查当前菜单项是否有子菜单,并查找是否在子菜单中找到路径
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
// 检查子菜单中是否有匹配的路径
|
||||||
|
const childIndex = item.children.findIndex((child) => child.path === path);
|
||||||
|
if (childIndex !== -1) {
|
||||||
|
return index; // 返回当前一级菜单的索引
|
||||||
|
}
|
||||||
|
// 递归查找子菜单
|
||||||
|
const foundIndex = findFirstLevelIndex(item.children, path);
|
||||||
|
if (foundIndex !== null) {
|
||||||
|
return index; // 返回找到的索引
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // 找不到路径时返回 null
|
||||||
|
};
|
||||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||||
const setFilterRoutes = () => {
|
const setFilterRoutes = (path='') => {
|
||||||
if (themeConfig.value.layout === 'columns') return false;
|
if (themeConfig.value.layout === 'columns') return false;
|
||||||
state.menuList = filterRoutesFun(routesList.value);
|
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||||
|
if (layout === 'classic' && isClassicSplitMenu) {
|
||||||
|
// 获取当前地址的索引,不用从参数选取
|
||||||
|
routesIndex.value = findFirstLevelIndex(routesList.value,path || route.path) || 0
|
||||||
|
state.menuList = filterRoutesFun(routesList.value[routesIndex.value].children || [routesList.value[routesIndex.value]]);
|
||||||
|
} else {
|
||||||
|
state.menuList = filterRoutesFun(routesList.value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// 路由过滤递归函数
|
// 路由过滤递归函数
|
||||||
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
|
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
|
||||||
@@ -122,7 +150,8 @@ onBeforeMount(() => {
|
|||||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||||
if (layout === 'classic' && isClassicSplitMenu) {
|
if (layout === 'classic' && isClassicSplitMenu) {
|
||||||
state.menuList = [];
|
state.menuList = [];
|
||||||
state.menuList = res.children;
|
// state.menuList = res.children;
|
||||||
|
setFilterRoutes(res.path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
||||||
|
|||||||
@@ -102,6 +102,5 @@ onUnmounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--next-bg-topBar);
|
background: var(--next-bg-topBar);
|
||||||
border-bottom: 1px solid var(--next-border-color-light);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
|
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-navbars-breadcrumb-user-icon">
|
<div class="layout-navbars-breadcrumb-user-icon">
|
||||||
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
|
<el-popover placement="bottom" trigger="hover" transition="el-zoom-in-top" :width="300" :persistent="false">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-badge :value="messageCenter.unread" :hidden="messageCenter.unread === 0">
|
<el-badge :value="messageCenter.unread" :hidden="messageCenter.unread === 0">
|
||||||
<el-icon :title="$t('message.user.title4')">
|
<el-icon :title="$t('message.user.title4')">
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span v-if="!isSocketOpen">
|
<span v-if="!isSocketOpen" class="online-status-span">
|
||||||
<el-popconfirm
|
<el-popconfirm
|
||||||
width="250"
|
width="250"
|
||||||
ref="onlinePopoverRef"
|
ref="onlinePopoverRef"
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
>
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
||||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
<img :src="getBaseURL(userInfos.avatar) || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||||
</el-badge>
|
</el-badge>
|
||||||
</template>
|
</template>
|
||||||
</el-popconfirm>
|
</el-popconfirm>
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
|
<el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
|
||||||
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
|
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
|
||||||
<el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item>
|
<el-dropdown-item command="/versionUpgradeLog">更新日志</el-dropdown-item>
|
||||||
<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
|
<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
@@ -250,6 +250,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
//消息中心的未读数量
|
//消息中心的未读数量
|
||||||
import { messageCenterStore } from '/@/stores/messageCenter';
|
import { messageCenterStore } from '/@/stores/messageCenter';
|
||||||
|
import {getBaseURL} from "/@/utils/baseUrl";
|
||||||
const messageCenter = messageCenterStore();
|
const messageCenter = messageCenterStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="el-menu-horizontal-warp">
|
<div class="el-menu-horizontal-warp">
|
||||||
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
|
<!-- <el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">-->
|
||||||
<el-menu router :default-active="state.defaultActive" :ellipsis="false" background-color="transparent" mode="horizontal">
|
<el-menu :default-active="defaultActive" background-color="transparent" mode="horizontal">
|
||||||
<template v-for="val in menuLists">
|
<template v-for="(val,index) in menuLists">
|
||||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||||
<template #title>
|
<template #title>
|
||||||
<SvgIcon :name="val.meta.icon" />
|
<SvgIcon :name="val.meta.icon" />
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<SubItem :chil="val.children" />
|
<SubItem :chil="val.children" />
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-menu-item :index="val.path" :key="val.path">
|
<el-menu-item :index="val.path" :key="val.path" style="--el-menu-active-color: #fff" @click="onToRouteClick(val,index)">
|
||||||
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||||
<SvgIcon :name="val.meta.icon" />
|
<SvgIcon :name="val.meta.icon" />
|
||||||
{{ $t(val.meta.title) }}
|
{{ $t(val.meta.title) }}
|
||||||
@@ -26,22 +26,25 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</el-scrollbar>
|
<!-- </el-scrollbar>-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="navMenuHorizontal">
|
<script setup lang="ts" name="navMenuHorizontal">
|
||||||
import { defineAsyncComponent, reactive, computed, onMounted, nextTick, onBeforeMount, ref } from 'vue';
|
import { defineAsyncComponent, reactive, computed, onMounted, nextTick, onBeforeMount, ref } from 'vue';
|
||||||
import { useRoute, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
|
import {useRoute, onBeforeRouteUpdate, RouteRecordRaw, useRouter} from 'vue-router';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useRoutesList } from '/@/stores/routesList';
|
import { useRoutesList } from '/@/stores/routesList';
|
||||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||||
import other from '/@/utils/other';
|
import other from '/@/utils/other';
|
||||||
import mittBus from '/@/utils/mitt';
|
import mittBus from '/@/utils/mitt';
|
||||||
|
const router = useRouter()
|
||||||
// 引入组件
|
// 引入组件
|
||||||
const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue'));
|
const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue'));
|
||||||
|
const state = reactive<AsideState>({
|
||||||
|
menuList: [],
|
||||||
|
clientWidth: 0
|
||||||
|
});
|
||||||
// 定义父组件传过来的值
|
// 定义父组件传过来的值
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 菜单列表
|
// 菜单列表
|
||||||
@@ -58,19 +61,33 @@ const storesThemeConfig = useThemeConfig();
|
|||||||
const { routesList } = storeToRefs(stores);
|
const { routesList } = storeToRefs(stores);
|
||||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const state = reactive({
|
const defaultActive = ref('')
|
||||||
defaultActive: '' as string | undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取父级菜单数据
|
// 获取父级菜单数据
|
||||||
const menuLists = computed(() => {
|
const menuLists = computed(() => {
|
||||||
|
<RouteItems>props.menuList.shift()
|
||||||
return <RouteItems>props.menuList;
|
return <RouteItems>props.menuList;
|
||||||
});
|
});
|
||||||
// 设置横向滚动条可以鼠标滚轮滚动
|
// 递归获取当前路由的顶级索引
|
||||||
const onElMenuHorizontalScroll = (e: WheelEventType) => {
|
const findFirstLevelIndex = (data, path) => {
|
||||||
const eventDelta = e.wheelDelta || -e.deltaY * 40;
|
for (let index = 0; index < data.length; index++) {
|
||||||
elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft + eventDelta / 4;
|
const item = data[index];
|
||||||
|
// 检查当前菜单项是否有子菜单,并查找是否在子菜单中找到路径
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
// 检查子菜单中是否有匹配的路径
|
||||||
|
const childIndex = item.children.findIndex((child) => child.path === path);
|
||||||
|
if (childIndex !== -1) {
|
||||||
|
return index; // 返回当前一级菜单的索引
|
||||||
|
}
|
||||||
|
// 递归查找子菜单
|
||||||
|
const foundIndex = findFirstLevelIndex(item.children, path);
|
||||||
|
if (foundIndex !== null) {
|
||||||
|
return index; // 返回找到的索引
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // 找不到路径时返回 null
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化数据,页面刷新时,滚动条滚动到对应位置
|
// 初始化数据,页面刷新时,滚动条滚动到对应位置
|
||||||
const initElMenuOffsetLeft = () => {
|
const initElMenuOffsetLeft = () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -107,17 +124,41 @@ const setSendClassicChildren = (path: string) => {
|
|||||||
const setCurrentRouterHighlight = (currentRoute: RouteToFrom) => {
|
const setCurrentRouterHighlight = (currentRoute: RouteToFrom) => {
|
||||||
const { path, meta } = currentRoute;
|
const { path, meta } = currentRoute;
|
||||||
if (themeConfig.value.layout === 'classic') {
|
if (themeConfig.value.layout === 'classic') {
|
||||||
state.defaultActive = `/${path?.split('/')[1]}`;
|
let firstLevelIndex = (findFirstLevelIndex(routesList.value, route.path) || 0) - 1
|
||||||
|
defaultActive.value = firstLevelIndex < 0 ? defaultActive.value : menuLists.value[firstLevelIndex].path
|
||||||
} else {
|
} else {
|
||||||
const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split('/') : path!.split('/');
|
const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split('/') : path!.split('/');
|
||||||
if (pathSplit.length >= 4 && meta?.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
|
if (pathSplit.length >= 4 && meta?.isHide) defaultActive.value = pathSplit.splice(0, 3).join('/');
|
||||||
else state.defaultActive = path;
|
else defaultActive.value = path;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 打开外部链接
|
// 打开外部链接
|
||||||
const onALinkClick = (val: RouteItem) => {
|
const onALinkClick = (val: RouteItem) => {
|
||||||
other.handleOpenLink(val);
|
other.handleOpenLink(val);
|
||||||
};
|
};
|
||||||
|
// 跳转页面
|
||||||
|
const onToRouteClick = (val: RouteItem,index) => {
|
||||||
|
// 跳转到子级页面
|
||||||
|
let children = val.children
|
||||||
|
if (children === undefined){
|
||||||
|
defaultActive.value = val.path
|
||||||
|
children = setSendClassicChildren(val.path).children
|
||||||
|
}
|
||||||
|
if (children.length >= 1){
|
||||||
|
if (children[0].is_catalog) {
|
||||||
|
onToRouteClick(children[0],index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
router.push(children[0].path)
|
||||||
|
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||||
|
if (layout === 'classic' && isClassicSplitMenu) {
|
||||||
|
mittBus.emit('setSendClassicChildren', children[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
router.push('/home')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 页面加载前
|
// 页面加载前
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
setCurrentRouterHighlight(route);
|
setCurrentRouterHighlight(route);
|
||||||
@@ -126,16 +167,6 @@ onBeforeMount(() => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initElMenuOffsetLeft();
|
initElMenuOffsetLeft();
|
||||||
});
|
});
|
||||||
// 路由更新时
|
|
||||||
onBeforeRouteUpdate((to) => {
|
|
||||||
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
|
||||||
setCurrentRouterHighlight(to);
|
|
||||||
// 修复经典布局开启切割菜单时,点击tagsView后左侧导航菜单数据不变的问题
|
|
||||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
|
||||||
if (layout === 'classic' && isClassicSplitMenu) {
|
|
||||||
mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -8,7 +8,21 @@
|
|||||||
<sub-item :chil="val.children" />
|
<sub-item :chil="val.children" />
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-menu-item :index="val.path" :key="val.path">
|
<a v-if="val.name==='templateCenter'" href="#/templateCenter" target="_blank">
|
||||||
|
<el-menu-item :key="val.path">
|
||||||
|
<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||||
|
<SvgIcon :name="val.meta.icon" />
|
||||||
|
<span>{{ $t(val.meta.title) }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a class="w100" @click.prevent="onALinkClick(val)">
|
||||||
|
<SvgIcon :name="val.meta.icon" />
|
||||||
|
{{ $t(val.meta.title) }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</a>
|
||||||
|
<el-menu-item v-else :index="val.path" :key="val.path">
|
||||||
<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||||
<SvgIcon :name="val.meta.icon" />
|
<SvgIcon :name="val.meta.icon" />
|
||||||
<span>{{ $t(val.meta.title) }}</span>
|
<span>{{ $t(val.meta.title) }}</span>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
<script setup lang="ts" name="layoutIframeView">
|
<script setup lang="ts" name="layoutIframeView">
|
||||||
import { computed, watch, ref, nextTick } from 'vue';
|
import { computed, watch, ref, nextTick } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import {cookie} from "xe-utils";
|
||||||
|
|
||||||
// 定义父组件传过来的值
|
// 定义父组件传过来的值
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -49,7 +50,15 @@ const route = useRoute();
|
|||||||
|
|
||||||
// 处理 list 列表,当打开时,才进行加载
|
// 处理 list 列表,当打开时,才进行加载
|
||||||
const setIframeList = computed(() => {
|
const setIframeList = computed(() => {
|
||||||
return (<RouteItems>props.list).filter((v: RouteItem) => v.meta?.isIframeOpen);
|
return (<RouteItems>props.list).filter((v: RouteItem) => {
|
||||||
|
if (v.meta?.isIframeOpen) {
|
||||||
|
const isLink = v.meta?.isLink || '';
|
||||||
|
if (isLink.includes("{{token}}")) {
|
||||||
|
v.meta.isLink = isLink.replace("{{token}}", cookie.get('token'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v.meta?.isIframeOpen
|
||||||
|
});
|
||||||
});
|
});
|
||||||
// 获取 iframe 当前路由 path
|
// 获取 iframe 当前路由 path
|
||||||
const getRoutePath = computed(() => {
|
const getRoutePath = computed(() => {
|
||||||
|
|||||||
@@ -17,19 +17,31 @@
|
|||||||
import { reactive, watch } from 'vue';
|
import { reactive, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { verifyUrl } from '/@/utils/toolsValidate';
|
import { verifyUrl } from '/@/utils/toolsValidate';
|
||||||
|
import {cookie} from "xe-utils";
|
||||||
|
|
||||||
// 定义变量内容
|
// 定义变量内容
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const state = reactive<LinkViewState>({
|
const state = reactive<LinkViewState>({
|
||||||
title: '',
|
title: '',
|
||||||
isLink: '',
|
isLink: '',
|
||||||
|
query: null
|
||||||
});
|
});
|
||||||
|
|
||||||
// 立即前往
|
// 立即前往
|
||||||
const onGotoFullPage = () => {
|
const onGotoFullPage = () => {
|
||||||
const { origin, pathname } = window.location;
|
const { origin, pathname } = window.location;
|
||||||
|
if (state.isLink.includes("{{token}}")) {
|
||||||
|
state.isLink = state.isLink.replace("{{token}}", cookie.get('token'))
|
||||||
|
}
|
||||||
if (verifyUrl(<string>state.isLink)) window.open(state.isLink);
|
if (verifyUrl(<string>state.isLink)) window.open(state.isLink);
|
||||||
else window.open(`${origin}${pathname}#${state.isLink}`);
|
else {
|
||||||
|
function objectToUrlParams(obj: { [key: string]: string | number }): string {
|
||||||
|
return Object.keys(obj)
|
||||||
|
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
|
||||||
|
.join('&');
|
||||||
|
}
|
||||||
|
window.open(`${origin}${pathname}#${state.isLink}?${objectToUrlParams(state.query)}`)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
// 监听路由的变化,设置内容
|
// 监听路由的变化,设置内容
|
||||||
watch(
|
watch(
|
||||||
@@ -37,6 +49,7 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
state.title = <string>route.meta.title;
|
state.title = <string>route.meta.title;
|
||||||
state.isLink = <string>route.meta.isLink;
|
state.isLink = <string>route.meta.isLink;
|
||||||
|
state.query = <any>route.query;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ const menuApi = useMenuApi();
|
|||||||
|
|
||||||
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
|
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
|
||||||
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
|
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
|
||||||
|
const greatDream: any = import.meta.glob('@great-dream/**/*.{vue,tsx}');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取目录下的 .vue、.tsx 全部文件
|
* 获取目录下的 .vue、.tsx 全部文件
|
||||||
* @method import.meta.glob
|
* @method import.meta.glob
|
||||||
* @link 参考:https://cn.vitejs.dev/guide/features.html#json
|
* @link 参考:https://cn.vitejs.dev/guide/features.html#json
|
||||||
*/
|
*/
|
||||||
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...layouModules }, { ...viewsModules });
|
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...layouModules }, { ...viewsModules }, { ...greatDream });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 后端控制路由:初始化方法,防止刷新时路由丢失
|
* 后端控制路由:初始化方法,防止刷新时路由丢失
|
||||||
@@ -198,7 +199,10 @@ export function dynamicImport(dynamicViewsModules: Record<string, Function>, com
|
|||||||
const keys = Object.keys(dynamicViewsModules);
|
const keys = Object.keys(dynamicViewsModules);
|
||||||
const matchKeys = keys.filter((key) => {
|
const matchKeys = keys.filter((key) => {
|
||||||
const k = key.replace(/..\/views|../, '');
|
const k = key.replace(/..\/views|../, '');
|
||||||
return k.startsWith(`${component}`) || k.startsWith(`/${component}`);
|
const k0 = k.replace("ode_modules/@great-dream/", '')
|
||||||
|
const k1 = k0.replace("/plugins", '')
|
||||||
|
const newComponent = component.replace("plugins/", "")
|
||||||
|
return k1.startsWith(`${newComponent}`) || k1.startsWith(`/${newComponent}`);
|
||||||
});
|
});
|
||||||
if (matchKeys?.length === 1) {
|
if (matchKeys?.length === 1) {
|
||||||
const matchKey = matchKeys[0];
|
const matchKey = matchKeys[0];
|
||||||
|
|||||||
1
web/src/types/layout.d.ts
vendored
1
web/src/types/layout.d.ts
vendored
@@ -56,4 +56,5 @@ declare type ParentViewState<T = any> = {
|
|||||||
declare type LinkViewState = {
|
declare type LinkViewState = {
|
||||||
title: string;
|
title: string;
|
||||||
isLink: string;
|
isLink: string;
|
||||||
|
query: any;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,259 +1,286 @@
|
|||||||
import {dict} from "@fast-crud/fast-crud";
|
import {dict} from '@fast-crud/fast-crud';
|
||||||
import {shallowRef} from 'vue'
|
import {shallowRef} from 'vue';
|
||||||
import deptFormat from "/@/components/dept-format/index.vue";
|
import deptFormat from '/@/components/dept-format/index.vue';
|
||||||
|
|
||||||
export const commonCrudConfig = (options = {
|
/** 1. 每个字段可选属性 */
|
||||||
create_datetime: {
|
export interface CrudFieldOption {
|
||||||
form: false,
|
form?: boolean;
|
||||||
table: false,
|
table?: boolean;
|
||||||
search: false
|
search?: boolean;
|
||||||
},
|
width?: number;
|
||||||
update_datetime: {
|
|
||||||
form: false,
|
|
||||||
table: false,
|
|
||||||
search: false
|
|
||||||
},
|
|
||||||
creator_name: {
|
|
||||||
form: false,
|
|
||||||
table: false,
|
|
||||||
search: false
|
|
||||||
},
|
|
||||||
modifier_name: {
|
|
||||||
form: false,
|
|
||||||
table: false,
|
|
||||||
search: false
|
|
||||||
},
|
|
||||||
dept_belong_id: {
|
|
||||||
form: false,
|
|
||||||
table: false,
|
|
||||||
search: false
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
form: false,
|
|
||||||
table: false,
|
|
||||||
search: false
|
|
||||||
},
|
|
||||||
}) => {
|
|
||||||
return {
|
|
||||||
dept_belong_id: {
|
|
||||||
title: '所属部门',
|
|
||||||
type: 'dict-tree',
|
|
||||||
search: {
|
|
||||||
show: options.dept_belong_id?.search || false
|
|
||||||
},
|
|
||||||
dict: dict({
|
|
||||||
url: '/api/system/dept/all_dept/',
|
|
||||||
isTree: true,
|
|
||||||
value: 'id',
|
|
||||||
label: 'name',
|
|
||||||
children: 'children',
|
|
||||||
}),
|
|
||||||
column: {
|
|
||||||
align: 'center',
|
|
||||||
width: 300,
|
|
||||||
show: options.dept_belong_id?.table || false,
|
|
||||||
component: {
|
|
||||||
name: shallowRef(deptFormat),
|
|
||||||
vModel: "modelValue",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
show: options.dept_belong_id?.form || false,
|
|
||||||
component: {
|
|
||||||
multiple: false,
|
|
||||||
clearable: true,
|
|
||||||
props: {
|
|
||||||
checkStrictly: true,
|
|
||||||
props: {
|
|
||||||
// 为什么这里要写两层props
|
|
||||||
// 因为props属性名与fs的动态渲染的props命名冲突,所以要多写一层
|
|
||||||
label: "name",
|
|
||||||
value: "id",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
helper: "默认不填则为当前创建用户的部门ID"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
title: '备注',
|
|
||||||
search: {
|
|
||||||
show: options.description?.search || false
|
|
||||||
},
|
|
||||||
type: 'textarea',
|
|
||||||
column: {
|
|
||||||
width: 100,
|
|
||||||
show: options.description?.table || false,
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
show: options.description?.form || false,
|
|
||||||
component: {
|
|
||||||
placeholder: '请输入内容',
|
|
||||||
showWordLimit: true,
|
|
||||||
maxlength: '200',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
viewForm: {
|
|
||||||
show: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier_name: {
|
|
||||||
title: '修改人',
|
|
||||||
search: {
|
|
||||||
show: options.modifier_name?.search || false
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
width: 100,
|
|
||||||
show: options.modifier_name?.table || false,
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
viewForm: {
|
|
||||||
show: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
creator_name: {
|
|
||||||
title: '创建人',
|
|
||||||
search: {
|
|
||||||
show: options.creator_name?.search || false
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
width: 100,
|
|
||||||
show: options.creator_name?.table || false,
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
viewForm: {
|
|
||||||
show: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update_datetime: {
|
|
||||||
title: '更新时间',
|
|
||||||
type: 'datetime',
|
|
||||||
search: {
|
|
||||||
show: options.update_datetime?.search || false,
|
|
||||||
col: {span: 8},
|
|
||||||
component: {
|
|
||||||
type: 'datetimerange',
|
|
||||||
props: {
|
|
||||||
'start-placeholder': '开始时间',
|
|
||||||
'end-placeholder': '结束时间',
|
|
||||||
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
|
||||||
'picker-options': {
|
|
||||||
shortcuts: [{
|
|
||||||
text: '最近一周',
|
|
||||||
onClick(picker) {
|
|
||||||
const end = new Date();
|
|
||||||
const start = new Date();
|
|
||||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
|
||||||
picker.$emit('pick', [start, end]);
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
text: '最近一个月',
|
|
||||||
onClick(picker) {
|
|
||||||
const end = new Date();
|
|
||||||
const start = new Date();
|
|
||||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
|
||||||
picker.$emit('pick', [start, end]);
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
text: '最近三个月',
|
|
||||||
onClick(picker) {
|
|
||||||
const end = new Date();
|
|
||||||
const start = new Date();
|
|
||||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
|
||||||
picker.$emit('pick', [start, end]);
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
valueResolve(context: any) {
|
|
||||||
const {key, value} = context
|
|
||||||
//value解析,就是把组件的值转化为后台所需要的值
|
|
||||||
//在form表单点击保存按钮后,提交到后台之前执行转化
|
|
||||||
if (value) {
|
|
||||||
context.form.update_datetime_after = value[0]
|
|
||||||
context.form.update_datetime_before = value[1]
|
|
||||||
}
|
|
||||||
// ↑↑↑↑↑ 注意这里是form,不是row
|
|
||||||
}
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
width: 160,
|
|
||||||
show: options.update_datetime?.table || false,
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
viewForm: {
|
|
||||||
show: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
create_datetime: {
|
|
||||||
title: '创建时间',
|
|
||||||
type: 'datetime',
|
|
||||||
search: {
|
|
||||||
show: options.create_datetime?.search || false,
|
|
||||||
col: {span: 8},
|
|
||||||
component: {
|
|
||||||
type: 'datetimerange',
|
|
||||||
props: {
|
|
||||||
'start-placeholder': '开始时间',
|
|
||||||
'end-placeholder': '结束时间',
|
|
||||||
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
|
||||||
'picker-options': {
|
|
||||||
shortcuts: [{
|
|
||||||
text: '最近一周',
|
|
||||||
onClick(picker) {
|
|
||||||
const end = new Date();
|
|
||||||
const start = new Date();
|
|
||||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
|
||||||
picker.$emit('pick', [start, end]);
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
text: '最近一个月',
|
|
||||||
onClick(picker) {
|
|
||||||
const end = new Date();
|
|
||||||
const start = new Date();
|
|
||||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
|
||||||
picker.$emit('pick', [start, end]);
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
text: '最近三个月',
|
|
||||||
onClick(picker) {
|
|
||||||
const end = new Date();
|
|
||||||
const start = new Date();
|
|
||||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
|
||||||
picker.$emit('pick', [start, end]);
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
valueResolve(context: any) {
|
|
||||||
const {key, value} = context
|
|
||||||
//value解析,就是把组件的值转化为后台所需要的值
|
|
||||||
//在form表单点击保存按钮后,提交到后台之前执行转化
|
|
||||||
if (value) {
|
|
||||||
context.form.create_datetime_after = value[0]
|
|
||||||
context.form.create_datetime_before = value[1]
|
|
||||||
}
|
|
||||||
// ↑↑↑↑↑ 注意这里是form,不是row
|
|
||||||
}
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
width: 160,
|
|
||||||
show: options.create_datetime?.table || false,
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
viewForm: {
|
|
||||||
show: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 2. 总配置接口 */
|
||||||
|
export interface CrudOptions {
|
||||||
|
create_datetime?: CrudFieldOption;
|
||||||
|
update_datetime?: CrudFieldOption;
|
||||||
|
creator_name?: CrudFieldOption;
|
||||||
|
modifier_name?: CrudFieldOption;
|
||||||
|
dept_belong_id?: CrudFieldOption;
|
||||||
|
description?: CrudFieldOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 3. 默认完整配置 */
|
||||||
|
const defaultOptions: Required<CrudOptions> = {
|
||||||
|
create_datetime: { form: false, table: false, search: false, width: 160 },
|
||||||
|
update_datetime: { form: false, table: false, search: false, width: 160 },
|
||||||
|
creator_name: { form: false, table: false, search: false, width: 100 },
|
||||||
|
modifier_name: { form: false, table: false, search: false, width: 100 },
|
||||||
|
dept_belong_id: { form: false, table: false, search: false, width: 300 },
|
||||||
|
description: { form: false, table: false, search: false, width: 100 },
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 4. mergeOptions 函数 */
|
||||||
|
function mergeOptions(baseOptions: Required<CrudOptions>, userOptions: CrudOptions = {}): Required<CrudOptions> {
|
||||||
|
const result = { ...baseOptions };
|
||||||
|
for (const key in userOptions) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(userOptions, key)) {
|
||||||
|
const baseField = result[key as keyof CrudOptions];
|
||||||
|
const userField = userOptions[key as keyof CrudOptions];
|
||||||
|
if (baseField && userField) {
|
||||||
|
result[key as keyof CrudOptions] = { ...baseField, ...userField };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最终暴露的 commonCrudConfig
|
||||||
|
* @param options 用户自定义配置(可传可不传,不传就用默认)
|
||||||
|
*/
|
||||||
|
export const commonCrudConfig = (options: CrudOptions = {}) => {
|
||||||
|
// ① 合并
|
||||||
|
const merged = mergeOptions(defaultOptions, options);
|
||||||
|
|
||||||
|
// ② 用 merged 中的值生成真正的 CRUD 配置
|
||||||
|
return {
|
||||||
|
dept_belong_id: {
|
||||||
|
title: '所属部门',
|
||||||
|
type: 'dict-tree',
|
||||||
|
search: {
|
||||||
|
show: merged.dept_belong_id.search,
|
||||||
|
},
|
||||||
|
dict: dict({
|
||||||
|
url: '/api/system/dept/all_dept/',
|
||||||
|
isTree: true,
|
||||||
|
value: 'id',
|
||||||
|
label: 'name',
|
||||||
|
children: 'children',
|
||||||
|
}),
|
||||||
|
column: {
|
||||||
|
align: 'center',
|
||||||
|
width: merged.dept_belong_id.width,
|
||||||
|
show: merged.dept_belong_id.table,
|
||||||
|
component: {
|
||||||
|
// fast-crud里自定义组件常用"component.is"
|
||||||
|
is: shallowRef(deptFormat),
|
||||||
|
vModel: 'modelValue',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: merged.dept_belong_id.form,
|
||||||
|
component: {
|
||||||
|
multiple: false,
|
||||||
|
clearable: true,
|
||||||
|
props: {
|
||||||
|
checkStrictly: true,
|
||||||
|
props: {
|
||||||
|
label: 'name',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
helper: '默认不填则为当前创建用户的部门ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
title: '备注',
|
||||||
|
search: {
|
||||||
|
show: merged.description.search,
|
||||||
|
},
|
||||||
|
type: 'textarea',
|
||||||
|
column: {
|
||||||
|
width: merged.description.width,
|
||||||
|
show: merged.description.table,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: merged.description.form,
|
||||||
|
component: {
|
||||||
|
placeholder: '请输入内容',
|
||||||
|
showWordLimit: true,
|
||||||
|
maxlength: '200',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
viewForm: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
modifier_name: {
|
||||||
|
title: '修改人',
|
||||||
|
search: {
|
||||||
|
show: merged.modifier_name.search,
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: merged.modifier_name.width,
|
||||||
|
show: merged.modifier_name.table,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: merged.modifier_name.form,
|
||||||
|
},
|
||||||
|
viewForm: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
creator_name: {
|
||||||
|
title: '创建人',
|
||||||
|
search: {
|
||||||
|
show: merged.creator_name.search,
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: merged.creator_name.width,
|
||||||
|
show: merged.creator_name.table,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: merged.creator_name.form,
|
||||||
|
},
|
||||||
|
viewForm: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
update_datetime: {
|
||||||
|
title: '更新时间',
|
||||||
|
type: 'datetime',
|
||||||
|
search: {
|
||||||
|
show: merged.update_datetime.search,
|
||||||
|
col: { span: 8 },
|
||||||
|
component: {
|
||||||
|
type: 'datetimerange',
|
||||||
|
props: {
|
||||||
|
'start-placeholder': '开始时间',
|
||||||
|
'end-placeholder': '结束时间',
|
||||||
|
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
'picker-options': {
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
text: '最近一周',
|
||||||
|
onClick(picker: any) {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
||||||
|
picker.$emit('pick', [start, end]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '最近一个月',
|
||||||
|
onClick(picker: any) {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||||||
|
picker.$emit('pick', [start, end]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '最近三个月',
|
||||||
|
onClick(picker: any) {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
||||||
|
picker.$emit('pick', [start, end]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
valueResolve(context: any) {
|
||||||
|
const { value } = context;
|
||||||
|
if (value) {
|
||||||
|
context.form.update_datetime_after = value[0];
|
||||||
|
context.form.update_datetime_before = value[1];
|
||||||
|
delete context.form.update_datetime;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: merged.update_datetime.width,
|
||||||
|
show: merged.update_datetime.table,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: merged.update_datetime.form,
|
||||||
|
},
|
||||||
|
viewForm: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
create_datetime: {
|
||||||
|
title: '创建时间',
|
||||||
|
type: 'datetime',
|
||||||
|
search: {
|
||||||
|
show: merged.create_datetime.search,
|
||||||
|
col: { span: 8 },
|
||||||
|
component: {
|
||||||
|
type: 'datetimerange',
|
||||||
|
props: {
|
||||||
|
'start-placeholder': '开始时间',
|
||||||
|
'end-placeholder': '结束时间',
|
||||||
|
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
'picker-options': {
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
text: '最近一周',
|
||||||
|
onClick(picker: any) {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
||||||
|
picker.$emit('pick', [start, end]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '最近一个月',
|
||||||
|
onClick(picker: any) {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||||||
|
picker.$emit('pick', [start, end]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '最近三个月',
|
||||||
|
onClick(picker: any) {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
||||||
|
picker.$emit('pick', [start, end]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
valueResolve(context: any) {
|
||||||
|
const { value } = context;
|
||||||
|
if (value) {
|
||||||
|
context.form.create_datetime_after = value[0];
|
||||||
|
context.form.create_datetime_before = value[1];
|
||||||
|
delete context.form.create_datetime;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: merged.create_datetime.width,
|
||||||
|
show: merged.create_datetime.table,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: merged.create_datetime.form,
|
||||||
|
},
|
||||||
|
viewForm: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,55 +1,58 @@
|
|||||||
import axios from "axios";
|
import axios from 'axios';
|
||||||
import * as process from "process";
|
import * as process from 'process';
|
||||||
import {Local, Session} from '/@/utils/storage';
|
import { Local, Session } from '/@/utils/storage';
|
||||||
import {ElNotification} from "element-plus";
|
import { ElNotification } from 'element-plus';
|
||||||
import fs from "fs";
|
import fs from 'fs';
|
||||||
|
|
||||||
// 是否显示升级提示信息框
|
// 是否显示升级提示信息框
|
||||||
const IS_SHOW_UPGRADE_SESSION_KEY = 'isShowUpgrade';
|
const IS_SHOW_UPGRADE_SESSION_KEY = 'isShowUpgrade';
|
||||||
const VERSION_KEY = 'DVADMIN3_VERSION'
|
const VERSION_KEY = 'DVADMIN3_VERSION';
|
||||||
const VERSION_FILE_NAME = 'version-build'
|
const VERSION_FILE_NAME = 'version-build';
|
||||||
|
|
||||||
export function showUpgrade () {
|
const META_ENV = import.meta.env;
|
||||||
const isShowUpgrade = Session.get(IS_SHOW_UPGRADE_SESSION_KEY) ?? false
|
|
||||||
if (isShowUpgrade) {
|
export function showUpgrade() {
|
||||||
Session.remove(IS_SHOW_UPGRADE_SESSION_KEY)
|
const isShowUpgrade = Session.get(IS_SHOW_UPGRADE_SESSION_KEY) ?? false;
|
||||||
ElNotification({
|
if (isShowUpgrade) {
|
||||||
title: '新版本升级',
|
Session.remove(IS_SHOW_UPGRADE_SESSION_KEY);
|
||||||
message: "检测到系统新版本,正在更新中!不用担心,更新很快的哦!",
|
ElNotification({
|
||||||
type: 'success',
|
title: '新版本升级',
|
||||||
duration: 5000,
|
message: '检测到系统新版本,正在更新中!不用担心,更新很快的哦!',
|
||||||
});
|
type: 'success',
|
||||||
}
|
duration: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生产环境前端版本校验,
|
// 生产环境前端版本校验,
|
||||||
export async function checkVersion(){
|
export async function checkVersion() {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (META_ENV.MODE === 'development') {
|
||||||
// 开发环境无需校验前端版本
|
// 开发环境无需校验前端版本
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
// 获取线上版本号 t为时间戳,防止缓存
|
// 获取线上版本号 t为时间戳,防止缓存
|
||||||
await axios.get(`${import.meta.env.VITE_PUBLIC_PATH}${VERSION_FILE_NAME}?t=${new Date().getTime()}`).then(res => {
|
await axios.get(`${META_ENV.VITE_PUBLIC_PATH}${VERSION_FILE_NAME}?t=${new Date().getTime()}`).then((res) => {
|
||||||
const {status, data} = res || {}
|
const { status, data } = res || {};
|
||||||
if (status === 200) {
|
if (status === 200) {
|
||||||
// 获取当前版本号
|
// 获取当前版本号
|
||||||
const localVersion = Local.get(VERSION_KEY)
|
const localVersion = Local.get(VERSION_KEY);
|
||||||
// 将当前版本号持久缓存至本地
|
// 将当前版本号持久缓存至本地
|
||||||
Local.set(VERSION_KEY, data)
|
Local.set(VERSION_KEY, data);
|
||||||
// 当用户本地存在版本号并且和线上版本号不一致时,进行页面刷新操作
|
// 当用户本地存在版本号并且和线上版本号不一致时,进行页面刷新操作
|
||||||
if (localVersion && localVersion !== data) {
|
if (localVersion && localVersion !== data) {
|
||||||
// 本地缓存版本号和线上版本号不一致,弹出升级提示框
|
// 本地缓存版本号和线上版本号不一致,弹出升级提示框
|
||||||
// 此处无法直接使用消息框进行提醒,因为 window.location.reload()会导致消息框消失,将在loading页面判断是否需要显示升级提示框
|
// 此处无法直接使用消息框进行提醒,因为 window.location.reload()会导致消息框消失,将在loading页面判断是否需要显示升级提示框
|
||||||
Session.set(IS_SHOW_UPGRADE_SESSION_KEY, true)
|
Session.set(IS_SHOW_UPGRADE_SESSION_KEY, true);
|
||||||
window.location.reload()
|
window.location.reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateVersionFile (){
|
export function generateVersionFile() {
|
||||||
// 生成版本文件到public目录下version文件中
|
// 生成版本文件到public目录下version文件中
|
||||||
const version = `${process.env.npm_package_version}.${new Date().getTime()}`;
|
const package_version = META_ENV?.npm_package_version ?? process.env?.npm_package_version;
|
||||||
fs.writeFileSync(`public/${VERSION_FILE_NAME}`, version);
|
|
||||||
|
const version = `${package_version}.${new Date().getTime()}`;
|
||||||
|
fs.writeFileSync(`public/${VERSION_FILE_NAME}`, version);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { defineAsyncComponent, AsyncComponentLoader } from 'vue';
|
|||||||
export let pluginsAll: any = [];
|
export let pluginsAll: any = [];
|
||||||
// 扫描插件目录并注册插件
|
// 扫描插件目录并注册插件
|
||||||
export const scanAndInstallPlugins = (app: any) => {
|
export const scanAndInstallPlugins = (app: any) => {
|
||||||
const components = import.meta.glob('./**/*.vue');
|
const components = import.meta.glob('./**/*.ts');
|
||||||
const pluginNames = new Set();
|
const pluginNames = new Set();
|
||||||
// 遍历对象并注册异步组件
|
// 遍历对象并注册异步组件
|
||||||
for (const [key, value] of Object.entries(components)) {
|
for (const [key, value] of Object.entries(components)) {
|
||||||
@@ -11,6 +11,15 @@ export const scanAndInstallPlugins = (app: any) => {
|
|||||||
const pluginsName = key.match(/\/([^\/]*)\//)?.[1];
|
const pluginsName = key.match(/\/([^\/]*)\//)?.[1];
|
||||||
pluginNames.add(pluginsName);
|
pluginNames.add(pluginsName);
|
||||||
}
|
}
|
||||||
|
const dreamComponents = import.meta.glob('/node_modules/@great-dream/**/*.ts');
|
||||||
|
// 遍历对象并注册异步组件
|
||||||
|
for (let [key, value] of Object.entries(dreamComponents)) {
|
||||||
|
key = key.replace('node_modules/@great-dream/', '');
|
||||||
|
const name = key.slice(key.lastIndexOf('/') + 1, key.lastIndexOf('.'));
|
||||||
|
app.component(name, defineAsyncComponent(value as AsyncComponentLoader));
|
||||||
|
const pluginsName = key.match(/\/([^\/]*)\//)?.[1];
|
||||||
|
pluginNames.add(pluginsName);
|
||||||
|
}
|
||||||
pluginsAll = Array.from(pluginNames);
|
pluginsAll = Array.from(pluginNames);
|
||||||
console.log('已发现插件:', pluginsAll);
|
console.log('已发现插件:', pluginsAll);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,253 +1,254 @@
|
|||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
import {
|
import {
|
||||||
UserPageQuery,
|
UserPageQuery,
|
||||||
AddReq,
|
AddReq,
|
||||||
DelReq,
|
DelReq,
|
||||||
EditReq,
|
EditReq,
|
||||||
CrudExpose,
|
CrudExpose,
|
||||||
CrudOptions,
|
CrudOptions,
|
||||||
CreateCrudOptionsProps,
|
CreateCrudOptionsProps,
|
||||||
CreateCrudOptionsRet,
|
CreateCrudOptionsRet,
|
||||||
dict
|
dict
|
||||||
} from '@fast-crud/fast-crud';
|
} from '@fast-crud/fast-crud';
|
||||||
import fileSelector from '/@/components/fileSelector/index.vue';
|
import fileSelector from '/@/components/fileSelector/index.vue';
|
||||||
import { shallowRef } from 'vue';
|
|
||||||
|
|
||||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export const createCrudOptions = function ({ crudExpose, context }: 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);
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
actionbar: {
|
actionbar: {
|
||||||
buttons: {
|
buttons: {
|
||||||
add: {
|
add: {
|
||||||
show: true,
|
show: true,
|
||||||
click: () => context.openAddHandle?.()
|
click: () => context.openAddHandle?.()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
pageRequest,
|
pageRequest,
|
||||||
addRequest,
|
addRequest,
|
||||||
editRequest,
|
editRequest,
|
||||||
delRequest,
|
delRequest,
|
||||||
},
|
},
|
||||||
tabs: {
|
tabs: {
|
||||||
show: true,
|
show: true,
|
||||||
name: 'file_type',
|
name: 'file_type',
|
||||||
type: '',
|
type: '',
|
||||||
options: [
|
options: [
|
||||||
{ value: 0, label: '图片' },
|
{ value: 0, label: '图片' },
|
||||||
{ value: 1, label: '视频' },
|
{ value: 1, label: '视频' },
|
||||||
{ value: 2, label: '音频' },
|
{ value: 2, label: '音频' },
|
||||||
{ value: 3, label: '其他' },
|
{ value: 3, label: '其他' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
//固定右侧
|
//固定右侧
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 200,
|
width: 200,
|
||||||
show: false,
|
show: false,
|
||||||
buttons: {
|
buttons: {
|
||||||
view: {
|
view: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
iconRight: 'Edit',
|
iconRight: 'Edit',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
iconRight: 'Delete',
|
iconRight: 'Delete',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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, //禁止在列设置中选择
|
||||||
formatter: (context) => {
|
formatter: (context) => {
|
||||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||||
let index = context.index ?? 1;
|
let index = context.index ?? 1;
|
||||||
let pagination = crudExpose!.crudBinding.value.pagination;
|
let pagination = crudExpose!.crudBinding.value.pagination;
|
||||||
return ((pagination!.currentPage ?? 1) - 1) * pagination!.pageSize + index + 1;
|
return ((pagination!.currentPage ?? 1) - 1) * pagination!.pageSize + index + 1;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
title: '关键词',
|
title: '关键词',
|
||||||
column: {
|
column: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
component: {
|
component: {
|
||||||
props: {
|
props: {
|
||||||
clearable: true,
|
clearable: true,
|
||||||
},
|
},
|
||||||
placeholder: '请输入关键词',
|
placeholder: '请输入关键词',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
show: false,
|
show: false,
|
||||||
component: {
|
component: {
|
||||||
props: {
|
props: {
|
||||||
clearable: true,
|
clearable: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
title: '文件名称',
|
title: '文件名称',
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
type: 'input',
|
type: 'input',
|
||||||
column: {
|
column: {
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
component: {
|
component: {
|
||||||
placeholder: '请输入文件名称',
|
placeholder: '请输入文件名称',
|
||||||
clearable: true
|
clearable: true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
title: '预览',
|
title: '预览',
|
||||||
column: {
|
column: {
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
show: false
|
show: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url: {
|
url: {
|
||||||
title: '文件地址',
|
title: '文件地址',
|
||||||
type: 'file-uploader',
|
type: 'file-uploader',
|
||||||
search: {
|
search: {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
minWidth: 360,
|
minWidth: 360,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
md5sum: {
|
md5sum: {
|
||||||
title: '文件MD5',
|
title: '文件MD5',
|
||||||
search: {
|
search: {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mime_type: {
|
mime_type: {
|
||||||
title: '文件类型',
|
title: '文件类型',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
form: {
|
form: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
minWidth: 160
|
minWidth: 160
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
file_type: {
|
file_type: {
|
||||||
title: '文件类型',
|
title: '文件类型',
|
||||||
type: 'dict-select',
|
type: 'dict-select',
|
||||||
dict: dict({
|
dict: dict({
|
||||||
data: [
|
data: [
|
||||||
{ label: '图片', value: 0, color: 'success' },
|
{ label: '图片', value: 0, color: 'success' },
|
||||||
{ label: '视频', value: 1, color: 'warning' },
|
{ label: '视频', value: 1, color: 'warning' },
|
||||||
{ label: '音频', value: 2, color: 'danger' },
|
{ label: '音频', value: 2, color: 'danger' },
|
||||||
{ label: '其他', value: 3, color: 'primary' },
|
{ label: '其他', value: 3, color: 'primary' },
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
column: {
|
column: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
show: false,
|
show: false,
|
||||||
component: {
|
component: {
|
||||||
placeholder: '请选择文件类型'
|
placeholder: '请选择文件类型'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
title: '文件大小',
|
title: '文件大小',
|
||||||
column: {
|
column: {
|
||||||
minWidth: 120
|
minWidth: 120
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
show: false
|
show: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
upload_method: {
|
upload_method: {
|
||||||
title: '上传方式',
|
title: '上传方式',
|
||||||
type: 'dict-select',
|
type: 'dict-select',
|
||||||
dict: dict({
|
dict: dict({
|
||||||
data: [
|
data: [
|
||||||
{ label: '默认上传', value: 0, color: 'primary' },
|
{ label: '默认上传', value: 0, color: 'primary' },
|
||||||
{ label: '文件选择器上传', value: 1, color: 'warning' },
|
{ label: '文件选择器上传', value: 1, color: 'warning' },
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
column: {
|
column: {
|
||||||
minWidth: 140
|
minWidth: 140
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
show: true
|
show: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
create_datetime: {
|
create_datetime: {
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
column: {
|
column: {
|
||||||
minWidth: 160
|
minWidth: 160
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
show: false
|
show: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// fileselectortest: {
|
fileselectortest: {
|
||||||
// title: '文件选择器测试',
|
title: '文件选择器测试',
|
||||||
// type: 'file-selector',
|
type: 'file-selector',
|
||||||
// width: 200,
|
column: {
|
||||||
// form: {
|
minWidth: 200
|
||||||
// component: {
|
},
|
||||||
// name: shallowRef(fileSelector),
|
form: {
|
||||||
// vModel: 'modelValue',
|
component: {
|
||||||
// tabsShow: 0b0100,
|
name: fileSelector,
|
||||||
// itemSize: 100,
|
vModel: 'modelValue',
|
||||||
// multiple: false,
|
tabsShow: 0b1111,
|
||||||
// selectable: true,
|
itemSize: 100,
|
||||||
// showInput: true,
|
multiple: true,
|
||||||
// inputType: 'video',
|
selectable: true,
|
||||||
// valueKey: 'url',
|
showInput: true,
|
||||||
// }
|
inputType: 'image',
|
||||||
// }
|
valueKey: 'url',
|
||||||
// }
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
|
|
||||||
<el-form-item v-if="!menuFormData.is_catalog && menuFormData.is_link" label="外链接" prop="link_url">
|
<el-form-item v-if="!menuFormData.is_catalog && menuFormData.is_link" label="外链接" prop="link_url">
|
||||||
<el-input v-model="menuFormData.link_url" placeholder="请输入外链接地址" />
|
<el-input v-model="menuFormData.link_url" placeholder="请输入外链接地址" />
|
||||||
|
<el-alert :title="`输入{{token}}可自动替换系统 token `" type="info" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item v-if="!menuFormData.is_catalog" label="缓存">
|
<el-form-item v-if="!menuFormData.is_catalog" label="缓存">
|
||||||
|
|||||||
@@ -1,9 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="pccm-item" v-if="RoleMenuBtn.$state.length > 0">
|
<div class="pccm-item" v-if="RoleMenuBtn.$state.length > 0">
|
||||||
<div class="menu-form-alert">配置操作功能接口权限,配置数据权限点击小齿轮</div>
|
<div class="menu-form-alert">
|
||||||
|
<div style="display:flex; align-items: center; white-space: nowrap; margin-bottom: 10px;">
|
||||||
|
<span>默认接口权限:</span>
|
||||||
|
<el-select
|
||||||
|
v-model="default_selectBtn.data_range"
|
||||||
|
@change="defaulthandlePermissionRangeChange"
|
||||||
|
placeholder="请选择"
|
||||||
|
style="margin-left: 5px; width: 250px; min-width: 250px;"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in dataPermissionRange" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-tree-select
|
||||||
|
v-show="default_selectBtn.data_range === 4"
|
||||||
|
node-key="id"
|
||||||
|
v-model="default_selectBtn.dept"
|
||||||
|
:props="defaultTreeProps"
|
||||||
|
:data="deptData"
|
||||||
|
@change="customhandlePermissionRangeChange(default_selectBtn.dept)"
|
||||||
|
placeholder="请选择自定义部门"
|
||||||
|
multiple
|
||||||
|
check-strictly
|
||||||
|
:render-after-expand="false"
|
||||||
|
show-checkbox
|
||||||
|
class="dialog-tree"
|
||||||
|
style="margin-left: 15px; width: AUTO; min-width: 250px; margin-top: 0;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<span>配置操作功能接口权限,配置数据权限点击小齿轮</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-checkbox v-for="btn in RoleMenuBtn.$state" :key="btn.id" v-model="btn.isCheck" @change="handleCheckChange(btn)">
|
<el-checkbox v-for="btn in RoleMenuBtn.$state" :key="btn.id" v-model="btn.isCheck" @change="handleCheckChange(btn)">
|
||||||
<div class="btn-item">
|
<div class="btn-item">
|
||||||
{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
|
{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range, btn.dept)})` : btn.name }}
|
||||||
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(btn)">
|
<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(btn)">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Setting />
|
<Setting />
|
||||||
@@ -48,10 +78,26 @@ import { RoleMenuBtnType } from '../types';
|
|||||||
import { getRoleToDeptAll, setRoleMenuBtn, setRoleMenuBtnDataRange } from './api';
|
import { getRoleToDeptAll, setRoleMenuBtn, setRoleMenuBtnDataRange } from './api';
|
||||||
import XEUtils from 'xe-utils';
|
import XEUtils from 'xe-utils';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { Local } from '/@/utils/storage';
|
||||||
|
|
||||||
const RoleDrawer = RoleDrawerStores(); // 角色-菜单
|
const RoleDrawer = RoleDrawerStores(); // 角色-菜单
|
||||||
const RoleMenuTree = RoleMenuTreeStores(); // 角色-菜单
|
const RoleMenuTree = RoleMenuTreeStores(); // 角色-菜单
|
||||||
const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单-按钮
|
const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单-按钮
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
|
|
||||||
|
// 默认按钮
|
||||||
|
const default_selectBtn = ref<RoleMenuBtnType>({
|
||||||
|
id: 0,
|
||||||
|
menu_btn_pre_id: 0,
|
||||||
|
/** 是否选中 */
|
||||||
|
isCheck: false,
|
||||||
|
/** 按钮名称 */
|
||||||
|
name: '',
|
||||||
|
/** 数据权限范围 */
|
||||||
|
data_range: Local.get('role_default_data_range'),
|
||||||
|
dept: Local.get('role_default_custom_dept'),
|
||||||
|
});
|
||||||
|
|
||||||
// 选中的按钮
|
// 选中的按钮
|
||||||
const selectBtn = ref<RoleMenuBtnType>({
|
const selectBtn = ref<RoleMenuBtnType>({
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -83,6 +129,29 @@ const defaultTreeProps = {
|
|||||||
value: 'id',
|
value: 'id',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认数据权限下拉选择事件
|
||||||
|
* 保留数据到cache
|
||||||
|
*/
|
||||||
|
const defaulthandlePermissionRangeChange = async (val: number) => {
|
||||||
|
if (val < 4) {
|
||||||
|
// default_selectBtn.value.dept = [];
|
||||||
|
// Local.set('role_default_custom_dept', []);
|
||||||
|
}
|
||||||
|
default_selectBtn.value.data_range = val;
|
||||||
|
Local.set('role_default_data_range', val);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认部门下拉选择事件
|
||||||
|
* 保留数据到cache
|
||||||
|
*/
|
||||||
|
const customhandlePermissionRangeChange = async (dept: Array<number>) => {
|
||||||
|
default_selectBtn.value.dept = dept;
|
||||||
|
Local.set('role_default_custom_dept', dept);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定数据权限下拉选择事件
|
* 自定数据权限下拉选择事件
|
||||||
*/
|
*/
|
||||||
@@ -95,12 +164,21 @@ const handlePermissionRangeChange = async (val: number) => {
|
|||||||
* 格式化按钮数据范围
|
* 格式化按钮数据范围
|
||||||
*/
|
*/
|
||||||
const formatDataRange = computed(() => {
|
const formatDataRange = computed(() => {
|
||||||
return function (datarange: number) {
|
return function (datarange: number, dept: Array<number>) {
|
||||||
const datarangeitem = XEUtils.find(dataPermissionRange.value, (item: any) => {
|
const datarangeitem = XEUtils.find(dataPermissionRange.value, (item: any) => {
|
||||||
if (item.value === datarange) {
|
if (item.value === datarange) {
|
||||||
return item.label;
|
return item.label;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 数据权限与默认数据权限一致
|
||||||
|
if (datarange === default_selectBtn.value.data_range) {
|
||||||
|
// 判断选择的部门是否一致
|
||||||
|
if (datarange !== 4 || JSON.stringify(dept) === JSON.stringify(default_selectBtn.value.dept)) {
|
||||||
|
|
||||||
|
return "默认接口权限"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// datarange === 4 选择的部门不一致返回datarangeitem.label
|
||||||
return datarangeitem.label;
|
return datarangeitem.label;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -108,11 +186,14 @@ const formatDataRange = computed(() => {
|
|||||||
* 勾选按钮
|
* 勾选按钮
|
||||||
*/
|
*/
|
||||||
const handleCheckChange = async (btn: RoleMenuBtnType) => {
|
const handleCheckChange = async (btn: RoleMenuBtnType) => {
|
||||||
|
selectBtn.value = default_selectBtn.value;
|
||||||
const put_data = {
|
const put_data = {
|
||||||
isCheck: btn.isCheck,
|
isCheck: btn.isCheck,
|
||||||
roleId: RoleDrawer.roleId,
|
roleId: RoleDrawer.roleId,
|
||||||
menuId: RoleMenuTree.id,
|
menuId: RoleMenuTree.id,
|
||||||
btnId: btn.id,
|
btnId: btn.id,
|
||||||
|
data_range: default_selectBtn.value.data_range,
|
||||||
|
dept: default_selectBtn.value.dept,
|
||||||
};
|
};
|
||||||
const { data, msg } = await setRoleMenuBtn(put_data);
|
const { data, msg } = await setRoleMenuBtn(put_data);
|
||||||
RoleMenuBtn.updateState(data);
|
RoleMenuBtn.updateState(data);
|
||||||
@@ -168,9 +249,10 @@ onMounted(async () => {
|
|||||||
background-color: var(--el-color-primary);
|
background-color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// .el-checkbox {
|
|
||||||
// width: 200px;
|
.el-checkbox {
|
||||||
// }
|
width: 20%;
|
||||||
|
}
|
||||||
.btn-item {
|
.btn-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
30
web/src/views/system/role/components/addUsers/api.ts
Normal file
30
web/src/views/system/role/components/addUsers/api.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { request } from '/@/utils/service';
|
||||||
|
import { UserPageQuery} from '@fast-crud/fast-crud';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前角色查询未授权的用户
|
||||||
|
* @param role_id 角色id
|
||||||
|
* @param query 查询条件 需要有角色id
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getRoleUsersUnauthorized(query: UserPageQuery) {
|
||||||
|
query["authorized"] = 0; // 未授权的用户
|
||||||
|
return request({
|
||||||
|
url: '/api/system/role/get_role_users/',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 当前用户角色添加用户
|
||||||
|
* @param role_id 角色id
|
||||||
|
* @param users_id 用户id数组
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function addRoleUsers(role_id: number, users_id: Array<Number>) {
|
||||||
|
return request({
|
||||||
|
url: `/api/system/role/${role_id}/add_role_users/`,
|
||||||
|
method: 'post',
|
||||||
|
data: {users_id: users_id},
|
||||||
|
});
|
||||||
|
}
|
||||||
184
web/src/views/system/role/components/addUsers/crud.tsx
Normal file
184
web/src/views/system/role/components/addUsers/crud.tsx
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import {getRoleUsersUnauthorized} from './api';
|
||||||
|
import {
|
||||||
|
compute,
|
||||||
|
dict,
|
||||||
|
UserPageQuery,
|
||||||
|
AddReq,
|
||||||
|
DelReq,
|
||||||
|
EditReq,
|
||||||
|
CrudOptions,
|
||||||
|
CreateCrudOptionsProps,
|
||||||
|
CreateCrudOptionsRet
|
||||||
|
} from '@fast-crud/fast-crud';
|
||||||
|
|
||||||
|
import { ref , nextTick} from 'vue';
|
||||||
|
import XEUtils from 'xe-utils';
|
||||||
|
|
||||||
|
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
|
const pageRequest = async (query: UserPageQuery) => {
|
||||||
|
return await getRoleUsersUnauthorized(query);
|
||||||
|
};
|
||||||
|
const editRequest = async ({ form, row }: EditReq) => {
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
const delRequest = async ({ row }: DelReq) => {
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
const addRequest = async ({ form }: AddReq) => {
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 记录选中的行
|
||||||
|
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 {
|
||||||
|
selectedRows,
|
||||||
|
crudOptions: {
|
||||||
|
request: {
|
||||||
|
pageRequest,
|
||||||
|
addRequest,
|
||||||
|
editRequest,
|
||||||
|
delRequest,
|
||||||
|
},
|
||||||
|
actionbar: {
|
||||||
|
show: false,
|
||||||
|
buttons: {
|
||||||
|
add: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowHandle: {
|
||||||
|
show: false,
|
||||||
|
//固定右侧
|
||||||
|
fixed: 'left',
|
||||||
|
width: 150,
|
||||||
|
buttons: {
|
||||||
|
view: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
rowKey: "id",
|
||||||
|
onSelectionChange,
|
||||||
|
onRefreshed: () => toggleRowSelection(),
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
$checked: {
|
||||||
|
title: "选择",
|
||||||
|
form: { show: false},
|
||||||
|
column: {
|
||||||
|
show: true,
|
||||||
|
type: "selection",
|
||||||
|
align: "center",
|
||||||
|
width: "55px",
|
||||||
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_index: {
|
||||||
|
title: '序号',
|
||||||
|
form: { show: false },
|
||||||
|
column: {
|
||||||
|
//type: 'index',
|
||||||
|
align: 'center',
|
||||||
|
width: '70px',
|
||||||
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
|
formatter: (context) => {
|
||||||
|
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||||
|
let index = context.index ?? 1;
|
||||||
|
let pagination = crudExpose!.crudBinding.value.pagination;
|
||||||
|
// @ts-ignore
|
||||||
|
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
title: '用户名',
|
||||||
|
search: {
|
||||||
|
show: true,
|
||||||
|
component: {
|
||||||
|
props: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'text',
|
||||||
|
form: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dept: {
|
||||||
|
title: '部门',
|
||||||
|
show: true,
|
||||||
|
type: 'dict-tree',
|
||||||
|
column: {
|
||||||
|
name: 'text',
|
||||||
|
formatter({value,row,index}){
|
||||||
|
return row.dept__name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
show: true,
|
||||||
|
disabled: true,
|
||||||
|
col:{span: 6},
|
||||||
|
component: {
|
||||||
|
multiple: false,
|
||||||
|
props: {
|
||||||
|
checkStrictly: true,
|
||||||
|
clearable: true,
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
dict: dict({
|
||||||
|
isTree: true,
|
||||||
|
url: '/api/system/dept/all_dept/',
|
||||||
|
value: 'id',
|
||||||
|
label: 'name'
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
91
web/src/views/system/role/components/addUsers/index.vue
Normal file
91
web/src/views/system/role/components/addUsers/index.vue
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog v-model="dialog" title="添加授权用户" direction="rtl" destroy-on-close :before-close="handleDialogClose">
|
||||||
|
<div style="height: 500px;" >
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
|
<template #pagination-right>
|
||||||
|
<el-popover placement="top" :width="200" trigger="click">
|
||||||
|
<template #reference>
|
||||||
|
<el-button text :type="selectedRowsCount > 0 ? 'primary' : ''">已选中{{ selectedRowsCount }}条数据</el-button>
|
||||||
|
</template>
|
||||||
|
<el-table :data="selectedRows" size="small" :max-height="500">
|
||||||
|
<!-- <el-table-column width="100" property="id" label="id" /> -->
|
||||||
|
<el-table-column width="100" property="name" label="用户名" />
|
||||||
|
<el-table-column fixed="right" label="操作" min-width="50">
|
||||||
|
<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>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="handleDialogConfirm"> 确定</el-button>
|
||||||
|
<el-button @click="handleDialogClose"> 取消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useFs } from '@fast-crud/fast-crud';
|
||||||
|
import { createCrudOptions } from './crud';
|
||||||
|
import { successNotification } from '/@/utils/message';
|
||||||
|
import { addRoleUsers } from './api';
|
||||||
|
import { Close } from '@element-plus/icons-vue';
|
||||||
|
import XEUtils from 'xe-utils';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
refreshCallback: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//对话框是否显示
|
||||||
|
const dialog = ref(false);
|
||||||
|
|
||||||
|
// 父组件刷新回调函数
|
||||||
|
const parentRefreshCallbackFunc = props.refreshCallback;
|
||||||
|
|
||||||
|
//抽屉关闭确认
|
||||||
|
const handleDialogClose = () => {
|
||||||
|
dialog.value = false;
|
||||||
|
selectedRows.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogConfirm = async () => {
|
||||||
|
if (selectedRows.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await addRoleUsers(crudRef.value.getSearchFormData().role_id, XEUtils.pluck(selectedRows.value, 'id')).then(res => {
|
||||||
|
successNotification(res.msg);
|
||||||
|
})
|
||||||
|
parentRefreshCallbackFunc && parentRefreshCallbackFunc(); // 刷新父组件
|
||||||
|
handleDialogClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const { crudBinding, crudRef, crudExpose, selectedRows } = useFs({ createCrudOptions, context: {} });
|
||||||
|
const { setSearchFormData, doRefresh } = crudExpose;
|
||||||
|
|
||||||
|
// 选中行的条数
|
||||||
|
const selectedRowsCount = computed(() => {
|
||||||
|
return selectedRows.value.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ dialog, setSearchFormData, doRefresh, parentRefreshCallbackFunc});
|
||||||
|
</script>
|
||||||
44
web/src/views/system/role/components/searchUsers/api.ts
Normal file
44
web/src/views/system/role/components/searchUsers/api.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { request } from '/@/utils/service';
|
||||||
|
import { UserPageQuery} from '@fast-crud/fast-crud';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前角色查询授权的用户
|
||||||
|
* @param query 查询条件 需要有角色id
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getRoleUsersAuthorized(query: UserPageQuery) {
|
||||||
|
query["authorized"] = 1; // 授权的用户
|
||||||
|
return request({
|
||||||
|
url: '/api/system/role/get_role_users/',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 当前角色删除授权的用户
|
||||||
|
* @param role_id 角色id
|
||||||
|
* @param user_id 用户id数组
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function removeRoleUser(role_id: number, user_id: Array<number>) {
|
||||||
|
return request({
|
||||||
|
url: `/api/system/role/${role_id}/remove_role_user/`,
|
||||||
|
method: 'delete',
|
||||||
|
data: {user_id: user_id},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户角色添加用户
|
||||||
|
* @param role_id 角色id
|
||||||
|
* @param data 用户id数组
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function addRoleUsers(role_id: number, data: Array<Number>) {
|
||||||
|
return request({
|
||||||
|
url: `/api/system/role/${role_id}/add_role_users/`,
|
||||||
|
method: 'post',
|
||||||
|
data: {users_id: data},
|
||||||
|
});
|
||||||
|
}
|
||||||
193
web/src/views/system/role/components/searchUsers/crud.tsx
Normal file
193
web/src/views/system/role/components/searchUsers/crud.tsx
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import {getRoleUsersAuthorized, removeRoleUser} from './api';
|
||||||
|
import {
|
||||||
|
compute,
|
||||||
|
dict,
|
||||||
|
UserPageQuery,
|
||||||
|
AddReq,
|
||||||
|
DelReq,
|
||||||
|
EditReq,
|
||||||
|
CrudOptions,
|
||||||
|
CreateCrudOptionsProps,
|
||||||
|
CreateCrudOptionsRet
|
||||||
|
} from '@fast-crud/fast-crud';
|
||||||
|
|
||||||
|
import {auth} from "/@/utils/authFunction";
|
||||||
|
import { ref , nextTick} from 'vue';
|
||||||
|
import XEUtils from 'xe-utils';
|
||||||
|
|
||||||
|
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
|
const pageRequest = async (query: UserPageQuery) => {
|
||||||
|
return await getRoleUsersAuthorized(query);
|
||||||
|
};
|
||||||
|
const editRequest = async ({ form, row }: EditReq) => {
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
const delRequest = async ({ row }: DelReq) => {
|
||||||
|
return await removeRoleUser(crudExpose.crudRef.value.getSearchFormData().role_id, [row.id]);
|
||||||
|
};
|
||||||
|
const addRequest = async ({ form }: AddReq) => {
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 记录选中的行
|
||||||
|
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 {
|
||||||
|
selectedRows,
|
||||||
|
crudOptions: {
|
||||||
|
request: {
|
||||||
|
pageRequest,
|
||||||
|
addRequest,
|
||||||
|
editRequest,
|
||||||
|
delRequest,
|
||||||
|
},
|
||||||
|
actionbar: {
|
||||||
|
buttons: {
|
||||||
|
add: {
|
||||||
|
show: auth('role:AuthorizedAdd'),
|
||||||
|
click: (ctx: any) => {
|
||||||
|
context!.subUserRef.value.dialog = true;
|
||||||
|
nextTick(() => {
|
||||||
|
context!.subUserRef.value.setSearchFormData({ form: { role_id: crudExpose.crudRef.value.getSearchFormData().role_id } });
|
||||||
|
context!.subUserRef.value.doRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
rowHandle: {
|
||||||
|
//固定右侧
|
||||||
|
fixed: 'left',
|
||||||
|
width: 120,
|
||||||
|
show: auth('role:AuthorizedDel'),
|
||||||
|
buttons: {
|
||||||
|
view: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
iconRight: 'Delete',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
rowKey: "id",
|
||||||
|
onSelectionChange,
|
||||||
|
onRefreshed: () => toggleRowSelection(),
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
$checked: {
|
||||||
|
title: "选择",
|
||||||
|
form: { show: false},
|
||||||
|
column: {
|
||||||
|
show: auth('role:AuthorizedDel'),
|
||||||
|
type: "selection",
|
||||||
|
align: "center",
|
||||||
|
width: "55px",
|
||||||
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_index: {
|
||||||
|
title: '序号',
|
||||||
|
form: { show: false },
|
||||||
|
column: {
|
||||||
|
//type: 'index',
|
||||||
|
align: 'center',
|
||||||
|
width: '70px',
|
||||||
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
|
formatter: (context) => {
|
||||||
|
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||||
|
let index = context.index ?? 1;
|
||||||
|
let pagination = crudExpose!.crudBinding.value.pagination;
|
||||||
|
// @ts-ignore
|
||||||
|
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
title: '用户名',
|
||||||
|
search: {
|
||||||
|
show: true,
|
||||||
|
component: {
|
||||||
|
props: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'text',
|
||||||
|
form: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dept: {
|
||||||
|
title: '部门',
|
||||||
|
show: true,
|
||||||
|
type: 'dict-tree',
|
||||||
|
column: {
|
||||||
|
name: 'text',
|
||||||
|
formatter({value,row,index}){
|
||||||
|
return row.dept__name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
show: true,
|
||||||
|
disabled: true,
|
||||||
|
col:{span: 6},
|
||||||
|
component: {
|
||||||
|
multiple: false,
|
||||||
|
props: {
|
||||||
|
checkStrictly: true,
|
||||||
|
clearable: true,
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
dict: dict({
|
||||||
|
isTree: true,
|
||||||
|
url: '/api/system/dept/all_dept/',
|
||||||
|
value: 'id',
|
||||||
|
label: 'name'
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
98
web/src/views/system/role/components/searchUsers/index.vue
Normal file
98
web/src/views/system/role/components/searchUsers/index.vue
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer size="70%" v-model="RoleUserDrawer.drawerVisible" direction="rtl" destroy-on-close :before-close="handleClose">
|
||||||
|
<template #header>
|
||||||
|
<div>
|
||||||
|
当前授权角色:
|
||||||
|
<el-tag>{{ RoleUserDrawer.role_name }}</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
|
<template #pagination-right>
|
||||||
|
<el-popover placement="top" :width="200" trigger="click">
|
||||||
|
<template #reference>
|
||||||
|
<el-button text :type="selectedRowsCount > 0 ? 'primary' : ''">已选中{{ selectedRowsCount }}条数据</el-button>
|
||||||
|
</template>
|
||||||
|
<el-table :data="selectedRows" size="small" :max-height="500" >
|
||||||
|
<!-- <el-table-column width="100" property="id" label="id" /> -->
|
||||||
|
<el-table-column width="100" property="name" label="用户名" />
|
||||||
|
<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>
|
||||||
|
<template #pagination-left>
|
||||||
|
<el-tooltip content="批量删除所选择的用户权限">
|
||||||
|
<el-button v-show="selectedRowsCount > 0 && auth('role:AuthorizedDel')" type="danger" @click="multipleDel" :icon="Delete">批量删除</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</fs-crud>
|
||||||
|
<subUser ref="subUserRef" :refreshCallback="refreshData"> </subUser>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {auth} from "/@/utils/authFunction";
|
||||||
|
import { ref, onMounted, defineAsyncComponent, computed } from 'vue';
|
||||||
|
import { useFs } from '@fast-crud/fast-crud';
|
||||||
|
import { createCrudOptions } from './crud';
|
||||||
|
import { Close, Delete } from '@element-plus/icons-vue';
|
||||||
|
import XEUtils from 'xe-utils';
|
||||||
|
import {removeRoleUser} from "./api"
|
||||||
|
import { ElMessageBox } from 'element-plus';
|
||||||
|
import { errorMessage, successNotification } from '/@/utils/message';
|
||||||
|
|
||||||
|
import { RoleUserStores } from '../../stores/RoleUserStores';
|
||||||
|
const RoleUserDrawer = RoleUserStores(); // 授权用户抽屉参数
|
||||||
|
|
||||||
|
const subUser = defineAsyncComponent(() => import('../addUsers/index.vue'));
|
||||||
|
const subUserRef = ref();
|
||||||
|
|
||||||
|
const refreshData = () => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
//抽屉是否显示
|
||||||
|
const drawer = ref(false);
|
||||||
|
|
||||||
|
//抽屉关闭确认
|
||||||
|
const handleClose = (done: () => void) => {
|
||||||
|
selectedRows.value = [];
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选中行的条数
|
||||||
|
const selectedRowsCount = computed(() => {
|
||||||
|
return selectedRows.value.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
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 multipleDel = async () => {
|
||||||
|
if (selectedRows.value.length < 1) {
|
||||||
|
errorMessage("请先勾选用户");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await ElMessageBox.confirm(`确定要删除这 “${selectedRows.value.length}” 位用户的权限吗`, "确认");
|
||||||
|
const req = await removeRoleUser(crudRef.value.getSearchFormData().role_id, XEUtils.pluck(selectedRows.value, 'id'));
|
||||||
|
selectedRows.value = [];
|
||||||
|
successNotification(req.msg)
|
||||||
|
crudExpose.doRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { crudBinding, crudRef, crudExpose, selectedRows } = useFs({ createCrudOptions, context: {subUserRef} });
|
||||||
|
const { setSearchFormData, doRefresh } = crudExpose;
|
||||||
|
|
||||||
|
defineExpose({ drawer, setSearchFormData, doRefresh });
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -3,6 +3,7 @@ import * as api from './api';
|
|||||||
import { dictionary } from '/@/utils/dictionary';
|
import { dictionary } from '/@/utils/dictionary';
|
||||||
import { successMessage } from '../../../utils/message';
|
import { successMessage } from '../../../utils/message';
|
||||||
import { auth } from '/@/utils/authFunction';
|
import { auth } from '/@/utils/authFunction';
|
||||||
|
import { nextTick, computed } from 'vue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -46,7 +47,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
rowHandle: {
|
rowHandle: {
|
||||||
//固定右侧
|
//固定右侧
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 320,
|
width: computed(() => {
|
||||||
|
if (auth('role:AuthorizedAdd') || auth('role:AuthorizedSearch')){
|
||||||
|
return 420;
|
||||||
|
}
|
||||||
|
return 320;
|
||||||
|
}),
|
||||||
buttons: {
|
buttons: {
|
||||||
view: {
|
view: {
|
||||||
show: true,
|
show: true,
|
||||||
@@ -57,6 +63,19 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
|||||||
remove: {
|
remove: {
|
||||||
show: auth('role:Delete'),
|
show: auth('role:Delete'),
|
||||||
},
|
},
|
||||||
|
assignment: {
|
||||||
|
type: 'primary',
|
||||||
|
text: '授权用户',
|
||||||
|
show: auth('role:AuthorizedAdd') || auth('role:AuthorizedSearch'),
|
||||||
|
click: (ctx: any) => {
|
||||||
|
const { row } = ctx;
|
||||||
|
context!.RoleUserDrawer.handleDrawerOpen(row);
|
||||||
|
nextTick(() => {
|
||||||
|
context!.RoleUserRef.value.setSearchFormData({ form: { role_id: row.id } });
|
||||||
|
context!.RoleUserRef.value.doRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
permission: {
|
permission: {
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
text: '权限配置',
|
text: '权限配置',
|
||||||
|
|||||||
@@ -2,17 +2,22 @@
|
|||||||
<fs-page>
|
<fs-page>
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||||
<PermissionDrawerCom />
|
<PermissionDrawerCom />
|
||||||
|
<RoleUser ref="RoleUserRef" />
|
||||||
</fs-page>
|
</fs-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="role">
|
<script lang="ts" setup name="role">
|
||||||
import { defineAsyncComponent, onMounted } from 'vue';
|
import { defineAsyncComponent, onMounted, 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 { RoleDrawerStores } from './stores/RoleDrawerStores';
|
import { RoleDrawerStores } from './stores/RoleDrawerStores';
|
||||||
import { RoleMenuBtnStores } from './stores/RoleMenuBtnStores';
|
import { RoleMenuBtnStores } from './stores/RoleMenuBtnStores';
|
||||||
import { RoleMenuFieldStores } from './stores/RoleMenuFieldStores';
|
import { RoleMenuFieldStores } from './stores/RoleMenuFieldStores';
|
||||||
import { RoleUsersStores } from './stores/RoleUsersStores';
|
import { RoleUsersStores } from './stores/RoleUsersStores';
|
||||||
|
import { RoleUserStores } from './stores/RoleUserStores';
|
||||||
|
|
||||||
|
const RoleUser = defineAsyncComponent(() => import('./components/searchUsers/index.vue'));
|
||||||
|
const RoleUserRef = ref();
|
||||||
|
|
||||||
const PermissionDrawerCom = defineAsyncComponent(() => import('./components/RoleDrawer.vue'));
|
const PermissionDrawerCom = defineAsyncComponent(() => import('./components/RoleDrawer.vue'));
|
||||||
|
|
||||||
@@ -20,9 +25,11 @@ const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
|
|||||||
const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单
|
const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单
|
||||||
const RoleMenuField = RoleMenuFieldStores();// 角色-菜单-字段
|
const RoleMenuField = RoleMenuFieldStores();// 角色-菜单-字段
|
||||||
const RoleUsers = RoleUsersStores();// 角色-用户
|
const RoleUsers = RoleUsersStores();// 角色-用户
|
||||||
|
const RoleUserDrawer = RoleUserStores(); // 授权用户抽屉参数
|
||||||
|
|
||||||
const { crudBinding, crudRef, crudExpose } = useFs({
|
const { crudBinding, crudRef, crudExpose } = useFs({
|
||||||
createCrudOptions,
|
createCrudOptions,
|
||||||
context: { RoleDrawer, RoleMenuBtn, RoleMenuField },
|
context: { RoleDrawer, RoleMenuBtn, RoleMenuField, RoleUserDrawer, RoleUserRef },
|
||||||
});
|
});
|
||||||
|
|
||||||
// 页面打开后获取列表数据
|
// 页面打开后获取列表数据
|
||||||
|
|||||||
29
web/src/views/system/role/stores/RoleUserStores.ts
Normal file
29
web/src/views/system/role/stores/RoleUserStores.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限抽屉:角色-用户
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const RoleUserStores = defineStore('RoleUserStores', {
|
||||||
|
state: (): any => ({
|
||||||
|
drawerVisible: false,
|
||||||
|
role_id: undefined,
|
||||||
|
role_name: undefined,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
/**
|
||||||
|
* 打开权限修改抽屉
|
||||||
|
*/
|
||||||
|
handleDrawerOpen(row: any) {
|
||||||
|
this.drawerVisible = true;
|
||||||
|
this.role_name = row.name;
|
||||||
|
this.role_id = row.id;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 关闭权限修改抽屉
|
||||||
|
*/
|
||||||
|
handleDrawerClose() {
|
||||||
|
this.drawerVisible = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -11,6 +11,7 @@ const pathResolve = (dir: string) => {
|
|||||||
|
|
||||||
const alias: Record<string, string> = {
|
const alias: Record<string, string> = {
|
||||||
'/@': pathResolve('./src/'),
|
'/@': pathResolve('./src/'),
|
||||||
|
'@great-dream': pathResolve('./node_modules/@great-dream/'),
|
||||||
'@views': pathResolve('./src/views'),
|
'@views': pathResolve('./src/views'),
|
||||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js',
|
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js',
|
||||||
'@dvaformflow':pathResolve('./src/viwes/plugins/dvaadmin_form_flow/src/')
|
'@dvaformflow':pathResolve('./src/viwes/plugins/dvaadmin_form_flow/src/')
|
||||||
|
|||||||
Reference in New Issue
Block a user