前端:更新依赖项以支持Markdown解析和目录功能,优化Vite构建配置,重构文章展示逻辑,改进主题切换和样式,移除不再使用的加载组件,修复多个小问题。

This commit is contained in:
lsy 2024-12-08 00:55:12 +08:00
parent 2aaffb9e2b
commit b3c0af91b8
14 changed files with 1002 additions and 538 deletions

View File

@ -11,8 +11,6 @@ import { hydrateRoot } from "react-dom/client";
startTransition(() => { startTransition(() => {
hydrateRoot( hydrateRoot(
document, document,
<StrictMode> <RemixBrowser />,
<RemixBrowser />
</StrictMode>,
); );
}); });

View File

@ -1,7 +1,7 @@
import React, { createContext, useState } from "react"; import React, { createContext, useEffect, useState } from "react";
import { DEFAULT_CONFIG } from "app/env"; import { DEFAULT_CONFIG } from "app/env";
import { HttpClient } from "core/http"; import { HttpClient } from "core/http";
import { ThemeModeToggle } from "hooks/themeMode"; import { ThemeModeToggle } from "hooks/ThemeMode";
import { import {
Theme, Theme,
Button, Button,
@ -13,8 +13,8 @@ import {
Box, Box,
TextField, TextField,
} from "@radix-ui/themes"; } from "@radix-ui/themes";
import { toast } from "hooks/notification"; import { toast } from "hooks/Notification";
import { Echoes } from "hooks/echoes"; import { Echoes } from "hooks/Echoes";
interface SetupContextType { interface SetupContextType {
currentStep: number; currentStep: number;
@ -36,7 +36,7 @@ const StepContainer: React.FC<{ title: string; children: React.ReactNode }> = ({
children, children,
}) => ( }) => (
<Box style={{ width: "90%", maxWidth: "600px", margin: "0 auto" }}> <Box style={{ width: "90%", maxWidth: "600px", margin: "0 auto" }}>
<Heading size="5" mb="4" weight="bold"> <Heading size="5" mb="4" weight="bold" style={{ userSelect: "none" }}>
{title} {title}
</Heading> </Heading>
<Flex direction="column" gap="4"> <Flex direction="column" gap="4">
@ -237,15 +237,27 @@ const DatabaseConfig: React.FC<StepProps> = ({ onNext }) => {
<div> <div>
<Box mb="6"> <Box mb="6">
<Text as="label" size="2" weight="medium" mb="2" className="block"> <Text as="label" size="2" weight="medium" mb="2" className="block">
<Text color="red">*</Text>
</Text> </Text>
<Select.Root value={dbType} onValueChange={setDbType}> <Select.Root value={dbType} onValueChange={setDbType}>
<Select.Trigger /> <Select.Trigger className="w-full" />
<Select.Content> <Select.Content position="popper" sideOffset={8}>
<Select.Group> <Select.Group>
<Select.Item value="postgresql">PostgreSQL</Select.Item> <Select.Item value="postgresql">
<Select.Item value="mysql">MySQL</Select.Item> <Flex gap="2" align="center">
<Select.Item value="sqllite">SQLite</Select.Item> <Text>PostgreSQL</Text>
</Flex>
</Select.Item>
<Select.Item value="mysql">
<Flex gap="2" align="center">
<Text>MySQL</Text>
</Flex>
</Select.Item>
<Select.Item value="sqllite">
<Flex gap="2" align="center">
<Text>SQLite</Text>
</Flex>
</Select.Item>
</Select.Group> </Select.Group>
</Select.Content> </Select.Content>
</Select.Root> </Select.Root>
@ -461,9 +473,12 @@ const SetupComplete: React.FC = () => (
); );
export default function SetupPage() { export default function SetupPage() {
const [currentStep, setCurrentStep] = useState(() => { const [currentStep, setCurrentStep] = useState(1);
return Number(import.meta.env.VITE_INIT_STATUS ?? 0) + 1; useEffect(() => {
}); // 在客户端组件挂载后更新状态
const initStatus = Number(import.meta.env.VITE_INIT_STATUS ?? 0) + 1;
setCurrentStep(initStatus);
}, []);
return ( return (
<Theme <Theme

View File

@ -5,9 +5,9 @@ import {
Scripts, Scripts,
ScrollRestoration, ScrollRestoration,
} from "@remix-run/react"; } from "@remix-run/react";
import { NotificationProvider } from "hooks/notification"; import { NotificationProvider } from "hooks/Notification";
import { Theme } from "@radix-ui/themes"; import { Theme } from "@radix-ui/themes";
import { ThemeScript } from "hooks/themeMode"; import { ThemeScript } from "hooks/ThemeMode";
import "~/index.css"; import "~/index.css";
@ -49,6 +49,7 @@ export function Layout() {
<body <body
className="h-full" className="h-full"
suppressHydrationWarning={true} suppressHydrationWarning={true}
data-cz-shortcut-listen="false"
> >
<Theme <Theme
grayColor="slate" grayColor="slate"

View File

@ -1,46 +1,47 @@
import ErrorPage from "hooks/error"; import ErrorPage from "hooks/Error";
import layout from "themes/echoes/layout"; import layout from "themes/echoes/layout";
import article from "themes/echoes/article"; import article from "themes/echoes/article";
import about from "themes/echoes/about"; import about from "themes/echoes/about";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import post from "themes/echoes/post"; import post from "themes/echoes/post";
import { memo, useCallback } from "react";
export default function Routes() { const args = {
const location = useLocation();
let path = location.pathname;
const args = {
title: "我的页面", title: "我的页面",
theme: "dark", theme: "dark",
nav: '<a href="/">index</a><a href="/error">error</a><a href="/about">about</a><a href="/post">post</a>', nav: '<a href="/">index</a><a href="/error">error</a><a href="/about">about</a><a href="/post">post</a>',
}; } as const;
console.log(path); const renderLayout = (children: React.ReactNode) => {
path = path.split("/")[1];
if (path === "error") {
return layout.render({ return layout.render({
children: ErrorPage.render(args), children,
args, args,
}); });
};
const Routes = memo(() => {
const location = useLocation();
const path = location.pathname.split("/")[1];
// 使用 useCallback 缓存渲染函数
const renderContent = useCallback((Component: any) => {
return renderLayout(Component.render(args));
}, []);
// 根据路径返回对应组件
if (path === "error") {
return renderContent(ErrorPage);
} }
if (path === "about") { if (path === "about") {
return layout.render({ return renderContent(about);
children: about.render(args),
args,
});
} }
if (path === "post") { if (path === "post") {
return layout.render({ return renderContent(post);
children: post.render(args),
args,
});
} }
return layout.render({ return renderContent(article);
children: article.render(args), });
args,
}); export default Routes;
}

View File

@ -224,7 +224,7 @@ const getOptimalImageParams = (width: number, height: number) => {
const pixelRatio = window.devicePixelRatio || 1; const pixelRatio = window.devicePixelRatio || 1;
const isMobile = window.innerWidth <= 768; const isMobile = window.innerWidth <= 768;
// 移动端使用更大的采样间隔来减少<EFBFBD><EFBFBD><EFBFBD>数量 // 移动端使用更大的采样间隔来减少数量
let samplingGap = isMobile let samplingGap = isMobile
? Math.ceil(Math.max(width, height) / 60) // 移动端降低采样密度 ? Math.ceil(Math.max(width, height) / 60) // 移动端降低采样密度
: Math.ceil(Math.max(width, height) / 120); // 桌面端保持较高采密度 : Math.ceil(Math.max(width, height) / 120); // 桌面端保持较高采密度
@ -394,15 +394,55 @@ export const ParticleImage = ({
// 清理场景资源 // 清理场景资源
if (sceneRef.current) { if (sceneRef.current) {
cleanupResources(sceneRef.current); // 遍历场景中的所有对象
sceneRef.current.traverse((object) => {
if (object instanceof THREE.Points) {
const geometry = object.geometry;
const material = object.material as THREE.PointsMaterial;
// 清理几何体
if (geometry) {
// 清空缓冲区数据
if (geometry.attributes.position) {
geometry.attributes.position.array = new Float32Array(0);
}
if (geometry.attributes.color) {
geometry.attributes.color.array = new Float32Array(0);
}
// 移除所有属性
geometry.deleteAttribute('position');
geometry.deleteAttribute('color');
geometry.dispose();
}
// 清理材质
if (material) {
material.dispose();
}
}
});
// 清空场景
while(sceneRef.current.children.length > 0) {
sceneRef.current.remove(sceneRef.current.children[0]);
}
} }
// 修改渲染器清理逻辑 // 修改渲染器清理逻辑
if (rendererRef.current) { if (rendererRef.current) {
const renderer = rendererRef.current; const renderer = rendererRef.current;
const domElement = renderer.domElement;
// 使用 requestAnimationFrame 确保在一帧进<E5B8A7><E8BF9B> DOM 操作 // 确保在移除 DOM 元素前停止渲染
renderer.setAnimationLoop(null);
// 清理渲染器上下文
renderer.dispose();
renderer.forceContextLoss();
// 安全地移除 DOM 元素
const domElement = renderer.domElement;
if (containerRef.current?.contains(domElement)) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (isMountedRef.current && containerRef.current?.contains(domElement)) { if (isMountedRef.current && containerRef.current?.contains(domElement)) {
try { try {
@ -411,13 +451,17 @@ export const ParticleImage = ({
console.warn('清理渲染器DOM元素失败:', e); console.warn('清理渲染器DOM元素失败:', e);
} }
} }
renderer.dispose();
renderer.forceContextLoss();
}); });
}
// 清空引用
rendererRef.current = undefined; rendererRef.current = undefined;
} }
// 清理相机引用
if (cameraRef.current) {
cameraRef.current = undefined;
}
}, []); }, []);
// 修改 useEffect 的清理 // 修改 useEffect 的清理
@ -1013,7 +1057,7 @@ export const ImageLoader = ({
}); });
}; };
// 确保src存在再设<EFBFBD><EFBFBD><EFBFBD> // 确保src存在再设
if (src) { if (src) {
img.src = src; img.src = src;
} }

View File

@ -1,103 +0,0 @@
import React, { createContext, useState, useContext } from "react";
interface LoadingContextType {
isLoading: boolean;
showLoading: () => void;
hideLoading: () => void;
}
const LoadingContext = createContext<LoadingContextType>({
isLoading: false,
showLoading: () => {},
hideLoading: () => {},
});
export const LoadingProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [isLoading, setIsLoading] = useState(false);
const showLoading = () => setIsLoading(true);
const hideLoading = () => setIsLoading(false);
return (
<LoadingContext.Provider value={{ isLoading, showLoading, hideLoading }}>
{children}
{isLoading && (
<div className="fixed inset-0 flex flex-col items-center justify-center bg-black/25 dark:bg-black/40 z-[999999]">
<div className="loading-spinner mb-2" />
<div className="text-custom-p-light dark:text-custom-p-dark text-sm">
...
</div>
</div>
)}
<style>{`
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-spinner {
width: 30px;
height: 30px;
border: 3px solid rgba(59, 130, 246, 0.2);
border-radius: 50%;
border-top-color: #3B82F6;
animation: spin 0.8s linear infinite;
}
.dark .loading-spinner {
border: 3px solid rgba(96, 165, 250, 0.2);
border-top-color: #60A5FA;
}
`}</style>
</LoadingContext.Provider>
);
};
// 全局loading实例
let globalShowLoading: (() => void) | null = null;
let globalHideLoading: (() => void) | null = null;
export const LoadingContainer: React.FC = () => {
const { showLoading, hideLoading } = useContext(LoadingContext);
React.useEffect(() => {
globalShowLoading = showLoading;
globalHideLoading = hideLoading;
return () => {
globalShowLoading = null;
globalHideLoading = null;
};
}, [showLoading, hideLoading]);
return null;
};
// 导出loading方法
export const loading = {
show: () => {
if (!globalShowLoading) {
console.warn("Loading system not initialized");
return;
}
globalShowLoading();
},
hide: () => {
if (!globalHideLoading) {
console.warn("Loading system not initialized");
return;
}
globalHideLoading();
},
};

View File

@ -1,10 +1,17 @@
import { HttpClient } from "core/http"; import { HttpClient } from "core/http";
import { CapabilityService } from "core/capability"; import { CapabilityService } from "core/capability";
import { Serializable } from "interface/serializableType"; import { Serializable } from "interface/serializableType";
import { createElement, memo } from 'react';
export class Layout { export class Layout {
private http: HttpClient; private http: HttpClient;
private capability: CapabilityService; private capability: CapabilityService;
private readonly MemoizedElement: React.MemoExoticComponent<(props: {
children: React.ReactNode;
args?: Serializable;
onTouchStart?: (e: TouchEvent) => void;
onTouchEnd?: (e: TouchEvent) => void;
}) => React.ReactNode>;
constructor( constructor(
public element: (props: { public element: (props: {
@ -20,6 +27,7 @@ export class Layout {
) { ) {
this.http = services?.http || HttpClient.getInstance(); this.http = services?.http || HttpClient.getInstance();
this.capability = services?.capability || CapabilityService.getInstance(); this.capability = services?.capability || CapabilityService.getInstance();
this.MemoizedElement = memo(element);
} }
render(props: { render(props: {
@ -28,7 +36,7 @@ export class Layout {
onTouchStart?: (e: TouchEvent) => void; onTouchStart?: (e: TouchEvent) => void;
onTouchEnd?: (e: TouchEvent) => void; onTouchEnd?: (e: TouchEvent) => void;
}) { }) {
return this.element({ return createElement(this.MemoizedElement, {
...props, ...props,
onTouchStart: props.onTouchStart, onTouchStart: props.onTouchStart,
onTouchEnd: props.onTouchEnd onTouchEnd: props.onTouchEnd

View File

@ -29,12 +29,16 @@
"gsap": "^3.12.5", "gsap": "^3.12.5",
"html-react-parser": "^5.1.19", "html-react-parser": "^5.1.19",
"isbot": "^4.1.0", "isbot": "^4.1.0",
"markdown-it": "^14.1.0",
"markdown-it-toc-done-right": "^4.2.0",
"r": "^0.0.5", "r": "^0.0.5",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.6.1", "react-syntax-highlighter": "^15.6.1",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.0",
"remark-parse": "^11.0.0",
"three": "^0.171.0" "three": "^0.171.0"
}, },
"devDependencies": { "devDependencies": {
@ -42,6 +46,7 @@
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/lodash": "^4.17.13", "@types/lodash": "^4.17.13",
"@types/markdown-it": "^14.1.2",
"@types/react": "^18.2.20", "@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/eslint-plugin": "^6.7.4",

View File

@ -8,7 +8,7 @@ import {
} from "@radix-ui/react-icons"; } from "@radix-ui/react-icons";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap"; import { gsap } from "gsap";
import { ParticleImage } from "hooks/particleImage"; import { ImageLoader } from "hooks/ParticleImage";
const socialLinks = [ const socialLinks = [
{ {
@ -93,7 +93,11 @@ export default new Template({}, ({ http, args }) => {
<Flex direction="column" align="center" className="text-center mb-16"> <Flex direction="column" align="center" className="text-center mb-16">
<Box className="w-40 h-40 mb-8 relative"> <Box className="w-40 h-40 mb-8 relative">
<div className="absolute inset-0 bg-gradient-to-br from-[rgb(10,37,77)] via-[rgb(8,27,57)] to-[rgb(2,8,23)] rounded-full overflow-hidden"> <div className="absolute inset-0 bg-gradient-to-br from-[rgb(10,37,77)] via-[rgb(8,27,57)] to-[rgb(2,8,23)] rounded-full overflow-hidden">
<ParticleImage src="/path/to/your/avatar.jpg" /> <ImageLoader
src="/images/avatar-placeholder.png"
alt="avatar"
className="w-full h-full"
/>
</div> </div>
</Box> </Box>

View File

@ -9,7 +9,7 @@ import {
import { Post, PostDisplay, Tag } from "interface/fields"; import { Post, PostDisplay, Tag } from "interface/fields";
import { useMemo } from "react"; import { useMemo } from "react";
import { ImageLoader } from "hooks/particleImage"; import { ImageLoader } from "hooks/ParticleImage";
import { getColorScheme, hashString } from "themes/echoes/utils/colorScheme"; import { getColorScheme, hashString } from "themes/echoes/utils/colorScheme";
// 修改模拟文章列表数据 // 修改模拟文章列表数据
@ -20,7 +20,7 @@ const mockArticles: PostDisplay[] = [
content: "在现代前端开发中,一个高效的工作流程对于提高开发效率至关重要...", content: "在现代前端开发中,一个高效的工作流程对于提高开发效率至关重要...",
authorName: "张三", authorName: "张三",
publishedAt: new Date("2024-03-15"), publishedAt: new Date("2024-03-15"),
coverImage: "", coverImage: "https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=500&auto=format",
metaKeywords: "", metaKeywords: "",
metaDescription: "", metaDescription: "",
status: "published", status: "published",
@ -42,7 +42,7 @@ const mockArticles: PostDisplay[] = [
content: "React 18 带来了许多令人兴奋的新特性,包括并发渲染、自动批处理更新...", content: "React 18 带来了许多令人兴奋的新特性,包括并发渲染、自动批处理更新...",
authorName: "李四", authorName: "李四",
publishedAt: new Date("2024-03-14"), publishedAt: new Date("2024-03-14"),
coverImage: "https://haowallpaper.com/link/common/file/previewFileIm", coverImage: "https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=500&auto=format",
metaKeywords: "", metaKeywords: "",
metaDescription: "", metaDescription: "",
status: "published", status: "published",
@ -63,7 +63,7 @@ const mockArticles: PostDisplay[] = [
content: "在这篇文章中,我们将探讨一些提高 JavaScript 性能的技巧和最佳实践...", content: "在这篇文章中,我们将探讨一些提高 JavaScript 性能的技巧和最佳实践...",
authorName: "王五", authorName: "王五",
publishedAt: new Date("2024-03-13"), publishedAt: new Date("2024-03-13"),
coverImage: "https://haowallpaper.com/link/common/file/previewFileImg/15789130517090624", coverImage: "https://images.unsplash.com/photo-1592609931095-54a2168ae893?w=500&auto=format",
metaKeywords: "", metaKeywords: "",
metaDescription: "", metaDescription: "",
status: "published", status: "published",
@ -84,7 +84,7 @@ const mockArticles: PostDisplay[] = [
content: "移动端开发中的各种适配问题及解决方案...", content: "移动端开发中的各种适配问题及解决方案...",
authorName: "田六", authorName: "田六",
publishedAt: new Date("2024-03-13"), publishedAt: new Date("2024-03-13"),
coverImage: "https://avatars.githubusercontent.com/u/2?v=4", coverImage: "https://images.unsplash.com/photo-1526498460520-4c246339dccb?w=500&auto=format",
metaKeywords: "", metaKeywords: "",
metaDescription: "", metaDescription: "",
status: "published", status: "published",
@ -105,7 +105,7 @@ const mockArticles: PostDisplay[] = [
content: "本文将深入探讨现代全栈开发的各个方面,包括前端框架选择、后端架构设计、数据库优化、微服务部署以及云原生实践...", content: "本文将深入探讨现代全栈开发的各个方面,包括前端框架选择、后端架构设计、数据库优化、微服务部署以及云原生实践...",
authorName: "赵七", authorName: "赵七",
publishedAt: new Date("2024-03-12"), publishedAt: new Date("2024-03-12"),
coverImage: "https://avatars.githubusercontent.com/u/3?v=4", coverImage: "https://images.unsplash.com/photo-1537432376769-00f5c2f4c8d2?w=500&auto=format",
metaKeywords: "", metaKeywords: "",
metaDescription: "", metaDescription: "",
status: "published", status: "published",
@ -136,7 +136,7 @@ const mockArticles: PostDisplay[] = [
content: "探索 TypeScript 的高级类型系统、装饰器、类型编程等特性,以及在大型项目中的最佳实践...", content: "探索 TypeScript 的高级类型系统、装饰器、类型编程等特性,以及在大型项目中的最佳实践...",
authorName: "孙八", authorName: "孙八",
publishedAt: new Date("2024-03-11"), publishedAt: new Date("2024-03-11"),
coverImage: "https://avatars.githubusercontent.com/u/4?v=4", coverImage: "https://images.unsplash.com/photo-1516116216624-53e697fedbea?w=500&auto=format",
metaKeywords: "", metaKeywords: "",
metaDescription: "", metaDescription: "",
status: "published", status: "published",
@ -160,7 +160,7 @@ const mockArticles: PostDisplay[] = [
content: "全面解析 Web 性能优化策略,包括资源加载优化、渲染性能优化、网络优化等多个维度...", content: "全面解析 Web 性能优化策略,包括资源加载优化、渲染性能优化、网络优化等多个维度...",
authorName: "周九", authorName: "周九",
publishedAt: new Date("2024-03-10"), publishedAt: new Date("2024-03-10"),
coverImage: "https://avatars.githubusercontent.com/u/5?v=4", coverImage: "https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=500&auto=format",
metaKeywords: "", metaKeywords: "",
metaDescription: "", metaDescription: "",
status: "published", status: "published",
@ -173,7 +173,7 @@ const mockArticles: PostDisplay[] = [
], ],
tags: [ tags: [
{ name: "性能监控" }, { name: "性能监控" },
{ name: "懒加" }, { name: "懒加<EFBFBD><EFBFBD><EFBFBD>" },
{ name: "缓存策略" }, { name: "缓存策略" },
{ name: "代码分割" } { name: "代码分割" }
] ]
@ -184,7 +184,7 @@ const mockArticles: PostDisplay[] = [
content: "详细介绍微前端的架构设计、实现方案、应用集成以及实际项目中的经验总结...", content: "详细介绍微前端的架构设计、实现方案、应用集成以及实际项目中的经验总结...",
authorName: "吴十", authorName: "吴十",
publishedAt: new Date("2024-03-09"), publishedAt: new Date("2024-03-09"),
coverImage: "https://avatars.githubusercontent.com/u/6?v=4", coverImage: "https://images.unsplash.com/photo-1517180102446-f3ece451e9d8?w=500&auto=format",
metaKeywords: "", metaKeywords: "",
metaDescription: "", metaDescription: "",
status: "published", status: "published",

View File

@ -1,6 +1,6 @@
import { Layout } from "interface/layout"; import { Layout } from "interface/layout";
import { ThemeModeToggle } from "hooks/themeMode"; import { ThemeModeToggle } from "hooks/ThemeMode";
import { Echoes } from "hooks/echoes"; import { Echoes } from "hooks/Echoes";
import { Container, Flex, Box, Link, TextField, Button } from "@radix-ui/themes"; import { Container, Flex, Box, Link, TextField, Button } from "@radix-ui/themes";
import { import {
MagnifyingGlassIcon, MagnifyingGlassIcon,
@ -187,8 +187,8 @@ export default new Layout(({ children, args }) => {
<Box <Box
className={`w-10 h-10 flex items-center justify-center ${ className={`w-10 h-10 flex items-center justify-center ${
scrollProgress > 0 scrollProgress > 0
? 'relative translate-x-0 opacity-100 transition-all duration-300 ease-out' ? 'block'
: 'pointer-events-none absolute translate-x-2 opacity-0 transition-all duration-300 ease-in' : 'hidden'
}`} }`}
> >
<Button <Button
@ -226,8 +226,8 @@ export default new Layout(({ children, args }) => {
<Box <Box
className={`w-10 h-10 flex items-center justify-center ${ className={`w-10 h-10 flex items-center justify-center ${
scrollProgress > 0 scrollProgress > 0
? 'relative translate-x-0 opacity-100 transition-all duration-300 ease-out' ? 'block'
: 'pointer-events-none absolute translate-x-2 opacity-0 transition-all duration-300 ease-in' : 'hidden'
}`} }`}
> >
<Button <Button
@ -275,27 +275,63 @@ export default new Layout(({ children, args }) => {
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
align="end" align="end"
sideOffset={5} sideOffset={5}
className="mt-2 p-3 min-w-[280px] rounded-md bg-[--gray-1] border border-[--gray-a5] shadow-lg animate-in fade-in slide-in-from-top-2" className="mt-2 min-w-[280px] rounded-md bg-[--gray-1] border border-[--gray-a5] shadow-lg animate-in fade-in slide-in-from-top-2"
> >
<Box className="flex flex-col gap-2 [&>a]:text-[--gray-12] [&>a]:transition-colors [&>a:hover]:text-[--accent-9]"> <Box className="flex flex-col">
{/* 导航链接区域 */}
<Box className="flex flex-col">
<Box className="flex flex-col [&>a]:px-4 [&>a]:py-2.5 [&>a]:text-[--gray-12] [&>a]:transition-colors [&>a:hover]:bg-[--gray-a3] [&>a]:text-lg [&>a]:text-center [&>a]:border-b [&>a]:border-[--gray-a5] [&>a:first-child]:rounded-t-md [&>a:last-child]:border-b-0">
{parse(navString)} {parse(navString)}
</Box> </Box>
<Box className="mt-3 pt-3 border-t border-[--gray-a5]"> </Box>
{/* 搜索框区域 */}
<Box className="p-4 border-t border-[--gray-a5]">
<TextField.Root <TextField.Root
size="2" size="2"
variant="surface" variant="surface"
placeholder="搜索..." placeholder="搜索..."
className="w-full [&_input]:pl-3" className="w-full [&_input]:pl-3 hover:border-[--accent-9] border transition-colors group"
id="search"
> >
<TextField.Slot <TextField.Slot
side="right" side="right"
className="p-2" className="p-2"
> >
<MagnifyingGlassIcon className="h-4 w-4 text-[--gray-a12]" /> <MagnifyingGlassIcon className="h-4 w-4 text-[--gray-11] transition-colors group-hover:text-[--accent-9]" />
</TextField.Slot> </TextField.Slot>
</TextField.Root> </TextField.Root>
</Box> </Box>
{/* 用户操作区域 */}
<Box className="p-4 border-t border-[--gray-a5]">
<Flex gap="3" align="center">
{/* 用户信息/登录按钮 - 占据 55% 宽度 */}
<Box className="w-[55%]">
<Button
variant="ghost"
className="w-full justify-start gap-2 text-[--gray-12] hover:text-[--accent-9] hover:bg-[--gray-a3] transition-colors"
>
{loginState ? (
<>
<AvatarIcon className="w-5 h-5" />
<span></span>
</>
) : (
<>
<PersonIcon className="w-5 h-5" />
<span>/</span>
</>
)}
</Button>
</Box>
{/* 主题切换按钮 - 占据剩余空间 */}
<Box className="flex-1 flex justify-end [&_button]:w-10 [&_button]:h-10 [&_svg]:w-5 [&_svg]:h-5 [&_button]:text-[--gray-12] [&_button:hover]:text-[--accent-9]">
<ThemeModeToggle />
</Box>
</Flex>
</Box>
</Box>
</DropdownMenuPrimitive.Content> </DropdownMenuPrimitive.Content>
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
</DropdownMenuPrimitive.Root> </DropdownMenuPrimitive.Root>

File diff suppressed because it is too large Load Diff

View File

@ -11,18 +11,26 @@ export function hashString(str: string): number {
export function getColorScheme(name: string) { export function getColorScheme(name: string) {
const colorSchemes = [ const colorSchemes = [
'amber', 'blue', 'crimson', 'cyan', 'grass', { name: 'amber', bg: 'bg-[--amber-a3]', text: 'text-[--amber-11]', border: 'border-[--amber-a6]' },
'green', 'indigo', 'orange', 'pink', 'purple' { name: 'blue', bg: 'bg-[--blue-a3]', text: 'text-[--blue-11]', border: 'border-[--blue-a6]' },
{ name: 'crimson', bg: 'bg-[--crimson-a3]', text: 'text-[--crimson-11]', border: 'border-[--crimson-a6]' },
{ name: 'cyan', bg: 'bg-[--cyan-a3]', text: 'text-[--cyan-11]', border: 'border-[--cyan-a6]' },
{ name: 'grass', bg: 'bg-[--grass-a3]', text: 'text-[--grass-11]', border: 'border-[--grass-a6]' },
{ name: 'mint', bg: 'bg-[--mint-a3]', text: 'text-[--mint-11]', border: 'border-[--mint-a6]' },
{ name: 'orange', bg: 'bg-[--orange-a3]', text: 'text-[--orange-11]', border: 'border-[--orange-a6]' },
{ name: 'pink', bg: 'bg-[--pink-a3]', text: 'text-[--pink-11]', border: 'border-[--pink-a6]' },
{ name: 'plum', bg: 'bg-[--plum-a3]', text: 'text-[--plum-11]', border: 'border-[--plum-a6]' },
{ name: 'violet', bg: 'bg-[--violet-a3]', text: 'text-[--violet-11]', border: 'border-[--violet-a6]' }
]; ];
const index = hashString(name) % colorSchemes.length; const index = hashString(name) % colorSchemes.length;
const color = colorSchemes[index]; const scheme = colorSchemes[index];
return { return {
bg: `bg-[--${color}-4]`, bg: scheme.bg,
text: `text-[--${color}-11]`, text: scheme.text,
border: `border-[--${color}-6]`, border: scheme.border,
hover: `hover:bg-[--${color}-5]`, hover: `hover:${scheme.bg.replace('3', '4')}`,
dot: `bg-current` dot: `bg-current`
}; };
} }

View File

@ -87,5 +87,17 @@ export default defineConfig(async ({ mode }) => {
}, },
publicDir: resolve(__dirname, "public"), publicDir: resolve(__dirname, "public"),
envPrefix: "VITE_", envPrefix: "VITE_",
build: {
rollupOptions: {
output: {
manualChunks: {
three: ['three'],
gsap: ['gsap']
}
}
},
// 优化大型依赖的处理
chunkSizeWarningLimit: 1000
}
}; };
}); });