diff --git a/frontend/hooks/ParticleImage.tsx b/frontend/hooks/ParticleImage.tsx
index 465007f..d151a83 100644
--- a/frontend/hooks/ParticleImage.tsx
+++ b/frontend/hooks/ParticleImage.tsx
@@ -447,7 +447,7 @@ export const ParticleImage = ({
}, [cleanup]);
// 修改 updateParticles 函数
- const updateParticles = useCallback((width: number, height: number) => {
+ const updateParticles = useCallback((width: number, height: number, instanceId: string) => {
if (!sceneRef.current || isAnimatingRef.current || !isMountedRef.current) return;
// 只有当src不为空时才执行cleanup
@@ -504,16 +504,16 @@ export const ParticleImage = ({
},
onComplete: () => {
completedAnimations++;
- // 当所有动画完成时设置标记
if (completedAnimations === totalAnimations && sceneRef.current) {
sceneRef.current.userData.isSmileComplete = true;
+ loadingQueue.remove('', instanceId);
}
}
});
});
}, [cleanup, src]);
- // 将 resize 处理逻辑移到组件顶层
+ // 将 handleResize 移到 ParticleImage 组件内部
const handleResize = useCallback(() => {
if (!containerRef.current || !cameraRef.current || !rendererRef.current ||
!sceneRef.current || !isMountedRef.current) return;
@@ -542,7 +542,7 @@ export const ParticleImage = ({
cleanupResources(sceneRef.current);
}
sceneRef.current.userData.previousSize = currentSize;
- updateParticles(width, height);
+ updateParticles(width, height, ''); // 传入空字符串作为 instanceId
}
}, [src, updateParticles]);
@@ -578,7 +578,7 @@ export const ParticleImage = ({
canvas: document.createElement('canvas')
});
- // 在初始化渲染器后立即添加错误检查
+ // 在初始化渲染后立即添加错误检查
if (!renderer.capabilities.isWebGL2) {
console.warn('WebGL2 not supported, falling back...');
renderer.dispose();
@@ -742,7 +742,7 @@ export const ParticleImage = ({
});
};
- // 加载图
+ // 加载
const img = new Image();
img.crossOrigin = 'anonymous';
@@ -757,8 +757,8 @@ export const ParticleImage = ({
const ctx = canvas.getContext('2d');
if (ctx) {
- // 增加一个小的边距以确保完全覆盖
- const padding = 2; // 添加2像素的内边距
+ // 增加一个小的边距以确保��盖
+ const padding = 2; // 添加2像素的距
canvas.width = width + padding * 2;
canvas.height = height + padding * 2;
@@ -823,8 +823,8 @@ export const ParticleImage = ({
delay: normalizedDistance * 0.3
});
- // 随机初始位置(根据距离调范围)
- const spread = 1 - normalizedDistance * 0.5; // 距离越远,始扩散越小
+ // 机初始位置(根据距离调范围)
+ const spread = 1 - normalizedDistance * 0.5; // 距离越远,扩散越小
positionArray.push(
(Math.random() - 0.5) * width * spread,
(Math.random() - 0.5) * height * spread,
@@ -925,17 +925,14 @@ export const ParticleImage = ({
.to(material, {
opacity: 0,
duration: 0.8
- }, "-=0.6"); // 提前开始消失
+ }, "-=0.6"); // 前开始消失
}
return { particles, positionArray, colorArray, particleSize };
}
};
- img.onerror = () => {
- clearTimeout(timeoutId);
- showErrorAnimation();
- };
+
img.src = src || '';
@@ -964,7 +961,7 @@ export const ParticleImage = ({
containerRef.current.removeChild(renderer.domElement);
renderer.dispose();
}
- // 清所有 GSAP 动画
+ // 清有 GSAP ���画
gsap.killTweensOf('*');
// 移除 resize 监听
@@ -978,7 +975,236 @@ export const ParticleImage = ({
return
;
};
-// 图片加载组件
+let instanceCounter = 0;
+
+// 添加性能检测函数
+const detectDevicePerformance = () => {
+ // 检查是否在浏览器环境
+ if (typeof window === 'undefined') {
+ return 1; // 服务器端返回默认值
+ }
+
+ try {
+ // 检查是否支持 hardwareConcurrency
+ const cores = navigator?.hardwareConcurrency || 2;
+
+ // 检查设备内 (如果支持)
+ const memory = (navigator as any)?.deviceMemory || 4;
+
+ // 检查是否为移动设备
+ const isMobile = typeof navigator !== 'undefined'
+ ? /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
+ : false;
+
+ // 基于性能指标计算并发数
+ let concurrent = 1; // 默认值
+
+ if (!isMobile) {
+ if (cores >= 8 && memory >= 8) {
+ concurrent = 4; // 高性能设备
+ } else if (cores >= 4 && memory >= 4) {
+ concurrent = 3; // 中等性能设备
+ } else {
+ concurrent = 2; // 较低性能设备
+ }
+ }
+
+ return concurrent;
+ } catch (error) {
+ console.warn('性能检测失败,使用默认值:', error);
+ return 1; // 出错时返回最保守的值
+ }
+};
+
+// 修改队列管理
+const loadingQueue = {
+ items: new Map(),
+ pendingQueue: new Set(), // 备选队列,存储已加载完成的长连接
+ maxConcurrent: 1,
+ currentProcessing: 0,
+
+ get availableSlots() {
+ // 只计算普通请求的槽位,排除错误和长连接
+ const normalProcessing = Array.from(this.items.values())
+ .filter(item =>
+ item.isProcessing &&
+ !item.isLongConnection
+ ).length;
+ return this.maxConcurrent - normalProcessing;
+ },
+
+ add(url: string, instanceId: string, isLongConnection = false) {
+ const key = `${instanceId}:${url}`;
+ if (!this.items.has(key)) {
+ console.log('[Queue] Adding:', key,
+ isLongConnection ? '(long connection)' : '',
+ 'Current processing:', this.currentProcessing,
+ 'Max concurrent:', this.maxConcurrent
+ );
+
+ this.items.set(key, {
+ isProcessing: false,
+ instanceId,
+ isLongConnection,
+ lastActiveTime: Date.now()
+ });
+
+ // 连接不再直接进入备选队列,而是等待加载完成后再加入
+ this.processQueue();
+ return true;
+ }
+ return false;
+ },
+
+ processQueue() {
+ console.log('[Queue] Processing queue:', {
+ availableSlots: this.availableSlots,
+ currentProcessing: this.currentProcessing,
+ pendingQueueSize: this.pendingQueue.size,
+ totalItems: this.items.size
+ });
+
+ if (this.availableSlots > 0) {
+ let nextKey: string | undefined;
+
+ // 优先从备选队列中获取已加载完成的长连接
+ if (this.pendingQueue.size > 0) {
+ nextKey = Array.from(this.pendingQueue)[0];
+ this.pendingQueue.delete(nextKey);
+ console.log('[Queue] Processing from pending queue:', nextKey);
+ } else {
+ // 如果没有待处理的长连接,处理普通请求
+ const normalItem = Array.from(this.items.entries())
+ .find(([_, item]) =>
+ !item.isProcessing &&
+ !item.isLongConnection
+ );
+ if (normalItem) {
+ nextKey = normalItem[0];
+ console.log('[Queue] Processing normal request:', nextKey);
+ }
+ }
+
+ if (nextKey) {
+ this.startProcessing(nextKey);
+ }
+ }
+ },
+
+ startProcessing(key: string) {
+ const item = this.items.get(key);
+ if (item && !item.isProcessing) {
+ console.log('[Queue] Start processing:', key, {
+ isLongConnection: item.isLongConnection,
+ isError: key.includes('error')
+ });
+
+ item.isProcessing = true;
+
+ // 只有普通请求且不是错误状态时才增加处理数量
+ if (!item.isLongConnection && !key.includes('error')) {
+ this.currentProcessing++;
+ console.log('[Queue] Increased processing count:', this.currentProcessing);
+ }
+ }
+ },
+
+ // 添加新方法:将长连接添加到备选队列
+ addToPending(url: string, instanceId: string) {
+ const key = `${instanceId}:${url}`;
+ const item = this.items.get(key);
+ if (item?.isLongConnection) {
+ this.pendingQueue.add(key);
+ console.log('[Queue] Added to pending queue:', key);
+ }
+ },
+
+ remove(url: string, instanceId: string) {
+ const key = `${instanceId}:${url}`;
+ const item = this.items.get(key);
+
+ console.log('[Queue] Removing:', key,
+ 'Is long connection:', item?.isLongConnection,
+ 'Was processing:', item?.isProcessing,
+ 'Has error:', key.includes('error')
+ );
+
+ // 只有普通请求且正在处理时才减少处理数量
+ if (item?.isProcessing && !item.isLongConnection && !key.includes('error')) {
+ this.currentProcessing--;
+ console.log('[Queue] Decreased processing count:', this.currentProcessing);
+ }
+
+ // 确保从队列中移除
+ this.items.delete(key);
+ this.pendingQueue.delete(key);
+
+ console.log('[Queue] After remove - Processing:', this.currentProcessing,
+ 'Pending queue size:', this.pendingQueue.size,
+ 'Total items:', this.items.size
+ );
+
+ // 如果是错误状态,立即处理下一个请求
+ if (key.includes('error')) {
+ this.processQueue();
+ } else {
+ // 使用 requestAnimationFrame 来确保状态更新后再处理队列
+ requestAnimationFrame(() => {
+ this.processQueue();
+ });
+ }
+ },
+
+ canProcess(url: string, instanceId: string): boolean {
+ const key = `${instanceId}:${url}`;
+ const item = this.items.get(key);
+
+ // 错误状态不占用槽位,只在第一次检查时输出日志
+ if (key.includes('error')) {
+ if (!item?.lastLogTime) {
+ console.log('[Queue] Can process (error):', key, true);
+ if (item) {
+ item.lastLogTime = Date.now();
+ }
+ }
+ return true;
+ }
+
+ // 长连接在备选队列中时可以处理
+ if (item?.isLongConnection && this.pendingQueue.has(key)) {
+ if (!item.lastLogTime || Date.now() - item.lastLogTime > 1000) {
+ console.log('[Queue] Can process (pending long):', key, true);
+ item.lastLogTime = Date.now();
+ }
+ return true;
+ }
+
+ const canProcess = item?.isProcessing || false;
+ // 只在状态发生变化时或者每秒最多输出一次日志
+ if (item &&
+ (item.lastProcessState !== canProcess ||
+ !item.lastLogTime ||
+ Date.now() - item.lastLogTime > 1000)) {
+ console.log('[Queue] Can process (normal):', key, canProcess);
+ item.lastProcessState = canProcess;
+ item.lastLogTime = Date.now();
+ }
+ return canProcess;
+ }
+};
+
+// 在组件挂载时更新 maxConcurrent
+if (typeof window !== 'undefined') {
+ loadingQueue.maxConcurrent = detectDevicePerformance();
+}
+
export const ImageLoader = ({
src,
alt,
@@ -988,6 +1214,9 @@ export const ImageLoader = ({
alt: string;
className: string;
}) => {
+ // 为每个实例创唯一ID
+ const instanceId = useRef(`img-${++instanceCounter}`);
+ // 保持现有的状态和引用
const [status, setStatus] = useState({
isLoading: true,
hasError: false,
@@ -1000,6 +1229,65 @@ export const ImageLoader = ({
const containerRef = useRef(null);
const [animationComplete, setAnimationComplete] = useState(false);
+ // 把 useEffect 移到这里
+ useEffect(() => {
+ if (!src) return;
+
+ console.log('[Queue] Effect triggered for:', src, 'Instance:', instanceId.current);
+ setShowImage(false);
+ setAnimationComplete(false);
+ setCanShowParticles(false);
+
+ loadingQueue.add(src, instanceId.current);
+
+ const checkQueue = () => {
+ if (loadingQueue.canProcess(src, instanceId.current)) {
+ const now = Date.now();
+ const timeSinceLastAnimation = now - lastAnimationTime;
+
+ if (particleLoadQueue.size === 0) {
+ console.log('[Queue] Starting immediate animation for:', src, 'Instance:', instanceId.current);
+ particleLoadQueue.add(src);
+ setCanShowParticles(true);
+ lastAnimationTime = now;
+ return;
+ }
+
+ const delay = Math.max(
+ MIN_DELAY,
+ Math.min(ANIMATION_THRESHOLD, timeSinceLastAnimation)
+ );
+
+ console.log('[Queue] Scheduling delayed animation for:', src, 'Instance:', instanceId.current);
+ const timer = setTimeout(() => {
+ const key = `${instanceId.current}:${src}`;
+ if (!loadingQueue.items.has(key)) return;
+
+ console.log('[Queue] Starting delayed animation for:', src, 'Instance:', instanceId.current);
+ particleLoadQueue.add(src);
+ setCanShowParticles(true);
+ lastAnimationTime = Date.now();
+ }, delay);
+
+ return () => {
+ clearTimeout(timer);
+ particleLoadQueue.delete(src);
+ };
+ }
+
+ const timer = setTimeout(checkQueue, 100);
+ return () => clearTimeout(timer);
+ };
+
+ const cleanup = checkQueue();
+
+ return () => {
+ console.log('[Queue] Cleanup effect for:', src, 'Instance:', instanceId.current);
+ cleanup?.();
+ loadingQueue.remove(src, instanceId.current);
+ };
+ }, [src]);
+
// 处理图片加载
const preloadImage = useCallback(() => {
if (!src || loadingRef.current) return;
@@ -1008,96 +1296,125 @@ export const ImageLoader = ({
// 清理之前的资源
if (imageRef.current) {
- imageRef.current.src = '';
- imageRef.current = null;
+ imageRef.current.src = '';
+ imageRef.current = null;
}
if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
+ clearTimeout(timeoutRef.current);
}
setStatus({
- isLoading: true,
- hasError: false,
- timeoutError: false
+ isLoading: true,
+ hasError: false,
+ timeoutError: false
});
setShowImage(false);
const img = new Image();
img.crossOrigin = 'anonymous';
- img.onload = () => {
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
- }
-
- // 在图片加载成功后,立即创建和缓存一个适应容器大小的图片
- if (containerRef.current) {
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
+ timeoutRef.current = setTimeout(() => {
+ loadingRef.current = false;
+ setStatus({
+ isLoading: false,
+ hasError: true,
+ timeoutError: true
+ });
- if (ctx) {
- const containerWidth = containerRef.current.offsetWidth;
- const containerHeight = containerRef.current.offsetHeight;
-
- canvas.width = containerWidth;
- canvas.height = containerHeight;
-
- // 保持比例绘制图片
- const targetAspect = containerWidth / containerHeight;
- const imgAspect = img.width / img.height;
-
- let sourceWidth = img.width;
- let sourceHeight = img.height;
- let sourceX = 0;
- let sourceY = 0;
-
- if (imgAspect > targetAspect) {
- sourceWidth = img.height * targetAspect;
- sourceX = (img.width - sourceWidth) / 2;
- } else {
- sourceHeight = img.width / targetAspect;
- sourceY = (img.height - sourceHeight) / 2;
- }
-
- ctx.drawImage(
- img,
- sourceX, sourceY, sourceWidth, sourceHeight,
- 0, 0, containerWidth, containerHeight
- );
-
- // 创建新的图片对象,使用调整后的canvas数据
- const adjustedImage = new Image();
- adjustedImage.src = canvas.toDataURL();
- imageRef.current = adjustedImage;
+ // 超时时触发错误动画
+ setCanShowParticles(true);
+ if (src) {
+ loadingQueue.remove(src, instanceId.current);
+ particleLoadQueue.delete(src);
}
- } else {
- imageRef.current = img;
- }
+ }, 5000);
- loadingRef.current = false;
- setStatus({
- isLoading: false,
- hasError: false,
- timeoutError: false
- });
+ img.onload = () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+
+ // 在图片加载成功后,立即创建缓存一个适应容器大小的图片
+ if (containerRef.current) {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ if (ctx) {
+ const containerWidth = containerRef.current.offsetWidth;
+ const containerHeight = containerRef.current.offsetHeight;
+
+ canvas.width = containerWidth;
+ canvas.height = containerHeight;
+
+ // 保持比例绘制图片
+ const targetAspect = containerWidth / containerHeight;
+ const imgAspect = img.width / img.height;
+
+ let sourceWidth = img.width;
+ let sourceHeight = img.height;
+ let sourceX = 0;
+ let sourceY = 0;
+
+ if (imgAspect > targetAspect) {
+ sourceWidth = img.height * targetAspect;
+ sourceX = (img.width - sourceWidth) / 2;
+ } else {
+ sourceHeight = img.width / targetAspect;
+ sourceY = (img.height - sourceHeight) / 2;
+ }
+
+ ctx.drawImage(
+ img,
+ sourceX, sourceY, sourceWidth, sourceHeight,
+ 0, 0, containerWidth, containerHeight
+ );
+
+ // 创建新的图片对象,使用调整后的canvas数据
+ const adjustedImage = new Image();
+ adjustedImage.src = canvas.toDataURL();
+ imageRef.current = adjustedImage;
+ }
+ } else {
+ imageRef.current = img;
+ }
+
+ // 如果是长连接,加载成功后添加到备选队列
+ if (src && src === src) { // 相同URL判断
+ loadingQueue.addToPending(src, instanceId.current);
+ }
+
+ loadingRef.current = false;
+ setStatus({
+ isLoading: false,
+ hasError: false,
+ timeoutError: false
+ });
};
img.onerror = () => {
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
- }
- loadingRef.current = false;
- setStatus({
- isLoading: false,
- hasError: true,
- timeoutError: false
- });
+ console.log('[Image Loader] Error loading image:', src);
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ loadingRef.current = false;
+ setStatus({
+ isLoading: false,
+ hasError: true,
+ timeoutError: false
+ });
+
+ // 错误时立即触发错误动画
+ setCanShowParticles(true);
+
+ if (src) {
+ loadingQueue.remove(src, instanceId.current);
+ particleLoadQueue.delete(src);
+ }
};
- // 确保src存在再设置
if (src) {
- img.src = src;
+ img.src = src;
}
}, [src]);
@@ -1114,67 +1431,43 @@ export const ImageLoader = ({
// 添加一个新的状来控制粒子动画
const [canShowParticles, setCanShowParticles] = useState(false);
-
- useEffect(() => {
- if (!src) return;
-
- // 重置状
- setShowImage(false);
- setAnimationComplete(false);
- setCanShowParticles(false);
-
- const now = Date.now();
- const timeSinceLastAnimation = now - lastAnimationTime;
-
- if (particleLoadQueue.size === 0) {
- particleLoadQueue.add(src);
- setCanShowParticles(true);
- lastAnimationTime = now;
- return;
- }
-
- const delay = Math.max(
- MIN_DELAY,
- Math.min(ANIMATION_THRESHOLD, timeSinceLastAnimation)
- );
-
- const timer = setTimeout(() => {
- particleLoadQueue.add(src);
- setCanShowParticles(true);
- lastAnimationTime = Date.now();
- }, delay);
-
- return () => {
- clearTimeout(timer);
- particleLoadQueue.delete(src);
- };
- }, [src]);
+
+ // 添加加载动画组件
+ const LoadingSpinner = () => (
+
+ );
return (
- {(!src || (!animationComplete && canShowParticles)) && (
+ {src && (status.isLoading || !canShowParticles) &&
}
+
+ {(!src || (src && !animationComplete && canShowParticles)) && (
{
+ console.log('[ParticleImage] onLoad START:', src);
if (imageRef.current) {
// 保持为空
}
+ console.log('[ParticleImage] onLoad END:', src);
}}
onAnimationComplete={() => {
+ console.log('[ParticleImage] Animation START:', src);
if (imageRef.current && src) {
- // 先显示图片,保持透明
setShowImage(true);
- // 等待一帧确保图片已经渲染
requestAnimationFrame(() => {
- // 标记动画完成,触发粒子消失
+ console.log('[ParticleImage] Setting animation complete:', src);
setAnimationComplete(true);
particleLoadQueue.delete(src);
+ loadingQueue.remove(src, instanceId.current);
- // 给图一个短暂延迟再开始淡入
setTimeout(() => {
+ console.log('[ParticleImage] Fading image:', src);
const img = document.querySelector(`img[src="${imageRef.current?.src}"]`) as HTMLImageElement;
if (img) {
img.style.opacity = '1';
@@ -1182,19 +1475,18 @@ export const ImageLoader = ({
}, 50);
});
}
+ console.log('[ParticleImage] Animation END:', src);
}}
/>
)}
+ {/* 保持现图片渲染部分 */}
{!status.hasError && !status.timeoutError && imageRef.current && (
diff --git a/frontend/themes/echoes/post.tsx b/frontend/themes/echoes/post.tsx
index dcb038a..b664cb1 100644
--- a/frontend/themes/echoes/post.tsx
+++ b/frontend/themes/echoes/post.tsx
@@ -837,7 +837,9 @@ export default new Template({}, ({ http, args }) => {
details: ({ node, ...props }: ComponentPropsWithoutRef<'details'> & { node?: any }) => (
*:not(summary)]:px-10 [&>*:not(summary)]:py-3
+ "
{...props}
/>
),
diff --git a/frontend/themes/echoes/posts.tsx b/frontend/themes/echoes/posts.tsx
index ccf461e..0d4cba8 100644
--- a/frontend/themes/echoes/posts.tsx
+++ b/frontend/themes/echoes/posts.tsx
@@ -19,7 +19,7 @@ const mockArticles: PostDisplay[] = [
content: "在现代前端开发中,一个高效的工作流程对于提高开发效率至关重要...",
authorName: "张三",
publishedAt: new Date("2024-03-15"),
- coverImage: "https://avatars.githubusercontent.com/u/72159?v=4",
+ coverImage: "https://www.helloimg.com/i/2024/12/11/6759312352499.png",
status: "published",
isEditor: false,
createdAt: new Date("2024-03-15"),
@@ -199,6 +199,30 @@ const mockArticles: PostDisplay[] = [
{ name: "应用通信", slug: "application-communication", type: "tag" }
]
}
+ },
+ {
+ id: 9,
+ title: "AI 驱动的前端开发:从概念到实践",
+ content: "探索如何将人工智能技术融入前端开发流程,包括智能代码补全、自动化测试、UI 生成、性能优化建议等实践应用...",
+ authorName: "陈十一",
+ publishedAt: new Date("2024-03-08"),
+ coverImage: "https://images.unsplash.com/photo-1677442136019-21780ecad995?w=500&auto=format",
+ status: "published",
+ isEditor: false,
+ createdAt: new Date("2024-03-08"),
+ updatedAt: new Date("2024-03-08"),
+ taxonomies: {
+ categories: [
+ { name: "人工智能", slug: "artificial-intelligence", type: "category" },
+ { name: "前端开发", slug: "frontend-development", type: "category" }
+ ],
+ tags: [
+ { name: "AI开发", slug: "ai-development", type: "tag" },
+ { name: "智能化", slug: "intelligence", type: "tag" },
+ { name: "自动化", slug: "automation", type: "tag" },
+ { name: "开发效率", slug: "development-efficiency", type: "tag" }
+ ]
+ }
}
];