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 }