优化赛程

This commit is contained in:
黄廿九 2026-03-20 00:51:24 +08:00
parent cc1f290f94
commit b165424eed
5 changed files with 30426 additions and 21238 deletions

44205
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,7 @@
"echarts-for-react": "2.0.16",
"echarts-wordcloud": "1.1.3",
"flv.js": "1.5.0",
"gsap": "^3.14.2",
"helmet": "3.23.3",
"hls.js": "^1.1.1",
"html2pdf.js": "^0.10.1",

View File

@ -1,6 +1,9 @@
import SpeTitle from '../components/Title'
import styles from './index.less'
import React, { useState, useRef, useEffect } from 'react'
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { ScrollToPlugin } from 'gsap/ScrollToPlugin'
import icon from '../img/schedule/icon.webp'
import right from '../img/schedule/right.webp'
import { timelineDir, ruleList } from '../info'
@ -8,6 +11,8 @@ import { getDocDetail, getDocList } from '../api'
import { history } from 'umi'
import { Base64 } from 'js-base64'
gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);
function Schedule(props) {
const [selectedRule, setSelectedRule] = useState(ruleList[1].id)
@ -23,10 +28,13 @@ function Schedule(props) {
const scheduleRefs = useRef({})
const periodRefs = useRef({})
const periodListRef = useRef(null)
const autoPlayTimerRef = useRef(null)
const progressTimerRef = useRef(null)
const startTimeRef = useRef(null)
const elapsedTimeRef = useRef(0)
const scheduleDetailRef = useRef(null)
const scheduleLeftRef = useRef(null)
const scheduleListRef = useRef(null)
const scrollTriggerRef = useRef(null)
useEffect(() => {
const ruleInfo = {}
@ -52,35 +60,34 @@ function Schedule(props) {
const sortedRows = res.rows.reverse();
setPeriodList(sortedRows);
// getDocDetail
const scheduleData = [];
const periodMap = new Map(sortedRows.map((period, index) => [period.id, { ...period, order: index }]));
const scheduleDataMap = new Map();
sortedRows.forEach(period => {
scheduleDataMap.set(period.id, []);
});
let processedCount = 0;
sortedRows.forEach((period, periodIndex) => {
sortedRows.forEach(period => {
getDocDetail(period.id).then(detailRes => {
if (detailRes.data && detailRes.data.content) {
// Base64content
const decodedContent = Base64.decode(detailRes.data.content);
// 使DOMParserHTML
const parser = new DOMParser();
const doc = parser.parseFromString(decodedContent, 'text/html');
const paragraphs = doc.querySelectorAll('p');
let scheduleId = scheduleData.length + 1;
let scheduleId = 1;
paragraphs.forEach((p) => {
const text = p.textContent.trim();
if (text) {
//
const parts = text.split(/\s+/);
if (parts.length > 0) {
const date = parts[0];
const content = parts.slice(1).join(' ');
//
scheduleData.push({
id: scheduleId++,
scheduleDataMap.get(period.id).push({
periodId: period.id,
date: date,
content: content,
@ -93,10 +100,21 @@ function Schedule(props) {
processedCount++;
// scheduleList
if (processedCount === sortedRows.length) {
// scheduleList
setScheduleList(scheduleData);
const finalScheduleData = [];
sortedRows.forEach(period => {
const periodSchedules = scheduleDataMap.get(period.id);
if (periodSchedules && periodSchedules.length > 0) {
periodSchedules.forEach(schedule => {
finalScheduleData.push({
...schedule,
globalId: finalScheduleData.length + 1
});
});
}
});
setScheduleList(finalScheduleData);
}
}).catch(err => {
@ -104,7 +122,20 @@ function Schedule(props) {
processedCount++;
if (processedCount === sortedRows.length) {
setScheduleList(scheduleData);
const finalScheduleData = [];
sortedRows.forEach(period => {
const periodSchedules = scheduleDataMap.get(period.id);
if (periodSchedules && periodSchedules.length > 0) {
periodSchedules.forEach(schedule => {
finalScheduleData.push({
...schedule,
globalId: finalScheduleData.length + 1
});
});
}
});
setScheduleList(finalScheduleData);
}
});
});
@ -165,6 +196,86 @@ function Schedule(props) {
};
}, [ruleList, isHovered]);
useEffect(() => {
if (!scheduleLeftRef.current) return;
// 使 GSAP ScrollTrigger pin
scrollTriggerRef.current = ScrollTrigger.create({
trigger: scheduleLeftRef.current,
start: "top top",
end: "bottom top",
pin: true,
pinSpacing: false,
anticipatePin: 1
});
return () => {
if (scrollTriggerRef.current) {
scrollTriggerRef.current.kill();
}
};
}, []);
// 使 ref selectedPeriod
const selectedPeriodRef = useRef(selectedPeriod);
useEffect(() => {
selectedPeriodRef.current = selectedPeriod;
}, [selectedPeriod]);
// 使 GSAP ScrollTrigger schedule_item period
useEffect(() => {
if (!scheduleList.length || !scheduleRefs.current) return;
const triggers = [];
// period schedule_item ScrollTrigger
const periodFirstSchedules = new Map();
scheduleList.forEach(schedule => {
if (!periodFirstSchedules.has(schedule.periodId)) {
periodFirstSchedules.set(schedule.periodId, schedule);
}
});
periodFirstSchedules.forEach((schedule, periodId) => {
const element = scheduleRefs.current[schedule.globalId];
if (element) {
const trigger = ScrollTrigger.create({
trigger: element,
start: "top 20%",
end: "top 0",
onEnter: () => {
if (selectedPeriodRef.current !== periodId) {
setSelectedPeriod(periodId);
//
if (periodRefs.current[periodId] && periodListRef.current) {
const periodElement = periodRefs.current[periodId];
const offsetTop = periodElement.offsetTop;
periodListRef.current.style.setProperty('--active-top', `${offsetTop}px`);
}
}
},
onEnterBack: () => {
if (selectedPeriodRef.current !== periodId) {
setSelectedPeriod(periodId);
//
if (periodRefs.current[periodId] && periodListRef.current) {
const periodElement = periodRefs.current[periodId];
const offsetTop = periodElement.offsetTop;
periodListRef.current.style.setProperty('--active-top', `${offsetTop}px`);
}
}
}
});
triggers.push(trigger);
}
});
return () => {
triggers.forEach(trigger => trigger.kill());
};
}, [scheduleList]);
const handleRuleChange = (ruleId) => {
elapsedTimeRef.current = 0;
@ -182,25 +293,21 @@ function Schedule(props) {
const element = periodRefs.current[periodId]
const offsetTop = element.offsetTop
periodListRef.current.style.setProperty('--active-top', `${offsetTop}px`)
//
element.scrollIntoView({
behavior: 'smooth',
block: 'center'
})
}
// 200px
// 使 GSAP
if (scheduleList && scheduleList.length > 0) {
const firstScheduleOfPeriod = scheduleList.find(schedule => schedule.periodId === periodId)
if (firstScheduleOfPeriod && scheduleRefs.current[firstScheduleOfPeriod.id]) {
const element = scheduleRefs.current[firstScheduleOfPeriod.id]
const container = element.parentElement
const offsetTop = element.getBoundingClientRect().top - container.getBoundingClientRect().top
container.scrollTo({
top: offsetTop + container.scrollTop,
behavior: 'smooth'
if (firstScheduleOfPeriod && scheduleRefs.current[firstScheduleOfPeriod.globalId]) {
const element = scheduleRefs.current[firstScheduleOfPeriod.globalId]
gsap.to(window, {
scrollTo: {
y: element,
offsetY: 100
},
duration: 0.5,
ease: "power2.inOut"
})
}
}
@ -214,7 +321,7 @@ function Schedule(props) {
ruleList.map(e => {
return <div className={`${ styles.rules_tab_item } ${ e.id === selectedRule ? styles.rules_tab_selected : '' }`} onClick={() => { handleRuleChange(e.id) }}>
<span>{ e.name }</span>
<span>2026第八届开源创新大赛</span>
<span>2026 第八届开源创新大赛</span>
<span>Competition Rules</span>
</div>
})
@ -238,8 +345,8 @@ function Schedule(props) {
</div>
<SpeTitle className={styles.title2} first={'详细赛程'} last={'XXXX'} english={'four tracks'}></SpeTitle>
<div className={styles.schedule_detail}>
<div className={styles.schedule_left}>
<div ref={scheduleDetailRef} className={styles.schedule_detail}>
<div ref={scheduleLeftRef} className={styles.schedule_left}>
<p className={styles.period_date}>03.30 08.16</p>
<div className={styles.period_list} ref={periodListRef}>
{periodList.map(period => (
@ -259,11 +366,11 @@ function Schedule(props) {
</div>
<div className={styles.schedule_right}>
<p className={styles.schedule_title}>具体赛程</p>
<div className={styles.schedule_list}>
<div ref={scheduleListRef} className={styles.schedule_list}>
{scheduleList.map(schedule => (
<div
key={schedule.id}
ref={el => scheduleRefs.current[schedule.id] = el}
key={schedule.globalId}
ref={el => scheduleRefs.current[schedule.globalId] = el}
className={styles.schedule_item}
>
<div className={styles.schedule_date}>{schedule.date}</div>
@ -279,4 +386,4 @@ function Schedule(props) {
</div>
}
export default Schedule
export default Schedule

View File

@ -4,7 +4,7 @@
background-position: top, bottom;
background-size: 100% auto, 100% auto;
.title {
padding-top: 40px;
padding-top: 160px;
}
.title2 {
padding-top: 100px;
@ -288,9 +288,6 @@
max-width: 1105px;
.schedule_list {
max-height: 700px;
overflow-y: auto;
overflow-x: hidden;
padding-right: 10px;
margin-bottom: 100px;
@ -343,17 +340,24 @@
flex: 1;
.schedule_content {
flex: 1;
font-size: 16px;
color: #333;
font-family: alibabaPuhuiM;
color:#091221;
font-size:20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 800px;
max-width: 700px;
}
.schedule_status {
font-size: 14px;
color: #999;
width: 150px;
line-height:normal;
font-family: alibabaReg;
color:#434769;
font-size:16px;
text-align: right;
}
}
@ -361,6 +365,7 @@
font-family: alibabaPuhuiM;
color: #091221;
font-size: 16px;
width: 150px;
}
&:hover {

7256
yarn.lock

File diff suppressed because it is too large Load Diff