前端:更新依赖项以支持Markdown解析和目录功能,优化Vite构建配置,重构文章展示逻辑,改进主题切换和样式,移除不再使用的加载组件,修复多个小问题。
This commit is contained in:
parent
2aaffb9e2b
commit
b3c0af91b8
@ -11,8 +11,6 @@ import { hydrateRoot } from "react-dom/client";
|
|||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
hydrateRoot(
|
hydrateRoot(
|
||||||
document,
|
document,
|
||||||
<StrictMode>
|
<RemixBrowser />,
|
||||||
<RemixBrowser />
|
|
||||||
</StrictMode>,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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;
|
||||||
}
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 { 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
|
||||||
|
@ -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",
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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
@ -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`
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -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
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user