update 优化 loadView 预建 Map 查找表,路由组件解析从 O(n) 降为 O(1)
update 优化 generateRoutes 三次 JSON 序列化改为 structuredClone fix 修复 layout watchEffect 改为 watch(width) 消除循环依赖 fix 修复 useDict 返回 reactive 对象保持响应式,增加并发请求去重 fix 修复 FileUpload/ImageUpload headers 改为 computed 避免 token 过期 update 优化 debounce 箭头函数改为 function 声明修复 this 绑定丢失 fix 修复 duplicateRouteChecker 修复 route.name 为空时的空指针崩溃 fix 修复 AppMain 动画随机选择 Math.round 改为 Math.floor 防止越界 fix 修复 TagsView 关闭页签时始终清除缓存,修复动态路由内存泄漏
This commit is contained in:
@@ -75,7 +75,7 @@ const uploadList = ref<any[]>([]);
|
||||
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
||||
const uploadFileUrl = ref(baseUrl + '/resource/oss/upload'); // 上传文件服务器地址
|
||||
const headers = ref(globalHeaders());
|
||||
const headers = computed(() => globalHeaders());
|
||||
|
||||
const fileList = ref<any[]>([]);
|
||||
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
|
||||
|
||||
@@ -83,7 +83,7 @@ const dialogVisible = ref(false);
|
||||
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
||||
const uploadImgUrl = ref(baseUrl + '/resource/oss/upload'); // 上传的图片服务器地址
|
||||
const headers = ref(globalHeaders());
|
||||
const headers = computed(() => globalHeaders());
|
||||
|
||||
const fileList = ref<any[]>([]);
|
||||
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
|
||||
|
||||
@@ -27,7 +27,7 @@ watch(
|
||||
(val: boolean) => {
|
||||
if (val) {
|
||||
animate.value = proxy?.animate.animateList[
|
||||
Math.round(Math.random() * proxy?.animate.animateList.length)
|
||||
Math.floor(Math.random() * proxy?.animate.animateList.length)
|
||||
] as string;
|
||||
} else {
|
||||
animate.value = proxy?.animate.defaultAnimate as string;
|
||||
|
||||
@@ -45,17 +45,14 @@ const classObj = computed(() => ({
|
||||
const { width } = useWindowSize();
|
||||
const WIDTH = 992; // refer to Bootstrap's responsive design
|
||||
|
||||
watchEffect(() => {
|
||||
if (device.value === 'mobile') {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false });
|
||||
}
|
||||
if (width.value - 1 < WIDTH) {
|
||||
watch(width, (w) => {
|
||||
if (w - 1 < WIDTH) {
|
||||
useAppStore().toggleDevice('mobile');
|
||||
useAppStore().closeSideBar({ withoutAnimation: true });
|
||||
} else {
|
||||
useAppStore().toggleDevice('desktop');
|
||||
}
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
const settingRef = ref<InstanceType<typeof Settings>>();
|
||||
|
||||
|
||||
@@ -10,8 +10,15 @@ import InnerLink from '@/layout/components/InnerLink/index.vue';
|
||||
import { ref } from 'vue';
|
||||
import { createCustomNameComponent } from '@/utils/createCustomNameComponent';
|
||||
|
||||
// 匹配views里面所有的.vue文件
|
||||
// 匹配views里面所有的.vue文件,预建查找表避免每次 O(n) 扫描
|
||||
const modules = import.meta.glob('./../../views/**/*.vue');
|
||||
const viewModuleMap = new Map<string, () => Promise<any>>();
|
||||
for (const path in modules) {
|
||||
const viewsIndex = path.indexOf('/views/');
|
||||
if (viewsIndex === -1) continue;
|
||||
const dir = path.substring(viewsIndex + 7, path.lastIndexOf('.vue'));
|
||||
viewModuleMap.set(dir, modules[path] as () => Promise<any>);
|
||||
}
|
||||
export const usePermissionStore = defineStore('permission', () => {
|
||||
const routes = ref<RouteRecordRaw[]>([]);
|
||||
const addRoutes = ref<RouteRecordRaw[]>([]);
|
||||
@@ -48,10 +55,9 @@ export const usePermissionStore = defineStore('permission', () => {
|
||||
const generateRoutes = async (): Promise<RouteRecordRaw[]> => {
|
||||
const res = await getRouters();
|
||||
const data = Array.isArray(res.data) ? res.data : [];
|
||||
const text = JSON.stringify(data);
|
||||
const sdata = JSON.parse(text);
|
||||
const rdata = JSON.parse(text);
|
||||
const defaultData = JSON.parse(text);
|
||||
const sdata = structuredClone(data);
|
||||
const rdata = structuredClone(data);
|
||||
const defaultData = structuredClone(data);
|
||||
const sidebarRoutes = filterAsyncRouter(sdata);
|
||||
const rewriteRoutes = filterAsyncRouter(rdata, undefined, true);
|
||||
const defaultRoutes = filterAsyncRouter(defaultData);
|
||||
@@ -149,17 +155,11 @@ export const filterDynamicRoutes = (routes: RouteRecordRaw[]) => {
|
||||
};
|
||||
|
||||
export const loadView = (view: any, name: string) => {
|
||||
let res;
|
||||
for (const path in modules) {
|
||||
const viewsIndex = path.indexOf('/views/');
|
||||
let dir = path.substring(viewsIndex + 7);
|
||||
dir = dir.substring(0, dir.lastIndexOf('.vue'));
|
||||
if (dir === view) {
|
||||
res = createCustomNameComponent(modules[path], { name });
|
||||
return res;
|
||||
}
|
||||
const loader = viewModuleMap.get(view);
|
||||
if (loader) {
|
||||
return createCustomNameComponent(loader, { name });
|
||||
}
|
||||
return res;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// 非setup
|
||||
@@ -197,7 +197,8 @@ function duplicateRouteChecker(localRoutes: Route[], routes: Route[]) {
|
||||
const nameList: string[] = [];
|
||||
allRoutes.forEach(route => {
|
||||
const name = route.name?.toString() ?? '';
|
||||
if (name && nameList.includes(name)) {
|
||||
if (!name) return;
|
||||
if (nameList.includes(name)) {
|
||||
const message = `路由名称: [${name}] 重复, 会造成 404`;
|
||||
console.error(message);
|
||||
ElNotification({
|
||||
@@ -207,6 +208,6 @@ function duplicateRouteChecker(localRoutes: Route[], routes: Route[]) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
nameList.push(route.name.toString());
|
||||
nameList.push(name);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,9 +143,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
|
||||
}> => {
|
||||
return new Promise(resolve => {
|
||||
delVisitedView(view);
|
||||
if (!isDynamicRoute(view)) {
|
||||
delCachedView(view);
|
||||
}
|
||||
delCachedView(view);
|
||||
resolve({
|
||||
visitedViews: visitedViews.value.slice() as RouteLocationNormalized[],
|
||||
cachedViews: [...cachedViews.value]
|
||||
@@ -309,10 +307,6 @@ export const useTagsViewStore = defineStore('tagsView', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const isDynamicRoute = (view: RouteLocationNormalized): boolean => {
|
||||
return view.matched.some(m => m.path.includes(':'));
|
||||
};
|
||||
|
||||
return {
|
||||
visitedViews,
|
||||
cachedViews,
|
||||
|
||||
Vendored
+3
@@ -4,6 +4,9 @@ declare module '*.vue' {
|
||||
export default Component;
|
||||
}
|
||||
|
||||
declare module 'virtual:svg-icons-register' {}
|
||||
declare module 'virtual:*' {}
|
||||
|
||||
// 环境变量
|
||||
interface ImportMetaEnv {
|
||||
VITE_APP_TITLE: string;
|
||||
|
||||
+24
-16
@@ -1,31 +1,39 @@
|
||||
import { getDicts } from '@/api/system/dict/data';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
|
||||
const pendingRequests = new Map<string, Promise<DictDataOption[]>>();
|
||||
|
||||
/**
|
||||
* 获取字典数据
|
||||
*/
|
||||
export const useDict = (...args: string[]): { [key: string]: DictDataOption[] } => {
|
||||
const res = ref<{
|
||||
[key: string]: DictDataOption[];
|
||||
}>({});
|
||||
const res = reactive<{ [key: string]: DictDataOption[] }>({});
|
||||
|
||||
args.forEach(async dictType => {
|
||||
res.value[dictType] = [];
|
||||
res[dictType] = [];
|
||||
const dicts = useDictStore().getDict(dictType);
|
||||
if (dicts) {
|
||||
res.value[dictType] = dicts;
|
||||
res[dictType] = dicts;
|
||||
} else {
|
||||
await getDicts(dictType).then(resp => {
|
||||
res.value[dictType] = resp.data.map(
|
||||
(p): DictDataOption => ({
|
||||
label: p.dictLabel,
|
||||
value: p.dictValue,
|
||||
elTagType: p.listClass,
|
||||
elTagClass: p.cssClass
|
||||
if (!pendingRequests.has(dictType)) {
|
||||
const request = getDicts(dictType)
|
||||
.then(resp => {
|
||||
const data = resp.data.map(
|
||||
(p): DictDataOption => ({
|
||||
label: p.dictLabel,
|
||||
value: p.dictValue,
|
||||
elTagType: p.listClass,
|
||||
elTagClass: p.cssClass
|
||||
})
|
||||
);
|
||||
useDictStore().setDict(dictType, data);
|
||||
return data;
|
||||
})
|
||||
);
|
||||
useDictStore().setDict(dictType, res.value[dictType]);
|
||||
});
|
||||
.finally(() => pendingRequests.delete(dictType));
|
||||
pendingRequests.set(dictType, request);
|
||||
}
|
||||
res[dictType] = await pendingRequests.get(dictType)!;
|
||||
}
|
||||
});
|
||||
return res.value;
|
||||
return res;
|
||||
};
|
||||
|
||||
+18
-17
@@ -204,39 +204,40 @@ export const getTime = (type: string) => {
|
||||
* @param {boolean} immediate
|
||||
* @return {*}
|
||||
*/
|
||||
export const debounce = (func: any, wait: number, immediate: boolean) => {
|
||||
let timeout: any, args: any, context: any, timestamp: any, result: any;
|
||||
export function debounce(func: (...args: any[]) => any, wait: number, immediate: boolean) {
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
let lastArgs: any[] | null = null;
|
||||
let lastContext: any = null;
|
||||
let timestamp = 0;
|
||||
let result: any;
|
||||
|
||||
const later = function () {
|
||||
// 据上一次触发时间间隔
|
||||
const last = +new Date() - timestamp;
|
||||
function later() {
|
||||
const last = Date.now() - timestamp;
|
||||
|
||||
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
|
||||
if (last < wait && last > 0) {
|
||||
timeout = setTimeout(later, wait - last);
|
||||
} else {
|
||||
timeout = null;
|
||||
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
|
||||
if (!immediate) {
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
result = func.apply(lastContext, lastArgs!);
|
||||
if (!timeout) lastContext = lastArgs = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (...args: any) => {
|
||||
context = this;
|
||||
timestamp = +new Date();
|
||||
return function (this: any, ...args: any[]) {
|
||||
lastContext = this;
|
||||
lastArgs = args;
|
||||
timestamp = Date.now();
|
||||
const callNow = immediate && !timeout;
|
||||
// 如果延时不存在,重新设定延时
|
||||
if (!timeout) timeout = setTimeout(later, wait);
|
||||
if (callNow) {
|
||||
result = func.apply(context, args);
|
||||
context = args = null;
|
||||
result = func.apply(lastContext, lastArgs);
|
||||
lastContext = lastArgs = null;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is just a simple version of deep copy
|
||||
|
||||
Reference in New Issue
Block a user