| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- /**
- * 拍照接口:与拍摄页一致,先写入货号,再走左脚 / 右脚按钮。
- * POST /take_photo 由 generateServer 转发到这里。
- */
- import client from '@/stores/modules/client'
- import socket from '@/stores/modules/socket'
- import icpList from '@/utils/ipc'
- export type FootSide = 'left' | 'right'
- export type TakePhotoParams = {
- goods_art_no: string
- foot: FootSide
- action: string
- }
- type TriggerResult = { started: boolean; code: number; msg?: string }
- type Trigger = (params: TakePhotoParams) => Promise<TriggerResult>
- type FailHandler = (msg: string) => void
- let trigger: Trigger | null = null
- let onFail: FailHandler | null = null
- let shooting = false
- const TAKE_PHOTO_TIMEOUT = 10 * 60 * 1000
- const MCU_ERRORS = ['mcu设备未连接', 'mcu设备未初始化', '当前有未完成的任务']
- const LINK_ERRORS = [
- '相机未连接',
- '请检查相机',
- '请先连接相机',
- '相机是否连接',
- 'MCU 连接失败',
- 'MCU连接失败',
- 'MCU串口未连接',
- 'mcu设备未连接',
- 'MCU硬件连接失败',
- ]
- type IpcLike = {
- on: (channel: string, listener: (...args: any[]) => void) => void
- removeListener?: (channel: string, listener: (...args: any[]) => void) => void
- off?: (channel: string, listener: (...args: any[]) => void) => void
- removeAllListeners?: (channel?: string) => void
- __takePhotoOriginalRemoveAll?: (channel?: string) => void
- }
- const keptListeners = new Map<string, Set<(...args: any[]) => void>>()
- export function registerTakePhotoTrigger(fn: Trigger, hooks?: { onFail?: FailHandler }) {
- trigger = fn
- onFail = hooks?.onFail || null
- return () => {
- if (trigger === fn) {
- trigger = null
- onFail = null
- }
- }
- }
- export function parseTakePhotoBody(payload: any): { ok: true; params: TakePhotoParams } | { ok: false; msg: string } {
- const goodsArtNo = String(payload?.goods_art_no ?? payload?.goodsArtNo ?? payload?.['货号'] ?? '').trim()
- const foot = normalizeFoot(payload?.foot ?? payload?.side ?? payload?.['左右脚'] ?? payload?.type)
- if (!goodsArtNo) return { ok: false, msg: '货号不能为空' }
- if (!foot) return { ok: false, msg: '左右脚不能为空,请传 left/right 或 左/右' }
- return {
- ok: true,
- params: {
- goods_art_no: goodsArtNo,
- foot,
- action: foot === 'right' ? '执行右脚程序' : '执行左脚程序',
- },
- }
- }
- function normalizeFoot(raw: unknown): FootSide | null {
- const text = String(raw ?? '').trim()
- if (!text) return null
- const value = text.toLowerCase()
- if (value === 'right' || value === 'r' || value === '1' || text.includes('右')) return 'right'
- if (value === 'left' || value === 'l' || value === '0' || text.includes('左')) return 'left'
- return null
- }
- export async function requestTakePhoto(payload: any) {
- const parsed = parseTakePhotoBody(payload)
- if (!parsed.ok) return { code: 1, msg: parsed.msg }
- if (shooting) return { code: 1, msg: '拍摄程序正在运行,请稍候' }
- const clientStore = client()
- if (!clientStore.isClient || !clientStore.ipc) {
- return { code: 1, msg: '拍照接口仅在客户端内可用' }
- }
- shooting = true
- const pending = waitForShoot(clientStore.ipc)
- try {
- const started = trigger
- ? await trigger(parsed.params)
- : await sendRunMcu(parsed.params)
- if (pending.settled()) {
- const result = await pending.promise
- return withData(parsed.params, result)
- }
- if (!started.started) {
- pending.cancel()
- return { code: started.code || 1, msg: started.msg || '未能开始拍摄' }
- }
- const result = await pending.promise
- return withData(parsed.params, result)
- } catch (error: any) {
- pending.cancel()
- return { code: 1, msg: error?.message || '拍照失败' }
- } finally {
- shooting = false
- }
- }
- function withData(params: TakePhotoParams, result: { code: number; msg: string }) {
- return {
- code: result.code,
- msg: result.msg,
- data: {
- goods_art_no: params.goods_art_no,
- foot: params.foot,
- action: params.action,
- },
- }
- }
- /** 拍摄页未打开时,仍下发与左右脚按钮相同的 run_mcu。 */
- async function sendRunMcu(params: TakePhotoParams): Promise<TriggerResult> {
- const socketStore = socket()
- await Promise.race([
- socketStore.connectSocket(),
- new Promise((_, reject) => {
- setTimeout(() => reject(new Error('设备服务未连接')), 8000)
- }),
- ])
- socketStore.sendMessage({
- type: 'run_mcu',
- data: {
- action: params.action,
- goods_art_no: params.goods_art_no,
- },
- })
- return { started: true, code: 0 }
- }
- function waitForShoot(ipc: IpcLike) {
- let settled = false
- let stepError = ''
- let stopped = false
- let resolveFn: (value: { code: number; msg: string }) => void = () => {}
- const promise = new Promise<{ code: number; msg: string }>((resolve) => {
- resolveFn = resolve
- })
- const channels = {
- runMcu: icpList.socket.message + '_run_mcu',
- mcu: icpList.socket.message + '_mcu',
- photoTake: icpList.socket.message + '_photo_take',
- imageProcess: icpList.socket.message + '_image_process',
- finish: icpList.socket.message + '_photo_take_finish',
- stop: icpList.socket.message + '_run_mcu_stop',
- }
- const handlers: Record<string, (...args: any[]) => void> = {}
- let timer: ReturnType<typeof setTimeout>
- let release = () => {}
- const done = (result: { code: number; msg: string }, reset = false) => {
- if (settled) return
- settled = true
- clearTimeout(timer)
- release()
- if (reset) onFail?.(result.msg)
- resolveFn(result)
- }
- const failIfLinkLost = (result: any) => {
- const msg = linkErrorMessage(result)
- if (!msg) return false
- done({ code: 1, msg }, true)
- return true
- }
- handlers[channels.runMcu] = (_event, result) => {
- if (failIfLinkLost(result)) return
- if (result && result.code !== 0) {
- done({ code: 1, msg: result.msg || '拍照失败' })
- }
- }
- handlers[channels.mcu] = (_event, result) => {
- if (failIfLinkLost(result)) return
- const msg = String(result?.msg || '')
- if (result?.code === 1 && MCU_ERRORS.some((item) => msg.includes(item))) {
- done({ code: 1, msg }, true)
- }
- }
- handlers[channels.photoTake] = (_event, result) => {
- if (failIfLinkLost(result)) return
- if (result?.code !== 0 && result?.msg) stepError = result.msg
- }
- handlers[channels.imageProcess] = (_event, result) => {
- failIfLinkLost(result)
- }
- handlers[channels.stop] = (_event, result) => {
- stopped = true
- stepError = result?.msg || '拍摄已终止'
- }
- handlers[channels.finish] = (_event, result) => {
- if (stopped || stepError) {
- done({ code: 1, msg: stepError || '拍摄失败' })
- return
- }
- if (result?.code === 0) {
- done({ code: 0, msg: result.msg || '拍摄完成' })
- return
- }
- done({ code: 1, msg: result?.msg || '拍照失败' }, true)
- }
- timer = setTimeout(() => {
- done({ code: 1, msg: '拍照超时' }, true)
- }, TAKE_PHOTO_TIMEOUT)
- release = () => {
- Object.entries(handlers).forEach(([channel, handler]) => keepOff(ipc, channel, handler))
- }
- ensureKeep(ipc)
- Object.entries(handlers).forEach(([channel, handler]) => keepOn(ipc, channel, handler))
- return {
- promise,
- settled: () => settled,
- cancel: () => {
- if (settled) return
- settled = true
- clearTimeout(timer)
- release()
- },
- }
- }
- function linkErrorMessage(result: any): string | null {
- const msg = String(result?.msg || '')
- if (!msg) return null
- if (LINK_ERRORS.some((item) => msg.includes(item))) return msg
- const status = result?.status ?? result?.device_status
- if (status === -1 && (msg.includes('相机') || msg.includes('MCU') || msg.includes('mcu'))) return msg
- return null
- }
- function keepOn(ipc: IpcLike, channel: string, handler: (...args: any[]) => void) {
- let set = keptListeners.get(channel)
- if (!set) {
- set = new Set()
- keptListeners.set(channel, set)
- }
- set.add(handler)
- ipc.on(channel, handler)
- }
- function keepOff(ipc: IpcLike, channel: string, handler: (...args: any[]) => void) {
- const set = keptListeners.get(channel)
- set?.delete(handler)
- if (set && set.size === 0) keptListeners.delete(channel)
- if (typeof ipc.removeListener === 'function') {
- ipc.removeListener(channel, handler)
- } else if (typeof ipc.off === 'function') {
- ipc.off(channel, handler)
- }
- }
- /** 拍摄页卸载时会 removeAllListeners,进行中的接口监听需要挂回去。 */
- function ensureKeep(ipc: IpcLike) {
- if (!ipc || ipc.__takePhotoOriginalRemoveAll || typeof ipc.removeAllListeners !== 'function') return
- const original = ipc.removeAllListeners.bind(ipc)
- ipc.__takePhotoOriginalRemoveAll = original
- ipc.removeAllListeners = (channel?: string) => {
- const result = original(channel)
- const channels = channel ? [channel] : Array.from(keptListeners.keys())
- channels.forEach((item) => {
- keptListeners.get(item)?.forEach((handler) => ipc.on(item, handler))
- })
- return result
- }
- }
|