Commit f40a6f536349f61dc6a525e20796d4ce40e34458

Authored by 史婷婷
1 parent a9c6fc82

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

@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 "axios": "^1.7.9", 15 "axios": "^1.7.9",
16 "react": "^18.3.1", 16 "react": "^18.3.1",
17 "react-dom": "^18.3.1", 17 "react-dom": "^18.3.1",
  18 + "react-pdf": "^9.2.1",
18 "react-router-dom": "^7.1.1" 19 "react-router-dom": "^7.1.1"
19 }, 20 },
20 "devDependencies": { 21 "devDependencies": {
@@ -38,4 +38,16 @@ export function getFilePreview(fileId?: string) { @@ -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 }
@@ -6,7 +6,7 @@ import { useNavigate } from 'react-router-dom'; @@ -6,7 +6,7 @@ import { useNavigate } from 'react-router-dom';
6 interface NavBarProps { 6 interface NavBarProps {
7 rightInfo?: any; 7 rightInfo?: any;
8 showBack?: any; 8 showBack?: any;
9 - title?: any; 9 + title?: string;
10 isWhite?: any; // 是否是白色返回箭头 10 isWhite?: any; // 是否是白色返回箭头
11 style?: any; 11 style?: any;
12 backContent?: any; 12 backContent?: any;
@@ -27,6 +27,9 @@ const NavBarSec: React.FC<NavBarProps> = (props) => { @@ -27,6 +27,9 @@ const NavBarSec: React.FC<NavBarProps> = (props) => {
27 27
28 const style = props?.style; 28 const style = props?.style;
29 29
  30 + useEffect(() => {
  31 + window.scrollTo(0, 0);
  32 + }, [])
30 33
31 const back = () => { 34 const back = () => {
32 navigate(-1); // 回退到上一个页面 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 import './style.less' 2 import './style.less'
3 import NavBar from '@/components/nav-bar' 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 return ( 75 return (
71 <div className={'sxjx-content-main sxjx-layout-main-unfoot'}> 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 </div> 89 </div>
88 </div> 90 </div>
89 ) 91 )
90 } 92 }
91 93
92 -export default ProductionManagement; 94 +export default ProductionDetail;
1 -.production-management { 1 +.production-detail {
2 background: #f7f7f7; 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 display: flex; 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 }
@@ -7,16 +7,18 @@ import _preview from './preview.png'; @@ -7,16 +7,18 @@ import _preview from './preview.png';
7 7
8 import {useSearchParams} from "react-router-dom"; 8 import {useSearchParams} from "react-router-dom";
9 import {getFilePreview, getProductBook} from "@/api/apiConfig"; 9 import {getFilePreview, getProductBook} from "@/api/apiConfig";
10 -import {SpinLoading, Checkbox} from 'antd-mobile' 10 +import {SpinLoading, Checkbox, Toast} from 'antd-mobile'
11 import { useNavigate } from "react-router-dom"; 11 import { useNavigate } from "react-router-dom";
  12 +import {a} from "vite/dist/node/types.d-aGj9QkWt";
12 13
13 interface ListType { 14 interface ListType {
14 id: string; 15 id: string;
  16 + fileId: string;
15 name: string; 17 name: string;
16 type: 'pdf' | 'mp4' | string 18 type: 'pdf' | 'mp4' | string
17 } 19 }
18 20
19 -type CheckboxValue = string | number 21 +type CheckboxValue = string;
20 22
21 const ProductionList: React.FC = () => { 23 const ProductionList: React.FC = () => {
22 const [title, setTitle] = useState<string>(''); 24 const [title, setTitle] = useState<string>('');
@@ -26,6 +28,7 @@ const ProductionList: React.FC = () => { @@ -26,6 +28,7 @@ const ProductionList: React.FC = () => {
26 const navigate = useNavigate(); 28 const navigate = useNavigate();
27 const [checkItems, setCheckItems] = useState<CheckboxValue[]>([]) 29 const [checkItems, setCheckItems] = useState<CheckboxValue[]>([])
28 const [checkValues, setCheckValues] = useState<CheckboxValue[]>([]) 30 const [checkValues, setCheckValues] = useState<CheckboxValue[]>([])
  31 + // const [sxPreViewList, setSxPreViewList] = useState<ListType[]>([])
29 32
30 33
31 // 因是hook,必须写在组件的顶部执行,useSearchParams() 返回的是数组 34 // 因是hook,必须写在组件的顶部执行,useSearchParams() 返回的是数组
@@ -36,6 +39,10 @@ const ProductionList: React.FC = () => { @@ -36,6 +39,10 @@ const ProductionList: React.FC = () => {
36 const id = params.get("id") || ""; 39 const id = params.get("id") || "";
37 40
38 useEffect(() => { 41 useEffect(() => {
  42 + localStorage.setItem('sxPreViewListStorage', '');
  43 + }, [])
  44 +
  45 + useEffect(() => {
39 if (name) { 46 if (name) {
40 setTitle(name) 47 setTitle(name)
41 } 48 }
@@ -51,7 +58,8 @@ const ProductionList: React.FC = () => { @@ -51,7 +58,8 @@ const ProductionList: React.FC = () => {
51 _checkItems.push(item?.guide_book_file_info_?.[0]?.fileId); 58 _checkItems.push(item?.guide_book_file_info_?.[0]?.fileId);
52 const _arr = item?.guide_book_file_info_?.[0]?.name?.split('.') || []; 59 const _arr = item?.guide_book_file_info_?.[0]?.name?.split('.') || [];
53 return { 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 name: _arr?.[0] || '', 63 name: _arr?.[0] || '',
56 type: _arr?.[1] || '', 64 type: _arr?.[1] || '',
57 } 65 }
@@ -78,20 +86,58 @@ const ProductionList: React.FC = () => { @@ -78,20 +86,58 @@ const ProductionList: React.FC = () => {
78 console.log('checkboxChange-value', value) 86 console.log('checkboxChange-value', value)
79 setCheckValues(value) 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 console.log('toDetail-ids', ids) 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 return ( 141 return (
96 <div className={'sxjx-content-main sxjx-layout-main-unfoot'}> 142 <div className={'sxjx-content-main sxjx-layout-main-unfoot'}>
97 <div className={`production-list ${showBatch ? 'production-list--batch' : ''}`}> 143 <div className={`production-list ${showBatch ? 'production-list--batch' : ''}`}>
@@ -107,7 +153,7 @@ const ProductionList: React.FC = () => { @@ -107,7 +153,7 @@ const ProductionList: React.FC = () => {
107 list?.map((item: ListType) => { 153 list?.map((item: ListType) => {
108 return <div 154 return <div
109 key={item?.id} 155 key={item?.id}
110 - className={`production-list_list-item ${item?.id}`} 156 + className={`production-list_list-item`}
111 style={{backgroundColor: '#fff'}} 157 style={{backgroundColor: '#fff'}}
112 onClick={() => { 158 onClick={() => {
113 if (!showBatch) { 159 if (!showBatch) {
@@ -156,13 +202,13 @@ const ProductionList: React.FC = () => { @@ -156,13 +202,13 @@ const ProductionList: React.FC = () => {
156 >取消</div> 202 >取消</div>
157 </div> 203 </div>
158 <div className={'production-list--batch_info-bottom'}> 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 <img className={'production-list--batch_info-bottom_item-icon'} src={_preview} alt=""/> 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 </div> 212 </div>
167 </div> 213 </div>
168 </div> : '' 214 </div> : ''
@@ -114,8 +114,8 @@ @@ -114,8 +114,8 @@
114 position: absolute; 114 position: absolute;
115 top: 0; 115 top: 0;
116 left: 30px; 116 left: 30px;
117 - width: 100%;  
118 - height: 100%; 117 + width: calc(100% - 30px);
  118 + height: calc(100% - 30px);
119 --icon-size: 30px; 119 --icon-size: 30px;
120 display: flex; 120 display: flex;
121 align-items: start; 121 align-items: start;
@@ -133,4 +133,10 @@ @@ -133,4 +133,10 @@
133 padding: 0 10px; 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 }