usePhotography.ts 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298
  1. import { ref, onMounted, onBeforeUnmount, watchEffect, computed } from 'vue'
  2. import icpList from '@/utils/ipc'
  3. import client from "@/stores/modules/client";
  4. import socket from "@/stores/modules/socket";
  5. import { ElMessage, ElMessageBox } from 'element-plus'
  6. import { getFilePath, getRouterUrl } from '@/utils/appfun'
  7. import { useRouter, useRoute } from "vue-router";
  8. import checkInfo from "@/stores/modules/check";
  9. import generate, { getGenerateChildren } from '@/utils/menus/generate'
  10. import { clickLog, setLogInfo } from '@/utils/log'
  11. import { useUuidStore } from '@/stores/modules/uuid'
  12. import useUserInfo from "@/stores/modules/user";
  13. import configInfo from '@/stores/modules/config';
  14. import tokenInfo from "@/stores/modules/token";
  15. import { getAllUserConfigs, getTopTabs } from '@/apis/setting';
  16. import { registerTakePhotoTrigger, type TakePhotoParams } from '@/utils/takePhotoApi';
  17. export default function usePhotography() {
  18. const loading = ref(false)
  19. const runLoading = ref(false)
  20. const takePictureLoading = ref(false)
  21. // pagination
  22. const pageSize = ref(10)
  23. const currentPage = ref(1)
  24. const totalPages = ref(1)
  25. const goodsList = ref([])
  26. const goods_art_no_tpl = ref('')
  27. const goods_art_no = ref('')
  28. const runAction = ref({
  29. "action": "",
  30. "goods_art_no": ""
  31. })
  32. const lastPhoto = ref({})
  33. const showlastPhoto = ref(false)
  34. const isDelGoodsGetList = ref(false)
  35. const reNosObj = ref({
  36. goods_art_no: null,
  37. action: null,
  38. })
  39. const goodsArtNo = ref()
  40. const searchGoodsArtNo = ref('')
  41. let smartShooterTimeout: ReturnType<typeof setTimeout> | null = null
  42. // ==================== 演示模式 ====================
  43. const isDemoMode = ref(false) // 是否处于演示模式
  44. const demoStopping = ref(false)
  45. const demoGoodsArtNo = ref('演示货号1') // 演示货号
  46. const demoProgramType = ref('执行左脚程序') // 演示程序类型
  47. const demoConfigList = ref<{id: number, mode_name: string}[]>([]) // 演示配置列表
  48. const demoSelectedConfigId = ref<number | null>(null) // 选中的演示配置ID
  49. const demoDialogVisible = ref(false) // 演示配置对话框
  50. // ==================== 演示模式 END ====================
  51. // 初始化 WebSocket 状态管理
  52. const socketStore = socket()
  53. const uuidStore = useUuidStore();
  54. const clientStore = client();
  55. const Router = useRouter()
  56. const route = useRoute();
  57. const useUserInfoStore = useUserInfo();
  58. const generateChildren = computed(() => getGenerateChildren())
  59. const configInfoStore = configInfo();
  60. const tokenInfoStore = tokenInfo();
  61. const checkInfoStore = checkInfo()
  62. let unbindTakePhoto: (() => void) | null = null
  63. let captureRunError: ((msg: string) => void) | null = null
  64. let onRunMcuResult: ((event: any, result: any) => void) | null = null
  65. // 拍照点位配置
  66. const STORAGE_KEY = 'photo_point_name'
  67. // 获取已配置相机的点位列表(从 getAllUserConfigs API 获取,仅返回已绑定相机的点位)
  68. const getPointList = async (): Promise<string[]> => {
  69. try {
  70. const result = await getAllUserConfigs({})
  71. if (result.code == 0 && result.data?.configs?.camera_configs?.iso_config) {
  72. const isoConfig = result.data.configs.camera_configs.iso_config
  73. const points = Object.keys(isoConfig).filter(key => {
  74. const config = isoConfig[key]
  75. return config && config.CameraKey && config.CameraKey !== ''
  76. })
  77. return points.length > 0 ? points : ['A']
  78. }
  79. } catch (err) {
  80. console.error('获取点位配置失败:', err)
  81. }
  82. return ['A']
  83. }
  84. // 获取保存的点位(单相机模式默认A,多相机模式验证点位合法性)
  85. const getPhotoPointName = async (): Promise<string> => {
  86. if (!useUserInfoStore.isMultiCameraMode) {
  87. return 'A'
  88. }
  89. const pointList = await getPointList()
  90. const savedPoint = localStorage.getItem(STORAGE_KEY)
  91. if (savedPoint && pointList.includes(savedPoint)) {
  92. return savedPoint
  93. }
  94. return pointList[0] || 'A'
  95. }
  96. // 抠图请求去重与延迟队列(key: goods_art_no, value: timeoutId)
  97. const segmentQueue = new Map<string, ReturnType<typeof setTimeout>>()
  98. function isGoodsStillInList(goodsArtNo: string): boolean {
  99. return goodsList.value?.some((g: any) => g.goods_art_no === goodsArtNo) || false
  100. }
  101. function scheduleSegment(goodsArtNo: string) {
  102. if (!goodsArtNo) return
  103. // 若已存在,则重置计时(重新插入)
  104. if (segmentQueue.has(goodsArtNo)) {
  105. const t = segmentQueue.get(goodsArtNo)
  106. if (t) clearTimeout(t)
  107. segmentQueue.delete(goodsArtNo)
  108. }
  109. const timeoutId = setTimeout(async () => {
  110. segmentQueue.delete(goodsArtNo)
  111. // if (!isGoodsStillInList(goodsArtNo)) return
  112. try {
  113. await socketStore.connectSocket();
  114. socketStore.sendMessage({
  115. type: 'segment_progress',
  116. data: {
  117. token: tokenInfoStore.getToken,
  118. uuid: uuidStore?.getUuid || '',
  119. goods_art_no: [goodsArtNo],
  120. }
  121. })
  122. } catch (e) {
  123. // 忽略发送异常,避免打断主流程
  124. }
  125. }, 20000)
  126. segmentQueue.set(goodsArtNo, timeoutId)
  127. }
  128. /**
  129. * 保存货号模板到货号变量中。
  130. */
  131. function saveGoodsArtNo() {
  132. if (goods_art_no_tpl.value) {
  133. goods_art_no.value = goods_art_no_tpl.value
  134. ElMessage.success('商品货号' + goods_art_no.value + '获取成功,请在遥控器上按下左或右脚按键,启动拍摄')
  135. }
  136. }
  137. /**
  138. * 获取拍照记录。
  139. * @param params - 可选参数,用于分页或其他筛选条件。
  140. */
  141. async function getPhotoRecords(params?: { page?: number; size?: number; goods_art_no?: string }) {
  142. if (loading.value) return;
  143. loading.value = true;
  144. const page = params?.page ?? currentPage.value ?? 1
  145. const size = params?.size ?? pageSize.value ?? 10
  146. console.log('params' , {
  147. ...params,
  148. page,
  149. size,
  150. })
  151. clientStore.ipc.send(icpList.takePhoto.getPhotoRecords, {
  152. ...params,
  153. page,
  154. size,
  155. });
  156. clientStore.ipc.on(icpList.takePhoto.getPhotoRecords, (event, result) => {
  157. loading.value = false;
  158. if (result.code === 0) {
  159. clientStore.ipc.removeAllListeners(icpList.takePhoto.getPhotoRecords);
  160. //console.log('getPhotoRecords print_time:' + new Date().toLocaleString())
  161. // console.log('getPhotoRecords print_time:' + JSON.stringify(result.data.list))
  162. console.log(result.data)
  163. // 在设置 goodsList.value 之前,检查 items 数组中的 action_name 是否为 null
  164. const modifiedList = result.data.list.map(item => {
  165. // 检查 items 数组中是否有 action_name 为 null 的情况
  166. const hasNullActionName = item.items && Array.isArray(item.items) &&
  167. item.items.some(image => image.action_name === null || image.action_name === undefined);
  168. // 根据检查结果设置 syncConfig
  169. return {
  170. ...item,
  171. syncConfig: hasNullActionName ? false : true
  172. };
  173. });
  174. goodsList.value = modifiedList;
  175. // update pagination info if provided by API
  176. if (result.data) {
  177. currentPage.value = result.data.current_page || page
  178. totalPages.value = result.data.total_pages || 1
  179. }
  180. if (isDelGoodsGetList.value) {
  181. isDelGoodsGetList.value = false;
  182. return;
  183. }
  184. getLastPhotoRecord()
  185. } else if (result.msg) {
  186. ElMessage.error(result.msg)
  187. }
  188. });
  189. }
  190. /**
  191. * 执行拍照操作。
  192. * @param data - 包含拍摄所需的数据对象。
  193. */
  194. async function runGoods(data) {
  195. if (runLoading.value || takePictureLoading.value) {
  196. ElMessage.error('拍摄程序正在运行,请稍候')
  197. return
  198. }
  199. await socketStore.connectSocket();
  200. socketStore.sendMessage({
  201. type: 'run_mcu',
  202. data,
  203. })
  204. runLoading.value = true;
  205. runAction.value.action = data.action
  206. runAction.value.goods_art_no = data.goods_art_no
  207. // goods_art_no.value = ''
  208. goods_art_no_tpl.value = ''
  209. reNosObj.value.goods_art_no = null;
  210. reNosObj.value.action = null;
  211. searchGoodsArtNo.value = ''
  212. const runMcuChannel = icpList.socket.message + '_run_mcu'
  213. if (onRunMcuResult && clientStore.ipc?.removeListener) {
  214. clientStore.ipc.removeListener(runMcuChannel, onRunMcuResult)
  215. }
  216. onRunMcuResult = (_event, result) => {
  217. if (clientStore.ipc?.removeListener) {
  218. clientStore.ipc.removeListener(runMcuChannel, onRunMcuResult)
  219. }
  220. onRunMcuResult = null
  221. console.log('_run_mcu');
  222. console.log(result);
  223. if (result.code !== 0 && result.msg) {
  224. captureRunError?.(result.msg)
  225. ElMessage.error(result.msg)
  226. runLoading.value = false
  227. return;
  228. } else if (!isDemoMode.value) {
  229. ElMessage.success('开始拍摄,请稍后')
  230. }
  231. }
  232. clientStore.ipc.on(runMcuChannel, onRunMcuResult)
  233. }
  234. /**
  235. * 格式化时间字符串。
  236. * @param time - 原始时间字符串。
  237. * @returns 格式化后的时间字符串,若输入为空则返回 null。
  238. */
  239. const getTime = function (time) {
  240. if (!time) return null
  241. return time.replace('T', ' ').substr(5, 11)
  242. }
  243. /**
  244. * 删除所有商品货号的历史记录。
  245. */
  246. async function delAll() {
  247. let params = goodsList.value.map(item => item.goods_art_no)
  248. try {
  249. await ElMessageBox.confirm('确定要删除当下的历史记录吗?', '提示', {
  250. confirmButtonText: '确定',
  251. cancelButtonText: '取消',
  252. })
  253. await clickLog({ describe: { action: '点击确认一键删除', goods_art_nos: params } }, route)
  254. del({ goods_art_nos: params })
  255. } catch (e) {
  256. await clickLog({ describe: { action: '点击取消一键删除' } }, route)
  257. }
  258. }
  259. /**
  260. * 删除指定的商品货号。
  261. * @param params - 包含需要删除的货号列表的对象。
  262. */
  263. const delGoods = async function (params) {
  264. try {
  265. await ElMessageBox.confirm('确定要删除货号:' + params.goods_art_nos[0] + '的拍摄数据吗?', '提示', {
  266. confirmButtonText: '确定',
  267. cancelButtonText: '取消',
  268. })
  269. await clickLog({ describe: { action: '点击确认删除货号', goods_art_no: params.goods_art_nos?.[0] } }, route)
  270. del(params)
  271. } catch (e) {
  272. await clickLog({ describe: { action: '点击取消删除货号', goods_art_no: params.goods_art_nos?.[0] } }, route)
  273. }
  274. }
  275. /**
  276. * 删除指定的商品货号。
  277. * @param params - 包含需要删除的货号列表的对象。
  278. */
  279. const del = async function (params, options?: { silent?: boolean }) {
  280. console.log(icpList.takePhoto.delectGoodsArts, params);
  281. return new Promise((resolve) => {
  282. const channel = icpList.takePhoto.delectGoodsArts
  283. clientStore.ipc.removeAllListeners(channel);
  284. clientStore.ipc.send(channel, params);
  285. const timer = setTimeout(() => {
  286. resolve({ code: -1, msg: '删除超时' })
  287. }, 10000)
  288. clientStore.ipc.once(channel, (event, result) => {
  289. clearTimeout(timer)
  290. console.log("icpList.takePhoto.delectGoodsArts", params);
  291. if (result.code === 0) {
  292. isDelGoodsGetList.value = true
  293. if (!options?.silent) {
  294. ElMessage.info('货号删除成功')
  295. }
  296. getPhotoRecords()
  297. if (reNosObj.value.goods_art_no) {
  298. runGoods(
  299. {
  300. "action": reNosObj.value.action,
  301. "goods_art_no": reNosObj.value.goods_art_no
  302. })
  303. }
  304. } else if (result.msg && !options?.silent) {
  305. ElMessage.error(result.msg)
  306. }
  307. resolve(result)
  308. });
  309. })
  310. }
  311. //单个重拍
  312. const reTakePicture = async (img) => {
  313. if (!img.id) return;
  314. if (img.image_path) {
  315. try {
  316. await ElMessageBox.confirm('此操作会先删除此数据,需要继续吗?', '提示', {
  317. confirmButtonText: '确定',
  318. cancelButtonText: '取消',
  319. })
  320. await clickLog({ describe: { action: '点击确认单张重拍', goods_art_no: img.goods_art_no, action_name: img.action_name } }, route)
  321. } catch (e) {
  322. await clickLog({ describe: { action: '点击取消单张重拍', goods_art_no: img.goods_art_no, action_name: img.action_name } }, route)
  323. return
  324. }
  325. }
  326. runLoading.value = true;
  327. reNosObj.value.goods_art_no = img.goods_art_no
  328. reNosObj.value.action = 're_take_picture'
  329. let params = {
  330. id: img.action_id
  331. }
  332. clientStore.ipc.removeAllListeners(icpList.setting.getDeviceConfigDetail);
  333. clientStore.ipc.send(icpList.setting.getDeviceConfigDetail, params);
  334. clientStore.ipc.on(icpList.setting.getDeviceConfigDetail, (event, result) => {
  335. console.log('getDeviceConfigDetail')
  336. console.log(result)
  337. if (result.code == 0 && result.data) {
  338. clientStore.ipc.removeAllListeners(icpList.setting.getDeviceConfigDetail);
  339. this_run_mcu_single(result.data)
  340. } else if (result.msg) {
  341. runLoading.value = false;
  342. reNosObj.value.goods_art_no = ''
  343. reNosObj.value.action = ''
  344. ElMessage.error(result.msg)
  345. }
  346. });
  347. function this_run_mcu_single(data) {
  348. clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu_single');
  349. socketStore.sendMessage({
  350. type: 'run_mcu_single',
  351. data: {
  352. camera_height: Number(data.camera_height),
  353. camera_angle: Number(data.camera_angle),
  354. led_switch: data.led_switch,
  355. id: 0,
  356. mode_type: data.mode_type,
  357. turntable_position: Number(data.turntable_position),
  358. action_name: data.action_name || '测试',
  359. turntable_angle: Number(data.turntable_angle),
  360. shoe_upturn: Number(data.shoe_upturn),
  361. camera_focal_distance: Number(data.camera_focal_distance),
  362. action_index: 1,
  363. number_focus: 0,
  364. take_picture: false,
  365. point_name:data.point_name,
  366. is_move_device: data.is_move_device,
  367. pre_delay: 0,
  368. after_delay: 0,
  369. }
  370. });
  371. clientStore.ipc.on(icpList.socket.message + '_run_mcu_single', async (event, result) => {
  372. console.log('_run_mcu_single_row')
  373. clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu_single');
  374. this_re_take_picture(data)
  375. })
  376. }
  377. async function this_re_take_picture(data) {
  378. await ElMessageBox.alert('已复位到该视图下,请把鞋子摆放完毕之后,点击按钮开始重拍', '提示', {
  379. confirmButtonText: "开始重拍",
  380. showClose: false,
  381. closeOnClickModal: false,
  382. closeOnPressEscape: false
  383. })
  384. await clickLog({ describe: { action: '点击开始重拍', goods_art_no: img.goods_art_no } }, route)
  385. socketStore.sendMessage({
  386. type: 'smart_shooter_photo_take',
  387. "data": {
  388. "id": img.id,
  389. "goods_art_no": img.goods_art_no,
  390. point_name:data.point_name,
  391. is_move_device: data.is_move_device
  392. },
  393. })
  394. }
  395. }
  396. // 删除单张图片
  397. const deletePhotoRecord = async (img) => {
  398. if (!img?.id) return
  399. try {
  400. await ElMessageBox.confirm('确定要删除这张图片吗?', '提示', {
  401. confirmButtonText: '确定',
  402. cancelButtonText: '取消',
  403. })
  404. await clickLog({ describe: { action: '点击确认删除单张图片', goods_art_no: img.goods_art_no, record_id: img.id, action_name: img.action_name } }, route)
  405. } catch (e) {
  406. await clickLog({ describe: { action: '点击取消删除单张图片', goods_art_no: img.goods_art_no, record_id: img.id, action_name: img.action_name } }, route)
  407. return
  408. }
  409. const channel = icpList.socket.message + '_delete_photo_record'
  410. clientStore.ipc.removeAllListeners(channel)
  411. socketStore.sendMessage({
  412. type: 'delete_photo_record',
  413. data: {
  414. record_id: img.id,
  415. },
  416. })
  417. clientStore.ipc.on(channel, (event, result) => {
  418. clientStore.ipc.removeAllListeners(channel)
  419. console.log('_delete_photo_record', result)
  420. if (result.code === 0) {
  421. ElMessage.success(result.msg || '图片删除成功')
  422. getPhotoRecords()
  423. if (img.goods_art_no) {
  424. scheduleSegment(img.goods_art_no)
  425. }
  426. } else if (result.msg) {
  427. ElMessage.error(result.msg)
  428. } else {
  429. ElMessage.error('图片删除失败')
  430. }
  431. })
  432. }
  433. const resetStatus = () => {
  434. runLoading.value = false;
  435. takePictureLoading.value = false;
  436. reNosObj.value.goods_art_no = ''
  437. reNosObj.value.action = ''
  438. runAction.value.goods_art_no = '';
  439. runAction.value.action = '';
  440. }
  441. //货号重拍
  442. const reTakePictureNos = async (goods_art_no, item) => {
  443. try {
  444. await ElMessageBox.confirm('此操作会先删除删除货号:' + goods_art_no + '的拍摄数据吗,需要继续吗?', '提示', {
  445. confirmButtonText: '确定',
  446. cancelButtonText: '取消',
  447. })
  448. await clickLog({ describe: { action: '点击确认重拍货号', goods_art_no } }, route)
  449. } catch (e) {
  450. await clickLog({ describe: { action: '点击取消重拍货号', goods_art_no } }, route)
  451. return
  452. }
  453. reNosObj.value.goods_art_no = goods_art_no
  454. reNosObj.value.action = '执行左脚程序'
  455. console.log(item);
  456. if (item.items && typeof item.items === 'object' && item.items[0].PhotoRecord.image_deal_mode) {
  457. reNosObj.value.action = '执行右脚程序'
  458. }
  459. del({ goods_art_nos: [goods_art_no] })
  460. }
  461. /**
  462. * 检查是否可以进入下一步操作。
  463. */
  464. const next = async function () {
  465. if (runLoading.value) {
  466. ElMessage.error('正在拍摄中,请稍候')
  467. return;
  468. }
  469. if (goodsList.length) {
  470. ElMessage.error('请先拍摄商品。')
  471. return;
  472. }
  473. }
  474. const oneClickStop = () => {
  475. if (!(runLoading.value || takePictureLoading.value)) {
  476. ElMessage.error('拍摄程序已结束,不需要单独停止!')
  477. return
  478. } else {
  479. socketStore.sendMessage({
  480. type: 'stop_action',
  481. })
  482. }
  483. }
  484. /**
  485. * 打开最近一张拍摄图
  486. */
  487. const getLastPhotoRecord = async () => {
  488. return;
  489. if (goodsList.value && goodsList.value.length === 0) return;
  490. clientStore.ipc.removeAllListeners(icpList.takePhoto.getLastPhotoRecord);
  491. clientStore.ipc.send(icpList.takePhoto.getLastPhotoRecord,);
  492. clientStore.ipc.on(icpList.takePhoto.getLastPhotoRecord, (event, result) => {
  493. console.log('getLastPhotoRecord');
  494. console.log(result.data?.goods_art_no);
  495. clientStore.ipc.removeAllListeners(icpList.takePhoto.getLastPhotoRecord);
  496. if (result.code === 0) {
  497. if (lastPhoto.value?.photo_file_name) {
  498. // if( lastPhoto.value?.image_path == result.data?.image_path) return;
  499. if (runAction.value.goods_art_no === result.data?.goods_art_no) {
  500. showlastPhoto.value = true
  501. }
  502. }
  503. lastPhoto.value = result.data
  504. } else if (result.msg) {
  505. ElMessage.error(result.msg)
  506. }
  507. });
  508. }
  509. /**
  510. * 打开主图详情页面。
  511. */
  512. function openPhotographyDetail() {
  513. // 埋点:开始生成
  514. clickLog({ describe: { action: '开始生成', goods_count: goodsList.value.length, goods_art_nos: goodsList.value.map(item => item.goods_art_no) } }, route);
  515. if (runLoading.value || takePictureLoading.value) {
  516. ElMessage.error('正在拍摄中,请稍候')
  517. return;
  518. }
  519. const { href } = Router.resolve({
  520. name: 'PhotographyDetail',
  521. query: {
  522. goods_art_nos: goodsList.value.map(item => item.goods_art_no),
  523. }
  524. })
  525. clientStore.ipc.removeAllListeners(icpList.utils.openMain);
  526. let params = {
  527. title: '主图与详情生成',
  528. width: 3840,
  529. height: 2160,
  530. frame: true,
  531. id: "PhotographyDetail",
  532. url: getRouterUrl(href)
  533. }
  534. clientStore.ipc.send(icpList.utils.openMain, params);
  535. }
  536. /*高级生成*/
  537. const onGenerateCLick = (menu, item) => {
  538. if (menu.name === '历史记录' || menu.name === '湛江场景图') {
  539. menu.click()
  540. return
  541. }
  542. const firstWithImagePath = item.items.find(
  543. (image) => image.PhotoRecord.image_path
  544. );
  545. if (firstWithImagePath) {
  546. menu.click({
  547. query: {
  548. image_path: firstWithImagePath.PhotoRecord.image_path
  549. }
  550. })
  551. } else {
  552. menu.click()
  553. }
  554. }
  555. // 打开输出目录:appConfig.appPath + '/build/extraResources/py/output'
  556. const openOutputDir = () => {
  557. try {
  558. const appPath = configInfoStore?.appConfig?.appPath || ''
  559. if (!appPath) {
  560. ElMessage.error('未获取到应用目录 appPath')
  561. return
  562. }
  563. const fullPath = `${appPath}\\output`
  564. clientStore.ipc.removeAllListeners(icpList.utils.shellFun);
  565. clientStore.ipc.send(icpList.utils.shellFun, {
  566. action: 'openMkPath',
  567. params: fullPath.replace(/\//g, '\\')
  568. });
  569. } catch (e) {
  570. console.error(e)
  571. ElMessage.error('打开目录失败')
  572. }
  573. }
  574. const menu = computed(() => {
  575. // 生成图目录(独立菜单项,点击直接打开目录)
  576. const outputDirMenu = {
  577. name: '生成图目录',
  578. click() {
  579. openOutputDir()
  580. }
  581. }
  582. // 快捷操作菜单子项(不含生成图目录)
  583. const quickActionsChildren = [
  584. {
  585. type: 'OneClickRelease'
  586. },
  587. {
  588. type: 'OneKeyDown'
  589. }
  590. ]
  591. // 一级导航:高级生成下拉
  592. const generateMenu = {
  593. ...generate,
  594. children: getGenerateChildren()
  595. }
  596. if (configInfoStore.appModel === 2) {
  597. return [
  598. {
  599. type: 'setting'
  600. },
  601. {
  602. name: '快捷操作',
  603. children: [
  604. ...quickActionsChildren,
  605. {
  606. name: '切换模式',
  607. click() {
  608. configInfoStore.updateAppModel(1)
  609. Router.push({
  610. name: 'PhotographyCheck'
  611. })
  612. }
  613. }
  614. ]
  615. },
  616. generateMenu,
  617. outputDirMenu
  618. ]
  619. }
  620. if (useUserInfoStore.isHLM || configInfoStore.appConfig.debug) {
  621. return [
  622. {
  623. type: 'setting'
  624. },
  625. {
  626. name: '快捷操作',
  627. children: quickActionsChildren
  628. },
  629. {
  630. name: '开发工具',
  631. children: [
  632. {
  633. type: 'developer'
  634. }
  635. ]
  636. },
  637. generateMenu,
  638. outputDirMenu
  639. ]
  640. }
  641. return [
  642. {
  643. type: 'setting'
  644. },
  645. {
  646. name: '快捷操作',
  647. children: quickActionsChildren
  648. },
  649. generateMenu,
  650. outputDirMenu
  651. ]
  652. })
  653. const onRemoteControl = async (type) => {
  654. const pointName = await getPhotoPointName()
  655. if (type == 'take_picture') {
  656. // 埋点:手动拍照
  657. clickLog({ describe: { action: '点击遥控器拍照按钮', point_name: pointName } }, route);
  658. if (runLoading.value || takePictureLoading.value) {
  659. ElMessage.error('拍摄程序正在运行,请稍候')
  660. return
  661. }
  662. ElMessage.success('正在拍摄中,请稍候')
  663. takePictureLoading.value = true;
  664. socketStore.sendMessage({
  665. type: 'handler_take_picture',
  666. data: {
  667. point_name: pointName
  668. }
  669. })
  670. return;
  671. }
  672. if (!goods_art_no.value) {
  673. ElMessage.error('请在左侧第一步中,先扫描货号或者手动输入货号!')
  674. goodsArtNo.value?.focus() // 聚焦输入框
  675. return;
  676. }
  677. let action = '执行左脚程序'
  678. if (type === 'right') action = '执行右脚程序'
  679. // 埋点:遥控器启动拍摄
  680. clickLog({ describe: { action: `点击遥控器${type === 'left' ? '左脚' : '右脚'}按钮`, goods_art_no: goods_art_no.value, point_name: pointName } }, route);
  681. await runGoods({
  682. "action": action,
  683. "goods_art_no": goods_art_no.value
  684. })
  685. }
  686. /**
  687. * 与拍摄页按钮相同:把货号写进输入框,再点左脚或右脚。
  688. */
  689. async function triggerTakePhoto(params: TakePhotoParams) {
  690. if (runLoading.value || takePictureLoading.value) {
  691. return { started: false, code: 1, msg: '拍摄程序正在运行,请稍候' }
  692. }
  693. goods_art_no.value = params.goods_art_no
  694. let runError = ''
  695. captureRunError = (msg: string) => {
  696. runError = msg
  697. }
  698. try {
  699. await onRemoteControl(params.foot)
  700. } finally {
  701. captureRunError = null
  702. }
  703. if (runError) return { started: false, code: 1, msg: runError }
  704. if (!runLoading.value) return { started: false, code: 1, msg: '未能开始拍摄' }
  705. return { started: true, code: 0 }
  706. }
  707. // 初始化事件监听
  708. const initEventListeners = () => {
  709. unbindTakePhoto?.()
  710. unbindTakePhoto = registerTakePhotoTrigger(triggerTakePhoto, {
  711. onFail(msg) {
  712. runLoading.value = false
  713. runAction.value.action = ''
  714. runAction.value.goods_art_no = ''
  715. if (msg) ElMessage.error(msg)
  716. },
  717. })
  718. // 监听蓝牙扫描事件
  719. clientStore.ipc.on(icpList.socket.message + '_blue_tooth_scan', (event, result) => {
  720. console.log('_blue_tooth_scan')
  721. if (result.code === 0 && result.data?.data) {
  722. console.log(goods_art_no.value);
  723. if (!goods_art_no.value) {
  724. ElMessage.error('请在左侧第一步中,先扫描货号或者手动输入货号!')
  725. goodsArtNo.value?.focus() // 聚焦输入框
  726. return;
  727. }
  728. runGoods({
  729. ...result.data?.data,
  730. goods_art_no: goods_art_no.value
  731. })
  732. }
  733. });
  734. // 监听图片处理完成事件
  735. clientStore.ipc.on(icpList.socket.message + '_image_process', (event, result) => {
  736. console.log('_image_process')
  737. console.log(result)
  738. getPhotoRecords()
  739. // 延迟两秒再获取一遍数据
  740. setTimeout(() => {
  741. getPhotoRecords()
  742. }, 3000)
  743. })
  744. // 监听拍照完成事件
  745. clientStore.ipc.on(icpList.socket.message + '_photo_take', (event, result) => {
  746. console.log('_photo_take')
  747. console.log(result)
  748. if (result.status === 2 && result.msg.includes('执行完成')) {
  749. getPhotoRecords()
  750. searchGoodsArtNo.value = ''
  751. // 延迟两秒再获取一遍数据
  752. setTimeout(() => {
  753. searchGoodsArtNo.value = ''
  754. getPhotoRecords()
  755. }, 3000)
  756. takePictureLoading.value = false;
  757. return;
  758. }
  759. if (result.code !== 0 && result.msg) {
  760. ElMessage.error(result.msg)
  761. takePictureLoading.value = false;
  762. }
  763. })
  764. // 监听一键停止
  765. clientStore.ipc.on(icpList.socket.message + '_stop_action', (event, result) => {
  766. console.log('_stop_action')
  767. console.log(result)
  768. oneClickStop()
  769. })
  770. // 监听一键停止结束
  771. clientStore.ipc.on(icpList.socket.message + '_run_mcu_stop', (event, result) => {
  772. console.log('_run_mcu_stop')
  773. resetStatus()
  774. })
  775. // 监听拍照完成后的最终状态事件
  776. clientStore.ipc.on(icpList.socket.message + '_photo_take_finish', (event, result) => {
  777. console.log('_photo_take_finish')
  778. console.log(result)
  779. if (result.code === 0) {
  780. setLogInfo(route, { action: '全部拍摄完成', goods_art_no: runAction.value.goods_art_no });
  781. // 演示模式不发送抠图,货号马上会删掉并进入下一轮
  782. if (runAction.value.goods_art_no && !isDemoMode.value) {
  783. scheduleSegment(runAction.value.goods_art_no)
  784. }
  785. const finishedGoodsNo = runAction.value.goods_art_no
  786. runLoading.value = false;
  787. runAction.value.goods_art_no = '';
  788. runAction.value.action = '';
  789. if (isDemoMode.value && !demoStopping.value) {
  790. continueDemoLoop(finishedGoodsNo || demoGoodsArtNo.value)
  791. }
  792. setTimeout(() => {
  793. showlastPhoto.value = false
  794. }, 3000)
  795. }
  796. })
  797. // 监听手动触发拍照事件
  798. clientStore.ipc.on(icpList.socket.message + '_handler_take_picture', async (event, result) => {
  799. console.log('_handler_take_picture')
  800. console.log(result)
  801. if (result.code === 0) {
  802. if(result.msg === '处理单拍消息'){
  803. const pointName = await getPhotoPointName()
  804. clickLog({ describe: { action: '实体遥控器拍照按钮', point_name: pointName } }, route);
  805. if (runLoading.value || takePictureLoading.value) {
  806. ElMessage.error('拍摄程序正在运行,请稍候')
  807. return
  808. }
  809. ElMessage.success('正在拍摄中,请稍候')
  810. takePictureLoading.value = true;
  811. socketStore.sendMessage({
  812. type: 'handler_take_picture',
  813. data: {
  814. point_name: pointName
  815. }
  816. })
  817. return;
  818. }
  819. /* if (runLoading.value || takePictureLoading.value) {
  820. ElMessage.error('拍摄程序正在运行,请稍候')
  821. return
  822. }*/
  823. /* ElMessage.success('正在拍摄中,请稍候')
  824. */
  825. takePictureLoading.value = true;
  826. // await socketStore.connectSocket();
  827. // socketStore.sendMessage(result.data)
  828. getPhotoRecords()
  829. // 延迟两秒再获取一遍数据
  830. setTimeout(() => {
  831. searchGoodsArtNo.value = ''
  832. getPhotoRecords()
  833. }, 3000)
  834. } else if (result.msg) {
  835. ElMessage.error(result.msg)
  836. }
  837. })
  838. //拍照成功 SmartShooter
  839. clientStore.ipc.on(icpList.socket.message + '_smart_shooter_photo_take', async (event, result) => {
  840. console.log('_smart_shooter_photo_take');
  841. console.log(result);
  842. if (result.code === 0) {
  843. if (!result.data.goods_art_no) return;
  844. setLogInfo(route, { action: '单张拍摄完成', goods_art_no: result.data.goods_art_no });
  845. if (reNosObj.value?.goods_art_no === result.data.goods_art_no) {
  846. runLoading.value = false;
  847. reNosObj.value.goods_art_no = ''
  848. reNosObj.value.action = ''
  849. }
  850. // 单张重拍完成且存在重拍货号时,触发抠图队列
  851. scheduleSegment(result.data.goods_art_no)
  852. if (smartShooterTimeout) {
  853. clearTimeout(smartShooterTimeout);
  854. }
  855. setTimeout(() => {
  856. showlastPhoto.value = true;
  857. lastPhoto.value = {
  858. file_path: result.data.photo_file_name,
  859. action_name: result.data.action_name
  860. };
  861. setTimeout(() => {
  862. if (!runAction.value.goods_art_no) {
  863. showlastPhoto.value = false;
  864. }
  865. }, 3000)
  866. }, 100);
  867. smartShooterTimeout = setTimeout(() => {
  868. getPhotoRecords();
  869. if (!runAction.value.goods_art_no) {
  870. showlastPhoto.value = false;
  871. }
  872. }, 2000);
  873. } else if (result.msg) {
  874. runLoading.value = false;
  875. reNosObj.value.goods_art_no = ''
  876. reNosObj.value.action = ''
  877. ElMessage.error(result.msg)
  878. }
  879. })
  880. // 监听拍照完成后的最终状态事件
  881. clientStore.ipc.on(icpList.socket.message + '_run_mcu_update', (event, result) => {
  882. console.log('run_mcu_updat print_time:' + new Date().toLocaleString())
  883. console.log('run_mcu_update print_time:' + JSON.stringify(result))
  884. if (result.code === 0) {
  885. if (result.data?.file_path) {
  886. if (lastPhoto.value?.file_path == result.data?.file_path) return;
  887. let goods_art_no = runAction.value.goods_art_no || reNosObj.value.goods_art_no
  888. if (goods_art_no === result.data?.goods_art_no) {
  889. showlastPhoto.value = true
  890. goodsList.value.map(item => {
  891. if (item.goods_art_no === result.data?.goods_art_no) {
  892. item.items[result.data.image_index].PhotoRecord.image_path = result.data?.file_path
  893. result.data.action_name = item.items[result.data.image_index].action_name
  894. setTimeout(() => {
  895. item.items[result.data.image_index].PhotoRecord.image_path = result.data?.file_path
  896. }, 1000)
  897. setTimeout(() => {
  898. showlastPhoto.value = false
  899. }, 3000)
  900. }
  901. })
  902. setTimeout(() => {
  903. getPhotoRecords()
  904. }, 2000)
  905. }
  906. lastPhoto.value = result.data
  907. }
  908. } else if (result.msg) {
  909. ElMessage.error(result.msg)
  910. }
  911. if (reNosObj.value.goods_art_no) {
  912. resetStatus()
  913. }
  914. })
  915. }
  916. // 清理事件监听
  917. const cleanupEventListeners = () => {
  918. unbindTakePhoto?.()
  919. unbindTakePhoto = null
  920. clientStore.ipc.removeAllListeners(icpList.socket.message + '_blue_tooth_scan');
  921. clientStore.ipc.removeAllListeners(icpList.socket.message + '_image_process');
  922. clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu');
  923. clientStore.ipc.removeAllListeners(icpList.socket.message + '_photo_take');
  924. clientStore.ipc.removeAllListeners(icpList.socket.message + '_photo_take_finish');
  925. clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu_update');
  926. clientStore.ipc.removeAllListeners(icpList.socket.message + '_stop_action');
  927. clientStore.ipc.removeAllListeners(icpList.socket.message + '_smart_shooter_photo_take');
  928. clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu_stop');
  929. clientStore.ipc.removeAllListeners(icpList.socket.message + '_digicam_take_picture');
  930. clientStore.ipc.removeAllListeners(icpList.socket.message + '_segment_progress');
  931. // 清理抠图队列的定时器
  932. try {
  933. segmentQueue.forEach((t) => { if (t) clearTimeout(t) })
  934. segmentQueue.clear()
  935. } catch (e) { }
  936. }
  937. // 监听蓝牙扫描
  938. checkInfoStore.set_blue_tooth_scan_NO('')
  939. watchEffect(async () => {
  940. if (checkInfoStore.blue_tooth_scan_NO) {
  941. ElMessage.success('商品货号' + checkInfoStore.blue_tooth_scan_NO + '获取成功,请在遥控器上按下左或右脚按键,启动拍摄')
  942. goods_art_no.value = checkInfoStore.blue_tooth_scan_NO
  943. checkInfoStore.set_blue_tooth_scan_NO('')
  944. }
  945. })
  946. /**
  947. * 导入图片目录。
  948. * @param dirPath - 需要导入的目录路径。
  949. */
  950. const importDirs = function (dirPath: string) {
  951. clientStore.ipc.removeAllListeners(icpList.takePhoto.import_dirs);
  952. clientStore.ipc.send(icpList.takePhoto.import_dirs, { dir_path: dirPath });
  953. clientStore.ipc.on(icpList.takePhoto.import_dirs, (event, result) => {
  954. clientStore.ipc.removeAllListeners(icpList.takePhoto.import_dirs);
  955. if (result.code === 0) {
  956. getPhotoRecords()
  957. } else {
  958. ElMessage.error(result.msg || '导入图片失败')
  959. }
  960. });
  961. }
  962. /**
  963. * 演示模式:获取配置列表
  964. * @param type 0-左脚程序, 1-右脚程序
  965. */
  966. const fetchDemoConfigList = async (type: number) => {
  967. demoConfigList.value = []
  968. demoSelectedConfigId.value = null
  969. const result = await getTopTabs({ type })
  970. if (result.code == 0 && result.data?.tabs) {
  971. demoConfigList.value = result.data.tabs
  972. if (demoConfigList.value.length > 0) {
  973. demoSelectedConfigId.value = demoConfigList.value[0].id
  974. }
  975. }
  976. }
  977. /**
  978. * 演示模式:打开配置选择对话框
  979. */
  980. const openDemoMode = async () => {
  981. demoGoodsArtNo.value = '演示货号1'
  982. demoProgramType.value = '执行左脚程序'
  983. demoSelectedConfigId.value = null
  984. demoConfigList.value = []
  985. // 获取左脚程序配置列表
  986. await fetchDemoConfigList(0)
  987. demoDialogVisible.value = true
  988. }
  989. /**
  990. * 演示模式:监听程序类型变化,重新获取配置列表
  991. */
  992. const onDemoProgramTypeChange = async () => {
  993. const type = demoProgramType.value === '执行左脚程序' ? 0 : 1
  994. await fetchDemoConfigList(type)
  995. }
  996. /**
  997. * 演示模式:开始演示
  998. */
  999. const startDemoMode = async () => {
  1000. if (!demoGoodsArtNo.value.trim()) {
  1001. ElMessage.error('请输入货号')
  1002. return
  1003. }
  1004. if (!demoSelectedConfigId.value) {
  1005. ElMessage.error('请选择配置')
  1006. return
  1007. }
  1008. // 设置演示模式标记
  1009. isDemoMode.value = true
  1010. // 设置货号
  1011. goods_art_no.value = demoGoodsArtNo.value
  1012. goods_art_no_tpl.value = demoGoodsArtNo.value
  1013. demoDialogVisible.value = false
  1014. // 直接启动拍摄
  1015. runGoods({
  1016. action: demoProgramType.value,
  1017. goods_art_no: demoGoodsArtNo.value
  1018. })
  1019. }
  1020. function waitForMcuStop(timeoutMs = 10000) {
  1021. return new Promise<void>((resolve) => {
  1022. const channel = icpList.socket.message + '_run_mcu_stop'
  1023. let settled = false
  1024. const finish = () => {
  1025. if (settled) return
  1026. settled = true
  1027. clearTimeout(timer)
  1028. if (clientStore.ipc?.removeListener) {
  1029. clientStore.ipc.removeListener(channel, onStop)
  1030. }
  1031. resolve()
  1032. }
  1033. const onStop = () => finish()
  1034. const timer = setTimeout(finish, timeoutMs)
  1035. clientStore.ipc.on(channel, onStop)
  1036. })
  1037. }
  1038. /**
  1039. * 演示模式:停止演示
  1040. * 先停止拍摄,再删除货号,然后退出演示模式
  1041. */
  1042. const stopDemoMode = async () => {
  1043. if (!isDemoMode.value || demoStopping.value) return
  1044. demoStopping.value = true
  1045. const demoGoodsNo = demoGoodsArtNo.value
  1046. try {
  1047. if (runLoading.value || takePictureLoading.value) {
  1048. const stopWait = waitForMcuStop()
  1049. socketStore.sendMessage({
  1050. type: 'stop_action',
  1051. })
  1052. await stopWait
  1053. }
  1054. resetStatus()
  1055. try {
  1056. await del({ goods_art_nos: [demoGoodsNo] })
  1057. } catch (e) {
  1058. console.error('删除演示货号失败:', e)
  1059. }
  1060. isDemoMode.value = false
  1061. goods_art_no.value = ''
  1062. goods_art_no_tpl.value = ''
  1063. ElMessage.success('已退出演示模式')
  1064. } finally {
  1065. demoStopping.value = false
  1066. }
  1067. }
  1068. /**
  1069. * 演示模式:拍完删货号后自动开始下一轮,直到用户点停止
  1070. */
  1071. const continueDemoLoop = (goodsArtNo: string) => {
  1072. const demoGoodsNo = goodsArtNo || demoGoodsArtNo.value
  1073. const demoAction = demoProgramType.value
  1074. setTimeout(async () => {
  1075. if (!isDemoMode.value || demoStopping.value) return
  1076. try {
  1077. await del({ goods_art_nos: [demoGoodsNo] }, { silent: true })
  1078. } catch (e) {
  1079. console.error('演示循环删除货号失败:', e)
  1080. }
  1081. if (!isDemoMode.value || demoStopping.value) return
  1082. runGoods({
  1083. action: demoAction,
  1084. goods_art_no: demoGoodsNo
  1085. })
  1086. }, 2000)
  1087. }
  1088. /**
  1089. * 删除所有货号。
  1090. */
  1091. const deleteAllGoods = function () {
  1092. clientStore.ipc.removeAllListeners(icpList.takePhoto.delete_all_goods_arts);
  1093. clientStore.ipc.send(icpList.takePhoto.delete_all_goods_arts);
  1094. clientStore.ipc.on(icpList.takePhoto.delete_all_goods_arts, (event, result) => {
  1095. clientStore.ipc.removeAllListeners(icpList.takePhoto.delete_all_goods_arts);
  1096. if (result.code === 0) {
  1097. ElMessage.success('删除所有货号成功')
  1098. getPhotoRecords()
  1099. } else {
  1100. ElMessage.error(result.msg || '删除所有货号失败')
  1101. }
  1102. });
  1103. }
  1104. return {
  1105. loading,
  1106. runLoading,
  1107. takePictureLoading,
  1108. goodsList,
  1109. pageSize,
  1110. currentPage,
  1111. totalPages,
  1112. goods_art_no_tpl,
  1113. goods_art_no,
  1114. runAction,
  1115. lastPhoto,
  1116. showlastPhoto,
  1117. goodsArtNo,
  1118. searchGoodsArtNo,
  1119. menu,
  1120. configInfoStore,
  1121. getTime,
  1122. getFilePath,
  1123. getPhotoRecords,
  1124. delAll,
  1125. delGoods,
  1126. del,
  1127. importDirs,
  1128. deleteAllGoods,
  1129. reTakePicture,
  1130. reTakePictureNos,
  1131. deletePhotoRecord,
  1132. oneClickStop,
  1133. onRemoteControl,
  1134. openPhotographyDetail,
  1135. generateChildren,
  1136. onGenerateCLick,
  1137. initEventListeners,
  1138. cleanupEventListeners,
  1139. resetStatus,
  1140. // 演示模式
  1141. isDemoMode,
  1142. demoStopping,
  1143. demoGoodsArtNo,
  1144. demoProgramType,
  1145. demoConfigList,
  1146. demoSelectedConfigId,
  1147. demoDialogVisible,
  1148. openDemoMode,
  1149. startDemoMode,
  1150. stopDemoMode,
  1151. onDemoProgramTypeChange,
  1152. }
  1153. }