主页布局构建完成

This commit is contained in:
li-chx
2025-08-17 17:22:06 +08:00
parent b8e2f4282d
commit 1fb97e1cc2
31 changed files with 1246 additions and 9972 deletions
+58
View File
@@ -0,0 +1,58 @@
const getInitialMode = () => {
if (typeof window !== 'undefined') {
// 优先用 html 的 class
if (document.documentElement.classList.contains('dark')) return 'dark';
if (document.documentElement.classList.contains('light')) return 'light';
// 其次用 localStorage
return localStorage.getItem('system-theme-mode') || 'light';
}
return 'light'; // SSR 默认
};
const useColorModeStore = defineStore('colorMode', {
state: () => ({
colorMode: getInitialMode() as 'light' | 'dark',
callBackFunctions: new Map<string, () => void>(),
callBackId: 0,
}),
getters: {
isDarkMode: (state) => state.colorMode === 'dark',
},
actions: {
registerCallBack(func: () => void) {
const id = `callback-${this.callBackId++}`;
this.callBackFunctions.set(id, func);
return id;
},
unregisterCallBack(id: string) {
this.callBackFunctions.delete(id);
},
notifyCallBacks() {
this.callBackFunctions.forEach((func) => {
try {
func();
} catch (error) {
console.error('Error in color mode callback:', error);
}
});
},
toggleColorMode() {
this.setColorMode(this.colorMode === 'dark' ? 'light' : 'dark');
},
setColorMode(mode: 'light' | 'dark') {
if (mode !== 'light' && mode !== 'dark') {
throw new Error('Invalid color mode. Use "light" or "dark".');
}
this.colorMode = mode;
if (mode === 'dark') {
document.querySelector('html')!.classList.remove('light');
document.querySelector('html')!.classList.add('dark');
} else {
document.querySelector('html')!.classList.remove('dark');
document.querySelector('html')!.classList.add('light');
}
this.notifyCallBacks();
},
},
});
export default useColorModeStore;
-4
View File
@@ -1,4 +0,0 @@
export const store = defineStore('counter', {
});
export default store;
+69
View File
@@ -0,0 +1,69 @@
const useIconStore = defineStore('icon', {
state: () => ({
iconCache: new Map<string, string>(),
addingSet: new Set<string>(),
waitingCallbackFunctions: new Map<string, (() => void)[]>(),
}),
actions: {
getIcon(iconName: string): string {
if (this.iconCache.has(iconName)) {
return this.iconCache.get(iconName) as string;
}
return '';
},
getColoredIcon(iconName: string, color: string): string {
const icon = this.getIcon(iconName);
if (icon === '')
return '';
return icon.replace('<svg', `<svg style="color:${color};"`);
},
async setIconInfo(iconName: string, iconData?: string) {
if (this.iconCache.has(iconName)) {
return;
}
if (this.addingSet.has(iconName)) {
return new Promise<void>((resolve) => {
if (!this.waitingCallbackFunctions.has(iconName)) {
this.waitingCallbackFunctions.set(iconName, [resolve]);
} else
this.waitingCallbackFunctions.get(iconName)!.push(resolve);
});
}
this.addingSet.add(iconName);
if (iconData === undefined || iconData === null || iconData === '') {
this.iconCache.set(iconName, await fetchSvg(iconName));
} else
this.iconCache.set(iconName, iconData as string);
this.addingSet.delete(iconName);
this.waitingCallbackFunctions.get(iconName)?.forEach((callback) => callback());
this.waitingCallbackFunctions.delete(iconName);
},
},
});
export default useIconStore;
export async function fetchSvg(svgName: string, maxRetries = 3) {
const iconifyUrl = 'https://api.iconify.design/';
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await (await $fetch<Blob>(iconifyUrl + svgName + '.svg', {
method: 'GET',
params: {
width: '100%',
},
})).text();
} catch (error) {
console.warn(`Attempt ${attempt} failed for ${svgName}:`, error);
if (attempt === maxRetries) {
console.error(`All ${maxRetries} attempts failed for ${svgName}`);
}
// 等待一段时间后重试(可选)
await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
}
}
return '<svg></svg>';
}