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