V4修复: 改用vol-box弹窗+Vue3写法 全部写在base_device.vue中

This commit is contained in:
2026-05-17 14:14:53 +08:00
parent 652513724b
commit cce032fe91

View File

@@ -1,9 +1,5 @@
<!--
*Authorjxx
*Date{Date}
*Contact283591387@qq.com
*业务请在@/extension/warehouse/device_manager/base_device.jsx或base_device.vue文件编写
*新版本支持vue或.jsx]文件编写业务,文档见:https://doc.volcore.xyz/docs/view-grid、https://doc.volcore.xyz/docs/web
-->
<template>
<view-grid ref="grid"
@@ -24,98 +20,159 @@
:rowClick="rowClick"
:modelOpenBefore="modelOpenBefore"
:modelOpenAfter="modelOpenAfter">
<!-- 自定义组件数据槽扩展更多数据槽slot见文档 -->
<template #gridHeader>
</template>
</view-grid>
<!-- 实时预览 -->
<vol-box :lazy="true" v-model="previewVisible" title="实时预览" :width="960" :padding="10">
<div style="background:#000;min-height:400px;display:flex;align-items:center;justify-content:center">
<div v-if="!wsFlv" style="color:#fff">{{ loading ? '加载中...' : '无法获取流地址' }}</div>
<video v-else ref="playerRef" :src="wsFlv" autoplay muted controls style="width:100%;height:450px" />
</div>
<template #footer><el-button size="small" @click="previewVisible=false">关闭</el-button></template>
</vol-box>
<!-- 云台控制 -->
<vol-box :lazy="true" v-model="ptzVisible" title="云台控制" :width="320" :padding="10">
<div style="display:flex;flex-direction:column;align-items:center;gap:4px">
<el-button circle @mousedown="ptzGo('up')" @mouseup="ptzStop" @mouseleave="ptzStop"><el-icon><ArrowUp /></el-icon></el-button>
<div style="display:flex;gap:8px">
<el-button circle @mousedown="ptzGo('left')" @mouseup="ptzStop"><el-icon><ArrowLeft /></el-icon></el-button>
<el-button circle type="danger" @click="ptzGo('stop')"><el-icon><Close /></el-icon></el-button>
<el-button circle @mousedown="ptzGo('right')" @mouseup="ptzStop"><el-icon><ArrowRight /></el-icon></el-button>
</div>
<el-button circle @mousedown="ptzGo('down')" @mouseup="ptzStop"><el-icon><ArrowDown /></el-icon></el-button>
<div style="display:flex;gap:8px;margin-top:8px">
<el-button circle @mousedown="ptzGo('zoom_in')" @mouseup="ptzStop">+</el-button>
<el-button circle @mousedown="ptzGo('zoom_out')" @mouseup="ptzStop">-</el-button>
</div>
</div>
</vol-box>
<!-- 实时数据 -->
<vol-box :lazy="true" v-model="realtimeVisible" title="实时数据" :width="600" :padding="10">
<el-table :data="realtimeValues" v-loading="realtimeLoading" stripe>
<el-table-column prop="pointIndex" label="点位" width="80" />
<el-table-column prop="value" label="当前值" width="120" />
<el-table-column prop="updateTime" label="更新时间" min-width="160" />
<el-table-column prop="interval" label="采集间隔(s)" width="100" />
</el-table>
<template #footer><el-button size="small" @click="realtimeVisible=false">关闭</el-button></template>
</vol-box>
<!-- 设备控制 -->
<vol-box :lazy="true" v-model="controlVisible" title="设备控制" :width="400" :padding="10">
<el-form label-width="80px">
<el-form-item label="点位索引"><el-input-number v-model="ctrlPointIndex" :min="0" /></el-form-item>
<el-form-item label="目标值"><el-input-number v-model="ctrlValue" :step="0.1" /></el-form-item>
</el-form>
<template #footer>
<el-button type="primary" size="small" @click="controlSend">发送指令</el-button>
<el-button size="small" @click="controlVisible=false">取消</el-button>
</template>
</vol-box>
</template>
<script setup lang="jsx">
import viewOptions from './base_device/options.js'
import viewOptions from './base_device/options.js'
import { ref, reactive, getCurrentInstance, watch, onMounted, h } from "vue";
import DeviceLivePreview from "./base_device/components/DeviceLivePreview.vue";
import PtzControlPanel from "./base_device/components/PtzControlPanel.vue";
import RealtimeDataPanel from "./base_device/components/RealtimeDataPanel.vue";
import DeviceControlPanel from "./base_device/components/DeviceControlPanel.vue";
const grid = ref(null);
const { proxy } = getCurrentInstance()
//http请求proxy.http.post/get
const { table, editFormFields, editFormOptions, searchFormFields, searchFormOptions, columns, detail, details } = reactive(viewOptions())
let gridRef;//对应[表.jsx]文件中this.使用方式一样
//生成对象属性初始化
const onInit = async ($vm) => {
gridRef = $vm;
//与jsx中的this.xx使用一样只需将this.xx改为gridRef.xx
//更多属性见https://doc.volcore.xyz/docs/view-grid
}
//生成对象属性初始化后,操作明细表配置用到
const onInited = async () => {
// 自定义操作列:按 DeviceGroup 渲染对应按钮
const curDev = ref(null);
const pv = ref(false), ptz = ref(false), rt = ref(false), ctrl = ref(false);
const GW = 'http://localhost:5100'
let gridRef, _timer;
//── 对话框状态 ──
const curDev = ref(null);
const previewVisible = ref(false), ptzVisible = ref(false);
const realtimeVisible = ref(false), controlVisible = ref(false);
const wsFlv = ref(null), loading = ref(false);
const realtimeValues = ref([]), realtimeLoading = ref(false);
const ctrlPointIndex = ref(0), ctrlValue = ref(0);
//── 预览取流 ──
const openPreview = async (d) => {
curDev.value = d; wsFlv.value = null; loading.value = true; previewVisible.value = true;
try {
const r = await fetch(`${GW}/api/gateway/streams/${d.adapterCode}/${d.sourceId}/live`);
const j = await r.json();
wsFlv.value = j.wsFlv || j.httpFlv;
} catch {} finally { loading.value = false }
}
//── 云台 ──
const ptzSend = (dir, speed = 0.5) => {
fetch(`${GW}/api/gateway/streams/${curDev.value?.adapterCode}/${curDev.value?.sourceId}/ptz`, {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({ direction: dir, action: dir === 'stop' ? 'stop' : 'continuous', speed })
})
}
const ptzGo = (d) => ptzSend(d)
const ptzStop = () => ptzSend('stop')
//── 实时数据 ──
const fetchRealtime = async () => {
if (!curDev.value) return; realtimeLoading.value = true;
try {
const r = await fetch(`${GW}/api/gateway/realtime/${curDev.value.adapterCode}/${curDev.value.sourceId}`);
realtimeValues.value = await r.json();
} catch {} finally { realtimeLoading.value = false }
}
const openRealtime = (d) => { curDev.value = d; realtimeVisible.value = true; fetchRealtime(); _timer = setInterval(fetchRealtime, 5000) }
watch(realtimeVisible, v => { if (!v) clearInterval(_timer) })
//── 控制 ──
const controlSend = () => {
fetch(`${GW}/api/gateway/realtime/${curDev.value?.adapterCode}/control`, {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({ deviceId: curDev.value?.sourceId, pointIndex: ctrlPointIndex.value, value: ctrlValue.value })
})
controlVisible.value = false
}
const openControl = (d) => { curDev.value = d; controlVisible.value = true }
//── 框架钩子 ──
const onInit = async ($vm) => { gridRef = $vm; }
const onInited = async () => {
// 自定义操作列
columns.push({
title: '操作', field: '_actions', width: 300, align: 'left', fixed: 'right',
render: (_h, { row }) => {
if (row.DeviceGroup === '视频设备') {
return _h('div', [
_h('el-button', { size: 'small', type: 'primary',
onClick: () => { curDev.value = row; pv.value = true }
h('el-button', { size: 'small', type: 'primary',
onClick: () => openPreview(row)
}, { default: () => '预览' }),
_h('el-button', { size: 'small',
onClick: () => { curDev.value = row; ptz.value = true }
h('el-button', { size: 'small',
onClick: () => { curDev.value = row; ptzVisible.value = true }
}, { default: () => '云台' }),
_h(DeviceLivePreview, { modelValue: pv.value, 'onUpdate:modelValue': v => pv.value = v, device: curDev.value }),
_h(PtzControlPanel, { modelValue: ptz.value, 'onUpdate:modelValue': v => ptz.value = v, device: curDev.value }),
]);
}
if (row.DeviceGroup === 'IoT设备') {
return _h('div', [
_h('el-button', { size: 'small', type: 'primary',
onClick: () => { curDev.value = row; rt.value = true }
h('el-button', { size: 'small', type: 'primary',
onClick: () => openRealtime(row)
}, { default: () => '实时数据' }),
_h('el-button', { size: 'small',
onClick: () => { curDev.value = row; ctrl.value = true }
h('el-button', { size: 'small',
onClick: () => openControl(row)
}, { default: () => '控制' }),
_h(RealtimeDataPanel, { modelValue: rt.value, 'onUpdate:modelValue': v => rt.value = v, device: curDev.value }),
_h(DeviceControlPanel, { modelValue: ctrl.value, 'onUpdate:modelValue': v => ctrl.value = v, device: curDev.value }),
]);
}
return null;
}
});
const gcol = columns.find(c => c.field === 'DeviceGroup');
if (gcol) gcol.hidden = false;
}
const searchBefore = async (param) => {
//界面查询前,可以给param.wheres添加查询参数
//返回false则不会执行查询
return true;
}
const searchAfter = async (rows, result) => {
return true;
}
const addBefore = async (formData) => {
//新建保存前formData为对象包括明细表可以给给表单设置值自己输出看formData的值
return true;
}
const updateBefore = async (formData) => {
//编辑保存前formData为对象包括明细表、删除行的Id
return true;
}
const rowClick = ({ row, column, event }) => {
//查询界面点击行事件
// grid.value.toggleRowSelection(row); //单击行时选中当前行;
}
const modelOpenBefore = async (row) => {//弹出框打开后方法
return true;//返回false不会打开弹出框
}
const modelOpenAfter = (row) => {
//弹出框打开后方法,设置表单默认值,按钮操作等
}
//监听表单输入,做实时计算
//watch(() => editFormFields.字段,(newValue, oldValue) => { })
//对外暴露数据
const searchBefore = async (param) => { return true; }
const searchAfter = async (rows, result) => { return true; }
const addBefore = async (formData) => { return true; }
const updateBefore = async (formData) => { return true; }
const rowClick = ({ row, column, event }) => {}
const modelOpenBefore = async (row) => { return true; }
const modelOpenAfter = (row) => {}
defineExpose({})
</script>