ccf专区首页
|
|
@ -1457,6 +1457,10 @@ a.edu-txt-w80,
|
|||
font-size: 36px !important
|
||||
}
|
||||
|
||||
.font-38 {
|
||||
font-size: 38px !important
|
||||
}
|
||||
|
||||
.font-40 {
|
||||
font-size: 40px !important
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,15 +13,18 @@ const { Search } = Input;
|
|||
function PublicBanner(props) {
|
||||
const { deptId } = props.match.params;
|
||||
const { pathname } = props.history.location;
|
||||
const { data: { sectionHomepageShow, sectionHomepageTitle, sectionProjectShow, sectionDataSetShow, sectionDataSetTitle, sectionProjectTitle, sectionResourceShow, sectionResourceTitle, sectionCmsShow, sectionCmsTitle, sectionMemberShow, sectionPointsShow, sectionMemberTitle, sectionHelperShow, sectionHelperTitle }, data, adminUrl, temp, showLoginDialog, showNotification, checkIfLogin, id } = props;
|
||||
const { data: { sectionHomepageShow, sectionHomepageTitle, sectionProjectShow, sectionDataSetShow, sectionDataSetTitle, sectionProjectTitle, sectionResourceShow, sectionResourceTitle, sectionCmsShow, sectionCmsTitle, sectionMemberShow, sectionPointsShow, sectionMemberTitle, sectionHelperShow, sectionHelperTitle }, data, adminUrl, temp: defaultTemp, showLoginDialog, showNotification, checkIfLogin, id } = props;
|
||||
const isNSCC = deptId === "NSCC";
|
||||
const isUOS = deptId === "uos";
|
||||
const isCCF = deptId === "CCF-ODC";
|
||||
const [memberStatus, setMemberStatus] = useState(memberStatusEnum.notMember);
|
||||
const [applyVisible, setApplyVisible] = useState(false);
|
||||
const [ searchShow ,setSearchShow ] = useState(false);
|
||||
const [ searchValue ,setSearchValue ] = useState(null);
|
||||
const searchRef = useRef();
|
||||
|
||||
const temp = isCCF ? "ccf" : defaultTemp
|
||||
|
||||
const showMenu = useMemo(() => {
|
||||
return Boolean(sectionHomepageShow | sectionProjectShow | sectionDataSetShow | sectionResourceShow | sectionCmsShow | sectionMemberShow | sectionHelperShow | adminUrl)
|
||||
}, [sectionHomepageShow, sectionProjectShow, sectionDataSetShow, sectionResourceShow, sectionCmsShow, sectionMemberShow, sectionHelperShow, adminUrl])
|
||||
|
|
@ -100,7 +103,7 @@ function PublicBanner(props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={`in_banner ${isNSCC ? "bannerByNSCC" : ""}`}>
|
||||
<div className={`in_banner ${isNSCC ? "bannerByNSCC" : isCCF ? "bannerByCCF" : ""}`}>
|
||||
<TopEdition bannerList={data && data.bannerList && data.bannerList.split(",")}></TopEdition>
|
||||
{
|
||||
data && deptId !== 'xjykyzx' &&
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
import React, { useEffect, useState, useRef } from "react";
|
||||
import CCFIntro from "./intro";
|
||||
import Registration from "./registration";
|
||||
import Vip from "./vip";
|
||||
import News from "./news";
|
||||
import Project from "./project";
|
||||
import Rule from "./rule";
|
||||
import "./index.scss";
|
||||
import { getNewsAllList } from "../../../api";
|
||||
|
||||
const CCFHomePage = (props) => {
|
||||
const {data={}, id, history, role={}} = props || {};
|
||||
const [sectionList, setSectionList] = useState([])
|
||||
|
||||
// 用于Intersection Observer的ref数组
|
||||
const sectionRefs = {
|
||||
intro: useRef(null),
|
||||
registration: useRef(null),
|
||||
vip: useRef(null),
|
||||
news: useRef(null),
|
||||
project: useRef(null),
|
||||
rule: useRef(null)
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
id && getNewsAllList(id).then(result => {
|
||||
if (result) {
|
||||
const rows = result.data.rows;
|
||||
setSectionList(rows);
|
||||
}
|
||||
})
|
||||
}, [id])
|
||||
|
||||
// Intersection Observer - 统一处理所有模块的入场动画
|
||||
useEffect(() => {
|
||||
const observerOptions = {
|
||||
root: null, // 使用视口作为根
|
||||
rootMargin: '0px',
|
||||
threshold: 0.15 // 元素15%进入视口时触发
|
||||
};
|
||||
|
||||
const observerCallback = (entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('is-visible');
|
||||
entry.target.classList.add('fadeInChar');
|
||||
observer.unobserve(entry.target); // 只触发一次
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(observerCallback, observerOptions);
|
||||
|
||||
// 观察所有模块
|
||||
Object.values(sectionRefs).forEach(ref => {
|
||||
if (ref.current) {
|
||||
observer.observe(ref.current);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="ccf-home-page">
|
||||
<div ref={sectionRefs.intro} data-animate="fade-in-up">
|
||||
<CCFIntro zoneInfo={data} sectionList={sectionList} role={role}/>
|
||||
</div>
|
||||
<div ref={sectionRefs.registration} data-animate="fade-in-up">
|
||||
<Registration sectionList={sectionList}/>
|
||||
</div>
|
||||
<div ref={sectionRefs.vip} data-animate="fade-in-up">
|
||||
<Vip id={id}/>
|
||||
</div>
|
||||
<div ref={sectionRefs.news} data-animate="fade-in-up">
|
||||
<News id={id}/>
|
||||
</div>
|
||||
<div ref={sectionRefs.project} data-animate="fade-in-up">
|
||||
<Project id={id}/>
|
||||
</div>
|
||||
<div ref={sectionRefs.rule} data-animate="fade-in-up">
|
||||
<Rule sectionList={sectionList} history={history}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CCFHomePage;
|
||||
|
|
@ -0,0 +1,900 @@
|
|||
// CCF 首页样式 - 自适应宽度,内容区1200px,使用Flexbox布局
|
||||
|
||||
.ccf-home-page {
|
||||
background: #f7faff;
|
||||
font-family: "alibabaReg";
|
||||
|
||||
.subtitle {
|
||||
color: #434343;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
text-align: center;
|
||||
font-family: "DouyinSans";
|
||||
font-size: 44px;
|
||||
font-weight: 700;
|
||||
color: #262626;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.color-235 {
|
||||
color: #235dfa;
|
||||
}
|
||||
|
||||
.color-5f6 {
|
||||
color: #5f6777;
|
||||
}
|
||||
|
||||
.font-bd {
|
||||
font-family: "alibaba";
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.ccf_block_btn {
|
||||
background: #262626;
|
||||
border-radius: 12px;
|
||||
color: #ffffff;
|
||||
font-size: 15px;
|
||||
padding: 12px 26px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background: #3a3a3a;
|
||||
|
||||
.iconfont {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ccf-intro-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 内容包装器 - 1200px宽,居中
|
||||
.ccf-content-wrapper {
|
||||
padding: 60px 0 20px 0;
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ccf-intro-df {
|
||||
gap: 96px;
|
||||
}
|
||||
|
||||
.ccf-intro-left {
|
||||
background-image: url('../image/ccf_bk2.png');
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
background-position-y: 40px;
|
||||
}
|
||||
|
||||
.ccf-calendar-section {
|
||||
width: 40%;
|
||||
background: linear-gradient(177.22deg, #062262 3.43%, #0c5bc8 97.74%);
|
||||
border: 2px solid;
|
||||
border-color: #ffffff;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 0px 10px rgba(7, 60, 157, 0.11);
|
||||
padding: 24px 27px;
|
||||
}
|
||||
|
||||
.ccf-calendar-content {
|
||||
border: 1px solid;
|
||||
border-color: rgba(255, 255, 255, 0.32);
|
||||
border-radius: 18px;
|
||||
padding: 21px 25px;
|
||||
height: 100%;
|
||||
background-image: url('../image/ccf_bk15.png');
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.ccf-calendar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 14px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-line {
|
||||
width: 4px;
|
||||
height: 40px;
|
||||
background: linear-gradient(180deg, #c19937 0%, #64d6f8 100%);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.ccf-add-button {
|
||||
width: 100px;
|
||||
height: 36px;
|
||||
background: linear-gradient(91deg, #235dfa 0%, #34b1ff 100%);
|
||||
border: 1px rgba(255, 255, 255, 0.45) solid;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
line-height: 35px;
|
||||
font-size: 15px;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0px 4px 12px rgba(35, 93, 250, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.ccf-expert-section {
|
||||
background-image: url('../image/ccf_bk1.png');
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.ccf-intro-text {
|
||||
font-family: "Alibaba PuHuiTi", "Microsoft YaHei", sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
color: #0c2244;
|
||||
line-height: 35px;
|
||||
text-align: left;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 603px;
|
||||
}
|
||||
|
||||
.ccf-features-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 27px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ccf-feature-card {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
padding: 8px 0;
|
||||
background: rgba(255, 255, 255, 0.77);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0px 0px 5px rgba(35, 93, 250, 0.04);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.group {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.element.text {
|
||||
color: #262626;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.ccf-activities-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
height: 285px; // 固定高度,一屏显示3个元素
|
||||
overflow-y: auto; // 允许垂直滚动
|
||||
scrollbar-width: none; // Firefox隐藏滚动条
|
||||
-ms-overflow-style: none; // IE/Edge隐藏滚动条
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none; // Chrome/Safari隐藏滚动条
|
||||
}
|
||||
}
|
||||
|
||||
.ccf-activity-item {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.39) 0%, rgba(255, 255, 255, 0.15) 100%);
|
||||
display: flex;
|
||||
min-height: 85px;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
gap: 34px;
|
||||
color: #ffffff;
|
||||
position: relative;
|
||||
|
||||
a,
|
||||
a:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.ccf-activity-item-top {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 80%;
|
||||
height: 2px;
|
||||
z-index: 537;
|
||||
background: linear-gradient(116deg, rgba(255, 255, 255, 0.00) 0%, #ffffff 52%, rgba(255, 255, 255, 0.00) 100%);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 1px;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.13) 0%, rgba(255, 255, 255, 0.08) 100%);
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ccf-registration-section {
|
||||
background-image: url('../image/ccf_bk12.png');
|
||||
background-size: 100% 100%;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.ccf-registration-header {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
// 主体内容区域 - 左右两栏布局
|
||||
.ccf-registration-body {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
gap: 40px;
|
||||
|
||||
// ====================
|
||||
// 左侧区域 - 活动详情轮播
|
||||
// ====================
|
||||
.ccf-registration-left {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// ====================
|
||||
// 右侧区域 - 活动列表
|
||||
// ====================
|
||||
.ccf-registration-right {
|
||||
flex: 0 0 12%;
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid rgba(38, 38, 38, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 30px 0;
|
||||
|
||||
.ccf-activity-list-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: #262626;
|
||||
padding: 16px 0;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
color: #235dfa;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #235dfa;
|
||||
background: linear-gradient(271.54deg, #ffffff 3.14%, rgba(255, 255, 255, 0) 99.51%);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 2px;
|
||||
height: 18px;
|
||||
background: linear-gradient(178.25deg, #235dfa 1.94%, #5320ff 97.6%);
|
||||
border-radius: 2px;
|
||||
animation: loadingLineMove 4s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ccf-vip-section {
|
||||
|
||||
.ccf-vip-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 25px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ccf-vip-card {
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
padding: 24px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0px 0px 10px rgba(35, 93, 250, 0.04);
|
||||
border: 1px solid rgba(35, 93, 250, 0.13);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
// &:hover {
|
||||
// transform: translateY(-20px);
|
||||
// }
|
||||
}
|
||||
|
||||
.ccf-vip-avatar {
|
||||
width: 112px;
|
||||
height: 112px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 5px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.ccf-vip-name {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ccf-vip-tags {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.ccf-vip-tag {
|
||||
padding: 2px 12px;
|
||||
background: rgba(35, 93, 250, 0.03);
|
||||
border: 1px solid rgba(106, 146, 255, 0.22);
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
color: #2c5c95;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ccf-vip-search {
|
||||
height: 120px;
|
||||
margin-top: 36px;
|
||||
text-align: center;
|
||||
background-image: url('../image/ccf_img2.png');
|
||||
background-size: auto 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
|
||||
.ant-input-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 自定义 Search 样式
|
||||
.ant-input-search {
|
||||
border-radius: 14px;
|
||||
width: 700px;
|
||||
height: 55px;
|
||||
|
||||
.ant-input {
|
||||
border: none;
|
||||
background: #ffffff;
|
||||
padding: 12px 16px 12px 29px;
|
||||
font-size: 15px;
|
||||
|
||||
&:focus {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 0 2px rgba(35, 93, 250, 0.1);
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #aab1c3;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input-group-addon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.anticon-search {
|
||||
width: 39px;
|
||||
height: 39px;
|
||||
background: #262626;
|
||||
border-radius: 8px;
|
||||
color: #ffffff;
|
||||
line-height: 39px;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '\e93e';
|
||||
font-family: 'iconfont';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ccf-news-section {
|
||||
background-image: url('../image/ccf_bk13.png');
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.ccf-content-wrapper {
|
||||
.subtitle {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
// 时间线列表容器
|
||||
.ccf-timeline-list {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
// 时间线项
|
||||
.ccf-timeline-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 34px;
|
||||
margin-bottom: 40px;
|
||||
padding-bottom: 32px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&.ccf-timeline-item-active {
|
||||
.ccf-timeline-date {
|
||||
background: #073c9d;
|
||||
border-color: transparent;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 连接线容器
|
||||
.ccf-timeline-connector {
|
||||
position: relative;
|
||||
top: 65px;
|
||||
left: 115px;
|
||||
width: 16px;
|
||||
height: 99px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// 顶部圆形
|
||||
.ccf-timeline-connector::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: #d6e2ff;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
// 中间连接线(底色 - 始终显示淡蓝色)
|
||||
.ccf-timeline-connector::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 2px;
|
||||
height: 70px;
|
||||
background: #d6e2ff;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// 底部箭头三角
|
||||
.ccf-timeline-arrow {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) scale(0.8);
|
||||
display: inline-block;
|
||||
color: #d6e2ff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
// 激活状态的时间线项
|
||||
.ccf-timeline-item-active {
|
||||
|
||||
// 顶部圆形 - 立即变蓝
|
||||
.ccf-timeline-connector::before {
|
||||
background: #235dfa;
|
||||
// box-shadow: 0 0 10px 2px rgba(35, 93, 250, 0.5);
|
||||
}
|
||||
|
||||
// 连接线底色 - 保持淡蓝色
|
||||
.ccf-timeline-connector::after {
|
||||
background: #d6e2ff;
|
||||
}
|
||||
|
||||
// 箭头 - 延迟变蓝
|
||||
.ccf-timeline-arrow {
|
||||
animation: flowArrowBlue 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
// 蓝色覆盖层 - 使用 clip-path 从上到下扩展
|
||||
.ccf-timeline-connector-blue {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 2px;
|
||||
height: 82px;
|
||||
background: #235dfa;
|
||||
clip-path: inset(0 0 100% 0);
|
||||
animation: flowBlueClip 4s ease-in-out infinite;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// 流动光点 - 在蓝色区域前端
|
||||
.ccf-timeline-connector-flow {
|
||||
// display: block;
|
||||
// position: absolute;
|
||||
// left: 50%;
|
||||
// transform: translateX(-50%);
|
||||
// width: 4px;
|
||||
// height: 4px;
|
||||
// border-radius: 50%;
|
||||
// background: #235dfa;
|
||||
// box-shadow: 0 0 8px 2px rgba(35, 93, 250, 0.8);
|
||||
// animation: flowDotMove 3.5s ease-in-out infinite;
|
||||
// z-index: 4;
|
||||
}
|
||||
}
|
||||
|
||||
// 日期标签
|
||||
.ccf-timeline-date {
|
||||
flex: 0 0 156px;
|
||||
height: 52px;
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(35, 93, 250, 0.1);
|
||||
border-radius: 13px;
|
||||
color: #262626;
|
||||
font-size: 20px;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
line-height: 52px;
|
||||
}
|
||||
|
||||
// 右侧内容区域
|
||||
.ccf-timeline-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
// 标题和标签行
|
||||
.ccf-timeline-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ccf-timeline-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #434343;
|
||||
}
|
||||
|
||||
// 标签
|
||||
.ccf-timeline-tag {
|
||||
display: inline-block;
|
||||
padding: 0px 12px;
|
||||
background: rgba(35, 93, 250, 0.03);
|
||||
border: 1px solid rgba(106, 146, 255, 0.22);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #2c5c95;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 描述文字
|
||||
.ccf-timeline-summary {
|
||||
color: #6b7280;
|
||||
font-size: 15px;
|
||||
line-height: 2.3;
|
||||
}
|
||||
}
|
||||
|
||||
.ccf-project-section {
|
||||
.ccf-project-img {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
top: 45px;
|
||||
}
|
||||
|
||||
.ccf-project-blue {
|
||||
height: 408px;
|
||||
background: url('../image/ccf_bk3.png'), linear-gradient(89.38deg, #062262 1.8%, #0c5bc8 98.52%);
|
||||
background-size: 100% 100%;
|
||||
border-radius: 20px;
|
||||
margin-top: 45px;
|
||||
padding: 44px 41px 40px 61px;
|
||||
}
|
||||
|
||||
.ccf-project-blue-left {
|
||||
color: white;
|
||||
width: 34.7%;
|
||||
flex-shrink: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.ccf-project-blue-bottom-1 {
|
||||
background: linear-gradient(180deg,rgba(7, 60, 157, 0.29) 0%,rgba(35, 93, 250, 0.34) 100%);
|
||||
border-radius: 20px;
|
||||
padding: 16px 0;
|
||||
border-radius: 17px;
|
||||
margin-top: 35px;
|
||||
}
|
||||
|
||||
.ccf-project-blue-bottom-2 {
|
||||
padding: 16px 20px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.29);
|
||||
border-radius: 17px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: url('../image/ccf_bk4.png');
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.ccf-project-blue-bottom-3 {
|
||||
padding: 10px 54px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.29);
|
||||
border-radius: 17px;
|
||||
position: relative;
|
||||
|
||||
.ccf-project-blue-bottom-3-top {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
width: 80%;
|
||||
height: 2px;
|
||||
z-index: 537;
|
||||
background: linear-gradient(116deg, rgba(255, 255, 255, 0.00) 0%, #ffffff 52%, rgba(255, 255, 255, 0.00) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.ccf_proType_1,
|
||||
.ccf_proType_2 {
|
||||
width: 26.5%;
|
||||
height: 100%;
|
||||
background: url('../image/ccf_bk5.png'), white;
|
||||
background-size: 100% 100%;
|
||||
border-radius: 20px;
|
||||
padding: 49px 34px 30px 34px;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
width: 125px;
|
||||
height: 36px;
|
||||
border: 1px solid;
|
||||
border-color: #262626;
|
||||
border-radius: 9px;
|
||||
color: #262626;
|
||||
line-height: 36px;
|
||||
margin-top: 25px;
|
||||
|
||||
&.unClick {
|
||||
cursor: no-drop;
|
||||
}
|
||||
|
||||
&:hover:not(.unClick) {
|
||||
color: #235dfa;
|
||||
border-color: #235dfa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ccf_proType_2 {
|
||||
background: url('../image/ccf_bk6.png'), white;
|
||||
background-size: 100% 100%;
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.ccf-rule-section {
|
||||
background-image: url('../image/ccf_bk14.png');
|
||||
background-size: 100% 100%;
|
||||
padding-bottom: 100px;
|
||||
|
||||
.ccf-rule-cards-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 360px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ccf-rule-card {
|
||||
position: absolute;
|
||||
width:243px;
|
||||
height:252px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transform-style: preserve-3d;
|
||||
will-change: transform, opacity;
|
||||
background-size: 100% 100%;
|
||||
|
||||
&:hover {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ccf-rule-card-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 30px 24px;
|
||||
}
|
||||
|
||||
.ccf-rule-arrows {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.ccf-rule-arrow {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(35, 93, 250, 0.15);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: #262626;
|
||||
|
||||
&:hover {
|
||||
background: #235dfa;
|
||||
border-color: #235dfa;
|
||||
color: #ffffff;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4px 12px rgba(35, 93, 250, 0.3);
|
||||
}
|
||||
|
||||
&:first-of-type{
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 蓝色覆盖层 clip-path 动画 - 从上到下展开(3.5秒)
|
||||
@keyframes flowBlueClip {
|
||||
0% {
|
||||
clip-path: inset(0 0 100% 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
clip-path: inset(0 0 0% 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 流动光点位置变化 - 从顶部移动到底部(3.5秒)
|
||||
@keyframes flowDotMove {
|
||||
0% {
|
||||
top: 0px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 82px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 箭头变蓝动画 - 延迟3.5秒执行
|
||||
@keyframes flowArrowBlue {
|
||||
|
||||
0%,
|
||||
94.5% {
|
||||
color: #d6e2ff;
|
||||
}
|
||||
|
||||
95%,
|
||||
100% {
|
||||
color: #235dfa;
|
||||
}
|
||||
}
|
||||
|
||||
// 蓝色线条从上往下移动的动画
|
||||
@keyframes loadingLineMove {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(100%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 统一模块出场动画 - Intersection Observer方案
|
||||
// 适用于所有高度不固定的模块
|
||||
// ============================================
|
||||
|
||||
// 动画初始状态(默认隐藏)
|
||||
.ccf-home-page > div[data-animate] {
|
||||
.ccf-content-animate {
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
transition: opacity 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) 1s,
|
||||
transform 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) 1s;
|
||||
|
||||
}
|
||||
.subtitle {
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
transition: opacity 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.5s,
|
||||
transform 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.5s;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 动画触发状态(进入视口时添加)
|
||||
.ccf-home-page > div[data-animate].is-visible{
|
||||
.ccf-content-animate, .subtitle{
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 可选:为不同模块添加延迟,实现交错动画效果
|
||||
// 如果希望所有模块同时动画,可以删除以下延迟设置
|
||||
// .ccf-home-page > div[data-animate]:nth-child(1) { transition-delay: 0s; }
|
||||
// .ccf-home-page > div[data-animate]:nth-child(2) { transition-delay: 0.1s; }
|
||||
// .ccf-home-page > div[data-animate]:nth-child(3) { transition-delay: 0.2s; }
|
||||
// .ccf-home-page > div[data-animate]:nth-child(4) { transition-delay: 0.3s; }
|
||||
// .ccf-home-page > div[data-animate]:nth-child(5) { transition-delay: 0.4s; }
|
||||
// .ccf-home-page > div[data-animate]:nth-child(6) { transition-delay: 0.5s; }
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
import React, { useEffect, useMemo, useState, useRef } from "react";
|
||||
import groupIcon1 from '../image/ccf_icon1.png';
|
||||
import groupIcon2 from '../image/ccf_icon2.png';
|
||||
import groupIcon3 from '../image/ccf_icon3.png';
|
||||
import groupIcon4 from '../image/ccf_icon4.png';
|
||||
import groupImg1 from '../image/ccf_img1.png';
|
||||
import moment from 'moment';
|
||||
import { getSubDocList } from "../../../api";
|
||||
import Nodata from "../../../../Nodata";
|
||||
import { nsccTitle } from "../nscc";
|
||||
import { Dropdown, Menu } from "antd";
|
||||
|
||||
// 功能卡片数据
|
||||
const featureCards = [
|
||||
{
|
||||
id: 1,
|
||||
title: "开源平台建设",
|
||||
icon: groupIcon1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "开源人才培养",
|
||||
icon: groupIcon2,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "开源项目孵化",
|
||||
icon: groupIcon3,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "开源生态建设",
|
||||
icon: groupIcon4,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
// 功能卡片组件
|
||||
const FeatureCard = ({ title, icon }) => (
|
||||
<div className="ccf-feature-card">
|
||||
<img src={icon} width={28} />
|
||||
<div className="element text">{title}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// 活动列表项组件
|
||||
const ActivityItem = ({ id, keywords, name, summary }) => (
|
||||
<div className="ccf-activity-item">
|
||||
<div className="ccf-activity-item-top"></div>
|
||||
<span className="font-bd">
|
||||
{moment(summary).format("YYYY")}<br />
|
||||
{moment(summary).format("MM/DD")}
|
||||
</span>
|
||||
<div>
|
||||
<div style={{ color: '#d8ebff' }} className="font-bd"><a href={`/zone/CCF-ODC/newdetail/${id}`}>{name}</a></div>
|
||||
<div style={{ color: '#d8ebffad' }}><i className="iconfont icon-weizhi font-15 mr5"></i>{keywords}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// 主组件
|
||||
const CCFIntro = ({ zoneInfo, sectionList, role }) => {
|
||||
const { firstTitle, introductionContent, key: deptId } = zoneInfo
|
||||
const [docList, setDocList] = useState([]);
|
||||
const scrollRef = useRef(null);
|
||||
const animationRef = useRef(null);
|
||||
const lastTimeRef = useRef(null);
|
||||
const isPausedRef = useRef(false);
|
||||
const animateRef = useRef(null); // 存储 animate 函数引用
|
||||
|
||||
// 从sectionList中过滤出name为"执委活动日历"的元素id
|
||||
const executiveCalendarId = useMemo(() => {
|
||||
const found = sectionList?.find(item => item.name === "执委活动日历");
|
||||
return found?.id;
|
||||
}, [sectionList]);
|
||||
|
||||
useEffect(() => {
|
||||
executiveCalendarId && getSubDocList(executiveCalendarId, { pageSize: 1000, pageNum: 1, auditStatus: 1 }).then(result => {
|
||||
if (result && result.data.rows) {
|
||||
setDocList(result.data.rows);
|
||||
}
|
||||
})
|
||||
}, [executiveCalendarId])
|
||||
|
||||
// 活动列表滚动效果:列表长度大于3时才启用
|
||||
useEffect(() => {
|
||||
const scrollContainer = scrollRef.current;
|
||||
if (!scrollContainer) return;
|
||||
|
||||
// 清理之前的动画帧
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
|
||||
// 列表长度<=3或为空时,不启用滚动
|
||||
if (!docList || docList.length <= 3) {
|
||||
scrollContainer.scrollTop = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollSpeed = 0.5; // 每帧滚动像素
|
||||
const intervalTime = 32; // 约60fps
|
||||
const resetDuration = 1000; // 重置过渡时间
|
||||
let isResetting = false;
|
||||
let resetStartTime = null;
|
||||
let resetStartScrollTop = 0;
|
||||
|
||||
const animate = (timestamp) => {
|
||||
animateRef.current = animate; // 存储引用供 JSX 使用
|
||||
// 鼠标悬停时暂停滚动
|
||||
if (isPausedRef.current) {
|
||||
return; // 暂停时不调用 requestAnimationFrame
|
||||
}
|
||||
|
||||
// 如果正在执行重置过渡动画
|
||||
if (isResetting) {
|
||||
const elapsed = timestamp - resetStartTime;
|
||||
if (elapsed >= resetDuration) {
|
||||
// 过渡完成,重置状态
|
||||
scrollContainer.scrollTop = 0;
|
||||
isResetting = false;
|
||||
resetStartTime = null;
|
||||
} else {
|
||||
// 使用 easeOutQuad 缓动函数
|
||||
const progress = elapsed / resetDuration;
|
||||
const easeOut = 1 - (1 - progress) * (1 - progress);
|
||||
scrollContainer.scrollTop = resetStartScrollTop * (1 - easeOut);
|
||||
}
|
||||
animationRef.current = requestAnimationFrame(animate);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lastTimeRef.current) {
|
||||
lastTimeRef.current = timestamp;
|
||||
}
|
||||
|
||||
const elapsed = timestamp - lastTimeRef.current;
|
||||
|
||||
if (elapsed >= intervalTime) {
|
||||
if (scrollContainer.scrollTop >= scrollContainer.scrollHeight - scrollContainer.clientHeight) {
|
||||
// 开始重置过渡
|
||||
isResetting = true;
|
||||
resetStartTime = timestamp;
|
||||
resetStartScrollTop = scrollContainer.scrollHeight - scrollContainer.clientHeight;
|
||||
lastTimeRef.current = null; // 重置时间戳,避免重置完成后速度异常
|
||||
} else {
|
||||
scrollContainer.scrollTop += scrollSpeed;
|
||||
}
|
||||
lastTimeRef.current = timestamp;
|
||||
}
|
||||
|
||||
animationRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animationRef.current = requestAnimationFrame(animate);
|
||||
|
||||
return () => {
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
lastTimeRef.current = null;
|
||||
};
|
||||
}, [docList]);
|
||||
|
||||
|
||||
const addNewMenu = (
|
||||
<Menu>
|
||||
<Menu.Item>
|
||||
<a href={`/zone/${deptId}/news/add?type=richEditor&dirId=${executiveCalendarId}&formType=ccfActivity`}>富文本编辑器</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<a href={`/zone/${deptId}/news/add?type=mdEditor&dirId=${executiveCalendarId}&formType=ccfActivity`}>MD编辑器</a>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="ccf-intro-section">
|
||||
{/* 专家简介区域 */}
|
||||
<div className="ccf-expert-section">
|
||||
{/* 内容区域 - 1200px宽 */}
|
||||
<div className="ccf-content-wrapper">
|
||||
<div className="main-title">
|
||||
{nsccTitle(firstTitle)}
|
||||
</div>
|
||||
<div className="subtitle mb60">
|
||||
{introductionContent}
|
||||
</div>
|
||||
<div className="df ccf-intro-df ccf-content-animate">
|
||||
<div className="flex1 ccf-intro-left">
|
||||
<div className="ccf-features-container">
|
||||
{featureCards.map((card) => (
|
||||
<FeatureCard
|
||||
key={card.id}
|
||||
title={card.title}
|
||||
icon={card.icon}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="center">
|
||||
<img src={groupImg1} style={{ width: '80%' }} />
|
||||
</div>
|
||||
<div className="color-235 font-24 font-bd center mt15 mb15">CCF开源发展技术委员</div>
|
||||
<div className="ccf-intro-text">
|
||||
CCF开源发展技术委员会是中国计算机学会(CCF)下属专业技术委员会,为国内开源领域权威学术与产业交流平台。汇聚顶尖专家学者、技术领袖与高校力量,推动开源生态建设、人才培养、技术创新与文化普及。
|
||||
<br />
|
||||
以标准化、公益化、生态化为核心,打造产学研协同的开源合作体系,赋能高校、企业与开发者,助力中国开源高质量发展。
|
||||
</div>
|
||||
</div>
|
||||
{/* 执委日历区域 */}
|
||||
<div className="ccf-calendar-section">
|
||||
<div className="ccf-calendar-content">
|
||||
{/* 左侧蓝色面板 */}
|
||||
{/* 标题 */}
|
||||
<div className="ccf-calendar-header">
|
||||
<div className="header-line mr15" />
|
||||
<div className="mrat">
|
||||
<div className="font-18">执委活动日历</div>
|
||||
<div style={{ color: '#b8cfe6' }}>近期重点活动预告</div>
|
||||
</div>
|
||||
{/* 添加事件按钮 */}
|
||||
{role && role.role !== "None" &&
|
||||
<Dropdown overlay={addNewMenu}>
|
||||
<div className="ccf-add-button">
|
||||
添加事件
|
||||
</div>
|
||||
</Dropdown>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* 活动列表 */}
|
||||
<div
|
||||
className="ccf-activities-list"
|
||||
ref={scrollRef}
|
||||
onMouseEnter={() => {
|
||||
isPausedRef.current = true;
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
isPausedRef.current = false;
|
||||
lastTimeRef.current = null; // 重置时间戳
|
||||
if (typeof animateRef.current === 'function') {
|
||||
animationRef.current = requestAnimationFrame(animateRef.current);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{docList && docList.length > 0 ? (
|
||||
docList.map((activity) => (
|
||||
<ActivityItem key={activity.id} {...activity} />
|
||||
))
|
||||
) : (
|
||||
<Nodata _html="暂无数据" small />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CCFIntro;
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
import React, { useState, useRef, useEffect, useMemo } from "react";
|
||||
import { gethomePageDocList } from "../../../api";
|
||||
import moment from 'moment';
|
||||
import Nodata from "../../../../Nodata";
|
||||
import { nsccTitle } from "../nscc";
|
||||
|
||||
// 时间线项组件
|
||||
const TimelineItem = ({ item, isActive }) => {
|
||||
const { id, name, publishTime, summary, keywords } = item;
|
||||
|
||||
return (
|
||||
<div key={id} className={`ccf-timeline-item ${isActive ? 'ccf-timeline-item-active' : ''}`}>
|
||||
{/* 连接线 */}
|
||||
<div className={`ccf-timeline-connector ${isActive ? 'active' : ''}`}>
|
||||
{isActive && <span className="ccf-timeline-connector-blue"></span>}
|
||||
{isActive && <span className="ccf-timeline-connector-flow"></span>}
|
||||
<i className={`iconfont icon-xialaanniu1 ccf-timeline-arrow font-12 ${isActive ? 'active' : ''}`}></i>
|
||||
</div>
|
||||
|
||||
{/* 左侧日期标签 */}
|
||||
<div className={`ccf-timeline-date ${isActive ? 'ccf-timeline-date-active' : ''}`}>
|
||||
{moment(publishTime).format('YYYY-MM-DD')}
|
||||
</div>
|
||||
|
||||
{/* 右侧内容 */}
|
||||
<div className="ccf-timeline-content">
|
||||
{/* 标题和标签 */}
|
||||
<div className="ccf-timeline-header">
|
||||
<div className="ccf-timeline-title">{name}</div>
|
||||
{keywords && (
|
||||
<div className="ccf-timeline-tag">{keywords}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 描述文字 */}
|
||||
{summary && (
|
||||
<div className="ccf-timeline-summary task-hide-2">
|
||||
{summary}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 主组件
|
||||
const News = ({ id, autoPlay = true }) => {
|
||||
const [docList, setDocList] = useState([]);
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const timerRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
id && gethomePageDocList(id).then(result => {
|
||||
if (result && result.data.rows) {
|
||||
setDocList(result.data.rows);
|
||||
}
|
||||
})
|
||||
}, [id])
|
||||
|
||||
// 自动切换 active 元素
|
||||
useEffect(() => {
|
||||
if (!autoPlay || docList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const intervalCount = docList.length;
|
||||
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
}
|
||||
|
||||
timerRef.current = setInterval(() => {
|
||||
setActiveIndex((prev) => {
|
||||
const next = (prev + 1) % intervalCount;
|
||||
return next;
|
||||
});
|
||||
}, 4000);
|
||||
|
||||
return () => {
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
}
|
||||
};
|
||||
}, [autoPlay, docList.length]);
|
||||
|
||||
return (
|
||||
<div className="ccf-news-section">
|
||||
{/* 内容包装器 - 1200px宽,居中 */}
|
||||
<div className="ccf-content-wrapper">
|
||||
{/* 顶部标题区域 */}
|
||||
<div className="main-title">
|
||||
{nsccTitle("最新动态 · 开源前沿", 0.1, "", 6, "color-235")}
|
||||
</div>
|
||||
<div className="subtitle">
|
||||
实时更新活动资讯、公告与行业开源前沿动态
|
||||
</div>
|
||||
|
||||
<div className="ccf-content-animate">
|
||||
{/* 时间线列表 */}
|
||||
<div className="ccf-timeline-list">
|
||||
{docList && docList.length > 0 ? (
|
||||
docList.map((item, index) => (
|
||||
<TimelineItem
|
||||
key={item.id || index}
|
||||
item={item}
|
||||
isActive={index === activeIndex}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Nodata _html="暂无数据" small />
|
||||
)}
|
||||
</div>
|
||||
{docList && docList.length > 0 && <div className="center mt80">
|
||||
<a className="ccf_block_btn" href="/zone/CCF-ODC/news">查看更多<i className="iconfont icon-jiantou3 font-14 ml10"></i></a>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default News;
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { getProjectsTypeLists, getZoneStatistics } from "../../../api";
|
||||
import icon1 from '../image/ccf_icon5.png';
|
||||
import icon2 from '../image/ccf_icon6.png';
|
||||
import ccf_img3 from '../image/ccf_img3.png';
|
||||
import ccf_img4 from '../image/ccf_img4.png';
|
||||
import { Link } from "react-router-dom";
|
||||
import { nsccTitle } from "../nscc";
|
||||
|
||||
function project({ id }) {
|
||||
const [ statistics, setStatistics ] = useState(undefined);
|
||||
const [ typeList , setTypeList ] = useState([]);
|
||||
const {communityUserCount, projectCount} = statistics || {}
|
||||
|
||||
useEffect(()=>{
|
||||
id && getZoneStatistics(id).then(result => {
|
||||
if (result && result.data) {
|
||||
setStatistics(result.data.data);
|
||||
}
|
||||
});
|
||||
|
||||
id && getProjectsTypeLists(id).then(result=>{
|
||||
if(result){
|
||||
setTypeList(result.data.rows);
|
||||
}
|
||||
}).catch(error=>{})
|
||||
}, [id])
|
||||
|
||||
const typeId1 = useMemo(() => {
|
||||
if (!typeList || !Array.isArray(typeList)) return null;
|
||||
const targetSection = typeList.find((item) => item.name === "捐赠开源项目库");
|
||||
return targetSection?.id || null;
|
||||
}, [typeList]);
|
||||
|
||||
const typeId2 = useMemo(() => {
|
||||
if (!typeList || !Array.isArray(typeList)) return null;
|
||||
const targetSection = typeList.find((item) => item.name === "开源学习资源库");
|
||||
return targetSection?.id || null;
|
||||
}, [typeList]);
|
||||
|
||||
return <div className="ccf-project-section">
|
||||
<div className="ccf-content-wrapper pt5">
|
||||
<img src={ccf_img3} className="ccf-project-img"/>
|
||||
<div className="main-title">
|
||||
{nsccTitle("开源项目 · 共享资源", 0.1, "", 6, "color-235")}
|
||||
</div>
|
||||
<div className="subtitle">
|
||||
优质开源项目合集 & 免费学习资源,助力开发者快速成长
|
||||
</div>
|
||||
|
||||
<div className="ccf-project-blue df-center ccf-content-animate">
|
||||
<div className="ccf-project-blue-left">
|
||||
<img src={ccf_img4} width={130} style={{margin: "0 6px 10px -10px"}}/>
|
||||
<i className="iconfont icon-a-duobianxing1 font-13 mr10"></i>
|
||||
<span className="font-38 font-bd mr10">{communityUserCount}+</span>
|
||||
<span className="font-15">贡献者</span>
|
||||
<div className="font-13">联结 CCF 会员,搭建全球开源协同创新实践平台</div>
|
||||
<div className="ccf-project-blue-bottom-1">
|
||||
<div className="ccf-project-blue-bottom-2">
|
||||
<div className="df-center-between ccf-project-blue-bottom-3">
|
||||
<div className="ccf-project-blue-bottom-3-top"></div>
|
||||
<div>
|
||||
<span className="font-38 font-bd">{typeList.length}+</span>
|
||||
<div className="opacity8">项目分类数</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-38 font-bd">{projectCount}+</span>
|
||||
<div className="opacity8">代码库数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ccf_proType_1 center">
|
||||
<img src={icon1} width={46}/>
|
||||
<div className="font-20 font-bd mt20 mb10">捐赠开源项目库</div>
|
||||
<div className="font-15 color-5f6">覆盖AI、云原生、嵌入式等多领域优质开源项目,开放共享</div>
|
||||
{typeId1 ? <Link to={{ pathname: `/zone/CCF-ODC/projects`, state: { id: typeId1 } }}>立即查看</Link> : <a className="unClick">暂未开放</a>}
|
||||
</div>
|
||||
<div className="ccf_proType_2 center">
|
||||
<img src={icon2} width={46}/>
|
||||
<div className="font-20 font-bd mt20 mb10">开源学习资源库</div>
|
||||
<div className="font-15 color-5f6">教程、文档、工具、案例合集,一站式开源学习平台</div>
|
||||
{typeId2 ? <Link to={{ pathname: `/zone/CCF-ODC/projects`, state: { id: typeId2 } }}>立即查看</Link> : <a className="unClick">暂未开放</a>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default project;
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import React, { useState, useRef, useEffect, useMemo } from "react";
|
||||
import Slider from "react-slick";
|
||||
import { getDocAndContentList, getSubDocList } from "../../../api";
|
||||
import { Base64 } from "js-base64";
|
||||
import { nsccTitle } from "../nscc";
|
||||
|
||||
// 主组件
|
||||
const Registration = ({ sectionList, autoPlay = true }) => {
|
||||
const [activeId, setActiveId] = useState();
|
||||
const [docList, setDocList] = useState([]);
|
||||
const sliderRef = useRef(null);
|
||||
|
||||
// 从 sectionList 中过滤出 name 为"参与开源"的元素的 id
|
||||
const participationOpenSourceId = useMemo(() => {
|
||||
if (!sectionList || !Array.isArray(sectionList)) return null;
|
||||
const targetSection = sectionList.find((item) => item.name === "参与开源");
|
||||
return targetSection?.id || null;
|
||||
}, [sectionList]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
participationOpenSourceId && getDocAndContentList(participationOpenSourceId, { pageSize: 4, pageNum: 1, auditStatus: 1 }).then(result => {
|
||||
if (result && result.data.rows) {
|
||||
setDocList(result.data.rows);
|
||||
result.data.rows[0] && setActiveId(result.data.rows[0].id)
|
||||
}
|
||||
})
|
||||
}, [participationOpenSourceId])
|
||||
|
||||
// 处理活动切换:同时更新状态和轮播位置
|
||||
const handleActivityChange = (activityId) => {
|
||||
setActiveId(activityId);
|
||||
if (sliderRef.current) {
|
||||
const index = docList.findIndex((item) => item.id === activityId);
|
||||
if (index >= 0) {
|
||||
sliderRef.current.slickGoTo(index);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// react-slick 水平轮播配置(左侧)
|
||||
const carouselSettings = {
|
||||
dots: false,
|
||||
autoplay: autoPlay,
|
||||
autoplaySpeed: 4000,
|
||||
speed: 600,
|
||||
infinite: true,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
// initialSlide: docList.findIndex((item) => item.id === (docList[0]?.id)),
|
||||
beforeChange: (current, next) => {
|
||||
const nextActivity = docList[next % docList.length];
|
||||
if (nextActivity) {
|
||||
setActiveId(nextActivity.id);
|
||||
}
|
||||
},
|
||||
pauseOnHover: true,
|
||||
arrows: true,
|
||||
adaptiveHeight: true,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ccf-registration-section">
|
||||
{/* 内容包装器 - 1200px宽,居中 */}
|
||||
<div className="ccf-content-wrapper">
|
||||
{/* 顶部标题区域 */}
|
||||
<div className="ccf-registration-header">
|
||||
<div className="main-title">
|
||||
{nsccTitle("一键报名 · 参与开源", 0.1, "", 6, "color-235")}
|
||||
</div>
|
||||
<div className="subtitle">
|
||||
四大品牌核心活动,全维度覆盖开源人才培养、行业交流与生态共建
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 主体内容区域 */}
|
||||
<div className="ccf-registration-body ccf-content-animate">
|
||||
{/* 左侧区域 - 活动详情轮播 */}
|
||||
<div className="ccf-registration-left">
|
||||
<Slider ref={sliderRef} {...carouselSettings}>
|
||||
{docList.map(item => {
|
||||
return <div><div className='width1600' dangerouslySetInnerHTML={{__html: Base64.decode(item.content)}}></div></div>
|
||||
})}
|
||||
</Slider>
|
||||
</div>
|
||||
|
||||
{/* 右侧区域 - 活动列表 */}
|
||||
<div className="ccf-registration-right">
|
||||
{docList.map((activity) => (
|
||||
<div key={activity.id} onClick={() => handleActivityChange(activity.id)} className={`font-bd ccf-activity-list-item ${activity.id === activeId ? "active" : ""}`}>
|
||||
{activity.name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Registration;
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { getSubDocList } from "../../../api";
|
||||
import bk1 from '../image/ccf_bk7.png';
|
||||
import bk2 from '../image/ccf_bk8.png';
|
||||
import bk3 from '../image/ccf_bk9.png';
|
||||
import bk4 from '../image/ccf_bk10.png';
|
||||
import bk5 from '../image/ccf_bk11.png';
|
||||
import icon1 from '../image/ccf_icon7.png';
|
||||
import icon2 from '../image/ccf_icon8.png';
|
||||
import icon3 from '../image/ccf_icon9.png';
|
||||
import icon4 from '../image/ccf_icon10.png';
|
||||
import icon5 from '../image/ccf_icon11.png';
|
||||
import { nsccTitle } from "../nscc";
|
||||
|
||||
// 卡片颜色主题配置
|
||||
const cardThemes = [
|
||||
{ bgImage: bk4, icon: icon4, textColor: "#ffffff" },
|
||||
{ bgImage: bk5, icon: icon5, textColor: "#262626" },
|
||||
{ bgImage: bk1, icon: icon1, textColor: "#ffffff" },
|
||||
{ bgImage: bk2, icon: icon2, textColor: "#ffffff" },
|
||||
{ bgImage: bk3, icon: icon3, textColor: "#262626" },
|
||||
];
|
||||
|
||||
function Rule({ sectionList, history }) {
|
||||
const [ruleCards, setRuleCards] = useState([]);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [isAutoPlaying, setIsAutoPlaying] = useState(true);
|
||||
const autoPlayInterval = 4000;
|
||||
|
||||
// 从 sectionList 中过滤出 name 为"官方规则"的元素的 id
|
||||
const ruleTypeId = useMemo(() => {
|
||||
if (!sectionList || !Array.isArray(sectionList)) return null;
|
||||
const targetSection = sectionList.find((item) => item.name === "官方规则");
|
||||
return targetSection?.id || null;
|
||||
}, [sectionList]);
|
||||
|
||||
useEffect(() => {
|
||||
ruleTypeId && getSubDocList(ruleTypeId, { pageSize: 100, pageNum: 1, auditStatus: 1 }).then(result => {
|
||||
if (result && result.data.rows) {
|
||||
const transformedCards = result.data.rows.map((item, index) => {
|
||||
const theme = cardThemes[index % cardThemes.length];
|
||||
return {
|
||||
...item,
|
||||
...theme
|
||||
};
|
||||
});
|
||||
// .concat(transformedCards)
|
||||
setRuleCards(transformedCards.concat(transformedCards).concat(transformedCards).concat(transformedCards).concat(transformedCards).concat(transformedCards));
|
||||
}
|
||||
})
|
||||
}, [ruleTypeId])
|
||||
|
||||
// 计算需要显示的卡片列表(循环填充到5张)
|
||||
const displayCards = useMemo(() => {
|
||||
const total = ruleCards.length;
|
||||
if (total === 0) return [];
|
||||
|
||||
const cards = [];
|
||||
|
||||
// 如果卡片数量 >= 5,使用原始卡片数;否则填充到5张
|
||||
const targetCount = Math.max(total, 5);
|
||||
|
||||
for (let i = 0; i < targetCount; i++) {
|
||||
const originalCard = ruleCards[i % total];
|
||||
const theme = cardThemes[i % cardThemes.length];
|
||||
cards.push({
|
||||
...originalCard,
|
||||
...theme,
|
||||
});
|
||||
}
|
||||
|
||||
return cards;
|
||||
}, [ruleCards]);
|
||||
|
||||
// 获取当前可见的5张卡片(循环窗口)
|
||||
const getVisibleCards = useCallback((current, cards) => {
|
||||
const total = cards.length;
|
||||
if (total === 0) return [];
|
||||
|
||||
const visible = [];
|
||||
for (let i = -2; i <= 2; i++) {
|
||||
const index = (current + i + total) % total;
|
||||
visible.push({ ...cards[index] });
|
||||
}
|
||||
return visible;
|
||||
}, []);
|
||||
|
||||
const visibleCards = getVisibleCards(currentIndex, displayCards);
|
||||
|
||||
// 切换到下一张
|
||||
const nextCard = useCallback(() => {
|
||||
setCurrentIndex((prev) => (prev + 1) % displayCards.length);
|
||||
}, [displayCards.length]);
|
||||
|
||||
// 切换到上一张
|
||||
const prevCard = useCallback(() => {
|
||||
setCurrentIndex((prev) => (prev - 1 + displayCards.length) % displayCards.length);
|
||||
}, [displayCards.length]);
|
||||
|
||||
// 跳转到详情页
|
||||
const handleCardClick = useCallback((id) => {
|
||||
if (id) {
|
||||
history.push(`/zone/CCF-ODC/newdetail/${id}`)
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 自动播放
|
||||
useEffect(() => {
|
||||
if (!isAutoPlaying || displayCards.length <= 5) return;
|
||||
const interval = setInterval(nextCard, autoPlayInterval);
|
||||
return () => clearInterval(interval);
|
||||
}, [isAutoPlaying, nextCard, displayCards.length]);
|
||||
|
||||
// 鼠标悬停时暂停自动播放
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
setIsAutoPlaying(false);
|
||||
}, []);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsAutoPlaying(true);
|
||||
}, []);
|
||||
|
||||
// 获取每张卡片的位置和样式(直接使用 position 参数,0-4 对应左2到右2)
|
||||
const getCardStyle = (position, bgImage) => {
|
||||
const rotations = [-35, -20, 0, 20, 35];
|
||||
const translatesX = [-510, -230, 0, 230, 510];
|
||||
const opacities = [1, 1, 1, 1, 1];
|
||||
const translateY = [130, 30, -10, 30, 130];
|
||||
|
||||
return {
|
||||
transform: `translateX(${translatesX[position]}px) rotate(${rotations[position]}deg) translateY(${translateY[position]}px)`,
|
||||
opacity: opacities[position],
|
||||
zIndex: 5 - Math.abs(position - 2),
|
||||
transition: "all 0.5s cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
backgroundImage: bgImage ? `url(${bgImage})` : 'none',
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ccf-rule-section">
|
||||
{/* 内容包装器 - 1200px宽,居中 */}
|
||||
<div className="ccf-content-wrapper">
|
||||
<div className="main-title">
|
||||
{nsccTitle("官方规则 · 公开透明", 0.1, "", 6, "color-235")}
|
||||
</div>
|
||||
<div className="subtitle">
|
||||
全量规则公示,规范运营、公开透明,接受社会监督
|
||||
</div>
|
||||
<div
|
||||
className="ccf_rule_box ccf-content-animate"
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{/* 卡片容器 */}
|
||||
<div className="ccf-rule-cards-container">
|
||||
{visibleCards.map((card, position) => {
|
||||
const isCenter = position === 2;
|
||||
return (
|
||||
<div
|
||||
key={`${card.id}-${currentIndex}-${position}`}
|
||||
className={`ccf-rule-card ${isCenter ? "ccf-rule-card-center" : ""}`}
|
||||
style={getCardStyle(position, card.bgImage)}
|
||||
onClick={() => handleCardClick(card.id)}
|
||||
>
|
||||
<div className="ccf-rule-card-content" style={{ color: card.textColor }}>
|
||||
<img src={card.icon} width={44}/>
|
||||
<div className="font-17 mt20 mb5 task-hide">{card.name}</div>
|
||||
<div className="opacity8 font-15 lineh-25 task-hide-2">{card.summary}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 导航控制 */}
|
||||
<div className="ccf-rule-nav">
|
||||
<div className="ccf-rule-arrows">
|
||||
<button
|
||||
className="ccf-rule-arrow ccf-rule-arrow-left"
|
||||
onClick={prevCard}
|
||||
type="button"
|
||||
disabled={displayCards.length <= 5}
|
||||
>
|
||||
<i className="iconfont icon-arrowRight"></i>
|
||||
</button>
|
||||
<button
|
||||
className="ccf-rule-arrow ccf-rule-arrow-right"
|
||||
onClick={nextCard}
|
||||
type="button"
|
||||
disabled={displayCards.length <= 5}
|
||||
>
|
||||
<i className="iconfont icon-arrowRight"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Rule;
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import { Input } from 'antd';
|
||||
import { getGlobal, getVIPLists } from "../../../api";
|
||||
import Nodata from "../../../../Nodata";
|
||||
import { nsccTitle } from "../nscc";
|
||||
|
||||
const Search = Input.Search;
|
||||
|
||||
function Ccf_vip({id}) {
|
||||
const [ vipLists , setVIPLists ] = useState(undefined);
|
||||
const [ searchValue, setSearchValue ] = useState('');
|
||||
|
||||
useEffect(()=>{
|
||||
if(searchValue){
|
||||
getGlobal({
|
||||
extraSearchParam: {
|
||||
supportMemberCategoryLevelSearch: true
|
||||
},
|
||||
keyword: searchValue,
|
||||
types: ["member"],
|
||||
zoneId: id
|
||||
}).then(result => {
|
||||
if (result && result.data) {
|
||||
setVIPLists(result.data.rows.map(item=>{
|
||||
const {username, imageUrl, memberLevelName, memberTypeName} = item.extendData
|
||||
return{
|
||||
name: username,
|
||||
imageUrl: imageUrl,
|
||||
memberLevel: memberLevelName,
|
||||
typeName: memberTypeName
|
||||
}
|
||||
}));
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
id && getVIPLists(id, {isHomepage: 1, search: searchValue}).then(response=>{
|
||||
if(response){
|
||||
// 将 typeName 合并到每个 zoneMemberList 元素中,然后聚合到一个数组
|
||||
const list = response.data.rows && response.data.rows.reduce((acc, item) => {
|
||||
const members = (item.zoneMemberList || []).map(member => ({
|
||||
...member,
|
||||
typeName: item.typeName
|
||||
}));
|
||||
return acc.concat(members);
|
||||
}, []);
|
||||
setVIPLists(list);
|
||||
}
|
||||
})
|
||||
}, [id, searchValue])
|
||||
|
||||
const handleSearch = (value) => {
|
||||
setSearchValue(value);
|
||||
};
|
||||
|
||||
return <div className="ccf-vip-section">
|
||||
<div className="ccf-content-wrapper">
|
||||
<div className="main-title">
|
||||
{nsccTitle("核心执委 · 专家智库", 0.1, "", 6, "color-235")}
|
||||
</div>
|
||||
<div className="subtitle">
|
||||
汇聚国内200+开源领域专家、学者与技术领袖,共建开源智慧平台
|
||||
</div>
|
||||
<div className="ccf-content-animate">
|
||||
{/* 搜索框 */}
|
||||
<div className="ccf-vip-search">
|
||||
<Search
|
||||
placeholder="搜索专家姓名/职位/研究领域......"
|
||||
onSearch={handleSearch}
|
||||
size="large"
|
||||
allowClear
|
||||
className="ccf-vip-search-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{vipLists && vipLists.length > 0 ? (
|
||||
<div className="ccf-vip-grid">
|
||||
{vipLists.map((member, index) => (
|
||||
<div className="ccf-vip-card" key={member.id}>
|
||||
<div className="ccf-vip-avatar">
|
||||
<img src={member.imageUrl} alt={member.name} />
|
||||
</div>
|
||||
<div className="ccf-vip-name">{member.name}</div>
|
||||
<div className="ccf-vip-tags">
|
||||
<span className="ccf-vip-tag">{member.typeName}</span>
|
||||
<span className="ccf-vip-tag">{member.memberLevel}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
searchValue ? <Nodata _html="暂无匹配的专家"/> : <Nodata _html="暂无数据"/>
|
||||
)}
|
||||
{vipLists && vipLists.length > 0 && <div className="center mt50 mb40">
|
||||
<a className="ccf_block_btn" href="/zone/CCF-ODC/VIP">查看更多专家<i className="iconfont icon-jiantou3 font-14 ml10"></i></a>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Ccf_vip;
|
||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 576 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 852 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 234 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
|
@ -13,11 +13,11 @@ import { Anchor, Carousel } from 'antd';
|
|||
import { Base64 } from 'js-base64';
|
||||
import CountUp from 'react-countup';
|
||||
// 标题出场效果组件
|
||||
export const nsccTitle = (title, delay=0.1, classname="") => {
|
||||
export const nsccTitle = (title, delay=0.1, classname="", colorStartIndex=0, charClass="") => {
|
||||
if (!title) return null;
|
||||
return <div className={`project-cate-title ${classname}`}>
|
||||
{title.split("").map((char, index) => (
|
||||
<span key={index} className="title-char" style={{ animationDelay: `${delay + index * 0.08}s` }}>
|
||||
<span key={index} className={`title-char ${index >= colorStartIndex ? charClass : ''}`} style={{ animationDelay: `${delay + index * 0.08}s` }}>
|
||||
{char}
|
||||
</span>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React,{ useState , useEffect } from 'react';
|
||||
import { Breadcrumb, Button, Form, Input, message, Select } from 'antd';
|
||||
import React,{ useState , useEffect, useMemo } from 'react';
|
||||
import { Breadcrumb, Button, Form, Input, Select } from 'antd';
|
||||
import { getZoneUrl } from 'educoder';
|
||||
import MdEditor from '../../../modules/tpm/challengesnew/tpm-md-editor';
|
||||
import RichEditor from '../../Component/RichEditor';
|
||||
|
|
@ -15,18 +15,37 @@ function NewsCreate(props){
|
|||
const { getFieldDecorator, validateFields , setFieldsValue, setFields } = props.form;
|
||||
const { deptId, id: newsId } = props.match.params;
|
||||
const pathname = props.location.pathname;
|
||||
const [type, setType] = useState(new URLSearchParams(props.location.search.substring(1)).get('type'));
|
||||
const searchParams = new URLSearchParams(props.location.search.substring(1));
|
||||
|
||||
const [type, setType] = useState(searchParams.get('type'));
|
||||
const [value, setValue] = useState(undefined);
|
||||
const [imageUrl, setImageUrl] = useState(undefined)
|
||||
const [dirList, setDirList] = useState(undefined);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const sectionCmsTitle = props.data && props.data.sectionCmsTitle;
|
||||
|
||||
const defaultDirId = searchParams.get('dirId');
|
||||
const filedLabel = searchParams.get('formType');
|
||||
const dirIdDisabled = defaultDirId && filedLabel;
|
||||
|
||||
const layout = {
|
||||
labelCol: { span: 2 },
|
||||
wrapperCol: { span: 14 },
|
||||
};
|
||||
|
||||
const labelMap = useMemo(() => {
|
||||
// 优先使用自定义labelMap(数字key)
|
||||
if(filedLabel === "ccfActivity"){
|
||||
return {
|
||||
name: '活动名称',
|
||||
summary: "时间",
|
||||
keywords: "活动地点"
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}, [filedLabel]);
|
||||
|
||||
useEffect(()=>{
|
||||
if((role && role.role === "None") || (current_user && !current_user.login)){
|
||||
history.push(`/zone/${deptId}`);
|
||||
|
|
@ -37,6 +56,15 @@ function NewsCreate(props){
|
|||
id && getDircList();
|
||||
},[id])
|
||||
|
||||
useEffect(()=>{
|
||||
// 如果有默认dirId,设置到表单
|
||||
if (defaultDirId) {
|
||||
setTimeout(() => {
|
||||
setFieldsValue({ dirId: +defaultDirId });
|
||||
}, 200);
|
||||
}
|
||||
},[defaultDirId])
|
||||
|
||||
useEffect(()=>{
|
||||
if(pathname){
|
||||
if(pathname.endsWith("/edit")){
|
||||
|
|
@ -132,42 +160,42 @@ function NewsCreate(props){
|
|||
</Breadcrumb>
|
||||
<div className='formBox'>
|
||||
<Form {...layout} onSubmit={submit}>
|
||||
<Form.Item label="标题">
|
||||
<Form.Item label={labelMap.name || "标题"}>
|
||||
{getFieldDecorator("name", {
|
||||
rules:[{ required:true,message:"请输入文章标题!" },
|
||||
rules:[{ required:true,message:`请输入${labelMap.name || "文章标题"}!` },
|
||||
{type: 'string', max: 200, message: "最大长度200"},
|
||||
{pattern: /^[^<>[\]/\\]+$/, message: "不能输入特殊字符:< > [ ] / \\"}]
|
||||
})(
|
||||
<Input placeholder="请输入文章标题" maxLength={ 200 } width="220px"/>
|
||||
<Input placeholder={`请输入${labelMap.name || "文章标题"}`} maxLength={ 200 } width="220px"/>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item label="概览">
|
||||
<Form.Item label={labelMap.summary || "概览"}>
|
||||
{getFieldDecorator("summary", {
|
||||
rules:[{ required:true,message:"请输入概览!" },
|
||||
rules:[{ required:true,message:`请输入${labelMap.summary || "概览"}!` },
|
||||
{type: 'string', max: 200, message: "最大长度200"}]
|
||||
})(
|
||||
<Input.TextArea placeholder="请输入概览" maxLength={ 200 } width="220px"/>
|
||||
<Input.TextArea placeholder={`请输入${labelMap.summary || "概览"}`} maxLength={ 200 } width="220px"/>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item label="栏目">
|
||||
<Form.Item label={labelMap.dirId || "栏目"}>
|
||||
{getFieldDecorator("dirId", {
|
||||
rules:[{ required:true,message:"请选择文章所属栏目!" }]
|
||||
})(
|
||||
<Select placeholder="请选择文章所属栏目">
|
||||
<Select placeholder="请选择文章所属栏目" disabled={dirIdDisabled}>
|
||||
{dirList && dirList.map(item=>{
|
||||
return <Select.Option key={item.id} value={item.id}>{item.name}</Select.Option>
|
||||
})}
|
||||
</Select>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item label="标签">
|
||||
<Form.Item label={labelMap.keywords || "标签"}>
|
||||
{getFieldDecorator("keywords", {
|
||||
rules:[{type: 'string', max: 30, message: "请精简文章标签,最大长度30"}],
|
||||
})(
|
||||
<Input placeholder="请输入关键词作为文章标签,使用逗号分隔" maxLength={ 30 } width="220px"/>
|
||||
<Input placeholder={`${labelMap.keywords ? `请输入${labelMap.keywords}` : "请输入关键词作为文章标签,使用逗号分隔"}`} maxLength={ 30 } width="220px"/>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item label="内容" wrapperCol={{span: 21}}>
|
||||
<Form.Item label={labelMap.content || "内容"} wrapperCol={{span: 21}}>
|
||||
{getFieldDecorator("content",{
|
||||
rules:[{required:true,message:"请输入内容!"}],
|
||||
})(
|
||||
|
|
@ -181,7 +209,7 @@ function NewsCreate(props){
|
|||
></MdEditor>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item label="首图" extra="建议图片比例,长:宽为3:2,上传jpg、jpeg、webp、png、svg、gif、bmp格式的图片">
|
||||
<Form.Item label={labelMap.headImg || "首图"} extra="建议图片比例,长:宽为3:2,上传jpg、jpeg、webp、png、svg、gif、bmp格式的图片">
|
||||
<UploadImage maxSize={ 5 } getImageUrl={ (url) => setImageUrl(url) } url={ imageUrl ? imageUrl : undefined} action={ getZoneUrl(`/api/file/common/upload`) } />
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ offset: 2 }}>
|
||||
|
|
|
|||
|
|
@ -13,9 +13,13 @@ import AddProjects from '../Component/addProjects';
|
|||
import { Link } from 'react-router-dom';
|
||||
const { Search } = Input;
|
||||
|
||||
function ProjectSource(props){
|
||||
function ProjectSource(props){
|
||||
const { deptId } = props.match.params;
|
||||
const [ typeId , setTypeId ] = useState(0);
|
||||
// 从路由 state 中获取默认项目分类 id(不显示在URL中)
|
||||
const getDefaultTypeId = () => {
|
||||
return props.location?.state?.id ? Number(props.location.state.id) : 0;
|
||||
};
|
||||
const [ typeId , setTypeId ] = useState(getDefaultTypeId());
|
||||
const [ typeList , setTypeList ] = useState(undefined);
|
||||
const [ lists , setLists ] = useState(undefined);
|
||||
const [ page, setPage ] = useState(1);
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -71,7 +71,7 @@ const HeaderPageZone1 = Loadable({
|
|||
});
|
||||
|
||||
const HeaderPageCCF = Loadable({
|
||||
loader : () => import("./Pages/headerPagebyCCF"),
|
||||
loader : () => import("./Pages/homepage/ccf"),
|
||||
loading : Loading,
|
||||
});
|
||||
|
||||
|
|
@ -397,7 +397,7 @@ function Index(props){
|
|||
<Route
|
||||
path="/zone/:deptId"
|
||||
render={(p) => (
|
||||
deptId==="CCF-ODC" ? <HeaderPageCCF {...props} {...p} data={data} id={id} temp={ temp } />:
|
||||
deptId==="CCF-ODC" ? <HeaderPageCCF {...props} {...p} data={data} id={id} role={role}/>:
|
||||
deptId === "JCC" ? <HomePageByJCC {...props} {...p} data={data} id={id}/>:
|
||||
deptId === "uos" ? <HomePageByUos {...props} {...p} data={data} id={id}/>:
|
||||
deptId==="xjykyzx" ? <HeaderPageKyzx {...props} {...p} data={data} id={id} temp={ temp } />:
|
||||
|
|
|
|||
|
|
@ -379,6 +379,59 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.bannerByCCF{
|
||||
height: 560px;
|
||||
.main_t{
|
||||
font-size:86px;
|
||||
color:#ffffff;
|
||||
font-family: "sanjikai";
|
||||
}
|
||||
.sub_t{
|
||||
font-size: 18px;
|
||||
}
|
||||
.bannerMenus{
|
||||
backdrop-filter:blur(44px);
|
||||
height:60px;
|
||||
|
||||
.ant-menu li{
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
|
||||
a{
|
||||
font-size: 15px;
|
||||
color:#cdd4f4 !important;
|
||||
|
||||
img{
|
||||
width: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.slick-track{
|
||||
height: 560px;
|
||||
}
|
||||
.slick-dots{
|
||||
li{
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #ffffff43;
|
||||
border-radius: 12px;
|
||||
}
|
||||
li.slick-active{
|
||||
width:59px;
|
||||
button{
|
||||
background: #ffffff43;
|
||||
}
|
||||
}
|
||||
li.slick-active::after{
|
||||
width:59px;
|
||||
height:10px;
|
||||
background:linear-gradient(267.49deg,#c19937 0.57%,#64d6f8 89.68%);
|
||||
border-radius:10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.main_content{
|
||||
padding: 40px 0px 80px;
|
||||
|
|
@ -2466,6 +2519,7 @@
|
|||
|
||||
.fadeInChar {
|
||||
.title-char {
|
||||
min-width: 10px;
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
animation: fadeInCharZone 0.5s ease-out forwards;
|
||||
|
|
|
|||