|
|
@@ -1,45 +1,100 @@
|
|
|
<template>
|
|
|
+ <div class="camera-config-container">
|
|
|
+ <!-- 统一使用多点位配置界面 -->
|
|
|
+ <div class="flex left fw-b fs-16 mar-top-20" style="padding-left: 100px">
|
|
|
+ {{ isMultiCameraMode ? '多点位相机配置' : '相机配置' }}<span style="color: #FD5E1A">(<el-icon style="position: relative; top:2px; margin: 0 3px"><WarningFilled /></el-icon>{{ isMultiCameraMode ? '每个点位绑定独立相机' : '点击点位切换相机配置' }})</span>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="flex left fw-b fs-16 mar-top-20" style="padding-left: 100px">相机ISO参数<span style="color: #FD5E1A">(<el-icon style="position: relative; top:2px; margin: 0 3px"><WarningFilled /></el-icon>相机设置ISO auto时无效)</span></div>
|
|
|
- <div class="selectBox">
|
|
|
- <div class="form-item" style="padding-bottom: 30px;">
|
|
|
- <div class="iso-inputs mar-top-20">
|
|
|
- <div class="iso-group">
|
|
|
- <span class="iso-label">拍照时:</span>
|
|
|
- <div class="select-wrapper">
|
|
|
- <el-select
|
|
|
- v-model="iso_config.low"
|
|
|
- filterable
|
|
|
- default-first-option
|
|
|
- placeholder="请选择或输入ISO值"
|
|
|
- class="iso-input"
|
|
|
- >
|
|
|
- <el-option
|
|
|
- v-for="item in iso_options"
|
|
|
- :key="item"
|
|
|
- :label="item"
|
|
|
- :value="item"
|
|
|
- />
|
|
|
- </el-select>
|
|
|
- </div>
|
|
|
+ <!-- 点位切换Tab -->
|
|
|
+ <div class="selectBox multi-camera-config">
|
|
|
+ <!-- 点位切换按钮 -->
|
|
|
+ <div class="point-tabs">
|
|
|
+ <div
|
|
|
+ v-for="point in displayPointList"
|
|
|
+ :key="point.key"
|
|
|
+ class="point-tab"
|
|
|
+ :class="{ active: activePoint === point.key }"
|
|
|
+ @click="switchPoint(point.key)"
|
|
|
+ >
|
|
|
+ <span class="tab-name">点位 {{ point.key }}</span>
|
|
|
+ <el-tag v-if="isMultiCameraMode" :type="point.connected ? 'success' : 'danger'" size="small">
|
|
|
+ {{ point.connected ? '已连接' : '未连接' }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 当前点位配置 -->
|
|
|
+ <div class="current-point-config">
|
|
|
+ <div class="config-header">
|
|
|
+ <span class="config-title">点位 {{ activePoint }} 配置</span>
|
|
|
</div>
|
|
|
- <div class="iso-group">
|
|
|
- <span class="iso-label">预览时:</span>
|
|
|
- <div class="select-wrapper">
|
|
|
- <el-select
|
|
|
- v-model="iso_config.high"
|
|
|
- filterable
|
|
|
- default-first-option
|
|
|
- placeholder="请选择或输入ISO值"
|
|
|
- class="iso-input"
|
|
|
- >
|
|
|
- <el-option
|
|
|
- v-for="item in iso_options"
|
|
|
- :key="item"
|
|
|
- :label="item"
|
|
|
- :value="item"
|
|
|
- />
|
|
|
- </el-select>
|
|
|
+
|
|
|
+ <div class="config-body">
|
|
|
+ <!-- 多相机模式下显示相机绑定 -->
|
|
|
+ <div v-if="isMultiCameraMode" class="form-item">
|
|
|
+ <label>绑定相机:</label>
|
|
|
+ <div class="camera-select-wrapper">
|
|
|
+ <el-select
|
|
|
+ v-model="multiConfig[activePoint].CameraKey"
|
|
|
+ placeholder="请选择相机"
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ @change="onCameraChange(activePoint)"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="camera in availableCameras"
|
|
|
+ :key="camera.CameraKey"
|
|
|
+ :label="camera.CameraName"
|
|
|
+ :value="camera.CameraKey"
|
|
|
+ :disabled="!camera.CameraStatus"
|
|
|
+ >
|
|
|
+ <span>{{ camera.CameraName }}</span>
|
|
|
+ <el-tag v-if="!camera.CameraStatus" type="danger" size="small" style="margin-left: 8px">未连接</el-tag>
|
|
|
+ </el-option>
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- ISO配置 -->
|
|
|
+ <div class="iso-section">
|
|
|
+ <div class="iso-group">
|
|
|
+ <span class="iso-label">拍照ISO:</span>
|
|
|
+ <div class="select-wrapper">
|
|
|
+ <el-select
|
|
|
+ v-model="multiConfig[activePoint].iso.low"
|
|
|
+ filterable
|
|
|
+ default-first-option
|
|
|
+ placeholder="请选择"
|
|
|
+ class="iso-select"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in iso_options"
|
|
|
+ :key="item"
|
|
|
+ :label="item"
|
|
|
+ :value="item"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="iso-group">
|
|
|
+ <span class="iso-label">预览ISO:</span>
|
|
|
+ <div class="select-wrapper">
|
|
|
+ <el-select
|
|
|
+ v-model="multiConfig[activePoint].iso.high"
|
|
|
+ filterable
|
|
|
+ default-first-option
|
|
|
+ placeholder="请选择"
|
|
|
+ class="iso-select"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in iso_options"
|
|
|
+ :key="item"
|
|
|
+ :label="item"
|
|
|
+ :value="item"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -48,13 +103,16 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { reactive, onMounted, ref, watch } from 'vue'
|
|
|
+import { reactive, onMounted, ref, watch, computed } from 'vue'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
import client from "@/stores/modules/client";
|
|
|
import icpList from '@/utils/ipc';
|
|
|
import socket from "@/stores/modules/socket.js";
|
|
|
+import useUserInfo from '@/stores/modules/user';
|
|
|
+
|
|
|
const clientStore = client();
|
|
|
-const socketStore = socket(); // WebSocket状态管理实例
|
|
|
+const socketStore = socket();
|
|
|
+const userInfoStore = useUserInfo();
|
|
|
|
|
|
// 定义props
|
|
|
const props = defineProps({
|
|
|
@@ -67,30 +125,186 @@ const props = defineProps({
|
|
|
// 定义emits
|
|
|
const emit = defineEmits(['update:camera_configs'])
|
|
|
|
|
|
-const iso_config = reactive({
|
|
|
- low: 100,
|
|
|
- high: 6000,
|
|
|
- mode: "auto22"
|
|
|
-})
|
|
|
+// 是否为多相机模式
|
|
|
+const isMultiCameraMode = computed(() => userInfoStore.isMultiCameraMode)
|
|
|
+
|
|
|
+// 当前选中的点位
|
|
|
+const activePoint = ref('A')
|
|
|
|
|
|
const iso_options = ref(['auto', 100, 200, 400, 800, 1600, 3200, 6400, 12800])
|
|
|
|
|
|
-// 监听iso_config变化并更新父组件
|
|
|
-watch(iso_config, (newVal) => {
|
|
|
+// 相机列表(通过 IPC 获取,不再依赖 store)
|
|
|
+const cameraList = ref([])
|
|
|
+
|
|
|
+// 点位列表
|
|
|
+const pointList = [
|
|
|
+ { key: 'A', connected: false },
|
|
|
+ { key: 'B', connected: false },
|
|
|
+ { key: 'C', connected: false },
|
|
|
+]
|
|
|
+
|
|
|
+// 多相机模式的配置结构(统一数据结构)
|
|
|
+const multiConfig = reactive({
|
|
|
+ A: {
|
|
|
+ iso: { low: '100', high: '6400' },
|
|
|
+ CameraKey: '',
|
|
|
+ CameraName: ''
|
|
|
+ },
|
|
|
+ B: {
|
|
|
+ iso: { low: '100', high: '6400' },
|
|
|
+ CameraKey: '',
|
|
|
+ CameraName: ''
|
|
|
+ },
|
|
|
+ C: {
|
|
|
+ iso: { low: '100', high: '6400' },
|
|
|
+ CameraKey: '',
|
|
|
+ CameraName: ''
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 根据模式显示不同的点位列表
|
|
|
+const displayPointList = computed(() => {
|
|
|
+ if (isMultiCameraMode.value) {
|
|
|
+ return pointList
|
|
|
+ } else {
|
|
|
+ // 单相机模式只显示点位A
|
|
|
+ return pointList.filter(p => p.key === 'A')
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 其他点位(用于多相机模式预览)
|
|
|
+// const otherPoints = computed(() => {
|
|
|
+// return pointList.filter(p => p.key !== activePoint.value)
|
|
|
+// })
|
|
|
+
|
|
|
+// 从本地 cameraList 获取可用相机列表(过滤掉已被其他点位绑定的相机)
|
|
|
+const availableCameras = computed(() => {
|
|
|
+ const cameras = cameraList.value || []
|
|
|
+ if (!isMultiCameraMode.value) {
|
|
|
+ return cameras
|
|
|
+ }
|
|
|
+ // 找出当前点位绑定的相机key
|
|
|
+ const currentBoundCameraKey = multiConfig[activePoint.value].CameraKey
|
|
|
+ // 返回未绑定或当前点位已绑定的相机
|
|
|
+ return cameras.filter(cam => {
|
|
|
+ // 如果是当前点位已绑定的相机,允许显示
|
|
|
+ if (cam.CameraKey === currentBoundCameraKey) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ // 检查是否被其他点位绑定
|
|
|
+ const isBoundByOtherPoint = Object.entries(multiConfig).some(([key, config]) => {
|
|
|
+ return key !== activePoint.value && config.CameraKey === cam.CameraKey
|
|
|
+ })
|
|
|
+ return !isBoundByOtherPoint
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+// 切换点位
|
|
|
+const switchPoint = (pointKey) => {
|
|
|
+ activePoint.value = pointKey
|
|
|
+}
|
|
|
+
|
|
|
+// 更新点位连接状态
|
|
|
+const updatePointConnectionStatus = () => {
|
|
|
+ const cameras = cameraList.value || []
|
|
|
+ pointList.forEach(point => {
|
|
|
+ const boundCamera = multiConfig[point.key].CameraKey
|
|
|
+ if (boundCamera) {
|
|
|
+ const camera = cameras.find(c => c.CameraKey === boundCamera)
|
|
|
+ point.connected = camera ? camera.CameraStatus : false
|
|
|
+ } else {
|
|
|
+ point.connected = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 通过 IPC 获取相机列表
|
|
|
+const fetchCameraList = () => {
|
|
|
+ clientStore.ipc.invoke(icpList.camera.getCameraList).then(result => {
|
|
|
+ if (result && result.CameraLists && Array.isArray(result.CameraLists)) {
|
|
|
+ cameraList.value = result.CameraLists
|
|
|
+ // 自动分配相机到点位
|
|
|
+ if (isMultiCameraMode.value) {
|
|
|
+ autoAssignCameras()
|
|
|
+ }
|
|
|
+ updatePointConnectionStatus()
|
|
|
+ }
|
|
|
+ }).catch(err => {
|
|
|
+ console.error('获取相机列表失败:', err)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 自动分配相机到点位
|
|
|
+const autoAssignCameras = () => {
|
|
|
+ const cameras = cameraList.value || []
|
|
|
+ const unassignedCameras = cameras.filter(cam => {
|
|
|
+ return !Object.values(multiConfig).some(p => p.CameraKey === cam.CameraKey)
|
|
|
+ })
|
|
|
+ const points = ['A', 'B', 'C']
|
|
|
+ unassignedCameras.slice(0, 3).forEach((cam, index) => {
|
|
|
+ if (points[index]) {
|
|
|
+ multiConfig[points[index]].CameraKey = cam.CameraKey
|
|
|
+ multiConfig[points[index]].CameraName = cam.CameraName
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 相机选择变更
|
|
|
+const onCameraChange = (pointKey) => {
|
|
|
+ const cameraKey = multiConfig[pointKey].CameraKey
|
|
|
+ if (cameraKey) {
|
|
|
+ const camera = cameraList.value.find(c => c.CameraKey === cameraKey)
|
|
|
+ if (camera) {
|
|
|
+ multiConfig[pointKey].CameraName = camera.CameraName
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 清除绑定时也清空 CameraName
|
|
|
+ multiConfig[pointKey].CameraName = ''
|
|
|
+ }
|
|
|
+ updatePointConnectionStatus()
|
|
|
+}
|
|
|
+
|
|
|
+// 监听multiConfig变化并更新父组件
|
|
|
+watch(multiConfig, (newVal) => {
|
|
|
emit('update:camera_configs', {
|
|
|
- iso_config,
|
|
|
+ iso_config: multiConfig,
|
|
|
})
|
|
|
}, { deep: true })
|
|
|
|
|
|
+// 初始化数据
|
|
|
onMounted(() => {
|
|
|
- // 从props初始化数据
|
|
|
- if (props.camera_configs.iso_config.low !== undefined) {
|
|
|
- iso_config.low = props.camera_configs.iso_config.low
|
|
|
- }
|
|
|
- if (props.camera_configs.iso_config.high !== undefined) {
|
|
|
- iso_config.high = props.camera_configs.iso_config.high
|
|
|
+ // 解析配置数据
|
|
|
+ if (props.camera_configs.iso_config) {
|
|
|
+ const config = props.camera_configs.iso_config
|
|
|
+
|
|
|
+ // 判断是旧格式还是新格式
|
|
|
+ if (config.low !== undefined || config.high !== undefined) {
|
|
|
+ // 旧格式(单相机旧数据) -> 转换为新格式
|
|
|
+ multiConfig.A.iso.low = String(config.low || '100')
|
|
|
+ multiConfig.A.iso.high = String(config.high || '6400')
|
|
|
+ } else {
|
|
|
+ // 新格式 - A/B/C 结构
|
|
|
+ Object.keys(config).forEach(key => {
|
|
|
+ if (multiConfig[key] !== undefined) {
|
|
|
+ if (config[key].iso) {
|
|
|
+ multiConfig[key].iso.low = config[key].iso.low || '100'
|
|
|
+ multiConfig[key].iso.high = config[key].iso.high || '6400'
|
|
|
+ }
|
|
|
+ if (config[key].CameraKey) {
|
|
|
+ multiConfig[key].CameraKey = config[key].CameraKey
|
|
|
+ multiConfig[key].CameraName = config[key].CameraName || ''
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ // 单相机模式默认选中点位A
|
|
|
+ activePoint.value = 'A'
|
|
|
+
|
|
|
+ // 通过 IPC 获取相机列表
|
|
|
+ fetchCameraList()
|
|
|
+
|
|
|
// 读取设备当前可用的 ISO 档位
|
|
|
clientStore.ipc.removeAllListeners(icpList.socket.message + '_smart_shooter_get_camera_property');
|
|
|
socketStore.sendMessage({
|
|
|
@@ -109,77 +323,140 @@ onMounted(() => {
|
|
|
})
|
|
|
|
|
|
// 暴露保存方法,给父组件调用
|
|
|
-const save = () => {
|
|
|
- // 必填校验(允许填写数字或 'auto')
|
|
|
+const save = () => {
|
|
|
+ // 校验当前点位配置
|
|
|
+ const current = multiConfig[activePoint.value]
|
|
|
const isEmpty = (v) => v === undefined || v === null || v === ''
|
|
|
- if (isEmpty(iso_config.low)) {
|
|
|
- ElMessage.error('请填写“用曝光灯时”的 ISO 值')
|
|
|
+
|
|
|
+ if (isEmpty(current.iso.low)) {
|
|
|
+ ElMessage.error('请选择拍照ISO')
|
|
|
return false
|
|
|
}
|
|
|
- if (isEmpty(iso_config.high)) {
|
|
|
- ElMessage.error('请填写“不用时”的 ISO 值')
|
|
|
+ if (isEmpty(current.iso.high)) {
|
|
|
+ ElMessage.error('请选择预览ISO')
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
- // 若均为数字,做简单范围校验(>0)
|
|
|
- const lowNum = Number(iso_config.low)
|
|
|
- const highNum = Number(iso_config.high)
|
|
|
+ const lowNum = Number(current.iso.low)
|
|
|
+ const highNum = Number(current.iso.high)
|
|
|
if (!Number.isNaN(lowNum) && lowNum <= 0) {
|
|
|
- ElMessage.error('“用曝光灯时”的 ISO 必须大于 0')
|
|
|
+ ElMessage.error('拍照ISO必须大于0')
|
|
|
return false
|
|
|
}
|
|
|
if (!Number.isNaN(highNum) && highNum <= 0) {
|
|
|
- ElMessage.error('“不用时”的 ISO 必须大于 0')
|
|
|
+ ElMessage.error('预览ISO必须大于0')
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
- return true;
|
|
|
-/* return await new Promise((resolve, reject) => {
|
|
|
- clientStore.ipc.removeAllListeners(icpList.setting.updateSysConfigs);
|
|
|
- clientStore.ipc.send(icpList.setting.updateSysConfigs,{
|
|
|
- key: 'camera_configs',
|
|
|
- value: JSON.stringify({
|
|
|
- iso_low: iso_config.low,
|
|
|
- iso_high: iso_config.high
|
|
|
- })
|
|
|
- });
|
|
|
-
|
|
|
- clientStore.ipc.on(icpList.setting.updateSysConfigs, async (event, result) => {
|
|
|
- clientStore.ipc.removeAllListeners(icpList.setting.updateSysConfigs);
|
|
|
- if(result.code === 0 && result.msg){
|
|
|
- resolve(true)
|
|
|
- } else {
|
|
|
- resolve(false)
|
|
|
- }
|
|
|
- });
|
|
|
- });*/
|
|
|
+ return true
|
|
|
}
|
|
|
|
|
|
defineExpose({ save })
|
|
|
-
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
-.iso-inputs {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 16px;
|
|
|
+.selectBox {
|
|
|
+ padding-top: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.multi-camera-config {
|
|
|
+ padding-top: 20px;
|
|
|
+ padding-left: 100px;
|
|
|
}
|
|
|
|
|
|
-.iso-group {
|
|
|
+// 点位切换Tab
|
|
|
+.point-tabs {
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
gap: 12px;
|
|
|
+ margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
-.iso-label {
|
|
|
- min-width: 80px;
|
|
|
- font-size: 14px;
|
|
|
- color: #1A1A1A;
|
|
|
+.point-tab {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 10px 20px;
|
|
|
+ background: #F5F7FA;
|
|
|
+ border: 2px solid #E4E7ED;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #C0C4CC;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background: #ECF5FF;
|
|
|
+ border-color: #409EFF;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tab-name {
|
|
|
+ font-weight: bold;
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-.iso-input {
|
|
|
- width: 200px;
|
|
|
+// 当前点位配置卡片
|
|
|
+.current-point-config {
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #E4E7ED;
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ max-width: 500px;
|
|
|
+
|
|
|
+ .config-header {
|
|
|
+ padding: 16px 20px;
|
|
|
+ background: #F5F7FA;
|
|
|
+ border-bottom: 1px solid #E4E7ED;
|
|
|
+
|
|
|
+ .config-title {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-body {
|
|
|
+ padding: 24px 20px;
|
|
|
+
|
|
|
+ .form-item {
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ label {
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #606266;
|
|
|
+ }
|
|
|
+
|
|
|
+ .camera-select-wrapper {
|
|
|
+ max-width: 300px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .iso-section {
|
|
|
+ display: flex;
|
|
|
+ gap: 20px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .iso-group {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+
|
|
|
+ .iso-label {
|
|
|
+ min-width: 70px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #1A1A1A;
|
|
|
+ }
|
|
|
+
|
|
|
+ .iso-select {
|
|
|
+ width: 150px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.select-wrapper {
|
|
|
@@ -187,7 +464,12 @@ defineExpose({ save })
|
|
|
border-radius: 6px;
|
|
|
}
|
|
|
}
|
|
|
-.selectBox {
|
|
|
- padding-top: 10px;
|
|
|
+
|
|
|
+:deep(.el-select) {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.camera-select-wrapper .el-select) {
|
|
|
+ width: 300px;
|
|
|
}
|
|
|
</style>
|