前端:更新依赖项以支持Markdown解析和目录功能,优化Vite构建配置,重构文章展示逻辑,改进主题切换和样式,移除不再使用的加载组件,修复多个小问题。
This commit is contained in:
parent
2aaffb9e2b
commit
b3c0af91b8
@ -11,8 +11,6 @@ import { hydrateRoot } from "react-dom/client";
|
||||
startTransition(() => {
|
||||
hydrateRoot(
|
||||
document,
|
||||
<StrictMode>
|
||||
<RemixBrowser />
|
||||
</StrictMode>,
|
||||
<RemixBrowser />,
|
||||
);
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { createContext, useState } from "react";
|
||||
import React, { createContext, useEffect, useState } from "react";
|
||||
import { DEFAULT_CONFIG } from "app/env";
|
||||
import { HttpClient } from "core/http";
|
||||
import { ThemeModeToggle } from "hooks/themeMode";
|
||||
import { ThemeModeToggle } from "hooks/ThemeMode";
|
||||
import {
|
||||
Theme,
|
||||
Button,
|
||||
@ -13,8 +13,8 @@ import {
|
||||
Box,
|
||||
TextField,
|
||||
} from "@radix-ui/themes";
|
||||
import { toast } from "hooks/notification";
|
||||
import { Echoes } from "hooks/echoes";
|
||||
import { toast } from "hooks/Notification";
|
||||
import { Echoes } from "hooks/Echoes";
|
||||
|
||||
interface SetupContextType {
|
||||
currentStep: number;
|
||||
@ -36,7 +36,7 @@ const StepContainer: React.FC<{ title: string; children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => (
|
||||
<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}
|
||||
</Heading>
|
||||
<Flex direction="column" gap="4">
|
||||
@ -237,15 +237,27 @@ const DatabaseConfig: React.FC<StepProps> = ({ onNext }) => {
|
||||
<div>
|
||||
<Box mb="6">
|
||||
<Text as="label" size="2" weight="medium" mb="2" className="block">
|
||||
数据库类型
|
||||
数据库类型 <Text color="red">*</Text>
|
||||
</Text>
|
||||
<Select.Root value={dbType} onValueChange={setDbType}>
|
||||
<Select.Trigger />
|
||||
<Select.Content>
|
||||
<Select.Trigger className="w-full" />
|
||||
<Select.Content position="popper" sideOffset={8}>
|
||||
<Select.Group>
|
||||
<Select.Item value="postgresql">PostgreSQL</Select.Item>
|
||||
<Select.Item value="mysql">MySQL</Select.Item>
|
||||
<Select.Item value="sqllite">SQLite</Select.Item>
|
||||
<Select.Item value="postgresql">
|
||||
<Flex gap="2" align="center">
|
||||
<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.Content>
|
||||
</Select.Root>
|
||||
@ -461,9 +473,12 @@ const SetupComplete: React.FC = () => (
|
||||
);
|
||||
|
||||
export default function SetupPage() {
|
||||
const [currentStep, setCurrentStep] = useState(() => {
|
||||
return Number(import.meta.env.VITE_INIT_STATUS ?? 0) + 1;
|
||||
});
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
useEffect(() => {
|
||||
// 在客户端组件挂载后更新状态
|
||||
const initStatus = Number(import.meta.env.VITE_INIT_STATUS ?? 0) + 1;
|
||||
setCurrentStep(initStatus);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Theme
|
||||
|
@ -5,9 +5,9 @@ import {
|
||||
Scripts,
|
||||
ScrollRestoration,
|
||||
} from "@remix-run/react";
|
||||
import { NotificationProvider } from "hooks/notification";
|
||||
import { NotificationProvider } from "hooks/Notification";
|
||||
import { Theme } from "@radix-ui/themes";
|
||||
import { ThemeScript } from "hooks/themeMode";
|
||||
import { ThemeScript } from "hooks/ThemeMode";
|
||||
|
||||
import "~/index.css";
|
||||
|
||||
@ -49,6 +49,7 @@ export function Layout() {
|
||||
<body
|
||||
className="h-full"
|
||||
suppressHydrationWarning={true}
|
||||
data-cz-shortcut-listen="false"
|
||||
>
|
||||
<Theme
|
||||
grayColor="slate"
|
||||
|
@ -1,46 +1,47 @@
|
||||
import ErrorPage from "hooks/error";
|
||||
import ErrorPage from "hooks/Error";
|
||||
import layout from "themes/echoes/layout";
|
||||
import article from "themes/echoes/article";
|
||||
import about from "themes/echoes/about";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import post from "themes/echoes/post";
|
||||
|
||||
export default function Routes() {
|
||||
const location = useLocation();
|
||||
let path = location.pathname;
|
||||
import { memo, useCallback } from "react";
|
||||
|
||||
const args = {
|
||||
title: "我的页面",
|
||||
theme: "dark",
|
||||
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);
|
||||
path = path.split("/")[1];
|
||||
|
||||
if (path === "error") {
|
||||
const renderLayout = (children: React.ReactNode) => {
|
||||
return layout.render({
|
||||
children: ErrorPage.render(args),
|
||||
children,
|
||||
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") {
|
||||
return layout.render({
|
||||
children: about.render(args),
|
||||
args,
|
||||
});
|
||||
return renderContent(about);
|
||||
}
|
||||
|
||||
if (path === "post") {
|
||||
return layout.render({
|
||||
children: post.render(args),
|
||||
args,
|
||||
});
|
||||
return renderContent(post);
|
||||
}
|
||||
|
||||
return layout.render({
|
||||
children: article.render(args),
|
||||
args,
|
||||
return renderContent(article);
|
||||
});
|
||||
}
|
||||
|
||||
export default Routes;
|
||||
|
@ -224,7 +224,7 @@ const getOptimalImageParams = (width: number, height: number) => {
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
|
||||
// 移动端使用更大的采样间隔来减少<EFBFBD><EFBFBD><EFBFBD>数量
|
||||
// 移动端使用更大的采样间隔来减少数量
|
||||
let samplingGap = isMobile
|
||||
? Math.ceil(Math.max(width, height) / 60) // 移动端降低采样密度
|
||||
: Math.ceil(Math.max(width, height) / 120); // 桌面端保持较高采密度
|
||||
@ -394,15 +394,55 @@ export const ParticleImage = ({
|
||||
|
||||
// 清理场景资源
|
||||
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) {
|
||||
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(() => {
|
||||
if (isMountedRef.current && containerRef.current?.contains(domElement)) {
|
||||
try {
|
||||
@ -411,13 +451,17 @@ export const ParticleImage = ({
|
||||
console.warn('清理渲染器DOM元素失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
renderer.dispose();
|
||||
renderer.forceContextLoss();
|
||||
});
|
||||
}
|
||||
|
||||
// 清空引用
|
||||
rendererRef.current = undefined;
|
||||
}
|
||||
|
||||
// 清理相机引用
|
||||
if (cameraRef.current) {
|
||||
cameraRef.current = undefined;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 修改 useEffect 的清理
|
||||
@ -1013,7 +1057,7 @@ export const ImageLoader = ({
|
||||
});
|
||||
};
|
||||
|
||||
// 确保src存在再设<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 确保src存在再设置
|
||||
if (src) {
|
||||
img.src = src;
|
||||
}
|
||||
|
@ -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();
|
||||
},
|
||||
};
|
@ -1,10 +1,17 @@
|
||||
import { HttpClient } from "core/http";
|
||||
import { CapabilityService } from "core/capability";
|
||||
import { Serializable } from "interface/serializableType";
|
||||
import { createElement, memo } from 'react';
|
||||
|
||||
export class Layout {
|
||||
private http: HttpClient;
|
||||
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(
|
||||
public element: (props: {
|
||||
@ -20,6 +27,7 @@ export class Layout {
|
||||
) {
|
||||
this.http = services?.http || HttpClient.getInstance();
|
||||
this.capability = services?.capability || CapabilityService.getInstance();
|
||||
this.MemoizedElement = memo(element);
|
||||
}
|
||||
|
||||
render(props: {
|
||||
@ -28,7 +36,7 @@ export class Layout {
|
||||
onTouchStart?: (e: TouchEvent) => void;
|
||||
onTouchEnd?: (e: TouchEvent) => void;
|
||||
}) {
|
||||
return this.element({
|
||||
return createElement(this.MemoizedElement, {
|
||||
...props,
|
||||
onTouchStart: props.onTouchStart,
|
||||
onTouchEnd: props.onTouchEnd
|
||||
|
@ -29,12 +29,16 @@
|
||||
"gsap": "^3.12.5",
|
||||
"html-react-parser": "^5.1.19",
|
||||
"isbot": "^4.1.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-toc-done-right": "^4.2.0",
|
||||
"r": "^0.0.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"three": "^0.171.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -42,6 +46,7 @@
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/lodash": "^4.17.13",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
} from "@radix-ui/react-icons";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { gsap } from "gsap";
|
||||
import { ParticleImage } from "hooks/particleImage";
|
||||
import { ImageLoader } from "hooks/ParticleImage";
|
||||
|
||||
const socialLinks = [
|
||||
{
|
||||
@ -93,7 +93,11 @@ export default new Template({}, ({ http, args }) => {
|
||||
<Flex direction="column" align="center" className="text-center mb-16">
|
||||
<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">
|
||||
<ParticleImage src="/path/to/your/avatar.jpg" />
|
||||
<ImageLoader
|
||||
src="/images/avatar-placeholder.png"
|
||||
alt="avatar"
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
import { Post, PostDisplay, Tag } from "interface/fields";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { ImageLoader } from "hooks/particleImage";
|
||||
import { ImageLoader } from "hooks/ParticleImage";
|
||||
import { getColorScheme, hashString } from "themes/echoes/utils/colorScheme";
|
||||
|
||||
// 修改模拟文章列表数据
|
||||
@ -20,7 +20,7 @@ const mockArticles: PostDisplay[] = [
|
||||
content: "在现代前端开发中,一个高效的工作流程对于提高开发效率至关重要...",
|
||||
authorName: "张三",
|
||||
publishedAt: new Date("2024-03-15"),
|
||||
coverImage: "",
|
||||
coverImage: "https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=500&auto=format",
|
||||
metaKeywords: "",
|
||||
metaDescription: "",
|
||||
status: "published",
|
||||
@ -42,7 +42,7 @@ const mockArticles: PostDisplay[] = [
|
||||
content: "React 18 带来了许多令人兴奋的新特性,包括并发渲染、自动批处理更新...",
|
||||
authorName: "李四",
|
||||
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: "",
|
||||
metaDescription: "",
|
||||
status: "published",
|
||||
@ -63,7 +63,7 @@ const mockArticles: PostDisplay[] = [
|
||||
content: "在这篇文章中,我们将探讨一些提高 JavaScript 性能的技巧和最佳实践...",
|
||||
authorName: "王五",
|
||||
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: "",
|
||||
metaDescription: "",
|
||||
status: "published",
|
||||
@ -84,7 +84,7 @@ const mockArticles: PostDisplay[] = [
|
||||
content: "移动端开发中的各种适配问题及解决方案...",
|
||||
authorName: "田六",
|
||||
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: "",
|
||||
metaDescription: "",
|
||||
status: "published",
|
||||
@ -105,7 +105,7 @@ const mockArticles: PostDisplay[] = [
|
||||
content: "本文将深入探讨现代全栈开发的各个方面,包括前端框架选择、后端架构设计、数据库优化、微服务部署以及云原生实践...",
|
||||
authorName: "赵七",
|
||||
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: "",
|
||||
metaDescription: "",
|
||||
status: "published",
|
||||
@ -136,7 +136,7 @@ const mockArticles: PostDisplay[] = [
|
||||
content: "探索 TypeScript 的高级类型系统、装饰器、类型编程等特性,以及在大型项目中的最佳实践...",
|
||||
authorName: "孙八",
|
||||
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: "",
|
||||
metaDescription: "",
|
||||
status: "published",
|
||||
@ -160,7 +160,7 @@ const mockArticles: PostDisplay[] = [
|
||||
content: "全面解析 Web 性能优化策略,包括资源加载优化、渲染性能优化、网络优化等多个维度...",
|
||||
authorName: "周九",
|
||||
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: "",
|
||||
metaDescription: "",
|
||||
status: "published",
|
||||
@ -173,7 +173,7 @@ const mockArticles: PostDisplay[] = [
|
||||
],
|
||||
tags: [
|
||||
{ name: "性能监控" },
|
||||
{ name: "懒加载" },
|
||||
{ name: "懒加<EFBFBD><EFBFBD><EFBFBD>" },
|
||||
{ name: "缓存策略" },
|
||||
{ name: "代码分割" }
|
||||
]
|
||||
@ -184,7 +184,7 @@ const mockArticles: PostDisplay[] = [
|
||||
content: "详细介绍微前端的架构设计、实现方案、应用集成以及实际项目中的经验总结...",
|
||||
authorName: "吴十",
|
||||
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: "",
|
||||
metaDescription: "",
|
||||
status: "published",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Layout } from "interface/layout";
|
||||
import { ThemeModeToggle } from "hooks/themeMode";
|
||||
import { Echoes } from "hooks/echoes";
|
||||
import { ThemeModeToggle } from "hooks/ThemeMode";
|
||||
import { Echoes } from "hooks/Echoes";
|
||||
import { Container, Flex, Box, Link, TextField, Button } from "@radix-ui/themes";
|
||||
import {
|
||||
MagnifyingGlassIcon,
|
||||
@ -187,8 +187,8 @@ export default new Layout(({ children, args }) => {
|
||||
<Box
|
||||
className={`w-10 h-10 flex items-center justify-center ${
|
||||
scrollProgress > 0
|
||||
? 'relative translate-x-0 opacity-100 transition-all duration-300 ease-out'
|
||||
: 'pointer-events-none absolute translate-x-2 opacity-0 transition-all duration-300 ease-in'
|
||||
? 'block'
|
||||
: 'hidden'
|
||||
}`}
|
||||
>
|
||||
<Button
|
||||
@ -226,8 +226,8 @@ export default new Layout(({ children, args }) => {
|
||||
<Box
|
||||
className={`w-10 h-10 flex items-center justify-center ${
|
||||
scrollProgress > 0
|
||||
? 'relative translate-x-0 opacity-100 transition-all duration-300 ease-out'
|
||||
: 'pointer-events-none absolute translate-x-2 opacity-0 transition-all duration-300 ease-in'
|
||||
? 'block'
|
||||
: 'hidden'
|
||||
}`}
|
||||
>
|
||||
<Button
|
||||
@ -275,27 +275,63 @@ export default new Layout(({ children, args }) => {
|
||||
<DropdownMenuPrimitive.Content
|
||||
align="end"
|
||||
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)}
|
||||
</Box>
|
||||
<Box className="mt-3 pt-3 border-t border-[--gray-a5]">
|
||||
</Box>
|
||||
|
||||
{/* 搜索框区域 */}
|
||||
<Box className="p-4 border-t border-[--gray-a5]">
|
||||
<TextField.Root
|
||||
size="2"
|
||||
variant="surface"
|
||||
placeholder="搜索..."
|
||||
className="w-full [&_input]:pl-3"
|
||||
id="search"
|
||||
className="w-full [&_input]:pl-3 hover:border-[--accent-9] border transition-colors group"
|
||||
>
|
||||
<TextField.Slot
|
||||
side="right"
|
||||
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.Root>
|
||||
</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.Portal>
|
||||
</DropdownMenuPrimitive.Root>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,18 +11,26 @@ export function hashString(str: string): number {
|
||||
|
||||
export function getColorScheme(name: string) {
|
||||
const colorSchemes = [
|
||||
'amber', 'blue', 'crimson', 'cyan', 'grass',
|
||||
'green', 'indigo', 'orange', 'pink', 'purple'
|
||||
{ name: 'amber', bg: 'bg-[--amber-a3]', text: 'text-[--amber-11]', border: 'border-[--amber-a6]' },
|
||||
{ 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 color = colorSchemes[index];
|
||||
const scheme = colorSchemes[index];
|
||||
|
||||
return {
|
||||
bg: `bg-[--${color}-4]`,
|
||||
text: `text-[--${color}-11]`,
|
||||
border: `border-[--${color}-6]`,
|
||||
hover: `hover:bg-[--${color}-5]`,
|
||||
bg: scheme.bg,
|
||||
text: scheme.text,
|
||||
border: scheme.border,
|
||||
hover: `hover:${scheme.bg.replace('3', '4')}`,
|
||||
dot: `bg-current`
|
||||
};
|
||||
}
|
@ -87,5 +87,17 @@ export default defineConfig(async ({ mode }) => {
|
||||
},
|
||||
publicDir: resolve(__dirname, "public"),
|
||||
envPrefix: "VITE_",
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
three: ['three'],
|
||||
gsap: ['gsap']
|
||||
}
|
||||
}
|
||||
},
|
||||
// 优化大型依赖的处理
|
||||
chunkSizeWarningLimit: 1000
|
||||
}
|
||||
};
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user