视频接收

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=/dist
#VITE_BASE_URL=http://10.199.64.27:8520/integrated_yjb/ #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/ #VITE_BASE_URL=https://qaaqwh.qhdsafety.com/integrated_whb/
#websocket t掉线 #websocket t掉线

View File

@ -22,6 +22,7 @@
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"echarts": "^5.4.3", "echarts": "^5.4.3",
"element-plus": "^2.6.1", "element-plus": "^2.6.1",
"hls.js": "^1.6.13",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"lodash-es": "^4.17.21", "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%" width="70%"
> >
<el-divider content-position="left">实时监测</el-divider> <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-card>
<layout-table <layout-table
:show-pagination="false" ref="tableRef"
:data="data.realTimeMonitoringDataList" 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="TARGET_NAME" label="监测节点名称" />
<el-table-column prop="CURRENT_VALUE" label="当前值"> <el-table-column prop="CURRENT_VALUE" label="当前值">
<template #default="{ row }"> <template #default="{ row }">
<!-- {{ formatValue(row.SIGNAL_TYPE, row.CURRENT_VALUE) }}--> <!-- {{ formatValue(row.SIGNAL_TYPE, row.CURRENT_VALUE) }}-->
{{ row.CURRENT_VALUE }} {{ row.CURRENT_VALUE }}
</template> </template>
</el-table-column> </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秒采集一次)" /> <el-table-column prop="GATHER_TIME" label="采集时间(每5秒采集一次)" />
</layout-table> </layout-table>
</layout-card> </layout-card>
@ -31,9 +73,9 @@
</template> </template>
<script setup> <script setup>
import { reactive } from "vue"; import { useVModels } from "@vueuse/core";
import { useIntervalFn, useVModels } from "@vueuse/core"; import { getRealTimeMonitoringData } from "@/request/tb_iron.js";
import { getIronAllPlcRealTimeMonitoringDataList } from "@/request/tb_iron.js"; import useListData from "@/assets/js/useListData.js";
const props = defineProps({ const props = defineProps({
visible: { visible: {
@ -52,30 +94,17 @@ const props = defineProps({
default: "", default: "",
}, },
}); });
const data = reactive({
realTimeMonitoringDataList: [],
});
const emits = defineEmits(["update:visible"]); const emits = defineEmits(["update:visible"]);
const { visible } = useVModels(props, emits); const { visible } = useVModels(props, emits);
const fnClose = () => { const fnClose = () => {
pause();
visible.value = false; visible.value = false;
}; };
const { list, pagination, searchForm, fnGetData, fnResetPagination, tableRef } =
const { pause } = useIntervalFn( useListData(getRealTimeMonitoringData, {
async () => { otherParams: {
if (props.equipmentId) { EQUIPMENT_ID: props.equipmentId,
const resData = await getIronAllPlcRealTimeMonitoringDataList({ },
EQUIPMENT_ID: props.equipmentId, });
DEVICE_TYPE: props.deviceType,
loading: false,
});
data.realTimeMonitoringDataList = resData.varList;
}
},
5000,
{ immediate: true, immediateCallback: true }
);
/* ----------------- 工具:把原始值转换成最终显示文本 ----------------- */ /* ----------------- 工具:把原始值转换成最终显示文本 ----------------- */
// function formatValue(type, val) { // function formatValue(type, val) {
// // ID // // ID

View File

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

View File

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

View File

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

View File

@ -78,3 +78,6 @@ export const setIronWarnInfoFeedbackEdit = (params) =>
export const setIronEarlyWarning = (params) => export const setIronEarlyWarning = (params) =>
post("/tbIronWarnInfo/earlyWarningEdit", 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 setvideoInfoDelete = (params) => post("/videoInfo/delete", params); // 删除
export const setUpToBi = (params) => post("/videoInfo/editZhiding", params); // 删除 export const setUpToBi = (params) => post("/videoInfo/editZhiding", params); // 删除
export const setvideoInfoEdit = (params) => post("/videoInfo/edit", 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> <script setup>
import { nextTick, reactive, ref } from "vue"; import { nextTick, reactive, ref } from "vue";
import { getCalendar, getWorkReminder } from "@/request/index.js"; import { getCalendar } from "@/request/index.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import ScheduleAdd from "./schedule_add.vue"; import ScheduleAdd from "./schedule_add.vue";
@ -70,9 +70,9 @@ const fnGetCalendar = async () => {
} }
}; };
const fnGetWorkReminder = async () => { const fnGetWorkReminder = async () => {
const resData = await getWorkReminder(); // const resData = await getWorkReminder();
wjcNum.value = resData.wjcNum; // wjcNum.value = resData.wjcNum;
yjcNum.value = resData.yjcNum; // yjcNum.value = resData.yjcNum;
}; };
const fnGetSchedule = async (CDATA, type = "") => { const fnGetSchedule = async (CDATA, type = "") => {
const resData = await getCalendar({ CDATA }); const resData = await getCalendar({ CDATA });

View File

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

View File

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

View File

@ -6,99 +6,99 @@
<script setup> <script setup>
import { onBeforeUnmount, onMounted } from "vue"; import { onBeforeUnmount, onMounted } from "vue";
import * as echarts from "echarts"; // import * as echarts from "echarts";
import { getRiskManagement } from "@/request/large_screen_data_display.js"; // import { getRiskManagement } from "@/request/large_screen_data_display.js";
let myChart2; let myChart2;
const fnGetData = async () => { const fnGetData = async () => {
const { riskAll } = await getRiskManagement(); // const { riskAll } = await getRiskManagement();
fnInitEcharts(riskAll); // 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 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(() => { onMounted(() => {
fnGetData(); fnGetData();
window.onresize = function () { window.onresize = function () {

View File

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

@ -2,9 +2,9 @@
<div> <div>
<el-card> <el-card>
<el-form <el-form
:model="searchForm" :model="searchForm"
label-width="60px" label-width="60px"
@submit.prevent="fnResetPagination" @submit.prevent="fnResetPagination"
> >
<el-row> <el-row>
<el-col :span="6"> <el-col :span="6">
@ -15,20 +15,25 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item label-width="10px"> <el-form-item label-width="10px">
<el-button type="primary" native-type="submit">搜索</el-button> <el-button type="primary" native-type="submit">搜索</el-button>
<el-button native-type="reset" @click="fnResetPagination"> <el-button native-type="reset" @click="fnResetPagination"
重置 >重置</el-button
</el-button> >
<el-button type="success" @click="handleStartTranscode"
>播放后台转码视频</el-button
>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
</el-card> </el-card>
<layout-card> <layout-card>
<layout-table <layout-table
v-model:pagination="pagination" v-model:pagination="pagination"
:data="list" :data="list"
@get-data="fnGetData" @get-data="fnGetData"
> >
<!-- 表格列定义保持不变 -->
<el-table-column label="序号" width="70"> <el-table-column label="序号" width="70">
<template #default="{ $index }"> <template #default="{ $index }">
{{ serialNumber(pagination, $index) }} {{ serialNumber(pagination, $index) }}
@ -40,9 +45,9 @@
<el-table-column property="PLS_ID" label="是否定位"> <el-table-column property="PLS_ID" label="是否定位">
<template #default="{ row }"> <template #default="{ row }">
<el-popover <el-popover
placement="top-start" placement="top-start"
trigger="hover" trigger="hover"
content="定位后才能在Bi页上展示" content="定位后才能在Bi页上展示"
> >
<template #reference> <template #reference>
<el-tag v-if="row.PLS_ID" type="success"> </el-tag> <el-tag v-if="row.PLS_ID" type="success"> </el-tag>
@ -51,55 +56,62 @@
</el-popover> </el-popover>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="250"> <el-table-column label="操作" width="250">
<template #default="{ row }"> <template #default="{ row }">
<el-button <el-button
v-if="row.ISSHOW === 0" v-if="row.ISSHOW === 0"
type="primary" type="primary"
text text
link link
@click="fnUpToBi(row.videomanagerId)" @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 <el-button
v-if="row.videomanagerId" v-if="row.videomanagerId"
link link
type="danger" type="danger"
@click="fnDeleteVideo(row)" @click="fnDeleteVideo(row)"
>移除定位</el-button
> >
移除定位
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</layout-table> </layout-table>
</layout-card> </layout-card>
<!-- 其他弹窗保持不变 -->
<add <add
v-model:visible="data.addDialog.Visible" v-model:visible="data.addDialog.Visible"
v-model:form="data.addDialog.form" v-model:form="data.addDialog.form"
:type="data.addDialog.type" :type="data.addDialog.type"
@get-data="fnResetPagination" @get-data="fnResetPagination"
/> />
<layout-video <layout-video
v-if="data.videoDialog.visible" v-if="data.videoDialog.visible"
v-model:visible="data.videoDialog.visible" v-model:visible="data.videoDialog.visible"
:src="data.videoDialog.src" :src="data.videoDialog.src"
/> />
<selecting-points <selecting-points
:id="data.selectingPointsDialog.id" :id="data.selectingPointsDialog.id"
v-model:visible="data.selectingPointsDialog.visible" v-model:visible="data.selectingPointsDialog.visible"
:index-code="data.selectingPointsDialog.indexCode" :index-code="data.selectingPointsDialog.indexCode"
:region-path-name="data.selectingPointsDialog.regionPathName" :region-path-name="data.selectingPointsDialog.regionPathName"
:cam-name="data.selectingPointsDialog.camName" :cam-name="data.selectingPointsDialog.camName"
:videomanager-id="data.selectingPointsDialog.videomanagerId" :videomanager-id="data.selectingPointsDialog.videomanagerId"
@get-data="fnResetPagination" @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> </div>
</template> </template>
@ -108,7 +120,6 @@
import { serialNumber } from "@/assets/js/utils.js"; import { serialNumber } from "@/assets/js/utils.js";
import useListData from "@/assets/js/useListData.js"; import useListData from "@/assets/js/useListData.js";
import { import {
getHkVideoHlsPath,
getHkVideoManagerList, getHkVideoManagerList,
setUpToBi, setUpToBi,
setVideoManagerDelete, setVideoManagerDelete,
@ -119,6 +130,8 @@ import { ElMessage, ElMessageBox } from "element-plus";
import SelectingPoints from "./components/selecting_points.vue"; import SelectingPoints from "./components/selecting_points.vue";
import LayoutVideo from "@/components/video/index.vue"; import LayoutVideo from "@/components/video/index.vue";
import { setVideoManagerList } from "@/request/eightwork_videomanager.js"; 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({ const data = reactive({
addDialog: { addDialog: {
@ -142,39 +155,60 @@ const data = reactive({
videomanagerId: "", videomanagerId: "",
visible: false, visible: false,
}, },
transcodeVideoDialog: {
visible: false,
src: "http://localhost:8100/api/hls/stream.m3u8", // HLS访
id: "",
},
}); });
const { list, pagination, searchForm, fnGetData, fnResetPagination } = //
useListData(getHkVideoManagerList); const handleStartTranscode = async () => {
try {
const fnSetPositioning = async (row) => { await startTransCode(); //
data.selectingPointsDialog.id = row.PLS_ID ? row.PLS_ID : ""; data.transcodeVideoDialog.visible = true; //
data.selectingPointsDialog.indexCode = row.indexCode; } catch (error) {
data.selectingPointsDialog.regionPathName = row.regionPathName; ElMessage.error("启动转码失败: " + (error.message || "未知错误"));
data.selectingPointsDialog.camName = row.name; }
data.selectingPointsDialog.videomanagerId = row.videomanagerId;
data.selectingPointsDialog.visible = true;
}; };
//
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 fnUpToBi = async (videomanagerId) => { const fnUpToBi = async (videomanagerId) => {
await ElMessageBox.confirm("确定要置顶吗置顶后将会默认展示在Bi页", { await ElMessageBox.confirm("确定要置顶吗置顶后将会默认展示在Bi页", {
type: "warning", type: "warning",
}); });
await setUpToBi({ await setUpToBi({ videomanagerId });
videomanagerId, ElMessage({ message: "操作成功", type: "success" });
});
ElMessage({
message: "操作成功",
type: "success",
});
fnResetPagination();
}; };
const fnPreviewVideo = async (row) => { const fnPreviewVideo = async (row) => {
data.videoDialog.visible = true; try {
data.videoDialog.src = ""; const resData = await startTransCode({ url: row.url, id: row.PLS_ID }); //
const resData = await getHkVideoHlsPath(row); data.transcodeVideoDialog.visible = true; //
data.videoDialog.src = resData.data.url; 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) => { const fnDeleteVideo = async (row) => {
@ -187,17 +221,14 @@ const fnDeleteVideo = async (row) => {
return; return;
} }
await ElMessageBox.confirm( await ElMessageBox.confirm(
"移除定位将会结束视频推送,并删除推送信息。确定要移除定位吗?", "移除定位将会结束视频推送,并删除推送信息。确定要移除定位吗?",
{ { type: "warning" }
type: "warning",
}
); );
await setVideoManagerDelete({ await setVideoManagerDelete({
VIDEOMANAGER_ID: row.videomanagerId, VIDEOMANAGER_ID: row.videomanagerId,
PLS_ID: row.PLS_ID, PLS_ID: row.PLS_ID,
}); });
await ElMessage.success("操作成功"); await ElMessage.success("操作成功");
fnResetPagination();
} }
}; };
</script> </script>