feat: 撑杆监测视频mock数据完成交互

main
donghao
parent aafad9e3f1
commit 9a6d064918

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-07 14:57:20
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-13 14:34:44
* @LastEditTime: 2025-03-14 15:11:04
* @FilePath: \5G-Loading-Bay-Web\mock\deviceStatus.ts
* @Description:
*/
@ -33,7 +33,6 @@ export default [
url: "/api/v1/device/device_history/",
method: "post",
response: req => {
const { page, pageSize } = req.body;
// console.log(req);
return {...fetchMockSuccessFullByOther(deviceHistoryListData)}
}

@ -2,13 +2,13 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-11 11:29:02
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-11 11:39:31
* @LastEditTime: 2025-03-14 15:10:47
* @FilePath: \5G-Loading-Bay-Web\mock\poleMonitor.ts
* @Description:
*/
import { MockMethod } from "vite-plugin-mock";
import { poleMonitorListData } from "./pools/poleMonitorData";
import { fetchCurrPageByList } from "./utils/apiMock";
import { poleMonitorListData, fileListData } from "./pools/poleMonitorData";
import { fetchCurrPageByList, fetchMockSuccessFullByOther } from "./utils/apiMock";
export default [
{
@ -28,5 +28,13 @@ export default [
})
};
}
},
{
url: "/api/v1/record/record_detail_list/",
method: "post",
response: req => {
// console.log(req);
return {...fetchMockSuccessFullByOther(fileListData)}
}
}
] as MockMethod[];

@ -2,43 +2,96 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-11 11:30:09
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-11 14:37:36
* @LastEditTime: 2025-03-14 15:18:42
* @FilePath: \5G-Loading-Bay-Web\mock\pools\poleMonitorData.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import Mock from 'mockjs';
import Mock from "mockjs";
// const images = [
// 'https://picsum.photos/300/200?random=1',
// 'https://picsum.photos/300/200?random=2',
// 'https://picsum.photos/300/200?random=3',
// 'https://picsum.photos/300/200?random=4',
// 'https://picsum.photos/300/200?random=5'
// ];
const videoUrls = [
"https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4",
"https://www.sample-videos.com/video123/mp4/1080/big_buck_bunny_1080p_100mb.mp4",
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
"https://www.sample-videos.com/video123/mp4/360/big_buck_bunny_360p_5mb.mp4",
"https://media.w3.org/2010/05/video/movie_300.mp4",
"https://www.w3schools.com/html/mov_bbb.mp4",
"https://media.w3.org/2010/05/sintel/trailer.mp4",
"https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/360/Big_Buck_Bunny_360_10s_1MB.mp4",
"https://archive.org/download/Popeye_forPresident/Popeye_forPresident_512kb.mp4",
"https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4",
];
const mockListData = Mock.mock({
// 生成 10 条数据,可以根据需要调整数量
"data|140": [
{
// 车号,生成随机的 4 位字母和数字组合
train_number: /[A-Z0-9]{10}/,
// 车型,从预定义的数组中随机选择一个
train_model: () => Mock.Random.pick(["轿车", "SUV", "客车", "货车"]),
// 车厢号,生成 1 到 10 的随机整数
"train_carriage_number|1-10": 1,
// 告警类型,从预定义的数组中随机选择一个
alarm_type: () =>
Mock.Random.pick(["超速告警", "碰撞告警", "低电量告警"]),
// 故障类型,从预定义的数组中随机选择一个
faultType: () => Mock.Random.pick(["撑杆弯曲", "撑杆断折"]),
// 等级,生成 1 到 3 的随机整数
"level|1-3": 1,
// 复核,随机生成 '是' 或 '否'
is_reviewed: () => Mock.Random.pick([true, false]),
// 时间,生成过去一个月内的随机日期和时间
created_at: () =>
Mock.Random.date("yyyy-MM-dd") + " " + Mock.Random.time("HH:mm:ss"),
},
],
});
// // 定义模拟数据生成规则
const mockData = Mock.mock({
// 生成 10 条数据,可以根据需要调整数量
'data|140': [
{
// 车号,生成随机的 4 位字母和数字组合
'carNo': /[A-Z0-9]{4}/,
// 车型,从预定义的数组中随机选择一个
'carType': () => Mock.Random.pick(['轿车', 'SUV', '客车', '货车']),
// 车厢号,生成 1 到 10 的随机整数
'carriageNo|1-10': 1,
// 告警类型,从预定义的数组中随机选择一个
'warnType': () => Mock.Random.pick(['超速告警', '碰撞告警', '低电量告警']),
// 故障类型,从预定义的数组中随机选择一个
'faultType': () => Mock.Random.pick(['电路故障', '机械故障', '传感器故障']),
// 等级,生成 1 到 3 的随机整数
'level|1-3': 1,
// 复核,随机生成 '是' 或 '否'
'review': () => Mock.Random.pick(['是', '否']),
// 时间,生成过去一个月内的随机日期和时间
'date': () => Mock.Random.date('yyyy-MM-dd') + ' ' + Mock.Random.time('HH:mm:ss')
}
]
const mockFilesData = Mock.mock({
[`list|${videoUrls.length}`]: [
{
"id|+1": 10,
key: "@id",
name: "@animal",
video_url: function () {
// 依次取出视频链接
return videoUrls[this.id - 10];
},
image_url: null,
created_at: '@datetime("yyyy-MM-dd HH:mm:ss")',
updated_at: '@datetime("yyyy-MM-dd HH:mm:ss")',
length: "@float(0.1, 10, 2, 2)",
width: "@float(0.1, 10, 2, 2)",
height: "@float(0.1, 10, 2, 2)",
weight: "@float(0.1, 1000, 1, 2)",
volume: function () {
return (this.length * this.width * this.height).toFixed(2);
},
record: 1,
},
],
});
// console.log(mockData, 'mockData');
const currentData = mockData.data;
// console.log(mockListData, 'mockListData');
const currentData = mockListData.data;
const currentFilesData = mockFilesData.list;
export const poleMonitorListData = {
data: {
list: currentData,
total: currentData.length,
page: 1,
pageSize: 10
}
pageSize: 10,
},
};
export const fileListData = {
data: {
data: currentFilesData
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-12 13:48:31
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-13 15:01:16
* @LastEditTime: 2025-03-14 16:37:49
* @FilePath: \5G-Loading-Bay-Web\src\components\videoPlayer\player.vue
* @Description: 视频播放器
-->
@ -11,7 +11,7 @@
<div class="video-player-box">
<video ref="videoRef" class="video-element" controls @timeupdate="handleTimeUpdate"
@loadedmetadata="handleLoadedMetadata" @play="handlePlay" @pause="handlePause" @waiting="handleWaiting"
@canplay="handleCanPlay" style="object-fit: fill;">
@canplay="handleCanPlay" style="object-fit: cover;" @error="handleVideoError">
<source :src="src" type="video/mp4">
您的浏览器不支持视频播放
</video>
@ -21,6 +21,9 @@
<Loading />
</el-icon>
</div>
<div class="bg_error_img" v-if="isVideoError">
</div>
</div>
</template>
@ -54,9 +57,14 @@ const emit = defineEmits<{
}>()
const videoRef = ref<HTMLVideoElement | null>(null)
const loading = ref(false)
const loading = ref<boolean>(false)
const isVideoError = ref<boolean>(false);
let currentDuration = 0
//
watch(() => props.isPlaying, (newVal) => {
if (!videoRef.value) return
@ -66,6 +74,7 @@ watch(() => props.isPlaying, (newVal) => {
//
watch(() => props.src, (newVal) => {
if (!videoRef.value) return
isVideoError.value = false
videoRef.value.pause()
videoRef.value.src = newVal
videoRef.value.load()
@ -97,6 +106,11 @@ const handleLoadedMetadata = () => {
emit('update:duration', currentDuration)
}
const handleVideoError = () => {
console.log('handleVideoError')
isVideoError.value = true;
};
//
const handlePlay = () => emit('play')
const handlePause = () => emit('pause')
@ -107,6 +121,7 @@ const handleCanPlay = () => loading.value = false
onMounted(() => {
console.log('onMounted', props.src)
if (videoRef.value) {
videoRef.value.src = props.src
}
@ -140,8 +155,24 @@ defineExpose({
.video-player-box {
position: relative;
width: 100%;
height: 100%;
margin: 0 auto;
.bg_error_img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 99999999;
background-color: #090F48;
background-image: url("@/assets/common/load_file_error.png");
background-position: center;
background-repeat: no-repeat;
background-size: 50%;
border: 1px dashed red;
}
.video-element {
width: 100%;
height: auto;

@ -25,12 +25,23 @@
}
.pole-monitor-main {
.left-panel {
width: 870px;
margin-right: 16px;
&.empty-bg {
background-image: url("@/assets/common/emptyBg.png");
background-size: 312px 204px;
background-position: center;
background-repeat: no-repeat;
}
.main-image {
box-sizing: border-box;
min-height: 511px;
height: 511px;
position: relative;
background-color: #090F48;
border-radius: 4px;
.video-screen{
height: calc(100% - 52px);
}
// img {
// width: 100%;
// max-height: 460px;
@ -60,6 +71,7 @@
.swiper-slide {
width: 20%;
border-radius: 4px;
height: 144px;
img {
width: 100%;
height: 144px;

@ -1,11 +1,3 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-06 15:15:01
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-14 11:19:33
* @FilePath: \vite-ai\data-dashboard\src\views\dashboard\PoleMonitor.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div class="pole-monitor-wrap mt-[32px]">
<div class="module-header">
@ -22,17 +14,11 @@
<div class="pole-main-content px-[16px]">
<!-- 搜索区域 -->
<div class="pole-monitor-search-box">
<el-select v-model="searchForm.train_number" placeholder="列车号" class="custom-select">
<el-option label="A" value="deviceA"></el-option>
<el-option label="B" value="deviceB"></el-option>
</el-select>
<el-select v-model="searchForm.train_carriage_number" placeholder="车厢号" class="custom-select">
<el-option label="ID-001" value="id001"></el-option>
<el-option label="ID-002" value="id002"></el-option>
</el-select>
<el-select v-model="searchForm.fault_type" placeholder="故障类型" class="custom-select">
<el-option label="类型1" value="id001"></el-option>
<el-option label="类型2" value="id002"></el-option>
<el-input v-model="searchForm.train_number" placeholder="列车号" class="custom-input" clearable />
<el-input v-model="searchForm.train_carriage_number" placeholder="车厢号" class="custom-input" clearable />
<el-select v-model="searchForm.fault_type" placeholder="故障类型" class="custom-select" clearable>
<el-option label="撑杆弯曲" value="撑杆弯曲"></el-option>
<el-option label="撑杆断折" value="撑杆断折"></el-option>
</el-select>
<el-button type="primary" @click="handleQuery" class="basic-btn query-btn">
<span class="icon"></span> 查询
@ -44,18 +30,21 @@
<!-- 主体内容区域 -->
<div class="flex justify-between pole-monitor-main">
<!-- 左侧视频与缩略图区域 -->
<div class="left-panel w-[870px] mr-[16px]">
<div class="left-panel" v-if="currFileList?.length">
<!-- 主图显示 -->
<div class="main-image">
<!-- <img src="https://picsum.photos/300/200?random=1" alt="监控画面"> -->
<!-- <img src="https://picsum.phfotos/300/200?random=1" alt="监控画面"> -->
<!-- <video ref="refVideo" controls muted :src="currFile?.video_url" width="100%" height="100%" style="object-fit: fill;"></video> -->
<img src="https://picsum.photos/300/200?random=1" alt="监控画面" v-if="currFile?.image_url">
<Player :src="currFile?.video_url" v-else-if="currFile?.video_url" />
<div class="video-screen" v-if="currFile?.video_url">
<Player :src="currFile?.video_url" :is-playing="isPlaying"
@play="isPlaying = true" @pause="isPlaying = false" />
</div>
<img src="https://picsum.photos/300/200?random=1" alt="监控画面" v-else-if="currFile?.image_url">
<div v-else>
<!-- //TODO -->
</div>
<div class="image-info">
<!-- //TODO -->
<!-- //TODO -->
<span>: {{ currFile?.length }}</span>
<span>: {{ currFile?.width }}</span>
<span>: {{ currFile?.height }}</span>
@ -63,7 +52,6 @@
<span>重量: {{ currFile?.weight }}</span>
</div>
</div>
<!-- 缩略图区域 -->
<div class="thumbnail-container mt-[16px] w-[870px]">
<swiper ref="swiperRef" :modules="modules" :slides-per-view="3" :space-between="10" navigation
@ -72,8 +60,8 @@
<swiper-slide v-for="(file, index) in currFileList" :key="index"
@click="handleSlideClick(index)" :class="{ 'active-slide': activeIndex === index }">
<img :src="file?.image_url" v-if="file?.image_url" />
<video ref="refVideo" :controls="false" muted :src="file?.video_url" width="100%"
height="144" v-else-if="file?.video_url" style="object-fit: fill;"></video>
<SwiperPlayer :videoUrl="file?.video_url" v-else-if="file?.video_url"
:isPlaying="isPlaying && (activeIndex === index)" />
<div v-else>
<!-- //TODO -->
</div>
@ -81,6 +69,7 @@
</swiper>
</div>
</div>
<div class="left-panel empty-bg" v-else></div>
<!-- 右侧表格区域 -->
<div class="flex-1 right-panel">
<div class="bg-transparent baseTable_wrap">
@ -103,6 +92,7 @@
import Player from '@/components/videoPlayer/Player.vue'
import ContentHeader from '@/components/ContentHeader.vue';
import { BaseTable } from "@/components/CustomTable";
import SwiperPlayer from './components/SwiperPlayer.vue'
import { Swiper, SwiperSlide } from "swiper/vue";
import { Navigation, Scrollbar } from "swiper/modules";
import { getAppearanceMonitorApi, getAppearanceMonitorDetailApi } from '@/api/dashboard';
@ -115,30 +105,9 @@ defineOptions({
name: "PoleMonitorIndex"
});
const modules = [Navigation, Scrollbar];
// const images = ref([
// 'https://picsum.photos/300/200?random=1',
// 'https://picsum.photos/300/200?random=2',
// 'https://picsum.photos/300/200?random=3',
// 'https://picsum.photos/300/200?random=4',
// 'https://picsum.photos/300/200?random=5'
// ]);
// const videoUrl = ref("http://192.168.10.28:8081/%E4%BA%A4%E8%AD%A6%E6%89%A7%E6%B3%95%E8%AE%B0%E5%BD%95%E4%BB%AA%E8%A7%86%E9%A2%91/guidang/zhixingren/AB4303403_090976_20240415164239_20240415162738MEDIA_CH0_090976_4303403_00000000_003.mp4");
const activeIndex = ref(-1);
const handleSlideClick = (index) => {
console.log(index);
activeIndex.value = index;
currFile.value = currFileList.value[index]
};
const swiperRef = ref(null);
console.log(swiperRef.value);
const onSwiper = (swiper) => {
swiperRef.value = swiper;
console.log('Swiper 实例已获取:', swiper);
};
const onSlideChange = () => {
console.log("slide change");
};
const columns = [
{
label: "车号",
@ -191,6 +160,7 @@ const listData = ref<Record<string, any>[]>([]); // 列表数据
const currentRow = ref<Record<string, any>>({}); //
const currFileList = ref<Record<string, any>[]>([]); //
const currFile = ref<Record<string, any>>({}); //
const isPlaying = ref<boolean>(false); //
const searchForm = reactive({
train_number: "",
train_carriage_number: "",
@ -198,17 +168,60 @@ const searchForm = reactive({
type: "pole"
});
const dataLoading = ref(true);
//
// const isCurrPlaying = computed(() => {
// return (index) => {
// return isPlaying.value && (activeIndex === index)
// }
// })
const togglePlay = () => {
isPlaying.value = !isPlaying.value;
};
const handleSlideClick = (index) => {
if (activeIndex.value === index) {
togglePlay() //
} else {
activeIndex.value = index;
currFile.value = currFileList.value[index]
}
};
const onSwiper = (swiper) => {
swiperRef.value = swiper;
console.log('Swiper 实例已获取:', swiper);
};
const onSlideChange = () => {
console.log("slide change");
};
//
// const getFileList = async () => {
// try {
// const res = await getAppearanceMonitorDetailApi({ id: currentRow.value?.id, current: 1, pageSize: 1000 })
// console.log(res.data, 'getDetailList_data')
// if (isSuccessApi(res)) {
// currFileList.value = res.data.data;
// currFile.value = res.data.data[0]
// }
// } catch (error) {
// console.log(error, 'getDetailList_error')
// }
// }
// TODO mock
const getFileList = async () => {
try {
const res = await getAppearanceMonitorDetailApi({ id: currentRow.value?.id, current: 1, pageSize: 1000 })
console.log(res.data, 'getDetailList_data')
const resAll = await fetch('/api/v1/record/record_detail_list/', {
method: 'POST'
})
const res = await resAll.json()
if (isSuccessApi(res)) {
currFileList.value = res.data.data;
currFile.value = res.data.data[0]
activeIndex.value = 0
}
} catch (error) {
console.log(error, 'getDetailList_error')
console.error('获取数据失败:', error)
}
}
@ -235,6 +248,7 @@ const getList = async () => {
console.error('获取数据失败:', error)
}
};
//
const handleQuery = () => {
getList()

@ -0,0 +1,49 @@
<template>
<div class="flex items-center justify-center w-full h-full position-relative">
<video ref="videoRef" :controls="false" muted :src="videoUrl" width="100%" height="144"
style="object-fit: cover;" @error="handleVideoError" v-if="!isVideoError"></video>
<div class="bg_error_img" v-if="isVideoError">
</div>
<div :class="{ 'bg_icon': true, 'playing': isPlaying }" v-if="!isVideoError">
<!-- {{ isPlaying }} -->
</div>
</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
videoUrl: string;
isPlaying: boolean;
}>();
const videoRef = ref<HTMLVideoElement | null>(null);
const isVideoError = ref<boolean>(false);
const handleVideoError = () => {
console.log('handleVideoError')
isVideoError.value = true;
};
</script>
<style lang="scss" scoped>
.bg_error_img {
width: 100%;
height: 100%;
background: url("@/assets/common/load_file_error.png") no-repeat center center;
background-size: 50%;
border: 1px dashed red;
}
.bg_icon {
position: absolute;
width: 100%;
height: 100%;
background: url("@/assets/common/player_icon_1.png") no-repeat center center;
background-size: 40px;
&.playing {
background: url("@/assets/common/player_icon_2.png") no-repeat center center;
background-size: 40px;
}
}
</style>
Loading…
Cancel
Save