feat:修改绑定相机

This commit is contained in:
dzc committed 2025-08-06 17:31:07 +08:00
1 parent b1397a182e
commit 2cb3da7f83
7 files changed
+252 -11

No files matched your search

+1 -2
View File
@@ -1,11 +1,10 @@
<template>
<!-- @visible-change="(e: boolean) => e && getOptions()" -->
<el-tree-select class="stafftree" :props="{
label: 'treeLabel',
children: 'deviceChannelList',
}" node-key="treeValue" v-model="state.selectId" :placeholder="props.placeholder" :clearable="props.clearable"
:loading="state.loading" :data="state.options" :render-after-expand="false" show-checkbox multiple
style="width: 100%" filterable default-expand-all />
style="width: 100%" filterable default-expand-all @visible-change="(e: boolean) => e && getOptions()" />
</template>
<script setup lang="ts" name="select-tree-channel">
@@ -0,0 +1,208 @@
<template>
<el-tree-select class="radio-tree-select" :props="{
label: 'treeLabel',
children: 'deviceChannelList',
}" node-key="treeValue" v-model="state.selectId" :placeholder="props.placeholder" :clearable="props.clearable"
:disabled="props.disabled" :loading="state.loading" :data="state.options" :render-after-expand="false"
style="width: 100%" filterable default-expand-all @node-click="handleNodeClick" @clear="handleClear"
@visible-change="(e: boolean) => e && getOptions()" />
</template>
<script setup lang="ts" name="select-tree-channel-radio">
import * as DeviceManageApi from '/@/api/device/deviceManage'
const emit = defineEmits(["change"])
const selectId = defineModel()
type Props = {
placeholder?: string
disabled?: boolean
clearable?: boolean
autoFirst?: boolean
apiParams?: any
/** 是否只允许选择叶子节点 */
checkStrictly?: boolean
}
const props = withDefaults(
defineProps<Props>(),
{
placeholder: "请选择设备通道",
disabled: false,
clearable: true,
autoFirst: false,
apiParams: {},
checkStrictly: false,
},
)
const queryForm = reactive<any>({
name: "",
})
const state = reactive<any>({
loading: true,
selectId: selectId,
options: [],
})
// 监听外部传入的 selectId 变化
watch(
selectId,
(newVal, oldValue) => {
if (newVal === oldValue) return
state.selectId = newVal
if (!state.options.length) {
getOptions()
}
},
{ immediate: true },
)
// 监听内部 selectId 变化,同步到外部
watch(
() => state.selectId,
(newVal) => {
if (newVal !== selectId.value) {
selectId.value = newVal
emit("change", newVal)
}
}
)
// 处理节点点击事件
const handleNodeClick = (data: any, node: any) => {
// 如果设置了 checkStrictly,只允许选择叶子节点
if (props.checkStrictly && node.childNodes && node.childNodes.length > 0) {
return
}
// 单选模式,直接设置选中的值
state.selectId = data.treeValue
}
// 处理清空事件
const handleClear = () => {
state.selectId = null
}
// 获取选项数据
async function getOptions() {
try {
state.loading = true
const _pa = {
...queryForm,
...props.apiParams,
}
const { data } = await DeviceManageApi.channelTree(_pa)
state.options = data ?? []
// 如果设置了 autoFirst 且有数据,自动选择第一个
if (props.autoFirst && state.options.length > 0) {
const firstSelectableNode = findFirstSelectableNode(state.options)
if (firstSelectableNode) {
state.selectId = firstSelectableNode.treeValue
}
}
} catch (error) {
console.error('获取设备通道树数据失败:', error)
state.options = []
} finally {
setTimeout(() => (state.loading = false), 500)
}
}
// 查找第一个可选择的节点(如果设置了 checkStrictly,则查找第一个叶子节点)
const findFirstSelectableNode = (nodes: any[]): any => {
for (const node of nodes) {
if (props.checkStrictly) {
// 只选择叶子节点
if (!node.deviceChannelList || node.deviceChannelList.length === 0) {
return node
}
// 递归查找子节点
if (node.deviceChannelList && node.deviceChannelList.length > 0) {
const childResult = findFirstSelectableNode(node.deviceChannelList)
if (childResult) return childResult
}
} else {
// 可以选择任意节点
return node
}
}
return null
}
// 获取当前选中的节点信息
const getSelectedNode = () => {
if (!state.selectId) return null
const findNode = (nodes: any[], targetValue: any): any => {
for (const node of nodes) {
if (node.treeValue === targetValue) {
return node
}
if (node.deviceChannelList && node.deviceChannelList.length > 0) {
const childResult = findNode(node.deviceChannelList, targetValue)
if (childResult) return childResult
}
}
return null
}
return findNode(state.options, state.selectId)
}
// 暴露方法给父组件使用
defineExpose({
getSelectedNode,
refresh: getOptions,
clearSelection: handleClear
})
</script>
<style lang="scss">
.radio-tree-select {
.el-tree-select__tags {
// 单选模式下隐藏多选标签样式
.el-tag {
max-width: 100%;
}
}
// 隐藏复选框,因为我们使用单选模式
.el-tree-node__content .el-checkbox {
display: none !important;
}
// 添加单选样式指示
.el-tree-node__content {
position: relative;
&:hover {
background-color: var(--el-tree-node-hover-bg-color);
}
}
// 选中状态样式
.el-tree-node.is-current>.el-tree-node__content {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font-weight: 500;
&::before {
content: '';
position: absolute;
left: 8px;
top: 50%;
transform: translateY(-50%);
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var(--el-color-primary);
}
}
}
</style>
+2
View File
@@ -26,6 +26,7 @@ import SelectArea from "/@/components/Select/SelectArea.vue"
import SelectDevice from "/@/components/Select/SelectDevice.vue"
import SelectDutyType from "/@/components/Select/SelectDutyType.vue"
import SelectTreeChannel from "/@/components/Select/SelectTreeChannel.vue"
import SelectTreeChannelRadio from "/@/components/Select/SelectTreeChannelRadio.vue"
import ColumnBtn from "/@/components/Column/ColumnBtn.vue"
@@ -82,6 +83,7 @@ export default {
app.component('SelectDevice', SelectDevice);
app.component('SelectDutyType', SelectDutyType);
app.component('SelectTreeChannel', SelectTreeChannel);
app.component('SelectTreeChannelRadio', SelectTreeChannelRadio);
app.component('ColumnBtn', ColumnBtn);
@@ -19,6 +19,13 @@
<select-tree-channel v-model="form.channelIds" placeholder="请选择设备" />
</el-form-item>
</el-col>
<el-col v-if="form.id" :span="24" class="mb20">
<el-form-item label="设备" prop="channelCode">
<select-tree-channel-radio v-model="form.channelCode" placeholder="请选择设备" />
</el-form-item>
</el-col>
<div v-if="form.id">
<el-col :span="24" class="mb20">
<el-form-item label="x" prop="x">
@@ -75,6 +82,7 @@ const dataRules = ref({
name: [{ required: true, message: '房间名不能为空', trigger: 'change' }],
uniqueCode: [{ required: true, message: '唯一编码不能为空', trigger: 'change' }],
channelIds: [{ required: true, message: '设备不能为空', trigger: 'change' }],
channelCode: [{ required: true, message: '设备不能为空', trigger: 'change' }]
})
// 打开弹窗
@@ -144,6 +152,7 @@ const getareaHouseDeviceData = async (bindId: any, areaHouseId: any) => {
form.name = houseDeviceRes.houseName
form.uniqueCode = houseDeviceRes.uniqueCode
form.channelIds = houseDeviceRes.channelIds
form.channelCode = houseDeviceRes.channelCode
form.x = houseDeviceRes.x
form.y = houseDeviceRes.y
form.z = houseDeviceRes.z
@@ -47,8 +47,9 @@
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button icon="VideoPlay" text type="primary"
@click="previewVideoDialogRef.openDialog(row.deviceId)">预览</el-button>
<el-button v-if="false" icon="VideoPlay" text type="primary"
@click="previewVideoDialogRef.openDialog('channel', row.channelCode)">
预览</el-button>
<el-button icon="edit-pen" text type="primary" v-auth="'area_areaHouse_edit'"
@click="formDialogRef.openDialog(state.queryForm.houseId, row.id)">编辑</el-button>
<el-button icon="delete" text type="primary" v-auth="'area_areaHouse_del'"
@@ -62,7 +62,7 @@ import PlayerManager from "../lib/icc/PlayerManager";
let replayVideo: any = null
const state = reactive({
const state = reactive<any>({
startTimestamp: null,
endTimestamp: null,
dialogVisible: false,
@@ -89,12 +89,21 @@ const channel = reactive<any>({
})
// 打开弹窗
const openDialog = async (type: string, code: string) => {
console.log("type:", type, "code:", code);
const openDialog = async (type: string, code: string, timeRange?: { startTime: string, endTime: string }) => {
console.log("type:", type, "code:", code, "timeRange:", timeRange);
if (!type || !code) return
state.type = type
state.dialogVisible = true
if (!code) return
// 如果传入了时间范围,则设置查询表单的时间
if (timeRange) {
queryForm.videoDateList = [timeRange.startTime, timeRange.endTime]
// 同时设置时间戳
state.startTimestamp = Math.floor(new Date(timeRange.startTime).getTime() / 1000)
state.endTimestamp = Math.floor(new Date(timeRange.endTime).getTime() / 1000)
}
// 设备详情
if (type === "device") {
await getdeviceManageData(code);
@@ -202,7 +211,7 @@ const playRecord = async (channelCode?: string) => {
}
// 获取回放列表
const recordRes = await AlarmReplayVideoListApi.defaultReplayVideoList(_data)
if (recordRes.code != 0) continue
if (recordRes.code != 0 || !recordRes.data) continue
const videoRes = await AlarmReplayVideoListApi.getRtspReplayVideo(_data)
if (videoRes.code != 0 || !videoRes.data) continue
console.log("data:", videoRes.data);
+16 -3
View File
@@ -112,11 +112,11 @@
</template>
</el-table-column>
<el-table-column prop="remark" label="开门失败原因" min-width="200" show-overflow-tooltip />
<el-table-column prop="swingTime" label="刷卡时间" min-width="180" />
<el-table-column prop="swingTime" label="触发时间" min-width="180" />
<el-table-column label="操作" min-width="100" fixed="right">
<template #default="{ row }">
<el-button v-if="row.alarmList && row.alarmList.length" icon="VideoCamera" text type="primary"
@click="replayVideoDialogRef.openDialog('channel', row.channelCode)">
<el-button v-if="row.alarmList && (row.alarmList.includes('1') || row.alarmList.includes('2'))"
icon="VideoCamera" text type="primary" @click="openReplayDialog(row)">
回放
</el-button>
</template>
@@ -199,6 +199,19 @@ function getDictByValue(value: any, tag: any) {
return target.label
}
}
function openReplayDialog(row: any) {
const dateTime = row.swingTime
if (!dateTime) return
// 获取dateTime前后30秒的时间
const baseTime = new Date(dateTime)
const startDateTime = new Date(baseTime.getTime() - 30 * 1000)
const endDateTime = new Date(baseTime.getTime() + 30 * 1000)
const startTime = parseTime(startDateTime, '{y}-{m}-{d} {h}:{i}:{s}')
const endTime = parseTime(endDateTime, '{y}-{m}-{d} {h}:{i}:{s}')
replayVideoDialogRef.value.openDialog('channel', row.channelCode, { startTime, endTime })
}
</script>
<style lang="scss" scoped></style>