b04bc3a5
杨鑫
打印日志 重新打印
|
1
2
3
4
5
6
7
|
/**
* TSC/Android 位图打印:`BitmapFactory.decodeFile` 无法直接读 http(s) 或「仅相对路径」;
* 打印前把需网络的图片拉到本地临时路径,重打/预览落库里的 /picture/ URL 才能出图。
*/
import { resolveMediaUrlForApp, storedValueLooksLikeImagePath } from '../resolveMediaUrl'
import type { SystemLabelTemplate, SystemTemplateElementBase } from './types/printer'
|
58d2e61c
杨鑫
最新代码
|
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
type HydrateDebugStage = 'skip' | 'download-success' | 'download-failed' | 'no-need-download'
type HydrateDebugRecord = {
stage: HydrateDebugStage
payload: Record<string, unknown>
}
const hydrateDebugRecords: HydrateDebugRecord[] = []
const MAX_HYDRATE_DEBUG_RECORDS = 40
function logHydrateDebug (
stage: HydrateDebugStage,
payload: Record<string, unknown>,
): void {
hydrateDebugRecords.push({ stage, payload: { ...payload } })
if (hydrateDebugRecords.length > MAX_HYDRATE_DEBUG_RECORDS) {
hydrateDebugRecords.splice(0, hydrateDebugRecords.length - MAX_HYDRATE_DEBUG_RECORDS)
}
try {
console.log('[print-image-hydrate]', stage, JSON.stringify(payload))
} catch {
console.log('[print-image-hydrate]', stage, payload)
}
}
export function resetHydrateImageDebugRecords (): void {
hydrateDebugRecords.length = 0
}
export function getHydrateImageDebugReport (): string {
if (!hydrateDebugRecords.length) return 'No hydrate image records.'
const lines: string[] = []
hydrateDebugRecords.forEach((r, i) => {
const p = r.payload || {}
lines.push(`#${i + 1} ${r.stage}`)
if (p.elementId != null && p.elementId !== '') lines.push(`id=${String(p.elementId)}`)
if (p.type != null && p.type !== '') lines.push(`type=${String(p.type)}`)
if (p.reason != null && p.reason !== '') lines.push(`reason=${String(p.reason)}`)
if (p.raw != null && p.raw !== '') lines.push(`raw=${String(p.raw)}`)
if (p.resolvedUrl != null && p.resolvedUrl !== '') lines.push(`resolved=${String(p.resolvedUrl)}`)
if (p.tempFilePath != null && p.tempFilePath !== '') lines.push(`temp=${String(p.tempFilePath)}`)
lines.push('---')
})
return lines.join('\n')
}
|
b04bc3a5
杨鑫
打印日志 重新打印
|
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
/** 与 usAppApiRequest 一致:静态图 /picture/ 常需登录态,无头下载会 401 → 解码失败、纸面空白 */
function downloadAuthHeaders (): Record<string, string> {
const h: Record<string, string> = {}
try {
const token = uni.getStorageSync('access_token')
if (token) h.Authorization = `Bearer ${token}`
} catch (_) {}
return h
}
function cfgStr(config: Record<string, unknown>, keys: string[]): string {
for (const k of keys) {
const v = config[k]
if (v != null && String(v).trim() !== '') return String(v).trim()
}
return ''
}
/** 需先下载再 decodeFile 的地址(非 data:、非已是本地 file) */
function needsDownloadForDecode (raw: string): boolean {
const s = String(raw || '').trim()
if (!s) return false
if (s.startsWith('data:')) return false
if (s.startsWith('file://')) return false
|
58d2e61c
杨鑫
最新代码
|
77
|
if (s.startsWith('_doc/') || s.startsWith('_www/') || /^[A-Za-z]:[\\/]/.test(s)) return false
|
b04bc3a5
杨鑫
打印日志 重新打印
|
78
|
if (/^https?:\/\//i.test(s)) return true
|
58d2e61c
杨鑫
最新代码
|
79
80
81
82
83
84
|
if (
s.startsWith('/picture/')
|| s.startsWith('/static/')
|| s.startsWith('picture/')
|| s.startsWith('static/')
) return true
|
b04bc3a5
杨鑫
打印日志 重新打印
|
85
86
87
88
89
90
91
92
93
94
|
return false
}
function downloadUrlToTempFile (url: string): Promise<string | null> {
return new Promise((resolve) => {
if (!url) {
resolve(null)
return
}
try {
|
58d2e61c
杨鑫
最新代码
|
95
96
97
98
99
100
|
let done = false
const timer = setTimeout(() => {
if (done) return
done = true
resolve(null)
}, 6000)
|
b04bc3a5
杨鑫
打印日志 重新打印
|
101
102
103
104
|
uni.downloadFile({
url,
header: downloadAuthHeaders(),
success: (res) => {
|
58d2e61c
杨鑫
最新代码
|
105
106
107
|
if (done) return
done = true
clearTimeout(timer)
|
b04bc3a5
杨鑫
打印日志 重新打印
|
108
109
110
|
if (res.statusCode === 200 && res.tempFilePath) resolve(res.tempFilePath)
else resolve(null)
},
|
58d2e61c
杨鑫
最新代码
|
111
112
113
114
115
116
|
fail: () => {
if (done) return
done = true
clearTimeout(timer)
resolve(null)
},
|
b04bc3a5
杨鑫
打印日志 重新打印
|
117
118
119
120
121
122
123
|
})
} catch {
resolve(null)
}
})
}
|
58d2e61c
杨鑫
最新代码
|
124
125
126
127
|
async function resolveToLocalPathIfNeeded (
raw: string,
debugMeta: Record<string, unknown>,
): Promise<string | null> {
|
b04bc3a5
杨鑫
打印日志 重新打印
|
128
|
const trimmed = String(raw || '').trim()
|
58d2e61c
杨鑫
最新代码
|
129
130
131
132
133
134
135
136
|
if (!trimmed) {
logHydrateDebug('skip', { ...debugMeta, reason: 'empty-source' })
return null
}
if (!needsDownloadForDecode(trimmed)) {
logHydrateDebug('no-need-download', { ...debugMeta, raw: trimmed })
return null
}
|
b04bc3a5
杨鑫
打印日志 重新打印
|
137
|
const url = resolveMediaUrlForApp(trimmed)
|
58d2e61c
杨鑫
最新代码
|
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
if (!url) {
logHydrateDebug('download-failed', {
...debugMeta,
raw: trimmed,
reason: 'resolveMediaUrlForApp-empty',
})
return null
}
const local = await downloadUrlToTempFile(url)
if (local) {
let finalLocal = local
// #ifdef APP-PLUS
try {
const plusAny = (globalThis as any)?.plus
const converted = plusAny?.io?.convertLocalFileSystemURL?.(local)
if (converted && typeof converted === 'string') {
finalLocal = converted
}
} catch (_) {}
// #endif
logHydrateDebug('download-success', {
...debugMeta,
raw: trimmed,
resolvedUrl: url,
tempFilePath: finalLocal,
})
return finalLocal
}
logHydrateDebug('download-failed', {
...debugMeta,
raw: trimmed,
resolvedUrl: url,
reason: 'downloadFile-null',
})
return null
|
b04bc3a5
杨鑫
打印日志 重新打印
|
173
174
175
176
177
|
}
async function hydrateElement (el: SystemTemplateElementBase): Promise<SystemTemplateElementBase> {
const type = String(el.type || '').toUpperCase()
const cfg = { ...(el.config || {}) } as Record<string, unknown>
|
58d2e61c
杨鑫
最新代码
|
178
179
180
181
|
const debugMeta = {
elementId: String(el.id || ''),
type,
}
|
b04bc3a5
杨鑫
打印日志 重新打印
|
182
183
|
if (type === 'IMAGE' || type === 'LOGO') {
|
58d2e61c
杨鑫
最新代码
|
184
185
|
const raw = cfgStr(cfg, ['src', 'url', 'data', 'Src', 'Url', 'Data'])
const local = await resolveToLocalPathIfNeeded(raw, debugMeta)
|
b04bc3a5
杨鑫
打印日志 重新打印
|
186
187
188
|
if (!local) return el
return {
...el,
|
58d2e61c
杨鑫
最新代码
|
189
|
config: { ...cfg, src: local, url: local, data: local, Src: local, Url: local, Data: local },
|
b04bc3a5
杨鑫
打印日志 重新打印
|
190
191
192
193
194
|
}
}
if (type === 'QRCODE') {
const raw = cfgStr(cfg, ['data', 'Data'])
|
58d2e61c
杨鑫
最新代码
|
195
196
197
198
199
|
if (!raw || !storedValueLooksLikeImagePath(raw)) {
logHydrateDebug('skip', { ...debugMeta, raw, reason: 'qrcode-data-not-image-path' })
return el
}
const local = await resolveToLocalPathIfNeeded(raw, debugMeta)
|
b04bc3a5
杨鑫
打印日志 重新打印
|
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
|
if (!local) return el
return {
...el,
config: { ...cfg, data: local, Data: local, src: local, url: local },
}
}
return el
}
/**
* 返回新模板对象;无元素或无需下载时可能与原引用相同(未改元素时仍返回浅拷贝以统一调用方)。
*/
export async function hydrateSystemTemplateImagesForPrint (
tmpl: SystemLabelTemplate
): Promise<SystemLabelTemplate> {
const elements = tmpl.elements || []
if (elements.length === 0) return tmpl
const next = await Promise.all(elements.map((el) => hydrateElement(el)))
return { ...tmpl, elements: next }
}
|