CameraConfig.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <template>
  2. <div class="camera-config-container">
  3. <div class="selectBox multi-camera-config" v-id>
  4. <div class="point-tabs" v-if="isMultiCameraMode">
  5. <div
  6. v-for="point in displayPointList"
  7. :key="point.key"
  8. class="point-tab"
  9. :class="{ active: activePoint === point.key }"
  10. @click="switchPoint(point.key)"
  11. >
  12. <span class="tab-name">{{ $t('cameraConfig.pointLabel', { point: point.key }) }}</span>
  13. <el-tag :type="getPointTagInfo(point.key).type" size="small">
  14. {{ getPointTagInfo(point.key).text }}
  15. </el-tag>
  16. </div>
  17. <el-button
  18. type="primary"
  19. link
  20. class="refresh-btn"
  21. :loading="refreshing"
  22. @click="handleRefresh"
  23. >
  24. <el-icon><Refresh /></el-icon>
  25. {{ $t('cameraConfig.refresh') }}
  26. </el-button>
  27. </div>
  28. <div class="current-point-config">
  29. <div class="config-body">
  30. <div v-if="isMultiCameraMode" class="form-item">
  31. <label class="iso-label">{{ $t('cameraConfig.bindCamera') }}</label>
  32. <div class="camera-select-wrapper">
  33. <el-select
  34. v-model="multiConfig[activePoint].CameraKey"
  35. :placeholder="$t('cameraConfig.selectCamera')"
  36. filterable
  37. clearable
  38. @change="onCameraChange(activePoint)"
  39. @focus="ensureCurrentCameraInList"
  40. >
  41. <el-option
  42. v-for="camera in availableCameras"
  43. :key="camera.CameraKey"
  44. :label="camera.CameraName"
  45. :value="camera.CameraKey"
  46. :disabled="!camera.CameraStatus"
  47. >
  48. <span>{{ camera.CameraName }}</span>
  49. <el-tag v-if="!camera.CameraStatus" type="danger" size="small" style="margin-left: 8px">{{ $t('cameraConfig.notConnected') }}</el-tag>
  50. </el-option>
  51. </el-select>
  52. </div>
  53. </div>
  54. <div class="iso-section">
  55. <div class="iso-group">
  56. <span class="iso-label">{{ $t('cameraConfig.photoISO') }}</span>
  57. <div class="select-wrapper">
  58. <el-select
  59. v-model="multiConfig[activePoint].iso.low"
  60. filterable
  61. default-first-option
  62. :placeholder="$t('cameraConfig.select')"
  63. class="iso-select"
  64. >
  65. <el-option
  66. v-for="item in iso_options"
  67. :key="item"
  68. :label="item"
  69. :value="item"
  70. />
  71. </el-select>
  72. </div>
  73. </div>
  74. <div class="iso-group">
  75. <span class="iso-label">{{ $t('cameraConfig.previewISO') }}</span>
  76. <div class="select-wrapper">
  77. <el-select
  78. v-model="multiConfig[activePoint].iso.high"
  79. filterable
  80. default-first-option
  81. :placeholder="$t('cameraConfig.select')"
  82. class="iso-select"
  83. >
  84. <el-option
  85. v-for="item in iso_options"
  86. :key="item"
  87. :label="item"
  88. :value="item"
  89. />
  90. </el-select>
  91. </div>
  92. </div>
  93. </div>
  94. </div>
  95. </div>
  96. </div>
  97. </div>
  98. </template>
  99. <script setup>
  100. import { reactive, onMounted, ref, watch, computed } from 'vue'
  101. import { ElMessage } from 'element-plus'
  102. import client from "@/stores/modules/client";
  103. import icpList from '@/utils/ipc';
  104. import socket from "@/stores/modules/socket.js";
  105. import useUserInfo from '@/stores/modules/user';
  106. import i18n from '@/locales';
  107. const clientStore = client();
  108. const socketStore = socket();
  109. const userInfoStore = useUserInfo();
  110. const props = defineProps({
  111. camera_configs: {
  112. type: Object,
  113. default: () => ({})
  114. }
  115. })
  116. const emit = defineEmits(['update:camera_configs'])
  117. const isMultiCameraMode = computed(() => userInfoStore.isMultiCameraMode)
  118. const activePoint = ref('A')
  119. const cameraList = ref([])
  120. const refreshing = ref(false)
  121. const pointList = reactive([
  122. { key: 'A', cameraName: '' },
  123. { key: 'B', cameraName: '' },
  124. { key: 'C', cameraName: '' },
  125. ])
  126. const getPointStatus = (pointKey) => {
  127. const config = multiConfig[pointKey]
  128. if (!config || !config.CameraKey) return 'unset'
  129. const camera = cameraList.value.find(c => c.CameraKey === config.CameraKey)
  130. if (!camera) return 'disconnected'
  131. return camera.CameraStatus ? 'connected' : 'disconnected'
  132. }
  133. const getPointTagInfo = (pointKey) => {
  134. const status = getPointStatus(pointKey)
  135. if (status === 'connected') return { text: i18n.global.t('cameraConfig.connected'), type: 'success' }
  136. if (status === 'disconnected') return { text: i18n.global.t('cameraConfig.notConnected'), type: 'danger' }
  137. return { text: i18n.global.t('cameraConfig.notSet'), type: 'info' }
  138. }
  139. const multiConfig = reactive({
  140. A: {
  141. iso: { low: '100', high: '6400' },
  142. CameraKey: '',
  143. CameraName: '',
  144. iso_options: ['auto', 100, 200, 400, 800, 1600, 3200, 6400, 12800]
  145. },
  146. B: {
  147. iso: { low: '100', high: '6400' },
  148. CameraKey: '',
  149. CameraName: '',
  150. iso_options: ['auto', 100, 200, 400, 800, 1600, 3200, 6400, 12800]
  151. },
  152. C: {
  153. iso: { low: '100', high: '6400' },
  154. CameraKey: '',
  155. CameraName: '',
  156. iso_options: ['auto', 100, 200, 400, 800, 1600, 3200, 6400, 12800]
  157. }
  158. })
  159. const iso_options = computed(() => {
  160. return multiConfig[activePoint.value]?.iso_options || ['auto', 100, 200, 400, 800, 1600, 3200, 6400, 12800]
  161. })
  162. const displayPointList = computed(() => {
  163. if (isMultiCameraMode.value) {
  164. return pointList
  165. } else {
  166. return pointList.filter(p => p.key === 'A')
  167. }
  168. })
  169. const availableCameras = computed(() => {
  170. const cameras = cameraList.value || []
  171. if (!isMultiCameraMode.value) {
  172. return cameras
  173. }
  174. const currentBoundCameraKey = multiConfig[activePoint.value].CameraKey
  175. return cameras.filter(cam => {
  176. if (cam.CameraKey === currentBoundCameraKey) {
  177. return true
  178. }
  179. const isBoundByOtherPoint = Object.entries(multiConfig).some(([key, config]) => {
  180. return key !== activePoint.value && config.CameraKey === cam.CameraKey
  181. })
  182. return !isBoundByOtherPoint
  183. })
  184. })
  185. const switchPoint = (pointKey) => {
  186. activePoint.value = pointKey
  187. const cameraKey = multiConfig[pointKey].CameraKey
  188. if (cameraKey) {
  189. const camera = cameraList.value.find(c => c.CameraKey === cameraKey)
  190. if (camera && camera.CameraISO && Array.isArray(camera.CameraISO)) {
  191. multiConfig[pointKey].iso_options = camera.CameraISO
  192. const currentLow = multiConfig[pointKey].iso.low
  193. const currentHigh = multiConfig[pointKey].iso.high
  194. if (!camera.CameraISO.includes(currentLow)) {
  195. multiConfig[pointKey].iso.low = camera.CameraISO[0]
  196. }
  197. if (!camera.CameraISO.includes(currentHigh)) {
  198. multiConfig[pointKey].iso.high = camera.CameraISO[0]
  199. }
  200. }
  201. }
  202. }
  203. const fetchCameraList = () => {
  204. return clientStore.ipc.invoke(icpList.camera.getCameraList).then(result => {
  205. if (result && result.CameraLists && Array.isArray(result.CameraLists)) {
  206. cameraList.value = result.CameraLists
  207. return true
  208. }
  209. return false
  210. }).catch(err => {
  211. throw err
  212. })
  213. }
  214. const handleRefresh = async () => {
  215. if (refreshing.value) return
  216. refreshing.value = true
  217. try {
  218. const ok = await fetchCameraList()
  219. if (ok) {
  220. ElMessage.success(i18n.global.t('cameraConfig.cameraConnected'))
  221. } else {
  222. ElMessage.warning(i18n.global.t('cameraConfig.noCameraList'))
  223. }
  224. } catch {
  225. ElMessage.error(i18n.global.t('cameraConfig.refreshFailed'))
  226. } finally {
  227. refreshing.value = false
  228. }
  229. }
  230. const fetchIsoConfig = () => {
  231. clientStore.ipc.removeAllListeners(icpList.socket.message + '_smart_shooter_getinfo');
  232. socketStore.sendMessage({ type: 'smart_shooter_getinfo' });
  233. clientStore.ipc.on(icpList.socket.message + '_smart_shooter_getinfo', async (event, result) => {
  234. if (result.code == 0 && result.data) {
  235. if (result.data.CameraISO && Array.isArray(result.data.CameraISO)) {
  236. iso_options.value = result.data.CameraISO
  237. }
  238. }
  239. clientStore.ipc.removeAllListeners(icpList.socket.message + '_smart_shooter_getinfo');
  240. })
  241. }
  242. const onCameraChange = (pointKey) => {
  243. const cameraKey = multiConfig[pointKey].CameraKey
  244. if (cameraKey) {
  245. const camera = cameraList.value.find(c => c.CameraKey === cameraKey)
  246. if (camera) {
  247. multiConfig[pointKey].CameraName = camera.CameraName
  248. if (camera.CameraISO && Array.isArray(camera.CameraISO)) {
  249. multiConfig[pointKey].iso_options = camera.CameraISO
  250. const currentLow = multiConfig[pointKey].iso.low
  251. const currentHigh = multiConfig[pointKey].iso.high
  252. if (!camera.CameraISO.includes(currentLow)) {
  253. multiConfig[pointKey].iso.low = camera.CameraISO[0]
  254. }
  255. if (!camera.CameraISO.includes(currentHigh)) {
  256. multiConfig[pointKey].iso.high = camera.CameraISO[0]
  257. }
  258. }
  259. }
  260. } else {
  261. multiConfig[pointKey].CameraName = ''
  262. multiConfig[pointKey].iso_options = ['auto', 100, 200, 400, 800, 1600, 3200, 6400, 12800]
  263. }
  264. }
  265. const ensureCurrentCameraInList = () => {
  266. const currentCameraKey = multiConfig[activePoint.value].CameraKey
  267. if (!currentCameraKey) return
  268. const exists = cameraList.value.some(c => c.CameraKey === currentCameraKey)
  269. if (!exists) {
  270. cameraList.value.push({
  271. CameraKey: currentCameraKey,
  272. CameraName: multiConfig[activePoint.value].CameraName || currentCameraKey,
  273. CameraStatus: false
  274. })
  275. }
  276. }
  277. watch(
  278. () => cameraList.value,
  279. () => {
  280. Object.keys(multiConfig).forEach(pointKey => {
  281. const config = multiConfig[pointKey]
  282. if (config.CameraKey) {
  283. const camera = cameraList.value.find(c => c.CameraKey === config.CameraKey)
  284. if (camera) {
  285. config.CameraName = camera.CameraName || config.CameraKey
  286. if (camera.CameraISO && Array.isArray(camera.CameraISO)) {
  287. config.iso_options = camera.CameraISO
  288. if (!camera.CameraISO.includes(config.iso.low)) {
  289. config.iso.low = camera.CameraISO[0]
  290. }
  291. if (!camera.CameraISO.includes(config.iso.high)) {
  292. config.iso.high = camera.CameraISO[0]
  293. }
  294. }
  295. }
  296. }
  297. })
  298. },
  299. { deep: true }
  300. )
  301. watch(multiConfig, (newVal) => {
  302. const isoConfig = {}
  303. Object.keys(newVal).forEach(key => {
  304. isoConfig[key] = {
  305. iso: newVal[key].iso,
  306. CameraKey: newVal[key].CameraKey,
  307. CameraName: newVal[key].CameraName
  308. }
  309. })
  310. emit('update:camera_configs', {
  311. iso_config: isoConfig,
  312. })
  313. }, { deep: true })
  314. onMounted(() => {
  315. if (props.camera_configs.iso_config) {
  316. const config = props.camera_configs.iso_config
  317. if (config.low !== undefined || config.high !== undefined) {
  318. multiConfig.A.iso.low = String(config.low || '100')
  319. multiConfig.A.iso.high = String(config.high || '6400')
  320. } else {
  321. Object.keys(config).forEach(key => {
  322. if (multiConfig[key] !== undefined) {
  323. if (config[key].iso) {
  324. multiConfig[key].iso.low = config[key].iso.low || '100'
  325. multiConfig[key].iso.high = config[key].iso.high || '6400'
  326. }
  327. if (config[key].CameraKey) {
  328. multiConfig[key].CameraKey = config[key].CameraKey
  329. multiConfig[key].CameraName = config[key].CameraName || ''
  330. }
  331. }
  332. })
  333. }
  334. }
  335. activePoint.value = 'A'
  336. fetchCameraList()
  337. })
  338. const save = () => {
  339. const current = multiConfig[activePoint.value]
  340. const isEmpty = (v) => v === undefined || v === null || v === ''
  341. if (isEmpty(current.iso.low)) {
  342. ElMessage.error('请选择拍照ISO')
  343. return false
  344. }
  345. if (isEmpty(current.iso.high)) {
  346. ElMessage.error('请选择预览ISO')
  347. return false
  348. }
  349. const lowNum = Number(current.iso.low)
  350. const highNum = Number(current.iso.high)
  351. if (!Number.isNaN(lowNum) && lowNum <= 0) {
  352. ElMessage.error('拍照ISO必须大于0')
  353. return false
  354. }
  355. if (!Number.isNaN(highNum) && highNum <= 0) {
  356. ElMessage.error('预览ISO必须大于0')
  357. return false
  358. }
  359. return true
  360. }
  361. defineExpose({ save })
  362. </script>
  363. <style lang="scss" scoped>
  364. .selectBox {
  365. padding-top: 10px;
  366. padding-bottom: 50px;
  367. }
  368. .multi-camera-config {
  369. padding-top: 20px;
  370. padding-left: 100px;
  371. }
  372. .point-tabs {
  373. display: flex;
  374. align-items: center;
  375. gap: 12px;
  376. margin-bottom: 20px;
  377. }
  378. .point-tab {
  379. display: flex;
  380. align-items: center;
  381. gap: 8px;
  382. padding: 10px 20px;
  383. background: #F5F7FA;
  384. border: 2px solid #E4E7ED;
  385. border-radius: 8px;
  386. cursor: pointer;
  387. transition: all 0.3s;
  388. &:hover {
  389. border-color: #C0C4CC;
  390. }
  391. &.active {
  392. background: #ECF5FF;
  393. border-color: #409EFF;
  394. }
  395. .tab-name {
  396. font-weight: bold;
  397. color: #303133;
  398. }
  399. }
  400. .refresh-btn {
  401. margin-left: auto;
  402. }
  403. .current-point-config {
  404. overflow: hidden;
  405. max-width: 500px;
  406. .config-body {
  407. padding: 24px 20px;
  408. .form-item {
  409. margin-bottom: 20px;
  410. label {
  411. display: block;
  412. margin-bottom: 8px;
  413. font-size: 14px;
  414. }
  415. .camera-select-wrapper {
  416. max-width: 200px;
  417. }
  418. }
  419. .iso-section {
  420. display: flex;
  421. gap: 20px;
  422. flex-wrap: wrap;
  423. }
  424. .iso-group {
  425. display: flex;
  426. align-items: center;
  427. gap: 15px;
  428. .iso-label {
  429. min-width: 125px;
  430. font-size: 14px;
  431. text-align: right;
  432. color: #1A1A1A;
  433. }
  434. }
  435. }
  436. }
  437. .select-wrapper {
  438. :deep(.el-input__inner) {
  439. border-radius: 6px;
  440. }
  441. }
  442. :deep(.el-select) {
  443. width: 100%;
  444. }
  445. :deep(.camera-select-wrapper .el-select) {
  446. width: 300px;
  447. }
  448. </style>