Merge remote-tracking branch '172/factory' into factory-hxy

This commit is contained in:
黄心宇 2025-12-18 08:55:51 +08:00
commit 5c75e7891c
176 changed files with 5941 additions and 53 deletions

View File

@ -20,6 +20,10 @@
font-family: "ziyouwenyihei";
src: url("../fonts/ziyouwenyihei.ttf");
}
@font-face {
font-family: "DouyinSansBold";
src: url("../fonts/DouyinSansBold.ttf");
}
.header {
width: 100%;
height: 51px;
@ -1433,6 +1437,10 @@ a.edu-txt-w80,
font-size: 36px !important
}
.font-38 {
font-size: 38px !important
}
.font-40 {
font-size: 40px !important
}
@ -1441,6 +1449,10 @@ a.edu-txt-w80,
font-size: 50px !important
}
.font-60 {
font-size: 60px !important
}
.font-80 {
font-size: 80px !important
}
@ -1462,6 +1474,14 @@ a.decoration {
font-weight: 400
}
.mt-5{
margin-top: -5px;
}
.mt-8{
margin-top: -8px;
}
.mt1 {
margin-top: 1px
}
@ -1618,6 +1638,10 @@ a.decoration {
margin-bottom: 7px
}
.mb8 {
margin-bottom: 8px
}
.mb10 {
margin-bottom: 10px
}
@ -1658,6 +1682,10 @@ a.decoration {
margin-bottom: 30px !important
}
.mb35 {
margin-bottom: 35px !important
}
.mb40 {
margin-bottom: 40px !important
}
@ -1742,6 +1770,10 @@ a.decoration {
margin-left: 22px
}
.ml24 {
margin-left: 24px
}
.ml25 {
margin-left: 25px
}
@ -1782,6 +1814,10 @@ a.decoration {
margin-left: 180px
}
.mr2 {
margin-right: 2px
}
.mr3 {
margin-right: 3px
}
@ -1862,6 +1898,10 @@ a.decoration {
padding-top: 5px !important
}
.pt8 {
padding-top: 8px !important
}
.pt10 {
padding-top: 10px
}
@ -1902,6 +1942,10 @@ a.decoration {
padding-top: 60px
}
.pt70 {
padding-top: 70px
}
.pt80 {
padding-top: 80px
}
@ -1923,11 +1967,7 @@ a.decoration {
}
.pb25 {
padding-bottom: 20px
}
.pb25 {
padding-bottom: 20px
padding-bottom: 25px
}
.pb28 {
@ -1954,6 +1994,18 @@ a.decoration {
padding-bottom: 60px
}
.pb70 {
padding-bottom: 70px
}
.pb80 {
padding-bottom: 80px
}
.pb90 {
padding-bottom: 90px
}
.pb100 {
padding-bottom: 100px
}
@ -1982,6 +2034,10 @@ a.decoration {
padding-right: 35px !important
}
.pr50 {
padding-right: 50px !important
}
.pl0 {
padding-left: 0 !important
}
@ -2030,6 +2086,10 @@ a.decoration {
padding-left: 40px
}
.pl45 {
padding-left: 45px
}
.pl50 {
padding-left: 50px
}
@ -2111,6 +2171,11 @@ a.decoration {
box-sizing: border-box
}
.padding25-30{
padding: 25px 30px;
box-sizing: border-box
}
.padding30 {
padding: 30px;
box-sizing: border-box
@ -2172,6 +2237,14 @@ a.decoration {
line-height: 40px !important
}
.lineh-50 {
line-height: 50px !important
}
.lineh-80 {
line-height: 80px !important
}
.break_word {
word-break: break-all;
word-wrap: break-word

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2340181 */
src: url('iconfont.woff2?t=1762825432985') format('woff2'),
url('iconfont.woff?t=1762825432985') format('woff'),
url('iconfont.ttf?t=1762825432985') format('truetype');
src: url('iconfont.woff2?t=1765938619495') format('woff2'),
url('iconfont.woff?t=1765938619495') format('woff'),
url('iconfont.ttf?t=1765938619495') format('truetype');
}
.iconfont {
@ -13,6 +13,34 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-svg_jiantou1:before {
content: "\e9c3";
}
.icon-zuojiantou1:before {
content: "\e9c4";
}
.icon-jiantou4:before {
content: "\e9c2";
}
.icon-hezuohuoban:before {
content: "\e9bf";
}
.icon-xiangmugongxian:before {
content: "\e9c1";
}
.icon-sijiaoxing:before {
content: "\e9c0";
}
.icon-a-zu15512:before {
content: "\e9be";
}
.icon-wendangbangong-01:before {
content: "\e9bd";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,55 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "46370299",
"name": "svg_箭头1",
"font_class": "svg_jiantou1",
"unicode": "e9c3",
"unicode_decimal": 59843
},
{
"icon_id": "46370300",
"name": "左箭头",
"font_class": "zuojiantou1",
"unicode": "e9c4",
"unicode_decimal": 59844
},
{
"icon_id": "46329189",
"name": "箭头",
"font_class": "jiantou4",
"unicode": "e9c2",
"unicode_decimal": 59842
},
{
"icon_id": "46248622",
"name": "合作伙伴",
"font_class": "hezuohuoban",
"unicode": "e9bf",
"unicode_decimal": 59839
},
{
"icon_id": "46248621",
"name": "项目贡献",
"font_class": "xiangmugongxian",
"unicode": "e9c1",
"unicode_decimal": 59841
},
{
"icon_id": "46245203",
"name": "四角星",
"font_class": "sijiaoxing",
"unicode": "e9c0",
"unicode_decimal": 59840
},
{
"icon_id": "46244977",
"name": "组 15512",
"font_class": "a-zu15512",
"unicode": "e9be",
"unicode_decimal": 59838
},
{
"icon_id": "46034062",
"name": "文档办公-01",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -146,3 +146,23 @@ body {
min-height: calc(100% - 60px) !important;
}
}
.width1600 {
width: 1600px;
margin: 0 auto;
}
.opacity9 {
opacity: 0.9;
}
.opacity8 {
opacity: 0.8;
}
.opacity7 {
opacity: 0.7;
}
.opacity6 {
opacity: 0.6;
}
.opacity4 {
opacity: 0.4;
}

View File

@ -145,7 +145,7 @@ const ProjectIndex = Loadable({
});
const Home = Loadable({
loader: () => import('./forge/Main/projecthome/Index'),
loader: () => import('./forge/Main/projecthome/v2/index'),
loading: Loading,
})
@ -216,8 +216,18 @@ const GovernanceIframe = Loadable({
loading: Loading,
});
const Forums = Loadable({
loader: () => import("./forums"),
loading: Loading,
});
const News = Loadable({
loader: () => import("./news"),
loading: Loading,
});
// 此处仅维护前端可能的一级路由,不用进行项目或者组织判断的字段。
const keyWord = ["explore", "settings", "setting", "mulan", "wiki", "issues", "setting", "trending", "code", "projects", "pulls", "mine", "login", "register", "email", "export", "nopage", "404", "403", "500", "501", "search", "organize", "login", "register", "resetPassword", "aboutus", "educoder", "glcc", "bindlogin", "softbot", "zone", "hiagent", "softwareFactory", "development", "governance"];
const keyWord = ["explore", "settings", "setting", "mulan", "wiki", "issues", "setting", "trending", "code", "projects", "pulls", "mine", "login", "register", "email", "export", "nopage", "404", "403", "500", "501", "search", "organize", "login", "register", "resetPassword", "aboutus","educoder", "glcc","bindlogin", "softbot","zone", "hiagent","softwareFactory", "development", "governance", "forums", "news"];
class App extends Component {
constructor(props) {
@ -400,20 +410,32 @@ class App extends Component {
{pathName && pathName.toLowerCase() === 'glcc' && <SiderBarHelp />}
{/* <Router> */}
{
pathType ?
<Switch>
{/* iframe页面 */}
<Route path="/hiagent"
render={
(props) => (<Iframe {...this.props} {...props} {...this.state} />)
}
></Route>
{/* wiki预览 */}
<Route path="/:owner/:proIdentity/:projectId/wiki/preview" render={
(props) => {
return (<WikiPreview {...this.props} {...props} {...this.state} />)
}
} />
pathType ?
<Switch>
{/* 社区动态页面 */}
<Route path="/news"
render={
(props) => (<News {...this.props} {...props} {...this.state} />)
}
></Route>
{/* 论坛交流页面 */}
<Route path="/forums"
render={
(props) => (<Forums {...this.props} {...props} {...this.state} />)
}
></Route>
{/* iframe页面 */}
<Route path="/hiagent"
render={
(props) => (<Iframe {...this.props} {...props} {...this.state} />)
}
></Route>
{/* wiki预览 */}
<Route path="/:owner/:proIdentity/:projectId/wiki/preview" render={
(props) => {
return (<WikiPreview {...this.props} {...props} {...this.state} />)
}
} />
{/* svg */}
<Route path="/:owner/:projectsId/badge/:type" render={
@ -549,20 +571,6 @@ class App extends Component {
{/* 项目标签列表页 */}
<Route path="/explore/topic/:id/:name" component={Topic} mygetHelmetapi={mygetHelmetapi} />
<Route exact path="/explore/all"
render={
(props) => (
<ProjectIndex {...this.props} {...props} />
)
}
/>
<Route exact path="/explore"
render={
(props) => (
<Store {...this.props} {...props} />
)
}
/>
<Route path={"/:OIdentifier/enterprise"}
render={
(props) => {
@ -642,6 +650,13 @@ class App extends Component {
}
}>
</Route>
<Route exact path="/explore"
render={
(props) => (
<Projects {...this.props} {...props} {...this.state}/>
)
}
/>
<Route
path={"/governance"}
render={

View File

@ -15,7 +15,7 @@ export function getImageUrl(path) {
// https://testbdweb.trustie.net
// const local = 'http://localhost:3000'
path && !path.startsWith('/') && !path.startsWith('http') && (path = '/'.concat(path));
const local = 'https://testforgeplus.trustie.net';
const local = 'http://172.20.32.203:4000';
if (isDev) {
return `${local}${path}`
}

View File

@ -0,0 +1,41 @@
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;

View File

@ -18,12 +18,12 @@ const ProjectNew = Loadable({
loading: Loading,
});
const ProjectIndex = Loadable({
loader: () => import("./Main/Index"),
loader: () => import("./projectHome/projectList"),
loading: Loading,
});
const ProjectHome = Loadable({
loader: () => import("./Main/projecthome/Index"),
loader: () => import("./projectHome/explore"),
loading: Loading,
});
@ -60,7 +60,7 @@ class Index extends Component {
)}
></Route>
<Route
path="/explore/all"
path="/projects"
render={(props) => (
<ProjectIndex {...this.props} {...props} />
)}

View File

@ -127,9 +127,9 @@ function IssueCommentList(props){
<div className='commentOperationBor'></div>
{
item.user.login ?
<Link to={`/${item.user.login}`}><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr15'/></Link>
<Link to={`/${item.user.login}`}><img src={item.user.image_url} alt="" className='commentUserImg mr15'/></Link>
:
<span><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr15'/></span>
<span><img src={item.user.image_url} alt="" className='commentUserImg mr15'/></span>
}
<div className='commentContentRight'>
{/* 判断是否是编辑状态 */}

View File

@ -52,7 +52,7 @@ function ProjectHomePage(props) {
getCate();
// getList();
getCateTopic();
document.title = '军用开源软件仓库';
document.title = 'JY开源软件仓库';
//
getInitCount();
}, [])
@ -121,7 +121,9 @@ function ProjectHomePage(props) {
params.language_id = topicId.split(",")
params.topic_id = undefined
}
cateID && axios.get(url, { params }).then(result => {
console.log('params', params);
axios.get(url, { params }).then(result => {
if (result && result.data) {
setTotal(result.data.total_count);
setProjectsList(result.data.projects);
@ -152,7 +154,7 @@ function ProjectHomePage(props) {
<div className="banner-content">
<div className="homepageMain banner_main df">
<div className="">
<div className="font-30 color-white banner-title-1">军用开源软件仓库</div>
<div className="font-30 color-white banner-title-1">JY开源软件仓库</div>
<div className="font-16 opacity80 color-white mt5 mb15">汇聚全球顶尖开源力量构建安全协同创新的软件生态赋能军事数字化转型与智能化升级</div>
<FeedBack {...props}/>
</div>

View File

@ -6,7 +6,7 @@ import banner3 from '../../img/home/banner3.webp';
import { Link } from "react-router-dom";
const BANNER_DATA = [
{ key: 1, keyword: '仓库', name: '军用开源软件仓库', title: '军用开源软件仓库', content: '提供软件源码的集中托管与协作、编译构建后的制品统一管理,并支持一键搜索与下载成熟可用的应用程序,为军事软件的全生命周期提供一站式管理与分发服务', img: banner1 },
{ key: 1, keyword: '仓库', name: 'JY开源软件仓库', title: 'JY开源软件仓库', content: '提供软件源码的集中托管与协作、编译构建后的制品统一管理,并支持一键搜索与下载成熟可用的应用程序,为军事软件的全生命周期提供一站式管理与分发服务', img: banner1 },
{ key: 2, keyword: '供应链', name: '供应链监测', title: '供应链监测', content: '实现供应链全流程可视化与智能决策。通过实时数据监控、需求预测与风险预警,优化库存与资源配置,助力精准掌控供应链动态,实现降本增效', img: banner2 },
// { key: 3, keyword: '', name: '', title: '', content: '线', img: banner3 }
]

View File

@ -71,7 +71,7 @@ function Factory(props) {
return (
<div className="home-factory home-module">
<h1 className="module-title">软件工厂专区</h1>
<p className="module-desc">面向军用软件开发的高效项目工程化系统管理与平台支撑</p>
<p className="module-desc">面向JY软件开发的高效项目工程化系统管理与平台支撑</p>
<div className={ `factory-content1 ${isVisible1 ? 'animate-active' : ''}`} ref={content1Ref}>
{
Item1.map((e, index) => {

View File

@ -49,7 +49,7 @@ function Store(props) {
return (
<div className="home-store home-module">
<h1 className="module-title">军用开源软件仓库</h1>
<h1 className="module-title">JY开源软件仓库</h1>
<p className="module-desc">一站式软件资源服务中心</p>
<div className="store-feature">
{

View File

@ -0,0 +1,112 @@
import { Divider } from 'antd';
import React from 'react';
import TerminalAnimation from './terminalAnimation2';
import ActionItem from '../../../../../factory/resource/components/ActionItem';
function Banner() {
const lines1 = [
'if(!isVisible)',
' return',
'',
'const interval = ',
'setInterval(() => {',
' setActive(prev => {',
' return prev + 1',
' });',
'}, 6000);',
'',
'return () => {',
' clearInterval',
' (interval);',
'};',
];
const lines2 = [
'const params = {',
' id: id,',
' ...values',
'}',
'',
'addComment(params)',
' if(res){',
' reloadComment();',
' }',
'})',
]
return (
<div className="banner_v2 color-white">
{/* 主要内容区域 */}
<div className="center">
<div className="main-title-v2">
XX可控开源社区·
<div className="highlight-wrapper">
<div className="text-carousel">
<div className="carousel-text">探索群智众创新范式</div>
<div className="carousel-text">构建军方协同新生态</div>
{/* 克隆元素用于循环 */}
<div className="carousel-text">探索群智众创新范式</div>
<div className="carousel-text">构建军方协同新生态</div>
</div>
</div>
</div>
<p className="font-18 mb40" style={{ marginTop: '-12px' }}>持续汇聚多源需求开放共享可信资源协同创新核心技术聚智赋能软件生态</p>
{/* 中央展示图 */}
<div className="center-image-container width1600">
<ActionItem className="center-1-bk-wrapper" padding="1px" borderRadius="15px">
<div className='center-1-bk'>
<div className='edu-txt-left pl25 pt8'>
<span className='center-1-circle'></span>
<span className='center-1-circle active ml8 mr8'></span>
<span className='center-1-circle'></span>
</div>
<img src={require('../image/img10.png').default}/>
</div>
</ActionItem>
<div className='center-left-bk'>
<TerminalAnimation lines={lines1} width={197} height={280} class_name="center-left-code"/>
</div>
<div className='center-right-bk'>
<TerminalAnimation lines={lines2} width={197} height={186}/>
</div>
</div>
{/* */}
</div>
{/* 底部统计数据 */}
<div className="df stats-container pt10 pb25">
<div className="stat-item">
<div className="stat-number">132</div>
<div className="stat-label">开源项目</div>
</div>
<Divider type="vertical" style={{ height: '60px' }} className="mt10 opacity4" />
<div className="stat-item">
<div className="stat-number">100</div>
<div className="stat-label">合作伙伴</div>
</div>
<Divider type="vertical" style={{ height: '60px' }} className="mt10 opacity4" />
<div className="stat-item">
<div className="stat-number">1000</div>
<div className="stat-label">应用软件</div>
</div>
<Divider type="vertical" style={{ height: '60px' }} className="mt10 opacity4" />
<div className="stat-item">
<div className="stat-number">99</div>
<div className="stat-label">漏洞数量</div>
</div>
<Divider type="vertical" style={{ height: '60px' }} className="mt10 opacity4" />
<div className="stat-item">
<div className="stat-number">56</div>
<div className="stat-label">软件工厂</div>
</div>
<Divider type="vertical" style={{ height: '60px' }} className="mt10 opacity4" />
<div className="stat-item">
<div className="stat-number">21</div>
<div className="stat-label">论坛数量</div>
</div>
</div>
</div>
);
}
export default Banner;

View File

@ -0,0 +1,120 @@
import { Button, Divider } from 'antd';
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';
const CommunityDynamic = () => {
const [list1, setList1] = useState([]);
const [list2, setList2] = useState([]);
const [firstActivity, setFirstActivity] = useState(undefined);
const [list3, setList3] = useState([]);
const firstDynamics = list3[0]
const firstByList1 = list1[0] || {}
useEffect(() => {
getSubDocList(28).then(res => {
if (res && res.data.rows) {
let rows = res.data.rows;
setList1(rows);
}
})
getSubDocList(34).then(res => {
if (res && res.data.rows) {
let rows = res.data.rows;
setList2(rows.slice(1));
// const firstActivity = rows[0];
// firstActivity && getNewsDetail(firstActivity.id).then(res1 => {
// if (res1 && res1.data) {
// setFirstActivity({
// ...res1.data.data,
// content: Base64.decode(res1.data.data.content),
// });
// }
// })
}
})
getSubDocList(39).then(res => {
if (res && res.data.rows) {
let rows = res.data.rows;
setList3(rows);
}
})
}, [])
return (
<div className="community-dynamic pb70">
<div className="title_v2 pt70">社区动态</div>
<div className='news_v2 width1600 padding25-30'>
{/* 精选新闻 */}
<div className="highlight-news">
<img src={require('../image/icon1.png')} alt='' width={30} className='mb10 mr10' />
<span className='font-24 font-bd'>{firstByList1.name}</span>
<p className="opacity8 font-16">
{firstByList1.summary}
</p>
<Link className="blackBut mt12" to={`/news/${firstByList1.id}`}>
<span>了解更多</span>
<span>了解更多</span>
</Link>
</div>
{/* 新闻列表 */}
<div className="df news-list-v2 mt30">
{list1.slice(1,5).map((item, index) => (
<Fragment key={index}>
<div className="news-item-v2">
<Link to={`/news/${firstByList1.id}`}><div className='font-20 font-bd mb10 title-border-to-right task-hide'>{item.name}</div></Link>
<p className='task-hide-2 opacity8' style={{ lineHeight: 'normal' }}>{item.summary}</p>
</div>
{index < 3 && <Divider type="vertical" style={{ height: '60px' }} className="mt20 opacity8" />}
</Fragment>
))}
</div>
</div>
{/* 活动和项目动态 */}
<div className="df width1600 mt20 activities-container">
<div className="padding20-30 activity-section mr20">
<div className='df'>
<div className='flex1 mr20'>
<img src={require('../image/icon2.png')} alt='' width={30} className='mb10 mr10' />
<span className='font-24 font-bd'>精彩活动</span>
<ul>
{list2.slice(0,4).map((item, index) => (
<li key={index} className="activity-item-v2 df">
<span className="font-16">
<Link to={`/news/${firstByList1.id}`} className="title-border-to-right">{item.summary}</Link>
{index === 1 && <span className="hot-tag color-white ml5">HOT</span>}
</span>
{/* <span className="opacity8 font-15">{moment(item.publishTime).format('MM月DD日')}</span> */}
<span className="opacity8 font-15">{item.name}</span>
</li>
))}
</ul>
</div>
<img src={require('../image/img2.png').default} alt='' width={392} />
</div>
</div>
<div className="padding20-30 flex1 project-section">
<img src={require('../image/icon3.png')} alt='' width={30} className='mb10 mr10' />
<span className='font-24 font-bd'>XX项目动态</span>
<ul>
{list3.slice(0,4).map((item, index) => (
<li key={index} className="activity-item-v2 df">
<span className="font-16">
<Link to={`/news/${firstByList1.id}`} className="title-border-to-right">{item.name}</Link>
{index === 0 && <span className="hot-tag color-white ml5">NEW</span>}
</span>
<span className="opacity8 font-15">{moment(item.publishTime).format('YYYY-MM-DD')}</span>
</li>
))}
</ul>
</div>
</div>
</div>
);
};
export default CommunityDynamic;

View File

@ -0,0 +1,142 @@
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') },
// { name: '', logo: require('../image/logo3.png').default },
// { name: '', logo: require('../image/logo4.png').default },
// { name: '', logo: require('../image/logo5.png') },
// { name: '', logo: require('../image/logo6.png') },
// { name: '', logo: require('../image/logo7.png').default },
// { name: '', logo: require('../image/logo8.png').default },
// { name: '', logo: require('../image/logo2.png') },
// { name: '', logo: require('../image/logo9.png') },
// { name: '', logo: require('../image/logo10.png') },
// { name: '', logo: require('../image/logo11.png') },
// { name: '', logo: require('../image/logo12.png') },
// { name: '', logo: require('../image/logo13.png').default }
// ];
//
// const firstRowPartners = partners.slice(0, Math.ceil(partners.length / 2));
// const secondRowPartners = partners.slice(Math.ceil(partners.length / 2));
const partners1 = [
{title: "合作高校", children: [
{ name: '国防科技大学', logo: require('../image/logo1.png') },
{ name: '海军工程大学', logo: require('../image/logo5.png').default },
{ name: '南京信息工程大学', logo: require('../image/logo4.png').default },
{ name: '陆军工程大学', logo: require('../image/logo6.png').default },
{ name: '航天工程大学', logo: require('../image/logo3.png').default },
{ name: '信息工程大学', logo: require('../image/logo14.png').default },
]},
{title: "合作企业", children: [
{ name: '中电太极', logo: require('../image/logo7.png').default },
{ name: '中国电科第十五研究所', logo: require('../image/logo8.png').default },
{ name: '华为', logo: require('../image/logo2.png') },
{ name: '阿里云', logo: require('../image/logo11.png') },
{ name: '飞腾', logo: require('../image/logo10.png') },
{ name: '航天706所', logo: require('../image/logo15.png') },
{ name: '麒麟', logo: require('../image/logo9.png') },
{ name: '中国电子云', logo: require('../image/logo12.png') },
{ name: '中国移动云', logo: require('../image/logo13.png').default }
]},
{title: "合作社区", children: [
{ name: 'GitLink', logo: require('../image/logo16.png') },
{ name: '红山开源', logo: require('../image/logo17.png') },
{ name: '麒麟社区', logo: require('../image/logo18.png') },
{ name: '鸿蒙社区', logo: require('../image/logo19.png') },
{ name: '鸥栖社区', logo: require('../image/logo20.png') },
{ name: '木兰开源社区', logo: require('../image/logo21.png').default },
{ name: '魔搭社区', logo: require('../image/logo22.png') },
{ name: '魔乐社区', logo: require('../image/logo25.png') },
{ name: '启智社区', logo: require('../image/logo23.png') },
{ name: '欧拉社区', logo: require('../image/logo24.png') },
]}
]
//
const handleTabChange = (tab) => {
setActiveTab(tab);
};
return (
<div ref={elementRef} className={`ecosystem-alliance animate-up-box center ${isVisible ? 'animate-up' : ''}`}>
<div className="title_v2 mb20">生态联盟</div>
<div className="tabs_v2 mb80">
<span className={`tab-button pointer ${activeTab === 'partners' ? 'active' : ''} mr20`} onClick={() => handleTabChange('partners')}>
<i className='iconfont icon-hezuohuoban font-14 mr5'></i>
合作伙伴
</span>
<span className={`tab-button pointer ${activeTab === 'contributions' ? 'active' : ''}`} onClick={() => handleTabChange('contributions')}>
<i className='iconfont icon-xiangmugongxian font-14 mr5'></i>
项目贡献
</span>
</div>
<div className='edu-txt-left width1600 mb100'>
<div className='font-24 font-bd mb3'>生态聚力 智创共赢</div>
<div className='font-16 opacity8'>开放协作与合作伙伴共拓市场蓝海链接前沿技术共建智慧产业新生态赋能合作伙伴<br />共筑可持续发展平台</div>
<Button className='mt20 blackBut'>了解更多</Button>
</div>
<div className='width1600 pt70'>
{partners1.map((i,key)=>{
return <div key={key} className='mb40'>
<div className='edu-txt-left font-24 font-bd mb20'>{i.title}</div>
<div className='partner1-list'>
{i.children.map((partner, index)=>{
return <div key={`${key}-${index}`} className="partner1-item edu-txt-left">
<img src={partner.logo} alt={partner.name} />
</div>
})}
</div>
</div>
})}
</div>
{/* <div className="partners-container width1600 mb100">
<div
className="partners-slider slider-row-1 mb20"
>
<div className="slider-content">
{[...firstRowPartners, ...firstRowPartners].map((partner, index) => (
<div key={index} className="partner-item edu-txt-left">
<div className="partner-logo mb15">
<img src={partner.logo} alt={partner.name} />
</div>
<div className="partner-name font-16 font-bd">{partner.name}</div>
</div>
))}
</div>
</div>
<div
className="partners-slider slider-row-2"
>
<div className="slider-content">
{[...secondRowPartners, ...secondRowPartners].map((partner, index) => (
<div key={index} className="partner-item edu-txt-left">
<div className="partner-logo mb15">
<img src={partner.logo} alt={partner.name} />
</div>
<div className="partner-name font-16 font-bd">{partner.name}</div>
</div>
))}
</div>
</div>
</div> */}
</div>
);
};
export default EcosystemAlliance;

View File

@ -0,0 +1,92 @@
import { Divider, Icon } from 'antd';
import React, { Fragment } from 'react';
const ForumExchange = () => {
const forums = [
{
id: 'software-resources',
title: '软件资源需求分论坛',
description: '汇聚XX各单位对开源代码、三方组件、应用软件、\n模型数据、容器镜像等方面的资源需求',
icon: require('../image/icon18.png')
},
{
id: 'supply-chain',
title: '供应链治理分论坛',
description: '交流讨论对软件漏洞、软件溯源、\nSBOM等方面的知识',
icon: require('../image/icon19.png')
},
{
id: 'open-source-development',
title: '开源软件开发协作分论坛',
description: '交流讨论开源写作、漏洞治理、兼容性分析、\n缺陷修复、学习资源等方面的知识',
icon: require('../image/icon20.png')
},
{
id: 'software-factory',
title: '软件工厂技术交流分论坛',
description: '汇聚用户对“软件工厂”的功能/性能需求,交流讨论项目管理、\n软件质量管理、软件智能开发、软件流水线等方面的知识',
icon: require('../image/icon21.png')
}
];
const features = [
{
id: 'technical-discussion',
title: '精准探讨专业技术',
description: '设有问答交流、技术专区、最佳实践等核心版块',
icon: require('../image/icon15.png')
},
{
id: 'knowledge-sharing',
title: '实现知识的共享与思想的碰撞',
description: '支持自由发帖、回帖,参与各类技术讨论',
icon: require('../image/icon16.png')
},
{
id: 'user-feedback',
title: '听见用户的声音',
description: '支持软件需求、功能需求、测试需求等需求汇聚',
icon: require('../image/icon17.png')
}
];
return (
<div className="forum-exchange">
<div className="title_v2 color-white">论坛交流</div>
<div className='center mb60 mt-8'>
<a className='font-16 color-white but-to-right'>探索更多内容 <Icon type="right" className="font-15 icon-to-right"/></a>
</div>
<div className="forums-container width1600 color-white">
{forums.map((forum, index) => (
<Fragment key={forum.id}>
<div>
<img src={forum.icon} alt={forum.title} width={30} className='mb7 mr10'/>
<span className="font-18 font-bd">{forum.title}</span>
<div style={{whiteSpace: 'pre-line'}} className='mt7'>{forum.description}</div>
</div>
{index !== forums.length - 1 && <Divider type="vertical" dashed className="forums_divider mt8" />}
</Fragment>
))}
</div>
<img src={require('../image/img3.png').default} alt="论坛交流" style={{width: '100%'}}/>
<div className="features-container width1600 mt8">
{features.map((feature, index) => (
<Fragment key={feature.id}>
<div className="df feature-item">
<img src={feature.icon} alt={feature.title} width={40}/>
<div className="ml20">
<span className="font-18 font-bd">{feature.title}</span>
<div className='mt5'>{feature.description}</div>
</div>
</div>
{index !== features.length - 1 && <Divider type="vertical" dashed className="forums_divider mt8" />}
</Fragment>
))}
</div>
</div>
);
};
export default ForumExchange;

View File

@ -0,0 +1,92 @@
import React from 'react';
const OpenSourceCollaboration = () => {
const collaborationFeatures = [
{
id: 'code-hosting',
title: '软件代码托管',
description: '提供开源代码git协议访问、分支管理、版本管理、许可证管理等服务',
icon: require('../image/icon11.png')
},
{
id: 'ecosystem',
title: '生态资源持续构建',
description: '提供持续集成流水线、供应链治理流水线、兼容测试流水线、发布流水线等服务',
icon: require('../image/icon12.png')
},
{
id: 'collaborative-improvement',
title: '群智协同改进',
description: '提供功能需求、漏洞治理、缺陷改进、迭代演化等开发能力',
icon: require('../image/icon13.png')
},
{
id: 'knowledge-sharing',
title: '社区信息学习共享',
description: '提供技术交流、开发知识库、开发训练营等服务',
icon: require('../image/icon14.png')
}
];
//
// const leftList = collaborationFeatures.slice(0, Math.ceil(collaborationFeatures.length / 2));
// const rightList = collaborationFeatures.slice(Math.ceil(collaborationFeatures.length / 2));
return (
<div className="open-source-collaboration">
<div className="title_v2">开源协作</div>
<div className="width1600 content-wrapper-v2 df">
{/* 左侧主内容 */}
<div className="left-content-collaboration flex1 pt30">
<span className="badge">
<img src={require("../image/icon22.png")} alt='' width={13} className='mr2'/>
协作开发
</span>
<div className="font-24 font-bd mt15 mb15">一站式协作平台</div>
<div className="font-16 opacity8 mb60">
5G自组网技术联盟是5G自组网技术学术交流平台非社会团体非实体机构由5G自组网领域单位组成遵循公开公平公正的原则开展5G自组网技术体制与标准研究制定促进成员单位深入交流与紧密合作为5G自组网技术发展做出贡献
</div>
<button className="blackBut">立即试用</button>
</div>
{/* 右侧功能卡片 */}
{/* <div className='df right-content-collaboration-1'>
<div className='content-collaboration-left'>
{[...leftList, ...leftList].map((iten, index)=>{
return (
<div key={index} className={`feature-card-left`}>
<img src={iten.icon} alt={iten.title} className="mb10 mr10" width={30} />
<span className="font-20 font-bd">{iten.title}</span>
<div className='' style={{lineHeight: '32px'}}>{iten.description}</div>
</div>
)
})}
</div>
<div className='content-collaboration-right'>
{[...rightList, ...rightList].map((iten, index)=>{
return (
<div key={index} className={`feature-card-right`}>
<img src={iten.icon} alt={iten.title} className="mb10 mr10" width={30} />
<span className="font-20 font-bd">{iten.title}</span>
<div className='' style={{lineHeight: '32px'}}>{iten.description}</div>
</div>
)
})}
</div>
</div> */}
<div className="right-content-collaboration">
{collaborationFeatures.map((feature, index) => (
<div key={feature.id} className={`feature-card ${index % 2 === 0 ? 'even' : 'odd'}`}>
<img src={feature.icon} alt={feature.title} className="mb10 mr10" width={30} />
<span className="font-20 font-bd">{feature.title}</span>
<div className='mt10' style={{lineHeight: '36px'}}>{feature.description}</div>
</div>
))}
</div>
</div>
</div>
);
};
export default OpenSourceCollaboration;

View File

@ -0,0 +1,103 @@
import React, { Fragment, useEffect, useState } from 'react';
import { AlignCenter } from '../../../../Component/layout';
import TerminalAnimation from './terminalAnimation1';
import useIntersectionObserver from '../../../../../common/hooks/useIntersectionObserver';
const OpenSourceCollaboration = () => {
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 = [
{
title: '软件代码托管',
description: '提供开源代码git协议访问、分支管理、版本管理、许可证管理等服务',
},
{
title: '群智协同改进',
description: '提供功能需求、漏洞治理、缺陷改进、迭代演化等开发能力',
},
{
title: '生态资源持续构建',
description: '提供持续集成流水线、供应链治理流水线、兼容测试流水线、发布流水线等服务',
},
{
title: '社区信息学习共享',
description: '提供技术交流、开发知识库、开发训练营等服务',
}
];
const handleFeatureClick = (feature) => {
setSelectedFeature(selectedFeature === feature ? null : feature);
};
return (
<div className={`open-source-collaboration-container animate-up-box ${isVisible ? 'animate-up animate-others' : ''}`} ref={elementRef} >
<div className="title_v2">开源协作</div>
<div className="content-wrapper-bottom width1600"></div>
<div className="content-wrapper width1600 df color-white mb35">
<div className="left-panel-features-list">
{features.map((feature, index) => (
<div
key={index}
className={`feature-item pb20 font-20`}
>
<AlignCenter>
<i className='iconfont icon-sijiaoxing font-15 mr10'></i>
{feature.title}
</AlignCenter>
<div className="opacity8 font-14 ml24">{feature.description}</div>
</div>
))}
<button className="blackBut white mt10">了解更多</button>
</div>
<div className="right-panel">
<div className="right-panel-1">
</div>
<div className="right-panel-2">
</div>
<div className="right-panel-3">
{active === 1 && <Fragment>
<TerminalAnimation startAnimation={isVisible}/>
</Fragment>}
{active === 2 && <Fragment>
<img src={require("../image/img9.png").default} alt='' className='right-img-center'/>
<img src={require("../image/img10.png").default} alt='' className='right-img-left'/>
<img src={require("../image/img8.png").default} alt='' className='right-img-right'/>
<img src={require("../image/img5.png")} alt='' className='right-img-1'/>
<img src={require("../image/img6.png").default} alt='' className='right-img-2'/>
<img src={require("../image/img7.png").default} alt='' className='right-img-3'/>
</Fragment>}
<img src={require('../image/img4.png').default} alt='' className='right-panel-3-img1'/>
</div>
</div>
</div>
</div>
);
};
export default OpenSourceCollaboration;

View File

@ -0,0 +1,91 @@
import React from 'react';
const RepositoryV2 = () => {
const tabs = [
{ id: 'open-source', label: '开源项目', icon: require('../image/icon4.png') },
{ id: 'application', label: '应用软件', icon: require('../image/icon5.png') },
{ id: 'model', label: '模型算法', icon: require('../image/icon6.png') },
{ id: 'dataset', label: '数据集', icon: require('../image/icon7.png') },
{ id: 'container', label: '容器镜像', icon: require('../image/icon8.png') },
{ id: 'component', label: '三方组件', icon: require('../image/icon9.png') }
];
const features = [
{
id: 'signing',
title: '可信签名溯源',
description: '统一认证、可信签名、全程溯源'
},
{
id: 'distribution',
title: '资源高效可靠分发',
description: '高校分发、可靠传输、多节点部署'
},
{
id: 'storage',
title: '广域多元数据统一存储',
description: '元数据管理、多元数据存储、广域数据同步'
},
{
id: 'access',
title: '数据统一可控访问',
description: '智能高效检索、数据可控访问、系统运行监控'
}
];
return (
<div className="repository-v2">
<div className="width1600">
{/* 标题 */}
<div className="title_v2">XX开源软件仓库</div>
{/* 导航标签 */}
<div className="nav-tabs-v2">
{tabs.map((tab) => (
<div key={tab.id} className={`tab-item-v2 font-18`}>
<img src={tab.icon} alt={tab.label} className="mb5 mr3" width={24}/>
<span>{tab.label}</span>
</div>
))}
</div>
{/* 主要内容区域 */}
<div className="content-wrapper-v2 df">
{/* 左侧功能介绍 */}
<div className="left-content-v2 flex1">
<div className="font-26 font-bd mb40">数据统一可控访问</div>
<div className="features-v2">
{features.map((feature) => (
<div key={feature.id}>
<div className="font-16 font-bd mb8">
<img
width={25}
src={require('../image/icon10.png')}
alt=""
className="mr10 mb2"
/>
{feature.title}
</div>
<div>{feature.description}</div>
</div>
))}
</div>
<button className="blackBut">了解更多</button>
</div>
{/* 右侧架构图 - 使用图片 */}
<div className="right-content-v2">
<img
src={require('../image/img3.svg').default}
alt="开源仓库架构图"
className="architecture-image-v2"
/>
</div>
</div>
</div>
</div>
);
};
export default RepositoryV2;

View File

@ -0,0 +1,57 @@
import React, { useCallback, useEffect, useState } from 'react';
import img11 from '../image/img11.png';
import img12 from '../image/img12.png';
import { Button } from 'antd';
function SoftwareFactory() {
const [active, setActive] = useState(1);
useEffect(() => {
console.log('av', active);
const interval = setInterval(() => {
setActive(prevActive => {
return prevActive === 1 ? 2 : 1;
});
}, 6000);
//
return () => {
clearInterval(interval);
};
}, []);
return (
<div className="software-factory">
<div className="title_v2 mb30">软件工厂</div>
<div className={`width1600 df software-factory-${active}`}>
<div className='software-left'>
<div className='software-left-border'>
<div className={`mb35 software-left-item ${active === 1 ? 'active' : ''}`}>
<div className='font-26 font-bd'>
敏捷模式
<span className='ml12'>轻松搭建<i className='iconfont icon-a-zu15512 font-14'></i></span>
</div>
<div className='font-16'>依托敏捷模式灵活迭代高效协同推进让产品搭建更轻松便</div>
</div>
<div className={`software-left-item ${active === 2 ? 'active' : ''}`}>
<div className='font-26 font-bd'>
极速模式
<span className='ml12'>高效快构<i className='iconfont icon-a-zu15512 font-14'></i></span>
</div>
<div className='font-16'>极速模式聚焦任务 - 代码 - 制品助力软件研发快速响应<br />实现极致效率</div>
</div>
</div>
<Button className='blackBut mt70'>了解更多</Button>
</div>
<div className='flex1'>
<img src={active === 1 ? img11 : img12} alt='' />
</div>
{/* 角标 */}
<div className='mode-tag font-16 font-bd color-white'>{active === 1 ? "敏捷模式" : "极速模式"}</div>
</div>
</div>
);
};
export default SoftwareFactory;

View File

@ -0,0 +1,82 @@
// components/SupplyChainGovernance.jsx
import React from 'react';
const SupplyChainGovernance = () => {
const governanceItems = [
{
id: 'security',
title: '供应链安全XX',
subtitle: '汇聚漏洞、恶意代码、组件三类关键信息要素',
image: '/images/supply-chain-security.png',
width: '40%'
},
{
id: 'software',
title: '软件供应链治理',
subtitle: '开展软件供应链成分分析和组件安全性分析',
image: '/images/software-supply-chain.png',
width: '28.5%'
},
{
id: 'risk',
title: '实时风险预警',
subtitle: '根据新发布漏洞信息,实时评估和通报软件供应链风险',
image: '/images/risk-warning.png',
width: '28.5%'
},
{
id: 'vulnerability',
title: '已知漏洞监测',
subtitle: '支持15种主流编程语言的软件漏洞检测',
image: '/images/vulnerability-detection.png',
width: '28.5%'
},
{
id: 'assessment',
title: '软件治理能力评估',
subtitle: '数据构建、多维分析、量化打分、结果融合的评估闭环',
image: '/images/software-assessment.png',
width: '28.5%'
},
{
id: 'hardware',
title: '硬件供应链能力评估',
subtitle: '构建单芯片或基于SIP封装的多芯片模块的供应链供给能力评估',
image: '/images/hardware-assessment.png',
width: '40%'
}
];
return (
<div className="supply-chain-governance">
<div className="title_v2">供应链治理</div>
<div className="width1600 governance-grid">
{governanceItems.map((item, index) => (
<div
key={item.id}
className={`governance-item governance-item${index}`}
style={{
width: item.width,
}}
>
{/* 标签区域 */}
<div className="tags">
<span className="tag-text">XXX防雷</span>
<span className="tag-value opacity7">20万激励金</span>
<span className="tag-value opacity7">多合作资源</span>
</div>
<div className='font-26 font-bd mt25'>{item.title}</div>
<div className='font-16'>{item.subtitle}</div>
{/* 图片 */}
<div className="image-container">
{/* <img src={item.image} alt={item.title} className="item-image" /> */}
</div>
</div>
))}
</div>
</div>
);
};
export default SupplyChainGovernance;

View File

@ -0,0 +1,200 @@
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 (
<div className='center'>
<canvas
ref={canvasRef}
width="755"
height="420"
/>
</div>
);
};
export default TerminalAnimation;

View File

@ -0,0 +1,267 @@
import React, { useEffect, useRef, useState } from 'react';
const TerminalAnimation = ({startAnimation=false}) => {
const [animationState, setAnimationState] = useState({
lineI: 0,
charI: 0,
curOn: true
});
const animationRef = useRef(null);
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',
];
const keyColor = {
'git clone': '#fff700',
'git commit': '#fff700',
'git log': '#fff700',
'git merge': '#fff700',
'git remote': '#fff700',
'git pull': '#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 lineNumberWidth = 40; //
//
const getCharWidth = (charCount) => charCount * 9.6;
//
const renderColoredText = (text, x, y, availableChars) => {
const elements = [];
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);
if (charsToRender > 0) {
const displayText = key.substring(0, charsToRender);
elements.push(
<text
key={`colored-${currentX}-${charsRendered}`}
x={currentX}
y={y}
fill={keyColor[key]}
fontFamily="'Courier New', monospace"
fontSize={fontSize}
dominantBaseline="middle"
fontWeight="bold"
>
{displayText}
</text>
);
currentX += getCharWidth(displayText.length);
charsRendered += charsToRender;
remainingText = remainingText.substring(charsToRender);
colored = true;
}
break;
}
}
//
if (!colored && remainingText.length > 0) {
const charsToRender = Math.min(1, availableChars - charsRendered);
if (charsToRender > 0) {
const char = remainingText[0];
elements.push(
<text
key={`normal-${currentX}-${charsRendered}`}
x={currentX}
y={y}
fill="#ffffff"
fontFamily="'Courier New', monospace"
fontSize={fontSize}
dominantBaseline="middle"
fontWeight="bold"
>
{char}
</text>
);
currentX += getCharWidth(1);
charsRendered += charsToRender;
remainingText = remainingText.substring(charsToRender);
}
}
if (charsRendered >= availableChars) break;
}
return { elements, currentX, charsRendered };
};
//
const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => {
let x = lineNumberWidth; //
//
const lineNumberElement = (
<text
key="line-number"
x={lineNumberWidth / 2}
y={y+1}
fill="#ffffff"
fontFamily="'Courier New', monospace"
fontSize={fontSize}
dominantBaseline="middle"
textAnchor="middle"
fontWeight="bold"
>
{lineIndex + 1}.
</text>
);
//
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(
<rect
key="cursor"
x={cursorX + 2}
y={y - fontSize/2 + 2}
width={2}
height={fontSize - 4}
fill="#ffffff"
/>
);
}
return allElements;
};
//
useEffect(() => {
if (!startAnimation) {
setAnimationState({
lineI: 0,
charI: 0,
curOn: true
});
return;
}
let lastCharTime = Date.now();
let lastLineTime = Date.now();
let lastBlinkTime = Date.now();
const animate = () => {
const now = Date.now();
let shouldUpdate = false;
let newState = { ...animationState };
//
if (now - lastBlinkTime > 500) {
newState.curOn = !newState.curOn;
lastBlinkTime = now;
shouldUpdate = true;
}
//
if (newState.lineI === 0 && newState.charI < lines[0].length && now - lastCharTime > charGap) {
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; //
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; //
lastLineTime = now;
shouldUpdate = true;
}
if (shouldUpdate) {
setAnimationState(newState);
}
animationRef.current = requestAnimationFrame(animate);
};
animationRef.current = requestAnimationFrame(animate);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [animationState, startAnimation]);
return (
<div className='center pl15'>
<svg
width="755"
height="420"
viewBox="0 0 755 420"
xmlns="http://www.w3.org/2000/svg"
style={{ backgroundColor: 'transparent' }}
>
{/* 渲染已完成的行 */}
{lines.slice(0, animationState.lineI).map((line, index) => (
<g key={index}>
{renderTextSegments(line, line.length, startY + index * lineH, false, index)}
</g>
))}
{/* 渲染当前正在输入的行 */}
{animationState.lineI < lines.length && (
<g key="current-line">
{renderTextSegments(lines[animationState.lineI], animationState.charI, startY + animationState.lineI * lineH, true, animationState.lineI)}
</g>
)}
</svg>
</div>
);
};
export default TerminalAnimation;

View File

@ -0,0 +1,253 @@
import React, { useEffect, useRef, useState } from 'react';
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 = {
'git clone': '#fff700',
'git commit': '#fff700',
'git log': '#fff700',
'git merge': '#fff700',
'git remote': '#fff700',
'git pull': '#d24a6f',
'git push': '#d24a6f',
'git status': '#2977ff',
'git fetch': '#2977ff',
'git add': '#9aff41',
'cd forgeplus': '#9aff41',
'git checkout': '#9aff41'
};
const fontSize = 12;
const lineH = 20;
const startY = 10;
const charGap = 30;
const lineGap = 300;
const lineNumberWidth = 0; //
//
const getCharWidth = (charCount) => charCount * 9.6;
//
const renderColoredText = (text, x, y, availableChars) => {
const elements = [];
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);
if (charsToRender > 0) {
const displayText = key.substring(0, charsToRender);
elements.push(
<text
key={`colored-${currentX}-${charsRendered}`}
x={currentX}
y={y}
fill={keyColor[key]}
fontFamily="'Courier New', monospace"
fontSize={fontSize}
dominantBaseline="middle"
fontWeight="bold"
>
{displayText}
</text>
);
currentX += getCharWidth(displayText.length);
charsRendered += charsToRender;
remainingText = remainingText.substring(charsToRender);
colored = true;
}
break;
}
}
//
if (!colored && remainingText.length > 0) {
const charsToRender = Math.min(1, availableChars - charsRendered);
if (charsToRender > 0) {
const char = remainingText[0];
elements.push(
<text
key={`normal-${currentX}-${charsRendered}`}
x={currentX}
y={y}
fill="#8187B9"
fontFamily="'Courier New', monospace"
fontSize={fontSize}
dominantBaseline="middle"
fontWeight="bold"
>
{char}
</text>
);
currentX += getCharWidth(1);
charsRendered += charsToRender;
remainingText = remainingText.substring(charsToRender);
}
}
if (charsRendered >= availableChars) break;
}
return { elements, currentX, charsRendered };
};
//
const renderTextSegments = (line, charLimit, y, isCurrentLine, lineIndex) => {
let x = lineNumberWidth; //
//
const lineNumberElement = (
<text
key="line-number"
x={lineNumberWidth / 2}
y={y+1}
fill="#ffffff"
fontFamily="'Courier New', monospace"
fontSize={fontSize}
dominantBaseline="middle"
textAnchor="middle"
fontWeight="bold"
>
{lineIndex + 1}.
</text>
);
//
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(
<rect
key="cursor"
x={cursorX + 2}
y={y - fontSize/2 + 2}
width={2}
height={fontSize - 4}
fill="#8187B9"
/>
);
}
return allElements;
};
//
useEffect(() => {
if (!startAnimation) {
setAnimationState({
lineI: 0,
charI: 0,
curOn: true
});
return;
}
let lastCharTime = Date.now();
let lastLineTime = Date.now();
let lastBlinkTime = Date.now();
const animate = () => {
const now = Date.now();
let shouldUpdate = false;
let newState = { ...animationState };
//
if (now - lastBlinkTime > 500) {
newState.curOn = !newState.curOn;
lastBlinkTime = now;
shouldUpdate = true;
}
//
if (newState.lineI === 0 && newState.charI < lines[0].length && now - lastCharTime > charGap) {
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; //
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; //
lastLineTime = now;
shouldUpdate = true;
}
if (shouldUpdate) {
setAnimationState(newState);
}
animationRef.current = requestAnimationFrame(animate);
};
animationRef.current = requestAnimationFrame(animate);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [animationState, startAnimation]);
return (
<div className={class_name}>
<svg
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
xmlns="http://www.w3.org/2000/svg"
style={{ backgroundColor: 'transparent' }}
>
{/* 渲染已完成的行 */}
{lines.slice(0, animationState.lineI).map((line, index) => (
<g key={index}>
{renderTextSegments(line, line.length, startY + index * lineH, false, index)}
</g>
))}
{/* 渲染当前正在输入的行 */}
{animationState.lineI < lines.length && (
<g key="current-line">
{renderTextSegments(lines[animationState.lineI], animationState.charI, startY + animationState.lineI * lineH, true, animationState.lineI)}
</g>
)}
</svg>
</div>
);
};
export default TerminalAnimation;

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="140" height="48" viewBox="0 0 140 48">
<defs>
<clipPath id="clip-path">
<path id="矩形_5746" data-name="矩形 5746" d="M0,0H125a15,15,0,0,1,15,15V48a0,0,0,0,1,0,0H0a0,0,0,0,1,0,0V0A0,0,0,0,1,0,0Z" fill="#fff" stroke="#707070" stroke-width="1"/>
</clipPath>
<linearGradient id="linear-gradient" x1="1.027" x2="0.073" y2="1" gradientUnits="objectBoundingBox">
<stop offset="0" stop-color="#258832"/>
<stop offset="1" stop-color="#86d564"/>
</linearGradient>
</defs>
<g id="绿色" clip-path="url(#clip-path)">
<path id="_0a3e2df8-6b3f-4c54-a365-1ee1dd6ae6b2" data-name="0a3e2df8-6b3f-4c54-a365-1ee1dd6ae6b2" d="M140.032,0H0L9.069,39.961C10.161,44.776,13.11,48,16.419,48H140.032Z" transform="translate(-0.032)" fill="url(#linear-gradient)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 890 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="140" height="48" viewBox="0 0 140 48">
<defs>
<clipPath id="clip-path">
<path id="矩形_5746" data-name="矩形 5746" d="M0,0H125a15,15,0,0,1,15,15V48a0,0,0,0,1,0,0H0a0,0,0,0,1,0,0V0A0,0,0,0,1,0,0Z" fill="#fff" stroke="#707070" stroke-width="1"/>
</clipPath>
<linearGradient id="linear-gradient" x1="1.027" x2="0.073" y2="1" gradientUnits="objectBoundingBox">
<stop offset="0" stop-color="#3f4df4"/>
<stop offset="1" stop-color="#649dff"/>
</linearGradient>
</defs>
<g id="蒙版组_862" data-name="蒙版组 862" clip-path="url(#clip-path)">
<path id="_0a3e2df8-6b3f-4c54-a365-1ee1dd6ae6b2" data-name="0a3e2df8-6b3f-4c54-a365-1ee1dd6ae6b2" d="M140.032,0H0L9.069,39.961C10.161,44.776,13.11,48,16.419,48H140.032Z" transform="translate(-0.032)" fill="url(#linear-gradient)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 608 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Some files were not shown because too many files have changed in this diff Show More