From 68860d6be82783c8d57d3eeb078c438ad1546bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E6=80=9D?= Date: Tue, 23 Dec 2025 16:54:37 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=B7=B3=E8=BD=AC=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A6=96=E9=A1=B5=E7=9B=91=E5=90=AC=20?= =?UTF-8?q?=E5=85=B6=E4=BB=96bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/hooks/useIntersectionObserver.js | 41 ---- .../Main/projecthome/v2/components/banner.jsx | 8 +- .../v2/components/communityDynamic.jsx | 29 +-- .../v2/components/ecosystemAlliance.jsx | 10 +- .../Main/projecthome/v2/components/forum.jsx | 9 +- .../v2/components/moreContentButton.jsx | 2 +- .../components/openSourceCollaboration1.jsx | 28 +-- .../projecthome/v2/components/project.jsx | 9 +- .../v2/components/softwareFactory.jsx | 6 +- .../v2/components/supplyChainGovernance.jsx | 2 +- .../v2/components/terminalAnimation.jsx | 200 ------------------ .../v2/components/terminalAnimation1.jsx | 146 +++++++------ .../v2/components/terminalAnimation2.jsx | 167 ++++++++------- src/forge/Main/projecthome/v2/index.jsx | 51 ++++- src/forge/Main/projecthome/v2/index.scss | 37 ++-- src/forge/projectHome/explore/index.jsx | 65 ++---- src/forge/projectHome/explore/index.scss | 27 +++ src/news/list/index.jsx | 6 +- src/news/list/index.scss | 6 +- 19 files changed, 327 insertions(+), 522 deletions(-) delete mode 100644 src/common/hooks/useIntersectionObserver.js delete mode 100644 src/forge/Main/projecthome/v2/components/terminalAnimation.jsx diff --git a/src/common/hooks/useIntersectionObserver.js b/src/common/hooks/useIntersectionObserver.js deleted file mode 100644 index 5740f44f9..000000000 --- a/src/common/hooks/useIntersectionObserver.js +++ /dev/null @@ -1,41 +0,0 @@ -import { useState, useEffect, useRef, useCallback } from 'react'; - -const useIntersectionObserver = (options = {}) => { - const [isIntersecting, setIsIntersecting] = useState(false); - const elementRef = useRef(null); - const observerRef = useRef(null); - - useEffect(() => { - // 如果已有观察器,先断开 - if (observerRef.current) { - observerRef.current.disconnect(); - } - - const observerOptions = { - threshold: options.threshold || 0.1, - rootMargin: options.rootMargin || '0px', - root: options.root || null - }; - - observerRef.current = new IntersectionObserver(([entry]) => { - // 避免在组件卸载后设置状态 - if (elementRef.current) { - setIsIntersecting(entry.isIntersecting); - } - }, observerOptions); - - if (elementRef.current) { - observerRef.current.observe(elementRef.current); - } - - return () => { - if (observerRef.current) { - observerRef.current.disconnect(); - } - }; - }, [options.threshold, options.rootMargin, options.root]); - - return [elementRef, isIntersecting]; -}; - -export default useIntersectionObserver; \ No newline at end of file diff --git a/src/forge/Main/projecthome/v2/components/banner.jsx b/src/forge/Main/projecthome/v2/components/banner.jsx index 68ce03654..d3f0d1c20 100644 --- a/src/forge/Main/projecthome/v2/components/banner.jsx +++ b/src/forge/Main/projecthome/v2/components/banner.jsx @@ -4,7 +4,7 @@ import TerminalAnimation from './terminalAnimation2'; import ActionItem from '../../../../../factory/resource/components/ActionItem'; import './index.scss'; -function Banner() { +function Banner({isVisible}) { const lines1 = [ 'if(!isVisible)', ' return', @@ -35,7 +35,7 @@ function Banner() { '})', ] return ( -
+
{/* 主要内容区域 */}
@@ -72,10 +72,10 @@ function Banner() {
- +
- +
diff --git a/src/forge/Main/projecthome/v2/components/communityDynamic.jsx b/src/forge/Main/projecthome/v2/components/communityDynamic.jsx index 3d076e1e5..361d082cb 100644 --- a/src/forge/Main/projecthome/v2/components/communityDynamic.jsx +++ b/src/forge/Main/projecthome/v2/components/communityDynamic.jsx @@ -3,6 +3,7 @@ import React, { Fragment, useEffect, useState } from 'react'; import { getSubDocList, getNewsDetail } from '../../../../Information/api'; import { BrowserRouter as Router, Route, Link } from "react-router-dom"; import moment from 'moment'; +import moreContentButton from './moreContentButton'; const CommunityDynamic = () => { const [list1, setList1] = useState([]); @@ -11,6 +12,7 @@ const CommunityDynamic = () => { const [list3, setList3] = useState([]); const firstDynamics = list3[0] const firstByList1 = list1[0] || {} + const list2_home = list2.slice(0,4); useEffect(() => { getSubDocList(28).then(res => { @@ -43,7 +45,7 @@ const CommunityDynamic = () => { }, []) return ( -
+
社区动态
{/* 精选新闻 */} @@ -64,7 +66,7 @@ const CommunityDynamic = () => { {list1.slice(1,5).map((item, index) => (
-
{item.name}
+
{item.name}

{item.summary}

{index < 3 && } @@ -81,43 +83,46 @@ const CommunityDynamic = () => { {/* */} 精彩活动
    - {list2.slice(0,5).map((item, index) => ( + {list2_home.map((item, index) => (
  • - - {item.summary} + + {item.summary} {index === 1 && HOT} {/* {moment(item.publishTime).format('MM月DD日')} */} - {item.name} + {item.name}
  • ))}
- {list2.slice(0,5).map((item, index)=>{ + {list2_home.map((item, index)=>{ return })} -
{/* */} - XX项目动态 + 项目动态
    {list3.slice(0,4).map((item, index) => (
  • - - {item.name} + + {item.name} {index === 0 && NEW} - {moment(item.publishTime).format('YYYY-MM-DD')} + {moment(item.publishTime).format('YYYY-MM-DD')}
  • ))}
+ +
+ {moreContentButton({ href: '/news' })} +
); }; diff --git a/src/forge/Main/projecthome/v2/components/ecosystemAlliance.jsx b/src/forge/Main/projecthome/v2/components/ecosystemAlliance.jsx index 1878c6d3e..65bc04d5c 100644 --- a/src/forge/Main/projecthome/v2/components/ecosystemAlliance.jsx +++ b/src/forge/Main/projecthome/v2/components/ecosystemAlliance.jsx @@ -1,13 +1,7 @@ import { Button } from 'antd'; import React, { useState, useEffect, useRef } from 'react'; -import useIntersectionObserver from '../../../../../common/hooks/useIntersectionObserver'; - const EcosystemAlliance = () => { const [activeTab, setActiveTab] = useState('partners'); // 默认显示合作伙伴 - const [elementRef, isVisible] = useIntersectionObserver({ - threshold: 0.2, - rootMargin: '0px 0px -50px 0px' - }); // const partners = [ // { name: '国防科技大学', logo: require('../image/logo1.png') }, @@ -68,7 +62,7 @@ const EcosystemAlliance = () => { }; return ( -
+
生态联盟
@@ -91,7 +85,7 @@ const EcosystemAlliance = () => {
-
+
{partners1.map((i,key)=>{ return
{i.title}
diff --git a/src/forge/Main/projecthome/v2/components/forum.jsx b/src/forge/Main/projecthome/v2/components/forum.jsx index b90df3962..b9661ccf1 100644 --- a/src/forge/Main/projecthome/v2/components/forum.jsx +++ b/src/forge/Main/projecthome/v2/components/forum.jsx @@ -1,5 +1,6 @@ import { Divider, Icon } from 'antd'; import React, { Fragment } from 'react'; +import moreContentButton from './moreContentButton'; const ForumExchange = () => { const forums = [ @@ -51,11 +52,8 @@ const ForumExchange = () => { ]; return ( -
+
论坛交流
-
{forums.map((forum, index) => ( @@ -85,6 +83,9 @@ const ForumExchange = () => { ))}
+
+ {moreContentButton({href:"/forums", classname:"font-16 but-to-right font-bd color-white"})} +
); }; diff --git a/src/forge/Main/projecthome/v2/components/moreContentButton.jsx b/src/forge/Main/projecthome/v2/components/moreContentButton.jsx index 850314c85..c540a57e7 100644 --- a/src/forge/Main/projecthome/v2/components/moreContentButton.jsx +++ b/src/forge/Main/projecthome/v2/components/moreContentButton.jsx @@ -2,7 +2,7 @@ import { Icon } from "antd"; import React from "react"; import { Link } from "react-router-dom"; -function moreContentButton({href="/", name="探索更多内容", classname="font-16 color-white but-to-right"}){ +function moreContentButton({href="/", name="探索更多内容", classname="font-16 but-to-right font-bd"}){ return {name} } diff --git a/src/forge/Main/projecthome/v2/components/openSourceCollaboration1.jsx b/src/forge/Main/projecthome/v2/components/openSourceCollaboration1.jsx index 799ad7469..3a2578fe1 100644 --- a/src/forge/Main/projecthome/v2/components/openSourceCollaboration1.jsx +++ b/src/forge/Main/projecthome/v2/components/openSourceCollaboration1.jsx @@ -1,28 +1,10 @@ import React, { Fragment, useEffect, useState } from 'react'; import { AlignCenter } from '../../../../Component/layout'; import TerminalAnimation from './terminalAnimation1'; -import useIntersectionObserver from '../../../../../common/hooks/useIntersectionObserver'; -const OpenSourceCollaboration = () => { +import { Button } from 'antd'; +const OpenSourceCollaboration = ({isVisible}) => { const [selectedFeature, setSelectedFeature] = useState(null); const [active, setActive] = useState(1); - const [elementRef, isVisible] = useIntersectionObserver({ - threshold: 0.2, - rootMargin: '0px 0px -20px 0px' - }); - - useEffect(() => { - if(!isVisible) return - const interval = setInterval(() => { - setActive(prevActive => { - return prevActive === 1 ? 2 : 1; - }); - }, 6000); - - // 清理定时器 - return () => { - clearInterval(interval); - }; - }, [isVisible]); const features = [ { @@ -48,7 +30,7 @@ const OpenSourceCollaboration = () => { }; return ( -
+
开源协作
@@ -66,10 +48,10 @@ const OpenSourceCollaboration = () => {
))} - +
diff --git a/src/forge/Main/projecthome/v2/components/project.jsx b/src/forge/Main/projecthome/v2/components/project.jsx index 1c29288fa..563c650f7 100644 --- a/src/forge/Main/projecthome/v2/components/project.jsx +++ b/src/forge/Main/projecthome/v2/components/project.jsx @@ -1,3 +1,4 @@ +import { Button } from 'antd'; import React from 'react'; const RepositoryV2 = () => { @@ -33,10 +34,10 @@ const RepositoryV2 = () => { } ]; return ( -
+
{/* 标题 */} -
XX开源软件仓库
+
开源软件仓
{/* 导航标签 */}
@@ -71,10 +72,10 @@ const RepositoryV2 = () => { ))}
- +
{/* 右侧架构图 - 使用图片 */} diff --git a/src/forge/Main/projecthome/v2/components/softwareFactory.jsx b/src/forge/Main/projecthome/v2/components/softwareFactory.jsx index 826aa53ff..f1f6f407b 100644 --- a/src/forge/Main/projecthome/v2/components/softwareFactory.jsx +++ b/src/forge/Main/projecthome/v2/components/softwareFactory.jsx @@ -7,8 +7,6 @@ function SoftwareFactory() { const [active, setActive] = useState(1); useEffect(() => { - console.log('av', active); - const interval = setInterval(() => { setActive(prevActive => { return prevActive === 1 ? 2 : 1; @@ -22,7 +20,7 @@ function SoftwareFactory() { }, []); return ( -
+
软件工厂
@@ -42,7 +40,7 @@ function SoftwareFactory() {
极速模式聚焦任务 - 代码 - 制品,助力软件研发快速响应、
实现极致效率
- diff --git a/src/forge/Main/projecthome/v2/components/supplyChainGovernance.jsx b/src/forge/Main/projecthome/v2/components/supplyChainGovernance.jsx index 265fb5472..5d8fb9edf 100644 --- a/src/forge/Main/projecthome/v2/components/supplyChainGovernance.jsx +++ b/src/forge/Main/projecthome/v2/components/supplyChainGovernance.jsx @@ -51,7 +51,7 @@ const SupplyChainGovernance = () => { ]; return ( -
+
供应链治理
diff --git a/src/forge/Main/projecthome/v2/components/terminalAnimation.jsx b/src/forge/Main/projecthome/v2/components/terminalAnimation.jsx deleted file mode 100644 index b8813fe48..000000000 --- a/src/forge/Main/projecthome/v2/components/terminalAnimation.jsx +++ /dev/null @@ -1,200 +0,0 @@ -import React, { useEffect, useRef } from 'react'; - -const TerminalAnimation = () => { - const canvasRef = useRef(null); - const animationRef = useRef(null); - const stateRef = useRef({ - lineI: 0, - charI: 0, - lastChT: 0, - lastLnT: 0, - curOn: true, - blinkT: 0 - }); - - const lines = [ - '$ git clone https://gitlink.org.cn/Gitlink/forgeplus.git', - '$ cd forgeplus', - '$ git remote -v', - '$ git pull origin master', - '$ git add .', - '$ git commit -m "feat: add new feature"', - '$ git push origin master', - '$ git checkout -b feature/new-ui', - '$ git merge develop', - '$ git status ', - '$ git log --oneline -10', - // '$ git fetch upstream' - ]; - - const keyColor = { - 'git clone': '#fff700', - 'git commit': '#fff700', - 'git log': '#fff700', - 'git merge': '#fff700', - 'git pull': '#d24a6f', - 'git remote': '#d24a6f', - 'git push': '#d24a6f', - 'git status': '#2977ff', - 'git fetch': '#2977ff', - 'git add': '#9aff41', - 'cd forgeplus': '#9aff41', - 'git checkout': '#9aff41' - }; - - const fontSize = 15; - const lineH = 36; - const startY = 40; - const charGap = 30; - const lineGap = 300; - - // 获取设备像素比 - const getPixelRatio = (context) => { - const backingStore = context.backingStorePixelRatio || - context.webkitBackingStorePixelRatio || - context.mozBackingStorePixelRatio || - context.msBackingStorePixelRatio || - context.oBackingStorePixelRatio || - context.backingStorePixelRatio || 1; - - return (window.devicePixelRatio || 1) / backingStore; - }; - - const drawLine = (ctx, t, y, showCur, lineIndex) => { - const prompt = '$ '; - const pW = ctx.measureText(prompt).width; - ctx.fillStyle = '#ffffff'; - ctx.fillText(prompt, 30, y); - - if (!t.startsWith(prompt)) return; - let seg = t.slice(prompt.length); - - const fullLine = lines[lineIndex] || t; - const fullSeg = fullLine.startsWith(prompt) ? fullLine.slice(prompt.length) : fullLine; - - let drawnWidth = 0; - - for (let key in keyColor) { - if (fullSeg.startsWith(key)) { - const visibleKeyLength = Math.min(key.length, seg.length); - if (visibleKeyLength > 0) { - const visibleKeyText = seg.slice(0, visibleKeyLength); - ctx.fillStyle = keyColor[key]; - ctx.fillText(visibleKeyText, 30 + pW + drawnWidth, y); - drawnWidth += ctx.measureText(visibleKeyText).width; - } - - seg = seg.slice(visibleKeyLength); - break; - } - } - - if (seg) { - ctx.fillStyle = '#ffffff'; - ctx.fillText(seg, 30 + pW + drawnWidth, y); - } - - if (showCur && stateRef.current.curOn) { - const curX = 30 + ctx.measureText(t + ' ').width; - ctx.fillRect(curX, y - fontSize + 4, 2, fontSize - 2); - } - }; - - const draw = (ctx) => { - // 直接清除画布,保持透明 - ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); - - for (let i = 0; i < stateRef.current.lineI; i++) { - drawLine(ctx, lines[i], startY + i * lineH, false, i); - } - - if (stateRef.current.lineI < lines.length) { - drawLine(ctx, lines[stateRef.current.lineI].slice(0, stateRef.current.charI), startY + stateRef.current.lineI * lineH, true, stateRef.current.lineI); - } - }; - - const animate = (timestamp) => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext('2d'); - const ratio = getPixelRatio(ctx); - - ctx.font = `${fontSize * ratio}px 'Courier New', monospace`; - // 优化字体渲染 - ctx.textBaseline = 'middle'; - ctx.imageSmoothingEnabled = false; - - const state = stateRef.current; - - if (timestamp - state.blinkT > 500) { - state.curOn = !state.curOn; - state.blinkT = timestamp; - } - - if (state.lineI === 0 && state.charI < lines[0].length && timestamp - state.lastChT > charGap) { - state.charI++; - state.lastChT = timestamp; - } else if (state.lineI === 0 && state.charI >= lines[0].length && timestamp - state.lastLnT > lineGap) { - state.lineI++; - state.lastLnT = timestamp; - } else if (state.lineI > 0 && state.lineI < lines.length && timestamp - state.lastLnT > lineGap) { - state.lineI++; - state.lastLnT = timestamp; - } - - draw(ctx); - animationRef.current = requestAnimationFrame(animate); - }; - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - // 处理高清屏幕显示 - const ctx = canvas.getContext('2d'); - const ratio = getPixelRatio(ctx); - - // 保存原始尺寸 - const originalWidth = canvas.width; - const originalHeight = canvas.height; - - // 设置实际渲染尺寸 - canvas.width = originalWidth * ratio; - canvas.height = originalHeight * ratio; - - // 设置显示尺寸 - canvas.style.width = originalWidth + 'px'; - canvas.style.height = originalHeight + 'px'; - - // 缩放绘图上下文 - ctx.scale(ratio, ratio); - - ctx.font = `${fontSize * ratio}px 'Courier New', monospace`; - ctx.textBaseline = 'middle'; - ctx.imageSmoothingEnabled = false; - ctx.antialiasing = 'none'; - - // 启动动画 - animationRef.current = requestAnimationFrame(animate); - - // 清理函数 - return () => { - if (animationRef.current) { - cancelAnimationFrame(animationRef.current); - } - }; - }, []); - - return ( -
- -
- ); -}; - -export default TerminalAnimation; \ No newline at end of file diff --git a/src/forge/Main/projecthome/v2/components/terminalAnimation1.jsx b/src/forge/Main/projecthome/v2/components/terminalAnimation1.jsx index 085063436..c3cf1f3cd 100644 --- a/src/forge/Main/projecthome/v2/components/terminalAnimation1.jsx +++ b/src/forge/Main/projecthome/v2/components/terminalAnimation1.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; -const TerminalAnimation = ({startAnimation=false}) => { +const TerminalAnimation = ({ startAnimation = false }) => { const [animationState, setAnimationState] = useState({ lineI: 0, charI: 0, @@ -43,9 +43,9 @@ const TerminalAnimation = ({startAnimation=false}) => { const startY = 40; const charGap = 30; const lineGap = 300; - const lineNumberWidth = 40; // 序号列宽度 + const lineNumberWidth = 40; - // 字符宽度估算函数(更准确) + // 字符宽度估算函数 const getCharWidth = (charCount) => charCount * 9.6; // 渲染带颜色的文本段 @@ -54,15 +54,12 @@ const TerminalAnimation = ({startAnimation=false}) => { let currentX = x; let charsRendered = 0; - // 按优先级检查关键字 const sortedKeys = Object.keys(keyColor).sort((a, b) => b.length - a.length); - let remainingText = text; while (remainingText.length > 0 && charsRendered < availableChars) { let colored = false; - // 查找匹配的关键字 for (const key of sortedKeys) { if (remainingText.startsWith(key)) { const charsToRender = Math.min(key.length, availableChars - charsRendered); @@ -91,7 +88,6 @@ const TerminalAnimation = ({startAnimation=false}) => { } } - // 如果没找到关键字,渲染普通文本 if (!colored && remainingText.length > 0) { const charsToRender = Math.min(1, availableChars - charsRendered); if (charsToRender > 0) { @@ -123,67 +119,72 @@ const TerminalAnimation = ({startAnimation=false}) => { }; // 渲染文本行 -const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => { - let x = lineNumberWidth; // 从序号列之后开始 - - // 渲染行号 - const lineNumberElement = ( - - {lineIndex + 1}. - - ); - - // 计算可用字符数 - const availableChars = charLimit; - - // 渲染命令部分(直接渲染整行,不再分离提示符) - const { elements: commandElements } = renderColoredText( - line, - x, - y, - availableChars - ); - - const allElements = [lineNumberElement, ...commandElements]; - - // 如果是当前行且需要光标 - if (isCurrentLine && animationState.curOn) { - const cursorX = x + getCharWidth(charLimit); - allElements.push( - { + let x = lineNumberWidth; + + const lineNumberElement = ( + + fontFamily="'Courier New', monospace" + fontSize={fontSize} + dominantBaseline="middle" + textAnchor="middle" + fontWeight="bold" + > + {lineIndex + 1}. + ); - } - - return allElements; -}; - - // 动画逻辑 - useEffect(() => { - if (!startAnimation) { - setAnimationState({ - lineI: 0, - charI: 0, - curOn: true - }); - return; + + const availableChars = charLimit; + const { elements: commandElements } = renderColoredText( + line, + x, + y, + availableChars + ); + + const allElements = [lineNumberElement, ...commandElements]; + + if (isCurrentLine && animationState.curOn) { + const cursorX = x + getCharWidth(charLimit); + allElements.push( + + ); } + + return allElements; + }; + + // 重置动画 + const resetAnimation = () => { + setAnimationState({ + lineI: 0, + charI: 0, + curOn: true + }); + }; + + // 监听 startAnimation 变化 + useEffect(() => { + if (startAnimation) { + resetAnimation(); + } + }, [startAnimation]); + + // 动画循环 + useEffect(() => { + if (!startAnimation) return; + let lastCharTime = Date.now(); let lastLineTime = Date.now(); let lastBlinkTime = Date.now(); @@ -209,14 +210,18 @@ const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => { // 第一行完成后,后续行整行显示 else if (newState.lineI === 0 && newState.charI >= lines[0].length && now - lastLineTime > lineGap) { newState.lineI++; - newState.charI = lines[newState.lineI] ? lines[newState.lineI].length : 0; // 整行显示 + if (newState.lineI < lines.length) { + newState.charI = lines[newState.lineI].length; + } lastLineTime = now; shouldUpdate = true; } // 后续行之间的间隔 else if (newState.lineI > 0 && newState.lineI < lines.length && now - lastLineTime > lineGap) { newState.lineI++; - newState.charI = newState.lineI < lines.length ? lines[newState.lineI].length : 0; // 整行显示 + if (newState.lineI < lines.length) { + newState.charI = lines[newState.lineI].length; + } lastLineTime = now; shouldUpdate = true; } @@ -225,7 +230,10 @@ const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => { setAnimationState(newState); } - animationRef.current = requestAnimationFrame(animate); + // 如果动画未完成,继续下一帧 + if (newState.lineI < lines.length && startAnimation) { + animationRef.current = requestAnimationFrame(animate); + } }; animationRef.current = requestAnimationFrame(animate); @@ -235,7 +243,7 @@ const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => { cancelAnimationFrame(animationRef.current); } }; - }, [animationState, startAnimation]); + }, [animationState]); // 包含 animationState 以确保状态更新 return (
diff --git a/src/forge/Main/projecthome/v2/components/terminalAnimation2.jsx b/src/forge/Main/projecthome/v2/components/terminalAnimation2.jsx index 5f5a740c5..54964645c 100644 --- a/src/forge/Main/projecthome/v2/components/terminalAnimation2.jsx +++ b/src/forge/Main/projecthome/v2/components/terminalAnimation2.jsx @@ -1,12 +1,12 @@ import React, { useEffect, useRef, useState } from 'react'; -const TerminalAnimation = ({startAnimation=true, lines, width, height, class_name}) => { +const TerminalAnimation = ({ startAnimation = true, lines, width, height, class_name }) => { const [animationState, setAnimationState] = useState({ lineI: 0, charI: 0, curOn: true }); - + const animationRef = useRef(null); const keyColor = { @@ -42,12 +42,12 @@ const TerminalAnimation = ({startAnimation=true, lines, width, height, class_nam // 按优先级检查关键字 const sortedKeys = Object.keys(keyColor).sort((a, b) => b.length - a.length); - + let remainingText = text; - + while (remainingText.length > 0 && charsRendered < availableChars) { let colored = false; - + // 查找匹配的关键字 for (const key of sortedKeys) { if (remainingText.startsWith(key)) { @@ -76,7 +76,7 @@ const TerminalAnimation = ({startAnimation=true, lines, width, height, class_nam break; } } - + // 如果没找到关键字,渲染普通文本 if (!colored && remainingText.length > 0) { const charsToRender = Math.min(1, availableChars - charsRendered); @@ -101,75 +101,85 @@ const TerminalAnimation = ({startAnimation=true, lines, width, height, class_nam remainingText = remainingText.substring(charsToRender); } } - + if (charsRendered >= availableChars) break; } - + return { elements, currentX, charsRendered }; }; // 渲染文本行 -const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => { - let x = lineNumberWidth; // 从序号列之后开始 - - // 渲染行号 - const lineNumberElement = ( - - {lineIndex + 1}. - - ); - - // 计算可用字符数 - const availableChars = charLimit; - - // 渲染命令部分(直接渲染整行,不再分离提示符) - const { elements: commandElements } = renderColoredText( - line, - x, - y, - availableChars - ); - - const allElements = [...commandElements]; - - // 如果是当前行且需要光标 - if (isCurrentLine && animationState.curOn) { - const cursorX = x + getCharWidth(charLimit); - allElements.push( - - ); - } - - return allElements; -}; + const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => { + let x = lineNumberWidth; // 从序号列之后开始 - // 动画逻辑 - useEffect(() => { - if (!startAnimation) { - setAnimationState({ - lineI: 0, - charI: 0, - curOn: true - }); - return; + // 渲染行号 + const lineNumberElement = ( + + {lineIndex + 1}. + + ); + + // 计算可用字符数 + const availableChars = charLimit; + + // 渲染命令部分(直接渲染整行,不再分离提示符) + const { elements: commandElements } = renderColoredText( + line, + x, + y, + availableChars + ); + + const allElements = [...commandElements]; + + // 如果是当前行且需要光标 + if (isCurrentLine && animationState.curOn) { + const cursorX = x + getCharWidth(charLimit); + allElements.push( + + ); } + + return allElements; + }; + + // 重置动画 + const resetAnimation = () => { + setAnimationState({ + lineI: 0, + charI: 0, + curOn: true + }); + }; + + // 监听 startAnimation 变化 + useEffect(() => { + if (startAnimation) { + resetAnimation(); + } + }, [startAnimation]); + + // 动画循环 + useEffect(() => { + if (!startAnimation) return; + let lastCharTime = Date.now(); let lastLineTime = Date.now(); let lastBlinkTime = Date.now(); @@ -191,18 +201,22 @@ const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => { newState.charI++; lastCharTime = now; shouldUpdate = true; - } + } // 第一行完成后,后续行整行显示 else if (newState.lineI === 0 && newState.charI >= lines[0].length && now - lastLineTime > lineGap) { newState.lineI++; - newState.charI = lines[newState.lineI] ? lines[newState.lineI].length : 0; // 整行显示 + if (newState.lineI < lines.length) { + newState.charI = lines[newState.lineI].length; + } lastLineTime = now; shouldUpdate = true; - } + } // 后续行之间的间隔 else if (newState.lineI > 0 && newState.lineI < lines.length && now - lastLineTime > lineGap) { newState.lineI++; - newState.charI = newState.lineI < lines.length ? lines[newState.lineI].length : 0; // 整行显示 + if (newState.lineI < lines.length) { + newState.charI = lines[newState.lineI].length; + } lastLineTime = now; shouldUpdate = true; } @@ -210,8 +224,11 @@ const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => { if (shouldUpdate) { setAnimationState(newState); } - - animationRef.current = requestAnimationFrame(animate); + + // 如果动画未完成,继续下一帧 + if (newState.lineI < lines.length && startAnimation) { + animationRef.current = requestAnimationFrame(animate); + } }; animationRef.current = requestAnimationFrame(animate); @@ -221,7 +238,7 @@ const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => { cancelAnimationFrame(animationRef.current); } }; - }, [animationState, startAnimation]); + }, [animationState]); // 包含 animationState 以确保状态更新 return (
@@ -238,7 +255,7 @@ const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => { {renderTextSegments(line, line.length, startY + index * lineH, false, index)} ))} - + {/* 渲染当前正在输入的行 */} {animationState.lineI < lines.length && ( diff --git a/src/forge/Main/projecthome/v2/index.jsx b/src/forge/Main/projecthome/v2/index.jsx index 4917cffac..afe99f4db 100644 --- a/src/forge/Main/projecthome/v2/index.jsx +++ b/src/forge/Main/projecthome/v2/index.jsx @@ -1,4 +1,4 @@ -import React , { useEffect , useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { TPMIndexHOC } from "../../../../modules/tpm/TPMIndexHOC"; import './index.scss'; import Banner from './components/banner'; @@ -11,21 +11,56 @@ import Forum from './components/forum'; import EcosystemAlliance from './components/ecosystemAlliance'; function Index(props) { + const observer = useRef(); + const [visibleItems, setVisibleItems] = useState({}); - useEffect(()=>{ + useEffect(() => { + // 创建 IntersectionObserver 实例 + observer.current = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + const id = entry.target.id; + const element = document.getElementById(id); + if (element) { + if (entry.isIntersecting) { + element.classList.add('home_v2_visible'); + setVisibleItems((prev)=>({...prev, [id]: true})) + } else { + element.classList.remove('home_v2_visible'); + setVisibleItems((prev)=>({...prev, [id]: false})) + } + } + }); + }, { + root: null, + rootMargin: '0px', + threshold: 0.1 + }); + + // 观察所有子组件 + document.querySelectorAll('.home_v2_child').forEach((el) => { + observer.current.observe(el); + }); + + return () => { + // 清理观察器 + observer.current.disconnect(); + }; + }, []); + + useEffect(() => { document.title = props.mygetHelmetapi && props.mygetHelmetapi.name ? props.mygetHelmetapi.name : '生态创新治理中心'; - },[]) + }, []) - return( + return (
- + - + - + - +
) } diff --git a/src/forge/Main/projecthome/v2/index.scss b/src/forge/Main/projecthome/v2/index.scss index 5f5f2af82..42f6da544 100644 --- a/src/forge/Main/projecthome/v2/index.scss +++ b/src/forge/Main/projecthome/v2/index.scss @@ -86,18 +86,18 @@ } // 从下边滑入可见区 - .animate-up-box { - // 初始状态:在下方不可见 - transform: translateY(200px); - opacity: 0; - transition: opacity 1s, transform 1s; + // .animate-up-box { + // // 初始状态:在下方不可见 + // transform: translateY(200px); + // opacity: 0; + // transition: opacity 1s, transform 1s; - // 动效状态:从下方滑入 - &.animate-up { - transform: translateY(0); - opacity: 1; - } - } + // // 动效状态:从下方滑入 + // &.home_v2_visible { + // transform: translateY(0); + // opacity: 1; + // } + // } // 标题悬停:下划线从左到右滑出 .title-border-to-right { @@ -392,7 +392,14 @@ .activity-section-Carousel{ width: 400px; border-radius: 10px; + max-height: 211px; + overflow: hidden; + .slick-track{ + display: flex; + align-items: flex-end; + } img{ + object-fit: cover; max-width: 100%; } } @@ -411,9 +418,9 @@ background-color: #df2659; border-radius: 5px; font-size: 0.8rem; - padding: 4px 5px; - position: relative; - top: -2px; + padding: 0px 5px; + height: 18px; + line-height: 18px; } } @@ -736,7 +743,7 @@ /* ForumExchange.scss */ .forum-exchange { - padding: 60px 20px 80px; + padding: 60px 20px 50px; background-image: url('./image/bk2.png'); background-size: 100% 100%; diff --git a/src/forge/projectHome/explore/index.jsx b/src/forge/projectHome/explore/index.jsx index 2b74b8ebc..68af1ae55 100644 --- a/src/forge/projectHome/explore/index.jsx +++ b/src/forge/projectHome/explore/index.jsx @@ -27,27 +27,7 @@ function ProjectHomePage(props) { const [topicDetail, setTopicDetail] = useState(undefined); const [count, setCount] = useState({}); const { countByGHM, countByOther, countByGHMType, countByMX, countByRQ } = count; - // const topicsByCateId = cateTopics && cateTopics[cateID]; - const topicsByCateId = cateTopics && cateTopics[cateID] || [ - { - "id": "1,2", - "name": "Maven", - "icon": "icon-maven", - "intro": "配置使用本站maven仓库,首先请定位 **settings.xml** 文件,通常位于本地 Maven 安装目录的 **conf/settings.xml** 或用户目录下的 **m2/settings.xml**。\n\n然后,在`settings.xml`​文件中` `​ 标签中添加私有仓库地址:\n\n```xml\n\n \n xzgd-repo-mirror\n *\n http://172.16.6.110:8081/repository/maven-repo/\n \n\n```\n\n\n在`settings.xml`​文件中``​标签中添加以下内容:\n\n\n```xml\n \n \n xzgd-maven-mirror-profile\n \n \n xzgd-maven-mirror\n http://172.16.6.110:8081/repository/maven-repo/\n \n \n \n \n xzgd-maven-mirror\n http://172.16.6.110:8081/repository/maven-repo/ \n \n \n \n\n```\n\n在`settings.xml`​文件中``​标签中添加以下内容:\n\n```xml\n\n xzgd-maven-mirror-profile\n\n```\n" - }, - { - "id": "2,4", - "name": "Python", - "icon": "icon-python", - "intro": "**方法一**:通过命令配置\n\n运行以下命令:\n```xml\npip config set global.index-url https://mirrors.aliyun.com/pypi/simple\npip config set install.trusted-host mirrors.aliyun.com\n```\n\n**方法二**:手动修改配置文件\n\n根据操作系统不同,修改 pip 配置文件:\n\n**Windows** 在用户目录下创建或编辑 pip.ini 文件(路径如:C:\\Users\\<用户名>\\pip\\pip.ini),内容如下:\n```xml\n[global]\nindex-url = https://mirrors.aliyun.com/pypi/simple/\n[install]\ntrusted-host = mirrors.aliyun.com\n```\n\n**Linux/Mac** 编辑或创建 ~/.config/pip/pip.conf 文件,内容如下:\n```xml\n[global]\nindex-url = https://mirrors.aliyun.com/pypi/simple\n[install]\ntrusted-host = mirrors.aliyun.com\n```\n\n验证配置,运行以下命令检查是否成功:\n```xml\npip config list\n```" - }, - { - "id": "4,5", - "name": "Npm", - "icon": "icon-npm", - "intro": "通过使用npm config set registry命令切换npm的镜像源\n\n使用示例:\n可以在命令行工具中输入以下命令:\n```xml\nnpm config set registry https://registry.npm.taobao.org\n```\n\n切换镜像源后,可以通过以下命令验证是否切换成功:\n```xml\nnpm config get registry\n```\n\n如果输出结果显示镜像源已切换到淘宝的npm镜像源,则表示切换成功。" - } - ]; + const topicsByCateId = cateTopics && cateTopics[cateID]; const topicsLength = topicsByCateId && topicsByCateId.length const LIMIT = !topicsLength ? 30 : 25; @@ -105,7 +85,7 @@ function ProjectHomePage(props) { if (result && result.data) { const { project_categories = [] } = result.data; setCateList(project_categories); - // project_categories[0] && setCateID(project_categories[0].id); + project_categories[0] && setCateID(project_categories[0].id); } }).catch(error => { }) } @@ -127,9 +107,8 @@ function ProjectHomePage(props) { params.language_id = topicId.split(",") params.topic_id = undefined } - console.log('params', params); - axios.get(url, { params }).then(result => { + cateID && axios.get(url, { params }).then(result => { if (result && result.data) { setTotal(result.data.total_count); setProjectsList(result.data.projects); @@ -241,7 +220,20 @@ function ProjectHomePage(props) { })}
-
+
+ {topicId && topicDetail && topicDetail.intro && trigger.parentNode} + content={ +
+ +
+ } + trigger="click" + placement="bottomLeft" + overlayClassName="tipByCate_Popover" + > +
{`设置${topicDetail.name}包管理器仓库地址,`}查看更多
+
}
- {/* 二级分类以及仓库列表(左右布局) */} -
- {/* 二级分类,即标签列表 */} - - {/* 项目列表 */} -
- {/* 三方组件下的提示信息
*/} - {cateID === 39 && topicId && topicDetail && topicDetail.intro && trigger.parentNode} - content={ -
- -
- } - trigger="click" - placement="bottomLeft" - overlayClassName="tipByCate_Popover" - > -
{`设置${topicDetail.name}包管理器仓库地址,`}查看更多
-
} - -
-
} diff --git a/src/forge/projectHome/explore/index.scss b/src/forge/projectHome/explore/index.scss index 8f1c96688..4c3c4677f 100644 --- a/src/forge/projectHome/explore/index.scss +++ b/src/forge/projectHome/explore/index.scss @@ -259,6 +259,18 @@ display: none; } } + .tipByCate { + padding: 10px 30px; + margin: 15px 0px; + background-image: linear-gradient(121.96deg, #fff6e2 0%, #fff4eb 100%); + border-radius: 8px; + color: #301403; + font-size: 16px; + } + + pre.prettyprint { + margin-bottom: 16px !important; + } } .project_recommend { @@ -306,6 +318,21 @@ } } } +.tipByCate_Popover_box{ + position: relative; + .tipByCate_Popover.ant-popover { + width: 100%; + + .ant-popover-arrow { + display: none; + } + + .ant-popover-content { + margin-top: -13px; + } + } +} + @keyframes flowLight { 0% { diff --git a/src/news/list/index.jsx b/src/news/list/index.jsx index 8719265bd..0c4351070 100644 --- a/src/news/list/index.jsx +++ b/src/news/list/index.jsx @@ -79,7 +79,7 @@ function List() {
{list1.slice(4).map(item => { - return {item.name}{item.name}{item.name} + return {item.name}{item.name}{item.name} })}
@@ -94,7 +94,7 @@ function List() {
}
- {list2.map(item=>{ + {list2.slice(1, 6).map(item=>{ return
@@ -117,7 +117,7 @@ function List() {
}
- {list3.slice(1).map(item=>{ + {list3.slice(1, 9).map(item=>{ return
{item.name} diff --git a/src/news/list/index.scss b/src/news/list/index.scss index 6845d302e..8f0866e66 100644 --- a/src/news/list/index.scss +++ b/src/news/list/index.scss @@ -61,6 +61,7 @@ .community-news-item-img-box{ overflow: hidden; border-radius: 10px; + max-height: 80px; } .icon-jiantou3{ display: none; @@ -81,12 +82,13 @@ } } .others-news{ - padding: 15px 30px 1px 30px; + padding: 15px 30px 15px 30px; background-color:#ffffff; border-radius:10px; box-shadow:0px 0px 6px rgba(92, 102, 193, 0.05); display: grid; grid-template-columns: repeat(2, 1fr); + gap: 6px 30px; } } .first-activity{ @@ -121,7 +123,7 @@ border-radius:10px; box-shadow:0px 0px 6px rgba(92, 102, 193, 0.05); padding: 40px 30px; - gap: 45px; + gap: 15px 45px; background-image: url('../image/bk4.png'); background-repeat: no-repeat; background-position: right center; From 3ebec23da11d2b6b94763ae6c5d13b7813776c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E6=80=9D?= Date: Tue, 23 Dec 2025 17:20:42 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/news/detail/index.jsx | 2 +- src/news/list/index.jsx | 10 +++++----- src/news/list/index.scss | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/news/detail/index.jsx b/src/news/detail/index.jsx index c2bf3f0d3..76e6266fa 100644 --- a/src/news/detail/index.jsx +++ b/src/news/detail/index.jsx @@ -30,7 +30,7 @@ function detail(props) { }, [id]) function getDocListByDirId(id) { - getSubDocList(id).then(res => { + getSubDocList(id, {pageSize:10,pageNum:1}).then(res => { if (res && res.data.rows) { let rows = res.data.rows; setList1(rows); diff --git a/src/news/list/index.jsx b/src/news/list/index.jsx index 0c4351070..6fd408068 100644 --- a/src/news/list/index.jsx +++ b/src/news/list/index.jsx @@ -70,7 +70,7 @@ function List() {
{item.summary}
- + {item.publishTime}发布
@@ -79,7 +79,7 @@ function List() {
{list1.slice(4).map(item => { - return {item.name}{item.name}{item.name} + return {item.name} })}
@@ -87,7 +87,7 @@ function List() {
精彩活动
{firstActivity &&
- {firstActivity.summary} + {firstActivity.summary} {moment(firstActivity.name).format('MM/DD')}
{moment(firstActivity.name).format('HH:MM')}
@@ -100,7 +100,7 @@ function List() {
{item.name}
{item.summary}
- +
})} @@ -114,7 +114,7 @@ function List() { {firstDynamics &&
{firstDynamics.name}
{firstDynamics.summary}
- + {/* */}
}
{list3.slice(1, 9).map(item=>{ diff --git a/src/news/list/index.scss b/src/news/list/index.scss index 8f0866e66..9e00887d8 100644 --- a/src/news/list/index.scss +++ b/src/news/list/index.scss @@ -17,6 +17,7 @@ } &.moreheight{ height: 40px; + line-height: 40px; } }