InitData组件:import { useEffect } from 'react';
import PropTypes from 'prop-types';
import { getRecommendSlot, getRacesBrandPageList, getDomainsList } from '@src/h5/routes/contest/home/thunk';
function InifData(props) {
const { setDynamicList, setDynamicData, setBannerData, setContestSoltList, setQuestionSoltList, refresh } = props;
const { setTechnicalList, setBrandList } = props;
const handSoltResult = (result) => {
result.forEach((item) => {
switch (item.slot) {
case 'competitionTopBanner':
setBannerData(item.contents || []);
break;
case 'hotFeed':
setDynamicData(item || {});
setDynamicList(item.contents || []);
break;
case 'hotCompetition':
setContestSoltList(item.contents || []);
break;
case 'hotQuestion':
setQuestionSoltList(item.contents || []);
break;
default:
break;
}
});
};
const getSoltData = async () => {
// 获取列表的推荐位
let slot = 'competitionTopBanner,hotFeed,hotCompetition,hotQuestion';
const params = { slot, size: 50, current: 1 };
const res = await getRecommendSlot(params);
if (res && res.code === '0' && res.data && res.data.length > 0) {
handSoltResult(res.data);
}
};
const getTechnicalList = async () => {
const res = await getDomainsList();
if (res && res.code === '0' && res.data) {
let records = res.data.records || [];
setTechnicalList(records);
}
};
const getBrandList = async () => {
const res = await getRacesBrandPageList();
if (res?.records) {
let records = res.records || [];
setBrandList(records);
}
};
useEffect(() => {
// 获取推荐位数据
getSoltData();
// 获取技术领域列表
getTechnicalList();
// 获取赛事品牌列表
getBrandList();
}, []);
useEffect(() => {
if (!refresh) return;
getSoltData(); // 下拉刷新更新
}, [refresh]);
return null;
}
InifData.propTypes = {
setDynamicList: PropTypes.func,
setBannerData: PropTypes.func,
setContestSoltList: PropTypes.func,
setQuestionSoltList: PropTypes.func,
refresh: PropTypes.bool,
setTechnicalList: PropTypes.func,
setBrandList: PropTypes.func,
};
export default InifData;
BannerSlots组件:/**
* H5大图轮播
*/
import PropTypes from 'prop-types';
import './index.less';
import { Carousel } from 'antd';
import listDefaultImg from '@src/assets/common/listDefaultImg.svg'; // 灰色裂图
import defaultCover from '@src/assets/common/defaultCover.svg'; // 蓝色黄大年茶思屋16:9
import { aLinkProps, filterUri } from '@src/utils/common';
import { redirectNewCon } from '@src/h5/utils/appCommon';
import { getThumbnail } from '@src/utils/media';
export default function BannerSlots(props) {
const { bannerData } = props;
if (!bannerData || bannerData.length === 0) {
return null;
}
const sortAndLimit = (data) => {
const sortedData = data.sort((a, b) => b.sort - a.sort);
return sortedData.slice(0, 10);
};
const bannerList = sortAndLimit(bannerData);
return (
<div className="csw-opensource-carousel">
<div className="csw-opensource-carousel-lists">
<Carousel repeat={true} {...{ autoplay: true }} autoplaySpeed={5000}>
{bannerList.map((item, index) => {
const cover = item?.appCover;
const title = item?.customTitle?.zh;
const onClick = (event) => {
event.stopPropagation();
const url = filterUri(item?.customLink);
if (!url) return;
redirectNewCon(url);
};
return (
<a key={index} {...{ ...aLinkProps, onClick, title }}>
<img
src={getThumbnail(cover, {
width: window.innerWidth - 48,
height: (window.innerWidth - 48)/2,
}) || defaultCover}
style={!cover ? { objectFit: 'cover' } : null}
{...{ onError: (e) => (e.target.src = listDefaultImg) }}
/>
</a>
);
})}
</Carousel>
</div>
</div>
);
}
BannerSlots.propTypes = {
bannerData: PropTypes.array,
};
DynamicSlots组件:/**
* H5开源动态
*/
import PropTypes from 'prop-types';
import './index.less';
import { getNls, locale } from '@src/utils/i18n';
import { filterUri } from '@src/utils/common';
import popularConsultations from '../../assets/popularConsultations.svg';
import News from '../../assets/News.svg';
import { RightOutlined } from '@ant-design/icons';
import listDefaultImg from '@src/assets/common/listDefaultImg.svg'; // 灰色裂图
import blackDots from '../../assets/blackDots.svg';
import { redirectNewCon } from '@src/h5/utils/appCommon';
import constants from '@src/configs/constants';
const dict = getNls('routes_openSource_modules_dynamic');
const contest_home = getNls('routes_h5_contest_home');
export default function DynamicSlots(props) {
const { dynamicData, dynamicList } = props;
if (!dynamicList || dynamicList.length === 0) {
return null;
}
const maxLimit = 4;
const onClick = (title, index, url) => {
redirectNewCon(url);
};
return (
<div className="csw-opensource-h5-dynamic">
<div className="csw-opensource-h5-dynamicHeader">
<img
src={locale === 'zh' ? popularConsultations : News}
className="csw-opensource-h5-dynamicHeader-title"
{...{ onError: (e) => (e.target.src = listDefaultImg) }}
/>
{dynamicData?.url && (
<div
onClick={() => redirectNewCon(filterUri(dynamicData?.url))}
className="csw-opensource-h5-dynamicHeader-more"
>
<span className="csw-opensource-h5-dynamicHeader-moreText">{dict.more}</span>
<RightOutlined />
</div>
)}
</div>
<ul>
{dynamicList.map((item, idx) => {
if (idx < maxLimit) {
const title = item?.customTitle?.[locale] || item?.title;
let detailPageUrl = '';
const { columnType, contentId, customLink } = item;
if (customLink) {
detailPageUrl = customLink;
} else {
if (['developer', 'DEVELOPER'].includes(item?.ext?.origin)) {
const TYPE = {
racesQuestion: 'question',
races: 'competition',
};
detailPageUrl = constants.racesDetail
.replace(':type', TYPE[columnType])
.replace(':contentId', contentId);
} else {
if (columnType) {
const ROUTE = {
racesQuestion: 'competitionsDetail',
races: 'contestsDetail',
};
const type = ROUTE[columnType];
detailPageUrl = constants[type].replace(':id', contentId);
}
}
}
const href = filterUri(detailPageUrl);
return (
<li key={idx} className="csw-contest-popular-rightContentItem">
<img src={blackDots} />
<span
className="csw-contest-popular-singleItem h5ap-font-14i"
{...{ onClick: () => onClick(title, idx, href) }}
>
{title}
</span>
</li>
);
}
return null;
})}
</ul>
</div>
);
}
DynamicSlots.propTypes = {
dynamicList: PropTypes.array,
};
RacesSlots组件:/* eslint-disable react/jsx-no-useless-fragment */
/**
* H5_热门赛事
*/
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import { Carousel } from 'antd';
import { JumboTabs } from 'antd-mobile';
import './index.less';
import { getNls, locale } from '@src/utils/i18n';
import { aLinkProps, filterUri } from '@src/utils/common';
import { RightOutlined } from '@ant-design/icons';
import listDefaultImg from '@src/assets/common/listDefaultImg.svg';
import defaultCoverSvg from '@src/assets/common/defaultCover.svg';
import { redirectNewCon } from '@src/h5/utils/appCommon';
import constants from '@src/configs/constants';
import { getThumbnail } from '@src/utils/media';
const dict = getNls('routes_h5_contest_home');
const onError = (e) => {
let img = e.target;
img.src = defaultCoverSvg;
img.onError = null;
img.setAttribute('style', 'object-fit:cover');
};
function RacesSlots(props) {
const { contestSoltList, questionSoltList } = props;
const [onKey, setOnKey] = useState('');
if (contestSoltList?.length === 0 && questionSoltList?.length === 0) return null;
const imgRedirect = (e, item) => {
let url = '';
const { columnType, contentId, customLink } = item;
if (customLink) {
url = customLink;
} else {
if (['developer', 'DEVELOPER'].includes(item?.ext?.origin)) {
const TYPE = {
racesQuestion: 'question',
races: 'competition',
};
url = constants.racesDetail.replace(':type', TYPE[columnType]).replace(':contentId', contentId);
} else {
if (columnType) {
const ROUTE = {
racesQuestion: 'competitionsDetail',
races: 'contestsDetail',
};
const type = ROUTE[columnType];
url = constants[type].replace(':id', contentId);
}
}
}
if (!url) return;
e.stopPropagation();
redirectNewCon(url);
};
const courseSlotsList = (list) => {
return list
?.map((item1, i) => {
if (i % 2 === 0 && i < list.length - 1) {
const item2 = list[i + 1];
return (
<div className="topSummitsList" key={`openSource-course${i}`}>
<a
key={i}
className="topSummitsItem"
{...{ ...aLinkProps }}
{...{
onClick: (e) => {
imgRedirect(e, item1);
},
}}
>
<img src={getThumbnail(item1?.appCover, {
width: Math.ceil((window.innerWidth - 48) / 2),
height: Math.ceil((window.innerWidth - 48) / 4), //比例为2:1
}) || defaultCoverSvg} {...{ onError }} />
<div className="mask" />
<div className="lineLimit2 topSummitsItemTitle h5ap-font-14i">{item1?.customTitle?.[locale]}</div>
<div className="lineLimit1 itemSubTitle h5ap-font-12i">{item1?.description?.[locale]}</div>
</a>
<a
key={i + 1}
className="topSummitsItem"
{...{ ...aLinkProps }}
{...{
onClick: (e) => {
imgRedirect(e, item2);
},
}}
>
<img src={getThumbnail(item2?.appCover, {
width: Math.ceil((window.innerWidth - 48) / 2),
height: Math.ceil((window.innerWidth - 48) / 4), //比例为2:1
}) || defaultCoverSvg} {...{ onError }} />
<div className="mask" />
<div className="lineLimit2 topSummitsItemTitle h5ap-font-14i">{item2?.customTitle?.[locale]}</div>
<div className="lineLimit1 itemSubTitle h5ap-font-12i">{item2?.description?.[locale]}</div>
</a>
</div>
);
}
if (list.length === 1) {
let aProps = {
className: 'topSummitsItem',
onClick: (e) => {
imgRedirect(e, item1);
},
...aLinkProps,
};
return (
<div className="topSummitsList" key={`openSource-course${i}`}>
<a key={i} {...aProps}>
<img src={getThumbnail(item1?.appCover, {
width: Math.ceil((window.innerWidth - 48) / 2),
height: Math.ceil((window.innerWidth - 48) / 4), //比例为2:1
}) || defaultCoverSvg} {...{ onError }} />
<div className="mask" />
</a>
</div>
);
}
return null;
})
.filter(Boolean);
};
const link = constants.moreEvents;
const moreDom = () => {
const jumpMore = () => {
redirectNewCon(filterUri(link));
};
return (
<div className="moreDomCss" onClick={jumpMore}>
<span className="moreText">{dict.more}</span>
<RightOutlined />
</div>
);
};
const jumboTabsOnChange = (val) => {
setOnKey(val);
};
return (
<div className="csw-h5-contest-racesSlots">
<div className="csw-h5-contest-racesSlots-content">
<JumboTabs defaultActiveKey="question" onChange={jumboTabsOnChange}>
{questionSoltList?.length > 0 ? (
<JumboTabs.Tab
key="question"
title={
<span className={`${locale === 'zh' ? 'h5ap-font-16' : 'h5ap-font-14'}`}>
{dict.hotCompetitionQuestions}
</span>
}
>
<Carousel className="csw-h5-contest-topSummitsCarousel" {...{ autoplay: false, infinite: false }}>
{courseSlotsList(questionSoltList)}
</Carousel>
</JumboTabs.Tab>
) : null}
{contestSoltList?.length > 0 ? (
<>
<JumboTabs.Tab
key="constest"
title={
<span className={`${locale === 'zh' ? 'h5ap-font-16' : 'h5ap-font-14'}`}>{dict.popularEvents}</span>
}
>
<Carousel className="csw-h5-contest-topSummitsCarousel" {...{ autoplay: false, infinite: false }}>
{courseSlotsList(contestSoltList)}
</Carousel>
</JumboTabs.Tab>
</>
) : null}
</JumboTabs>
{onKey === 'constest' && moreDom()}
</div>
</div>
);
}
RacesSlots.propTypes = {
contestSoltList: PropTypes.array,
questionSoltList: PropTypes.array,
};
export default RacesSlots;
BetaSlots组件:/**
* 【竞赛】---首页---我要做题
*/
import { useEffect, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { getNls } from '@src/utils/i18n';
import { CapsuleTabs, InfiniteScroll, List, DotLoading } from 'antd-mobile';
import FilterFav from './modules/FilterNav';
import SportingEventItem from '@src/h5/components/FilterList/modules/SportingEventItem/index.js';
import { racesList } from '@src/h5/routes/contest/detail/Races/thunk.js';
import { getScrollTopFun } from '@src/h5/components/FilterNav/modules/FilterFields/modules/DrawerContent/common.js';
import { createPortal } from 'react-dom';
import useAppTopVisibility from '@src/utils/hooks/useAppTopVisibility';
import { terminalInfo } from '@src/utils/utils';
import './index.less';
const dict = getNls('routes_h5_contest_home');
const menusInit = [
// 我要做题
{
label: <span className="h5ap-font-16">{dict.dealProblem}</span>,
tabKey: 'question',
},
// 我要参赛
{
label: <span className="h5ap-font-16">{dict.joinCompete}</span>,
tabKey: 'competition',
},
];
// 筛选条件
let raceStatusType = -1;
let brandIds = [];
let domainIds = [];
export default function BetaSlots(props) {
const { current, setCurrent, refresh, setRefresh, setTotal, pages, setPages } = props;
const key = window.sessionStorage.getItem('contestTabKey');
const [tabKey, setTabKey] = useState(key || 'question'); // 主页的tabs id
const [list, setList] = useState([]); // 展示的竞赛列表
const [hasMore, setHasMore] = useState(false); // 是否还有更多数据
const [sortBy, setSortBy] = useState(null);
const [isFilterList, setIsFilterList] = useState(false);
const [filterListParams, setFilterListParams] = useState(null);
const [showLoading, setShowLoading] = useState(false);
const portalRoot = useRef(null);
const { isAppTopAlive } = useAppTopVisibility();
const { isApp } = terminalInfo();
// 获取列表数据
const getData = async (val, current = 1) => {
setShowLoading(true);
setRefresh(false);
try {
const isQuestionTab = tabKey === 'question';
const PAGE_SIZE = 20;
const params = {
pageNo: current,
pageSize: PAGE_SIZE,
type: isQuestionTab ? 'QUESTION' : 'COMPETITION',
technologyDomainIds: val?.domainIds?.length ? val.domainIds : [],
competitionBrandIds: val?.brandIds?.length ? val.brandIds : [],
order: sortBy !== null ? Number(sortBy) : '',
questionStatus: isQuestionTab ? val?.questionStatus : '',
competitionStatus: !isQuestionTab ? val?.competitionStatus : '',
};
const res = await racesList(params);
setShowLoading(false);
if (res?.records) {
const { records, total } = res;
const calculatedTotalPages = Math.ceil(total / PAGE_SIZE);
setCurrent(current);
setTotal(total || 0);
setPages(calculatedTotalPages);
setHasMore(current < calculatedTotalPages);
setList((prevList) => (current === 1 ? [...records] : [...prevList, ...records]));
} else {
setList([]);
setTotal(0);
setPages(0);
setHasMore(false);
}
} catch (error) {
setShowLoading(false);
setList([]);
setTotal(0);
setPages(0);
setHasMore(false);
}
};
// 清空筛选
const clearFilter = () => {
raceStatusType = -1;
brandIds = [];
domainIds = [];
};
// 筛选确认
const handleFilterSubmit = async (val) => {
setFilterListParams(val);
getData(val);
};
// 切换tab
const onChange = (key) => {
setHasMore(false); // 防止切换时候加载两次接口
clearFilter();
setTabKey(key);
window.sessionStorage.setItem('contestTabKey', key);
getScrollTopFun(props, isAppTopAlive);
};
// 上拉加载更多
const loadMore = () => {
if (hasMore && current < pages) {
setHasMore(false);
getData(filterListParams, current + 1);
}
};
// 监听切换tab、按时间按热度
useEffect(() => {
setIsFilterList(false);
setSortBy(null);
if (!sortBy) {
getData();
}
}, [tabKey]);
useEffect(() => {
getData();
}, [sortBy]);
// 监听下拉刷新
useEffect(() => {
if (refresh) {
clearFilter();
getData();
}
}, [refresh]);
useEffect(() => {
const root = document.createElement('div');
root.id = 'global-loading-container1';
portalRoot.current = root;
document.body.appendChild(root);
return () => {
document.body.removeChild(root);
};
}, []);
const renderDotLoading = () => {
return createPortal(<DotLoading color="primary" className="dotLoadingCss" />, portalRoot.current);
};
const filterProps = {
...props,
handleFilterSubmit,
sortBy,
setSortBy,
tabKey,
refresh,
isFilterList,
setIsFilterList,
getData,
};
const InfiniteScrollContent = ({ hasMore }) => {
return (
<>
{hasMore ? (
<>
<span>Loading...</span>
<DotLoading />
</>
) : (
<span>{dict.finishedText}</span>
)}
</>
);
};
let tabsTop = '';
if (isApp) {
tabsTop = '0';
} else {
if (isAppTopAlive) {
tabsTop = '158px';
} else {
tabsTop = '88px';
}
}
return (
<div className="csw-betaSlots-h5-beta" id="csw-h5list-scrollWarp">
<CapsuleTabs
style={{ top: tabsTop }}
defaultActiveKey={tabKey}
{...{ onChange, className: 'csw-betaSlots-h5-dateTab' }}
>
{menusInit.map((item) => {
return <CapsuleTabs.Tab title={item?.label} key={item?.tabKey} />;
})}
</CapsuleTabs>
<div>
<FilterFav {...filterProps} />
{showLoading && renderDotLoading()}
<List className="csw-betaSlots-h5-list">
{list?.map((data, index) => {
return <SportingEventItem key={data.id} {...{ ...props, dict, data }} {...filterProps} />;
})}
</List>
<InfiniteScroll loadMore={loadMore} hasMore={hasMore}>
<InfiniteScrollContent hasMore={hasMore} />
</InfiniteScroll>
</div>
</div>
);
}
BetaSlots.propTypes = {
current: PropTypes.number,
setCurrent: PropTypes.func,
refresh: PropTypes.bool,
setRefresh: PropTypes.func,
setTotal: PropTypes.func,
pages: PropTypes.number,
setPages: PropTypes.func,
};