Merge branch 'factory-hxy' of http://172.20.32.203:3000/durian/forgeplus-react into factory

This commit is contained in:
谢思 2025-12-29 19:31:50 +08:00
commit e0f621dbbc
25 changed files with 475 additions and 154 deletions

View File

@ -1,6 +1,3 @@
import imageBack1 from '../images/factory/back1.png'
import imageBack2 from '../images/factory/back2.png'
import imageBack3 from '../images/factory/back3.png'
export const factoryZoneId = 102
export const dirId = 9

View File

@ -12,9 +12,9 @@ import '../../common.scss'
gsap.registerPlugin(ScrollTrigger);
const resourceInfo = [
{ name: '项目', title: '软件项目', icon: 'icon-xiangmu', desc: '开源共建模块化架构一键集成性能提升50%,安全合规,助力企业快速构建高并发、高可用系统,让开发者专注业务,无需关心底层复杂度。', path: '/softwareFactory/resource/softwareProject' },
{ name: '版本', title: '工厂版本', icon: 'icon-banben', desc: '一键生成版本快照,自动比对差异、回滚热补丁,多环境并行追踪。', path: '/softwareFactory/resource/factoryVersion' },
{ name: '工具', title: '开发工具', icon: 'icon-gongju', desc: '一键生成版本快照,自动比对差异、回滚热补丁,多环境并行追踪,审批流+签名验真,让每次发布可溯源、可复刻、零差错。', path: '/softwareFactory/resource/developmentTools' },
{ name: '项目', title: '软件项目', icon: 'icon-xiangmu', desc: '汇聚用户需求,形成开发任务,设置质量门槛,提升软件质量,快速构建软件版本,高效应对市场变化。', path: '/softwareFactory/resource/softwareProject' },
{ name: '版本', title: '软件工厂基座', icon: 'icon-banben', desc: '按照敏捷开发快速迭代的思路形成解决微小问题的小版本提供长期支持版本LTS版。', path: '/softwareFactory/resource/factoryVersion' },
{ name: '工具', title: '开发工具', icon: 'icon-gongju', desc: '提供开发者所需的各类软件开发工具包括IDE、Git工具、命令行终端工具、数据库设计工具、图像编辑工具等。', path: '/softwareFactory/resource/developmentTools' },
]
const Resource = forwardRef(({ zoneId }, ref) => {
@ -156,12 +156,14 @@ const Resource = forwardRef(({ zoneId }, ref) => {
{
resourceList1 && resourceList1.length > 0 && resourceList1.map((item, index) => {
const { projectProperties } = item;
let imageUrl = projectProperties.authorImageUrl.split(':4000/') && projectProperties.authorImageUrl.split(':4000/')[1]
const authorImageUrl = imageUrl ? `${window.location.origin}/${ imageUrl }` : projectProperties.authorImageUrl
// projectProperties.praisesCount = Math.floor(Math.random(0, 1) * 50)
// projectProperties.forkedCount = Math.floor(Math.random(0, 1) * 50)
const tags = projectProperties.topics && projectProperties.topics.split(',');
return <div key={index} className="content-item1">
<div className="item-head">
<img src={projectProperties.authorImageUrl} alt="" />
<img src={authorImageUrl} alt="" />
<Link to={`/${projectProperties.fullName}`}><div className="item-name">{projectProperties.name.split('/')[1]}</div></Link>
</div>
{

View File

@ -245,6 +245,7 @@
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
h3 {

View File

@ -4,17 +4,17 @@ import lessonsRight1 from '../../../images/factory/lessons-right-1.webp'
import { Link } from 'react-router-dom';
import KFIcon from '../../../components/KFIcon';
import back1 from '../../../images/factory/back1.png'
import back2 from '../../../images/factory/back2.png'
import tagIcon from '../../../images/factory/lessons-tag-icon.webp'
import '../../common.scss'
import { getSourceList } from '../../api'
import { factoryZoneId, manualId, standardId } from '../../../constants/factory'
import { factoryZoneId, manualId, standardId, lessonId } from '../../../constants/factory'
import { downloadFunc } from '../../../forge/Utils/utils'
const Resource2 = forwardRef(({ zoneId }, ref) => {
const [isVisible, setIsVisible] = useState(false);
const [standard, setStandard] = useState({});
const [manualList, setManualList] = useState([]);
const [chapterList, setChapterList] = useState([]);
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
@ -25,7 +25,7 @@ const Resource2 = forwardRef(({ zoneId }, ref) => {
}, { threshold: 0.1 });
getStandard()
getManualList()
getLessonList()
if (ref.current) {
observer.observe(ref.current);
@ -61,6 +61,35 @@ const Resource2 = forwardRef(({ zoneId }, ref) => {
});
}
const getLessonList = () => {
getSourceList({ id: factoryZoneId, keywords: lessonId }).then(res => {
const rows = []
res.data.rows.forEach(e => {
let extendData = {}
if (e.extendData) {
extendData = JSON.parse(e.extendData)
}
rows.push({ ...e, extendDataUrl: extendData.url, extendDataHeadImg: extendData.headImg })
});
// setLesson(rows[0]);
getChapterList(rows[0].name)
});
};
const getChapterList = (id) => {
getSourceList({ id: factoryZoneId, keywords: id }).then(res => {
const rows = []
res.data.rows.forEach(e => {
let extendData = {}
if (e.extendData) {
extendData = JSON.parse(e.extendData)
}
rows.push({ ...e, extendDataUrl: extendData.url, extendDataHeadImg: extendData.headImg })
});
setChapterList(rows);
});
};
return (
<div
className={`factory-resource2 factory-module ${isVisible ? 'animate-active' : ''}`}
@ -72,22 +101,18 @@ const Resource2 = forwardRef(({ zoneId }, ref) => {
<h3>在线课程</h3>
<p>全景智治贯通需求到交付敏捷可视管控风险打造高效可信软件生产新范式赋能研发跃迁</p>
<div className="left-content">
<div className="left-item">
<img src={back1} alt="" />
<div className="item-info">
<h4>Numpy科学计算</h4>
<p>本AI核心通识课课程是一门全面且深入的入门课程旨在为学生提供一个从基础到高级的全方位...</p>
<button className="shiny-button">查看详情</button>
</div>
</div>
<div className="left-item">
<img src={back2} alt="" />
<div className="item-info">
<h4>探索数据类型</h4>
<p>本AI核心通识课课程是一门全面且深入的入门课程旨在为学生提供一个从基础到高级的全方位...</p>
<button className="shiny-button">查看详情</button>
</div>
</div>
{
chapterList.slice(0,2).map((item, index) => {
return <div className="left-item" key={index}>
<img src={ item.extendDataHeadImg || back1} alt="" />
<div className="item-info">
<h4>{ item.name }</h4>
<p>{item.summary}</p>
<button className="shiny-button" onClick={() => { window.open(item.extendDataUrl) }}>查看详情</button>
</div>
</div>
})
}
</div>
</div>

View File

@ -13,23 +13,34 @@ import { factoryZoneId } from '../constants/factory';
import bg1 from '../images/factory/bg-1.mov'
import bg2 from '../images/factory/bg-2.mp4'
import mouse from '../images/factory/mouse.png'
import DelayedScroll from '../forge/Utils/delayScroll'
gsap.registerPlugin(ScrollTrigger);
const bannerInfo = {
title: `软件工厂,开启菌用软件研发新时代`,
desc: '软件开发过程数字化,软件开发活动智能化,软件开发链路敏捷化',
link: '/'
link: '/',
forums: 'forums',
forumsName: '软件工厂'
}
let delayScroll
function Index(props) {
const contentRef = useRef(null);
const bannerRef = useRef(null);
const resourceRef = useRef(null);
const resource2Ref = useRef(null);
const [isVisible, setIsVisible] = useState(false);
const [banner2isVisible, setBanner2IsVisible] = useState(false);
const [firstV, setFirstV] = useState(true)
const isAnimating = useRef(false)
const lastScrollY = useRef(false)
const firstScreenRef = useRef(false)
const ticking = useRef(false)
useEffect(() => {
delayScroll = new DelayedScroll()
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
//
@ -46,8 +57,27 @@ function Index(props) {
observer.observe(contentRef.current);
}
const observer2 = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
setBanner2IsVisible(true);
observer2.disconnect();
}
}, {
threshold: 0.1
});
if (bannerRef.current) {
observer2.observe(bannerRef.current);
}
setupScrollListeners()
return () => {
if (contentRef.current) observer.disconnect();
if (bannerRef.current) observer2.disconnect();
if (delayScroll) delayScroll.cleanup()
window.removeEventListener('scroll', helpScroll)
};
}, []);
@ -77,24 +107,108 @@ function Index(props) {
};
}, [resourceRef, resource2Ref]);
const setupScrollListeners = () => {
window.addEventListener('scroll', helpScroll);
//
// const observer = new IntersectionObserver(
// (entries) => {
// entries.forEach(entry => {
// if (!entry.isIntersecting) {
// // snap
// // this.disableSnap();
// // this.isFirstScreenActive = false;
// }
// });
// },
// {
// threshold: 0.1,
// rootMargin: '0px'
// }
// );
// observer.observe(firstScreenRef.current);
}
const helpScroll = () => {
if (!ticking.current) {
requestAnimationFrame(() => {
handleScroll();
ticking.current = false;
});
ticking.current = true;
}
}
const scrollToNextSection = () => {
if (isAnimating.current) return;
isAnimating.current = true;
const targetScroll = window.innerHeight;
//
window.scrollTo({
top: targetScroll,
behavior: 'smooth'
});
//
setTimeout(() => {
isAnimating.current = false;
}, 800);
}
const scrollToTop = () => {
if (isAnimating.current) return;
isAnimating.current = true;
window.scrollTo({
top: 0,
behavior: 'smooth'
});
setTimeout(() => {
isAnimating.current = false;
}, 800);
}
const handleScroll = () => {
const currentScrollY = window.scrollY;
const firstScreenHeight = window.innerHeight;
// if (this.isFirstScreenActive) {
if (currentScrollY > 50 && currentScrollY < firstScreenHeight && currentScrollY > lastScrollY.current) {
scrollToNextSection();
}
if ( currentScrollY < firstScreenHeight - 50 && currentScrollY < lastScrollY.current) {
scrollToTop()
}
// }
lastScrollY.current = currentScrollY;
}
return (
<div className="factory">
<div className="factory-banner">
<video className={ `factory-banner-video ${ firstV ? 'video-show' : '' }` } src={bg1} autoPlay muted onEnded={() => { setFirstV(false) }}></video>
<video className={ `factory-banner-video ${ !firstV ? 'video-show' : '' }` } src={bg2} autoPlay muted loop></video>
<div className="video-wpapper" ref={firstScreenRef}>
<video className={`factory-banner-video ${firstV ? 'video-show' : ''}`} src={bg1} autoPlay muted onEnded={() => { setFirstV(false) }}></video>
<video className={`factory-banner-video ${!firstV ? 'video-show' : ''}`} src={bg2} autoPlay muted loop></video>
</div>
<div className="banner-content">
<h1>{bannerInfo.title}</h1>
<p>{bannerInfo.desc}</p>
<Link to={`${bannerInfo.link}`}>
<div className="banner-content-button">立即体验</div>
</Link>
<div className="banner-content-more">
<div className="h-wrapper delayed-element"><h1>{bannerInfo.title}</h1></div>
<div className="p-wrapper delayed-element"><p>{bannerInfo.desc}</p></div>
<div className="b-wrapper delayed-element">
<Link to={`${bannerInfo.link}`}>
<div className="banner-content-button">立即体验</div>
</Link>
<Link to={{ pathname: `${bannerInfo.forums}`, state: { name: bannerInfo.forumsName } }}>
<div className="banner-content-button">论坛交流</div>
</Link>
</div>
<div className={`banner-content-more delayed-element ${!banner2isVisible ? '' : 'hide'}`} onClick={() => { scrollToNextSection() }}>
<div>
<img src={ mouse } alt="" ></img>
<img src={mouse} alt="" ></img>
</div>
<span>下滑了解更多</span>
</div>
</div>
<div className="banner-2" ref={bannerRef}></div>
{/* <div className="banner-data">
{
bannerData.map(e => {
@ -108,10 +222,10 @@ function Index(props) {
}
</div> */}
</div>
<div className={`factory-video factory-module ${isVisible ? 'animate-active' : ''}`} ref={contentRef}>
<h1 className="module-title">软件工厂 宣传视频</h1>
{/* <div className={`factory-video factory-module ${isVisible ? 'animate-active' : ''}`} ref={contentRef}>
<h1 className="module-title">软件工厂 精彩呈现</h1>
<video controls autoPlay muted src="http://172.20.32.203:4000/api/attachments/598dbdfe-a767-46d6-aafa-6c3329edccba"></video>
</div>
</div> */}
<News zoneId={factoryZoneId} />
<Feature />

View File

@ -11,6 +11,7 @@
align-items: center;
height: 100vh;
overflow: hidden;
background-color: #000316;
// background-image: url("../images/factory/banner1.png");
// background-size: cover;
@ -18,6 +19,11 @@
// background-repeat: no-repeat;
// background-position: top center;
// /* 可选将图片居中显示 */
.video-wpapper {
width: 100%;
}
&-video {
width: 100%;
display: none;
@ -35,6 +41,12 @@
align-items: center;
height: 40vh;
.h-wrapper {
--delay-factor: 0.45;
transition: transform 0.1s ease-out;
transform: translateY(calc(var(--scroll-delay) * 1px)) !important;
}
h1 {
line-height: normal;
font-family: "DouyinSansBold";
@ -49,6 +61,13 @@
letter-spacing: 10px;
}
.p-wrapper {
--delay-factor: 0.5;
transition: transform 0.1s ease-out;
transform: translateY(calc(var(--scroll-delay) * 1px)) !important;
}
p {
opacity: 0;
color: #ffffffd3;
@ -61,7 +80,36 @@
animation-delay: 2s;
}
.b-wrapper {
--delay-factor: 0.5;
transition: transform 0.2s ease-out !important;
transform: translateY(calc(var(--scroll-delay) * 1px)) !important;
display: flex;
a:nth-child(2) {
div {
background: url('../images/factory/header-button.png');
background-repeat: no-repeat;
background-size: 100% 100%;
margin-right: 0;
&::after {
opacity: 0;
}
&::before {
opacity: 0;
}
}
}
}
a {
margin-top: 50px;
}
&-button {
margin-right: 50px;
opacity: 0;
width: 230px;
height: 64px;
@ -75,11 +123,11 @@
text-align: center;
overflow: hidden;
line-height: 58px;
margin-top: 50px;
animation: fadeInUp 2s ease-in-out forwards;
animation-delay: 2s;
&::before {
z-index: 1;
content: '';
opacity: 0.9;
width: 138px;
@ -93,6 +141,7 @@
}
&::after {
z-index: 1;
content: '';
width: 138px;
position: absolute;
@ -113,12 +162,16 @@
flex-direction: column;
align-items: center;
padding-bottom: 20px;
animation: mouseShow 1s forwards;
animation: mouseShow 1s forwards;
animation-delay: 4s;
--delay-factor: 0.05;
transition: transform 0.3s ease-out;
transform: translateY(calc(var(--scroll-delay) * 1px)) !important;
div {
position: relative;
&::after {
&::after {
content: "";
width: 3px;
height: 8px;
@ -126,7 +179,7 @@
position: absolute;
top: 8px;
left: 50%;
transform: translate(-50%,0);
transform: translate(-50%, 0);
animation: mouse 1.5s ease-out infinite;
}
}
@ -145,6 +198,18 @@
margin-top: 10px;
}
}
}
.banner-2 {
height: 100vh;
width: 100%;
position: absolute;
top: 100vh;
}
.hide {
opacity: 0 !important;
}
.banner-data {
@ -298,6 +363,7 @@
50% {
top: 8px
}
to {
top: 16px;
}

View File

@ -21,7 +21,7 @@ import { message } from 'antd';
const backList = [back1, back2, back3]
function Index(props) {
const [nowTop, setNowTop] = useState({})
const [nowTop, setNowTop] = useState(0)
const [isVisible, setIsVisible] = useState(false);
const carouselEL = useRef(null);
@ -96,7 +96,7 @@ function Index(props) {
return (
<div className={`lessons ${isVisible ? 'animate-active' : ''}`}>
<img src={ chapterBg } className="lessons-bg" alt="" />
<img src={chapterBg} className="lessons-bg" alt="" />
<div className="lessons-header">
<h2><span>学习专区</span>多样资源服务工厂应用</h2>
<p >从核心课程到规范标准构建持续进化的工程能力体系</p>
@ -106,33 +106,35 @@ function Index(props) {
</div>
<div className="lesson-body">
<div className="lesson-left">
<h3>{ lesson.name }</h3>
<p>{ lesson.summary }</p>
<div className="chapter-list">
{
chapterList.map((item, index) => {
return <div className="chapter-item" key={item.id}>
{
index < 10 ?
<span className="chapter-index">0{index + 1}</span> :
<span className="chapter-index">{index + 1}</span>
}
<div title={ item.name }>
<h3>{lesson.name}</h3>
<p>{lesson.summary}</p>
<div className="chapter-wrapper">
<div className="chapter-list" style={{ marginTop: `-${ 88 * ( chapterList.length - nowTop < 4 ? chapterList.length - 4 : nowTop) }px` }}>
{
chapterList.map((item, index) => {
return <div className="chapter-item" key={item.id}>
{
item.name
index < 10 ?
<span className="chapter-index">0{index + 1}</span> :
<span className="chapter-index">{index + 1}</span>
}
</div>
<div title={item.name}>
{
item.name
}
</div>
</div>
})
}
</div>
})
}
</div>
</div>
</div>
<div className="lesson-right">
<Carousel
autoplay
ref={carouselEL}
afterChange={e => { setNowTop(chapterList[e]) }}
afterChange={e => { setNowTop(e)}}
dots={false}
>
{chapterList.map((item, index) => {
@ -220,7 +222,7 @@ function Index(props) {
chapterList.slice(3).map((item, index) => {
return <div className="chapter-chapter2-item" key={item.id}>
<div className="chapter-head">
<h4 title={ item.name } onClick={() => { window.open(item.extendDataUrl) }}>{item.name}</h4>
<h4 title={item.name} onClick={() => { window.open(item.extendDataUrl) }}>{item.name}</h4>
<div className="line-1"></div>
<div className="line-2"></div>
<div className="line-3"></div>
@ -267,10 +269,10 @@ function Index(props) {
{
manualList.slice(0, 3).map((item, index) => {
const nameList = item.name.split('软件工厂')
return <div className={ `manual-${ index + 1 }` }>
<img src={ index === 0 ? manual1 : manual2} alt="" />
{ index === 0 ? <div>{item.name}</div> : <div><span>软件工厂</span><div></div>{ nameList[1] }</div> }
<p>{ item.summary }</p>
return <div className={`manual-${index + 1}`}>
<img src={index === 0 ? manual1 : manual2} alt="" />
{index === 0 ? <div>{item.name}</div> : <div><span>软件工厂</span><div></div>{nameList[1]}</div>}
<p>{item.summary}</p>
<button onClick={() => { downloadFunc(`${httpUrl}/file/open/download/${item.fileIds}`) }} >下载</button>
</div>
})

View File

@ -114,57 +114,58 @@
position: relative;
}
.chapter-list {
margin-left: 36px;
// height: 360px;
// overflow-y: scroll;
// overflow-x: hidden;
.chapter-wrapper {
height: 387px;
overflow: hidden;
.chapter-list {
transition: all .4s cubic-bezier(.25, .46, .45, .94);
margin-left: 36px;
.chapter-item {
height: 88px;
width: 100%;
line-height: 88px;
font-family: alibaba;
font-weight: 500;
color: #091221;
font-size: 22px;
.chapter-item {
height: 88px;
width: 100%;
line-height: 88px;
font-family: alibaba;
font-weight: 500;
color: #091221;
font-size: 22px;
border-top: 2px solid rgba(9, 18, 33, 0.04);
position: relative;
display: flex;
border-top: 2px solid rgba(9, 18, 33, 0.04);
position: relative;
display: flex;
div {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding-right: 30px;
div {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding-right: 30px;
}
}
&::before {
content: url('../../images/factory/lessons/triangle.webp');
width: 5px;
top: -45px;
right: 0;
display: inline-block;
position: absolute;
}
&:nth-last-child(1) {
border-bottom: 2px solid rgba(9, 18, 33, 0.04);
&::after {
&::before {
content: url('../../images/factory/lessons/triangle.webp');
width: 5px;
top: 41px;
top: -45px;
right: 0;
display: inline-block;
position: absolute;
}
}
.chapter-index {
margin-right: 21px;
&:nth-last-child(1) {
border-bottom: 2px solid rgba(9, 18, 33, 0.04);
&::after {
content: url('../../images/factory/lessons/triangle.webp');
width: 5px;
top: 41px;
right: 0;
display: inline-block;
position: absolute;
}
}
.chapter-index {
margin-right: 21px;
}
}
}
}
@ -232,7 +233,7 @@
width: 348px;
height: 343.59px;
border-radius: 16px;
object-fit: cover;
object-fit: contain;
}
&:nth-child(1) {

View File

@ -76,12 +76,12 @@ function Index({searchValue, searchType},ref) {
{
projectList.map((item, index) => {
const project = item.projectProperties || {}
project.praisesCount = 3
project.forkedCount = 3
let imageUrl = project.authorImageUrl.split(':4000/') && project.authorImageUrl.split(':4000/')[1]
const authorImageUrl = imageUrl ? `${window.location.origin}/${ imageUrl }` : project.authorImageUrl
return <ActionItem className="content-item-wrapper" key={index} padding="1px" borderRadius="23px">
<div className="content-project-item">
<div className="item-top">
<img src={project.authorImageUrl} alt="" className="item-img"></img>
<img src={authorImageUrl} alt="" className="item-img"></img>
<h5 className="item-title">{project.name}</h5>
</div>
<div className="item-desc">
@ -98,13 +98,8 @@ function Index({searchValue, searchType},ref) {
</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>
}
<span><i className="iconfont icon-guanzhu mr5 font-12" />{project.forkedCount}</span>
<span><i className="iconfont icon-dianzan mr5 font-12" />{project.praisesCount}</span>
</div>
</div>
<div className="content-project-btn">
@ -155,7 +150,7 @@ function Index({searchValue, searchType},ref) {
{
versionList.length > 0 && <div className="search-version">
<div className="search-header">
<h3>工厂版本</h3>
<h3>软件工厂基座</h3>
<div><span>#</span>版本 <span>{versionTotal}</span></div>
</div>
<div className="search-content">

View File

@ -85,7 +85,7 @@ function Index(props) {
<span>工具类型</span>
</div>
</div>
<button className="shiny-button" onClick={() => { downloadFunc(`${httpUrl}/file/open/download/${item.fileIds}`) }}>立即下载</button>
<button className="shiny-button" onClick={() => { downloadFunc(`${httpUrl}/file/open/download/${detail.fileIds}`) }}>立即下载</button>
<h6 className="more-title">更多热门工具</h6>
<div className="more-tools">
{

View File

@ -31,7 +31,7 @@ const DevelopmentTools = Loadable({
const zoneId = 199
const typeMap = [
{ name: '工厂版本', key: 'factoryVersion', icon: 'icon-gongchangbanben' },
{ name: '软件工厂基座', key: 'factoryVersion', icon: 'icon-gongchangbanben' },
{ name: '软件项目', key:'softwareProject', icon: 'icon-ruanjianxiangmu' },
{ name: '开发工具', key: 'developmentTools', icon: 'icon-kaifagongju' },
]

View File

@ -20,6 +20,8 @@
position: absolute;
width: 1600px;
height: 410px;
z-index: 1;
pointer-events: none;
div:nth-child(1) {
top: 70px;

View File

@ -6,7 +6,7 @@ import KFIcon from '../../../components/KFIcon';
import bg1 from '../../../images/factory/resource/project-top-1.webp';
import bg2 from '../../../images/factory/resource/project-top-2.webp';
import bg3 from '../../../images/factory/resource/project-top-3.png';
import bg3 from '../../../images/factory/resource/project-top-3.webp';
import bg4 from '../../../images/factory/resource/project-content-1.webp';
import bg5 from '../../../images/factory/resource/project-content-2.webp';
@ -74,12 +74,14 @@ function Index(props) {
{
topList.map((item, index) => {
const project = item.projectProperties
let imageUrl = project.authorImageUrl.split(':4000/') && project.authorImageUrl.split(':4000/')[1]
const authorImageUrl = imageUrl ? `${window.location.origin}/${imageUrl}` : project.authorImageUrl
// project.praisesCount = Math.floor(Math.random(0, 1) * 50)
// project.forkedCount = Math.floor(Math.random(0, 1) * 50)
return (
<Link to={`/${project.fullName}`}><div className="project-top-item" key={index}>
<img src={bgList[index]} alt="" className="item-bg" />
<img src={project.authorImageUrl} alt="" className="item-img"></img>
<img src={authorImageUrl} alt="" className="item-img"></img>
<div className="item-info">
<h5 className="item-title">{project.name.split('/')[1]}</h5>
<p className="item-desc">{project.description}</p>
@ -92,8 +94,8 @@ function Index(props) {
)
})
} */}
<img src={ user1 } alt="" />
<span>捐赠者</span> <div></div>{ project.name.split('/')[0] }
<img src={user1} alt="" />
<span>捐赠者</span> <div></div>{project.name.split('/')[0]}
</div>
<div className="item-bottom-data">
<span><i className="iconfont icon-guanzhu mr5 font-12" />{project.forkedCount}</span>
@ -117,12 +119,14 @@ function Index(props) {
{
contentList.map((item, index) => {
const project = item.projectProperties
let imageUrl = project.authorImageUrl.split(':4000/') && project.authorImageUrl.split(':4000/')[1]
const authorImageUrl = imageUrl ? `${window.location.origin}/${imageUrl}` : project.authorImageUrl
// project.praisesCount = Math.floor(Math.random(0, 1) * 50)
// project.forkedCount = Math.floor(Math.random(0, 1) * 50)
return <Link to={`/${project.fullName}`} className="content-top-item"><div key={index}>
<img className="item-bg" src={contentBgList[index]} alt="" />
<div className="item-top">
<img src={project.authorImageUrl} alt="" className="item-img"></img>
<img src={authorImageUrl} alt="" className="item-img"></img>
<h5 className="item-title">{project.name.split('/')[1]}</h5>
</div>
<div className="item-tags">
@ -133,8 +137,8 @@ function Index(props) {
)
})
} */}
<img src={ user2 } alt="" />
<span>捐赠者</span> <div></div>{ project.name.split('/')[0] }
<img src={user2} alt="" />
<span>捐赠者</span> <div></div>{project.name.split('/')[0]}
</div>
<div className="item-desc">
{project.description}
@ -161,6 +165,8 @@ function Index(props) {
{
fullList.map((item, index) => {
const project = item.projectProperties
let imageUrl = project.authorImageUrl.split(':4000/') && project.authorImageUrl.split(':4000/')[1]
const authorImageUrl = imageUrl ? `${window.location.origin}/${imageUrl}` : project.authorImageUrl
// project.praisesCount = Math.floor(Math.random(0, 1) * 50)
// project.forkedCount = Math.floor(Math.random(0, 1) * 50)
return <ActionItem className="content-bottom-wrapper" key={index} backgroundColor="rgba(19, 53, 91, 0.11)" padding="1px" borderRadius="16px">
@ -173,19 +179,21 @@ function Index(props) {
<img src={project.authorImageUrl} alt="" className="item-img"></img>
<h5 className="item-title">{project.name.split('/')[1]}</h5>
</div>
<div className="item-desc">
{!project.description ? '暂无简介' : project.description}
</div>
<div className="item-tags">
{
<div className="item-bottom-tags">
{/* {
project.topics && project.topics.split(',').splice(0, 3).map((topic, index) => {
return (
<span key={index} className="item-tag">{topic}</span>
)
})
}
} */}
<img src={user1} alt="" />
<span>捐赠者</span> <div></div>{project.name.split('/')[0]}
</div>
<div className="item-desc">
{!project.description ? '暂无简介' : project.description}
</div>
<div className="item-data">
<span><i className="iconfont icon-guanzhu mr5 font-12" />{project.forkedCount}</span>
<span><i className="iconfont icon-dianzan mr5 font-12" />{project.praisesCount}</span>

View File

@ -317,10 +317,10 @@
.item-bg {
position: absolute;
width: 420px;
height: 219px;
left: 0;
top: 0;
width: 416px;
height: 215px;
left: 2px;
top: 2px;
z-index: -1;
}
@ -447,27 +447,59 @@
.item-desc {
-webkit-line-clamp: 2;
margin-top: 26px;
margin-top: 16px;
}
.item-tags {
margin-top: auto;
.item-bottom-tags {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 20px;
width: fit-content;
background: rgba(179, 190, 252, 0.18);
border: 1px solid;
border-color: #ffffff;
border-radius: 4px;
height: 32px;
padding: 5px 9px;
line-height: 22px;
font-family: alibabareg;
color: #091221;
font-size: 13px;
.item-tag {
display: inline-block;
padding: 0 8px;
margin-right: 8px;
height: 32px;
background: rgba(22, 76, 228, 0.05);
border-radius: 4px;
line-height: 32px;
font-family: alibabareg;
color: #091221;
font-size: 14px;
img {
width: 15px;
margin-right: 5px;
height: 15px;
}
div {
width: 6px;
height: 6px;
background: #ffffff;
border-radius: 50%;
margin: 0 6px;
}
}
// .item-tags {
// margin-top: auto;
// display: flex;
// justify-content: flex-start;
// .item-tag {
// display: inline-block;
// padding: 0 8px;
// margin-right: 8px;
// height: 32px;
// background: rgba(22, 76, 228, 0.05);
// border-radius: 4px;
// line-height: 32px;
// font-family: alibabareg;
// color: #091221;
// font-size: 14px;
// }
// }
}
.content-bottom-btn {

View File

@ -0,0 +1,76 @@
/**
* 延迟滚动
* 延迟滚动元素classdelayed-element
* 延迟滚动元素css属性(用于设定延迟多少)--scroll-delay
*/
class DelayedScroll {
constructor() {
this.initialized = false;
this.maxScroll = window.innerHeight; // 最多监听2屏的高度
this.init();
}
init() {
if (this.initialized) return;
let ticking = false;
const handleScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
const scrollY = window.scrollY;
// 只在首屏范围内应用效果
if (scrollY <= this.maxScroll) {
this.updateElements(scrollY);
} else {
// 超出范围时停止监听
window.removeEventListener('scroll', scrollHandler);
this.cleanup();
}
ticking = false;
});
ticking = true;
}
};
// 使用节流的滚动监听
const scrollHandler = () => {
if (window.scrollY <= this.maxScroll) {
handleScroll();
}
};
window.addEventListener('scroll', scrollHandler, { passive: true });
// 初始执行一次
this.updateElements(window.scrollY);
this.initialized = true;
}
updateElements(scrollY) {
document.querySelectorAll('.delayed-element').forEach(element => {
const delayFactor = this.getDelayFactor(element);
const delay = scrollY * delayFactor;
element.style.setProperty('--scroll-delay', delay);
});
}
getDelayFactor(element) {
return parseFloat(
getComputedStyle(element).getPropertyValue('--delay-factor') || 0.2
);
}
cleanup() {
// 可选移除transform恢复原状
document.querySelectorAll('.delayed-element').forEach(element => {
element.style.removeProperty('--scroll-delay');
element.style.removeProperty('transform');
});
}
}
export default DelayedScroll

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB