Commit f40a6f536349f61dc6a525e20796d4ce40e34458

Authored by 史婷婷
1 parent a9c6fc82

feat: 生产线详情-预览功能:mp4(单个与批量)

... ... @@ -15,6 +15,7 @@
15 15 "axios": "^1.7.9",
16 16 "react": "^18.3.1",
17 17 "react-dom": "^18.3.1",
  18 + "react-pdf": "^9.2.1",
18 19 "react-router-dom": "^7.1.1"
19 20 },
20 21 "devDependencies": {
... ...
... ... @@ -38,4 +38,16 @@ export function getFilePreview(fileId?: string) {
38 38 {
39 39 }
40 40 );
  41 +}
  42 +
  43 +/**生产线信息--轮播图设置
  44 + * **/
  45 +export function getCarouselSettings() {
  46 + return post(
  47 + `/open/qx-apaas-lowcode/getCarouselSettings`,
  48 + {
  49 + },
  50 + {
  51 + }
  52 + );
41 53 }
\ No newline at end of file
... ...
... ... @@ -6,7 +6,7 @@ import { useNavigate } from 'react-router-dom';
6 6 interface NavBarProps {
7 7 rightInfo?: any;
8 8 showBack?: any;
9   - title?: any;
  9 + title?: string;
10 10 isWhite?: any; // 是否是白色返回箭头
11 11 style?: any;
12 12 backContent?: any;
... ... @@ -27,6 +27,9 @@ const NavBarSec: React.FC<NavBarProps> = (props) => {
27 27
28 28 const style = props?.style;
29 29
  30 + useEffect(() => {
  31 + window.scrollTo(0, 0);
  32 + }, [])
30 33
31 34 const back = () => {
32 35 navigate(-1); // 回退到上一个页面
... ...
  1 +import React, {useEffect, useRef, useState} from 'react';
  2 +
  3 +type VideoPlayProps = {
  4 + key: string;
  5 + url: string;
  6 + className: string;
  7 +};
  8 +// 视频自动播放组件
  9 +const VideoPlay: React.FC<VideoPlayProps> = (props) => {
  10 +
  11 + const videoRef = useRef<HTMLVideoElement>(null);
  12 + const [isPlaying, setIsPlaying] = useState(true);
  13 +
  14 + useEffect(() => {
  15 + if (videoRef.current) {
  16 + videoRef.current.onended = () => {
  17 + setIsPlaying(true);
  18 + };
  19 + }
  20 + }, [props?.key]);
  21 +
  22 + useEffect(() => {
  23 + if (isPlaying && videoRef.current) {
  24 + videoRef.current.play().catch(() => {
  25 + setIsPlaying(false);
  26 + });
  27 + }
  28 + }, [isPlaying]);
  29 +
  30 + return (
  31 + <>
  32 + {
  33 + props?.url ? <video
  34 + id={props?.url}
  35 + ref={videoRef}
  36 + autoPlay
  37 + loop
  38 + muted
  39 + playsInline
  40 + className={`${props?.className}`}
  41 + >
  42 + <source src={props?.url} type="video/mp4" />
  43 + </video> : ''
  44 + }
  45 + </>
  46 + );
  47 +};
  48 +
  49 +export default VideoPlay;
... ...
1   -import React from 'react'
  1 +import React, {useEffect, useState} from 'react'
2 2 import './style.less'
3 3 import NavBar from '@/components/nav-bar'
4   -import {baseColorPrimary} from "@/utils/common";
5   -import {hexToRgba} from "@/utils/utils";
  4 +import VideoPlay from '@/components/videoPlay'
  5 +import {useSearchParams} from "react-router-dom";
  6 +import { Document, Page, pdfjs } from "react-pdf";
  7 +import 'react-pdf/dist/esm/Page/AnnotationLayer.css'
  8 +import 'react-pdf/dist/esm/Page/TextLayer.css';
  9 +import {getCarouselSettings} from "@/api/apiConfig";
  10 +pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`;
6 11
7 12
8   -const ProductionManagement: React.FC = () => {
  13 +const ProductionDetail: React.FC = () => {
  14 + const [title, setTitle] = useState<string>('');
  15 + const [filePathItem, setFilePathItem] = useState();
  16 + // 自动预览 定时器
  17 + const [preViewTimer, setPreViewTimer] = useState<any>(null);
9 18
10   - const list = [
11   - {
12   - id: '1',
13   - name: '车顶生产线A'
14   - },
15   - {
16   - id: '2',
17   - name: '车顶生产线B'
18   - },
19   - {
20   - id: '3',
21   - name: '车顶生产线C'
22   - },
23   - {
24   - id: '4',
25   - name: '车顶生产线D'
26   - },
27   - {
28   - id: '5',
29   - name: '车顶生产线E'
30   - },
31   - {
32   - id: '6',
33   - name: '车顶生产线F'
34   - },
35   - {
36   - id: '7',
37   - name: '车顶生产线G'
38   - },
39   - {
40   - id: '8',
41   - name: '车顶生产线H'
42   - },
43   - {
44   - id: '9',
45   - name: '车顶生产线I'
46   - },
47   - {
48   - id: '10',
49   - name: '车顶生产线J'
50   - },
51   - {
52   - id: '11',
53   - name: '车顶生产线K'
54   - },
55   - {
56   - id: '12',
57   - name: '车顶生车顶生车顶生产线L'
58   - },
59   - {
60   - id: '13',
61   - name: '车顶生产线M'
62   - },
63   - {
64   - id: '14',
65   - name: '车顶生产线N'
  19 + // // 因是hook,必须写在组件的顶部执行,useSearchParams() 返回的是数组
  20 + const [params] = useSearchParams();
  21 + // // 通过 get 方法获取目标参数
  22 + const name = params.get("name") || "";
  23 +
  24 + const sxPreViewList = localStorage.getItem('sxPreViewListStorage') ? JSON.parse(localStorage.getItem('sxPreViewListStorage')) : [];
  25 +
  26 + console.log('sxPreViewList', sxPreViewList)
  27 +
  28 + useEffect(() => {
  29 + if (sxPreViewList?.length) {
  30 + const _isLoop = sxPreViewList?.length > 1;
  31 + const loopPreView = (index: number, interval: number) => {
  32 + setFilePathItem(sxPreViewList?.[index]);
  33 + clearInterval(preViewTimer);
  34 + // 3秒调一次
  35 + let _preViewTimer = setInterval(() => {
  36 + index = index + 1;
  37 + if (index > sxPreViewList?.length - 1) {
  38 + index = 0;
  39 + }
  40 + setFilePathItem(sxPreViewList?.[index]);
  41 + }, interval * 1000);
  42 + setPreViewTimer(_preViewTimer);
  43 + }
  44 + if (_isLoop) {
  45 + getCarouselSettings().then((res: any) => {
  46 + const interval = res?.carousel?.interval || 10;
  47 + loopPreView(0, interval);
  48 + }).catch(() => {
  49 + loopPreView(0, 10)
  50 + })
  51 + } else {
  52 + setFilePathItem(sxPreViewList?.[0]);
  53 + }
  54 + }
  55 + }, [JSON.stringify(sxPreViewList)])
  56 +
  57 + useEffect(() => {
  58 + if (name) {
  59 + setTitle(name)
66 60 }
67   - ]
  61 + }, [name])
  62 +
  63 + useEffect(() => {
  64 + return () => {
  65 + clearInterval(preViewTimer);
  66 + };
  67 + }, [])
  68 +
  69 + useEffect(() => {
  70 + console.log('filePathItem', filePathItem)
  71 + }, [filePathItem])
  72 +
68 73
69 74
70 75 return (
71 76 <div className={'sxjx-content-main sxjx-layout-main-unfoot'}>
72   - <div className={'production-management'}>
73   - <NavBar title={'上产线管理'}/>
74   - <div className={'production-management_list'}>
75   - {
76   - list?.map((item: any) => {
77   - return <div
78   - key={item?.id}
79   - className={'production-management_list-item'}
80   - style={{backgroundColor: hexToRgba(baseColorPrimary, 0.6)}}
81   - >
82   - {item?.name}
83   - </div>
84   - })
85   - }
86   - </div>
  77 + <div className={'production-detail'}>
  78 + <NavBar showBack={true} title={title}/>
  79 + {
  80 + filePathItem?.type === 'mp4' ? <div className={'production-detail-video-box'}>
  81 + <VideoPlay
  82 + key={filePathItem?.url}
  83 + url={filePathItem?.url || ''}
  84 + className={'production-detail-video'}
  85 + />
  86 + </div> : <>
  87 + </>
  88 + }
87 89 </div>
88 90 </div>
89 91 )
90 92 }
91 93
92   -export default ProductionManagement;
  94 +export default ProductionDetail;
... ...
1   -.production-management {
  1 +.production-detail {
2 2 background: #f7f7f7;
3   -
4   - &_list {
5   - padding: 60px;
  3 + position: relative;
  4 + width: 100%;
  5 + height: calc(100vh - 88px);
  6 + &-video-box {
  7 + position: absolute;
  8 + top: 0;
  9 + left: 0;
  10 + right: 0;
  11 + bottom: 0;
6 12 display: flex;
7   - flex-wrap: wrap;
8   -
9   - &-item {
10   - font-size: 28px;
11   - width: 312px;
12   - height: 312px;
13   - color: #fff;
14   - margin-right: 60px;
15   - margin-bottom: 60px;
16   - cursor: pointer;
17   - border-radius: 12px;
18   - display: flex;
19   - align-items: center;
20   - justify-content: center;
21   - line-height: 40px;
22   - padding: 20px;
23   - box-sizing: border-box;
24   - text-align: center;
25   -
26   - &:nth-child(5n) {
27   - margin-right: 0;
28   - }
29   - }
  13 + align-items: center;
  14 + justify-content: center;
  15 + }
  16 + &-video {
  17 + width: 100%;
  18 + max-width: 100%;
  19 + max-height: 100%;
  20 + height: auto;
30 21 }
31 22 }
\ No newline at end of file
... ...
... ... @@ -7,16 +7,18 @@ import _preview from './preview.png';
7 7
8 8 import {useSearchParams} from "react-router-dom";
9 9 import {getFilePreview, getProductBook} from "@/api/apiConfig";
10   -import {SpinLoading, Checkbox} from 'antd-mobile'
  10 +import {SpinLoading, Checkbox, Toast} from 'antd-mobile'
11 11 import { useNavigate } from "react-router-dom";
  12 +import {a} from "vite/dist/node/types.d-aGj9QkWt";
12 13
13 14 interface ListType {
14 15 id: string;
  16 + fileId: string;
15 17 name: string;
16 18 type: 'pdf' | 'mp4' | string
17 19 }
18 20
19   -type CheckboxValue = string | number
  21 +type CheckboxValue = string;
20 22
21 23 const ProductionList: React.FC = () => {
22 24 const [title, setTitle] = useState<string>('');
... ... @@ -26,6 +28,7 @@ const ProductionList: React.FC = () => {
26 28 const navigate = useNavigate();
27 29 const [checkItems, setCheckItems] = useState<CheckboxValue[]>([])
28 30 const [checkValues, setCheckValues] = useState<CheckboxValue[]>([])
  31 + // const [sxPreViewList, setSxPreViewList] = useState<ListType[]>([])
29 32
30 33
31 34 // 因是hook,必须写在组件的顶部执行,useSearchParams() 返回的是数组
... ... @@ -36,6 +39,10 @@ const ProductionList: React.FC = () => {
36 39 const id = params.get("id") || "";
37 40
38 41 useEffect(() => {
  42 + localStorage.setItem('sxPreViewListStorage', '');
  43 + }, [])
  44 +
  45 + useEffect(() => {
39 46 if (name) {
40 47 setTitle(name)
41 48 }
... ... @@ -51,7 +58,8 @@ const ProductionList: React.FC = () => {
51 58 _checkItems.push(item?.guide_book_file_info_?.[0]?.fileId);
52 59 const _arr = item?.guide_book_file_info_?.[0]?.name?.split('.') || [];
53 60 return {
54   - id: item?.guide_book_file_info_?.[0]?.fileId || '',
  61 + id: item?.id || '',
  62 + fileId: item?.guide_book_file_info_?.[0]?.fileId || '',
55 63 name: _arr?.[0] || '',
56 64 type: _arr?.[1] || '',
57 65 }
... ... @@ -78,20 +86,58 @@ const ProductionList: React.FC = () => {
78 86 console.log('checkboxChange-value', value)
79 87 setCheckValues(value)
80 88 }
81   - const toDetail = (ids: CheckboxValue[]) => {
  89 +
  90 + // 假设这是你的API调用函数
  91 + const fetchData = async (item: ListType) => {
  92 + // 替换为你的实际API URL和请求逻辑
  93 + const res = await getFilePreview(item?.fileId);
  94 + return res;
  95 + }
  96 +
  97 +
  98 + const toDetail = async (ids: CheckboxValue[]) => {
82 99 console.log('toDetail-ids', ids)
83   - if (ids?.[0]) {
84   - const _id: string = ids?.[0].toString() || '';
85   - getFilePreview(_id).then((res: any) => {
86   - console.log('res', res)
87   - window.open(res, '_blank')
88   - }).catch((err: any) => {
89   - console.log('err', err)
  100 + if (ids?.length) {
  101 + let _arr = list?.filter((item: ListType) => ids?.includes(item?.id || '')) || [];
  102 + console.log('_arr', _arr)
  103 + // 创建一个promise数组,每个promise都是对API的一次调用
  104 + const promises = _arr?.map((item: ListType) => fetchData(item));
  105 + // 使用Promise.all来并行地解决这些promise
  106 + Promise.all(promises)
  107 + .then((results: any) => {
  108 + let sxPreViewList = _arr?.map((it: ListType, index: number) => {
  109 + return {
  110 + ...it,
  111 + url: results?.[index]
  112 + }
  113 + })
  114 + console.log('sxPreViewList', sxPreViewList)
  115 + localStorage.setItem('sxPreViewListStorage', JSON.stringify(sxPreViewList));
  116 + navigate(`/production/detail?name=${sxPreViewList?.[0]?.name}`);
  117 + })
  118 + .catch(err => {
  119 + localStorage.setItem('sxPreViewListStorage', '');
  120 + console.log('err', err)
  121 + });
  122 + } else {
  123 + localStorage.setItem('sxPreViewListStorage', '');
  124 + Toast.show({
  125 + content: '请先选择数据!',
  126 + maskClassName: 'to-detail-mask',
90 127 })
91 128 }
92   - // navigate(`/production/detail?id=${item?.id}&name=${item?.name}`);
  129 +
93 130 }
94 131
  132 + // useEffect(() => {
  133 + // console.log('sxPreViewList', sxPreViewList)
  134 + // if (sxPreViewList?.length) {
  135 + // const _name = sxPreViewList?.[0]?.name;
  136 + // console.log('_name', _name)
  137 + // navigate(`/production/detail?name=${_name}`);
  138 + // }
  139 + // }, [sxPreViewList])
  140 +
95 141 return (
96 142 <div className={'sxjx-content-main sxjx-layout-main-unfoot'}>
97 143 <div className={`production-list ${showBatch ? 'production-list--batch' : ''}`}>
... ... @@ -107,7 +153,7 @@ const ProductionList: React.FC = () => {
107 153 list?.map((item: ListType) => {
108 154 return <div
109 155 key={item?.id}
110   - className={`production-list_list-item ${item?.id}`}
  156 + className={`production-list_list-item`}
111 157 style={{backgroundColor: '#fff'}}
112 158 onClick={() => {
113 159 if (!showBatch) {
... ... @@ -156,13 +202,13 @@ const ProductionList: React.FC = () => {
156 202 >取消</div>
157 203 </div>
158 204 <div className={'production-list--batch_info-bottom'}>
159   - <div className={'production-list--batch_info-bottom_item'}>
  205 + <div className={'production-list--batch_info-bottom_item'}
  206 + onClick={() => {
  207 + toDetail(checkValues)
  208 + }}
  209 + >
160 210 <img className={'production-list--batch_info-bottom_item-icon'} src={_preview} alt=""/>
161   - <span className={'production-list--batch_info-bottom_item-info'}
162   - onClick={() => {
163   - toDetail(checkValues)
164   - }}
165   - >预览</span>
  211 + <span className={'production-list--batch_info-bottom_item-info'}>预览</span>
166 212 </div>
167 213 </div>
168 214 </div> : ''
... ...
... ... @@ -114,8 +114,8 @@
114 114 position: absolute;
115 115 top: 0;
116 116 left: 30px;
117   - width: 100%;
118   - height: 100%;
  117 + width: calc(100% - 30px);
  118 + height: calc(100% - 30px);
119 119 --icon-size: 30px;
120 120 display: flex;
121 121 align-items: start;
... ... @@ -133,4 +133,10 @@
133 133 padding: 0 10px;
134 134 }
135 135 }
  136 +}
  137 +
  138 +.to-detail-mask {
  139 + .adm-auto-center-content {
  140 + font-size: 28px !important;
  141 + }
136 142 }
\ No newline at end of file
... ...