feat: 外观监测、撑杆监测视频播放图片展示模块调整完成

main
donghao 2 months ago
parent f7822dcffc
commit 30a7e562b1

@ -92,7 +92,8 @@ const mockHistroyData = Mock.mock({
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}, },
device: 1, device: 1,
}, }
], ],
}); });

@ -2,21 +2,15 @@
* @Author: donghao donghao@supervision.ltd * @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-11 11:30:09 * @Date: 2025-03-11 11:30:09
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-14 15:18:42 * @LastEditTime: 2025-03-17 15:24:42
* @FilePath: \5G-Loading-Bay-Web\mock\pools\poleMonitorData.ts * @FilePath: \5G-Loading-Bay-Web\mock\pools\poleMonitorData.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @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 = [ import { isImage } from "../utils/is";
// '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 = [ const videoUrls = [
"https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4", "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", "http://192.168.10.14:8123/ftp/1.jpg",
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.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://www.sample-videos.com/video123/mp4/360/big_buck_bunny_360p_5mb.mp4",
"https://media.w3.org/2010/05/video/movie_300.mp4", "https://media.w3.org/2010/05/video/movie_300.mp4",
@ -60,9 +54,20 @@ const mockFilesData = Mock.mock({
name: "@animal", name: "@animal",
video_url: function () { video_url: function () {
// 依次取出视频链接 // 依次取出视频链接
return videoUrls[this.id - 10]; const currFile = videoUrls[this.id - 10];
if (!isImage(currFile)) {
return videoUrls[this.id - 10];
}
return null;
},
image_url: function () {
// 依次取出视频链接
const currFile = videoUrls[this.id - 10];
if (isImage(currFile)) {
return videoUrls[this.id - 10];
}
return null;
}, },
image_url: null,
created_at: '@datetime("yyyy-MM-dd HH:mm:ss")', created_at: '@datetime("yyyy-MM-dd HH:mm:ss")',
updated_at: '@datetime("yyyy-MM-dd HH:mm:ss")', updated_at: '@datetime("yyyy-MM-dd HH:mm:ss")',
length: "@float(0.1, 10, 2, 2)", length: "@float(0.1, 10, 2, 2)",
@ -92,6 +97,6 @@ export const poleMonitorListData = {
export const fileListData = { export const fileListData = {
data: { data: {
data: currentFilesData data: currentFilesData,
}, },
}; };

@ -0,0 +1,43 @@
/**
*
* @param {string} filename
* @returns {boolean}
*/
const IMAGE_EXTENSIONS = new Set([
"jpg",
"jpeg",
"png",
"gif",
"bmp",
"svg",
"webp",
"tiff",
"psd",
"ico",
"jfif",
"apng",
"avif",
]);
export function isImage(filename) {
// 1. 去除路径,只保留文件名
const baseName = filename.split("/").pop().split("\\").pop();
if (!baseName) return false;
// 2. 提取扩展名(处理多扩展名,取最后一个)
const ext = baseName.split(".").pop()?.toLowerCase();
if (!ext || ext.length < 2) return false; // 扩展名长度至少2位如.jpg
// 3. 检查是否在图片扩展名白名单
return IMAGE_EXTENSIONS.has(ext);
}
// 示例测试
// console.log(isImage('photo.jpg')); // true
// console.log(isImage('image.png')); // true
// console.log(isImage('logo.svg')); // true
// console.log(isImage('cover.tar.gz')); // false非图片扩展名
// console.log(isImage('file')); // false无扩展名
// console.log(isImage('.hidden.png')); // true隐藏文件
// console.log(isImage('icon.JPEG')); // true大小写不敏感
// console.log(isImage('video.mp4')); // false视频

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd * @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-06 15:52:40 * @Date: 2025-03-06 15:52:40
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-07 14:19:16 * @LastEditTime: 2025-03-17 10:20:28
* @FilePath: \vite-ai\data-dashboard\src\components\contentHeader.vue * @FilePath: \vite-ai\data-dashboard\src\components\contentHeader.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
--> -->
@ -31,7 +31,7 @@ import Bg800 from '@/assets/header/bg_800.png';
import Bg450 from '@/assets/header/bg_450.png'; import Bg450 from '@/assets/header/bg_450.png';
// props // props
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
bgLayout: number; // 1855 918 800 450 bgLayout: number | string; // 1855 918 800 450
}>(), { }>(), {
bgLayout: 1855, bgLayout: 1855,
}); });

@ -7,11 +7,15 @@
/* 去掉表头下边框 */ /* 去掉表头下边框 */
.el-table__header-wrapper thead th { .el-table__header-wrapper thead th {
border-bottom: none !important; border-bottom: none !important;
border: none !important;
} }
/* 去掉单元格边框 */ /* 去掉单元格边框 */
.el-table td, .el-table td,
.el-table th.is-leaf { .el-table th.is-leaf {
border-bottom: none !important; border-bottom: none !important;
border: none !important;
} }
/* 去掉纵向分割线 */ /* 去掉纵向分割线 */
.el-table--border::after, .el-table--border::after,
@ -23,10 +27,12 @@
.el-table--group { .el-table--group {
border-right: none !important; border-right: none !important;
border-bottom: none !important; border-bottom: none !important;
border: none !important;
} }
.el-table td, .el-table td,
.el-table th { .el-table th {
border-right: none !important; border-right: none !important;
border: none !important;
} }
.el-scrollbar__view { .el-scrollbar__view {

@ -2,14 +2,14 @@
* @Author: donghao donghao@supervision.ltd * @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-12 13:48:31 * @Date: 2025-03-12 13:48:31
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-14 16:37:49 * @LastEditTime: 2025-03-17 15:36:41
* @FilePath: \5G-Loading-Bay-Web\src\components\videoPlayer\player.vue * @FilePath: \5G-Loading-Bay-Web\src\components\videoPlayer\player.vue
* @Description: 视频播放器 * @Description: 视频播放器
--> -->
<template> <template>
<!-- 视频播放器 --> <!-- 视频播放器 -->
<div class="video-player-box"> <div class="video-player-box">
<video ref="videoRef" class="video-element" controls @timeupdate="handleTimeUpdate" <video ref="videoRef" class="video-element" controls @timeupdate="handleTimeUpdate"
@loadedmetadata="handleLoadedMetadata" @play="handlePlay" @pause="handlePause" @waiting="handleWaiting" @loadedmetadata="handleLoadedMetadata" @play="handlePlay" @pause="handlePause" @waiting="handleWaiting"
@canplay="handleCanPlay" style="object-fit: cover;" @error="handleVideoError"> @canplay="handleCanPlay" style="object-fit: cover;" @error="handleVideoError">
<source :src="src" type="video/mp4"> <source :src="src" type="video/mp4">
@ -28,7 +28,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, onMounted, onUnmounted } from 'vue'
import { ElIcon } from 'element-plus' import { ElIcon } from 'element-plus'
import { Loading } from '@element-plus/icons-vue' import { Loading } from '@element-plus/icons-vue'
@ -169,7 +168,7 @@ defineExpose({
background-image: url("@/assets/common/load_file_error.png"); background-image: url("@/assets/common/load_file_error.png");
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 50%; background-size: 25%;
border: 1px dashed red; border: 1px dashed red;
} }

@ -2,15 +2,18 @@
* @Author: donghao donghao@supervision.ltd * @Author: donghao donghao@supervision.ltd
* @Date: 2025-03-12 13:48:53 * @Date: 2025-03-12 13:48:53
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2025-03-14 13:21:40 * @LastEditTime: 2025-03-17 14:10:46
* @FilePath: \5G-Loading-Bay-Web\src\components\videoPlayer\RealPlayer.vue * @FilePath: \5G-Loading-Bay-Web\src\components\videoPlayer\RealPlayer.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
--> -->
<template> <template>
<div class="real-video-player"> <div class="real-video-player" v-show="isPlaying">
<video ref="refPlayer" autoplay controls muted width="100%" height="100%" style="object-fit: fill;"></video> <video ref="refPlayer" autoplay controls muted width="100%" height="100%" style="object-fit: fill;"></video>
<!-- <iframe src="http://192.168.10.113:8889/cam/" frameborder="0"></iframe> --> <!-- <iframe src="http://192.168.10.113:8889/cam/" frameborder="0"></iframe> -->
</div> </div>
<div class="flex items-center justify-center bg-black before-open-video" v-show="!isPlaying" @click.stop="openReal()">
<img src="@/assets/common/player_icon_1.png" alt="">
</div>
<!-- http://192.168.10.113:8889/cam/ --> <!-- http://192.168.10.113:8889/cam/ -->
</template> </template>
@ -18,6 +21,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { playRtspApi, stopRtspApi } from '@/api/dashboard'; import { playRtspApi, stopRtspApi } from '@/api/dashboard';
import { isSuccessApi } from "@/utils/forApi"; import { isSuccessApi } from "@/utils/forApi";
import { nextTick } from 'vue';
interface Props { interface Props {
show: boolean; // show: boolean; //
@ -26,10 +30,11 @@ interface Props {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
show: false, show: false,
videoSrc: "" videoSrc: "",
}); });
const webRtcServer = ref(null); const webRtcServer = ref(null);
const refPlayer = ref(null); const refPlayer = ref(null);
const isPlaying = ref<boolean>(false);
const videosInfo = ref<{ process_id: number | string, host: string }>({ process_id: null, host: '' }); const videosInfo = ref<{ process_id: number | string, host: string }>({ process_id: null, host: '' });
function initData() { function initData() {
// const videoSrc = 'rtsp://192.168.10.63:8554/mystream'; // const videoSrc = 'rtsp://192.168.10.63:8554/mystream';
@ -61,7 +66,8 @@ async function initPlayer() {
} }
async function stopPlayer() { async function stopPlayer() {
if(!videosInfo.value.process_id){ isPlaying.value = false;
if (!videosInfo.value.process_id) {
return return
} }
try { try {
@ -78,15 +84,8 @@ async function stopPlayer() {
} }
} }
// watch(() => props.videoSrc, (newVal) => {
// console.log(newVal, 'videoSrc')
// // initPlayer()
// },{ immediate: true })
watch(() => props.show, (newVal) => { watch(() => props.show, (newVal) => {
if(newVal){ if (!newVal) {
initPlayer();
} else {
stopPlayer() stopPlayer()
} }
console.log(newVal, 'show') console.log(newVal, 'show')
@ -95,8 +94,32 @@ watch(() => props.show, (newVal) => {
onUnmounted(() => { onUnmounted(() => {
webRtcServer.disconnect(); webRtcServer.disconnect();
webRtcServer.value = null; webRtcServer.value = null;
if(videosInfo.value.process_id){ if (videosInfo.value.process_id) {
stopPlayer(); stopPlayer();
} }
}) })
function openReal() {
isPlaying.value = true;
nextTick(() => initPlayer())
console.log("开启")
}
defineExpose({
initPlayer
})
</script> </script>
<style lang="scss">
.before-open-video {
width: 100%;
height: 100%;
background: #030D26;
img {
width: 40px;
height: 40px;
}
}
</style>

@ -10,18 +10,16 @@
), ),
), ),
$select-dropdown: ( $select-dropdown: (
"background-color": red, "background-color": transparent,
) )
// $el-pagination: ( // $el-pagination: (
// --el-pagination-button-height-small: 24px; // --el-pagination-button-height-small: 24px;
// ) // )
); );
/* 引入 Element Plus 样式 */ /* 引入 Element Plus 样式 */
@use "element-plus/theme-chalk/src/index.scss" as *; @use "element-plus/theme-chalk/src/index.scss" as *;
.el-button.is-text:not(.is-disabled):hover { .el-button.is-text:not(.is-disabled):hover {
background-color: transparent; background-color: transparent;
} }
@ -112,12 +110,15 @@
/* 下拉选择 */ /* 下拉选择 */
.custom-select { .custom-select {
width: 150px; width: 150px;
background-color: #032b5c; /* 自定义背景色 */
border: none; /* 可选:去掉边框 */
.el-select__selected-item { .el-select__selected-item {
color: white; /* 文字颜色 */ color: white; /* 文字颜色 */
} }
.el-select__wrapper { .el-select__wrapper {
background: rgba(74, 126, 191, 0.1); /* 下拉框背景色 */ background: rgba(74, 126, 191, 0.1); /* 下拉框背景色 */
border: 1px solid transparent; /* 边框 */ border: none !important; /* 边框 */
color: white; /* 文字颜色 */ color: white; /* 文字颜色 */
height: 32px; height: 32px;
box-shadow: none; box-shadow: none;
@ -137,22 +138,17 @@
border-color: transparent; /* 悬停边框颜色 */ border-color: transparent; /* 悬停边框颜色 */
} }
} }
.custom-select {
background-color: #032b5c; /* 自定义背景色 */
border: none; /* 可选:去掉边框 */
}
.custom-input { .custom-input {
width: 150px; width: 150px;
background-color: #032b5c; /* 自定义背景色 */ background-color: #032b5c; /* 自定义背景色 */
border: none; /* 可选:去掉边框 */ border: none; /* 可选:去掉边框 */
.el-input__wrapper { .el-input__wrapper {
background: rgba(74, 126, 191, 0.1); /* 下拉框背景色 */ background: rgba(74, 126, 191, 0.1); /* 下拉框背景色 */
border: 1px solid transparent; /* 边框 */ border: none; /* 边框 */
color: white; /* 文字颜色 */ color: white; /* 文字颜色 */
height: 32px; height: 32px;
box-shadow: none; box-shadow: none;
&::placeholder { &::placeholder {
color: rgba(255, 255, 255, 0.6); /* 占位符颜色 */ color: rgba(255, 255, 255, 0.6); /* 占位符颜色 */
} }
@ -176,14 +172,11 @@
} }
.el-select-dropdown__item.is-hovering { .el-select-dropdown__item.is-hovering {
background-color: #2de6ff; background-color: #0c4eac;
color: white; color: white;
} }
/* table */ /* table */
.el-table {
// background-color: transparent;
}
.table_action_box { .table_action_box {
.el-button { .el-button {
padding: 0; padding: 0;

@ -45,9 +45,21 @@
padding: 32px 16px 20px; padding: 32px 16px 20px;
min-height: 600px; min-height: 600px;
img { .file-preview-screen {
width: 100%; width: 100%;
height: 590px; height: 590px;
display: flex;
justify-content: center;
align-items: center;
img {
max-width: 100%;
max-height: 460px;
object-fit: cover
}
video {
width: 100%;
max-height: calc(100%);
}
} }
} }
.monitor-left-bottom { .monitor-left-bottom {
@ -61,13 +73,16 @@
.swiper-slide { .swiper-slide {
width: 20%; width: 20%;
border-radius:4px; border-radius:4px;
height: 144px;
img { img {
width: 100%; width: 100%;
height: 144px; height: 144px;
border-radius:4px; border-radius:4px;
object-fit: cover
} }
} }
.active-slide img { .active-slide img,
.active-slide video {
border-radius:4px; border-radius:4px;
border: 2px solid #2ECCE0; border: 2px solid #2ECCE0;
} }

@ -1,18 +1,29 @@
<template> <template>
<div class="appearance-monitor-warp"> <div class="appearance-monitor-warp">
<div class="appearance-monitor-left h-[100%]"> <div class="appearance-monitor-left h-[100%]">
<template v-if="isImageFlag"> <template v-if="currFileList?.length">
<div class="monitor-left-top"> <div class="monitor-left-top">
<img :src="imageBig" alt="监控画面" /> <div class="file-preview-screen">
<!-- <video src=""></video> --> <Player :src="currFile?.video_url" :is-playing="isPlaying" v-if="currFile?.video_url"
@play="isPlaying = true" @pause="isPlaying = false" />
<img :src="currFile?.image_url" v-else-if="currFile?.image_url" >
<div v-else>
<!-- //TODO -->
</div>
</div>
</div> </div>
<div class="monitor-left-bottom"> <div class="monitor-left-bottom">
<swiper ref="swiperRef" :modules="modules" :slides-per-view="4" :space-between="10" navigation <swiper ref="swiperRef" :modules="modules" :slides-per-view="4" :space-between="10" navigation
:scrollbar="{ draggable: false }" :centered-slides="false" :observer="true" :observeParents="true" :scrollbar="{ draggable: false }" :centered-slides="false" :observer="true" :observeParents="true"
@swiper="onSwiper" @slideChange="onSlideChange"> @swiper="onSwiper" @slideChange="onSlideChange">
<swiper-slide v-for="(image, index) in images" :key="index" @click="handleSlideClick(index)" <swiper-slide v-for="(file, index) in currFileList" :key="index" @click="handleSlideClick(index)"
:class="{ 'active-slide': activeIndex === index }"> :class="{ 'active-slide': activeIndex === index }">
<img :src="image" alt="Slide" /> <img :src="file?.image_url" v-if="file?.image_url" class="cursor-pointer" />
<SwiperPlayer class="cursor-pointer" :videoUrl="file?.video_url" v-else-if="file?.video_url"
:isPlaying="isPlaying && (activeIndex === index)" />
<div v-else>
<!-- //TODO -->
</div>
</swiper-slide> </swiper-slide>
</swiper> </swiper>
</div> </div>
@ -53,13 +64,12 @@
</el-button> </el-button>
</div> </div>
<!-- 右侧表格区域 --> <!-- 右侧表格区域 -->
<div class="right-panel w-[800px]"> <div class="w-full right-panel">
<div class="bg-transparent baseTable_wrap"> <div class="bg-transparent baseTable_wrap">
<template v-if="pagination.total > 0"> <template v-if="pagination.total > 0">
<BaseTable class="bg-transparent baseTable_box" :total="pagination.total" :pageSize="pagination.pageSize" <BaseTable class="bg-transparent baseTable_box" :total="pagination.total" :pageSize="pagination.pageSize"
:dataSource="listData" :isFixedPagination="true" :columns="columns" :page="pagination.currentPage" :dataSource="listData" :isFixedPagination="true" :columns="columns" :page="pagination.currentPage"
@change="handleTableChange":row-class-name="handleRowClassName" @change="handleTableChange" :row-class-name="handleRowClassName" @row-click="handleRowClick">
@row-click="handleRowClick">
</BaseTable> </BaseTable>
</template> </template>
</div> </div>
@ -70,17 +80,21 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import Player from '@/components/videoPlayer/Player.vue'
import SwiperPlayer from './components/SwiperPlayer.vue'
import ContentHeader from "@/components/ContentHeader.vue"; import ContentHeader from "@/components/ContentHeader.vue";
import { BaseTable } from "@/components/CustomTable"; import { BaseTable } from "@/components/CustomTable";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import { Navigation, Scrollbar } from "swiper/modules"; import { Navigation, Scrollbar } from "swiper/modules";
import { getAppearanceMonitorApi, getAppearanceMonitorDetailApi } from '@/api/dashboard'; import { getAppearanceMonitorApi, getAppearanceMonitorDetailApi } from '@/api/dashboard';
import { isSuccessApi } from "@/utils/forApi";
import "swiper/css"; import "swiper/css";
import 'swiper/scss'; import 'swiper/scss';
import 'swiper/scss/navigation'; import 'swiper/scss/navigation';
const modules = [Navigation, Scrollbar]; const modules = [Navigation, Scrollbar];
const activeIndex = ref(-1);
// const currentRow = ref<Record<string, any>>({}); const swiperRef = ref(null);
const columns = [ const columns = [
{ {
label: "车号", label: "车号",
@ -116,24 +130,24 @@ const columns = [
label: "复核", label: "复核",
property: "is_reviewed", property: "is_reviewed",
formatter: ({ is_reviewed }) => { formatter: ({ is_reviewed }) => {
return is_reviewed === true return is_reviewed === true
? "是" ? "是"
:"否"; : "否";
}, },
width: 60, width: 60,
}, },
{ {
label: "时间", label: "时间",
property: "created_at", property: "created_at",
width: 170
} }
] ]
const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 }); const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 });
const listData = ref([]); //
const listData = ref([]); const currentRow = ref<Record<string, any>>({}); //
const listDetail = ref([]); const currFileList = ref<Record<string, any>[]>([]); //
const isImageFlag = ref(true); const currFile = ref<Record<string, any>>({}); //
const isPlaying = ref<boolean>(false); //
// //
const searchForm = reactive({ const searchForm = reactive({
train_number: "", train_number: "",
@ -143,30 +157,83 @@ const searchForm = reactive({
}); });
const dataLoading = ref(true); const dataLoading = ref(true);
const getList = async () => { const togglePlay = () => {
const { currentPage, pageSize } = pagination.value; isPlaying.value = !isPlaying.value;
const res = await getAppearanceMonitorApi({ ...searchForm, current: currentPage, pageSize })
const { data } = await res
console.log(data, 'getList_data')
listData.value = data.data;
getDetail(listData.value[0]?.id)
console.log(data.list);
pagination.value = {
...pagination.value,
total: data.total
};
dataLoading.value = false;
}; };
const getDetail = async (id: any) => { const handleSlideClick = (index) => {
const res = await getAppearanceMonitorDetailApi({ id, current:1,}) if (activeIndex.value === index) {
const { data } = await res togglePlay() //
console.log(data, 'getDetail_data') } else {
listDetail.value = data.data; activeIndex.value = index;
isImageFlag.value = data.data?.length > 0 ? true : false; currFile.value = currFileList.value[index];
console.log(data.data); isPlaying.value = false;
}
};
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];
activeIndex.value = 0;
}
} catch (error) {
console.log(error, 'getDetailList_error')
}
}
// TODO mock
// const getFileList = async () => {
// try {
// 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.error(':', error)
// }
// }
function loadDetail() {
currentRow.value = listData.value[0]
getFileList()
} }
//
const getList = async () => {
try {
const { currentPage, pageSize } = pagination.value;
const res = await getAppearanceMonitorApi({ ...searchForm, current: currentPage, pageSize })
console.log(res.data, 'getList_data')
if (isSuccessApi(res)) {
listData.value = res.data.data;
loadDetail()
pagination.value = {
...pagination.value,
total: res.data.total
};
}
} catch (error) {
console.error('获取数据失败:', error)
}
};
// //
const handleQuery = () => { const handleQuery = () => {
getList() getList()
@ -190,41 +257,15 @@ function handleTableChange(record) {
getList(); getList();
} }
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 imageBig = ref(images.value[0])
const activeIndex = ref(-1);
const handleSlideClick = (index) => {
console.log(index);
activeIndex.value = index;
imageBig.value = images.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 currentRowIndex = ref(-1);
// //
const handleRowClassName = ({ row }) => { const handleRowClassName = ({ row }) => {
return row.train_number === currentRowIndex.value ? 'selected-row' : ''; return row.id === currentRow.value.id ? 'selected-row' : '';
}; };
// //
const handleRowClick = (row, event, rowIndex) => { const handleRowClick = (row, event, rowIndex) => {
currentRowIndex.value = row.train_number; currentRow.value = row;
getDetail(row.id) getFileList()
}; };
onMounted(() => { onMounted(() => {

@ -1,5 +1,9 @@
.device-status-content-box{ .device-status-content-box{
background-image: url("@/assets/common/device_status_bg_line.png");
background-size: 100% 100%;
background-position: bottom;
background-repeat: no-repeat;
.el-scrollbar__view { .el-scrollbar__view {
background: transparent !important; background: transparent !important;
height: 600px; height: 600px;

@ -37,27 +37,32 @@
box-sizing: border-box; box-sizing: border-box;
height: 511px; height: 511px;
position: relative; position: relative;
background-color: #090F48; background-color: #090f48;
border-radius: 4px; border-radius: 4px;
.video-screen{ .file-preview-screen {
height: calc(100% - 52px); height: calc(100%);
display: flex;
justify-content: center;
align-items: center;
img {
max-width: 100%;
max-height: 460px;
object-fit: cover
}
} }
// img {
// width: 100%;
// max-height: 460px;
// }
video { video {
width: 100%; width: 100%;
max-height: 460px; max-height: calc(100%);
} }
.image-info{ .image-info {
position: absolute; position: absolute;
height: 52px; height: 52px;
line-height: 52px; line-height: 52px;
bottom: 0; bottom: 0;
font-size: 14px; font-size: 14px;
padding: 0 16px; padding: 0 16px;
&> span { & > span {
margin-right: 10px; margin-right: 10px;
} }
} }
@ -76,6 +81,7 @@
width: 100%; width: 100%;
height: 144px; height: 144px;
border-radius: 4px; border-radius: 4px;
object-fit: cover
} }
} }
.active-slide img, .active-slide img,

@ -15,7 +15,8 @@
<!-- 搜索区域 --> <!-- 搜索区域 -->
<div class="pole-monitor-search-box"> <div class="pole-monitor-search-box">
<el-input v-model="searchForm.train_number" placeholder="请输入列车号" class="custom-input" clearable /> <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-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-select v-model="searchForm.fault_type" placeholder="故障类型" class="custom-select" clearable>
<el-option label="撑杆弯曲" value="撑杆弯曲"></el-option> <el-option label="撑杆弯曲" value="撑杆弯曲"></el-option>
<el-option label="撑杆断折" value="撑杆断折"></el-option> <el-option label="撑杆断折" value="撑杆断折"></el-option>
@ -35,15 +36,15 @@
<div class="main-image"> <div class="main-image">
<!-- <img src="https://picsum.phfotos/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> --> <!-- <video ref="refVideo" controls muted :src="currFile?.video_url" width="100%" height="100%" style="object-fit: fill;"></video> -->
<div class="video-screen" v-if="currFile?.video_url"> <div class="file-preview-screen">
<Player :src="currFile?.video_url" :is-playing="isPlaying" <Player :src="currFile?.video_url" :is-playing="isPlaying" v-if="currFile?.video_url"
@play="isPlaying = true" @pause="isPlaying = false" /> @play="isPlaying = true" @pause="isPlaying = false" />
<img :src="currFile?.image_url" v-else-if="currFile?.image_url">
<div v-else>
<!-- //TODO -->
</div>
</div> </div>
<img src="https://picsum.photos/300/200?random=1" alt="监控画面" v-else-if="currFile?.image_url"> <div class="image-info" v-if="currFile?.image_url">
<div v-else>
<!-- //TODO -->
</div>
<div class="image-info">
<!-- //TODO --> <!-- //TODO -->
<span>: {{ currFile?.length }}</span> <span>: {{ currFile?.length }}</span>
<span>: {{ currFile?.width }}</span> <span>: {{ currFile?.width }}</span>
@ -59,9 +60,9 @@
:observeParents="true" @swiper="onSwiper" @slideChange="onSlideChange"> :observeParents="true" @swiper="onSwiper" @slideChange="onSlideChange">
<swiper-slide v-for="(file, index) in currFileList" :key="index" <swiper-slide v-for="(file, index) in currFileList" :key="index"
@click="handleSlideClick(index)" :class="{ 'active-slide': activeIndex === index }"> @click="handleSlideClick(index)" :class="{ 'active-slide': activeIndex === index }">
<img :src="file?.image_url" v-if="file?.image_url" /> <img :src="file?.image_url" v-if="file?.image_url" class="cursor-pointer"/>
<SwiperPlayer :videoUrl="file?.video_url" v-else-if="file?.video_url" <SwiperPlayer class="cursor-pointer" :videoUrl="file?.video_url"
:isPlaying="isPlaying && (activeIndex === index)" /> v-else-if="file?.video_url" :isPlaying="isPlaying && (activeIndex === index)" />
<div v-else> <div v-else>
<!-- //TODO --> <!-- //TODO -->
</div> </div>
@ -107,7 +108,6 @@ defineOptions({
const modules = [Navigation, Scrollbar]; const modules = [Navigation, Scrollbar];
const activeIndex = ref(-1); const activeIndex = ref(-1);
const swiperRef = ref(null); const swiperRef = ref(null);
const columns = [ const columns = [
{ {
label: "车号", label: "车号",
@ -184,6 +184,7 @@ const handleSlideClick = (index) => {
if (activeIndex.value === index) { if (activeIndex.value === index) {
togglePlay() // togglePlay() //
} else { } else {
isPlaying.value = false;
activeIndex.value = index; activeIndex.value = index;
currFile.value = currFileList.value[index] currFile.value = currFileList.value[index]
} }
@ -196,34 +197,35 @@ const onSlideChange = () => {
console.log("slide change"); 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 () => { const getFileList = async () => {
try { try {
const resAll = await fetch('/api/v1/record/record_detail_list/', { const res = await getAppearanceMonitorDetailApi({ id: currentRow.value?.id, current: 1, pageSize: 1000 })
method: 'POST' console.log(res.data, 'getDetailList_data')
})
const res = await resAll.json()
if (isSuccessApi(res)) { if (isSuccessApi(res)) {
currFileList.value = res.data.data; currFileList.value = res.data.data;
currFile.value = res.data.data[0] currFile.value = res.data.data[0];
activeIndex.value = 0 activeIndex.value = 0;
} }
} catch (error) { } catch (error) {
console.error('获取数据失败:', error) console.log(error, 'getDetailList_error')
} }
} }
// TODO mock
// const getFileList = async () => {
// try {
// 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.error(':', error)
// }
// }
function loadDetail() { function loadDetail() {
currentRow.value = listData.value[0] currentRow.value = listData.value[0]
@ -257,7 +259,8 @@ const handleQuery = () => {
// //
const handleReset = () => { const handleReset = () => {
searchForm.train_number = ''; searchForm.train_number = '';
// deviceId.value = ''; searchForm.train_carriage_number = '';
searchForm.fault_type = '';
getList() getList()
}; };
// //

@ -199,6 +199,11 @@ defineExpose({ loadData })
overflow: hidden; overflow: hidden;
width: 752px; width: 752px;
height: 502px; height: 502px;
.video-element {
width: 100%;
height: 100%;
}
} }
.video-record-list { .video-record-list {
@ -210,13 +215,30 @@ defineExpose({ loadData })
width: 100%; width: 100%;
display: flex; display: flex;
flex: 1; flex: 1;
// border-radius: 2px;
border: none;
.el-select__wrapper { .el-select__wrapper {
width: 100%; width: 100%;
&.is-focused {
// border: 1px solid rgba(8, 139, 214, 0.4);
box-shadow: none;
}
&.is-hovering {
border: none;
}
&:hover {
border: none;
}
} }
} }
.record-list-box { .record-list-box {
margin-top: 5px; margin-top: 12px;
li { li {
color: white; color: white;
@ -224,11 +246,15 @@ defineExpose({ loadData })
cursor: pointer; cursor: pointer;
padding-left: 16px; padding-left: 16px;
padding-right: 24px; padding-right: 24px;
// margin: 6px 0;
&.active, &.active,
&:hover { &:hover {
background: linear-gradient(90deg, rgba(30, 54, 88, 0) 0%, #0C4FAD 53%, rgba(65, 117, 190, 0) 100%); background: linear-gradient(90deg, rgba(30, 54, 88, 0) 0%, #0C4FAD 53%, rgba(65, 117, 190, 0) 100%);
color: #37DBFF; color: #37DBFF;
border-radius: 2px;
border: 1px solid;
border-image: linear-gradient(90deg, rgba(12, 24, 64, 0), rgba(69, 174, 250, 1), rgba(102, 102, 102, 0)) 1 1;
} }
.time { .time {
@ -237,8 +263,8 @@ defineExpose({ loadData })
} }
.play-btn { .play-btn {
width: 18px; width: 20px;
height: 18px; height: 20px;
background: url("@/assets/common/player_icon.png") no-repeat center center; background: url("@/assets/common/player_icon.png") no-repeat center center;
background-size: contain; background-size: contain;
} }

@ -11,8 +11,9 @@
</div> </div>
</template> </template>
<!-- 视频播放区域 --> <!-- 视频播放区域 -->
<div class="video-container"> <div class="relative video-container">
<RealPlayer :show="show" :videoSrc="info.url" /> <RealPlayer :show="show" :videoSrc="info.url" />
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
@ -39,13 +40,15 @@ const emit = defineEmits<Emits>();
const isPlaying = ref(false); const isPlaying = ref(false);
const togglePlay = () => { const openReal = () => {
isPlaying.value = !isPlaying.value; // isPlaying.value = !isPlaying.value;
}; };
// //
const handleClose = () => { const handleClose = () => {
emits('close'); isPlaying.value = false;
// emits('close');
}; };
const show = computed({ const show = computed({
@ -110,12 +113,15 @@ const show = computed({
.video-container { .video-container {
padding: 24px; padding: 24px;
height: 550px;
// background: red; // background: red;
.real-video-player { .real-video-player {
border-radius: 0px 0px 4px 4px; border-radius: 0px 0px 4px 4px;
overflow: hidden; overflow: hidden;
} }
} }
} }
</style> </style>
Loading…
Cancel
Save