Merge branch 'factory' of https://gitlink.org.cn/Gitlink/forgeplus-react-v2 into factory
This commit is contained in:
commit
b1635ba336
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import './index.scss';
|
||||
import featureLeft1 from '../../../images/factory/feature-left-1.webp';
|
||||
import featureLeft1 from '../../../images/factory/feature-left-1.png';
|
||||
import featureLeft2 from '../../../images/factory/feature-left-2.jpg';
|
||||
import featureright1 from '../../../images/factory/feature-right-1.webp';
|
||||
import featureright2 from '../../../images/factory/feature-right-2.jpg';
|
||||
|
|
@ -53,6 +53,7 @@ function Feature(props) {
|
|||
const getStandard = () => {
|
||||
getSourceList({ id: factoryZoneId, keywords: standardId }).then(res => {
|
||||
const rows = []
|
||||
if (!res || !res.data || !res.data.rows) return
|
||||
res.data.rows.forEach(e => {
|
||||
let extendData = {}
|
||||
if (e.extendData) {
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@
|
|||
background-image: url('../../../images/factory/feature-left-bg2.webp');
|
||||
img {
|
||||
width: 620px;
|
||||
margin-top: 32px;
|
||||
// margin-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import React, { useEffect, useRef, useState, forwardRef } from 'react';
|
||||
import React, { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
|
||||
import './index.scss';
|
||||
import gsap from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import { ScrollToPlugin } from 'gsap/ScrollToPlugin';
|
||||
import KFIcon from '../../../components/KFIcon';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getProjectsLists, getSourceList, httpUrl } from '../../api';
|
||||
|
|
@ -10,7 +11,7 @@ import { downloadFunc } from '../../../forge/Utils/utils'
|
|||
import '../../common.scss'
|
||||
import { Modal } from 'antd';
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);
|
||||
|
||||
const resourceInfo = [
|
||||
{ name: '项目', title: '工厂开源项目', icon: 'icon-xiangmu', desc: '基于标准化生产线开发的项目源码、依赖包源码、标准化生产线源码等', path: '/sf/resources/project' },
|
||||
|
|
@ -26,6 +27,8 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
const r4Ref = useRef(null);
|
||||
const containerRef = useRef(null);
|
||||
const timelineRef = useRef(null);
|
||||
const scrollTriggerRef = useRef(null);
|
||||
const wrapperRef = useRef(null); // 用于 IntersectionObserver 的内部 ref
|
||||
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [resourceList1, setResourceList1] = useState([]);
|
||||
|
|
@ -34,6 +37,14 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
const [resourceList4, setResourceList4] = useState([]);
|
||||
const [manualList, setManualList] = useState([]);
|
||||
|
||||
// 调试参数:调整每个面板的滚动偏移量(单位:像素)
|
||||
const scrollOffsetConfig = useRef({
|
||||
0: 0, // resource-1 偏移量
|
||||
1: 0, // resource-2 偏移量
|
||||
2: 0, // resource-3 偏移量
|
||||
3: 300, // resource-4 偏移量
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!zoneId) return;
|
||||
getToolsList();
|
||||
|
|
@ -55,61 +66,152 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
}
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
if (ref.current) {
|
||||
observer.observe(ref.current);
|
||||
if (wrapperRef.current) {
|
||||
observer.observe(wrapperRef.current);
|
||||
}
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
// 初始化GSAP动画
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
// 获取面板元素
|
||||
const panels = [r1Ref.current, r2Ref.current, r3Ref.current, r4Ref.current];
|
||||
|
||||
// 创建时间轴动画
|
||||
timelineRef.current = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
trigger: containerRef.current,
|
||||
start: "top top",
|
||||
end: "+=800",
|
||||
scrub: 1,
|
||||
pin: false,
|
||||
anticipatePin: 1,
|
||||
markers: false,
|
||||
// 检查元素是否在 DOM 中并且有正确的 offsetParent
|
||||
const checkAndInit = () => {
|
||||
const container = containerRef.current;
|
||||
const panels = [r1Ref.current, r2Ref.current, r3Ref.current, r4Ref.current];
|
||||
|
||||
// 确保所有元素都存在且在 DOM 中有位置信息
|
||||
if (!container || !container.offsetParent || panels.some(p => !p || !p.offsetParent)) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// 为每个面板创建动画并添加回调
|
||||
panels.forEach((panel, index) => {
|
||||
const animations = [
|
||||
{ y: "-5%", scale: 0.85, duration: 1 },
|
||||
{ y: "-50%", scale: 0.9, duration: 1 },
|
||||
{ y: "-265%", scale: 0.95, duration: 1.2 },
|
||||
{ y: "-415%", scale: 0.98, duration: 2 }
|
||||
];
|
||||
try {
|
||||
// 创建时间轴动画
|
||||
timelineRef.current = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
trigger: container,
|
||||
start: "top top",
|
||||
end: "+=800",
|
||||
scrub: 1,
|
||||
pin: false,
|
||||
anticipatePin: 1,
|
||||
markers: false,
|
||||
onUpdate: (self) => {
|
||||
scrollTriggerRef.current = self;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
timelineRef.current.to(panel, {
|
||||
...animations[index],
|
||||
onComplete: () => {
|
||||
panel.classList.add('animated-panel');
|
||||
},
|
||||
onReverseComplete: () => {
|
||||
panel.classList.remove('animated-panel');
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
// 为每个面板创建动画
|
||||
const animations = [
|
||||
{ y: "-5%", scale: 0.85, duration: 1 },
|
||||
{ y: "-50%", scale: 0.9, duration: 1 },
|
||||
{ y: "-265%", scale: 0.95, duration: 1.2 },
|
||||
{ y: "-415%", scale: 0.98, duration: 2 }
|
||||
];
|
||||
|
||||
panels.forEach((panel, index) => {
|
||||
if (panel && animations[index]) {
|
||||
timelineRef.current.to(panel, {
|
||||
...animations[index],
|
||||
onComplete: () => {
|
||||
panel.classList.add('animated-panel');
|
||||
},
|
||||
onReverseComplete: () => {
|
||||
panel.classList.remove('animated-panel');
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('GSAP animation init error:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 尝试初始化,如果失败则每 100ms 重试一次
|
||||
let attempts = 0;
|
||||
const maxAttempts = 100; // 最多等待 10 秒
|
||||
|
||||
const tryInit = () => {
|
||||
if (checkAndInit()) {
|
||||
console.log('Resource GSAP animation initialized');
|
||||
} else if (attempts < maxAttempts) {
|
||||
attempts++;
|
||||
setTimeout(tryInit, 100);
|
||||
} else {
|
||||
console.error('Failed to initialize Resource GSAP animation');
|
||||
}
|
||||
};
|
||||
|
||||
// 立即开始尝试
|
||||
tryInit();
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
if (timelineRef.current) {
|
||||
timelineRef.current.kill();
|
||||
}
|
||||
ScrollTrigger.getAll().forEach(trigger => trigger.kill());
|
||||
// 只清理当前组件创建的 ScrollTrigger
|
||||
ScrollTrigger.getAll().forEach(trigger => {
|
||||
if (trigger.trigger === containerRef.current) {
|
||||
trigger.kill();
|
||||
}
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 点击面板跳转到对应滚动位置
|
||||
// progressValues: 每个面板对应的动画进度值 (0-1)
|
||||
const progressValues = [0, 0.25, 0.5, 0.75];
|
||||
|
||||
const handlePanelClick = (index, event) => {
|
||||
// 如果点击的是子元素(按钮、链接、内容项等),不触发面板滚动
|
||||
if (event) {
|
||||
const target = event.target;
|
||||
// 检查是否点击了按钮、链接、内容项或它们的子元素
|
||||
if (target.closest('button') ||
|
||||
target.closest('a') ||
|
||||
target.closest('.item-btn') ||
|
||||
target.closest('.content-item1') ||
|
||||
target.closest('.content-item2') ||
|
||||
target.closest('.content-item3')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!scrollTriggerRef.current) return;
|
||||
|
||||
const st = scrollTriggerRef.current;
|
||||
const targetProgress = progressValues[index];
|
||||
|
||||
// 计算目标滚动位置
|
||||
// start 是动画开始的滚动位置,end 是动画结束的滚动位置
|
||||
const start = st.start;
|
||||
const end = st.end;
|
||||
const targetScroll = start + (end - start) * targetProgress + scrollOffsetConfig.current[index];
|
||||
|
||||
// 平滑滚动到目标位置
|
||||
gsap.to(window, {
|
||||
scrollTo: {
|
||||
y: targetScroll,
|
||||
autoKill: false
|
||||
},
|
||||
duration: 0.8,
|
||||
ease: "power2.inOut"
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露方法给父组件(可选)
|
||||
useImperativeHandle(ref, () => ({
|
||||
scrollToPanel: handlePanelClick,
|
||||
// 获取/设置调试偏移量
|
||||
getScrollOffset: (index) => scrollOffsetConfig.current[index],
|
||||
setScrollOffset: (index, value) => { scrollOffsetConfig.current[index] = value; },
|
||||
getAllOffsets: () => ({ ...scrollOffsetConfig.current }),
|
||||
}));
|
||||
|
||||
const getLeftContent = (index) => {
|
||||
const item = resourceInfo[index];
|
||||
return <div className="content-left">
|
||||
|
|
@ -126,6 +228,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
|
||||
const getVersionList = () => {
|
||||
getSourceList({ id: factoryZoneId, keywords: versionId }).then(res => {
|
||||
if (!res || !res.data || !res.data.rows) return
|
||||
setResourceList2(res.data.rows.slice(0, 1));
|
||||
});
|
||||
}
|
||||
|
|
@ -133,6 +236,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
const getToolsList = () => {
|
||||
getSourceList({ id: factoryZoneId, keywords: toolId }).then(res => {
|
||||
const rows = []
|
||||
if (!res || !res.data || !res.data.rows) return
|
||||
res.data.rows.forEach(e => {
|
||||
let extendData = {}
|
||||
if (e.extendData) {
|
||||
|
|
@ -148,6 +252,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
const getCommonToolsList = () => {
|
||||
getSourceList({ id: factoryZoneId, keywords: commontoolId }).then(res => {
|
||||
const rows = []
|
||||
if (!res || !res.data || !res.data.rows) return
|
||||
res.data.rows.forEach(e => {
|
||||
let extendData = {}
|
||||
if (e.extendData) {
|
||||
|
|
@ -168,6 +273,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
const getManualList = () => {
|
||||
getSourceList({ id: factoryZoneId, keywords: manualId }).then(res => {
|
||||
const rows = []
|
||||
if (!res || !res.data || !res.data.rows) return
|
||||
res.data.rows.forEach(e => {
|
||||
let extendData = {}
|
||||
if (e.extendData) {
|
||||
|
|
@ -182,14 +288,22 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
return (
|
||||
<div
|
||||
className={`factory-resource factory-module ${isVisible ? 'animate-active' : ''}`}
|
||||
ref={ref}
|
||||
ref={(node) => {
|
||||
// 合并内部 ref 和外部 forwarded ref
|
||||
wrapperRef.current = node;
|
||||
if (typeof ref === 'function') {
|
||||
ref(node);
|
||||
} else if (ref) {
|
||||
ref.current = node;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="resource-container" ref={containerRef}>
|
||||
<h1 className="module-title">共建共用共享,为软件工厂赋能</h1>
|
||||
|
||||
<div className="panels-stack">
|
||||
|
||||
<div ref={r1Ref} className="resource-1">
|
||||
<div ref={r1Ref} className="resource-1" onClick={(e) => handlePanelClick(0, e)} style={{ cursor: 'pointer' }}>
|
||||
<div className="panel-content panel-content-1">
|
||||
{
|
||||
getLeftContent(1)
|
||||
|
|
@ -230,7 +344,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div ref={r2Ref} className="resource-2">
|
||||
<div ref={r2Ref} className="resource-2" onClick={(e) => handlePanelClick(1, e)} style={{ cursor: 'pointer' }}>
|
||||
<div className="panel-content panel-content-2">
|
||||
{
|
||||
getLeftContent(0)
|
||||
|
|
@ -270,7 +384,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div ref={r3Ref} className="resource-3">
|
||||
<div ref={r3Ref} className="resource-3" onClick={(e) => handlePanelClick(2, e)} style={{ cursor: 'pointer' }}>
|
||||
<div className="panel-content panel-content-3">
|
||||
{
|
||||
getLeftContent(2)
|
||||
|
|
@ -302,7 +416,7 @@ const Resource = forwardRef(({ zoneId }, ref) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref={r4Ref} className="resource-4">
|
||||
<div ref={r4Ref} className="resource-4" onClick={(e) => handlePanelClick(3, e)} style={{ cursor: 'pointer' }}>
|
||||
<div className="panel-content panel-content-4">
|
||||
{
|
||||
getLeftContent(3)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 15px;
|
||||
cursor: default;
|
||||
|
||||
.item-head {
|
||||
display: flex;
|
||||
|
|
@ -162,6 +163,7 @@
|
|||
align-items: center;
|
||||
padding: 30px 35px;
|
||||
margin-right: 18px;
|
||||
cursor: default;
|
||||
|
||||
.item-name {
|
||||
max-width: 210px;
|
||||
|
|
@ -198,6 +200,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 15px;
|
||||
cursor: default;
|
||||
|
||||
.item-desc {
|
||||
margin-top: 27px;
|
||||
|
|
@ -243,6 +246,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 15px;
|
||||
cursor: default;
|
||||
|
||||
.item-desc {
|
||||
margin-top: 27px;
|
||||
|
|
|
|||
|
|
@ -100,26 +100,60 @@ function Index(props) {
|
|||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const resource = resourceRef.current;
|
||||
const resource2 = resource2Ref.current;
|
||||
if (!resource || !resource2) return;
|
||||
const cleanup = [];
|
||||
|
||||
const pinContainer = ScrollTrigger.create({
|
||||
trigger: resource,
|
||||
start: "top top",
|
||||
endTrigger: ".factory-resource .panels-stack", // 使用Resource内部的堆叠区域
|
||||
end: "bottom top", // 当堆叠区域滚动到顶部时结束固定
|
||||
pin: resource,
|
||||
pinSpacing: false,
|
||||
scrub: true
|
||||
});
|
||||
cleanup.push(() => pinContainer.kill());
|
||||
|
||||
// 检查元素是否存在的函数
|
||||
const checkAndInit = () => {
|
||||
// 通过选择器获取 DOM 元素(因为 useImperativeHandle 使 ref 不再是 DOM 元素)
|
||||
const resourceEl = document.querySelector('.factory-resource');
|
||||
const endTriggerEl = document.querySelector('.factory-resource .panels-stack');
|
||||
|
||||
if (!resourceEl || !resourceEl.offsetParent || !endTriggerEl || !endTriggerEl.offsetParent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const pinContainer = ScrollTrigger.create({
|
||||
trigger: resourceEl,
|
||||
start: "top top",
|
||||
endTrigger: endTriggerEl,
|
||||
end: "bottom top",
|
||||
pin: resourceEl,
|
||||
pinSpacing: false,
|
||||
scrub: true
|
||||
});
|
||||
cleanup.push(() => pinContainer.kill());
|
||||
console.log('pinContainer initialized');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error creating pinContainer ScrollTrigger:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 立即尝试初始化,如果失败则重试
|
||||
let attempts = 0;
|
||||
const maxAttempts = 50;
|
||||
|
||||
const tryInit = () => {
|
||||
if (checkAndInit()) {
|
||||
// 成功
|
||||
} else if (attempts < maxAttempts) {
|
||||
attempts++;
|
||||
setTimeout(tryInit, 100);
|
||||
} else {
|
||||
console.warn('Failed to initialize pinContainer after max attempts');
|
||||
}
|
||||
};
|
||||
|
||||
// 稍微延迟启动,让 Resource 组件先开始初始化
|
||||
const timeoutId = setTimeout(tryInit, 200);
|
||||
cleanup.push(() => clearTimeout(timeoutId));
|
||||
|
||||
return () => {
|
||||
cleanup.forEach(fn => fn());
|
||||
};
|
||||
}, [resourceRef, resource2Ref]);
|
||||
}, []);
|
||||
|
||||
const setupScrollListeners = () => {
|
||||
window.addEventListener('scroll', helpScroll);
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 116 KiB |
Loading…
Reference in New Issue