From c1a17bdec32d3e90a119104c775886656bb08503 Mon Sep 17 00:00:00 2001 From: Tevin <tingquanren@163.com> Date: Sat, 23 Jul 2022 18:47:18 +0800 Subject: [PATCH] Merge branch 'temp' --- common/Tools.js | 2 common/FileTransform.js | 148 +++++++++++++ bases/Fetcher.js | 30 ++ forms/imagePicker/CImagePicker.vue | 187 ++++++++++------ common/Bridge.js | 240 +++++++++++++++++++++ bases/HostBoot.js | 2 6 files changed, 530 insertions(+), 79 deletions(-) diff --git a/bases/Fetcher.js b/bases/Fetcher.js index b2ffbd9..5a3c494 100644 --- a/bases/Fetcher.js +++ b/bases/Fetcher.js @@ -147,6 +147,13 @@ if (process.env.TARO_ENV === 'weapp') { this._saveCookies(response.cookies); } + // 捕获响应 + options && options.onCapture && options.onCapture({ + url: this._createUrlPrefix(options) + url, + request: data, + response: { ...response.data }, + httpCode: response.statusCode, + }); /** * @type {{state: {code, http, msg}, data: Object}} * @example response.state.code @@ -161,9 +168,16 @@ resolve(this._transformResponseData(responseData, options)); }, fail: error => { - if (typeof options.silence === 'undefined' || !options.silence) { - this._resolveCaughtNetErr(error); - } + // 处理响应 + this._resolveCaughtNetErr(error, options, msg => { + // 捕获响应 + options && options.onCapture && options.onCapture({ + url: this._createUrlPrefix(options) + url, + request: data, + httpCode: error && error.status, + httpMsg: msg + (error.message ? (' / ' + error.message) : ''), + }); + }); reject(null); }, }); @@ -309,9 +323,11 @@ /** * 解析捕获的网络错误 * @param err + * @param options + * @param callback * @private */ - _resolveCaughtNetErr(err) { + _resolveCaughtNetErr(err, options, callback) { let msg = ''; if (err && err.status) { switch (err.status) { @@ -349,14 +365,18 @@ } else { msg += '解析通讯数据异常!'; } + callback(msg); setTimeout(() => { - this._message('fail', msg); + if (typeof options.silence === 'undefined' || !options.silence) { + this._message('fail', msg); + } }, 20); } /** * 转换响应体 * @param response + * @param options * @returns {Object|{}|null} * @private */ diff --git a/bases/HostBoot.js b/bases/HostBoot.js index 6907708..12f98bc 100644 --- a/bases/HostBoot.js +++ b/bases/HostBoot.js @@ -52,7 +52,7 @@ }; } // 如果有匹配服务器,使用指定的服务器地址 - if (typeof this._data.hostList[server] !== 'undefined') { + else if (typeof this._data.hostList[server] !== 'undefined') { this._data.activeHost[typeName] = { name: server, host: this._data.hostList[server], diff --git a/common/Bridge.js b/common/Bridge.js index 1f644ea..f46cf14 100644 --- a/common/Bridge.js +++ b/common/Bridge.js @@ -24,12 +24,15 @@ import { Fetcher } from '@components/bases/Fetcher'; import { Tools } from '@components/common/Tools'; +import { $fileTrans } from '@components/common/FileTransform'; export class Bridge { constructor() { this._data = { count: 100, + fileSaved: {}, // 已保存图片名称列表 { 'blob:***' : 'bridge:***' } + fileLoaded: {}, // 已读取图片名称列表 { 'bridge:***' : 'blob:***' } }; this._receives = {}; this._earlyInvok = []; @@ -218,6 +221,243 @@ return Fetcher.prototype.transKeyName(type, json); } + /* ----- 文件系统 ----- */ + + /** + * 保存文件到 java 端,并返回文件名 + * @param objUrl + * @param callback + * @param onError + */ + fileSave(objUrl = '', callback, onError) { + // 非 ObjectURL 跳过 + if (objUrl.indexOf('blob:') < 0) { + callback(objUrl); + return; + } + if (this._data.fileSaved[objUrl]) { + callback(this._data.fileSaved[objUrl]); + return; + } + // 分段存储 + const saveFileChunk = (baseData, index) => { + const writeData = { + fileName: baseData.fileName, + currentIdx: index, + totalIdx: baseData.total, + data: baseData.baseArr[index], + }; + this.invoking('img_write', writeData, res => { + if (res.result === false) { + Tools.toast('离线图片存储:' + res.msg); + onError({ + method: 'img_write', + request: { + fileName: writeData.fileName, + currentIdx: writeData.currentIdx, + totalIdx: writeData.total, + data: (writeData.data || '').substr(0, 10) + + '...(共' + (writeData.data || '').length + '个base64字符)', + }, + response: res, + }); + return; + } + // 按分段递归保存 + if (index < baseData.total - 1) { + setTimeout(() => { + saveFileChunk(baseData, index + 1); + }, 10); + } + // 已完成 + else { + callback && callback('bridge:' + baseData.fileName); + } + }); + }; + $fileTrans.transObjUrlToBaseData(objUrl, baseData => { + this._data.fileSaved[objUrl] = 'bridge:' + baseData.fileName; + saveFileChunk(baseData, 0); + }); + } + + /** + * 从 java 读取文件 base64 + * @param bridgeName + * @param callback + * @param onError + */ + fileLoad(bridgeName = '', callback, onError) { + // 非存储地址,跳过 + if (bridgeName.indexOf('bridge:') < 0) { + callback(bridgeName); + return; + } + if (this._data.fileLoaded[bridgeName]) { + callback(this._data.fileLoaded[bridgeName]); + return; + } + const fileName = bridgeName.split(':')[1]; + const chunkSize = $fileTrans.getChunkSize(); + const baseArr = []; + let totalSize = 0; + let totalCount = 0; + const loadFileChunk = (index) => { + const loadData = { + fileName, + offset: chunkSize * index, + length: chunkSize, + }; + this.invoking('img_read', loadData, res => { + if (res.result === false) { + Tools.toast('离线图片读取:' + res.msg); + onError({ + method: 'img_read', + request: { + ...loadData, + totalCount, + }, + response: { + result: res.result, + msg: res.msg, + 'total_size': res.totalSize, + data: (res.data || '').substr(0, 10) + + '...(共' + (res.data || '').length + '个base64字符)', + }, + }); + return; + } + if (totalSize === 0) { + totalSize = res.totalSize; + totalCount = Math.ceil(res.totalSize / chunkSize); + } + baseArr.push(res.data); + // 按分段递归读取 + if (totalCount > 1 && totalCount - 1 > index) { + loadFileChunk(index + 1); + } + // 读取完成 + else { + const baseData = { + baseArr, + fileName, + }; + try { + $fileTrans.transBaseDataToObjUrl(baseData, objUrl => { + this._data.fileLoaded[bridgeName] = objUrl; + callback && callback(objUrl); + }); + } catch (e) { + onError({ + method: 'img_read@merge_after_base64_loaded', + request: { + ...loadData, + totalCount, + }, + response: { + result: res.result, + msg: res.msg, + 'total_size': res.totalSize, + data: (res.data || '').substr(0, 10) + + '...(共' + (res.data || '').length + '个base64字符)', + }, + base64Arr: baseData.baseArr.map(baseItem => (baseItem || []).substr(0, 10) + + '...(共' + (res.data || '').length + '个base64字符)'), + message: 'Base64合并解析异常!', + }); + } + } + }); + }; + loadFileChunk(0); + } + + /** + * 从 java 端移除文件 + * @param bridgeName + */ + fileRemove(bridgeName = '') { + if (bridgeName.indexOf('bridge:') < 0) { + return; + } + const fileName = bridgeName.split(':')[1]; + this.invoking('img_del', { fileName }); + // 移除 + Object.keys(this._data.fileSaved).forEach(objUrl => { + if (bridgeName === this._data.fileSaved[objUrl]) { + delete this._data.fileSaved[objUrl]; + } + }); + } + + /** + * 文件存储批量操作服务 + * @param type + * @param names + * @param callback + */ + fileStore(type, names = [], callback) { + if (!names || names.length === 0) { + callback && callback([]); + return; + } + if (typeof names === 'string') { + names = names.split(','); + } + // 保存 + if (type === 'save') { + const list = []; + const save = index => { + this.fileSave(names[index], bridgeName => { + list.push(bridgeName); + // 递归下一个 + if (index < names.length - 1) { + setTimeout(() => { + save(index + 1); + }, 10); + } + // 完成 + else { + callback && callback(list); + } + }, err => { + callback && callback(null, err); + }); + }; + save(0); + } + // 读取 + else if (type === 'load') { + const list = []; + const load = index => { + this.fileLoad(names[index], objUrl => { + list.push(objUrl); + // 递归下一个 + if (index < names.length - 1) { + setTimeout(() => { + load(index + 1); + }, 10); + } + // 完成 + else { + callback && callback(list); + } + }, err => { + callback && callback(null, err); + }); + }; + load(0); + } + // 移除 + else if (type === 'remove') { + names.forEach((name, index) => { + setTimeout(() => { + this.fileRemove(name); + }, 10 * index); + }); + } + } + } // 全局服务实例 diff --git a/common/FileTransform.js b/common/FileTransform.js new file mode 100644 index 0000000..cc35f73 --- /dev/null +++ b/common/FileTransform.js @@ -0,0 +1,148 @@ +/** + * 文件转换 + * @author Tevin + */ + +import moment from 'moment'; + +export class FileTransform { + + constructor() { + this._data = { + chunkSize: 20 * 1024, + }; + } + + getChunkSize() { + return this._data.chunkSize; + } + + setChunkSize() { + return this._data.chunkSize; + } + + /** + * 转换 ObjectURL 为 base64 数据体 + * @param objUrl + * @param callback + */ + transObjUrlToBaseData(objUrl, callback) { + this.convertObjectUrlToBlob(objUrl, blob => { + this.convertBlobToBase64(blob, base64 => { + const mime = base64.split(',')[0].match(/:(.*?);/)[1]; + const fileName = this._makeFileName(mime); + const baseArr = this.splitBase64ToArray(base64); + callback && callback({ + fileName, + baseArr, + total: baseArr.length, + }); + }); + }); + } + + /** + * 转换 base64 数据体为 ObjectURL + * @param baseData + * @param callback + */ + transBaseDataToObjUrl(baseData, callback) { + const base64 = baseData.baseArr.join(''); + this.convertBase64ToFile(base64, baseData.fileName, file => { + this.convertFileToObjectUrl(file, objUrl => { + callback && callback(objUrl); + }); + }); + } + + convertBlobToBase64(blob, callback) { + const reader = new FileReader(); + reader.onload = evt => { + callback && callback(evt.target.result); + }; + reader.readAsDataURL(blob); + } + + convertObjectUrlToBlob(objUrl, callback) { + const xhr = new XMLHttpRequest(); + xhr.open('GET', objUrl, true); + xhr.responseType = 'blob'; + xhr.onload = function () { + if (this.status === 200) { + callback && callback(this.response); + } else { + /* eslint-disable prefer-promise-reject-errors */ + callback && callback(null, { status: this.status }); + } + }; + xhr.send(); + } + + convertBase64ToFile(base64, fileName, callback) { + const arr = base64.split(','); + const mime = arr[0].match(/:(.*?);/)[1]; + const bstr = atob(arr[1]); + let n = bstr.length; + const u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + fileName = fileName || this._makeFileName(mime); + const file = new File([u8arr], fileName, { type: mime }); + callback && callback(file); + } + + convertFileToObjectUrl(file, callback) { + let URLMaker = URL; + if (window) { + URLMaker = window.URL || window.webkitURL; + } + const url = URLMaker.createObjectURL(file); + callback && callback(url); + } + + // 生成文件名 + _makeFileName(mime) { + const curMoment = moment(); + const name = 'IMG' + curMoment.format('-YYYYMMDD-HHmmss-') + + curMoment.format('x').substr(-3) + '-' + parseInt(Math.random() * 10000); + const mimeTypes = { + 'image/jpeg': 'jpg', + 'image/gif': 'gif', + 'image/png': 'png', + 'image/svg+xml': 'svg', + 'image/webp': 'webp', + 'image/tiff': 'tif', + 'text/plain': 'txt', + 'text/css': 'css', + 'text/html': 'html', + 'text/xml': 'xml', + 'text/javascript': 'js', + 'application/x-javascript': 'js', + 'audio/mpeg': 'mp3', + 'video/mp4': 'mp4', + 'video/webm': 'webm', + 'application/zip': 'zip', + 'application/rar': 'rar', + 'application/pdf': 'pdf', + 'application/rtf': 'rtf', + }; + return name + '.' + mimeTypes[mime]; + } + + splitBase64ToArray(base64) { + const count = Math.ceil(base64.length / this._data.chunkSize); + if (count === 1) { + return [base64]; + } + const array = []; + for (let i = 0; i < count; i++) { + const chunk = base64.substr(this._data.chunkSize * i, this._data.chunkSize); + array.push(chunk); + } + return array; + } + +} + +export const $fileTrans = new FileTransform(); \ No newline at end of file diff --git a/common/Tools.js b/common/Tools.js index 2f36e53..3fbb8a8 100644 --- a/common/Tools.js +++ b/common/Tools.js @@ -103,7 +103,7 @@ img.onerror = null; callback && callback(false); }; - img.src = 'http://tt.zhiheiot.com/static/online.png?t=' + Date.now(); + img.src = 'http://gz.zhiheiot.com/disp/download/buildArchive/online.png?t=' + Date.now(); } // 网络未开启 else { diff --git a/forms/imagePicker/CImagePicker.vue b/forms/imagePicker/CImagePicker.vue index 0eb0e69..6ebd882 100644 --- a/forms/imagePicker/CImagePicker.vue +++ b/forms/imagePicker/CImagePicker.vue @@ -167,6 +167,9 @@ this.showImg = false; }, handleFail(msg) { + if (typeof msg === 'object') { + msg = msg.message; + } Taro.showToast({ title: msg, icon: 'none', @@ -175,85 +178,24 @@ }); }, $uploadImage(callback) { - let url = $fetchCommon.getUploadImgURL(); - if (url.indexOf('http') < 0) { - url = $hostBoot.getHost() + url; - } - const uploadTeam = []; - const imgs = []; + const files = []; this.files.forEach(file => { if (file.type === 'btn') { return; } - // 临时文件才上传 - if ( - file.url.indexOf('blob') >= 0 || - file.url.indexOf('wxfile') >= 0 || - file.url.indexOf('http://tmp/') >= 0 - ) { - let header = {}; - if (process.env.TARO_ENV === 'weapp') { - const localCookies = JSON.parse( - Taro.getStorageSync('cookies') || '{}' - ); - const cookiesArr = []; - Object.keys(localCookies).forEach(key => { - cookiesArr.push(key + '=' + localCookies[key]); - }); - header['Cookie'] = cookiesArr.join('; '); - } - uploadTeam.push( - new Promise((resolve, reject) => { - Taro.uploadFile({ - url, - header, - filePath: file.url, - fileName: this.fileNames[file.url], - name: 'file', - formData: {}, - success(res) { - const res2 = - typeof res.data === 'string' - ? JSON.parse(res.data) - : res.data; - if (res2.state.code === 2000) { - resolve( - $fetchCommon.transImgPath( - 'fix', - res2.data.src || - res2.data.file || - res2.data.url - ) - ); - } else { - reject({ message: res2.state.msg }); - } - }, - cancel() { - reject({ message: '上传图片已取消!' }); - }, - fail() { - reject({ message: '上传图片失败!' }); - }, - }); - }) - ); - } - // 其他类型视为 url,忽略 - else { - uploadTeam.push(Promise.resolve(file.url)); - } + file.fileName = this.fileNames[file.url]; + files.push(file); }); - Promise.all(uploadTeam) - .then(res => { + uploadImage(files, (state, res) => { + if (state === 'success') { this.itemRes.onChange(res); setTimeout(() => { - callback('success'); - }, 0); - }) - .catch(err => { - callback('error', err); - }); + callback(state); + }, 10); + } else if (state === 'error') { + callback(state, res); + } + }); }, }, mounted() { @@ -268,4 +210,105 @@ } }, }; + +// 图片上传节流 +const _readyUpload = {}; + +export const uploadImage = (files, callback) => { + if (!files || files.length === 0) { + callback('success', []); + return; + } + let url = $fetchCommon.getUploadImgURL(); + if (url.indexOf('http') < 0) { + url = $hostBoot.getHost() + url; + } + const uploadTeam = []; + const imgs = []; + files.forEach(file => { + // 临时文件才上传 + if ( + file.url.indexOf('blob') >= 0 || + file.url.indexOf('wxfile') >= 0 || + file.url.indexOf('http://tmp/') >= 0 + ) { + const header = {}; + if (process.env.TARO_ENV === 'weapp') { + const localCookies = JSON.parse(Taro.getStorageSync('cookies') || '{}'); + const cookiesArr = []; + Object.keys(localCookies).forEach(key => { + cookiesArr.push(key + '=' + localCookies[key]); + }); + header['Cookie'] = cookiesArr.join('; '); + } + const requestFile = { + url, + filePath: file.url, + fileName: file.fileName || '', + }; + uploadTeam.push( + new Promise((resolve, reject) => { + // 如果本次已上传过,直接返回地址 + if (_readyUpload[file.url] && _readyUpload[file.url].length > 5) { + resolve(_readyUpload[file.url]); + return; + } + // 上传 + Taro.uploadFile({ + ...requestFile, + header, + name: 'file', + formData: {}, + success(res) { + const res2 = + typeof res.data === 'string' + ? JSON.parse(res.data) + : res.data; + // 上传成功 + if (res2.state.code === 2000) { + const imgUrl = $fetchCommon.transImgPath( + 'fix', + res2.data.src || res2.data.file || res2.data.url + ); + _readyUpload[file.url] = imgUrl; + resolve(imgUrl); + } + // 上传失败 + else { + reject({ + ...requestFile, + response: res2, + message: res2.state.msg, + }); + } + }, + cancel(err) { + reject({ + ...requestFile, + message: '上传图片已取消!', + }); + }, + fail() { + reject({ + ...requestFile, + message: '上传图片失败!', + }); + }, + }); + }) + ); + } + // 其他类型视为 url,忽略 + else { + uploadTeam.push(Promise.resolve(file.url)); + } + }); + Promise.all(uploadTeam) + .then(res => { + callback('success', res); + }) + .catch(err => { + callback('error', err); + }); +}; </script> \ No newline at end of file -- Gitblit v1.9.1