ccf专区首页

This commit is contained in:
谢思 2026-07-20 16:04:35 +08:00
parent 27e5336c3b
commit 1ffbfa2ea9
53 changed files with 1990 additions and 23 deletions

View File

@ -1457,6 +1457,10 @@ a.edu-txt-w80,
font-size: 36px !important
}
.font-38 {
font-size: 38px !important
}
.font-40 {
font-size: 40px !important
}

View File

@ -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' &&

View File

@ -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 Observerref
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;

View File

@ -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; }

View File

@ -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
// sectionListname""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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 22
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;

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -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>
))}

View File

@ -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(() => {
// 使labelMapkey
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="建议图片比例宽为32上传jpg、jpeg、webp、png、svg、gif、bmp格式的图片">
<Form.Item label={labelMap.headImg || "首图"} extra="建议图片比例宽为32上传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 }}>

View File

@ -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 idURL
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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -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 } />:

View File

@ -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;