mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-02 00:27:16 +00:00
style: 移除多余空行并统一代码格式
修复代码中的多余空行问题,统一字符串引号使用双引号,调整对象和数组的格式,优化代码缩进和换行,提升代码可读性和一致性
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user