Files
2026-09-23 16:52:48 +08:00

231 lines
7.1 KiB
JavaScript

import { OA_BASE, IM_HTTP } from './config.js'
import { toast } from './format.js'
export function fileId(file) {
if (file == null) return ''
if (typeof file === 'string' || typeof file === 'number') {
const s = String(file)
return !s || s === '0' || s === 'null' ? '' : s
}
const id = file.id ?? file.fileId
if (id == null || id === '' || id === 0 || id === '0' || id === 'null') return ''
return String(id)
}
export function fileName(file, fallback = '附件') {
if (!file || typeof file !== 'object') return fallback
return String(file.name || file.fileName || file.originalName || fallback)
}
export function isImageFile(file) {
const name = fileName(file, '')
const type = String((file && file.contentType) || '').toLowerCase()
return type.startsWith('image/') || /\.(png|jpe?g|gif|webp|bmp|heic)$/i.test(name)
}
function unwrapUpload(body) {
if (!body || typeof body !== 'object') return body
if (body.code != null && body.code !== 0 && body.code !== 200) {
throw new Error(body.message || '上传失败')
}
return Object.prototype.hasOwnProperty.call(body, 'data') ? body.data : body
}
export function uploadLocal(filePath, token, { kind = 'attach', name } = {}) {
const urls = [`${OA_BASE}/api/files`, `${IM_HTTP}/api/im/oa/files`]
const send = (url) => new Promise((resolve, reject) => {
uni.uploadFile({
url,
filePath,
name: 'file',
formData: { kind, ...(kind === 'im' || kind === 'IM' ? { bizType: 'im' } : {}) },
header: token ? { Authorization: 'Bearer ' + token } : {},
timeout: 120000,
success(res) {
let body = res.data
try { body = typeof body === 'string' ? JSON.parse(body) : body } catch (_) {}
if (res.statusCode >= 400) {
reject(new Error((body && body.message) || `上传失败 ${res.statusCode}`))
return
}
try {
const data = unwrapUpload(body)
const id = fileId(data)
if (!id) throw new Error((body && body.message) || '上传未返回文件')
resolve({
id,
name: (data && data.name) || name || '附件',
size: (data && data.size) || 0,
contentType: (data && data.contentType) || ''
})
} catch (e) {
reject(e)
}
},
fail(e) {
reject(new Error((e && e.errMsg) || '上传失败'))
}
})
})
return send(urls[0]).catch(() => send(urls[1]))
}
export function fileTicket(id, token) {
return new Promise((resolve, reject) => {
const urls = [`${OA_BASE}/api/files/${id}/ticket`, `${IM_HTTP}/api/im/oa/files/${id}/ticket`]
const post = (url, next) => {
uni.request({
url,
method: 'POST',
header: {
Accept: 'application/json',
...(token ? { Authorization: 'Bearer ' + token } : {})
},
success(res) {
const body = res.data
if (res.statusCode >= 400) {
if (next) return next()
reject(new Error((body && body.message) || '无法预览'))
return
}
const data = body && body.data !== undefined ? body.data : body
if (data && data.url) {
resolve(data)
return
}
if (next) return next()
reject(new Error('无法预览'))
},
fail() {
if (next) next()
else reject(new Error('无法预览'))
}
})
}
post(urls[0], () => post(urls[1], null))
})
}
export async function openFile(file, token) {
const id = fileId(file)
if (!id) {
toast('文件未迁移或无法预览')
return
}
try {
uni.showLoading({ title: '打开附件…', mask: true })
const ticket = await fileTicket(id, token)
const url = ticket.url
uni.hideLoading()
if (isImageFile(file)) {
uni.previewImage({ urls: [url], current: url })
return
}
uni.downloadFile({
url,
header: token ? { Authorization: 'Bearer ' + token } : {},
success(res) {
if (res.statusCode === 200 && res.tempFilePath) {
uni.openDocument({
filePath: res.tempFilePath,
showMenu: true,
fail() {
plus.runtime.openURL(url)
}
})
} else {
toast('下载失败')
}
},
fail() {
try { plus.runtime.openURL(url) } catch (_) { toast('无法打开附件') }
}
})
} catch (e) {
uni.hideLoading()
toast((e && e.message) || '无法预览附件')
}
}
function uploadPaths(paths, token, kind) {
return Promise.all(paths.map((p) => uploadLocal(p, token, { kind })))
}
export function pickAndUpload(token, kind = 'attach') {
return new Promise((resolve, reject) => {
const fromImages = () => {
uni.chooseImage({
count: 6,
sizeType: ['compressed', 'original'],
sourceType: ['album', 'camera'],
success(res) {
const paths = res.tempFilePaths || []
if (!paths.length) return resolve([])
uni.showLoading({ title: '上传中…', mask: true })
uploadPaths(paths, token, kind)
.then((rows) => { uni.hideLoading(); resolve(rows) })
.catch((e) => { uni.hideLoading(); reject(e) })
},
fail() { resolve([]) }
})
}
const fromFiles = () => {
const chooser = typeof uni.chooseFile === 'function' ? uni.chooseFile : null
if (!chooser) return fromImages()
chooser({
count: 6,
type: 'all',
extension: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.pdf', '.doc', '.docx', '.xls', '.xlsx'],
success(res) {
const files = res.tempFiles || []
const paths = files.map((f) => f.path || f.tempFilePath).filter(Boolean)
if (!paths.length) return resolve([])
uni.showLoading({ title: '上传中…', mask: true })
uploadPaths(paths, token, kind)
.then((rows) => { uni.hideLoading(); resolve(rows) })
.catch((e) => { uni.hideLoading(); reject(e) })
},
fail() { resolve([]) }
})
}
uni.showActionSheet({
itemList: ['拍照或相册', '选择文件'],
success(res) {
if (res.tapIndex === 0) fromImages()
else fromFiles()
},
fail() { resolve([]) }
})
})
}
export function collectFiles(row) {
const out = []
const seen = new Set()
const push = (f) => {
if (!f) return
const id = fileId(f)
const key = id || fileName(f)
if (!key || seen.has(key)) return
seen.add(key)
out.push(typeof f === 'object' ? f : { id })
}
const walk = (v) => {
if (!v) return
if (Array.isArray(v)) v.forEach(walk)
else if (typeof v === 'object') {
if (fileId(v) || v.name) push(v)
}
}
if (!row || typeof row !== 'object') return out
walk(row.files)
walk(row.attachments)
walk(row.invoiceFiles)
walk(row.receiptFiles)
const form = row.form && typeof row.form === 'object' ? row.form : row
walk(form.files)
walk(form.attachments)
;(form.invoices || row.invoices || []).forEach((inv) => walk(inv && inv.files))
return out
}