Merge branch 'develop' of https://e.coding.net/dvadmin-private/code/dvadmin3 into develop
This commit is contained in:
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -98,5 +98,4 @@ media/
|
||||
__pypackages__/
|
||||
package-lock.json
|
||||
gunicorn.pid
|
||||
plugins/*
|
||||
!plugins/__init__.py
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import functools
|
||||
import os
|
||||
|
||||
from celery.signals import task_postrun
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
|
||||
from django.conf import settings
|
||||
@@ -38,3 +40,12 @@ def retry_base_task_error():
|
||||
return wrapper
|
||||
|
||||
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 dvadmin3_celery.settings import * # celery 异步任务
|
||||
from dvadmin3_celery.settings import * # celery 异步任务
|
||||
# from dvadmin_third.settings import * # 第三方用户管理
|
||||
# from dvadmin_ak_sk.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('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
|
||||
# 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/terms_service.html', TermsServiceView.as_view()),
|
||||
]
|
||||
|
||||
@@ -10,16 +10,17 @@ from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
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.menu import MenuSerializer
|
||||
from dvadmin.system.views.menu_button import MenuButtonSerializer
|
||||
from dvadmin.utils.crud_mixin import FastCrudMixin
|
||||
from dvadmin.utils.field_permission import FieldPermissionMixin
|
||||
from dvadmin.utils.json_response import SuccessResponse, DetailResponse
|
||||
from dvadmin.utils.json_response import SuccessResponse, DetailResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.validator import CustomUniqueValidator
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
from dvadmin.utils.permission import CustomPermission
|
||||
|
||||
|
||||
class RoleSerializer(CustomModelSerializer):
|
||||
@@ -107,7 +108,6 @@ class MenuButtonPermissionSerializer(CustomModelSerializer):
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
|
||||
class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin):
|
||||
"""
|
||||
角色管理接口
|
||||
@@ -142,3 +142,62 @@ class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin):
|
||||
role.users_set.add(*movedKeys)
|
||||
serializer = RoleSerializer(role)
|
||||
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)
|
||||
roleId = data.get('roleId', None)
|
||||
btnId = data.get('btnId', None)
|
||||
data_range = data.get('data_range', None) or 0 # 默认仅本人权限
|
||||
dept = data.get('dept', None) or [] # 默认空部门
|
||||
|
||||
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:
|
||||
# 删除权限:移除关联记录
|
||||
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_before = request.query_params.get('create_datetime_before', 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]):
|
||||
create_filter = Q()
|
||||
if create_datetime_after and create_datetime_before:
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
@Created on: 2021/6/1 001 22:57
|
||||
@Remark: 自定义视图集
|
||||
"""
|
||||
import copy
|
||||
|
||||
from django.db import transaction
|
||||
from django_filters import DateTimeFromToRangeFilter
|
||||
from django_filters.rest_framework import FilterSet
|
||||
@@ -67,12 +69,14 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
kwargs.setdefault('context', self.get_serializer_context())
|
||||
# 全部以可见字段为准
|
||||
can_see = self.get_menu_field(serializer_class)
|
||||
# 排除掉序列化器级的字段
|
||||
# sub_set = set(serializer_class._declared_fields.keys()) - set(can_see)
|
||||
# for field in sub_set:
|
||||
# serializer_class._declared_fields.pop(field)
|
||||
# if not self.request.user.is_superuser:
|
||||
# serializer_class.Meta.fields = can_see
|
||||
# 排除掉序列化器级的字段(排除字段权限中未授权的字段)
|
||||
if not self.request.user.is_superuser:
|
||||
exclude_set = set(serializer_class._declared_fields.keys()) - set(can_see)
|
||||
for field in exclude_set:
|
||||
serializer_class._declared_fields.pop(field)
|
||||
meta = copy.deepcopy(serializer_class.Meta)
|
||||
meta.fields = list(can_see)
|
||||
serializer_class.Meta = meta
|
||||
# 在分页器中使用
|
||||
self.request.permission_fields = can_see
|
||||
if isinstance(self.request.data, list):
|
||||
@@ -90,8 +94,9 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
break
|
||||
if finded is False:
|
||||
return []
|
||||
return MenuField.objects.filter(model=model['model']
|
||||
).values('field_name', 'title')
|
||||
roles = self.request.user.role.values_list('id', flat=True)
|
||||
return FieldPermission.objects.filter(is_query=True, role__in=roles, field__model=model['model']).values_list(
|
||||
'field__field_name', flat=True)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data, request=request)
|
||||
@@ -131,8 +136,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
instance.delete()
|
||||
return DetailResponse(data=[], msg="删除成功")
|
||||
|
||||
keys = openapi.Schema(description='主键列表', type=openapi.TYPE_ARRAY, items=openapi.TYPE_STRING)
|
||||
|
||||
keys = openapi.Schema(description='主键列表', type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING))
|
||||
@swagger_auto_schema(request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
required=['keys'],
|
||||
|
||||
@@ -29,3 +29,4 @@ gunicorn==22.0.0
|
||||
gevent==24.2.1
|
||||
Pillow==10.4.0
|
||||
pyinstaller==6.9.0
|
||||
dvadmin3-celery==3.1.6
|
||||
58
init.sh
58
init.sh
@@ -1,5 +1,6 @@
|
||||
#!/bin/bash
|
||||
ENV_FILE=".env"
|
||||
HOST="177.10.0.13"
|
||||
# 检查 .env 文件是否存在
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
echo "$ENV_FILE 文件已存在。"
|
||||
@@ -15,17 +16,60 @@ else
|
||||
echo "REDIS随机密码已生成并写入 $ENV_FILE 文件。"
|
||||
|
||||
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|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
|
||||
echo "初始化密码创建成功"
|
||||
fi
|
||||
|
||||
echo "正在启动容器..."
|
||||
docker-compose up -d
|
||||
docker exec dvadmin3-django python manage.py makemigrations
|
||||
docker exec dvadmin3-django python manage.py migrate
|
||||
docker exec dvadmin3-django python manage.py init
|
||||
echo "欢迎使用dvadmin3项目"
|
||||
echo "登录地址:http://ip:8080"
|
||||
echo "如访问不到,请检查防火墙配置"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "docker-compose up -d 执行失败!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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",
|
||||
"version": "3.0.4",
|
||||
"version": "3.1.0",
|
||||
"description": "是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。\n django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -15,6 +15,7 @@
|
||||
"@fast-crud/fast-extends": "^1.21.2",
|
||||
"@fast-crud/ui-element": "^1.21.2",
|
||||
"@fast-crud/ui-interface": "^1.21.2",
|
||||
"@great-dream/dvadmin3-celery-web": "^3.1.3",
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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-row>
|
||||
<el-col class="flex justify-center">
|
||||
@@ -50,10 +50,11 @@ import { VueCropper } from 'vue-cropper';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { getCurrentInstance, nextTick, reactive, ref, computed, onMounted, defineExpose } from 'vue';
|
||||
import { base64ToFile } from '/@/utils/tools';
|
||||
import headerImage from "/@/assets/img/headerImage.png";
|
||||
import {getBaseURL} from "/@/utils/baseUrl";
|
||||
const userStore = useUserInfo();
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const open = ref(false);
|
||||
const visible = ref(false);
|
||||
const title = ref('修改头像');
|
||||
const emit = defineEmits(['uploadImg']);
|
||||
@@ -75,7 +76,7 @@ const dialogVisiable = computed({
|
||||
|
||||
//图片裁剪数据
|
||||
const options = reactive({
|
||||
img: userStore.userInfos.avatar, // 裁剪图片的地址
|
||||
img: userStore.userInfos.avatar || headerImage, // 裁剪图片的地址
|
||||
fileName: '',
|
||||
autoCrop: true, // 是否默认生成截图框
|
||||
autoCropWidth: 200, // 默认生成截图框宽度
|
||||
@@ -165,6 +166,7 @@ const updateAvatar = (img) => {
|
||||
|
||||
defineExpose({
|
||||
updateAvatar,
|
||||
editCropper
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -172,7 +174,6 @@ defineExpose({
|
||||
.user-info-head {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.user-info-head:hover:after {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
popper-class="popperClass"
|
||||
class="tableSelector"
|
||||
multiple
|
||||
:collapseTags="props.tableConfig.collapseTags"
|
||||
@remove-tag="removeTag"
|
||||
v-model="data"
|
||||
placeholder="请选择"
|
||||
@@ -18,20 +19,22 @@
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="tableData"
|
||||
size="mini"
|
||||
:size="props.tableConfig.size"
|
||||
border
|
||||
row-key="id"
|
||||
:lazy="props.tableConfig.lazy"
|
||||
:load="props.tableConfig.load"
|
||||
:tree-props="props.tableConfig.treeProps"
|
||||
style="width: 400px"
|
||||
style="width: 600px"
|
||||
max-height="200"
|
||||
height="200"
|
||||
:highlight-current-row="!props.tableConfig.isMultiple"
|
||||
@selection-change="handleSelectionChange"
|
||||
@select="handleSelectionChange"
|
||||
@selectAll="handleSelectionChange"
|
||||
@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
|
||||
:prop="item.prop"
|
||||
@@ -56,23 +59,31 @@
|
||||
</template>
|
||||
|
||||
<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 { request } from '/@/utils/service';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {},
|
||||
modelValue: {
|
||||
type: Array || String || Number,
|
||||
default: () => []
|
||||
},
|
||||
tableConfig: {
|
||||
type: Object,
|
||||
default:{
|
||||
url: null,
|
||||
label: null, //显示值
|
||||
value: null, //数据值
|
||||
isTree: false,
|
||||
lazy: true,
|
||||
size:'default',
|
||||
load: () => {},
|
||||
data: [], //默认数据
|
||||
isMultiple: false, //是否多选
|
||||
collapseTags:false,
|
||||
treeProps: { children: 'children', hasChildren: 'hasChildren' },
|
||||
columns: [], //每一项对应的列表项
|
||||
},
|
||||
},
|
||||
displayLabel: {},
|
||||
} as any);
|
||||
@@ -86,7 +97,7 @@ const multipleSelection = ref();
|
||||
// 搜索值
|
||||
const search = ref(undefined);
|
||||
//表格数据
|
||||
const tableData = ref();
|
||||
const tableData = ref([]);
|
||||
// 分页的配置
|
||||
const pageConfig = reactive({
|
||||
page: 1,
|
||||
@@ -99,7 +110,6 @@ const pageConfig = reactive({
|
||||
* @param val:Array
|
||||
*/
|
||||
const handleSelectionChange = (val: any) => {
|
||||
multipleSelection.value = val;
|
||||
const { tableConfig } = props;
|
||||
const result = val.map((item: any) => {
|
||||
return item[tableConfig.value];
|
||||
@@ -117,7 +127,7 @@ const handleSelectionChange = (val: any) => {
|
||||
const handleCurrentChange = (val: any) => {
|
||||
const { tableConfig } = props;
|
||||
if (!tableConfig.isMultiple && val) {
|
||||
data.value = [val[tableConfig.label]];
|
||||
// data.value = [val[tableConfig.label]];
|
||||
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
|
||||
@@ -169,20 +205,12 @@ const handlePageChange = (page: any) => {
|
||||
getDict();
|
||||
};
|
||||
|
||||
// 监听displayLabel的变化,更新数据
|
||||
watch(
|
||||
() => {
|
||||
return props.displayLabel;
|
||||
},
|
||||
(value) => {
|
||||
const { tableConfig } = props;
|
||||
const result = value
|
||||
? value.map((item: any) => { return item[tableConfig.label];})
|
||||
: null;
|
||||
data.value = result;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
onMounted(()=>{
|
||||
setTimeout(()=>{
|
||||
getNodeValues()
|
||||
},1000)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -17,13 +17,15 @@ import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
|
||||
import { useRoute } from 'vue-router';
|
||||
const route = useRoute();
|
||||
// 引入组件
|
||||
const Logo = defineAsyncComponent(() => import('/@/layout/logo/index.vue'));
|
||||
const Vertical = defineAsyncComponent(() => import('/@/layout/navMenu/vertical.vue'));
|
||||
|
||||
// 定义变量内容
|
||||
const layoutAsideScrollbarRef = ref();
|
||||
const routesIndex = ref(0);
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
@@ -83,10 +85,36 @@ const closeLayoutAsideMobileMode = () => {
|
||||
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
|
||||
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;
|
||||
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[] => {
|
||||
@@ -122,7 +150,8 @@ onBeforeMount(() => {
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
if (layout === 'classic' && isClassicSplitMenu) {
|
||||
state.menuList = [];
|
||||
state.menuList = res.children;
|
||||
// state.menuList = res.children;
|
||||
setFilterRoutes(res.path);
|
||||
}
|
||||
});
|
||||
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
||||
|
||||
@@ -102,6 +102,5 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--next-bg-topBar);
|
||||
border-bottom: 1px solid var(--next-border-color-light);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
|
||||
</div>
|
||||
<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>
|
||||
<el-badge :value="messageCenter.unread" :hidden="messageCenter.unread === 0">
|
||||
<el-icon :title="$t('message.user.title4')">
|
||||
@@ -58,7 +58,7 @@
|
||||
></i>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="!isSocketOpen">
|
||||
<span v-if="!isSocketOpen" class="online-status-span">
|
||||
<el-popconfirm
|
||||
width="250"
|
||||
ref="onlinePopoverRef"
|
||||
@@ -71,7 +71,7 @@
|
||||
>
|
||||
<template #reference>
|
||||
<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>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
@@ -93,7 +93,7 @@
|
||||
<el-dropdown-menu>
|
||||
<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="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-menu>
|
||||
</template>
|
||||
@@ -250,6 +250,7 @@ onMounted(() => {
|
||||
|
||||
//消息中心的未读数量
|
||||
import { messageCenterStore } from '/@/stores/messageCenter';
|
||||
import {getBaseURL} from "/@/utils/baseUrl";
|
||||
const messageCenter = messageCenterStore();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="el-menu-horizontal-warp">
|
||||
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
|
||||
<el-menu router :default-active="state.defaultActive" :ellipsis="false" background-color="transparent" mode="horizontal">
|
||||
<template v-for="val in menuLists">
|
||||
<!-- <el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">-->
|
||||
<el-menu :default-active="defaultActive" background-color="transparent" mode="horizontal">
|
||||
<template v-for="(val,index) in menuLists">
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
@@ -11,7 +11,7 @@
|
||||
<SubItem :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
<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)">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
{{ $t(val.meta.title) }}
|
||||
@@ -26,22 +26,25 @@
|
||||
</template>
|
||||
</template>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
<!-- </el-scrollbar>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="navMenuHorizontal">
|
||||
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 { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import other from '/@/utils/other';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
|
||||
const router = useRouter()
|
||||
// 引入组件
|
||||
const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue'));
|
||||
|
||||
const state = reactive<AsideState>({
|
||||
menuList: [],
|
||||
clientWidth: 0
|
||||
});
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
// 菜单列表
|
||||
@@ -58,19 +61,33 @@ const storesThemeConfig = useThemeConfig();
|
||||
const { routesList } = storeToRefs(stores);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const route = useRoute();
|
||||
const state = reactive({
|
||||
defaultActive: '' as string | undefined,
|
||||
});
|
||||
|
||||
const defaultActive = ref('')
|
||||
// 获取父级菜单数据
|
||||
const menuLists = computed(() => {
|
||||
<RouteItems>props.menuList.shift()
|
||||
return <RouteItems>props.menuList;
|
||||
});
|
||||
// 设置横向滚动条可以鼠标滚轮滚动
|
||||
const onElMenuHorizontalScroll = (e: WheelEventType) => {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40;
|
||||
elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft + eventDelta / 4;
|
||||
// 递归获取当前路由的顶级索引
|
||||
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 initElMenuOffsetLeft = () => {
|
||||
nextTick(() => {
|
||||
@@ -107,17 +124,41 @@ const setSendClassicChildren = (path: string) => {
|
||||
const setCurrentRouterHighlight = (currentRoute: RouteToFrom) => {
|
||||
const { path, meta } = currentRoute;
|
||||
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 {
|
||||
const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split('/') : path!.split('/');
|
||||
if (pathSplit.length >= 4 && meta?.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
|
||||
else state.defaultActive = path;
|
||||
if (pathSplit.length >= 4 && meta?.isHide) defaultActive.value = pathSplit.splice(0, 3).join('/');
|
||||
else defaultActive.value = path;
|
||||
}
|
||||
};
|
||||
// 打开外部链接
|
||||
const onALinkClick = (val: RouteItem) => {
|
||||
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(() => {
|
||||
setCurrentRouterHighlight(route);
|
||||
@@ -126,16 +167,6 @@ onBeforeMount(() => {
|
||||
onMounted(() => {
|
||||
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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -8,7 +8,21 @@
|
||||
<sub-item :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
<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)">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ $t(val.meta.title) }}</span>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<script setup lang="ts" name="layoutIframeView">
|
||||
import { computed, watch, ref, nextTick } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import {cookie} from "xe-utils";
|
||||
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
@@ -49,7 +50,15 @@ const route = useRoute();
|
||||
|
||||
// 处理 list 列表,当打开时,才进行加载
|
||||
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
|
||||
const getRoutePath = computed(() => {
|
||||
|
||||
@@ -17,19 +17,31 @@
|
||||
import { reactive, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { verifyUrl } from '/@/utils/toolsValidate';
|
||||
import {cookie} from "xe-utils";
|
||||
|
||||
// 定义变量内容
|
||||
const route = useRoute();
|
||||
const state = reactive<LinkViewState>({
|
||||
title: '',
|
||||
isLink: '',
|
||||
query: null
|
||||
});
|
||||
|
||||
// 立即前往
|
||||
const onGotoFullPage = () => {
|
||||
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);
|
||||
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(
|
||||
@@ -37,6 +49,7 @@ watch(
|
||||
() => {
|
||||
state.title = <string>route.meta.title;
|
||||
state.isLink = <string>route.meta.isLink;
|
||||
state.query = <any>route.query;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
|
||||
@@ -21,13 +21,14 @@ const menuApi = useMenuApi();
|
||||
|
||||
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
|
||||
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
|
||||
const greatDream: any = import.meta.glob('@great-dream/**/*.{vue,tsx}');
|
||||
|
||||
/**
|
||||
* 获取目录下的 .vue、.tsx 全部文件
|
||||
* @method import.meta.glob
|
||||
* @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 matchKeys = keys.filter((key) => {
|
||||
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) {
|
||||
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 = {
|
||||
title: string;
|
||||
isLink: string;
|
||||
query: any;
|
||||
};
|
||||
|
||||
@@ -1,45 +1,65 @@
|
||||
import {dict} from "@fast-crud/fast-crud";
|
||||
import {shallowRef} from 'vue'
|
||||
import deptFormat from "/@/components/dept-format/index.vue";
|
||||
import {dict} from '@fast-crud/fast-crud';
|
||||
import {shallowRef} from 'vue';
|
||||
import deptFormat from '/@/components/dept-format/index.vue';
|
||||
|
||||
export const commonCrudConfig = (options = {
|
||||
create_datetime: {
|
||||
form: false,
|
||||
table: false,
|
||||
search: false
|
||||
},
|
||||
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
|
||||
},
|
||||
}) => {
|
||||
/** 1. 每个字段可选属性 */
|
||||
export interface CrudFieldOption {
|
||||
form?: boolean;
|
||||
table?: boolean;
|
||||
search?: boolean;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
/** 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: options.dept_belong_id?.search || false
|
||||
show: merged.dept_belong_id.search,
|
||||
},
|
||||
dict: dict({
|
||||
url: '/api/system/dept/all_dept/',
|
||||
@@ -50,91 +70,93 @@ export const commonCrudConfig = (options = {
|
||||
}),
|
||||
column: {
|
||||
align: 'center',
|
||||
width: 300,
|
||||
show: options.dept_belong_id?.table || false,
|
||||
width: merged.dept_belong_id.width,
|
||||
show: merged.dept_belong_id.table,
|
||||
component: {
|
||||
name: shallowRef(deptFormat),
|
||||
vModel: "modelValue",
|
||||
}
|
||||
// fast-crud里自定义组件常用"component.is"
|
||||
is: shallowRef(deptFormat),
|
||||
vModel: 'modelValue',
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: options.dept_belong_id?.form || false,
|
||||
show: merged.dept_belong_id.form,
|
||||
component: {
|
||||
multiple: false,
|
||||
clearable: true,
|
||||
props: {
|
||||
checkStrictly: true,
|
||||
props: {
|
||||
// 为什么这里要写两层props
|
||||
// 因为props属性名与fs的动态渲染的props命名冲突,所以要多写一层
|
||||
label: "name",
|
||||
value: "id",
|
||||
}
|
||||
}
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
helper: '默认不填则为当前创建用户的部门ID',
|
||||
},
|
||||
helper: "默认不填则为当前创建用户的部门ID"
|
||||
}
|
||||
},
|
||||
description: {
|
||||
title: '备注',
|
||||
search: {
|
||||
show: options.description?.search || false
|
||||
show: merged.description.search,
|
||||
},
|
||||
type: 'textarea',
|
||||
column: {
|
||||
width: 100,
|
||||
show: options.description?.table || false,
|
||||
width: merged.description.width,
|
||||
show: merged.description.table,
|
||||
},
|
||||
form: {
|
||||
show: options.description?.form || false,
|
||||
show: merged.description.form,
|
||||
component: {
|
||||
placeholder: '请输入内容',
|
||||
showWordLimit: true,
|
||||
maxlength: '200',
|
||||
}
|
||||
},
|
||||
},
|
||||
viewForm: {
|
||||
show: true
|
||||
}
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
|
||||
modifier_name: {
|
||||
title: '修改人',
|
||||
search: {
|
||||
show: options.modifier_name?.search || false
|
||||
show: merged.modifier_name.search,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
show: options.modifier_name?.table || false,
|
||||
width: merged.modifier_name.width,
|
||||
show: merged.modifier_name.table,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
show: merged.modifier_name.form,
|
||||
},
|
||||
viewForm: {
|
||||
show: true
|
||||
}
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
|
||||
creator_name: {
|
||||
title: '创建人',
|
||||
search: {
|
||||
show: options.creator_name?.search || false
|
||||
show: merged.creator_name.search,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
show: options.creator_name?.table || false,
|
||||
width: merged.creator_name.width,
|
||||
show: merged.creator_name.table,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
show: merged.creator_name.form,
|
||||
},
|
||||
viewForm: {
|
||||
show: true
|
||||
}
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
|
||||
update_datetime: {
|
||||
title: '更新时间',
|
||||
type: 'datetime',
|
||||
search: {
|
||||
show: options.update_datetime?.search || false,
|
||||
col: {span: 8},
|
||||
show: merged.update_datetime.search,
|
||||
col: { span: 8 },
|
||||
component: {
|
||||
type: 'datetimerange',
|
||||
props: {
|
||||
@@ -142,62 +164,65 @@ export const commonCrudConfig = (options = {
|
||||
'end-placeholder': '结束时间',
|
||||
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
||||
'picker-options': {
|
||||
shortcuts: [{
|
||||
shortcuts: [
|
||||
{
|
||||
text: '最近一周',
|
||||
onClick(picker) {
|
||||
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) {
|
||||
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) {
|
||||
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 {key, value} = context
|
||||
//value解析,就是把组件的值转化为后台所需要的值
|
||||
//在form表单点击保存按钮后,提交到后台之前执行转化
|
||||
const { value } = context;
|
||||
if (value) {
|
||||
context.form.update_datetime_after = value[0]
|
||||
context.form.update_datetime_before = value[1]
|
||||
}
|
||||
// ↑↑↑↑↑ 注意这里是form,不是row
|
||||
context.form.update_datetime_after = value[0];
|
||||
context.form.update_datetime_before = value[1];
|
||||
delete context.form.update_datetime;
|
||||
}
|
||||
},
|
||||
},
|
||||
column: {
|
||||
width: 160,
|
||||
show: options.update_datetime?.table || false,
|
||||
width: merged.update_datetime.width,
|
||||
show: merged.update_datetime.table,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
show: merged.update_datetime.form,
|
||||
},
|
||||
viewForm: {
|
||||
show: true
|
||||
}
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
|
||||
create_datetime: {
|
||||
title: '创建时间',
|
||||
type: 'datetime',
|
||||
search: {
|
||||
show: options.create_datetime?.search || false,
|
||||
col: {span: 8},
|
||||
show: merged.create_datetime.search,
|
||||
col: { span: 8 },
|
||||
component: {
|
||||
type: 'datetimerange',
|
||||
props: {
|
||||
@@ -205,55 +230,57 @@ export const commonCrudConfig = (options = {
|
||||
'end-placeholder': '结束时间',
|
||||
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
||||
'picker-options': {
|
||||
shortcuts: [{
|
||||
shortcuts: [
|
||||
{
|
||||
text: '最近一周',
|
||||
onClick(picker) {
|
||||
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) {
|
||||
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) {
|
||||
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 {key, value} = context
|
||||
//value解析,就是把组件的值转化为后台所需要的值
|
||||
//在form表单点击保存按钮后,提交到后台之前执行转化
|
||||
const { value } = context;
|
||||
if (value) {
|
||||
context.form.create_datetime_after = value[0]
|
||||
context.form.create_datetime_before = value[1]
|
||||
}
|
||||
// ↑↑↑↑↑ 注意这里是form,不是row
|
||||
context.form.create_datetime_after = value[0];
|
||||
context.form.create_datetime_before = value[1];
|
||||
delete context.form.create_datetime;
|
||||
}
|
||||
},
|
||||
},
|
||||
column: {
|
||||
width: 160,
|
||||
show: options.create_datetime?.table || false,
|
||||
width: merged.create_datetime.width,
|
||||
show: merged.create_datetime.table,
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
show: merged.create_datetime.form,
|
||||
},
|
||||
viewForm: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import axios from "axios";
|
||||
import * as process from "process";
|
||||
import {Local, Session} from '/@/utils/storage';
|
||||
import {ElNotification} from "element-plus";
|
||||
import fs from "fs";
|
||||
import axios from 'axios';
|
||||
import * as process from 'process';
|
||||
import { Local, Session } from '/@/utils/storage';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import fs from 'fs';
|
||||
|
||||
// 是否显示升级提示信息框
|
||||
const IS_SHOW_UPGRADE_SESSION_KEY = 'isShowUpgrade';
|
||||
const VERSION_KEY = 'DVADMIN3_VERSION'
|
||||
const VERSION_FILE_NAME = 'version-build'
|
||||
const VERSION_KEY = 'DVADMIN3_VERSION';
|
||||
const VERSION_FILE_NAME = 'version-build';
|
||||
|
||||
export function showUpgrade () {
|
||||
const isShowUpgrade = Session.get(IS_SHOW_UPGRADE_SESSION_KEY) ?? false
|
||||
const META_ENV = import.meta.env;
|
||||
|
||||
export function showUpgrade() {
|
||||
const isShowUpgrade = Session.get(IS_SHOW_UPGRADE_SESSION_KEY) ?? false;
|
||||
if (isShowUpgrade) {
|
||||
Session.remove(IS_SHOW_UPGRADE_SESSION_KEY)
|
||||
Session.remove(IS_SHOW_UPGRADE_SESSION_KEY);
|
||||
ElNotification({
|
||||
title: '新版本升级',
|
||||
message: "检测到系统新版本,正在更新中!不用担心,更新很快的哦!",
|
||||
message: '检测到系统新版本,正在更新中!不用担心,更新很快的哦!',
|
||||
type: 'success',
|
||||
duration: 5000,
|
||||
});
|
||||
@@ -23,33 +25,34 @@ export function showUpgrade () {
|
||||
}
|
||||
|
||||
// 生产环境前端版本校验,
|
||||
export async function checkVersion(){
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
export async function checkVersion() {
|
||||
if (META_ENV.MODE === 'development') {
|
||||
// 开发环境无需校验前端版本
|
||||
return
|
||||
return;
|
||||
}
|
||||
// 获取线上版本号 t为时间戳,防止缓存
|
||||
await axios.get(`${import.meta.env.VITE_PUBLIC_PATH}${VERSION_FILE_NAME}?t=${new Date().getTime()}`).then(res => {
|
||||
const {status, data} = res || {}
|
||||
await axios.get(`${META_ENV.VITE_PUBLIC_PATH}${VERSION_FILE_NAME}?t=${new Date().getTime()}`).then((res) => {
|
||||
const { status, data } = res || {};
|
||||
if (status === 200) {
|
||||
// 获取当前版本号
|
||||
const localVersion = Local.get(VERSION_KEY)
|
||||
const localVersion = Local.get(VERSION_KEY);
|
||||
// 将当前版本号持久缓存至本地
|
||||
Local.set(VERSION_KEY, data)
|
||||
Local.set(VERSION_KEY, data);
|
||||
// 当用户本地存在版本号并且和线上版本号不一致时,进行页面刷新操作
|
||||
if (localVersion && localVersion !== data) {
|
||||
// 本地缓存版本号和线上版本号不一致,弹出升级提示框
|
||||
// 此处无法直接使用消息框进行提醒,因为 window.location.reload()会导致消息框消失,将在loading页面判断是否需要显示升级提示框
|
||||
Session.set(IS_SHOW_UPGRADE_SESSION_KEY, true)
|
||||
window.location.reload()
|
||||
|
||||
Session.set(IS_SHOW_UPGRADE_SESSION_KEY, true);
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function generateVersionFile (){
|
||||
export function generateVersionFile() {
|
||||
// 生成版本文件到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;
|
||||
|
||||
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 const scanAndInstallPlugins = (app: any) => {
|
||||
const components = import.meta.glob('./**/*.vue');
|
||||
const components = import.meta.glob('./**/*.ts');
|
||||
const pluginNames = new Set();
|
||||
// 遍历对象并注册异步组件
|
||||
for (const [key, value] of Object.entries(components)) {
|
||||
@@ -11,6 +11,15 @@ export const scanAndInstallPlugins = (app: any) => {
|
||||
const pluginsName = key.match(/\/([^\/]*)\//)?.[1];
|
||||
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);
|
||||
console.log('已发现插件:', pluginsAll);
|
||||
};
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
|
||||
<el-form-item v-if="!menuFormData.is_catalog && menuFormData.is_link" label="外链接" prop="link_url">
|
||||
<el-input v-model="menuFormData.link_url" placeholder="请输入外链接地址" />
|
||||
<el-alert :title="`输入{{token}}可自动替换系统 token `" type="info" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!menuFormData.is_catalog" label="缓存">
|
||||
|
||||
@@ -1,9 +1,39 @@
|
||||
<template>
|
||||
<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)">
|
||||
<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)">
|
||||
<el-icon>
|
||||
<Setting />
|
||||
@@ -48,10 +78,26 @@ import { RoleMenuBtnType } from '../types';
|
||||
import { getRoleToDeptAll, setRoleMenuBtn, setRoleMenuBtnDataRange } from './api';
|
||||
import XEUtils from 'xe-utils';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Local } from '/@/utils/storage';
|
||||
|
||||
const RoleDrawer = RoleDrawerStores(); // 角色-菜单
|
||||
const RoleMenuTree = RoleMenuTreeStores(); // 角色-菜单
|
||||
const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单-按钮
|
||||
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>({
|
||||
id: 0,
|
||||
@@ -83,6 +129,29 @@ const defaultTreeProps = {
|
||||
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(() => {
|
||||
return function (datarange: number) {
|
||||
return function (datarange: number, dept: Array<number>) {
|
||||
const datarangeitem = XEUtils.find(dataPermissionRange.value, (item: any) => {
|
||||
if (item.value === datarange) {
|
||||
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;
|
||||
};
|
||||
});
|
||||
@@ -108,11 +186,14 @@ const formatDataRange = computed(() => {
|
||||
* 勾选按钮
|
||||
*/
|
||||
const handleCheckChange = async (btn: RoleMenuBtnType) => {
|
||||
selectBtn.value = default_selectBtn.value;
|
||||
const put_data = {
|
||||
isCheck: btn.isCheck,
|
||||
roleId: RoleDrawer.roleId,
|
||||
menuId: RoleMenuTree.id,
|
||||
btnId: btn.id,
|
||||
data_range: default_selectBtn.value.data_range,
|
||||
dept: default_selectBtn.value.dept,
|
||||
};
|
||||
const { data, msg } = await setRoleMenuBtn(put_data);
|
||||
RoleMenuBtn.updateState(data);
|
||||
@@ -168,9 +249,10 @@ onMounted(async () => {
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
// .el-checkbox {
|
||||
// width: 200px;
|
||||
// }
|
||||
|
||||
.el-checkbox {
|
||||
width: 20%;
|
||||
}
|
||||
.btn-item {
|
||||
display: flex;
|
||||
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 { successMessage } from '../../../utils/message';
|
||||
import { auth } from '/@/utils/authFunction';
|
||||
import { nextTick, computed } from 'vue';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -46,7 +47,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 320,
|
||||
width: computed(() => {
|
||||
if (auth('role:AuthorizedAdd') || auth('role:AuthorizedSearch')){
|
||||
return 420;
|
||||
}
|
||||
return 320;
|
||||
}),
|
||||
buttons: {
|
||||
view: {
|
||||
show: true,
|
||||
@@ -57,6 +63,19 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
remove: {
|
||||
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: {
|
||||
type: 'primary',
|
||||
text: '权限配置',
|
||||
|
||||
@@ -2,17 +2,22 @@
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
<PermissionDrawerCom />
|
||||
<RoleUser ref="RoleUserRef" />
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<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 { createCrudOptions } from './crud';
|
||||
import { RoleDrawerStores } from './stores/RoleDrawerStores';
|
||||
import { RoleMenuBtnStores } from './stores/RoleMenuBtnStores';
|
||||
import { RoleMenuFieldStores } from './stores/RoleMenuFieldStores';
|
||||
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'));
|
||||
|
||||
@@ -20,9 +25,11 @@ const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
|
||||
const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单
|
||||
const RoleMenuField = RoleMenuFieldStores();// 角色-菜单-字段
|
||||
const RoleUsers = RoleUsersStores();// 角色-用户
|
||||
const RoleUserDrawer = RoleUserStores(); // 授权用户抽屉参数
|
||||
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({
|
||||
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> = {
|
||||
'/@': pathResolve('./src/'),
|
||||
'@great-dream': pathResolve('./node_modules/@great-dream/'),
|
||||
'@views': pathResolve('./src/views'),
|
||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js',
|
||||
'@dvaformflow':pathResolve('./src/viwes/plugins/dvaadmin_form_flow/src/')
|
||||
|
||||
Reference in New Issue
Block a user