style: 移除多余空行并统一代码格式

修复代码中的多余空行问题,统一字符串引号使用双引号,调整对象和数组的格式,优化代码缩进和换行,提升代码可读性和一致性
This commit is contained in:
zhangtao
2025-11-14 01:45:22 +08:00
parent 66c345d590
commit d588abb386
100 changed files with 9192 additions and 5181 deletions
+13 -30
View File
@@ -1,5 +1,5 @@
// 问候语:根据当前小时返回不同问候语
export function greetings () {
export function greetings() {
// 当前时间(用于计算问候语)
const currentDate = new Date();
const hours = currentDate.getHours();
@@ -16,7 +16,7 @@ export function greetings () {
} else {
return "偷偷向银河要了一把碎星,只等你闭上眼睛撒入你的梦中,晚安🌛!";
}
};
}
export function getRangeDate(startDate: string | number | Date, endDate: string | number | Date) {
const targetArr = [];
@@ -37,27 +37,19 @@ export function getRangeDate(startDate: string | number | Date, endDate: string
if (startDateInfo.month !== endDateInfo.month) {
//同年,不同月份
//获取开始时间所在月的月底日期
const startMax = new Date(
startDateInfo.year,
startDateInfo.month,
0
).getDate();
const startMax = new Date(startDateInfo.year, startDateInfo.month, 0).getDate();
const endNum = startMax - startDateInfo.day + endDateInfo.day;
for (let i = startDateInfo.day; i <= startDateInfo.day + endNum; i++) {
if (i > startMax) {
targetArr.push(
`${endDateInfo.year}-${
endDateInfo.month < 10
? "0" + endDateInfo.month
: endDateInfo.month
endDateInfo.month < 10 ? "0" + endDateInfo.month : endDateInfo.month
}-${i - startMax < 10 ? "0" + (i - startMax) : i - startMax}`
);
} else {
targetArr.push(
`${startDateInfo.year}-${
startDateInfo.month < 10
? "0" + startDateInfo.month
: startDateInfo.month
startDateInfo.month < 10 ? "0" + startDateInfo.month : startDateInfo.month
}-${i < 10 ? "0" + i : i}`
);
}
@@ -67,20 +59,14 @@ export function getRangeDate(startDate: string | number | Date, endDate: string
for (let i = startDateInfo.day; i <= endDateInfo.day; i++) {
targetArr.push(
`${startDateInfo.year}-${
startDateInfo.month < 10
? "0" + startDateInfo.month
: startDateInfo.month
startDateInfo.month < 10 ? "0" + startDateInfo.month : startDateInfo.month
}-${i < 10 ? "0" + i : i}`
);
}
}
} else {
//不同年 【既然不同年那肯定也不同月】
const startMax = new Date(
startDateInfo.year,
startDateInfo.month,
0
).getDate();
const startMax = new Date(startDateInfo.year, startDateInfo.month, 0).getDate();
const endNum = startMax - startDateInfo.day + endDateInfo.day;
for (let i = startDateInfo.day; i <= startDateInfo.day + endNum; i++) {
if (i > startMax) {
@@ -92,9 +78,7 @@ export function getRangeDate(startDate: string | number | Date, endDate: string
} else {
targetArr.push(
`${startDateInfo.year}-${
startDateInfo.month < 10
? "0" + startDateInfo.month
: startDateInfo.month
startDateInfo.month < 10 ? "0" + startDateInfo.month : startDateInfo.month
}-${i < 10 ? "0" + i : i}`
);
}
@@ -131,17 +115,17 @@ export function listToTree(list: any[]) {
// 加载部门选项
export function formatTree(nodes: any[]): any[] {
return nodes.map(node => {
return nodes.map((node) => {
const formattedNode = {
value: node.id,
label: node.name,
disabled: node.status === false || String(node.status) === 'false'
disabled: node.status === false || String(node.status) === "false",
};
if (node.children && node.children.length > 0) {
Object.assign(formattedNode, { children: formatTree(node.children) });
}
return formattedNode;
});
}
@@ -160,6 +144,5 @@ export function isEmpty(obj: string | null | undefined) {
// 验证是否为blob格式
export function blobValidate(data: Blob): boolean {
return data.type !== 'application/json'
return data.type !== "application/json";
}
+37 -37
View File
@@ -1,73 +1,73 @@
import { reactive, toRefs } from 'vue'
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'
import dayjs from 'dayjs'
import { reactive, toRefs } from "vue";
import { tryOnMounted, tryOnUnmounted } from "@vueuse/core";
import dayjs from "dayjs";
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
const DATE_FORMAT = 'YYYY-MM-DD'
const DATE_TIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
const DATE_FORMAT = "YYYY-MM-DD";
export function formatToDateTime(date?: dayjs.ConfigType, format = DATE_TIME_FORMAT): string {
return dayjs(date).format(format)
return dayjs(date).format(format);
}
export function formatToDate(date?: dayjs.ConfigType, format = DATE_FORMAT): string {
return dayjs(date).format(format)
return dayjs(date).format(format);
}
export const useNow = (immediate = true) => {
let timer: ReturnType<typeof setInterval>
let timer: ReturnType<typeof setInterval>;
const state = reactive({
year: 0,
month: 0,
week: '',
week: "",
day: 0,
hour: '',
minute: '',
hour: "",
minute: "",
second: 0,
meridiem: ''
})
meridiem: "",
});
const update = () => {
const now = dayjs()
const now = dayjs();
const h = now.format('HH')
const m = now.format('mm')
const s = now.get('s')
const h = now.format("HH");
const m = now.format("mm");
const s = now.get("s");
state.year = now.get('y')
state.month = now.get('M') + 1
state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]
state.day = now.get('date')
state.hour = h
state.minute = m
state.second = s
state.year = now.get("y");
state.month = now.get("M") + 1;
state.week = "星期" + ["日", "一", "二", "三", "四", "五", "六"][now.day()];
state.day = now.get("date");
state.hour = h;
state.minute = m;
state.second = s;
state.meridiem = now.format('A')
}
state.meridiem = now.format("A");
};
function start() {
update()
clearInterval(timer)
timer = setInterval(() => update(), 1000)
update();
clearInterval(timer);
timer = setInterval(() => update(), 1000);
}
function stop() {
clearInterval(timer)
clearInterval(timer);
}
tryOnMounted(() => {
if (immediate) {
start()
start();
}
})
});
tryOnUnmounted(() => {
stop()
})
stop();
});
return {
...toRefs(state),
start,
stop
}
}
stop,
};
};
+6 -6
View File
@@ -3,10 +3,10 @@ interface ErrorCodeMap {
}
const errorCode: ErrorCodeMap = {
'401': '认证失败,无法访问系统资源',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'default': '系统未知错误,请反馈给管理员'
}
"401": "认证失败,无法访问系统资源",
"403": "当前操作没有权限",
"404": "访问资源不存在",
default: "系统未知错误,请反馈给管理员",
};
export default errorCode
export default errorCode;
+25 -25
View File
@@ -60,61 +60,61 @@ export function formatGrowthRate(growthRate: number) {
//表单生成相关
export function isNumberStr(str: string): boolean {
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str);
}
export function titleCase(str: string): string {
return str.replace(/([A-Z])/g, ' $1').replace(/^./, function(str) { return str.toUpperCase(); })
return str.replace(/([A-Z])/g, " $1").replace(/^./, function (str) {
return str.toUpperCase();
});
}
export function makeMap(str: string, expectsLowerCase?: boolean) {
const map = Object.create(null)
const list = str.split(',')
const map = Object.create(null);
const list = str.split(",");
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
map[list[i]] = true;
}
return expectsLowerCase
? (val: string) => map[val.toLowerCase()]
: (val: string) => map[val]
return expectsLowerCase ? (val: string) => map[val.toLowerCase()] : (val: string) => map[val];
}
export const beautifierConf = {
html: {
indent_size: '2',
indent_char: ' ',
max_preserve_newlines: '-1',
indent_size: "2",
indent_char: " ",
max_preserve_newlines: "-1",
preserve_newlines: false,
keep_array_indentation: false,
break_chained_methods: false,
indent_scripts: 'separate',
brace_style: 'end-expand',
indent_scripts: "separate",
brace_style: "end-expand",
space_before_conditional: true,
unescape_strings: false,
jslint_happy: false,
end_with_newline: true,
wrap_line_length: '110',
wrap_line_length: "110",
indent_inner_html: true,
comma_first: false,
e4x: true,
indent_empty_lines: true
indent_empty_lines: true,
},
js: {
indent_size: '2',
indent_char: ' ',
max_preserve_newlines: '-1',
indent_size: "2",
indent_char: " ",
max_preserve_newlines: "-1",
preserve_newlines: false,
keep_array_indentation: false,
break_chained_methods: false,
indent_scripts: 'normal',
brace_style: 'end-expand',
indent_scripts: "normal",
brace_style: "end-expand",
space_before_conditional: true,
unescape_strings: false,
jslint_happy: true,
end_with_newline: true,
wrap_line_length: '110',
wrap_line_length: "110",
indent_inner_html: true,
comma_first: false,
e4x: true,
indent_empty_lines: true
}
}
indent_empty_lines: true,
},
};
+14 -15
View File
@@ -1,4 +1,4 @@
import { ElMessage } from 'element-plus';
import { ElMessage } from "element-plus";
// 快速链接数据类型
export interface QuickLink {
@@ -10,7 +10,7 @@ export interface QuickLink {
// 快速开始管理器类
class QuickStartManager {
private storageKey = 'quick-start-links';
private storageKey = "quick-start-links";
private listeners: Array<(links: QuickLink[]) => void> = [];
// 获取所有快速链接
@@ -19,7 +19,7 @@ class QuickStartManager {
const stored = localStorage.getItem(this.storageKey);
return stored ? JSON.parse(stored) : this.getDefaultLinks();
} catch (error) {
console.error('Failed to load quick links:', error);
console.error("Failed to load quick links:", error);
return this.getDefaultLinks();
}
}
@@ -35,16 +35,16 @@ class QuickStartManager {
localStorage.setItem(this.storageKey, JSON.stringify(links));
this.notifyListeners(links);
} catch (error) {
console.error('Failed to save quick links:', error);
console.error("Failed to save quick links:", error);
}
}
// 添加快速链接
addQuickLink(link: QuickLink): void {
const links = this.getQuickLinks();
// 检查是否已存在相同路径的链接
const existingIndex = links.findIndex(l => l.href === link.href);
const existingIndex = links.findIndex((l) => l.href === link.href);
if (existingIndex !== -1) {
// 更新现有链接
links[existingIndex] = { ...links[existingIndex], ...link };
@@ -53,15 +53,15 @@ class QuickStartManager {
// 添加新链接
links.push(link);
}
this.saveQuickLinks(links);
}
// 删除快速链接
removeQuickLink(id: string): void {
const links = this.getQuickLinks();
const filteredLinks = links.filter(link => link.id !== id);
const filteredLinks = links.filter((link) => link.id !== id);
if (filteredLinks.length < links.length) {
this.saveQuickLinks(filteredLinks);
}
@@ -74,13 +74,13 @@ class QuickStartManager {
// 从路由或菜单信息创建快速链接
createQuickLinkFromRoute(route: any, customTitle?: string): QuickLink {
// 确定最终使用的标题 - 优先使用route.title
const finalTitle = customTitle || route.title || route.name || '未命名页面';
const finalTitle = customTitle || route.title || route.name || "未命名页面";
return {
title: finalTitle,
icon: route.icon,
href: route.fullPath || route.path,
id: `route-${route.path.replace(/\//g, '-')}-${Date.now()}`
id: `route-${route.path.replace(/\//g, "-")}-${Date.now()}`,
};
}
@@ -99,11 +99,11 @@ class QuickStartManager {
// 通知所有监听器
private notifyListeners(links: QuickLink[]): void {
this.listeners.forEach(callback => {
this.listeners.forEach((callback) => {
try {
callback(links);
} catch (error) {
console.error('Error in quick start listener:', error);
console.error("Error in quick start listener:", error);
}
});
}
@@ -111,10 +111,9 @@ class QuickStartManager {
// 检查链接是否已存在
isLinkExists(href: string): boolean {
const links = this.getQuickLinks();
return links.some(link => link.href === href);
return links.some((link) => link.href === href);
}
}
// 创建全局实例
export const quickStartManager = new QuickStartManager();
+19 -11
View File
@@ -1,4 +1,9 @@
import axios, { type InternalAxiosRequestConfig, type AxiosResponse, type AxiosInstance, type AxiosError} from "axios";
import axios, {
type InternalAxiosRequestConfig,
type AxiosResponse,
type AxiosInstance,
type AxiosError,
} from "axios";
import qs from "qs";
import { useUserStoreHook } from "@/store/modules/user.store";
import { ResultEnum } from "@/enums/api/result.enum";
@@ -18,7 +23,8 @@ const httpRequest: AxiosInstance = axios.create({
/**
* 请求拦截器 - 添加 Authorization 头
*/
httpRequest.interceptors.request.use((config: InternalAxiosRequestConfig) => {
httpRequest.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const accessToken = Auth.getAccessToken();
// 如果 Authorization 设置为 no-auth,则不携带 Token
@@ -39,7 +45,8 @@ httpRequest.interceptors.request.use((config: InternalAxiosRequestConfig) => {
/**
* 响应拦截器 - 统一处理响应和错误
*/
httpRequest.interceptors.response.use((response: AxiosResponse<ApiResponse>) => {
httpRequest.interceptors.response.use(
(response: AxiosResponse<ApiResponse>) => {
// 如果响应是二进制流,则直接返回(用于文件下载、Excel 导出等)
if (response.config.responseType === "blob") {
return response;
@@ -54,21 +61,22 @@ httpRequest.interceptors.response.use((response: AxiosResponse<ApiResponse>) =>
}
// 如果请求不是 GET 请求,请求成功时显示成功提示
if (response.config.method?.toUpperCase() !== 'GET') {
if (response.config.method?.toUpperCase() !== "GET") {
ElMessage.success(data.msg);
}
return response;
}, async (error: AxiosError<ApiResponse>) => {
},
async (error: AxiosError<ApiResponse>) => {
const data = error.response?.data;
// 处理blob类型的错误响应
if (error.response?.config.responseType === 'blob' && error.response.data instanceof Blob) {
if (error.response?.config.responseType === "blob" && error.response.data instanceof Blob) {
try {
// 将blob转换为JSON
const text = await new Response(error.response.data).text();
const jsonData: ApiResponse = JSON.parse(text);
if (jsonData.code === ResultEnum.ERROR) {
ElMessage.error(jsonData.msg || "请求错误");
return Promise.reject(new Error(jsonData.msg || "请求错误"));
@@ -95,12 +103,12 @@ httpRequest.interceptors.response.use((response: AxiosResponse<ApiResponse>) =>
} else if (data?.code === ResultEnum.EXCEPTION) {
ElMessage.error(data.msg || "服务异常");
return Promise.reject(new Error(data.msg || "服务异常"));
} else {
} else {
ElMessage.error(error.message || "请求异常");
return Promise.reject(new Error(error.message || "请求异常"));
}
}
);
);
/**
* 重定向到登录页面
+391 -55
View File
@@ -1,64 +1,400 @@
/**
* 存储工具类
* 提供localStorage和sessionStorage操作方法
*/
export class Storage {
/**
* localStorage 存储
*/
static set(key: string, value: any): void {
localStorage.setItem(key, JSON.stringify(value));
}
<!-- 角色授权 -->
<template>
<!-- 分配权限弹窗 -->
<el-drawer
v-model="drawerVisible"
:title="'【' + props.roleName + '】权限分配'"
:size="drawerSize"
>
<el-container>
<!-- 数据权限 -->
<el-aside>
<div class="border-r-1 border-r-[#f0f0f0] b-r-solid h-[100%] p-[20px] box-border">
<div class="flex items-center">
<div style="display: flex; gap: 10px">
<div style="width: 10px; background-color: #409eff"></div>
<div>
<span style="font-size: 16px">数据授权</span>
<el-tooltip placement="right">
<template #content>
<span>授权用户可操作的数据范围</span>
</template>
<el-icon class="ml-1 inline-block cursor-pointer">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</div>
</div>
<div class="mt-3">
<el-form ref="dataFormRef" :model="permissionState">
<el-form-item prop="data_scope">
<el-select v-model="permissionState.data_scope">
<el-option :key="1" label="仅本人数据权限" :value="1" />
<el-option :key="2" label="本部门数据权限" :value="2" />
<el-option :key="3" label="本部门及以下数据权限" :value="3" />
<el-option :key="4" label="全部数据权限" :value="4" />
<el-option :key="5" label="自定义数据权限" :value="5" />
</el-select>
</el-form-item>
</el-form>
static get<T>(key: string, defaultValue?: T): T {
const value = localStorage.getItem(key);
if (!value) return defaultValue as T;
<div
v-if="permissionState.data_scope === 5 && deptTreeData.length"
class="mt-5 max-h-[60vh] b-1 b-solid b-[#e5e7eb] p-10px overflow-auto box-border"
>
<el-input v-model="deptFilterText" placeholder="部门名称" />
<el-tree
ref="deptTreeRef"
node-key="value"
show-checkbox
:data="deptTreeData"
:filter-node-method="handleFilter"
default-expand-all
:highlight-current="true"
:check-strictly="!parentChildLinked"
style="height: calc(100% - 60px); overflow-y: auto; margin-top: 10px"
@check="deptTreeCheck"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
</el-tree>
</div>
</div>
</div>
</el-aside>
try {
return JSON.parse(value);
} catch {
// 如果解析失败,返回原始字符串
return value as unknown as T;
<!-- 菜单权限 -->
<el-main>
<div style="display: flex; gap: 10px">
<div style="width: 10px; background-color: #409eff"></div>
<div>
<span style="font-size: 16px">菜单授权</span>
<el-tooltip placement="right">
<template #content>
<span>授权用户可操作的菜单权限</span>
</template>
<el-icon class="ml-1 inline-block cursor-pointer">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</div>
<div class="mt-3 flex-x-between">
<el-input v-model="permFilterText" placeholder="菜单名称" />
<div class="flex-center ml-5">
<el-button type="primary" size="small" plain @click="togglePermTree">
<template #icon>
<Switch />
</template>
{{ isExpanded ? "收缩" : "展开" }}
</el-button>
<el-checkbox
v-model="parentChildLinked"
class="ml-5"
@change="handleParentChildLinkedChange"
>
父子联动
</el-checkbox>
<el-tooltip placement="bottom">
<template #content>
如果只需勾选菜单权限,不需要勾选子菜单或者按钮权限,请关闭父子联动
</template>
<el-icon class="ml-1 color-[--el-color-primary] inline-block cursor-pointer">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</div>
<div class="mt-5 max-h-[65vh] b-1 b-solid b-[#e5e7eb] p-10px overflow-auto box-border">
<el-tree
ref="permTreeRef"
node-key="value"
show-checkbox
:data="menuTreeData"
:filter-node-method="handleFilter"
default-expand-all
:highlight-current="true"
:check-strictly="!parentChildLinked"
style="height: calc(100% - 60px); overflow: auto; margin-top: 10px"
@check="menuTreeCheck"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
</el-tree>
</div>
</el-main>
</el-container>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCancel">取 消</el-button>
<el-button type="primary" @click.stop="handleDrawerSave">确 定</el-button>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
// 添加 props 来接收 dictType
const props = defineProps({
roleName: {
type: String,
required: true,
},
roleId: {
type: Number,
required: true,
},
modelValue: {
type: Boolean,
required: true,
},
});
import { listToTree, formatTree } from "@/utils/common";
import RoleAPI, {
permissionDataType,
permissionDeptType,
permissionMenuType,
} from "@/api/module_system/role";
import DeptAPI from "@/api/module_system/dept";
import MenuAPI from "@/api/module_system/menu";
import type { TreeInstance } from "element-plus";
import { useAppStore } from "@/store/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
import { useUserStore } from "@/store";
const appStore = useAppStore();
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "800px" : "90%"));
const emit = defineEmits(["update:modelValue", "saved"]);
const permTreeRef = ref<TreeInstance>();
const deptTreeRef = ref<TreeInstance>();
const deptFilterText = ref("");
const permFilterText = ref("");
const dataFormRef = ref();
const drawerVisible = computed({
get() {
return props.modelValue;
},
set(value) {
emit("update:modelValue", value);
},
});
const isExpanded = ref(true);
const parentChildLinked = ref(false);
const loading = ref<boolean>(false);
const deptTreeData = ref<permissionDeptType[]>([]);
const menuTreeData = ref<permissionMenuType[]>([]);
const permissionState = ref<permissionDataType>({
role_ids: [],
menu_ids: [],
data_scope: 1,
dept_ids: [],
});
// 初始化方法,用于打开抽屉并加载数据
const init = async () => {
loading.value = true;
try {
// 获取全部部门树
const deptResponse = await DeptAPI.getDeptList();
deptTreeData.value = formatTree(listToTree(deptResponse.data.data));
// 获取全部菜单树
const menuResponse = await MenuAPI.getMenuList();
menuTreeData.value = formatTree(listToTree(menuResponse.data.data));
// 获取角色详情
const roleResponse = await RoleAPI.getRoleDetail(props.roleId);
// 更新权限状态
permissionState.value = {
role_ids: [props.roleId],
menu_ids: roleResponse.data.data.menus?.map((menu) => menu.id) || [],
data_scope: roleResponse.data.data.data_scope || 1,
dept_ids: roleResponse.data.data.depts?.map((dept) => dept.id) || [],
};
// 根据保存的权限数据判断是否应该开启父子联动
parentChildLinked.value = checkParentChildLinked(
permissionState.value.menu_ids,
menuTreeData.value
);
// 回显菜单树选中项
if (permTreeRef.value) {
await permTreeRef.value.setCheckedKeys(permissionState.value.menu_ids);
}
}
static remove(key: string): void {
localStorage.removeItem(key);
}
/**
* localStorage 清空
*/
static clear(): void {
localStorage.clear();
}
/**
* sessionStorage 存储
*/
static sessionSet(key: string, value: any): void {
sessionStorage.setItem(key, JSON.stringify(value));
}
static sessionGet<T>(key: string, defaultValue?: T): T {
const value = sessionStorage.getItem(key);
if (!value) return defaultValue as T;
try {
return JSON.parse(value);
} catch {
// 如果解析失败,返回原始字符串
return value as unknown as T;
// 修改:增加对 deptTreeRef.value 的存在性判断,并添加日志
if (permissionState.value.data_scope === 5 && deptTreeRef.value) {
// await 一定不能丢,否则到导致初始化时候deptTreeRef.value 为 undefined
await deptTreeRef.value.setCheckedKeys(permissionState.value.dept_ids);
}
} catch (error: any) {
ElMessage.error("获取权限数据失败: " + error.message);
} finally {
loading.value = false;
// handleCancel()
}
};
static sessionRemove(key: string): void {
sessionStorage.removeItem(key);
}
/**
* sessionStorage 清空
*/
static sessionClear(): void {
sessionStorage.clear();
// 取消按钮处理函数
function handleCancel() {
drawerVisible.value = false;
}
// 保存权限分配
async function handleDrawerSave() {
try {
if (props.roleId === 1) {
ElMessage.warning("系统默认角色,不可操作");
return;
}
loading.value = true;
// 构造提交数据
const submitData: permissionDataType = {
role_ids: [props.roleId],
menu_ids: (permTreeRef.value?.getCheckedKeys() || []).map((key) => Number(key)),
data_scope: permissionState.value.data_scope,
dept_ids: (deptTreeRef.value?.getCheckedKeys() || []).map((key) => Number(key)),
};
await RoleAPI.setPermission(submitData);
// 更新全局用户状态,刷新权限信息
const userStore = useUserStore();
await userStore.getUserInfo();
drawerVisible.value = false;
emit("saved");
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}
// 部门树选择回调
const deptTreeCheck = (checkedIds: number[]) => {
permissionState.value.dept_ids = checkedIds;
};
// 菜单选择变更回调
const menuTreeCheck = (checkedIds: number[]) => {
permissionState.value.menu_ids = checkedIds;
};
// 展开/收缩 菜单权限树
function togglePermTree() {
isExpanded.value = !isExpanded.value;
if (permTreeRef.value) {
Object.values(permTreeRef.value.store.nodesMap).forEach((node: any) => {
if (isExpanded.value) {
node.expand();
} else {
node.collapse();
}
});
}
}
// 部门筛选
watch(deptFilterText, (val) => {
deptTreeRef.value!.filter(val);
});
// 菜单筛选
watch(permFilterText, (val) => {
permTreeRef.value!.filter(val);
});
// 树节点过滤
function handleFilter(value: string, data: { [key: string]: any }) {
if (!value) return true;
return data.label.includes(value);
}
// 检查权限数据是否遵循父子联动模式
function checkParentChildLinked(menuIds: number[], menuTreeData: permissionMenuType[]): boolean {
if (!menuIds.length || !menuTreeData.length) return false;
// 创建一个映射来快速查找菜单项
const menuMap = new Map<number, permissionMenuType>();
const buildMenuMap = (menus: permissionMenuType[]) => {
menus.forEach((menu) => {
menuMap.set(menu.id, menu);
if (menu.children) {
buildMenuMap(menu.children);
}
});
};
buildMenuMap(menuTreeData);
let hasParentChildConflict = false;
// 检查每个选中的菜单项
for (const menuId of menuIds) {
const menu = menuMap.get(menuId);
if (!menu) continue;
// 如果选中了父菜单,检查是否有子菜单未被选中
if (menu.children && menu.children.length > 0) {
const hasUnselectedChildren = menu.children.some((child) => !menuIds.includes(child.id));
if (hasUnselectedChildren) {
hasParentChildConflict = true;
break; // 发现冲突,直接返回false
}
}
// 如果选中了子菜单,检查父菜单是否也被选中
const parentMenu = findParentMenu(menuId, menuTreeData);
if (parentMenu && !menuIds.includes(parentMenu.id)) {
hasParentChildConflict = true;
break; // 发现冲突,直接返回false
}
}
// 如果没有发现父子冲突,说明是父子联动模式
return !hasParentChildConflict;
}
// 查找父菜单
function findParentMenu(
menuId: number,
menuTreeData: permissionMenuType[]
): permissionMenuType | null {
for (const menu of menuTreeData) {
if (menu.children) {
for (const child of menu.children) {
if (child.id === menuId) {
return menu;
}
// 递归查找更深层的父菜单
const found = findParentMenu(menuId, [child]);
if (found) return found;
}
}
}
return null;
}
// 父子菜单节点是否联动
function handleParentChildLinkedChange(val: any) {
parentChildLinked.value = val;
}
onMounted(async () => {
await init();
});
</script>
<style lang="scss" scoped></style>