index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. /* eslint-disable jsx-a11y/iframe-has-title */
  2. import React, {
  3. useCallback,
  4. useEffect,
  5. useMemo,
  6. useRef,
  7. useState,
  8. } from "react";
  9. import styles from "./index.module.scss";
  10. import toBackImg from "@/assets/img/goods/toBack.png";
  11. import { envUrl } from "@/utils/env";
  12. import {
  13. A2BarrageType,
  14. A2FileObjType,
  15. A2FileType,
  16. A2GoodsType,
  17. A2QuestionType,
  18. } from "@/types";
  19. import {
  20. A2_APIgetBarrage,
  21. A2_APIgetBarrageAll,
  22. A2_APIgetConfigBarrage,
  23. A2_APIgetGoodsInfo,
  24. A2_APIgetQuestion,
  25. A2_APIgoodsaddStar,
  26. } from "@/store/action/A2Main";
  27. import { MessageFu } from "@/utils/message";
  28. import classNames from "classnames";
  29. import { baseURL } from "@/utils/http";
  30. import ImageLazy from "@/components/ImageLazy";
  31. import R_leftImg from "@/assets/img/goods/R_left.png";
  32. import R_rightImg from "@/assets/img/goods/R_right.png";
  33. import { gsap } from "gsap";
  34. import Tab4Msg from "../Tab4Msg";
  35. import Tab4S3 from "../Tab4S3";
  36. type Props = {
  37. isOpen: boolean;
  38. id: number;
  39. closePage?: () => void;
  40. };
  41. function Tab4Info({ isOpen, id, closePage }: Props) {
  42. // 文物信息
  43. const [info, setInfo] = useState<A2GoodsType>({} as A2GoodsType);
  44. // 附件数据(除开音频)
  45. const [fileList, setFileList] = useState<A2FileObjType>({
  46. model: [],
  47. video: [],
  48. img: [],
  49. });
  50. // ---------------右侧图标
  51. // 音频
  52. const [right1, setRight1] = useState({ show: false, url: "" });
  53. const audioUrlRef = useRef("");
  54. // 留言板(默认显示),传递 到二级页面 看看是否开启了 留言到后端的 功能
  55. const [right2, setRight2] = useState(false);
  56. //问答
  57. const [right3, setRight3] = useState(false);
  58. // 知识
  59. const [right4, setRight4] = useState(false);
  60. // 弹幕
  61. const [right5, setRight5] = useState({ show: false, done: false });
  62. // 点赞
  63. const [right6, setRight6] = useState(false);
  64. // 分享
  65. const [right7, setRight7] = useState(false);
  66. // 点击点赞
  67. const likeClickFu = useCallback(async () => {
  68. if (right6) return;
  69. setRight6(true);
  70. try {
  71. await A2_APIgoodsaddStar(id);
  72. } catch (error) {
  73. console.log(error);
  74. }
  75. window.setTimeout(() => {
  76. setRight6(false);
  77. }, 3000);
  78. }, [id, right6]);
  79. // 点击分享链接
  80. const fenXClickFu = useCallback(() => {
  81. if (right7) return;
  82. setRight7(true);
  83. window.setTimeout(() => {
  84. setRight7(false);
  85. }, 3000);
  86. let OrderNumber = window.location.origin + "/pc/#/goods?id=" + id;
  87. let newInput = document.createElement("input");
  88. newInput.value = OrderNumber;
  89. document.body.appendChild(newInput);
  90. newInput.select();
  91. document.execCommand("Copy");
  92. newInput.remove();
  93. MessageFu.success("复制链接成功");
  94. }, [id, right7]);
  95. // 留言-知识-问答 弹窗的选中状态(打开)
  96. const [rightPageAc, setRightPageAc] = useState<0 | 2 | 3 | 4>(0);
  97. // 问答数组
  98. const [question, setQuestion] = useState<A2QuestionType[]>([]);
  99. // 文物留言的数组
  100. const [barrage, setBarrage] = useState<A2BarrageType[]>([]);
  101. // 所有弹幕的数组
  102. const [barrageAll, setBarrageAll] = useState<A2BarrageType[]>([]);
  103. const barrMoveRef = useRef<HTMLDivElement>(null);
  104. const [barrInd, setBarrInd] = useState(0);
  105. const barrMoveRefKill = useRef<any>(null);
  106. // 每次弹幕的索引变化的时候
  107. useEffect(() => {
  108. window.setTimeout(() => {
  109. const width = barrMoveRef.current?.offsetWidth || 0;
  110. if (barrMoveRef.current) {
  111. barrMoveRef.current.style.right = -width - 200 + "px";
  112. const endNum = -window.innerWidth - width - 200;
  113. barrMoveRefKill.current?.kill();
  114. // 开始动画
  115. // 总弹幕数量只有 1 的情况
  116. if (barrageAll.length === 1) {
  117. barrMoveRefKill.current = gsap.to(barrMoveRef.current, {
  118. duration: 16,
  119. ease: "none",
  120. x: endNum,
  121. repeat: -1,
  122. });
  123. } else if (barrageAll.length > 1) {
  124. // 有超过 1 的情况
  125. barrMoveRefKill.current = gsap.fromTo(
  126. barrMoveRef.current,
  127. { x: 0 },
  128. {
  129. duration: 16,
  130. ease: "none",
  131. x: endNum,
  132. onComplete: () => {
  133. let num = barrInd + 1;
  134. if (num >= barrageAll.length) num = 0;
  135. setBarrInd(num);
  136. },
  137. }
  138. );
  139. }
  140. }
  141. }, 200);
  142. }, [barrInd, barrageAll.length]);
  143. // 通过id获取详情
  144. const getDataFu = useCallback(
  145. async (id: number) => {
  146. const res = await A2_APIgetGoodsInfo(id);
  147. if (res.code === 0) {
  148. const info: A2GoodsType = res.data.entity;
  149. // 留言板 和 弹幕开关 是否显示
  150. const res_b = await A2_APIgetConfigBarrage();
  151. if (res_b.code === 0) {
  152. const value_b = JSON.parse(res_b.data.content);
  153. if (value_b.value) {
  154. // 获取所有的弹幕数组
  155. const resBa_all = await A2_APIgetBarrageAll();
  156. if (resBa_all.code === 0) {
  157. setBarrageAll(resBa_all.data);
  158. }
  159. //打开了弹幕总开关 并且所有弹幕的数组长度>0
  160. if (resBa_all.data.length) {
  161. // 显示弹幕开关
  162. setRight5({ show: true, done: true });
  163. }
  164. // 文物的弹幕留言开关 和总 弹幕开关都开启了(可以在二级页面发送接口留言)
  165. if (info.isBarrage) setRight2(true);
  166. }
  167. }
  168. const file: A2FileType[] = res.data.file;
  169. // 整理附件数据
  170. const fileObj: A2FileObjType = {
  171. model: [],
  172. video: [],
  173. img: [],
  174. };
  175. file.forEach((v) => {
  176. if (v.type === "model") fileObj.model.push(v);
  177. else if (v.type === "video") fileObj.video.push(v);
  178. else if (v.type === "img") fileObj.img.push(v);
  179. });
  180. setFileList(fileObj);
  181. // 有上传音频
  182. const isAudioObj = file.find((v) => v.type === "audio");
  183. if (isAudioObj) {
  184. setRight1({ show: !isOpen, url: isAudioObj.filePath });
  185. audioUrlRef.current = isAudioObj.filePath;
  186. }
  187. setInfo(info);
  188. // 看看是否有相关的问答
  189. const res2 = await A2_APIgetQuestion(id);
  190. if (res2.code === 0) {
  191. // 如有有选择 知识驿站
  192. if (info.tagType) setRight4(true);
  193. // 如果有问答
  194. if (res2.data.length) setRight3(true);
  195. const res3 = await A2_APIgetBarrage(id);
  196. setQuestion(res2.data);
  197. if (res3.code === 0) {
  198. // 如果这条文物有留言
  199. // if (res3.data.length) setRight2;
  200. setBarrage(res3.data);
  201. }
  202. }
  203. }
  204. },
  205. [isOpen]
  206. );
  207. // 有关音频
  208. useEffect(() => {
  209. if (right1.url) {
  210. // 如果是从 文物 详情 进来---直接播放音频
  211. window.setTimeout(() => {
  212. const dom: HTMLAudioElement | null = document.querySelector("#myAudio");
  213. if (dom) {
  214. dom.onended = () => {
  215. // console.log("-------音频播放结束");
  216. setRight1({ show: false, url: audioUrlRef.current });
  217. };
  218. // 音频的状态
  219. if (!isOpen) dom.play();
  220. }
  221. }, 100);
  222. }
  223. }, [isOpen, right1.url]);
  224. // 打开和关闭音频
  225. const audioCutFu = useCallback((val: boolean) => {
  226. const dom: HTMLAudioElement | null = document.querySelector("#myAudio");
  227. if (dom) {
  228. if (!val) dom.play();
  229. else dom.pause();
  230. setRight1({ show: !val, url: audioUrlRef.current });
  231. }
  232. }, []);
  233. useEffect(() => {
  234. getDataFu(id);
  235. return () => {
  236. barrMoveRefKill.current?.kill();
  237. };
  238. }, [getDataFu, id]);
  239. // 文件类型 type
  240. const [type, setType] = useState<"model" | "video" | "img">("model");
  241. // 每次变化type的时候把 索引变成0
  242. useEffect(() => {
  243. setShowInd(0);
  244. }, [type]);
  245. // 切换不同文件(模型/视频/图片的数组)
  246. const typeArr = useMemo(() => {
  247. const arr: {
  248. name: "模型" | "视频" | "图片";
  249. type: "model" | "video" | "img";
  250. }[] = [];
  251. if (fileList.model.length) arr.push({ name: "模型", type: "model" });
  252. if (fileList.video.length) arr.push({ name: "视频", type: "video" });
  253. if (fileList.img.length) arr.push({ name: "图片", type: "img" });
  254. return arr;
  255. }, [fileList]);
  256. // 当前默认展示什么模块(优先级按照 模型-视频-图片)
  257. useEffect(() => {
  258. if (fileList.model.length) setType("model");
  259. else if (fileList.video.length) setType("video");
  260. else setType("img");
  261. }, [fileList]);
  262. // 在页面展示的数组
  263. const list = useMemo(() => {
  264. return fileList[type];
  265. }, [fileList, type]);
  266. // 当前显示的 索引
  267. const [showInd, setShowInd] = useState(0);
  268. const cutIndFu = useCallback(
  269. (num: number) => {
  270. setShowInd(showInd + num);
  271. },
  272. [showInd]
  273. );
  274. return (
  275. <div
  276. className={styles.Tab4Info}
  277. style={{ backgroundImage: `url(${envUrl}/goods/bac.jpg)` }}
  278. >
  279. {/* 音频控件 */}
  280. {right1.url ? (
  281. <audio hidden src={baseURL + right1.url} controls id="myAudio"></audio>
  282. ) : null}
  283. {/* 返回按钮 */}
  284. <div className="t4ItoBack" onClick={closePage}>
  285. <img src={toBackImg} alt="" />
  286. </div>
  287. {/* 顶部名称 */}
  288. <div className="topName">{info.name}</div>
  289. {/* 顶部tab */}
  290. <div className="R_typeCutBox" hidden={typeArr.length <= 1}>
  291. {typeArr.map((v) => (
  292. <div
  293. onClick={() => setType(v.type)}
  294. className={classNames(
  295. "R_typeCutRow",
  296. type === v.type ? "R_typeCutRowAc" : ""
  297. )}
  298. key={v.type}
  299. >
  300. {v.name}
  301. {fileList[v.type].length > 1 ? fileList[v.type].length : null}
  302. </div>
  303. ))}
  304. </div>
  305. {/* 主体模型 */}
  306. {fileList.model && fileList.model[0] && fileList.model[0].id ? (
  307. <div className="t4IifrBox" hidden={type !== "model"}>
  308. <iframe
  309. src={`model.html?m=${fileList.model[0].filePath}`}
  310. frameBorder="0"
  311. ></iframe>
  312. </div>
  313. ) : null}
  314. {/* 主体 其他附件 */}
  315. <div className="t4IFileBox">
  316. {list.map((v, i) => (
  317. <div
  318. className={classNames("R1_row", i === showInd ? "R1_rowAc" : "")}
  319. key={v.id}
  320. >
  321. {v.type === "video" && i === showInd ? (
  322. <video src={baseURL + v.filePath} controls></video>
  323. ) : v.type === "img" ? (
  324. <ImageLazy src={v.filePath} width="100%" height="100%" />
  325. ) : null}
  326. </div>
  327. ))}
  328. {/* 索引 */}
  329. {list.length > 1 ? (
  330. <div className="showIndBox">
  331. {showInd + 1} / {list.length}
  332. </div>
  333. ) : null}
  334. </div>
  335. {/* 左右按钮 */}
  336. <div
  337. hidden={list.length <= 1}
  338. onClick={() => cutIndFu(-1)}
  339. className={classNames("R_left", showInd === 0 ? "R_arrowNo" : "")}
  340. >
  341. <img src={R_leftImg} alt="" />
  342. </div>
  343. <div
  344. hidden={list.length <= 1}
  345. onClick={() => cutIndFu(1)}
  346. className={classNames(
  347. "R_right",
  348. showInd >= list.length - 1 ? "R_arrowNo" : ""
  349. )}
  350. >
  351. <img src={R_rightImg} alt="" />
  352. </div>
  353. {/* 底部文字介绍集合 */}
  354. <div className="t4ITxts myscroll">
  355. {info.dictTexture ? (
  356. <>
  357. <span>类别:</span>
  358. {info.dictTexture}&emsp;
  359. </>
  360. ) : null}
  361. {info.dictAge ? (
  362. <>
  363. <span>年代:</span>
  364. {info.dictAge}&emsp;
  365. </>
  366. ) : null}
  367. {info.dictLevel ? (
  368. <>
  369. <span>级别:</span>
  370. {info.dictLevel}&emsp;
  371. </>
  372. ) : null}
  373. {info.description ? (
  374. <>
  375. <span>简介:</span>
  376. {info.description}&emsp;
  377. </>
  378. ) : null}
  379. </div>
  380. {/* 右侧所有按钮图标 */}
  381. <div className="rightIconBox">
  382. {/* 音频 */}
  383. {right1.url ? (
  384. <div
  385. className="rightIconRow"
  386. onClick={() => audioCutFu(right1.show)}
  387. title={right1.show ? "关闭音频" : "打开音频"}
  388. >
  389. <img
  390. src={`${envUrl}/goods/icon1${right1.show ? "Ac" : ""}.png`}
  391. alt=""
  392. />
  393. </div>
  394. ) : null}
  395. {/* 留言板 */}
  396. <div
  397. hidden={barrage.length<=0}
  398. className="rightIconRow"
  399. onClick={() => setRightPageAc(2)}
  400. title="留言板"
  401. >
  402. <img src={`${envUrl}/goods/icon2.png`} alt="" />
  403. </div>
  404. {/* 问答 */}
  405. {right3 ? (
  406. <div
  407. className="rightIconRow"
  408. onClick={() => setRightPageAc(3)}
  409. title="问答"
  410. >
  411. <img src={`${envUrl}/goods/icon3.png`} alt="" />
  412. </div>
  413. ) : null}
  414. {/* 知识 */}
  415. {right4 ? (
  416. <div
  417. className="rightIconRow"
  418. onClick={() => setRightPageAc(4)}
  419. title="知识"
  420. >
  421. <img src={`${envUrl}/goods/icon4.png`} alt="" />
  422. </div>
  423. ) : null}
  424. {/* 弹幕 */}
  425. {right5.done ? (
  426. <div
  427. className="rightIconRow"
  428. title={right5.show ? "关闭弹幕" : "开启弹幕"}
  429. onClick={() => setRight5({ show: !right5.show, done: true })}
  430. >
  431. <img
  432. src={`${envUrl}/goods/icon5${right5.show ? "Ac" : ""}.png`}
  433. alt=""
  434. />
  435. </div>
  436. ) : null}
  437. {/* 点赞 */}
  438. <div
  439. className="rightIconRow"
  440. onClick={() => likeClickFu()}
  441. title="点赞"
  442. >
  443. <img src={`${envUrl}/goods/icon6${right6 ? "Ac" : ""}.png`} alt="" />
  444. <div className="moveImg" hidden={!right6}>
  445. +1
  446. </div>
  447. </div>
  448. {/* 分享 */}
  449. <div
  450. className="rightIconRow"
  451. onClick={() => fenXClickFu()}
  452. title="分享"
  453. >
  454. <img src={`${envUrl}/goods/icon7${right7 ? "Ac" : ""}.png`} alt="" />
  455. </div>
  456. </div>
  457. {/* 弹幕的盒子 */}
  458. {barrageAll.length ? (
  459. <div className="barrMove" ref={barrMoveRef} hidden={!right5.show}>
  460. <h3>{barrageAll[barrInd].name}</h3>
  461. <p>
  462. {barrageAll[barrInd].authorName}&nbsp;
  463. {barrageAll[barrInd].createTime}
  464. &nbsp;观&nbsp;[{barrageAll[barrInd].goodsName}]&nbsp;有感
  465. </p>
  466. </div>
  467. ) : null}
  468. {/* 留言板 - 知识 - 问答 */}
  469. {rightPageAc === 2 ? (
  470. <Tab4Msg
  471. btnOkFlag={right2}
  472. barrageList={barrage}
  473. goodsId={id}
  474. closeFu={() => setRightPageAc(0)}
  475. />
  476. ) : rightPageAc === 3 ? (
  477. <Tab4S3 closeFu={() => setRightPageAc(0)} questionList={question}/>
  478. ) : null}
  479. </div>
  480. );
  481. }
  482. const MemoTab4Info = React.memo(Tab4Info);
  483. export default MemoTab4Info;