Merge remote-tracking branch '172/factory' into factory-hxy
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
20
src/App.css
|
|
@ -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;
|
||||
}
|
||||
75
src/App.js
|
|
@ -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={
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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'>
|
||||
{/* 判断是否是编辑状态 */}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 4.5 MiB |
|
After Width: | Height: | Size: 888 KiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 704 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 752 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -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 |
|
|
@ -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 |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 805 B |
|
After Width: | Height: | Size: 698 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 956 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 917 B |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 345 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 608 KiB |
|
After Width: | Height: | Size: 637 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 357 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |