fix:联调权限接口
This commit is contained in:
@@ -19,6 +19,16 @@ export const login = (data: { username: string; password: string }) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 获取字典映射
|
||||
export const getDictMap = (ids:string) => {
|
||||
return request.get(`/auth/v1/dict/type/${ids}`);
|
||||
};
|
||||
|
||||
// 获取二级菜单数据
|
||||
export const getDictMapLevel = (key:string,parentId:string) => {
|
||||
return request.get(`/auth/v1/dict/type/${key}/${parentId}`);
|
||||
};
|
||||
|
||||
|
||||
/**员工公用接口*/
|
||||
|
||||
|
||||
@@ -50,12 +50,12 @@ export const batchSaveRole = (roleId: string,data: number[]) => {
|
||||
|
||||
// 保存角色权限
|
||||
export const saveRolePermission = (data: any) => {
|
||||
return request.post(`/auth/v1/backend/role/permission/save`, data);
|
||||
return request.post(`/auth/v1/backend/role/permissions/save`, data);
|
||||
}
|
||||
|
||||
// 查询角色权限
|
||||
// 查询角色权限 (获取角色权限详情)
|
||||
export const getRolePermission = (roleId: string) => {
|
||||
return request.get(`/auth/v1/backend/role/${roleId}/permission`);
|
||||
return request.get(`/auth/v1/backend/role/${roleId}/permissions`);
|
||||
}
|
||||
|
||||
// 获取角色成员列表
|
||||
|
||||
126
src/components/proTable/proTablev2.vue
Normal file
126
src/components/proTable/proTablev2.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div ref="tableContainerRef" class="pro-table-v2-container">
|
||||
<el-table-v2
|
||||
ref="tableRef"
|
||||
v-bind="$attrs"
|
||||
:columns="adaptedColumns"
|
||||
:data="data"
|
||||
:width="tableSize.width"
|
||||
:height="tableSize.height"
|
||||
:fixed="true"
|
||||
@rows-rendered="handleRowsRendered"
|
||||
>
|
||||
<template #overlay v-if="loading">
|
||||
<div class="v2-loading-overlay">
|
||||
<el-icon class="is-loading" :size="26"><Loading /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-v2>
|
||||
|
||||
<div class="pro-table-v2-footer">
|
||||
<div class="footer-left">
|
||||
<span>已加载 {{ data.length }} / 共 {{ total }} 条</span>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<span v-if="loading">加载中...</span>
|
||||
<span v-else-if="noMore">已加载全部</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||
import { debounce } from 'lodash-es';
|
||||
import { ElTag, ElText } from 'element-plus';
|
||||
import NameAvatar from "@/components/NameAvatar/index.vue";
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const props = defineProps({
|
||||
columns: { type: Array, required: true },
|
||||
data: { type: Array, required: true },
|
||||
total: { type: Number, default: 0 },
|
||||
loading: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits(["load-more"]);
|
||||
|
||||
// --- 内置渲染逻辑工厂 ---
|
||||
const builtInRenderers = {
|
||||
// 1. 人员头像渲染器 (默认读取 row.name 和 row.avatar)
|
||||
member: (scope: any, col: any) => (
|
||||
<div style="display:flex; align-items:center; gap:8px;">
|
||||
<NameAvatar
|
||||
name={scope.rowData[col.prop]}
|
||||
src={scope.rowData[col.avatarKey || 'avatar']}
|
||||
size={28}
|
||||
/>
|
||||
<span>{scope.rowData[col.prop]}</span>
|
||||
</div>
|
||||
),
|
||||
|
||||
// 2. 状态标签渲染器 (支持自定义映射)
|
||||
status: (scope: any, col: any) => {
|
||||
const val = scope.rowData[col.prop];
|
||||
const option = col.options?.find((opt: any) => opt.value === val) || { label: val, type: 'info' };
|
||||
return <ElTag type={option.type} size="small">{option.label}</ElTag>;
|
||||
},
|
||||
|
||||
// 3. 日期格式化
|
||||
date: (scope: any, col: any) => {
|
||||
const val = scope.rowData[col.prop];
|
||||
return <span>{val ? dayjs(val).format(col.format || 'YYYY-MM-DD HH:mm') : '-'}</span>;
|
||||
},
|
||||
|
||||
// 4. 金额格式化
|
||||
money: (scope: any, col: any) => {
|
||||
const val = scope.rowData[col.prop];
|
||||
return <ElText type="warning">¥ {Number(val || 0).toLocaleString()}</ElText>;
|
||||
}
|
||||
};
|
||||
|
||||
const adaptedColumns = computed(() => {
|
||||
return props.columns.map((col: any) => ({
|
||||
key: col.prop,
|
||||
dataKey: col.prop,
|
||||
title: col.label,
|
||||
width: col.width || 150,
|
||||
fixed: col.fixed,
|
||||
align: col.align || 'left',
|
||||
cellRenderer: (scope: any) => {
|
||||
// 优先级 1: 用户自定义了 render 函数
|
||||
if (typeof col.render === 'function') return col.render(scope);
|
||||
|
||||
// 优先级 2: 使用内置的 valueType 渲染器
|
||||
if (col.valueType && builtInRenderers[col.valueType]) {
|
||||
return builtInRenderers[col.valueType](scope, col);
|
||||
}
|
||||
|
||||
// 优先级 3: 默认展示
|
||||
return scope.rowData[col.prop] ?? '-';
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
// --- 容器与滚动逻辑 (保持之前的一致) ---
|
||||
const tableContainerRef = ref(null);
|
||||
const tableSize = ref({ width: 0, height: 400 });
|
||||
const noMore = computed(() => props.data.length >= props.total && props.total > 0);
|
||||
|
||||
const updateSize = () => {
|
||||
if (tableContainerRef.value) {
|
||||
const rect = tableContainerRef.value.getBoundingClientRect();
|
||||
tableSize.value.width = rect.width;
|
||||
tableSize.value.height = window.innerHeight - rect.top - 45;
|
||||
}
|
||||
};
|
||||
const handleResize = debounce(updateSize, 200);
|
||||
const handleRowsRendered = ({ endRowIndex }: any) => {
|
||||
if (endRowIndex >= props.data.length - 5 && !props.loading && !noMore.value) {
|
||||
emit("load-more");
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => { updateSize(); window.addEventListener('resize', handleResize); });
|
||||
onUnmounted(() => { window.removeEventListener('resize', handleResize); });
|
||||
</script>
|
||||
@@ -20,7 +20,7 @@
|
||||
<template #title>{{ row.meta.title }}</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-else :index="item.path">
|
||||
<el-menu-item v-else :index="getFirstChildPath(item)">
|
||||
<el-icon v-if="item.meta?.icon"><component :is="item.meta.icon" /></el-icon>
|
||||
<template #title>{{ item.meta.title }}</template>
|
||||
</el-menu-item>
|
||||
@@ -68,6 +68,18 @@ const resolvePath = (parentPath: string, childPath: string) => {
|
||||
// 4. 返回拼接后的路径
|
||||
return `${parent}/${child}`;
|
||||
};
|
||||
/**
|
||||
* 获取菜单项的跳转路径
|
||||
* 如果是顶级菜单且有子项,点击应跳转到第一个子项
|
||||
*/
|
||||
const getFirstChildPath = (item: any) => {
|
||||
// 如果是顶级菜单且有子菜单
|
||||
if (item.children && item.children.length > 0) {
|
||||
return resolvePath(item.path, item.children[0].path);
|
||||
}
|
||||
// 如果没有子菜单,直接返回 item.path,但要确保它是以 / 开头
|
||||
return item.path.startsWith('/') ? item.path : `/${item.path}`;
|
||||
};
|
||||
|
||||
// 处理菜单选中事件
|
||||
const handleMenuSelect = (index: string) => {
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// 后台 - 权限管理模块-权限内容信息
|
||||
|
||||
|
||||
// 权限状态映射
|
||||
const roleDict = {
|
||||
1: '启用',
|
||||
0: '禁用'
|
||||
}
|
||||
|
||||
// 权限状态颜色
|
||||
const roleDictColor = {
|
||||
1:'#66E5BE',
|
||||
0:'#90A1B9'
|
||||
}
|
||||
|
||||
// 创建角色类型字典
|
||||
const roleTypeOptions = [
|
||||
{ label: '实例角色', value: 'INSTANCE' },
|
||||
{ label: '默认角色', value: 'DEFAULT' },
|
||||
{ label: '系统角色', value: 'SYSTEM' }
|
||||
]
|
||||
|
||||
// 设置权限转换为目标格式
|
||||
const statusOptions = Object.keys(roleDict).map((key) => {
|
||||
return {
|
||||
label: roleDict[key],
|
||||
value: Number(key)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
export default {
|
||||
roleDict,
|
||||
roleDictColor,
|
||||
statusOptions,
|
||||
roleTypeOptions
|
||||
}
|
||||
@@ -1,29 +1,13 @@
|
||||
// 后台 - 字典管理模块-字典内容信息
|
||||
|
||||
|
||||
// 字典状态映射
|
||||
const statusDict = {
|
||||
1: '正常',
|
||||
0: '禁用'
|
||||
}
|
||||
|
||||
// 字典状态颜色
|
||||
const statusDictColor = {
|
||||
1:'#66E5BE',
|
||||
0:'#90A1B9'
|
||||
}
|
||||
|
||||
// 设置字典转换为目标格式
|
||||
const statusOptions = Object.keys(statusDict).map((key) => {
|
||||
return {
|
||||
label: statusDict[key],
|
||||
value: Number(key)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
export default {
|
||||
statusDict,
|
||||
statusDictColor,
|
||||
statusOptions
|
||||
}
|
||||
@@ -9,6 +9,5 @@ Object.entries(modules).forEach(([path, module]: [string, any]) => {
|
||||
// 2. 统一导出
|
||||
export const {
|
||||
DictManage,
|
||||
PermissionManage
|
||||
} = components;
|
||||
export default components;
|
||||
79
src/hooks/useDictData.ts
Normal file
79
src/hooks/useDictData.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { getDictMap,getDictMapLevel } from '@/api';
|
||||
|
||||
// 全局静态缓存,跨组件共享
|
||||
const dictCache = reactive({});
|
||||
const pendingPromises = new Map();
|
||||
|
||||
export function useDict(codes) {
|
||||
const dicts = ref({});
|
||||
const loading = ref(false);
|
||||
|
||||
const fetchDicts = async () => {
|
||||
if (!codes) return;
|
||||
|
||||
const codeArray = codes.split(',').map(s => s.trim());
|
||||
const result = {};
|
||||
const needFetch = [];
|
||||
|
||||
// 1. 区分哪些在缓存,哪些需要查
|
||||
codeArray.forEach(code => {
|
||||
if (dictCache[code]) {
|
||||
result[code] = dictCache[code];
|
||||
} else {
|
||||
needFetch.push(code);
|
||||
}
|
||||
});
|
||||
|
||||
if (needFetch.length === 0) {
|
||||
dicts.value = result;
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 处理并发合并
|
||||
const fetchKey = needFetch.sort().join(',');
|
||||
if (!pendingPromises.has(fetchKey)) {
|
||||
const p = getDictMap(codes).then(res => {
|
||||
const data = res || [];
|
||||
Object.assign(dictCache, data);
|
||||
pendingPromises.delete(fetchKey);
|
||||
return data;
|
||||
});
|
||||
pendingPromises.set(fetchKey, p);
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const remoteData = await pendingPromises.get(fetchKey);
|
||||
dicts.value = { ...result, ...remoteData };
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchLevel = async (code, parentId) => {
|
||||
if (!code || parentId === undefined) return [];
|
||||
|
||||
// 构造层级缓存 Key:例如 "GENDER_1"
|
||||
const cacheKey = `${code}_${parentId}`;
|
||||
|
||||
if (dictCache[cacheKey]) {
|
||||
return dictCache[cacheKey];
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
// 假设 getDictMapLevel 接受 code 和 parentId
|
||||
const res = await getDictMapLevel(code, parentId);
|
||||
const data = res || [];
|
||||
// 写入全局缓存
|
||||
dictCache[cacheKey] = data;
|
||||
return data;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(fetchDicts);
|
||||
return { dicts, loading,refresh: fetchDicts,fetchLevel };
|
||||
}
|
||||
@@ -302,36 +302,26 @@ export const mockBackendMenuData = [
|
||||
"name": "字典管理",
|
||||
"code": "dict",
|
||||
"icon": "OfficeBuilding",
|
||||
"metadata": null,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"name": "组织管理",
|
||||
"code": "origanization",
|
||||
"icon": "OfficeBuilding",
|
||||
"metadata": null,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"name": "人员管理",
|
||||
"code": "personnel",
|
||||
"icon": "OfficeBuilding",
|
||||
"metadata": null,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"name": "权限管理",
|
||||
"code": "permission",
|
||||
"icon": "OfficeBuilding",
|
||||
"metadata": null,
|
||||
"children": null
|
||||
},
|
||||
{
|
||||
"name": "流程管理",
|
||||
"code": "flow",
|
||||
"icon": "OfficeBuilding",
|
||||
"metadata": null,
|
||||
"children": null
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
@menu-select="handleTopMenuSelect"
|
||||
/>
|
||||
<!-- 右侧用户的内容 -->
|
||||
<rightMenuGroup @on-stage-manage="onStageManage" />
|
||||
<rightMenuGroup @on-stage-manage="(path)=>handleTopMenuSelect(path)" />
|
||||
</el-header>
|
||||
<el-main class="mj-main-backend-content">
|
||||
<router-view />
|
||||
@@ -55,6 +55,7 @@ defineOptions({
|
||||
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
// 响应式断点(小屏阈值,小于此值视为小屏)
|
||||
const BREAKPOINT = 1024;
|
||||
@@ -104,10 +105,7 @@ const backTitle = computed(()=>{
|
||||
return menuList.value.find(itv=>itv.name === 'stage')?.meta?.title || '-';
|
||||
})
|
||||
|
||||
// 后台管理点击获取列表
|
||||
const onStageManage = () =>{
|
||||
selectedTopMenu.value = '/stage';
|
||||
}
|
||||
|
||||
|
||||
const topTitle = computed(() => {
|
||||
return (
|
||||
@@ -165,6 +163,18 @@ const sideMenuList = computed(() => {
|
||||
// 处理顶部菜单选中事件
|
||||
const handleTopMenuSelect = (menuPath: string) => {
|
||||
selectedTopMenu.value = menuPath;
|
||||
const currentModule = menuList.value.find(item => item.path === menuPath);
|
||||
|
||||
if (currentModule && currentModule.children && currentModule.children.length > 0) {
|
||||
const firstChild = currentModule.children[0];
|
||||
const targetPath = firstChild.path.startsWith("/")
|
||||
? firstChild.path
|
||||
: `${currentModule.path}/${firstChild.path}`;
|
||||
router.push(targetPath);
|
||||
selectedActiveMenu.value = targetPath;
|
||||
} else {
|
||||
router.push(menuPath);
|
||||
}
|
||||
};
|
||||
|
||||
// 左侧菜单选中事件
|
||||
@@ -172,23 +182,37 @@ const handleSideMenuSelect = (menuPath: string) => {
|
||||
selectedActiveMenu.value = menuPath;
|
||||
};
|
||||
|
||||
// 高亮当前激活的菜单
|
||||
const activeMenuByUrl = () => {
|
||||
// 1. 获取当前路由路径,例如 "/stage/dict" 或 "/business/customer"
|
||||
const currentPath = route.path;
|
||||
|
||||
// 2. 尝试从 topLevelMenuList 中直接找匹配项
|
||||
let matchedMenu = topLevelMenuList.value.find(menu =>
|
||||
currentPath.startsWith(menu.path)
|
||||
);
|
||||
|
||||
// 3. 如果没找到,且路径以 /stage 开头 那就是后台管理模块
|
||||
if (!matchedMenu && currentPath.startsWith('/stage')) {
|
||||
selectedTopMenu.value = '/stage';
|
||||
}else{
|
||||
// 4. 赋值选中的菜单
|
||||
if (matchedMenu) {
|
||||
selectedTopMenu.value = matchedMenu.path;
|
||||
} else if (topLevelMenuList.value.length > 0) {
|
||||
selectedTopMenu.value = topLevelMenuList.value[0].path;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => route.path, () => {
|
||||
activeMenuByUrl();
|
||||
}, { immediate: true });
|
||||
|
||||
// 初始化:默认选中第一个菜单
|
||||
onMounted(() => {
|
||||
|
||||
const currentRoutePath = router.currentRoute.value.path;
|
||||
const matchedTopMenu = topLevelMenuList.value.find(menu =>
|
||||
currentRoutePath.startsWith(`${menu.path}/`) || currentRoutePath === menu.path
|
||||
);
|
||||
|
||||
if (matchedTopMenu && matchedTopMenu.path) {
|
||||
selectedTopMenu.value = matchedTopMenu.path;
|
||||
} else if (topLevelMenuList.value.length > 0) {
|
||||
// 否则默认选中第一个菜单
|
||||
const firstMenu = topLevelMenuList.value[0];
|
||||
if (firstMenu && firstMenu.path) {
|
||||
selectedTopMenu.value = firstMenu.path;
|
||||
}
|
||||
}
|
||||
activeMenuByUrl();
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
@@ -80,7 +80,7 @@ const handleCommand = (command: string) => {
|
||||
};
|
||||
|
||||
const onStageManage = () => {
|
||||
emits("on-stage-manage");
|
||||
emits("on-stage-manage",'/stage');
|
||||
};
|
||||
|
||||
// 获取当前的用户的数据信息
|
||||
|
||||
@@ -110,19 +110,9 @@
|
||||
}"
|
||||
@click="handleDictStatus(row)"
|
||||
>
|
||||
{{ DictManage.statusDict[row.status] }}
|
||||
{{ dicts.permission_list_enable_disable.find(item=>item.value == row.status)?.label }}
|
||||
</div>
|
||||
</template>
|
||||
<!-- <template #actions="{ row }">
|
||||
<el-button link type="primary" v-if="!hasChild" @click="handleAddNext(row)"
|
||||
>添加二级字段</el-button>
|
||||
<el-button link type="primary" @click="handleEdit(row)"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button link type="danger" @click="handleDelete(row)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template> -->
|
||||
</CommonTable>
|
||||
<!-- 新增字段 -->
|
||||
<dictFieldLevelManage
|
||||
@@ -147,6 +137,8 @@ import CommonTable from "@/components/proTable/index.vue";
|
||||
import dayjs from "dayjs";
|
||||
import dictFieldLevelManage from "./dictFieldLevelManage.vue";
|
||||
import { DictManage } from "@/dict";
|
||||
import { useDict } from '@/hooks/useDictData';
|
||||
const { dicts,refresh } = useDict('permission_list_enable_disable');
|
||||
import {
|
||||
getDictTypeValue,
|
||||
deleteDictTypeValue,
|
||||
|
||||
@@ -18,10 +18,17 @@
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="字段名称:" prop="label">
|
||||
<el-input placeholder="请输入字典名称" v-model="form.label"></el-input>
|
||||
<el-input
|
||||
placeholder="请输入字典名称"
|
||||
v-model="form.label"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="字典值:" prop="value">
|
||||
<el-input placeholder="请输入字典值" v-model="form.value" :disabled="form.id ? true : false"></el-input>
|
||||
<el-input
|
||||
placeholder="请输入字典值"
|
||||
v-model="form.value"
|
||||
:disabled="form.id ? true : false"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="sort">
|
||||
<template #label>
|
||||
@@ -34,12 +41,21 @@
|
||||
</div>
|
||||
</template>
|
||||
<!-- 换成排序的输入框 -->
|
||||
<el-input-number placeholder="请输入排序" v-model="form.sort" :min="0" controls-position="right"></el-input-number>
|
||||
<el-input-number
|
||||
placeholder="请输入排序"
|
||||
v-model="form.sort"
|
||||
:min="0"
|
||||
controls-position="right"
|
||||
></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :value="1">启用</el-radio>
|
||||
<el-radio :value="0">停用</el-radio>
|
||||
<el-radio
|
||||
:value="item.value"
|
||||
v-for="(item, index) in dicts.permission_list_enable_disable"
|
||||
:key="item.value"
|
||||
>{{ item.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注:" prop="remark">
|
||||
@@ -55,7 +71,12 @@
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onCancel">取消</el-button>
|
||||
<el-button @click="onConfirm(ruleFormRef)" type="primary" :loading="loading">确认</el-button>
|
||||
<el-button
|
||||
@click="onConfirm(ruleFormRef)"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
>确认</el-button
|
||||
>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -64,6 +85,8 @@
|
||||
import { reactive, ref, onMounted } from "vue";
|
||||
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
||||
import { saveDictTypeValue, updateDictTypeValue } from "@/api/stage/dict";
|
||||
import { useDict } from "@/hooks/useDictData";
|
||||
const { dicts, refresh } = useDict("permission_list_enable_disable");
|
||||
defineOptions({ name: "DictFieldLevelManage" });
|
||||
const loading = ref(false);
|
||||
const {
|
||||
@@ -74,12 +97,10 @@ const {
|
||||
} = defineProps<{
|
||||
dialogVisible: boolean;
|
||||
row?: any;
|
||||
parentId?: string|number;
|
||||
parentId?: string | number;
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:dialogVisible", value: boolean): void;
|
||||
(e: "confirm-success"): void;
|
||||
@@ -90,22 +111,26 @@ const form = reactive({
|
||||
label: "",
|
||||
value: "",
|
||||
sort: 0,
|
||||
status:1,
|
||||
remark:''
|
||||
status: '1',
|
||||
remark: "",
|
||||
});
|
||||
|
||||
// 监听组件中传递的数据-然后进行复制操作
|
||||
watch(()=>row,(newRow)=>{
|
||||
if (newRow && Object.keys(newRow).length > 0) {
|
||||
Object.assign(form, newRow);
|
||||
watch(
|
||||
() => row,
|
||||
(newRow) => {
|
||||
if (newRow && Object.keys(newRow).length > 0) {
|
||||
Object.assign(form, newRow,{status:String(newRow.status)});
|
||||
}
|
||||
},{deep:true})
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const rules = reactive({
|
||||
label: [{ required: true, message: "请输入字段名称", trigger: "blur" }],
|
||||
value: [{ required: true, message: "请输入字典值", trigger: "blur" }],
|
||||
sort: [{ required: true, message: "请输入排序", trigger: "blur" }],
|
||||
remark:[{ required: false, message: "请输入备注", trigger: "blur" }]
|
||||
remark: [{ required: false, message: "请输入备注", trigger: "blur" }],
|
||||
});
|
||||
|
||||
// 确定
|
||||
@@ -114,16 +139,18 @@ const onConfirm = async (formEl: FormInstance | undefined) => {
|
||||
await formEl.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
console.log("获取外部的数据信息:",form,parentId)
|
||||
console.log("获取外部的数据信息:", form, parentId);
|
||||
// 如果是一级新增字段就是parentId为0 如果是添加二级字段就是parentId为父级的id
|
||||
try {
|
||||
const response = row.id ? await updateDictTypeValue(parentId,row.id,form) : await saveDictTypeValue(parentId,form);
|
||||
ElMessage.success(row.id ? '修改成功' : '新增成功');
|
||||
const response = row.id
|
||||
? await updateDictTypeValue(parentId, row.id, form)
|
||||
: await saveDictTypeValue(parentId, form);
|
||||
ElMessage.success(row.id ? "修改成功" : "新增成功");
|
||||
onCancel();
|
||||
emit('confirm-success');
|
||||
emit("confirm-success");
|
||||
} catch (error) {
|
||||
console.log('error',error);
|
||||
} finally{
|
||||
console.log("error", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -34,8 +34,12 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :value="1">启用</el-radio>
|
||||
<el-radio :value="0">停用</el-radio>
|
||||
<el-radio
|
||||
:value="item.value"
|
||||
v-for="(item, index) in dicts.permission_list_enable_disable"
|
||||
:key="item.value"
|
||||
>{{ item.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注:">
|
||||
@@ -60,6 +64,8 @@
|
||||
import { reactive, ref, onMounted } from "vue";
|
||||
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
||||
import { addDictValue, updateDictValue } from "@/api/stage/dict";
|
||||
import { useDict } from "@/hooks/useDictData";
|
||||
const { dicts, refresh } = useDict("permission_list_enable_disable");
|
||||
defineOptions({ name: "DictManage" });
|
||||
const loading = ref(false);
|
||||
const {
|
||||
@@ -80,14 +86,14 @@ const ruleFormRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
name: "",
|
||||
key: "",
|
||||
status: 1,
|
||||
status: '1',
|
||||
remark: "",
|
||||
});
|
||||
|
||||
// 监听组件中传递的数据-然后进行复制操作
|
||||
watch(()=>row,(newRow)=>{
|
||||
if (newRow && Object.keys(newRow).length > 0) {
|
||||
Object.assign(form, newRow);
|
||||
Object.assign(form, newRow,{status:String(newRow.status)});
|
||||
}
|
||||
},{deep:true})
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<el-option
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
v-for="(item, index) in DictManage.statusOptions"
|
||||
v-for="(item, index) in dicts.permission_list_enable_disable"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
@@ -89,19 +89,9 @@
|
||||
}"
|
||||
@click="handleDictStatus(row)"
|
||||
>
|
||||
{{ DictManage.statusDict[row.status] }}
|
||||
{{ dicts.permission_list_enable_disable.find(item=>item.value == row.status)?.label }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- <template #actions="{ row }">
|
||||
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button link type="primary" @click="handlefieldsConfig(row)"
|
||||
>字段配置</el-button
|
||||
>
|
||||
<el-button link type="danger" @click="handleDelete(row)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template> -->
|
||||
</CommonTable>
|
||||
|
||||
<!-- 新增-编辑字典弹窗 -->
|
||||
@@ -130,6 +120,9 @@ import { DictManage } from "@/dict";
|
||||
import { formatIndex } from "@/utils/utils";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
import { useDict } from '@/hooks/useDictData';
|
||||
const { dicts,refresh } = useDict('permission_list_enable_disable');
|
||||
|
||||
defineOptions({ name: "Dictionary" });
|
||||
const fieldsConfigRef = ref(null);
|
||||
const dictTableRef = ref(null);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="full-width-radio">
|
||||
<BaseSegmented
|
||||
v-model="form.type"
|
||||
:options="PermissionManage.roleTypeOptions"
|
||||
:options="dicts.permission_role_type"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
@@ -117,7 +117,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BaseSegmented from "./baseSegmented.vue";
|
||||
import { PermissionManage } from "@/dict";
|
||||
import { Loading } from "@element-plus/icons-vue";
|
||||
import type { FormInstance, FormRules } from "element-plus";
|
||||
import {
|
||||
@@ -127,6 +126,8 @@ import {
|
||||
} from "@/api/stage/permission/index.ts";
|
||||
import { getEnterprisePosition } from "@/api/stage/organization";
|
||||
import { useSelectLoadMore } from "@/hooks/useSelectLoadMore";
|
||||
import { useDict } from '@/hooks/useDictData';
|
||||
const { dicts,refresh } = useDict('permission_role_type');
|
||||
defineOptions({ name: "addRoles" });
|
||||
const dialogVisible = defineModel("visible", { type: Boolean, default: false });
|
||||
const props = defineProps({
|
||||
@@ -135,7 +136,6 @@ const props = defineProps({
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
options,
|
||||
remoteLoading,
|
||||
@@ -219,6 +219,11 @@ const handleSubmit = (formEl: FormInstance | undefined) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化刷新角色类型值
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
<template>
|
||||
<div class="permission-scroll-area">
|
||||
<div v-for="group in permissions" :key="group.id" class="permission-group">
|
||||
<div v-for="group in menuList" :key="group.id" class="permission-group">
|
||||
<div class="group-header">
|
||||
<el-checkbox
|
||||
v-model="group.allSelected"
|
||||
v-model="group.selected"
|
||||
:indeterminate="group.isIndeterminate"
|
||||
@change="(val) => handleGroupCheckAll(group, val)"
|
||||
>
|
||||
<span class="group-title">{{ group.name }}</span>
|
||||
<span class="group-count"
|
||||
>({{ getCheckedCount(group) }}/{{ group.children.length }})</span
|
||||
>
|
||||
<span class="group-count" v-if="group.children">({{ getCheckedCount(group) }}/{{ group.children.length }})</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
||||
@@ -18,23 +16,19 @@
|
||||
<div v-for="row in group.children" :key="row.id" class="permission-row">
|
||||
<div class="row-label">
|
||||
<el-checkbox
|
||||
v-model="row.checked"
|
||||
v-model="row.selected"
|
||||
:indeterminate="row.isIndeterminate"
|
||||
@change="() => handleRowChange(group, row)"
|
||||
>
|
||||
{{ row.name }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="row-actions">
|
||||
<el-checkbox-group
|
||||
v-model="row.actions"
|
||||
:disabled="!row.checked"
|
||||
@change="() => handleActionChange(group, row)"
|
||||
>
|
||||
<el-checkbox value="add">新增</el-checkbox>
|
||||
<el-checkbox value="delete" class="is-danger">删除</el-checkbox>
|
||||
<el-checkbox value="import">导入</el-checkbox>
|
||||
<el-checkbox value="export">导出</el-checkbox>
|
||||
<el-checkbox :value="check.id" v-for="(check,checkIndex) in row.operations" :class="check.code.search('delete') > -1 ? 'is-danger' : ''">{{ check.name }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,123 +37,55 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted } from "vue";
|
||||
defineOptions({name: "baseSegmentedMenu"});
|
||||
|
||||
// 模拟数据结构
|
||||
const permissions = reactive([
|
||||
{
|
||||
id: 1,
|
||||
name: "项目管理",
|
||||
allSelected: false,
|
||||
isIndeterminate: true,
|
||||
children: [
|
||||
{
|
||||
id: 11,
|
||||
name: "需求管理",
|
||||
checked: true,
|
||||
actions: ["add", "delete", "import", "export"],
|
||||
},
|
||||
{ id: 12, name: "项目管理", checked: false, actions: [] },
|
||||
{ id: 13, name: "任务管理", checked: false, actions: [] },
|
||||
],
|
||||
const props = defineProps({
|
||||
menuList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "招聘管理",
|
||||
allSelected: false,
|
||||
isIndeterminate: false,
|
||||
children: [
|
||||
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "招聘管理",
|
||||
allSelected: false,
|
||||
isIndeterminate: false,
|
||||
children: [
|
||||
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "招聘管理",
|
||||
allSelected: false,
|
||||
isIndeterminate: false,
|
||||
children: [
|
||||
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "招聘管理",
|
||||
allSelected: false,
|
||||
isIndeterminate: false,
|
||||
children: [
|
||||
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "招聘管理",
|
||||
allSelected: false,
|
||||
isIndeterminate: false,
|
||||
children: [
|
||||
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "招聘管理",
|
||||
allSelected: false,
|
||||
isIndeterminate: false,
|
||||
children: [
|
||||
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// 获取已选中的子项数量
|
||||
const getCheckedCount = (group) =>
|
||||
group.children.filter((item) => item.checked).length;
|
||||
const getCheckedCount = (group) =>{
|
||||
return (group.children || []).filter((item) => item.selected).length;
|
||||
}
|
||||
|
||||
// 处理一级全选
|
||||
const handleGroupCheckAll = (group, val) => {
|
||||
group.children.forEach((row) => {
|
||||
row.checked = val;
|
||||
row.actions = val ? ["add", "delete", "import", "export"] : [];
|
||||
row.selected = val;
|
||||
row.actions = val ? row.operations.map((item) => item.id) : [];
|
||||
});
|
||||
group.isIndeterminate = false;
|
||||
};
|
||||
|
||||
// 处理二级勾选
|
||||
const handleRowChange = (group, row) => {
|
||||
if (!row.checked) row.actions = [];
|
||||
row.actions = row.selected ? row.operations.map((item) => item.id) : [];
|
||||
updateGroupStatus(group);
|
||||
};
|
||||
|
||||
// 处理三级按钮勾选
|
||||
const handleActionChange = (group, row) => {
|
||||
if (row.actions.length > 0) row.checked = true;
|
||||
if (row.actions.length > 0) row.selected = true;
|
||||
const allActions = row.operations && row.operations.length;
|
||||
const checkedActionsCount = row.actions ? row.actions.length : 0;
|
||||
row.isIndeterminate = checkedActionsCount > 0 && checkedActionsCount < allActions;
|
||||
row.selected = checkedActionsCount > 0 && checkedActionsCount === allActions;
|
||||
updateGroupStatus(group);
|
||||
};
|
||||
|
||||
// 更新父级的半选/全选状态
|
||||
const updateGroupStatus = (group) => {
|
||||
const checkedCount = getCheckedCount(group);
|
||||
group.allSelected = checkedCount === group.children.length;
|
||||
group.isIndeterminate =
|
||||
checkedCount > 0 && checkedCount < group.children.length;
|
||||
const children = group.children || [];
|
||||
const total = children.length;
|
||||
const fullyCheckedCount = children.filter(c => c.selected && !c.isIndeterminate).length;
|
||||
const anyCheckedCount = children.filter(c => c.selected || c.isIndeterminate).length;
|
||||
group.selected = total > 0 && fullyCheckedCount === total;
|
||||
group.isIndeterminate = !group.selected && anyCheckedCount > 0;
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use './baseSegmentedPermission.scss' as *;
|
||||
|
||||
</style>
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { useFormItem } from "element-plus";
|
||||
const props = defineProps({
|
||||
modelValue: [String, Number],
|
||||
@@ -31,6 +29,11 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
watch(() => props.options, (val) => {
|
||||
// 1. 获取父组件的值
|
||||
console.log("父组件的值111:", val);
|
||||
},{deep:true});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
// 生成唯一ID,防止页面存在多个组件时 name 冲突
|
||||
@@ -42,7 +45,7 @@ const handleChange = (val) => {
|
||||
emit("update:modelValue", val);
|
||||
emit("change", val);
|
||||
|
||||
// 3. 关键:通知 el-form-item 进行校验
|
||||
// 通知 el-form-item 进行校验
|
||||
if (formItem) {
|
||||
formItem.validate("change").catch(() => {});
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
v-for="mod in modules"
|
||||
:key="mod.id"
|
||||
class="module-card"
|
||||
:class="{ 'is-active': modelValue === mod.id }"
|
||||
:class="{ 'is-active': activeModules === mod.id }"
|
||||
@click="handleModuleChange(mod.id)"
|
||||
>
|
||||
{{ mod.name }}
|
||||
@@ -13,23 +13,24 @@
|
||||
</div>
|
||||
|
||||
<div class="field-group-list">
|
||||
<template v-if="fieldGroupsChildren.length">
|
||||
<div
|
||||
v-for="(group, index) in fieldGroups"
|
||||
:key="group.groupId"
|
||||
v-for="(group, index) in fieldGroupsChildren"
|
||||
:key="group.id"
|
||||
class="group-container"
|
||||
:class="{ 'is-collapsed': group.collapsed }"
|
||||
:class="{ 'is-collapsed': collapsedIds.has(group.id) }"
|
||||
>
|
||||
<div class="group-header" :class="{ 'is-collapsed': group.collapsed }">
|
||||
<div class="group-header" :class="{ 'is-collapsed': collapsedIds.has(group.id) }">
|
||||
<div class="header-left">
|
||||
<el-icon><List /></el-icon>
|
||||
<span class="group-title">{{ group.groupName }}</span>
|
||||
<span class="group-title">{{ group.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<span class="quick-set-label">快速设置:</span>
|
||||
<div class="quick-actions">
|
||||
<span
|
||||
v-for="opt in quickOptions"
|
||||
v-for="opt in dicts.permission_setting_status"
|
||||
:key="opt.value"
|
||||
class="quick-btn"
|
||||
:class="{ 'is-disabled': isProcessing }"
|
||||
@@ -38,7 +39,7 @@
|
||||
{{ opt.label }}
|
||||
</span>
|
||||
</div>
|
||||
<el-icon class="collapse-icon" @click.stop="toggleGroup(index)"
|
||||
<el-icon class="collapse-icon" @click.stop="toggleGroup(group)"
|
||||
><ArrowDown
|
||||
/></el-icon>
|
||||
</div>
|
||||
@@ -56,15 +57,22 @@
|
||||
</div>
|
||||
<div class="field-ctrl">
|
||||
<el-select v-model="field.permission">
|
||||
<el-option label="可查看" value="read" />
|
||||
<el-option label="可编辑" value="edit" />
|
||||
<el-option label="不可查看" value="none" />
|
||||
<el-option
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
v-for="(
|
||||
dict, dicIndex
|
||||
) in dicts.permission_setting_status"
|
||||
:key="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty description="暂无数据~" v-else/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -74,26 +82,61 @@ import { ref, computed } from "vue";
|
||||
import { List, Document, ArrowDown } from "@element-plus/icons-vue";
|
||||
|
||||
const props = defineProps({
|
||||
// 模块列表
|
||||
modules: { type: Array, default: () => [] },
|
||||
// 初始选中的模块ID
|
||||
modelValue: [String, Number],
|
||||
fieldGroups: {
|
||||
dicts:{
|
||||
type:Object,
|
||||
default:()=>({})
|
||||
},
|
||||
fieldGroupsList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "update:fieldGroups", "change"]);
|
||||
const emit = defineEmits(["update:fieldGroupsList"]);
|
||||
const isProcessing = ref(false);
|
||||
const processingGroupId = ref(null);
|
||||
const currentActiveId = ref(null);
|
||||
const collapsedIds = ref(new Set()); //可折叠的集合
|
||||
// 渲染顶部的数据
|
||||
const modules = computed(() => {
|
||||
return props.fieldGroupsList.map((group) => {
|
||||
return {
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// 快速设置选项
|
||||
const quickOptions = [
|
||||
{ label: "可查看", value: "read" },
|
||||
{ label: "可编辑", value: "edit" },
|
||||
{ label: "不可查看", value: "none" },
|
||||
];
|
||||
// 动态渲染activeModules的值
|
||||
const activeModules = computed(() => currentActiveId.value);
|
||||
|
||||
// 动态匹配对应的下级数据
|
||||
const fieldGroupsChildren = computed({
|
||||
get: () => {
|
||||
if (!currentActiveId.value) return [];
|
||||
return (
|
||||
props.fieldGroupsList.find((group) => group.id === currentActiveId.value)
|
||||
?.fieldGroups || []
|
||||
);
|
||||
},
|
||||
set: () => {},
|
||||
});
|
||||
|
||||
// 监听值的变化
|
||||
watch(
|
||||
() => props.fieldGroupsList,
|
||||
(newList) => {
|
||||
if (newList.length > 0) {
|
||||
const exists = newList.find((item) => item.id === currentActiveId.value);
|
||||
if (!exists) {
|
||||
currentActiveId.value = newList[0].id;
|
||||
}
|
||||
} else {
|
||||
currentActiveId.value = null;
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
const batchSetPermissionChunked = (group, type) => {
|
||||
if (isProcessing.value) return;
|
||||
@@ -105,6 +148,8 @@ const batchSetPermissionChunked = (group, type) => {
|
||||
|
||||
isProcessing.value = true;
|
||||
processingGroupId.value = group.groupId;
|
||||
// 同步更新permission的数据
|
||||
group.permission = type;
|
||||
|
||||
const runChunk = () => {
|
||||
const nextLimit = Math.min(currentIndex + chunkSize, total);
|
||||
@@ -120,31 +165,27 @@ const batchSetPermissionChunked = (group, type) => {
|
||||
} else {
|
||||
isProcessing.value = false;
|
||||
processingGroupId.value = null;
|
||||
emit("update:fieldGroups", [...props.fieldGroups]);
|
||||
emit("update:fieldGroupsList", [...props.fieldGroupsList]);
|
||||
}
|
||||
};
|
||||
|
||||
runChunk();
|
||||
};
|
||||
|
||||
// 切换折叠逻辑
|
||||
const toggleGroup = (index) => {
|
||||
console.log("");
|
||||
props.fieldGroups[index].collapsed = !props.fieldGroups[index].collapsed;
|
||||
// 切换折叠面板
|
||||
const toggleGroup = (group) => {
|
||||
if (collapsedIds.value.has(group.id)) {
|
||||
collapsedIds.value.delete(group.id);
|
||||
} else {
|
||||
collapsedIds.value.add(group.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 切换顶部权限的模块
|
||||
const handleModuleChange = (id) => {
|
||||
emit("update:modelValue", id);
|
||||
emit("change", id);
|
||||
currentActiveId.value = id;
|
||||
};
|
||||
|
||||
// 批量设置权限逻辑
|
||||
const batchSetPermission = (group, type) => {
|
||||
group.fields.forEach((field) => {
|
||||
field.permission = type;
|
||||
});
|
||||
emit("update:fieldGroups", [...props.fieldGroups]);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -41,11 +41,11 @@
|
||||
<div
|
||||
class="mj-status-dot"
|
||||
:style="{
|
||||
'--data-status-color': PermissionManage.roleDictColor[row.status],
|
||||
'--data-status-color': DictManage.statusDictColor[row.status],
|
||||
}"
|
||||
@click="handleDictStatus(row)"
|
||||
>
|
||||
{{ PermissionManage.roleDict[row.status] }}
|
||||
{{ dicts.permission_list_enable_disable.find(item=>item.value == row.status)?.label }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -78,11 +78,11 @@
|
||||
<add-roles
|
||||
v-model:visible="showRoles"
|
||||
@on-success="onRefresh"
|
||||
:detail-id="detailId"
|
||||
:detail-id="selectMember?.id"
|
||||
/>
|
||||
|
||||
<!-- 权限抽屉 -->
|
||||
<permission-drawer v-model:visible="showPermission" />
|
||||
<permission-drawer v-model:visible="showPermission" :checkAuth="selectMember"/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import dayjs from "dayjs";
|
||||
@@ -90,7 +90,8 @@ import CommonTable from "@/components/proTable/index.vue";
|
||||
import memberSelector from "@/components/memberSelector/index.vue";
|
||||
import addRoles from "./addRoles.vue";
|
||||
import permissionDrawer from "./permissionDrawer.vue";
|
||||
import { PermissionManage } from "@/dict";
|
||||
import { DictManage } from "@/dict";
|
||||
import { useDict } from '@/hooks/useDictData';
|
||||
import { formatIndex } from "@/utils/utils";
|
||||
import {
|
||||
getRoleList,
|
||||
@@ -106,6 +107,7 @@ import {
|
||||
} from "@/api/stage/permission/index.ts";
|
||||
import { useLocalManager } from "@/hooks/useLocalManager";
|
||||
defineOptions({ name: "PermissionManagement" });
|
||||
const { dicts,refresh } = useDict('permission_list_enable_disable');
|
||||
const activeTab = ref(1);
|
||||
const tableRef = ref(null);
|
||||
const searchVal = ref("");
|
||||
@@ -114,7 +116,6 @@ const memberList = ref([]); // 获取成员角色列表
|
||||
const total = ref(0);
|
||||
const showMember = ref(false);
|
||||
const showRoles = ref(false);
|
||||
const detailId = ref("");
|
||||
const showPermission = ref(false);
|
||||
const selectMember = ref({}); //当前选择的成员数量数据
|
||||
const tabList = [
|
||||
@@ -208,13 +209,14 @@ const roleColumns = [
|
||||
permission: ["*"],
|
||||
onClick: (row) => {
|
||||
showPermission.value = true;
|
||||
selectMember.value = {...row};
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "复制",
|
||||
type: "default",
|
||||
link: true,
|
||||
permission: ["edit"],
|
||||
permission: ["*"],
|
||||
onClick: async (row) => {
|
||||
try {
|
||||
await copyRolePermission(row.id);
|
||||
@@ -229,17 +231,17 @@ const roleColumns = [
|
||||
label: "编辑",
|
||||
type: "default",
|
||||
link: true,
|
||||
permission: ["config"],
|
||||
permission: ["*"],
|
||||
onClick: (row) => {
|
||||
showRoles.value = true;
|
||||
detailId.value = row.id;
|
||||
selectMember.value = {...row};
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "删除",
|
||||
type: "danger",
|
||||
link: true,
|
||||
permission: ["delete"],
|
||||
permission: ["*"],
|
||||
disabledFn: (row) => {
|
||||
return row.type !== "SYSTEM";
|
||||
},
|
||||
@@ -278,7 +280,7 @@ const tableColumns = computed(() => {
|
||||
// 当前成员数量
|
||||
const addMember = (row) => {
|
||||
showMember.value = true;
|
||||
selectMember.value = row;
|
||||
selectMember.value = {...row};
|
||||
getMemberList({ id: row.id });
|
||||
};
|
||||
|
||||
@@ -309,7 +311,7 @@ watch(activeTab, () => {
|
||||
tableRef.value && tableRef.value.reset();
|
||||
});
|
||||
|
||||
// 权限
|
||||
// 启用-停用
|
||||
const handleDictStatus = async (row) => {
|
||||
try {
|
||||
row.status === 1 ? await disableRole(row.id) : await enableRole(row.id);
|
||||
@@ -349,7 +351,7 @@ const getMemberList = async (params) => {
|
||||
|
||||
// 获取
|
||||
const checkRolesText = computed(() => {
|
||||
const btnText = {
|
||||
const btnText = {
|
||||
1: "新增角色",
|
||||
}[activeTab.value];
|
||||
|
||||
@@ -366,10 +368,15 @@ const checkRolesText = computed(() => {
|
||||
// 新增角色
|
||||
const addBtnClick = () => {
|
||||
if (activeTab.value === 1) {
|
||||
detailId.value = "";
|
||||
showRoles.value = true;
|
||||
selectMember.value = {id:''}
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.mj-permission-management {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
/></el-icon>
|
||||
</div>
|
||||
<div class="sub-title">
|
||||
正在为:<span class="sub-roles">[超级管理员] </span>分配系统访问权限
|
||||
正在为:<span class="sub-roles">[{{ personAuth }}] </span>分配系统访问权限
|
||||
</div>
|
||||
</div>
|
||||
<!-- 内容 -->
|
||||
@@ -29,14 +29,12 @@
|
||||
<div class="permission-container">
|
||||
<el-tabs v-model="activeTab" class="custom-permission-tabs">
|
||||
<el-tab-pane label="菜单权限" name="menu">
|
||||
<base-segment-menu></base-segment-menu>
|
||||
<base-segment-menu :menuList="menuList"></base-segment-menu>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="字段权限" name="field">
|
||||
<fieldPermissionManager
|
||||
v-model="currentModule"
|
||||
:modules="moduleList"
|
||||
v-model:field-groups="fieldsList"
|
||||
@change="onModuleTabChange"
|
||||
:dicts="dicts"
|
||||
v-model:field-groups-list="fieldsList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
@@ -49,7 +47,7 @@
|
||||
<div class="stats-info"></div>
|
||||
<div class="actions">
|
||||
<el-button link @click="drawerVisible = false">取消</el-button>
|
||||
<el-button type="primary" class="btn-confirm">确认应用</el-button>
|
||||
<el-button type="primary" class="btn-confirm" @click="handleSavePermission" :loading="loading">确认应用</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -59,44 +57,160 @@
|
||||
import { Close } from "@element-plus/icons-vue";
|
||||
import baseSegmentMenu from "./baseSegmentMenu.vue";
|
||||
import fieldPermissionManager from "./fieldPermissionManager.vue";
|
||||
import { saveRolePermission,getRolePermission } from '@/api/stage/permission';
|
||||
import { useDict } from '@/hooks/useDictData';
|
||||
const { dicts,refresh } = useDict('permission_setting_status');
|
||||
|
||||
interface permissionProps {
|
||||
permissions:Record<string,any>[];
|
||||
}
|
||||
defineOptions({ name: "PermissionDrawer" });
|
||||
|
||||
const props = defineProps({
|
||||
checkAuth:{
|
||||
type: Object,
|
||||
default: ()=>({}),
|
||||
}
|
||||
});
|
||||
const drawerVisible = defineModel("visible", { type: Boolean, default: false });
|
||||
const activeTab = ref("menu");
|
||||
const currentModule = ref(4); // 默认选中“商机详情”
|
||||
const fieldsList = ref([
|
||||
{
|
||||
groupId: "g1",
|
||||
groupName: "文本",
|
||||
fields: [
|
||||
{ id: "f1", name: "商机名称", permission: "read", collapsed: false },
|
||||
{ id: "f2", name: "商机需求描述", permission: "read", collapsed: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
groupId: "g2",
|
||||
groupName: "选择",
|
||||
fields: [
|
||||
{ id: "f3", name: "合作类型", permission: "read", collapsed: false },
|
||||
{ id: "f4", name: "需求品类", permission: "read", collapsed: false },
|
||||
{ id: "f5", name: "线索/商机渠道", permission: "read", collapsed: false },
|
||||
],
|
||||
},
|
||||
]);
|
||||
const moduleList = [
|
||||
{ id: 1, name: "线索详情" },
|
||||
{ id: 2, name: "客户详情" },
|
||||
{ id: 3, name: "工作室详情" },
|
||||
{ id: 4, name: "商机详情" },
|
||||
{ id: 5, name: "合同详情" },
|
||||
{ id: 6, name: "项目详情" },
|
||||
// ...以此类推
|
||||
];
|
||||
const activeTab = ref<string>("menu");
|
||||
const currentModule = ref<number>(4); // 当前高亮的模块
|
||||
const menuList = ref([]);
|
||||
const loading = ref<boolean>(false);
|
||||
// 动态展示当前人员名称
|
||||
const personAuth = computed(() => {
|
||||
return props.checkAuth.name;
|
||||
});
|
||||
|
||||
const onModuleTabChange = (id) => {
|
||||
console.log("切换到模块:", id);
|
||||
// 这里可以调用接口更新 currentFields 的数据
|
||||
// 动态的数据
|
||||
const fieldsList = computed({
|
||||
get:()=>{
|
||||
const activeFields = [];
|
||||
menuList.value.forEach(group => {
|
||||
(group.children || []).forEach(row => {
|
||||
if (row.selected) {
|
||||
activeFields.push(row); // 将包含 fields 的整个对象传过去
|
||||
}
|
||||
});
|
||||
});
|
||||
return activeFields;
|
||||
},
|
||||
set:()=>{}
|
||||
})
|
||||
|
||||
// 获取角色详情数据
|
||||
const roleDetail = async (roleId) =>{
|
||||
try {
|
||||
const response = await getRolePermission(roleId);
|
||||
menuList.value = response.map(group => {
|
||||
const checkedGroup = (group.children || []).filter(c => c.selected);
|
||||
const checkedCount = checkedGroup.length;
|
||||
return {
|
||||
...group,
|
||||
isIndeterminate: checkedCount > 0 && checkedCount < group.children.length,
|
||||
children:(group.children||[]).map(child=>{
|
||||
const actions = child.selected ? child.operations.filter(action => action.selected).map(action => action.id) : [];
|
||||
return {
|
||||
...child,
|
||||
isIndeterminate: actions.length > 0 && actions.length < child.operations.length,
|
||||
actions,
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('error',error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 非阻塞提取权限载荷
|
||||
* @param {Array} menuList 原始响应式数据
|
||||
*/
|
||||
const extractPermissionPayload = async (menuList) => {
|
||||
// 使用深拷贝,避免清洗过程中由于响应式追踪导致的 UI 卡顿
|
||||
const sourceData = JSON.parse(JSON.stringify(menuList));
|
||||
const result = [];
|
||||
// 辅助函数:处理单个功能节点(Leaf Node)
|
||||
const formatLeafNode = (node) => {
|
||||
const operationIds = (node.actions || []);
|
||||
const fieldPermissions = (node.fieldGroups || []).map(group => ({
|
||||
groupId: group.id,
|
||||
permission: group.permission,
|
||||
fields: (group.fields || []).map(field => ({
|
||||
fieldId: field.id,
|
||||
permission: field.permission
|
||||
}))
|
||||
}));
|
||||
|
||||
return {
|
||||
leafNodeId: node.id,
|
||||
operationIds: operationIds,
|
||||
fieldPermissions: fieldPermissions
|
||||
};
|
||||
};
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let i = 0;
|
||||
const chunk = () => {
|
||||
const startTime = performance.now();
|
||||
// 保持每一片执行时间在 16ms 以内(一帧的时间),确保不卡顿
|
||||
while (i < sourceData.length && (performance.now() - startTime) < 16) {
|
||||
const group = sourceData[i];
|
||||
|
||||
// 情况 A: 有二级菜单 (如商机管理)
|
||||
if (group.children && group.children.length > 0) {
|
||||
group.children.forEach(row => {
|
||||
// 只要有选中的操作或二级被选中,就视为有效节点
|
||||
if (row.selected || (row.actions && row.actions.length > 0)) {
|
||||
result.push(formatLeafNode(row));
|
||||
}
|
||||
});
|
||||
}
|
||||
// 情况 B: 一级就是功能节点 (如团队管理,无children有operations)
|
||||
else {
|
||||
if (group.selected || group.actions && group.actions.length > 0) {
|
||||
result.push(formatLeafNode(group));
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i < sourceData.length) {
|
||||
requestAnimationFrame(chunk);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
};
|
||||
|
||||
chunk();
|
||||
});
|
||||
};
|
||||
watch(()=>menuList.value,(val)=>{
|
||||
// console.log('获取数据动态变化',val);
|
||||
},{deep:true})
|
||||
// 保存权限
|
||||
const handleSavePermission = async () =>{
|
||||
try {
|
||||
loading.value = true;
|
||||
const finalPayload = await extractPermissionPayload(menuList.value);
|
||||
const res = await saveRolePermission({roleId:props.checkAuth.id,permissions:finalPayload});
|
||||
ElMessage.success('操作成功');
|
||||
drawerVisible.value = false;
|
||||
} catch (error) {
|
||||
console.log('save error',error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 监听传入的id值的变化
|
||||
watch(drawerVisible,(newVisible)=>{
|
||||
if(newVisible){
|
||||
const newRoleId = props.checkAuth.id;
|
||||
roleDetail(newRoleId);
|
||||
refresh();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use "./baseSegmentedPermission.scss" as *;
|
||||
|
||||
33
src/router/generateFinalMenu.ts
Normal file
33
src/router/generateFinalMenu.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
import { componentMap } from "./routeMap";
|
||||
/**
|
||||
* 组装函数:将后端原始嵌套树重组为前端要求的骨架结构
|
||||
* @param backendTree 后端返回的原始数据 (包含 backend, business 等顶层节点的树)
|
||||
*/
|
||||
export const transformBackendTree = (backendTree: any[]): any[] => {
|
||||
if (!backendTree || !Array.isArray(backendTree)) return [];
|
||||
|
||||
// 排序操作
|
||||
const sortedTree = [...backendTree].sort((a, b) => {
|
||||
const sortA = a.sort ?? 999; // 兜底值,防止没有 sort 字段
|
||||
const sortB = b.sort ?? 999;
|
||||
return sortA - sortB;
|
||||
});
|
||||
return sortedTree.map(item => {
|
||||
// 2. 构造当前节点
|
||||
const newNode: any = {
|
||||
...item,
|
||||
code: componentMap[item.code],
|
||||
originalCode: item.code, // 保留一份原始 code 备用
|
||||
};
|
||||
|
||||
// 3. 递归处理 children
|
||||
if (item.children && item.children.length > 0) {
|
||||
newNode.children = transformBackendTree(item.children);
|
||||
} else {
|
||||
newNode.children = null;
|
||||
}
|
||||
|
||||
return newNode;
|
||||
});
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import TokenManager from "@/utils/storage";
|
||||
|
||||
import Login from "@/pages/Login/index.vue";
|
||||
import HomeView from "@/pages/Layout/index.vue";
|
||||
import { transformBackendTree } from './generateFinalMenu';
|
||||
import { mockBackendMenuData } from "@/mock/menu";
|
||||
const tokenManager = TokenManager.getInstance();
|
||||
// 基础路由(不需要权限验证)
|
||||
@@ -161,30 +162,9 @@ const addDynamicRoutes = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// 从后端获取路由菜单数据 (这边需要区分 后台的菜单 和用户的菜单)
|
||||
let allRoutes: any[] = [];
|
||||
if (userStore.isBackendUser) {
|
||||
const backendResponses = await getUserInfo();
|
||||
console.log("获取当前的菜单数据信息:",backendResponses);
|
||||
const backendResponse = [];
|
||||
allRoutes = [
|
||||
{
|
||||
code: "stage",
|
||||
name: "管理中心",
|
||||
icon: "",
|
||||
meta: {
|
||||
title: "管理中心",
|
||||
},
|
||||
children: Object.keys(backendResponse).length
|
||||
? backendResponse
|
||||
: mockBackendMenuData,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
// TODO:获取用户端的数据信息
|
||||
// response = await getUserMenus();
|
||||
allRoutes = [];
|
||||
}
|
||||
const { modules:backendRawData } = await getUserInfo();
|
||||
const allRoutes = transformBackendTree(backendRawData);
|
||||
// console.log('获取最终渲染的菜单数据格式:',allRoutes);
|
||||
if (allRoutes) {
|
||||
// 转换路由数据
|
||||
const dynamicRoutes = transformRoutes(
|
||||
|
||||
22
src/router/routeMap.ts
Normal file
22
src/router/routeMap.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
const stageRoute = {
|
||||
backend: "stage",
|
||||
flow: "flow",
|
||||
origanization: "origanization",
|
||||
personnel: "personnel",
|
||||
permission: "permission",
|
||||
dict: "dict",
|
||||
}
|
||||
|
||||
const businessRoute = {
|
||||
business: "businessManage",
|
||||
"business.customer": "customerManage",
|
||||
"business.game_studio":"customerManage",
|
||||
"business.opportunity":"customerManage",
|
||||
}
|
||||
|
||||
|
||||
export const componentMap: Record<string, string> = {
|
||||
...stageRoute,
|
||||
...businessRoute
|
||||
};
|
||||
Reference in New Issue
Block a user