/**
* 剪贴板图片提取的公用件,供「新建工单」与 profile 值编辑器共享。
*
* 刻意拆成「同步探测」+「异步读取」两步:粘贴的默认行为(把二进制当乱码文本插入)
* 必须在事件处理里同步 `preventDefault` 才拦得住,而读文件是异步的。所以调用方先用
* `getPastedImageFiles` 同步拿到图片文件、立刻 preventDefault,再 await `readClipboardImages`。
*/
export interface PastedImage {
/** e.g. "image/png" */
mediaType: string
/** 裸 base64(不含 `data:...;base64,` 前缀) */
base64: string
/** `data:...;base64,...` 形式,供 `
` 直接预览 */
previewDataUrl: string
}
/** 同步从粘贴事件里挑出所有 image/* 文件;无图返回空数组(调用方据此决定是否 preventDefault)。 */
export function getPastedImageFiles(e: React.ClipboardEvent): File[] {
const items = e.clipboardData?.items
if (!items)
return []
const files: File[] = []
for (const item of items) {
if (item.kind === 'file' && item.type.startsWith('image/')) {
const f = item.getAsFile()
if (f)
files.push(f)
}
}
return files
}
/** 把单个图片文件读成 base64 + data URL;读失败回 null。 */
export function readClipboardImage(file: File): Promise {
return new Promise((resolve) => {
const reader = new FileReader()
reader.onload = () => {
const result = reader.result
if (typeof result !== 'string') {
resolve(null)
return
}
// result 形如 "data:image/png;base64,iVBORw0K..."
const commaIdx = result.indexOf(',')
const header = commaIdx > 0 ? result.slice(0, commaIdx) : ''
const base64 = commaIdx > 0 ? result.slice(commaIdx + 1) : ''
const mediaMatch = header.match(/^data:([^;]+);base64$/)
const mediaType = mediaMatch ? mediaMatch[1] : file.type || 'image/png'
resolve({ mediaType, base64, previewDataUrl: result })
}
reader.onerror = () => resolve(null)
reader.readAsDataURL(file)
})
}
/** 批量读取,丢弃读失败的项。 */
export function readClipboardImages(files: File[]): Promise {
return Promise.all(files.map(readClipboardImage)).then(
arr => arr.filter((p): p is PastedImage => p !== null),
)
}