视频接收

dev
songwenxuan 2025-09-24 20:09:25 +08:00
parent 783a8b2425
commit 8d890cb357
16 changed files with 605 additions and 371 deletions

View File

@ -1,6 +1,6 @@
VITE_BASE=/dist
#VITE_BASE_URL=http://10.199.64.27:8520/integrated_yjb/
VITE_BASE_URL=http://172.16.112.251:80/api/
VITE_BASE_URL=http://172.16.112.251:8081/sx_yjb/
#VITE_BASE_URL=https://qaaqwh.qhdsafety.com/integrated_whb/
#websocket t掉线

View File

@ -22,6 +22,7 @@
"dayjs": "^1.11.10",
"echarts": "^5.4.3",
"element-plus": "^2.6.1",
"hls.js": "^1.6.13",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.1",
"lodash-es": "^4.17.21",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 901 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -7,20 +7,62 @@
width="70%"
>
<el-divider content-position="left">实时监测</el-divider>
<el-card>
<el-form
:model="searchForm"
label-width="100px"
@submit.prevent="fnResetPagination"
>
<el-row>
<el-col :span="8">
<el-form-item label="开始时间" prop="START_TIME">
<el-date-picker
v-model="searchForm.START_TIME"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结束时间" prop="START_TIME">
<el-date-picker
v-model="searchForm.END_TIME"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label-width="10px">
<el-button type="primary" native-type="submit">搜索</el-button>
<el-button native-type="reset" @click="fnResetPagination">
重置
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<layout-card>
<layout-table
:show-pagination="false"
:data="data.realTimeMonitoringDataList"
ref="tableRef"
v-model:pagination="pagination"
row-key="AREA_ID"
:show-pagination="true"
:data="list"
@get-data="fnGetData"
>
<!-- <el-table-column prop="PLC_NAME" label="监测节点名称" />-->
<!-- <el-table-column prop="PLC_NAME" label="监测节点名称" />-->
<el-table-column prop="TARGET_NAME" label="监测节点名称" />
<el-table-column prop="CURRENT_VALUE" label="当前值">
<template #default="{ row }">
<!-- {{ formatValue(row.SIGNAL_TYPE, row.CURRENT_VALUE) }}-->
<!-- {{ formatValue(row.SIGNAL_TYPE, row.CURRENT_VALUE) }}-->
{{ row.CURRENT_VALUE }}
</template>
</el-table-column>
<el-table-column prop="TARGET_UNIT" label="单位"/>
<el-table-column prop="TARGET_UNIT" label="单位" />
<el-table-column prop="GATHER_TIME" label="采集时间(每5秒采集一次)" />
</layout-table>
</layout-card>
@ -31,9 +73,9 @@
</template>
<script setup>
import { reactive } from "vue";
import { useIntervalFn, useVModels } from "@vueuse/core";
import { getIronAllPlcRealTimeMonitoringDataList } from "@/request/tb_iron.js";
import { useVModels } from "@vueuse/core";
import { getRealTimeMonitoringData } from "@/request/tb_iron.js";
import useListData from "@/assets/js/useListData.js";
const props = defineProps({
visible: {
@ -52,30 +94,17 @@ const props = defineProps({
default: "",
},
});
const data = reactive({
realTimeMonitoringDataList: [],
});
const emits = defineEmits(["update:visible"]);
const { visible } = useVModels(props, emits);
const fnClose = () => {
pause();
visible.value = false;
};
const { pause } = useIntervalFn(
async () => {
if (props.equipmentId) {
const resData = await getIronAllPlcRealTimeMonitoringDataList({
const { list, pagination, searchForm, fnGetData, fnResetPagination, tableRef } =
useListData(getRealTimeMonitoringData, {
otherParams: {
EQUIPMENT_ID: props.equipmentId,
DEVICE_TYPE: props.deviceType,
loading: false,
});
data.realTimeMonitoringDataList = resData.varList;
}
},
5000,
{ immediate: true, immediateCallback: true }
);
});
/* ----------------- 工具:把原始值转换成最终显示文本 ----------------- */
// function formatValue(type, val) {
// // ID

View File

@ -1,82 +1,36 @@
<template>
<el-dialog v-model="visible" title="视频" :append-to-body="appendToBody">
<ali-player
ref="playerRef"
:source="fnSrc(src)"
:vid="vid"
:play-auth="playAuth"
:visible="visible"
:cover="cover"
:autoplay="autoplay"
:show-progress="showProgress"
:play-time="playTime"
/>
<!-- 原生video标签支持大部分基础格式如MP4 -->
<video controls :src="fnSrc(src)" autoplay></video>
</el-dialog>
</template>
<script setup>
import { useVModel } from "@vueuse/core";
import AliPlayer from "@/components/ali-player/index.vue";
import { ref, watchEffect } from "vue";
import { watchEffect } from "vue";
const VITE_FILE_URL = import.meta.env.VITE_FILE_URL;
defineOptions({
name: "LayoutVideo",
});
const props = defineProps({
src: {
type: String,
default: "",
},
vid: {
type: String,
default: "",
},
playAuth: {
type: String,
default: "",
},
visible: {
type: Boolean,
required: true,
default: false,
},
appendToBody: {
type: Boolean,
default: false,
},
cover: {
type: String,
default: "",
},
autoplay: {
type: Boolean,
default: true,
},
showProgress: {
type: Boolean,
default: true,
},
playTime: {
type: Number,
default: 0,
},
src: { type: String, default: "" },
visible: { type: Boolean, required: true, default: false },
appendToBody: { type: Boolean, default: false },
autoplay: { type: Boolean, default: true },
});
const emits = defineEmits(["update:visible"]);
const visible = useVModel(props, "visible", emits);
const playerRef = ref(null);
const fnSrc = (src) => {
if (!src) return;
if (src.indexOf("http") !== -1 || src.indexOf("https") !== -1) return src;
else return VITE_FILE_URL + src;
if (src.includes("http") || src.includes("https")) return src;
return VITE_FILE_URL + src;
};
watchEffect(() => {
if (visible.value) {
playerRef.value && playerRef.value.play();
} else {
playerRef.value && playerRef.value.pause();
const video = document.querySelector("video");
if (visible.value && video) {
video.play().catch((err) => console.error("自动播放失败:", err));
} else if (video) {
video.pause();
}
});
</script>
<style scoped lang="scss"></style>

View File

@ -69,7 +69,7 @@ import { useMenuStore } from "@/pinia/menu";
import { useUserStore } from "@/pinia/user";
import { MENU } from "@/assets/js/constant";
import { getInfo, getUserInfo, logout } from "@/request/api";
import { getSpecialOperationsWarnAmount } from "@/request/special_operations";
// import { getSpecialOperationsWarnAmount } from "@/request/special_operations";
import UpdateInfo from "./components/update_info.vue";
import UpdateAvatar from "./components/update_avatar.vue";
import { checkImgExists, addingPrefixToFile } from "@/assets/js/utils.js";
@ -174,19 +174,19 @@ const fnSignOut = async () => {
userStore.$reset();
await router.replace("/login");
};
const fnSpecialOperationsWarnAmount = async () => {
const resData = await getSpecialOperationsWarnAmount({ loading: false });
if (resData.message) {
notify = ElNotification({
title: "温馨提示",
dangerouslyUseHTMLString: true,
message: resData.message,
duration: 0,
type: "warning",
});
}
};
fnSpecialOperationsWarnAmount();
// const fnSpecialOperationsWarnAmount = async () => {
// const resData = await getSpecialOperationsWarnAmount({ loading: false });
// if (resData.message) {
// notify = ElNotification({
// title: "",
// dangerouslyUseHTMLString: true,
// message: resData.message,
// duration: 0,
// type: "warning",
// });
// }
// };
// fnSpecialOperationsWarnAmount();
const fnNavigationBI = async () => {
notify && notify.close();
const res = await getRydyWebsiteStatus();

View File

@ -31,5 +31,4 @@ export const setLicensedManagementView = (params) =>
post("/licensedPersonnel/goEdit", params); // 单个持证人员
export const setSpecialOperationsImport = (params) =>
upload("/specialoperations/readExcel", params);
export const getForeNsicStatisticsList = () =>
post("/specialoperations/stats"); // 特种作业人员管理列表
export const getForeNsicStatisticsList = () => post("/specialoperations/stats"); // 特种作业人员管理列表

View File

@ -78,3 +78,6 @@ export const setIronWarnInfoFeedbackEdit = (params) =>
export const setIronEarlyWarning = (params) =>
post("/tbIronWarnInfo/earlyWarningEdit", params);
export const getRealTimeMonitoringData = (params) =>
post("/deviceMonitoring/getRealTimeMonitoringData", params);

View File

@ -8,3 +8,9 @@ export const getvideoInfoView = (params) => post("/videoInfo/goEdit", params); /
export const setvideoInfoDelete = (params) => post("/videoInfo/delete", params); // 删除
export const setUpToBi = (params) => post("/videoInfo/editZhiding", params); // 删除
export const setvideoInfoEdit = (params) => post("/videoInfo/edit", params);
export const startTransCode = (params) =>
post("/playVideo/startTranscode", params);
export const stopTransCode = (params) =>
post("/playVideo/stopTranscode", params);
export const getTranscodeStatus = (params) =>
post("/playVideo/getTranscodeStatus", params);

View File

@ -40,7 +40,7 @@
<script setup>
import { nextTick, reactive, ref } from "vue";
import { getCalendar, getWorkReminder } from "@/request/index.js";
import { getCalendar } from "@/request/index.js";
import dayjs from "dayjs";
import ScheduleAdd from "./schedule_add.vue";
@ -70,9 +70,9 @@ const fnGetCalendar = async () => {
}
};
const fnGetWorkReminder = async () => {
const resData = await getWorkReminder();
wjcNum.value = resData.wjcNum;
yjcNum.value = resData.yjcNum;
// const resData = await getWorkReminder();
// wjcNum.value = resData.wjcNum;
// yjcNum.value = resData.yjcNum;
};
const fnGetSchedule = async (CDATA, type = "") => {
const resData = await getCalendar({ CDATA });

View File

@ -21,8 +21,8 @@
<script setup>
import { onBeforeUnmount, onMounted, ref } from "vue";
import * as echarts from "echarts";
import { getChecklistCheck } from "@/request/index.js";
// import * as echarts from "echarts";
// import { getChecklistCheck } from "@/request/index.js";
import dayjs from "dayjs";
let myChart3;
@ -30,87 +30,87 @@ const dates = ref([
dayjs().startOf("year").format("YYYY-MM-DD"),
dayjs().endOf("year").format("YYYY-MM-DD"),
]);
const fnGetData = async () => {
const resData = await getChecklistCheck({
STARTTIME: dates.value[0],
ENDTIME: dates.value[1],
});
myChart3 && myChart3.dispose();
fnInitEcharts(resData.varList);
};
const fnInitEcharts = (data) => {
myChart3 = echarts.init(document.querySelector("#main3"));
const x_Data = [];
const y_Data = [];
for (let i = 0; i < data.length; i++) {
x_Data.push(data[i].percentage);
y_Data.push(data[i].name);
}
const option = {
title: {
text: "清单检查完成率统计(单位%",
top: "3%",
textStyle: {
fontSize: "14",
color: "#fff",
fontWeight: "700",
},
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
top: "15%",
containLabel: true,
},
xAxis: {
show: false,
type: "value",
boundaryGap: [0, 0.01],
},
yAxis: {
type: "category",
data: y_Data,
axisLabel: {
color: "#fff",
},
},
series: [
{
type: "bar",
data: x_Data,
label: {
show: true,
position: "right",
color: "#fff",
},
barWidth: 15,
itemStyle: {
color: function (params) {
const colorList = [
"#ee6666",
"#73c0de",
"#3ca272",
"#fc8452",
"#9a60b4",
"#5470c6",
];
return colorList[params.dataIndex];
},
},
},
],
};
myChart3.setOption(option);
};
// const fnGetData = async () => {
// const resData = await getChecklistCheck({
// STARTTIME: dates.value[0],
// ENDTIME: dates.value[1],
// });
// myChart3 && myChart3.dispose();
// fnInitEcharts(resData.varList);
// };
// const fnInitEcharts = (data) => {
// myChart3 = echarts.init(document.querySelector("#main3"));
// const x_Data = [];
// const y_Data = [];
// for (let i = 0; i < data.length; i++) {
// x_Data.push(data[i].percentage);
// y_Data.push(data[i].name);
// }
// const option = {
// title: {
// text: "%",
// top: "3%",
// textStyle: {
// fontSize: "14",
// color: "#fff",
// fontWeight: "700",
// },
// },
// tooltip: {
// trigger: "axis",
// axisPointer: {
// type: "shadow",
// },
// },
// grid: {
// left: "3%",
// right: "4%",
// bottom: "3%",
// top: "15%",
// containLabel: true,
// },
// xAxis: {
// show: false,
// type: "value",
// boundaryGap: [0, 0.01],
// },
// yAxis: {
// type: "category",
// data: y_Data,
// axisLabel: {
// color: "#fff",
// },
// },
// series: [
// {
// type: "bar",
// data: x_Data,
// label: {
// show: true,
// position: "right",
// color: "#fff",
// },
// barWidth: 15,
// itemStyle: {
// color: function (params) {
// const colorList = [
// "#ee6666",
// "#73c0de",
// "#3ca272",
// "#fc8452",
// "#9a60b4",
// "#5470c6",
// ];
// return colorList[params.dataIndex];
// },
// },
// },
// ],
// };
// myChart3.setOption(option);
// };
onMounted(() => {
fnGetData();
// fnGetData();
window.onresize = function () {
myChart3 && myChart3.resize();
};

View File

@ -94,7 +94,7 @@ import { getHiddenCount, getMemoryUsage } from "@/request/index.js";
import { reactive } from "vue";
import CountTo from "vue-countup-v3";
import { useRouter } from "vue-router";
import { getRiskManagement } from "@/request/large_screen_data_display.js";
// import { getRiskManagement } from "@/request/large_screen_data_display.js";
const router = useRouter();
const data = reactive({
@ -114,10 +114,10 @@ const data = reactive({
});
const fnGetData = async () => {
const { hidCount } = await getHiddenCount();
const { riskCount } = await getRiskManagement();
// const { riskCount } = await getRiskManagement();
const { pd: memoryUsage } = await getMemoryUsage();
data.hidCount = hidCount;
data.riskCount = riskCount;
// data.riskCount = riskCount;
data.memoryUsage = memoryUsage;
};
fnGetData();

View File

@ -6,99 +6,99 @@
<script setup>
import { onBeforeUnmount, onMounted } from "vue";
import * as echarts from "echarts";
import { getRiskManagement } from "@/request/large_screen_data_display.js";
// import * as echarts from "echarts";
// import { getRiskManagement } from "@/request/large_screen_data_display.js";
let myChart2;
const fnGetData = async () => {
const { riskAll } = await getRiskManagement();
fnInitEcharts(riskAll);
};
const fnInitEcharts = (data) => {
myChart2 = echarts.init(document.querySelector("#main2"));
let acount = 0;
let bcount = 0;
let ccount = 0;
let dcount = 0;
for (let i = 0; data.length > i; i++) {
if (data[i].LEVELID) {
if (data[i].LEVELID === "levelA") acount = data[i].COUNT;
if (data[i].LEVELID === "levelB") bcount = data[i].COUNT;
if (data[i].LEVELID === "levelC") ccount = data[i].COUNT;
if (data[i].LEVELID === "levelD") dcount = data[i].COUNT;
}
}
const option = {
title: {
text: "风险分级统计",
textStyle: {
fontSize: "14",
color: "#fff",
fontWeight: "700",
},
},
color: ["#10b9f8", "#ffc600", "#f49545", "#ec2c26"],
tooltip: {
trigger: "item",
},
grid: {},
legend: {
orient: "vertical",
left: "6%",
top: "20%",
textStyle: {
color: "#fff",
},
},
series: [
{
name: "风险类型",
type: "pie",
radius: ["30%", "70%"],
center: ["64%", "49%"],
avoidLabelOverlap: false,
itemStyle: {
borderColor: "rgb(8, 24, 58)",
borderWidth: 2,
},
label: {
show: false,
position: "center",
},
emphasis: {
label: {
show: true,
fontSize: "18",
fontWeight: "bold",
color: "#fff",
},
},
labelLine: {
show: false,
},
data: [
{
value: dcount,
name: "低风险",
},
{
value: ccount,
name: "一般风险",
},
{
value: bcount,
name: "较大风险",
},
{
value: acount,
name: "重大风险",
},
],
},
],
};
myChart2.setOption(option);
// const { riskAll } = await getRiskManagement();
// fnInitEcharts(riskAll);
};
// const fnInitEcharts = (data) => {
// myChart2 = echarts.init(document.querySelector("#main2"));
// let acount = 0;
// let bcount = 0;
// let ccount = 0;
// let dcount = 0;
// for (let i = 0; data.length > i; i++) {
// if (data[i].LEVELID) {
// if (data[i].LEVELID === "levelA") acount = data[i].COUNT;
// if (data[i].LEVELID === "levelB") bcount = data[i].COUNT;
// if (data[i].LEVELID === "levelC") ccount = data[i].COUNT;
// if (data[i].LEVELID === "levelD") dcount = data[i].COUNT;
// }
// }
// const option = {
// title: {
// text: "",
// textStyle: {
// fontSize: "14",
// color: "#fff",
// fontWeight: "700",
// },
// },
// color: ["#10b9f8", "#ffc600", "#f49545", "#ec2c26"],
// tooltip: {
// trigger: "item",
// },
// grid: {},
// legend: {
// orient: "vertical",
// left: "6%",
// top: "20%",
// textStyle: {
// color: "#fff",
// },
// },
// series: [
// {
// name: "",
// type: "pie",
// radius: ["30%", "70%"],
// center: ["64%", "49%"],
// avoidLabelOverlap: false,
// itemStyle: {
// borderColor: "rgb(8, 24, 58)",
// borderWidth: 2,
// },
// label: {
// show: false,
// position: "center",
// },
// emphasis: {
// label: {
// show: true,
// fontSize: "18",
// fontWeight: "bold",
// color: "#fff",
// },
// },
// labelLine: {
// show: false,
// },
// data: [
// {
// value: dcount,
// name: "",
// },
// {
// value: ccount,
// name: "",
// },
// {
// value: bcount,
// name: "",
// },
// {
// value: acount,
// name: "",
// },
// ],
// },
// ],
// };
// myChart2.setOption(option);
// };
onMounted(() => {
fnGetData();
window.onresize = function () {

View File

@ -5,16 +5,17 @@
:before-close="fnClose"
>
<el-form ref="formRef" :rules="rules" :model="form" label-width="150px">
<!-- <el-form-item label="设备编码" prop="EQUIP_CODE">-->
<!-- <el-input-->
<!-- v-model="form.EQUIP_CODE"-->
<!-- :disabled="type === 'edit'"-->
<!-- minlength="18"-->
<!-- maxlength="18"-->
<!-- show-word-limit-->
<!-- placeholder="企业数据接入标识12位+2位类型编码+4位流水编码 "-->
<!-- />-->
<!-- </el-form-item>-->
<!--设备编码必填 完成后端校验-->
<el-form-item label="设备编码" prop="EQUIP_CODE">
<el-input
v-model="form.EQUIP_CODE"
:disabled="type === 'edit'"
minlength="18"
maxlength="18"
show-word-limit
placeholder="企业数据接入标识12位+2位类型编码+4位流水编码 "
/>
</el-form-item>
<el-form-item label="企业内部编号" prop="DEVICE_ID">
<el-input
v-model="form.DEVICE_ID"
@ -179,9 +180,9 @@ const { visible, form } = useVModels(props, emits);
const OLD_EQUIPMENT_ID = ref(form.value.DEVICE_ID);
const rules = {
// EQUIP_CODE: [
// { required: true, message: "", trigger: "blur" },
// ],
EQUIP_CODE: [
{ required: true, message: "设备编码不能为空", trigger: "blur" },
],
DEVICE_ID: [
{ required: true, message: "企业内部编号不能为空", trigger: "change" },
],

View File

@ -0,0 +1,210 @@
<template>
<el-dialog v-model="dialogVisible" title="播放后台转码视频" width="50%">
<!-- 原生video播放器 - 移除了controls属性以去掉默认控制栏 -->
<video ref="videoRef" playsinline class="video-player"></video>
<!-- 底部操作按钮 -->
<template #footer>
<el-button type="primary" @click="dialogVisible = false">
关闭播放
</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, watch } from "vue";
import Hls from "hls.js"; // HLS
import { ElMessage } from "element-plus";
import { getTranscodeStatus, stopTransCode } from "@/request/video_info.js"; //
//
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
src: {
type: String,
required: true, // HLS
},
videoId: {
type: String,
required: true, // Id
},
});
//
const emit = defineEmits(["update:visible", "onStop"]);
// props.visible
const dialogVisible = ref(props.visible);
const videoRef = ref(null); // video
const isPlaying = ref(false); //
let hlsInstance = null; // HLS
// visibledialogVisible
watch(
() => props.visible,
(newVal) => {
dialogVisible.value = newVal;
}
);
//
let checkInterval = null;
//
const onDialogOpen = () => {
// 3
checkInterval = setInterval(async () => {
try {
// checkVideoProgress
const response = await getTranscodeStatus({ id: props.videoId });
if (response.progress > 2) {
handlePlay();
//
if (checkInterval) {
clearInterval(checkInterval);
checkInterval = null;
}
}
//
} catch (error) {}
}, 3000); // 3
};
// dialogVisible
watch(dialogVisible, (newVal) => {
emit("update:visible", newVal);
if (newVal) {
onDialogOpen();
} else {
destroyPlayer(); //
}
});
//
const handlePlay = () => {
const video = videoRef.value;
if (!video) {
ElMessage.error("视频播放器初始化失败");
return;
}
//
video.src = "";
// src
if (!props.src) {
ElMessage.error("视频源地址为空");
return;
}
// HLS.js
if (Hls.isSupported()) {
//
if (hlsInstance) {
hlsInstance.destroy();
}
hlsInstance = new Hls({
maxBufferLength: 30, //
maxMaxBufferLength: 60,
});
//
hlsInstance.on(Hls.Events.ERROR, (event, data) => {
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
hlsInstance.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
hlsInstance.recoverMediaError();
break;
default:
//
destroyPlayer();
handlePlay();
break;
}
}
});
hlsInstance.loadSource(props.src);
hlsInstance.attachMedia(video);
hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => {
video.play().then(() => {
isPlaying.value = true;
});
});
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
// SafariHLS
video.src = props.src;
video.addEventListener("loadedmetadata", () => {
video
.play()
.then(() => {
isPlaying.value = true;
ElMessage.success("视频开始播放");
})
.catch((err) => {
ElMessage.error(`播放失败:${err.message}`);
});
});
} else {
ElMessage.error("您的浏览器不支持HLS视频流播放请更换浏览器");
}
};
//
const destroyPlayer = () => {
//
if (checkInterval) {
clearInterval(checkInterval);
checkInterval = null;
}
//
isPlaying.value = false;
if (hlsInstance) {
hlsInstance.destroy();
hlsInstance = null;
}
if (videoRef.value) {
const video = videoRef.value;
video.pause();
video.src = "";
}
handleStopTranscode();
};
//
const handleStopTranscode = async () => {
try {
await stopTransCode({ id: props.videoId }); //
emit("onStop"); //
dialogVisible.value = false; //
} catch (error) {
if (error !== "cancel") {
ElMessage.error(`停止转码失败:${error.message || "未知错误"}`);
}
}
};
</script>
<style scoped>
.video-player {
width: 100%;
min-height: 600px;
object-fit: contain; /* 保持视频比例 */
background-color: #000; /* 增加黑色背景,使视频区域更明显 */
}
.video-controls {
margin-top: 16px;
text-align: center;
}
</style>

View File

@ -15,20 +15,25 @@
<el-col :span="12">
<el-form-item label-width="10px">
<el-button type="primary" native-type="submit">搜索</el-button>
<el-button native-type="reset" @click="fnResetPagination">
重置
</el-button>
<el-button native-type="reset" @click="fnResetPagination"
>重置</el-button
>
<el-button type="success" @click="handleStartTranscode"
>播放后台转码视频</el-button
>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<layout-card>
<layout-table
v-model:pagination="pagination"
:data="list"
@get-data="fnGetData"
>
<!-- 表格列定义保持不变 -->
<el-table-column label="序号" width="70">
<template #default="{ $index }">
{{ serialNumber(pagination, $index) }}
@ -51,7 +56,6 @@
</el-popover>
</template>
</el-table-column>
<el-table-column label="操作" width="250">
<template #default="{ row }">
<el-button
@ -60,27 +64,27 @@
text
link
@click="fnUpToBi(row.videomanagerId)"
>置顶</el-button
>
<!-- <el-button type="primary" text link @click="fnSetPositioning(row)">-->
<!-- {{ row.PLS_ID ? "修改定位" : "添加定位" }}-->
<!-- </el-button>-->
<el-button link type="primary" @click="fnPreviewVideo(row)"
>播放视频</el-button
>
置顶
</el-button>
<el-button type="primary" text link @click="fnSetPositioning(row)">
{{ row.PLS_ID ? "修改定位" : "添加定位" }}
</el-button>
<el-button link type="primary" @click="fnPreviewVideo(row)">
播放
</el-button>
<el-button
v-if="row.videomanagerId"
link
type="danger"
@click="fnDeleteVideo(row)"
>移除定位</el-button
>
移除定位
</el-button>
</template>
</el-table-column>
</layout-table>
</layout-card>
<!-- 其他弹窗保持不变 -->
<add
v-model:visible="data.addDialog.Visible"
v-model:form="data.addDialog.form"
@ -101,6 +105,14 @@
:videomanager-id="data.selectingPointsDialog.videomanagerId"
@get-data="fnResetPagination"
/>
<!-- 转码视频播放器修改后双向绑定visible + 传递src -->
<play-video
v-model:visible="data.transcodeVideoDialog.visible"
:src="data.transcodeVideoDialog.src"
:video-id="data.transcodeVideoDialog.id"
@on-stop="onTranscodeStopped"
/>
</div>
</template>
@ -108,7 +120,6 @@
import { serialNumber } from "@/assets/js/utils.js";
import useListData from "@/assets/js/useListData.js";
import {
getHkVideoHlsPath,
getHkVideoManagerList,
setUpToBi,
setVideoManagerDelete,
@ -119,6 +130,8 @@ import { ElMessage, ElMessageBox } from "element-plus";
import SelectingPoints from "./components/selecting_points.vue";
import LayoutVideo from "@/components/video/index.vue";
import { setVideoManagerList } from "@/request/eightwork_videomanager.js";
import PlayVideo from "@/views/video_manager/video_manager/components/playVideo.vue"; //
import { startTransCode, stopTransCode } from "@/request/video_info.js"; //
const data = reactive({
addDialog: {
@ -142,39 +155,60 @@ const data = reactive({
videomanagerId: "",
visible: false,
},
transcodeVideoDialog: {
visible: false,
src: "http://localhost:8100/api/hls/stream.m3u8", // HLS访
id: "",
},
});
//
const handleStartTranscode = async () => {
try {
await startTransCode(); //
data.transcodeVideoDialog.visible = true; //
} catch (error) {
ElMessage.error("启动转码失败: " + (error.message || "未知错误"));
}
};
//
const onTranscodeStopped = async () => {
await stopTransCode(); //
};
//
const { list, pagination, searchForm, fnGetData, fnResetPagination } =
useListData(getHkVideoManagerList);
const fnSetPositioning = async (row) => {
data.selectingPointsDialog.id = row.PLS_ID ? row.PLS_ID : "";
data.selectingPointsDialog.indexCode = row.indexCode;
data.selectingPointsDialog.regionPathName = row.regionPathName;
data.selectingPointsDialog.camName = row.name;
data.selectingPointsDialog.videomanagerId = row.videomanagerId;
data.selectingPointsDialog.visible = true;
};
//
// const fnSetPositioning = async (row) => {
// data.selectingPointsDialog.id = row.PLS_ID ? row.PLS_ID : "";
// data.selectingPointsDialog.indexCode = row.indexCode;
// data.selectingPointsDialog.regionPathName = row.regionPathName;
// data.selectingPointsDialog.camName = row.name;
// data.selectingPointsDialog.videomanagerId = row.videomanagerId;
// data.selectingPointsDialog.visible = true;
// };
const fnUpToBi = async (videomanagerId) => {
await ElMessageBox.confirm("确定要置顶吗置顶后将会默认展示在Bi页", {
type: "warning",
});
await setUpToBi({
videomanagerId,
});
ElMessage({
message: "操作成功",
type: "success",
});
fnResetPagination();
await setUpToBi({ videomanagerId });
ElMessage({ message: "操作成功", type: "success" });
};
const fnPreviewVideo = async (row) => {
data.videoDialog.visible = true;
data.videoDialog.src = "";
const resData = await getHkVideoHlsPath(row);
data.videoDialog.src = resData.data.url;
try {
const resData = await startTransCode({ url: row.url, id: row.PLS_ID }); //
data.transcodeVideoDialog.visible = true; //
data.transcodeVideoDialog.id = row.PLS_ID; //
data.transcodeVideoDialog.src =
"http://localhost:8100/api/" + resData.videoUrl + "stream.m3u8";
} catch (error) {
ElMessage.error("启动转码失败: " + (error.message || "未知错误"));
}
};
const fnDeleteVideo = async (row) => {
@ -188,16 +222,13 @@ const fnDeleteVideo = async (row) => {
}
await ElMessageBox.confirm(
"移除定位将会结束视频推送,并删除推送信息。确定要移除定位吗?",
{
type: "warning",
}
{ type: "warning" }
);
await setVideoManagerDelete({
VIDEOMANAGER_ID: row.videomanagerId,
PLS_ID: row.PLS_ID,
});
await ElMessage.success("操作成功");
fnResetPagination();
}
};
</script>