WebApp【公共组件库】@前端(For Git Submodule)
Tevin
2022-07-23 c1a17bdec32d3e90a119104c775886656bb08503
Merge branch 'temp'
1 files added
5 files modified
609 ■■■■ changed files
bases/Fetcher.js 30 ●●●● patch | view | raw | blame | history
bases/HostBoot.js 2 ●●● patch | view | raw | blame | history
common/Bridge.js 240 ●●●●● patch | view | raw | blame | history
common/FileTransform.js 148 ●●●●● patch | view | raw | blame | history
common/Tools.js 2 ●●● patch | view | raw | blame | history
forms/imagePicker/CImagePicker.vue 187 ●●●●● patch | view | raw | blame | history
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
     */
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],
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);
            });
        }
    }
}
// 全局服务实例
common/FileTransform.js
New file
@@ -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();
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 {
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>