新增下拉加载、新增动画、部分样式修复

This commit is contained in:
黄心宇 2025-12-22 15:12:15 +08:00
parent c600b153e6
commit 7bc8d11423
12 changed files with 324 additions and 175 deletions

View File

@ -39,29 +39,31 @@ function Feature(props) {
return (
<div className={`factory-feature factory-module ${isVisible ? 'animate-active' : ''}`} ref={contentRef}>
<h1 className="module-title">工厂引擎全面赋能XX软件敏捷开发</h1>
<div className="feature-content">
<div className="content-top">
<div className="content-top-left">
<h3>智能化驱动释放研发新效能</h3>
<p>AI深度融入需求分析代码生成测试验证等关键环节大幅提升开发效率</p>
<img className="content-top-left-gif" src={gif1} alt="" />
<img src={featureLeft1} alt="" />
</div>
<div className="content-top-right">
<h3>数字化纳管掌控研发全链路</h3>
<p>研发活动及制品全流程数字化纳管支持精准决策和持续优化</p>
<img className="content-top-right-gif" src={gif2} alt="" />
<img src={featureright1} alt="" />
</div>
</div>
<div className="content-bottom">
<div>
<h3>敏捷化协同实现能力极速交付</h3>
<p>打破用户-研发团队-运维团队协作壁垒大幅缩短交付周期</p>
<div className="feature-container">
<h1 className="module-title">工厂引擎全面赋能XX软件敏捷开发</h1>
<div className="feature-content">
<div className="content-top">
<div className="content-top-left">
<h3>智能化驱动释放研发新效能</h3>
<p>AI深度融入需求分析代码生成测试验证等关键环节大幅提升开发效率</p>
<img className="content-top-left-gif" src={gif1} alt="" />
<img src={featureLeft1} alt="" />
</div>
<div className="content-top-right">
<h3>数字化纳管掌控研发全链路</h3>
<p>研发活动及制品全流程数字化纳管支持精准决策和持续优化</p>
<img className="content-top-right-gif" src={gif2} alt="" />
<img src={featureright1} alt="" />
</div>
</div>
<div className="content-bottom">
<div>
<h3>敏捷化协同实现能力极速交付</h3>
<p>打破用户-研发团队-运维团队协作壁垒大幅缩短交付周期</p>
</div>
<img className="content-bottom-gif" src={gif3} alt="" />
<img src={ featureBottom1 } alt="" />
<img src={featureBottom1} alt="" />
</div>
</div>
</div>
</div>

View File

@ -1,10 +1,19 @@
.factory-feature {
margin: auto;
padding-top: 67px;
background-image: url('../../../images/factory/feature-bg.png');
background-repeat: no-repeat;
background-size: 100% auto;
background-position: center -0px;
.feature-container {
opacity: 0;
transform: scale(0.9);
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
margin: auto;
padding-top: 67px;
background-image: url('../../../images/factory/feature-bg.png');
background-repeat: no-repeat;
background-size: 100% auto;
background-position: center -0px;
}
h1 {
color: #ffffff !important;
}

View File

@ -81,6 +81,7 @@
margin-top: 69px;
&-left {
opacity: 0;
width: 1153px;
height: 475px;
background-image: url('../../../images/factory/news-1-left.png');
@ -170,6 +171,7 @@
}
&-right {
opacity: 0;
background: url('../../../images/factory/news-1-right.png'), #ffffff;
background-repeat: no-repeat;
background-size: 100% 100%;

View File

@ -167,6 +167,7 @@
margin-top: 21px;
line-height: 26px;
}
button {
&:hover {
border-color: #164ce4;
@ -182,6 +183,7 @@
height: 622px;
background: #d5dfff;
z-index: 3;
.content-item3 {
width: 385px;
height: 246px;
@ -192,20 +194,25 @@
display: flex;
flex-direction: column;
margin-right: 15px;
.item-desc {
margin-top: 27px;
}
.item-btn {
margin-top: auto;
button {
margin-right: 20px;
background-color: #ffffff;
color: #091221;
&:nth-child(1) {
background-color: #091221;
color: #ffffff;
border: none;
}
&:nth-child(2) {
&:hover {
border-color: #164ce4;
@ -264,6 +271,7 @@
color: #164ce4;
font-size: 14px;
width: fit-content;
&:hover {
opacity: 0.7;
}
@ -317,7 +325,7 @@
overflow: hidden;
}
button{
button {
margin-top: auto;
width: 84px;
height: 36px;
@ -337,18 +345,4 @@
}
}
}
@keyframes resourceShow {
to {
opacity: 1;
transform: scale(1);
}
}
}
.animate-active {
.resource-container {
animation: resourceShow 0.8s ease-in-out forwards;
}
}

View File

@ -23,6 +23,7 @@
}
&-left {
opacity: 0;
width: 997px;
height: 600px;
background-image: url('../../../images/factory/lessons-left-bg.webp');
@ -113,7 +114,7 @@
}
&-right {
opacity: 0;
.standard,
.manual {
background-repeat: no-repeat;
@ -179,6 +180,13 @@
margin-left: 35px;
}
h3 {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 285px;
}
p {
margin-top: 15px;
display: -webkit-box;

View File

@ -2,6 +2,7 @@
margin-top: 70px;
.scene-list {
opacity: 0;
margin-top: 43px;
display: flex;
flex-wrap: wrap;

View File

@ -154,15 +154,39 @@
}
.animate-active {
.module-title {
.module-title,
video,
.news-1-left,
.news-1-right,
.resource2-content-left,
.resource2-content-right,
.scene-list {
animation: fadeInUp 0.8s ease-out forwards;
}
.resource-container, .feature-container {
animation: resourceShow 0.8s ease-in-out forwards;
}
video,
.news-1-left,
.resource2-content-left,
.scene-list {
animation-delay: 0.2s; // 延迟 0.2s 执行
}
.news-1-right,
.resource2-content-right {
animation-delay: 0.4s; // 延迟 0.2s 执行
}
}
&-video {
margin-top: 83px;
video {
opacity: 0;
margin-top: 31px;
width: 1144px;
height: 654px;
@ -188,4 +212,11 @@
opacity: 1;
transform: translateY(0);
}
}
@keyframes resourceShow {
to {
opacity: 1;
transform: scale(1);
}
}

View File

@ -330,6 +330,7 @@
font-size: 16px;
}
}
.button-wrapper {
width: 100%;
height: 80px;
@ -338,6 +339,7 @@
justify-content: center;
display: none;
margin-top: auto;
button {
cursor: pointer;
width: 449px;
@ -353,15 +355,18 @@
}
}
}
&:hover {
.chapter-info {
height: 302px;
background: #ffffff;
margin-top: -73px;
}
.chapter-tag {
display: none;
}
.button-wrapper {
display: flex;
}
@ -397,10 +402,15 @@
h4 {
color: #164ce4;
}
&:hover {
.line-1,.line-2,.line-3 {
.line-1,
.line-2,
.line-3 {
opacity: 1;
}
}
.line-1 {
animation: wave1 1.2s ease-in-out infinite;
}
@ -568,14 +578,20 @@
&-head {
display: flex;
h4 {
line-height: 33px;
font-family: alibabaBold;
font-weight: 700;
color: #091221;
font-size: 24px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
button {
flex-shrink: 0;
cursor: pointer;
margin-left: auto;
margin-right: 88px;
@ -618,6 +634,7 @@
transform: scale(1.1);
}
}
button {
opacity: 1;
}

View File

@ -12,7 +12,8 @@ import headerBottom from '../../images/factory/news/header-bottom.png'
import bodyBg from '../../images/factory/news/news-bg.webp'
import '../common.scss'
import { Link } from "react-router-dom";
import moment from "moment";
import moment from "moment";
import InfiniteScroll from 'react-infinite-scroller';
const backList = [back1, back2, back3]
@ -27,6 +28,11 @@ function Index(props) {
const [isVisible, setIsVisible] = useState(false);
const carouselEL = useRef(null);
const containerRef = useRef(null);
const pageSize = 10
const [pageNum, setPageNum] = useState(1)
const [hasMore, setHasMore] = useState(true)
const [loading, setLoading] = useState(false)
const [total, setTotal] = useState(0)
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
@ -68,12 +74,23 @@ function Index(props) {
})
}
const getFullList = () => {
getSubDocList(dirId, { isHomepage: 0 }).then(res => {
const rows = res.data.rows
setNewsList1(rows.slice(0, 2))
setNewsList2(rows.slice(2, 6))
setNewsList3(rows.splice(6))
const getFullList = (isMore = false) => {
if (!hasMore) return
setLoading(true)
getSubDocList(dirId, { isHomepage: 0, pageSize, pageNum : isMore || pageNum }).then(res => {
const rows = res.data.rows
setLoading(false)
if (isMore) {
setPageNum(isMore)
setNewsList3([...newsList3, ...rows])
} else {
setNewsList1(rows.slice(0, 2))
setNewsList2(rows.slice(2, 6))
setNewsList3(rows.slice(6))
}
setHasMore(res.data.total > (isMore ? 6 + newsList3.length + rows.length : rows.length))
setTotal(res.data.total)
})
}
@ -193,28 +210,38 @@ function Index(props) {
})
}
</div>
<div className="news-body-bottom">
{
newsList3.map((item, index) => {
return <div className="news-item" key={index}>
<div className="news-item-img">
<img src={item.headImg || backList[index % 3]} alt="" />
</div>
<div className="news-item-info">
<Link to={`/softwareFactory/news/${item.id}`}><h3>{item.name}</h3></Link>
<p>{item.summary}</p>
<div className="news-item-info-bottom">
<span>{item.publishTime}</span>
<Link to={`/softwareFactory/news/${item.id}`}>
<span>查看详情</span>
<KFIcon type="icon-icon_blackarrow_hover" font={40} />
</Link>
<InfiniteScroll
threshold={10}
initialLoad={false}
pageStart={1}
loadMore={(e) => { getFullList(e) }}
hasMore={!loading && hasMore}
>
<div className="news-body-bottom">
{
newsList3.map((item, index) => {
return <div className="news-item" key={index}>
<div className="news-item-img">
<img src={item.headImg || backList[index % 3]} alt="" />
</div>
<div className="news-item-info">
<Link to={`/softwareFactory/news/${item.id}`}><h3>{item.name}</h3></Link>
<p>{item.summary}</p>
<div className="news-item-info-bottom">
<span>{item.publishTime}</span>
<Link to={`/softwareFactory/news/${item.id}`}>
<span>查看详情</span>
<KFIcon type="icon-icon_blackarrow_hover" font={40} />
</Link>
</div>
</div>
</div>
</div>
})
}
</div>
})
}
</div>
</InfiniteScroll>
</div>
</div>
)

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState, useRef } from 'react';
import './index.scss';
import { getSourceList, getSourceZoneList, httpUrl } from '../../api'
import { factoryZoneId, lessonId , versionId, toolId } from '../../../constants/factory'
import { factoryZoneId, lessonId, versionId, toolId } from '../../../constants/factory'
import KFIcon from '../../../components/KFIcon';
import bg1 from '../../../images/factory/resource/tools-top-1.webp';
import bg2 from '../../../images/factory/resource/tools-top-2.webp';
@ -14,6 +14,7 @@ import ActionItem from '../components/ActionItem';
import '../../common.scss'
import { downloadFunc } from '../../../forge/Utils/utils'
import { Link } from "react-router-dom";
import InfiniteScroll from 'react-infinite-scroller';
const bgList = [bg1, bg2, bg3]
@ -28,39 +29,55 @@ function Index(props) {
const [newList, setNewList] = useState([]);
const [selectedType, setSelectedType] = useState(null);
const pageSize = 10
const [pageNum, setPageNum] = useState(1)
const [loading, setLoading] = useState(false)
const hasMore = useRef(true)
useEffect(() => {
getFullList();
// getFullList();
getTypeList();
getNewList()
}, []);
useEffect(() => {
if (selectedType) {
getTypeSourceList()
} else {
getFullList()
}
setPageNum(1)
hasMore.current = true
getFullList()
}, [selectedType])
const getFullList = () => {
const getFullList = (isMore = false) => {
if (!hasMore.current) return
let params = {
id: factoryZoneId,
keywords: toolId,
pageNum: isMore ? isMore : pageNum,
pageSize
}
getSourceList({ id: factoryZoneId, keywords: toolId }).then(res => {
if (selectedType) params.domainId = selectedType
setLoading(true)
getSourceList(params).then(res => {
setLoading(false)
if (res.data.rows) {
const rows = res.data.rows
setTopList(rows.slice(0, 3));
setNewList(rows.slice(0, 2));
setContentList(rows.slice(3, 9));
setFullList(rows.slice(9));
if (isMore) {
setPageNum(isMore)
setFullList([...fullList, ...rows])
} else if (selectedType) {
setContentList(res.data.rows.slice(0, 6));
setFullList(res.data.rows.slice(6));
} else {
setTopList(rows.slice(0, 3));
setContentList(rows.slice(3, 9));
setFullList(rows.slice(9));
}
hasMore.current = res.data.total > (isMore ? selectedType ? 6 : 9 + fullList.length + rows.length : rows.length)
}
});
};
const getTypeSourceList = () => {
getSourceList({ id: factoryZoneId, domainId: selectedType }).then(res => {
setContentList(res.data.rows.slice(0, 6));
setFullList(res.data.rows.slice(6));
});
}
const getTypeList = () => {
getSourceZoneList(factoryZoneId).then(response => {
if (response && response.data) {
@ -69,6 +86,13 @@ function Index(props) {
}).catch(error => { })
}
const getNewList = () => {
getSourceList({ id: factoryZoneId, sort: 'createTime', keywords: toolId }).then(res => {
const rows = res.data.rows
setNewList(rows.slice(0, 2));
});
}
const getBackgroundImage = (index) => {
const row = Math.floor(index / 4); //
const col = index % 4; //
@ -103,7 +127,7 @@ function Index(props) {
<span className="item-tag">{item.domainName}</span>
<p className="item-desc">{item.summary}</p>
</div>
<button onClick={() => {downloadFunc(`${httpUrl}/file/open/download/${item.fileIds}`)}}>立即下载</button>
<button onClick={() => { downloadFunc(`${httpUrl}/file/open/download/${item.fileIds}`) }}>立即下载</button>
</div>
)
})
@ -146,7 +170,7 @@ function Index(props) {
</div>
<div className="two-btn">
<Link to={`/softwareFactory/resource/developmentTools/${item.id}`}><button className="shiny-button">查看</button></Link>
<button onClick={() => {downloadFunc(`${httpUrl}/file/open/download/${item.fileIds}`)}}>下载</button>
<button onClick={() => { downloadFunc(`${httpUrl}/file/open/download/${item.fileIds}`) }}>下载</button>
</div>
</ActionItem>
})
@ -171,7 +195,7 @@ function Index(props) {
<p className="item-desc">
{item.summary}
</p>
<button onClick={() => {downloadFunc(`${httpUrl}/file/open/download/${item.fileIds}`)}}>下载</button>
<button onClick={() => { downloadFunc(`${httpUrl}/file/open/download/${item.fileIds}`) }}>下载</button>
</div>
</div>
})
@ -179,36 +203,45 @@ function Index(props) {
</div>
</div>
</div>
{
fullList.length > 0 && <InfiniteScroll
key={selectType}
threshold={10}
initialLoad={false}
pageStart={1}
loadMore={(e) => { getFullList(e) }}
hasMore={!loading && hasMore.current}
><div className="tools-bottom">
{
fullList.map((item, index) => {
const createUser = item.createUser
return <ActionItem className="content-bottom-wrapper" key={index} backgroundColor="rgba(241, 247, 255, 0.48)" padding="1px" borderRadius="16px">
<div className="content-bottom-item">
<div className="tools-bottom">
{
fullList.map((item, index) => {
const createUser = item.createUser
return <ActionItem className="content-bottom-wrapper" key={index} backgroundColor="rgba(241, 247, 255, 0.48)" padding="1px" borderRadius="16px">
<div className="content-bottom-item">
<img src={getBackgroundImage(index)} alt="" className="item-bg" />
<div className="item-top">
<img src={createUser.avatar} alt="" className="item-img"></img>
<h5 className="item-title">{item.name}</h5>
</div>
<div className="item-desc">
{item.summary}
</div>
<div className="item-data">
<span><i className="iconfont icon-xiazai mr5 font-12" />{item.downloadCount}</span>
</div>
</div>
<div className="content-bottom-btn">
<div className="two-btn">
<Link to={`/softwareFactory/resource/developmentTools/${item.id}`}><button className="shiny-button">查看</button></Link>
<button onClick={() => {downloadFunc(`${httpUrl}/file/open/download/${item.fileIds}`)}}>下载</button>
</div>
</div>
</ActionItem>
})
}
</div>
<img src={getBackgroundImage(index)} alt="" className="item-bg" />
<div className="item-top">
<img src={createUser.avatar} alt="" className="item-img"></img>
<h5 className="item-title">{item.name}</h5>
</div>
<div className="item-desc">
{item.summary}
</div>
<div className="item-data">
<span><i className="iconfont icon-xiazai mr5 font-12" />{item.downloadCount}</span>
</div>
</div>
<div className="content-bottom-btn">
<div className="two-btn">
<Link to={`/softwareFactory/resource/developmentTools/${item.id}`}><button className="shiny-button">查看</button></Link>
<button onClick={() => { downloadFunc(`${httpUrl}/file/open/download/${item.fileIds}`) }}>下载</button>
</div>
</div>
</ActionItem>
})
}
</div>
</InfiniteScroll>
}
</div>
)
}

View File

@ -3,6 +3,7 @@
flex-direction: column;
align-items: center;
margin-top: -50px;
padding-bottom: 100px;
&-top {
display: flex;

View File

@ -18,6 +18,7 @@ import bg8 from '../../../images/factory/resource/project-bottom-1.webp'
import ActionItem from '../components/ActionItem';
import '../../common.scss'
import { Link } from 'react-router-dom';
import InfiniteScroll from 'react-infinite-scroller';
const bgList = [bg1, bg2, bg3]
@ -28,6 +29,10 @@ function Index(props) {
const [topList, setTopList] = useState([]);
const [contentList, setContentList] = useState([]);
const [fullList, setFullList] = useState([]);
const pageSize = 10
const [pageNum, setPageNum] = useState(1)
const [hasMore, setHasMore] = useState(true)
const [loading, setLoading] = useState(false)
useEffect(() => {
getFullList();
@ -40,12 +45,24 @@ function Index(props) {
});
};
const getFullList = () => {
getProjectsLists(factoryZoneId, { isHomepage: 0 }).then(res => {
// contentList
setContentList(res.data.rows.slice(0, 4));
// fullList
setFullList(res.data.rows.slice(4));
const getFullList = (isMore = false) => {
if (!hasMore) return
setLoading(true)
getProjectsLists(factoryZoneId, { isHomepage: 0, pageSize, pageNum: isMore || pageNum }).then(res => {
const rows = res.data.rows
setLoading(false)
if (isMore) {
setPageNum(isMore)
setFullList([...fullList, ...rows])
} else {
// contentList
setContentList(res.data.rows.slice(0, 4));
// fullList
setFullList(res.data.rows.slice(4));
}
setHasMore(res.data.total > (isMore ? 4 + fullList.length + rows.length : rows.length))
// setTotal(res.data.total)
});
};
@ -126,56 +143,63 @@ function Index(props) {
</div>
<img className="project-content-top-right" src={contentRight} alt="" />
</div>
<InfiniteScroll
threshold={10}
initialLoad={false}
pageStart={1}
loadMore={(e) => { getFullList(e) }}
hasMore={!loading && hasMore}
>
<div className="project-content-bottom">
{
fullList.map((item, index) => {
const project = item.projectProperties
project.praisesCount = 3
project.forkedCount = 3
return <ActionItem className="content-bottom-wrapper" key={index} backgroundColor="rgba(19, 53, 91, 0.11)" padding="1px" borderRadius="16px">
<div className="content-bottom-item">
{
index % 2 === 0 && <img className="item-bg" src={bg8} alt="" />
}
<div className="project-content-bottom">
{
fullList.map((item, index) => {
const project = item.projectProperties
project.praisesCount = 3
project.forkedCount = 3
return <ActionItem className="content-bottom-wrapper" key={index} backgroundColor="rgba(19, 53, 91, 0.11)" padding="1px" borderRadius="16px">
<div className="content-bottom-item">
{
index % 2 === 0 && <img className="item-bg" src={bg8} alt="" />
}
<div className="item-top">
<img src={project.authorImageUrl} alt="" className="item-img"></img>
<h5 className="item-title">{project.name}</h5>
</div>
{
project.description && <div className="item-desc">
{project.description}
<div className="item-top">
<img src={project.authorImageUrl} alt="" className="item-img"></img>
<h5 className="item-title">{project.name}</h5>
</div>
}
<div className="item-tags">
{
project.topics && project.topics.split(',').splice(0, 3).map((topic, index) => {
return (
<span key={index} className="item-tag">{topic}</span>
)
})
project.description && <div className="item-desc">
{project.description}
</div>
}
<div className="item-tags">
{
project.topics && project.topics.split(',').splice(0, 3).map((topic, index) => {
return (
<span key={index} className="item-tag">{topic}</span>
)
})
}
</div>
<div className="item-data">
{
project.praisesCount > 0 && <span><i className="iconfont icon-liulanliang mr5 font-12" />{project.praisesCount}</span>
}
{
project.forkedCount > 0 && <span><i className="iconfont icon-shoucang mr5 font-12" />{project.forkedCount}</span>
}
</div>
</div>
<div className="item-data">
{
project.praisesCount > 0 && <span><i className="iconfont icon-liulanliang mr5 font-12" />{project.praisesCount}</span>
}
{
project.forkedCount > 0 && <span><i className="iconfont icon-shoucang mr5 font-12" />{project.forkedCount}</span>
}
<div className="content-bottom-btn">
<Link to={`/${project.fullName}`}><button className="shiny-button">查看详情</button></Link>
</div>
</div>
<div className="content-bottom-btn">
<Link to={`/${project.fullName}`}><button className="shiny-button">查看详情</button></Link>
</div>
</ActionItem>
})
}
</div>
</ActionItem>
})
}
</div>
</InfiniteScroll>
</div>
</div>
)