check.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. <template>
  2. <headerBar
  3. v-if="!isSetting"
  4. title="拍摄物体镜头矫正"
  5. :menu="menu"
  6. showUser
  7. showDeviceStatus
  8. />
  9. <div class="check-wrap">
  10. <div class="check-page flex-col" :class="isSetting ? 'check-page_seeting' : '' ">
  11. <div class="main-container flex-col">
  12. <div class="content-wrapper flex-row justify-between">
  13. <div class="left-panel flex-col justify-between">
  14. <div class="tips-container flex-row" v-if="show">
  15. <spanc v-if="isSetting" class="tips-tex">
  16. 请在圆盘上摆上鞋子(注意左右脚),要求鞋外侧朝向拍照机,鞋子中轴线和红外线对齐,如果光亮不够,可以打开环境光源,关闭闪光灯。
  17. </spanc>
  18. <span v-else class="tips-tex">
  19. 请在圆盘上摆上鞋子(注意左右脚),要求鞋外侧朝向拍照机,鞋子中轴线和红外线对齐,如果光亮不够,可以打开环境光源,关闭闪光灯。
  20. </span>
  21. </div>
  22. <div class="camera-preview flex col center ">
  23. <div class="camera-preview-img" v-if="step === 1">
  24. <img v-if="previewKey" class="camera-img" :src="previewSrc" />
  25. <el-button class="auto-focus-btn" @click="handleAutoFocus" v-if="previewKey">
  26. <span>点击对焦</span>
  27. </el-button>
  28. <div class="example-image flex-col" v-if="!isSetting && previewKey > 1">
  29. <img :src="exampleImage">
  30. </div>
  31. </div>
  32. <template v-if="step === 2" >
  33. <img class="camera-img" :src="getFilePath(imageTplPath)" />
  34. <span class="camera-description">这是一张用于检查镜头是否合适的测试图</span>
  35. </template>
  36. </div>
  37. </div>
  38. </div>
  39. <template v-if="!isSetting">
  40. <div v-if="step === 1" class="action-button flex cente">
  41. <div @click="takePictures" class="check-button button--primary1 flex-col" v-log="{ describe: { action: '点击拍照检查' } }"><span class="button-text" v-loading="loading">拍照检查</span>
  42. </div>
  43. </div>
  44. <div v-else class="action-button flex center">
  45. <div @click="checkConfirm(false)" class="check-button button--white flex-col" v-log="{ describe: { action: '点击重新拍照检查' } }">
  46. <span class="button-text cu-p">重新拍照检查</span>
  47. </div>
  48. <router-link class="mar-left-20 " :to="{
  49. name: 'PhotographyShot'
  50. }">
  51. <div class="check-button button--primary1 flex-col" v-log="{ describe: { action: '点击确认无误下一步' } }">
  52. <span class="button-text cu-p">确认无误,下一步</span>
  53. </div>
  54. </router-link>
  55. </div>
  56. </template>
  57. </div>
  58. </div>
  59. <editRow
  60. v-if="showrEditRow || isSetting"
  61. :id="id"
  62. :key="id"
  63. @confirm="confirm"
  64. ref="editData"
  65. @onClose="onClose"
  66. @onRunMcuSingle="onRunMcuSingle"
  67. @onTestPhoto="takePictures"
  68. @onBackPreview="backToPreview"
  69. @onReady="onEditRowReady"
  70. @onPointChange="switchPreviewPoint"
  71. :addRowData="addRowData"
  72. :disablePointConfig="!isSetting"
  73. :showingTestPhoto="isSetting && step === 2"
  74. />
  75. </div>
  76. <hardware-check v-if="!isSetting"
  77. isInitCheck
  78. :z-index="2000"
  79. @confirm="checkConfirm(true)"
  80. />
  81. <QuickActions v-if="!isSetting && mcuConnected" />
  82. </template>
  83. <script setup lang="ts">
  84. import {watchEffect, ref, defineEmits, defineProps, computed, onBeforeUnmount, onMounted} from 'vue'
  85. import client from "@/stores/modules/client";
  86. import socket from "@/stores/modules/socket";
  87. import icpList from '@/utils/ipc'
  88. import useUserInfo from "@/stores/modules/user";
  89. import headerBar from '@/components/header-bar/index.vue'
  90. import editRow from './components/editRow'
  91. import QuickActions from '@/components/quick-actions/index.vue'
  92. import checkInfo from '@/stores/modules/check'
  93. const clientStore = client();
  94. const socketStore = socket(); // WebSocket状态管理实例
  95. const emit = defineEmits([ 'confirm','onClose']);
  96. import configInfo from '@/stores/modules/config';
  97. const configInfoStore = configInfo();
  98. const useUserInfoStore = useUserInfo();
  99. const checkInfoStore = checkInfo();
  100. const mcuConnected = computed(() => checkInfoStore.mcuConnected)
  101. const confirm = async () => {
  102. console.log('confirm');
  103. setTimeout(()=>{
  104. hideVideo(currentPointName.value)
  105. },1000)
  106. emit('confirm')
  107. }
  108. const onClose = async () => {
  109. console.log('onClose');
  110. setTimeout(()=>{
  111. hideVideo(currentPointName.value)
  112. },1000)
  113. emit('onClose')
  114. }
  115. // 定义 props
  116. const props = defineProps({
  117. id:{
  118. type: Number||String,
  119. default: 0
  120. },
  121. addRowData:{
  122. type: Object,
  123. default: () => {
  124. return { }
  125. }
  126. }
  127. })
  128. const show = ref(true)
  129. const previewKey = ref(0)
  130. const isSetting = computed(()=>{
  131. return props.id || props.addRowData.mode_type
  132. })
  133. import HardwareCheck from '@/components/check/index.vue'
  134. import { digiCamControlWEB } from '@/utils/appconfig'
  135. import { getFilePath } from '@/utils/appfun'
  136. import {ElMessage} from "element-plus";
  137. const preview = ref(digiCamControlWEB+'liveview.jpg')
  138. const previewSrc = computed(()=>{
  139. let time = new Date().getTime()
  140. return preview.value+'?key='+previewKey.value+'&time='+time
  141. })
  142. const step = ref(1)
  143. async function checkConfirm(init){
  144. step.value =1
  145. if(!init) previewKey.value++;
  146. showrEditRow.value = true
  147. loading.value = false;
  148. }
  149. // 打开输出目录:appConfig.appPath + '/build/extraResources/py/output'
  150. const openOutputDir = () => {
  151. try {
  152. const appPath = configInfoStore?.appConfig?.appPath || ''
  153. if (!appPath) {
  154. ElMessage.error('未获取到应用目录 appPath')
  155. return
  156. }
  157. const fullPath = `${appPath}\\output`
  158. clientStore.ipc.removeAllListeners(icpList.utils.shellFun);
  159. clientStore.ipc.send(icpList.utils.shellFun, {
  160. action: 'openMkPath',
  161. params: fullPath.replace(/\//g, '\\')
  162. });
  163. } catch (e) {
  164. console.error(e)
  165. ElMessage.error('打开目录失败')
  166. }
  167. }
  168. // 快捷操作菜单子项(不含生成图目录)
  169. const quickActionsChildren = [
  170. {
  171. type: 'OneClickRelease'
  172. },
  173. {
  174. type: 'OneKeyDown'
  175. }
  176. ]
  177. // 重构 menu 为带快捷操作和开发工具的下拉菜单结构
  178. const menu = computed(() => {
  179. // 生成图目录(独立菜单项,点击直接打开目录)
  180. const outputDirMenu = {
  181. name: '生成图目录',
  182. click() {
  183. openOutputDir()
  184. }
  185. }
  186. // 开发者模式:设置 + 快捷操作 + 开发工具 + 生成图目录
  187. if (useUserInfoStore.isHLM || configInfoStore.appConfig.debug) {
  188. return [
  189. {
  190. type: 'setting'
  191. },
  192. {
  193. name: '快捷操作',
  194. children: quickActionsChildren
  195. },
  196. {
  197. name: '开发工具',
  198. children: [
  199. {
  200. type: 'developer'
  201. }
  202. ]
  203. },
  204. outputDirMenu
  205. ]
  206. }
  207. // 普通模式:设置 + 快捷操作 + 生成图目录
  208. return [
  209. {
  210. type: 'setting'
  211. },
  212. {
  213. name: '快捷操作',
  214. children: quickActionsChildren
  215. },
  216. outputDirMenu
  217. ]
  218. })
  219. // editRow 数据加载完成后调用,仅记录点位,首次不打开预览
  220. function onEditRowReady(point_name) {
  221. console.log('onEditRowReady,记录点位:' + point_name)
  222. currentPointName.value = point_name
  223. }
  224. const init = ref(true)
  225. function onRunMcuSingle (){
  226. if(init.value) {
  227. loading.value = false
  228. init.value = false
  229. }
  230. }
  231. const showrEditRow = ref(false)
  232. let interval:any = null
  233. let currentPointName = ref('A')
  234. let isVideoShowing = ref(false)
  235. let isHidingVideo = ref(false)
  236. let hideVideoTimer: any = null
  237. let pendingShowVideoPoint: string | null = null
  238. let isFirstShow = false // 标记是否已首次显示预览
  239. function clearPreviewInterval() {
  240. if (interval) {
  241. clearInterval(interval)
  242. interval = null
  243. }
  244. if (hideVideoTimer) {
  245. clearTimeout(hideVideoTimer)
  246. hideVideoTimer = null
  247. }
  248. }
  249. function showVideo(point_name = 'A') {
  250. if (isHidingVideo.value) {
  251. console.log('正在关闭预览中,延迟打开:' + point_name)
  252. pendingShowVideoPoint = point_name
  253. return
  254. }
  255. if (isVideoShowing.value && currentPointName.value === point_name) {
  256. console.log('预览已在显示,跳过重复调用')
  257. return
  258. }
  259. console.log('打开预览=====================' + point_name)
  260. clearPreviewInterval()
  261. isVideoShowing.value = true
  262. currentPointName.value = point_name
  263. clientStore.ipc.removeAllListeners(icpList.camera.PreviewShow);
  264. clientStore.ipc.send(icpList.camera.PreviewShow, { point_name });
  265. clientStore.ipc.on(icpList.camera.PreviewShow, async (event, result) => {
  266. console.log('打开预览=====================')
  267. console.log(result)
  268. if (result.device_status === -1 && result.msg){
  269. ElMessage.error(result.msg)
  270. return;
  271. }
  272. clearPreviewInterval()
  273. hideVideoTimer = setTimeout(() => {
  274. interval = setInterval(() => {
  275. previewKey.value++;
  276. }, 200)
  277. }, 500)
  278. })
  279. }
  280. async function hideVideo(point_name = 'A') {
  281. console.log('关闭预览=====================' + point_name)
  282. clearPreviewInterval()
  283. isVideoShowing.value = false
  284. isHidingVideo.value = true
  285. return new Promise((resolve) => {
  286. clientStore.ipc.removeAllListeners(icpList.camera.PreviewHide);
  287. clientStore.ipc.send(icpList.camera.PreviewHide, { point_name });
  288. clientStore.ipc.on(icpList.camera.PreviewHide, async (event, result) => {
  289. console.log('关闭预览回调=====================')
  290. console.log(result)
  291. if (result.device_status === -1 && result.msg){
  292. console.log(result.msg)
  293. resolve(true)
  294. }
  295. isHidingVideo.value = false
  296. // 如果有待执行的打开请求,执行它
  297. if (pendingShowVideoPoint) {
  298. const point = pendingShowVideoPoint
  299. pendingShowVideoPoint = null
  300. setTimeout(() => showVideo(point), 100)
  301. }
  302. resolve(true)
  303. })
  304. })
  305. }
  306. async function switchPreviewPoint(point_name = 'A') {
  307. console.log('switchPreviewPoint:' + point_name);
  308. // 如果点位相同,不做任何处理
  309. /* if (point_name === currentPointName.value) {
  310. console.log('点位相同,跳过切换');
  311. return;
  312. }*/
  313. // 首次显示预览之前不做切换
  314. /* if (!isFirstShow) {
  315. console.log('首次显示预览前,跳过切换');
  316. currentPointName.value = point_name;
  317. return;
  318. }*/
  319. // 执行切换
  320. await hideVideo(currentPointName.value)
  321. showVideo(point_name)
  322. }
  323. function goArts(){
  324. }
  325. function handleAutoFocus() {
  326. console.log('自动对焦,点位:' + currentPointName.value)
  327. socketStore.sendMessage({
  328. type: 'smart_shooter_auto_focus',
  329. data: {
  330. point_name: currentPointName.value || 'A'
  331. }
  332. });
  333. }
  334. const loading = ref(false)
  335. const editData = ref(null);
  336. const imageTplPath = ref(null)
  337. function backToPreview() {
  338. step.value = 1
  339. imageTplPath.value = null
  340. loading.value = false
  341. showVideo(currentPointName.value)
  342. }
  343. function takePictures() {
  344. if(loading.value) return;
  345. console.log(editData);
  346. console.log(editData.value?.editRowData);
  347. if (clientStore.isClient) {
  348. loading.value = true;
  349. const row = editData.value?.editRowData || {}
  350. const shotData: Record<string, any> = {
  351. camera_height: Number(row.camera_height),
  352. camera_angle: Number(row.camera_angle),
  353. led_switch: row.led_switch,
  354. id: 0,
  355. mode_type: row.mode_type,
  356. turntable_position: Number(row.turntable_position),
  357. action_name: row.action_name || '测试',
  358. turntable_angle: Number(row.turntable_angle),
  359. shoe_upturn: Number(row.shoe_upturn),
  360. camera_focal_distance: Number(row.camera_focal_distance),
  361. action_index: 1,
  362. number_focus: 0,
  363. take_picture: true,
  364. pre_delay: 0,
  365. after_delay: 0,
  366. point_name: row.point_name || currentPointName.value || 'A',
  367. is_move_device: row.is_move_device,
  368. }
  369. const cameraConfigs = editData.value?.getCameraConfigs?.()
  370. if (cameraConfigs) shotData.camera_configs = cameraConfigs
  371. console.log('takePictures');
  372. console.log(shotData)
  373. hideVideo(currentPointName.value).then(() => {
  374. socketStore.sendMessage({
  375. type: 'run_mcu_single',
  376. data: shotData,
  377. });
  378. })
  379. }
  380. }
  381. // 获取主图
  382. function createMainImage (file_path){
  383. loading.value = true;
  384. clientStore.ipc.removeAllListeners(icpList.takePhoto.createMainImage);
  385. clientStore.ipc.send(icpList.takePhoto.createMainImage,{
  386. file_path:file_path
  387. });
  388. clientStore.ipc.on(icpList.takePhoto.createMainImage, async (event, result) => {
  389. if(result.code === 0 && result.data?.main_out_path){
  390. imageTplPath.value = result.data?.main_out_path
  391. // 预览已在 takePictures 中关闭,不再重复调用 hideVideo
  392. step.value = 2
  393. loading.value = false;
  394. }else if(result.msg){
  395. loading.value = false;
  396. console.log('createMainImage');
  397. showVideo(currentPointName.value)
  398. if(result.code !== 0) ElMessage.error(result.msg)
  399. }
  400. clientStore.ipc.removeAllListeners(icpList.takePhoto.createMainImage);
  401. });
  402. }
  403. //拍照成功 SmartShooter
  404. clientStore.ipc.on(icpList.socket.message+'_smart_shooter_photo_take', async (event, result) => {
  405. console.log('_smart_shooter_photo_take');
  406. console.log(result);
  407. if(result.code === 0 && result.data?.photo_file_name){
  408. imageTplPath.value = result.data?.photo_file_name
  409. step.value = 2
  410. loading.value = false;
  411. // 首次拍摄完成后打开预览用于检查镜头
  412. if (!isFirstShow) {
  413. isFirstShow = true
  414. setTimeout(() => showVideo(currentPointName.value), 100)
  415. }
  416. }else {
  417. loading.value = false;
  418. console.log('_smart_shooter_photo_take');
  419. showVideo(currentPointName.value)
  420. if(result.code !== 0 && result.msg) ElMessage.error(result.msg)
  421. }
  422. })
  423. //运行的时候 直接拍照 digiCamControl
  424. clientStore.ipc.on(icpList.socket.message+'_run_mcu_single', async (event, result) => {
  425. console.log('_run_mcu_single_check_on')
  426. console.log(result)
  427. if(result.code === 0 && result.data?.file_path){
  428. imageTplPath.value = result.data?.file_path
  429. step.value = 2
  430. loading.value = false;
  431. // 首次拍摄完成后打开预览用于检查镜头
  432. if (!isFirstShow) {
  433. isFirstShow = true
  434. setTimeout(() => showVideo(currentPointName.value), 100)
  435. }
  436. }else {
  437. loading.value = false;
  438. console.log('_run_mcu_single');
  439. showVideo(currentPointName.value)
  440. if(result.code !== 0 && result.msg) ElMessage.error(result.msg)
  441. }
  442. /* if(result.code === 0 && result.data?.file_path){
  443. createMainImage(result.data?.file_path)
  444. }else if(result.msg){
  445. if( result.msg.indexOf('处理失败,请重试') >= 0){
  446. ElMessage.error(result.msg)
  447. step.value = 1
  448. loading.value = false;
  449. showVideo()
  450. }
  451. }*/
  452. })
  453. const exampleImage = ref('https://huilimaimg.cnhqt.com/frontend/zhihuiyin/demo.jpg?x-oss-process=image/resize,w_400')
  454. onMounted(async ()=>{
  455. if (!isSetting.value) {
  456. checkInfoStore.requestHardwareCheckInfo()
  457. }
  458. await configInfoStore.getAppConfig()
  459. if(configInfoStore.appConfig.controlType === "SmartShooter" || configInfoStore.appConfig.controlType === "SmartShooter6"){
  460. preview.value = configInfoStore.appConfig.userDataPath + "\\preview\\liveview.png"
  461. }
  462. exampleImage.value = configInfoStore.appConfig.exampleImage || 'https://huilimaimg.cnhqt.com/frontend/zhihuiyin/demo.jpg?x-oss-process=image/resize,w_400'
  463. })
  464. /**
  465. * 页面卸载时移除所有事件监听器。
  466. */
  467. onBeforeUnmount(() => {
  468. clearPreviewInterval()
  469. console.log('onBeforeUnmount');
  470. hideVideo(currentPointName.value)
  471. clientStore.ipc.removeAllListeners(icpList.camera.takePictures);
  472. clientStore.ipc.removeAllListeners(icpList.camera.PreviewHide);
  473. clientStore.ipc.removeAllListeners(icpList.camera.PreviewShow);
  474. clientStore.ipc.removeAllListeners(icpList.socket.message+'_run_mcu_single');
  475. clientStore.ipc.removeAllListeners(icpList.socket.message+'_smart_shooter_photo_take');
  476. })
  477. </script>
  478. <style scoped lang="scss">
  479. .check-wrap {
  480. background-color: rgba(234, 236, 237, 1);
  481. display: flex;
  482. justify-content: left;
  483. }
  484. .check-page {
  485. width: calc(100% - 330px) ;
  486. position: relative;
  487. min-height: calc(100vh - 35px);
  488. overflow: hidden;
  489. background: #fff;
  490. .main-container {
  491. width: 100%;
  492. min-height: calc(100vh - 35px);
  493. margin-bottom: 1px;
  494. .content-wrapper {
  495. width:100%;
  496. padding: 10px;
  497. .left-panel {
  498. width: 100%;
  499. height: calc(100% - 80px);
  500. .tips-container {
  501. background-color: rgba(255, 241, 222, 0.8);
  502. border-radius: 4px;
  503. width: 100%;
  504. height: 40px;
  505. border: 1px solid rgba(255, 228, 190, 1);
  506. justify-content: flex-center;
  507. .tips-icon {
  508. width: 16px;
  509. height: 16px;
  510. margin: 12px 0 0 16px;
  511. }
  512. .tips-tex {
  513. width: 549px;
  514. height: 22px;
  515. overflow-wrap: break-word;
  516. color: rgba(0, 0, 0, 0.85);
  517. font-size: 14px;
  518. font-weight: NaN;
  519. text-align: left;
  520. white-space: nowrap;
  521. line-height: 22px;
  522. margin: 9px 0 0 7px;
  523. }
  524. .close-icon {
  525. width: 12px;
  526. height: 12px;
  527. margin: 14px 16px 0 17px;
  528. }
  529. }
  530. .camera-preview {
  531. background-color: rgb(234, 236, 237);
  532. padding: 10px;
  533. width: 100%;
  534. height: calc(100vh - 200px);
  535. position: relative;
  536. margin-top: 20px;
  537. overflow: hidden;
  538. display: flex;
  539. align-items: center;
  540. justify-content: center;
  541. .camera-description {
  542. position: absolute;
  543. bottom: 20px;
  544. width: 60%;
  545. padding: 8px 20px;
  546. background: rgba($color: #ffffff, $alpha: .5);
  547. border-radius: 20px;
  548. font-size: 14px;
  549. }
  550. .camera-preview-img {
  551. display: flex;
  552. align-items: center;
  553. justify-content: center;
  554. width: 100%;
  555. height: 100%;
  556. max-width: 100%;
  557. max-height: 100%;
  558. position: relative;
  559. }
  560. .auto-focus-btn {
  561. position: absolute;
  562. top: 0px;
  563. right: 0px;
  564. z-index: 10;
  565. }
  566. .camera-img {
  567. display: block;
  568. position: relative;
  569. max-width: 100%;
  570. max-height: 100%;
  571. width: 100%;
  572. height: 100%;
  573. object-fit: contain;
  574. object-position: center;
  575. }
  576. .example-image {
  577. position: absolute;
  578. right: 0px;
  579. bottom: 0px;
  580. background-color: rgba(216, 216, 216, 1);
  581. height: 135px;
  582. margin-top: 10px;
  583. width: 200px;
  584. img {
  585. width: 200px;
  586. height: 135px;
  587. display: block;
  588. }
  589. }
  590. }
  591. }
  592. }
  593. .action-button {
  594. width: 100%;
  595. text-align: center;
  596. margin-top: 28px;
  597. .check-button {
  598. .button-text {
  599. width: 180px;
  600. overflow-wrap: break-word;
  601. font-size: 16px;
  602. text-align: center;
  603. white-space: nowrap;
  604. height: 40px;
  605. line-height: 40px;
  606. }
  607. }
  608. }
  609. }
  610. }
  611. .check-page_seeting {
  612. min-height: calc(100vh - 92px);
  613. .main-container {
  614. min-height: calc(100vh - 92px);
  615. }
  616. }
  617. </style>