231 lines
7.1 KiB
JavaScript
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
|
|
}
|