Blame view

美国版/Food Labeling Management App UniApp/src/services/usAppLabeling.ts 11.6 KB
143afd59   杨鑫   打印,标签
1
  import type {
43d16ca6   杨鑫   打印日志
2
3
    PrintLogGetListInputVo,
    PrintLogItemDto,
143afd59   杨鑫   打印,标签
4
5
6
7
8
    UsAppLabelCategoryTreeNodeDto,
    UsAppLabelingProductNodeDto,
    UsAppLabelPreviewInputVo,
    UsAppLabelPrintInputVo,
    UsAppLabelPrintOutputDto,
43d16ca6   杨鑫   打印日志
9
    UsAppLabelReprintInputVo,
143afd59   杨鑫   打印,标签
10
11
12
    UsAppLabelTypeNodeDto,
    UsAppProductCategoryNodeDto,
  } from '../types/usAppLabeling'
43d16ca6   杨鑫   打印日志
13
  import { extractPagedItems } from '../utils/pagedList'
143afd59   杨鑫   打印,标签
14
  import { usAppApiRequest } from '../utils/usAppApiRequest'
699ea6e8   杨鑫   完善打印逻辑
15
  import { enqueueOfflineMutation, fetchWithOfflineCache, isNetworkOnline } from '../utils/sqliteSync'
143afd59   杨鑫   打印,标签
16
  
a001da6d   杨鑫   APP 预览打印
17
18
19
  /** 接口 9:与文档路径一致,供日志与请求共用 */
  export const US_APP_LABEL_PRINT_PATH = '/api/app/us-app-labeling/print' as const
  
143afd59   杨鑫   打印,标签
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
  function asArr(v: unknown): unknown[] {
    return Array.isArray(v) ? v : []
  }
  
  /** 兼容 camelCase / PascalCase,对齐《标签模块接口对接说明(6).md》8.1 四级树 */
  function normalizeLabelingTreePayload(raw: unknown): UsAppLabelCategoryTreeNodeDto[] {
    const list = asArr(raw)
    return list.map((node: any) => {
      const pcs = asArr(node?.productCategories ?? node?.ProductCategories)
      const productCategories: UsAppProductCategoryNodeDto[] = pcs.map((p: any) => {
        const prods = asArr(p?.products ?? p?.Products)
        const products: UsAppLabelingProductNodeDto[] = prods.map((x: any) => {
          const lts = asArr(x?.labelTypes ?? x?.LabelTypes)
          const labelTypes: UsAppLabelTypeNodeDto[] = lts.map((t: any) => ({
            labelTypeId: String(t?.labelTypeId ?? t?.LabelTypeId ?? ''),
            typeName: String(t?.typeName ?? t?.TypeName ?? ''),
            orderNum: Number(t?.orderNum ?? t?.OrderNum ?? 0),
            labelCode: String(t?.labelCode ?? t?.LabelCode ?? ''),
            templateCode: (t?.templateCode ?? t?.TemplateCode ?? null) as string | null,
            labelSizeText: (t?.labelSizeText ?? t?.LabelSizeText ?? null) as string | null,
          }))
          return {
            productId: String(x?.productId ?? x?.ProductId ?? ''),
dc39baae   李曜臣   后台端:管理员查看日志优化,产品与...
43
44
45
46
47
            templateId: (x?.templateId ?? x?.TemplateId ?? null) as string | null,
            templateCode: (x?.templateCode ?? x?.TemplateCode ?? null) as string | null,
            templateLabelSizeText: (x?.templateLabelSizeText ?? x?.TemplateLabelSizeText ?? null) as
              | string
              | null,
143afd59   杨鑫   打印,标签
48
49
50
51
52
53
54
55
56
57
58
59
            productName: String(x?.productName ?? x?.ProductName ?? ''),
            productCode: String(x?.productCode ?? x?.ProductCode ?? ''),
            productImageUrl: (x?.productImageUrl ?? x?.ProductImageUrl ?? null) as string | null,
            subtitle: String(x?.subtitle ?? x?.Subtitle ?? ''),
            labelTypeCount: Number(x?.labelTypeCount ?? x?.LabelTypeCount ?? labelTypes.length),
            labelTypes,
          }
        })
        const catId = p?.categoryId ?? p?.CategoryId
        return {
          categoryId: catId != null && String(catId).trim() !== '' ? String(catId) : null,
          categoryPhotoUrl: (p?.categoryPhotoUrl ?? p?.CategoryPhotoUrl ?? null) as string | null,
3ead62fc   杨鑫   优化
60
61
62
63
64
65
66
67
          // 透传平台端扩展字段(用于 APP 按 buttonAppearance 渲染 icon)
          displayText: (p?.displayText ?? p?.DisplayText ?? null) as string | null,
          buttonAppearance: (p?.buttonAppearance ?? p?.ButtonAppearance ?? null) as string | null,
          buttonTextColor: (p?.buttonTextColor ?? p?.ButtonTextColor ?? null) as string | null,
          buttonBgColor: (p?.buttonBgColor ?? p?.ButtonBgColor ?? null) as string | null,
          buttonImageUrl: (p?.buttonImageUrl ?? p?.ButtonImageUrl ?? null) as string | null,
          buttonStyleJson: (p?.buttonStyleJson ?? p?.ButtonStyleJson ?? null) as string | null,
          availabilityType: (p?.availabilityType ?? p?.AvailabilityType ?? null) as string | null,
143afd59   杨鑫   打印,标签
68
69
70
71
72
73
74
75
76
          name: String(p?.name ?? p?.Name ?? ''),
          itemCount: Number(p?.itemCount ?? p?.ItemCount ?? products.length),
          products,
        }
      })
      return {
        id: String(node?.id ?? node?.Id ?? ''),
        categoryName: String(node?.categoryName ?? node?.CategoryName ?? ''),
        categoryPhotoUrl: (node?.categoryPhotoUrl ?? node?.CategoryPhotoUrl ?? null) as string | null,
699ea6e8   杨鑫   完善打印逻辑
77
78
79
80
        displayText: (node?.displayText ?? node?.DisplayText ?? null) as string | null,
        buttonBgColor: (node?.buttonBgColor ?? node?.ButtonBgColor ?? null) as string | null,
        buttonImageUrl: (node?.buttonImageUrl ?? node?.ButtonImageUrl ?? null) as string | null,
        buttonTextColor: (node?.buttonTextColor ?? node?.ButtonTextColor ?? null) as string | null,
3ead62fc   杨鑫   优化
81
        // 透传平台端扩展字段(用于 APP 按 buttonAppearance 渲染 icon)
699ea6e8   杨鑫   完善打印逻辑
82
83
        buttonAppearance: (node?.buttonAppearance ?? node?.ButtonAppearance ?? null) as string | string[] | null,
        buttonStyleJson: (node?.buttonStyleJson ?? node?.ButtonStyleJson ?? null) as string | null,
143afd59   杨鑫   打印,标签
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
        orderNum: Number(node?.orderNum ?? node?.OrderNum ?? 0),
        productCategories,
      }
    })
  }
  
  function buildLabelingTreePath(params: {
    locationId: string
    keyword?: string
    labelCategoryId?: string
  }): string {
    const q: string[] = [`locationId=${encodeURIComponent(params.locationId)}`]
    if (params.keyword != null && String(params.keyword).trim() !== '') {
      q.push(`keyword=${encodeURIComponent(String(params.keyword).trim())}`)
    }
    if (params.labelCategoryId != null && String(params.labelCategoryId).trim() !== '') {
      q.push(`labelCategoryId=${encodeURIComponent(String(params.labelCategoryId).trim())}`)
    }
    return `/api/app/us-app-labeling/labeling-tree?${q.join('&')}`
  }
  
  /** 接口 8.1 */
  export async function fetchUsAppLabelingTree(input: {
    locationId: string
    keyword?: string
    labelCategoryId?: string
  }): Promise<UsAppLabelCategoryTreeNodeDto[]> {
699ea6e8   杨鑫   完善打印逻辑
111
112
113
114
115
116
117
118
    const key = `labeling-tree:${input.locationId}:${input.keyword || ''}:${input.labelCategoryId || ''}`
    return fetchWithOfflineCache('labeling', key, async () => {
      const raw = await usAppApiRequest<unknown>({
        path: buildLabelingTreePath(input),
        method: 'GET',
        auth: true,
      })
      return normalizeLabelingTreePayload(raw)
143afd59   杨鑫   打印,标签
119
    })
143afd59   杨鑫   打印,标签
120
121
122
123
124
125
126
127
128
129
130
131
  }
  
  /** 接口 8.2 */
  export async function postUsAppLabelPreview(body: UsAppLabelPreviewInputVo): Promise<unknown> {
    return usAppApiRequest<unknown>({
      path: '/api/app/us-app-labeling/preview',
      method: 'POST',
      auth: true,
      data: body,
    })
  }
  
a001da6d   杨鑫   APP 预览打印
132
133
134
135
  /**
   * 接口 9.1 原始请求。
   * 注意:仅供业务落库场景调用;打印机设置页「测试打印」、蓝牙页 Test Print 等 **不得** 使用(避免脏数据)。
   */
143afd59   杨鑫   打印,标签
136
  export async function postUsAppLabelPrint(body: UsAppLabelPrintInputVo): Promise<UsAppLabelPrintOutputDto> {
699ea6e8   杨鑫   完善打印逻辑
137
138
139
140
141
142
143
144
    const online = await isNetworkOnline()
    if (!online) {
      await enqueueOfflineMutation('/api/app/us-app-labeling/print', 'POST', body)
      return {
        taskId: body.clientRequestId || `offline-${Date.now()}`,
        printQuantity: Math.max(1, Number(body.printQuantity || 1)),
      }
    }
43d16ca6   杨鑫   打印日志
145
146
147
148
149
150
151
    console.log('[UsAppLabelPrint] 接口 9 请求前 — path:', US_APP_LABEL_PRINT_PATH)
    console.log('[UsAppLabelPrint] 接口 9 请求体 body:', body)
    try {
      console.log('[UsAppLabelPrint] 接口 9 请求体 JSON:', JSON.stringify(body))
    } catch {
      console.log('[UsAppLabelPrint] 接口 9 请求体 JSON 序列化失败(含循环引用等)')
    }
699ea6e8   杨鑫   完善打印逻辑
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
    try {
      return await usAppApiRequest<UsAppLabelPrintOutputDto>({
        path: US_APP_LABEL_PRINT_PATH,
        method: 'POST',
        auth: true,
        data: body,
      })
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : String(e)
      // 在线请求失败时若是网络类错误,自动降级写入本地队列,避免用户操作丢失
      if (/network|timeout|offline|断网|网络/i.test(msg)) {
        await enqueueOfflineMutation('/api/app/us-app-labeling/print', 'POST', body)
        return {
          taskId: body.clientRequestId || `offline-${Date.now()}`,
          printQuantity: Math.max(1, Number(body.printQuantity || 1)),
        }
      }
      throw e
    }
143afd59   杨鑫   打印,标签
171
  }
a001da6d   杨鑫   APP 预览打印
172
173
174
175
176
177
  
  export function buildUsAppLabelPrintRequestBody(input: {
    locationId?: string | null
    labelCode?: string | null
    productId?: string | null
    printQuantity: number
b04bc3a5   杨鑫   打印日志 重新打印
178
    /** 写入接口 9 `printInputJson`:合并模板快照(可与 `buildPrintInputJson` 结果浅合并),应对齐列表 `renderTemplateJson` */
43d16ca6   杨鑫   打印日志
179
180
    mergedTemplate: Record<string, unknown>
    clientRequestId?: string | null
a001da6d   杨鑫   APP 预览打印
181
182
183
184
185
186
187
    printerMac?: string | null
    printerAddress?: string | null
  }): UsAppLabelPrintInputVo | null {
    const locationId = String(input.locationId || '').trim()
    const labelCode = String(input.labelCode || '').trim()
    if (!locationId || !labelCode) return null
  
43d16ca6   杨鑫   打印日志
188
189
190
191
192
193
194
    let printInputJson: Record<string, unknown>
    try {
      printInputJson = JSON.parse(JSON.stringify(input.mergedTemplate)) as Record<string, unknown>
    } catch {
      return null
    }
  
a001da6d   杨鑫   APP 预览打印
195
196
197
198
    const body: UsAppLabelPrintInputVo = {
      locationId,
      labelCode,
      printQuantity: Math.max(1, Math.round(Number(input.printQuantity) || 1)),
43d16ca6   杨鑫   打印日志
199
      printInputJson,
a001da6d   杨鑫   APP 预览打印
200
201
      baseTime: new Date().toISOString(),
    }
43d16ca6   杨鑫   打印日志
202
203
    const cid = String(input.clientRequestId || '').trim()
    if (cid) body.clientRequestId = cid
a001da6d   杨鑫   APP 预览打印
204
205
206
207
208
209
210
211
212
213
214
    const pid = String(input.productId || '').trim()
    if (pid) body.productId = pid
    const mac = String(input.printerMac || '').trim()
    if (mac) body.printerMac = mac
    const addr = String(input.printerAddress || '').trim()
    if (addr) body.printerAddress = addr
    return body
  }
  
  /**
   * 接口 9:仅在 **标签预览页**(`pages/labels/preview`)用户打印真实标签、出纸成功后落库。
43d16ca6   杨鑫   打印日志
215
   * `mergedTemplate` 写入请求体 `printInputJson`(与 label-template 同构);缺少 `locationId` 或 `labelCode` 时不发请求。
a001da6d   杨鑫   APP 预览打印
216
217
218
219
220
221
222
   * 测试打印模板(printers / bluetooth Test Print)不走此函数。
   */
  export async function reportUsAppLabelPrintIfReady(input: {
    locationId?: string | null
    labelCode?: string | null
    productId?: string | null
    printQuantity: number
43d16ca6   杨鑫   打印日志
223
224
    mergedTemplate: Record<string, unknown>
    clientRequestId?: string | null
a001da6d   杨鑫   APP 预览打印
225
226
227
228
229
230
231
    printerMac?: string | null
    printerAddress?: string | null
  }): Promise<UsAppLabelPrintOutputDto | null> {
    const body = buildUsAppLabelPrintRequestBody(input)
    if (!body) return null
    return postUsAppLabelPrint(body)
  }
43d16ca6   杨鑫   打印日志
232
233
234
  
  /** 接口 10:分页打印日志 */
  export async function fetchUsAppPrintLogList (input: PrintLogGetListInputVo) {
699ea6e8   杨鑫   完善打印逻辑
235
236
237
238
239
240
241
242
243
244
245
246
247
    const key = `print-log:${input.locationId}:${input.skipCount ?? 1}:${input.maxResultCount ?? 20}`
    return fetchWithOfflineCache('labeling', key, async () => {
      const raw = await usAppApiRequest<unknown>({
        path: '/api/app/us-app-labeling/get-print-log-list',
        method: 'POST',
        auth: true,
        data: {
          locationId: input.locationId,
          skipCount: input.skipCount ?? 1,
          maxResultCount: input.maxResultCount ?? 20,
        },
      })
      return extractPagedItems<PrintLogItemDto>(raw)
43d16ca6   杨鑫   打印日志
248
    })
43d16ca6   杨鑫   打印日志
249
250
251
252
  }
  
  /** 接口 11:重打并返回含 mergedTemplateJson 的出参 */
  export async function postUsAppLabelReprint (body: UsAppLabelReprintInputVo): Promise<UsAppLabelPrintOutputDto> {
699ea6e8   杨鑫   完善打印逻辑
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    const online = await isNetworkOnline()
    if (!online) {
      await enqueueOfflineMutation('/api/app/us-app-labeling/reprint', 'POST', body)
      return {
        taskId: body.clientRequestId || body.taskId || `offline-reprint-${Date.now()}`,
        printQuantity: Math.max(1, Number(body.printQuantity || 1)),
      }
    }
    try {
      return await usAppApiRequest<UsAppLabelPrintOutputDto>({
        path: '/api/app/us-app-labeling/reprint',
        method: 'POST',
        auth: true,
        data: body,
      })
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : String(e)
      if (/network|timeout|offline|断网|网络/i.test(msg)) {
        await enqueueOfflineMutation('/api/app/us-app-labeling/reprint', 'POST', body)
        return {
          taskId: body.clientRequestId || body.taskId || `offline-reprint-${Date.now()}`,
          printQuantity: Math.max(1, Number(body.printQuantity || 1)),
        }
      }
      throw e
    }
43d16ca6   杨鑫   打印日志
279
  }