效果
ps1:点击图片打开的页面(GoodsInfo组件),这里不提供。自行根据业务需求修改即可
ps2:该项目使用了 reactHooks dedux ts @根目录配置等。具体配置可以看 我的这篇文章 快速搭建react18.2+TS框架(包含redux,sass,@根目录等有用配置)
废话不说,上代码
tsx:
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import styles from "./index.module.scss";
import { useDispatch, useSelector } from "react-redux";
import { A2_APIgetGoodsList, A2_APIgetSelectData } from "@/store/action/A2Main";
import { A2GoodsType, A2getGoodsDataType } from "@/types";
import { RootState } from "@/store";
// 这里使用了自己封装的图片懒加载组件,可以自行替换
import ImageLazy from "@/ImageLazy";
import { Input, Select } from "antd";
import classNames from "classnames";
import GoodsInfo from "../GoodsInfo";
import { CloseOutlined } from "@ant-design/icons";
import curImg from "@/assets/img/goods/cur.png";
import curAcImg from "@/assets/img/goods/curAc.png";
function GoodsSw() {
// 滚轮提示
const [curTit, setCurTit] = useState(true);
const curTimeRef = useRef(0);
const [curCut, setCurCut] = useState(true);
useEffect(() => {
if (!curTit) {
localStorage.setItem("YPZZ_CUR", "1");
clearInterval(curTimeRef.current);
}
}, [curTit]);
useEffect(() => {
// 开启滚轮提示 图片更换 定时器
clearInterval(curTimeRef.current);
curTimeRef.current = window.setInterval(() => {
setCurCut(!curCut);
}, 2000);
}, [curCut]);
useEffect(() => {
const isFlag = localStorage.getItem("YPZZ_CUR");
if (isFlag) setCurTit(false);
}, []);
const dispatch = useDispatch();
useEffect(() => {
// 发送请求拿到下拉框数据
dispatch(A2_APIgetSelectData("age"));
dispatch(A2_APIgetSelectData("texture"));
}, [dispatch]);
// 获取下拉数据
const selectData = useSelector((state: RootState) => state.A2Main.selectData);
// 第一次不显示暂无信息
const [flagOne, setFlagOne] = useState(false);
useEffect(() => {
window.setTimeout(() => {
setFlagOne(true);
}, 500);
}, []);
// 发送请求函数
const [getData, setGetData] = useState<A2getGoodsDataType>({
dictAge: "",
dictTexture: "",
searchKey: "",
pageNum: 1,
pageSize: 9999,
});
const getListFu = useCallback(async () => {
await dispatch(A2_APIgetGoodsList(getData));
}, [dispatch, getData]);
// 从仓库获取数据
const goodsListAll = useSelector(
(state: RootState) => state.A2Main.goodsList
);
// 分为上下2个模块
const list1 = useMemo(() => {
const arr = [] as A2GoodsType[];
goodsListAll.forEach((v, i) => {
if (i % 2 === 0) arr.push(v);
});
if (arr.length > 4) {
for (let i = 0; i < 4; i++) {
arr.push(arr[i]);
}
}
return arr;
}, [goodsListAll]);
// 下面的数组
const list2 = useMemo(() => {
const arr = [] as A2GoodsType[];
goodsListAll.forEach((v, i) => {
if (i % 2 !== 0) arr.push(v);
});
if (arr.length > 4) {
for (let i = 0; i < 4; i++) {
arr.push(arr[i]);
}
}
return arr;
}, [goodsListAll]);
useEffect(() => {
// 发送请求
getListFu();
}, [getListFu]);
// 名称的输入
const nameTime = useRef(-1);
const nameChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
clearTimeout(nameTime.current);
nameTime.current = window.setTimeout(() => {
setGetData({
...getData,
searchKey: e.target.value,
});
}, 500);
},
[getData]
);
// -----点击了单个藏品打开的页面----------
const [open, setOpen] = useState(false);
const openRef = useRef(0);
// 第一个盒子的总宽度
const goddsSw1 = useMemo(() => {
return window.innerWidth * 0.25 * list1.length;
}, [list1.length]);
// 4个定时器
const timeS1 = useRef(-1);
const timeS2 = useRef(-1);
const time1 = useRef(-1);
const time2 = useRef(-1);
// 上面的开始自动滚动
const scroolFu1 = useCallback(
(dom: HTMLDivElement, num: number, time: number) => {
clearTimeout(timeS1.current);
timeS1.current = window.setTimeout(() => {
clearInterval(time1.current);
let numAuto = num;
time1.current = window.setInterval(() => {
numAuto += 1;
if (dom.scrollLeft + dom.clientWidth >= dom.scrollWidth) {
numAuto = 0;
}
dom.scrollLeft = numAuto;
}, 20);
}, time);
},
[]
);
// 清理第一个定时器
const colseTime1 = useCallback(() => {
clearTimeout(timeS1.current);
clearInterval(time1.current);
const dom = document.querySelector("#goddsSwBox1") as HTMLDivElement;
if (dom && list1.length > 4 && !open) {
scroolFu1(dom, dom.scrollLeft, 3000);
}
}, [list1, open, scroolFu1]);
// 下面的开始自动滚动
const scroolFu2 = useCallback(
(dom: HTMLDivElement, num: number, time: number) => {
clearTimeout(timeS2.current);
timeS2.current = window.setTimeout(() => {
clearInterval(time2.current);
let numAuto = num;
dom.scrollLeft = numAuto;
time2.current = window.setInterval(() => {
numAuto -= 1;
if (numAuto <= 0) {
numAuto = dom.scrollWidth - dom.clientWidth;
}
dom.scrollLeft = numAuto;
}, 20);
}, time);
},
[]
);
// 清理第二个定时器
const closeTime2 = useCallback(() => {
clearTimeout(timeS2.current);
clearInterval(time2.current);
const dom = document.querySelector("#goddsSwBox2") as HTMLDivElement;
if (dom && list2.length > 4 && !open) {
scroolFu2(dom, dom.scrollLeft, 3000);
}
}, [list2, open, scroolFu2]);
// 上面的数组改变了数据
useEffect(() => {
if (list1.length > 4) {
const dom = document.querySelector("#goddsSwBox1") as HTMLDivElement;
// 监听鼠标滚轮
dom.onwheel = (e) => {
// 使用过滚轮
setCurTit(false);
e.preventDefault();
const num = dom.scrollLeft;
if (dom.scrollLeft + dom.clientWidth >= dom.scrollWidth)
dom.scrollLeft = 0;
// else if (num <= 0) dom.scrollLeft = dom.scrollWidth - dom.clientWidth;
else dom.scrollLeft = num + e.deltaY;
colseTime1();
};
// 开启定时器
scroolFu1(dom, 0, 1000);
} else colseTime1();
}, [colseTime1, list1, scroolFu1]);
// 下面的数组改变了数据
useEffect(() => {
if (list2.length > 4) {
const dom = document.querySelector("#goddsSwBox2") as HTMLDivElement;
// 初始滚动到末尾
dom.scrollLeft = dom.scrollWidth - dom.clientWidth;
// 监听鼠标滚轮
dom.onwheel = (e) => {
// 使用过滚轮
setCurTit(false);
e.preventDefault();
const num = dom.scrollLeft;
if (num <= 0) dom.scrollLeft = dom.scrollWidth - dom.clientWidth;
// else if (dom.scrollLeft + dom.clientWidth >= dom.scrollWidth)
// dom.scrollLeft = 0;
else dom.scrollLeft = num + e.deltaY;
closeTime2();
};
// 开启定时器
scroolFu2(dom, dom.scrollWidth - dom.clientWidth, 1000);
} else closeTime2();
}, [closeTime2, list2, scroolFu2]);
// 第二个盒子的总宽度
const goddsSw2 = useMemo(() => {
return window.innerWidth * 0.25 * list2.length;
}, [list2.length]);
// 点击藏品
const clickGoodFu = useCallback((id: number) => {
openRef.current = id;
setOpen(true);
// console.log(123, id);
}, []);
// 打开了单个文物详情页,关闭之后的逻辑
useEffect(() => {
if (open) {
clearTimeout(timeS1.current);
clearInterval(time1.current);
clearTimeout(timeS2.current);
clearInterval(time2.current);
} else if (openRef.current) {
const dom1 = document.querySelector("#goddsSwBox1") as HTMLDivElement;
const dom2 = document.querySelector("#goddsSwBox2") as HTMLDivElement;
scroolFu1(dom1, dom1.scrollLeft, 3000);
scroolFu2(dom2, dom2.scrollLeft, 3000);
}
}, [open, scroolFu1, scroolFu2]);
// 离开页面的时候清理4个定时器
useEffect(() => {
return () => {
clearTimeout(timeS1.current);
clearTimeout(timeS2.current);
clearInterval(time1.current);
clearInterval(time2.current);
// 清理 滚轮提示 定时器
clearInterval(curTimeRef.current);
};
}, []);
return (
<div className={styles.GoodsSw}>
{/* 轮播图主体 */}
{goodsListAll.length <= 0 ? (
<div className="noInfo" hidden={!flagOne}>
暂无更多内容
</div>
) : (
<div className="goddsBox">
{/* 第一个自动播发的盒子 */}
<div
id="goddsSwBox1"
className="goddsSwBox"
onClick={() => colseTime1()}
// onMouseLeave={() => auto1Fu()}
>
<div className="goddsSw" style={{ width: goddsSw1 + "px" }}>
{list1.map((v, i) => (
<div key={i} className="goddsRow">
<div className="name">
{v.name.length > 10
? v.name.substring(0, 10) + "..."
: v.name}
</div>
<div
title={v.name}
className="goddsImg"
onClick={() => clickGoodFu(v.id)}
>
<ImageLazy
src={v.thumb}
width="100%"
height="100%"
noLook
/>
</div>
</div>
))}
</div>
</div>
{/* 第二个自动播放的盒子 */}
<div
id="goddsSwBox2"
className="goddsSwBox goddsSwBox2"
onClick={() => closeTime2()}
// onMouseLeave={() => auto2Fu()}
>
<div className="goddsSw" style={{ width: goddsSw2 + "px" }}>
{list2.map((v, i) => (
<div key={i} className="goddsRow" title={v.name}>
<div className="name">
{v.name.length > 10
? v.name.substring(0, 10) + "..."
: v.name}
</div>
<div className="goddsImg" onClick={() => clickGoodFu(v.id)}>
<ImageLazy
src={v.thumb}
width="100%"
height="100%"
noLook
/>
</div>
</div>
))}
</div>
</div>
</div>
)}
{/* 下面的筛选框 */}
<div className="searchBox">
<div className="searRow">
<Select
placeholder="种类"
style={{ width: 150 }}
options={selectData["texture"]}
onChange={(e) => setGetData({ ...getData, dictTexture: e })}
/>
</div>
<div className="searRow">
<Select
placeholder="年代"
style={{ width: 150 }}
options={selectData["age"]}
onChange={(e) => setGetData({ ...getData, dictAge: e })}
/>
</div>
<div className="searRow">
<Input
maxLength={25}
style={{ width: 220 }}
placeholder="请输入关键词"
allowClear
onChange={(e) => nameChange(e)}
/>
</div>
</div>
{/* 单个藏品详情盒子 */}
{/* ***********这部分代码和页面自行准备 和 处理*********** */}
<div className={classNames("goodsInfoBox", open ? "goodsInfoBoxAc" : "")}>
{open ? (
<GoodsInfo
isOpen={false}
id={openRef.current}
closePage={() => setOpen(false)}
/>
) : null}
</div>
{/* 滚轮提示 */}
{curTit ? (
<div className="curTitBox" hidden={open}>
<div className="curClose" onClick={() => setCurTit(false)}>
<CloseOutlined />
</div>
<div className="curImg">
<img src={curImg} alt="" style={{ opacity: curCut ? 1 : 0 }} />
<img src={curAcImg} alt="" style={{ opacity: curCut ? 0 : 1 }} />
</div>
<p>鼠标滚轮可控制图片滚动</p>
</div>
) : null}
</div>
);
}
const MemoGoodsSw = React.memo(GoodsSw);
export default MemoGoodsSw;
scss:
.GoodsSw {
height: calc(100% - 100px);
:global {
.noInfo {
height: calc(100% - 100px);
display: flex;
justify-content: center;
align-items: center;
color: var(--themeColor);
font-size: 40px;
padding-top: 100px;
}
.goddsBox {
height: calc(100% - 100px);
// 第一个自动播发的盒子
.goddsSwBox {
height: calc(50% - 20px);
border-bottom: 1px solid rgba(231, 219, 188, 0.50);
position: relative;
width: 100%;
overflow-x: auto;
&::-webkit-scrollbar {
/*滚动条整体样式*/
width: 0px;
/*高宽分别对应横竖滚动条的尺寸*/
height: 0px;
}
.goddsSw {
display: flex;
// position: absolute;
// left: 0;
// top: 0;
height: 100%;
.goddsRow {
width: 25vw;
color: #fff;
padding: 0 10px 0 90px;
display: flex;
height: 100%;
.name {
width: 40px;
margin-right: 20px;
word-wrap: break-word;
writing-mode: vertical-lr;
writing-mode: tb-lr;
font-size: 16px;
color: #C8B992;
padding-top: 20px;
letter-spacing: 3px;
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 7.5px;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #C8B992;
}
}
.goddsImg {
cursor: pointer;
width: calc(100% - 80px);
height: calc(100% - 80px);
position: relative;
&::before {
pointer-events: none;
content: '';
position: absolute;
bottom: -80px;
left: 50%;
transform: translateX(-50%);
background: linear-gradient(rgba(231, 219, 188, 0), rgba(231, 219, 188, 1));
width: 1px;
height: 90px;
}
img {
pointer-events: none;
object-fit: contain;
}
}
}
}
}
.goddsSwBox2 {
overflow-y: hidden;
border-bottom: none;
border-top: 1px solid rgba(231, 219, 188, 0.50);
margin-top: 40px;
.goddsSw {
.goddsRow {
padding: 0 90px 0 10px;
.name {
position: relative;
top: 80px;
}
.goddsImg {
top: 80px;
&::before {
bottom: auto;
top: -80px;
background: linear-gradient(rgba(231, 219, 188, 1), rgba(231, 219, 188, 0));
}
}
}
}
}
}
.searchBox {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
.searRow {
margin: 20px;
.ant-select-selector {
border-radius: 0 6px 0px 6px;
background-color: transparent;
color: var(--themeColor);
border: 1px solid var(--themeColor);
.ant-select-selection-search-input::-webkit-input-placeholder {
/* WebKit browsers */
color: var(--themeColor);
}
.ant-select-selection-search-input:-moz-placeholder {
/* Mozilla Firefox 4 to 18 */
color: var(--themeColor);
}
.ant-select-selection-search-input::-moz-placeholder {
/* Mozilla Firefox 19+ */
color: var(--themeColor);
}
.ant-select-selection-search-input:-ms-input-placeholder {
/* Internet Explorer 10+ */
color: var(--themeColor);
}
.ant-select-selection-item {
color: var(--themeColor);
}
.ant-select-selection-placeholder {
color: var(--themeColor);
}
}
.ant-select-arrow {
color: var(--themeColor);
}
.anticon-down {
color: var(--themeColor);
}
}
.ant-input-affix-wrapper {
border-radius: 0 6px 0px 6px;
background-color: transparent;
color: var(--themeColor);
border: 1px solid var(--themeColor);
.ant-input {
background-color: transparent;
color: var(--themeColor);
&::-webkit-input-placeholder {
/* WebKit browsers */
color: var(--themeColor);
}
&:-moz-placeholder {
/* Mozilla Firefox 4 to 18 */
color: var(--themeColor);
}
&::-moz-placeholder {
/* Mozilla Firefox 19+ */
color: var(--themeColor);
}
&:-ms-input-placeholder {
/* Internet Explorer 10+ */
color: var(--themeColor);
}
}
.ant-input-clear-icon {
color: var(--themeColor);
}
}
}
// 藏品详情
.goodsInfoBox {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
pointer-events: none;
transition: all .3s;
backdrop-filter: blur(10px);
padding-top: 30px;
&>div {
color: #fff;
}
}
.goodsInfoBoxAc {
opacity: 1;
pointer-events: auto;
}
.curTitBox {
border-radius: 0 0 4px 4px;
border-top: 3px solid #b6ab97;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 370px;
background-color: #726b56;
.curClose {
position: absolute;
right: 15px;
top: 15px;
cursor: pointer;
color: #98856f;
font-size: 24px;
}
.curImg {
margin: 70px auto 30px;
width: 213px;
height: 213px;
position: relative;
&>img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 1;
transition: all 2s;
}
}
&>p {
font-size: 14px;
color: #fff;
text-align: center;
}
}
}
}