feat:修改绑定相机
This commit is contained in:
1 parent
b1397a182e
commit
2cb3da7f83
7 files changed
+252
-11
No files matched your search
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user