<template>
  <view class="lsj-upload">
    <!-- #ifndef MP -->
    <view
      id="render"
      ref="render"
      :config="config"
      v-html="inputHtml"
      :uploadParams="uploadParams"
      :renderInput="renderInput"
      :renderFile="renderFile"
      :change:config="render.initConfig"
      :change:renderFile="render.renderFileHandle"
      :change:uploadParams="render.uploadHandle"
      :change:renderInput="render.chooseFileHandle">
    </view>
    <!-- #endif -->
  </view>
</template>
<script>
  export default {
    name: 'lsj-upload',
    props: {
      // 调试打印
      debug: {
        type: Boolean,
        default: false,
      },
      // 是否去重文件（同名文件覆盖）
      distinct: {
        type: Boolean,
        default: false,
      },
    },
    data() {
      return {
        isIniting: false,
        isUpdating: false,
        isProcessing: false,
        initQueue: [],
        uploadQueue: [],
        taskQueue: [],
        chooseFileSuccess: null,
        chooseFileFail: null,
        uploadEventMap: new Map(),
        baseEventMap: new Map(),
        arrayBufferEventMap: new Map(),
        tempFilePathEventMap: new Map(),
        inputHtml: '',
        inputId: '',
        option: {},
        uploadParams: '{}',
        handleName: '',
        handleType: '',
        params: {},
      };
    },
    computed: {
      config() {
        return {
          debug: this.debug,
          distinct: this.distinct,
        };
      },
      renderInput() {
        return {
          id: this.inputId,
          html: this.inputHtml,
          option: this.option,
        };
      },
      renderFile() {
        return {
          name: this.handleName,
          type: this.handleType,
          params: this.params,
        };
      },
    },
    methods: {
      chooseFile(e) {
        const { success, fail, ...option } = e;
        if (success) {
          this.chooseFileSuccess = success;
        } else {
          this.chooseFileSuccess = null;
        }
        if (fail) {
          this.chooseFileFail = fail;
        } else {
          this.chooseFileFail = null;
        }
        this.option = option;

        // #ifndef MP
        this.inputId = +new Date();
        this.inputHtml = `<input type="file" id="lsj-file-${this.inputId}" name="lsj-file" style="opacity:0" />`;
        // #endif
        // #ifdef MP
        const { count = 9, type = 'all' } = option;
        wx.chooseMessageFile({
          count,
          type,
          success: ({ tempFiles }) => {
            this.chooseFileSuccess(
              tempFiles.map(_ => {
                _['status'] = 'waiting';
                _['progress'] = 0;
                return _;
              })
            );
          },
          fail: () => {
            this.toToast(`打开失败`);
          },
        });
        // #endif
      },
      deleted(e) {
        if (!e?.name) {
          throw Error('abort ERROR: file is undefined');
        }
        this.resetHandle();
        this.handleName = e.name;
        this.handleType = 'deleted';
      },
      clear() {
        this.resetHandle();
        this.handleName = '';
        this.handleType = 'clear';
      },
      abort(e) {
        if (!e?.name) {
          throw Error('abort ERROR: file is undefined');
        }
        // #ifndef MP
        this.resetHandle();
        this.handleName = e.name;
        this.handleType = 'abort';
        // #endif
        // #ifdef MP
        try {
          this.uploadEventMap.set(params.file.name, {
            xmlRequest,
          });
          const cuXmlRequest = this.uploadEventMap.get(params.file.name)?.xmlRequest;
          if (cuXmlRequest) {
            cuXmlRequest?.abort();
          }
        } catch (error) {
          console.error(error);
        }

        // #endif
      },
      wxReadFile(params, encoding) {
        uni.getFileSystemManager().readFile({
          filePath: params?.file.path,
          encoding,
          success: res => {
            if (params?.success && typeof params?.success === 'function') {
              return params.success({
                name: params.file.name,
                result: res.data,
              });
            }
          },
          fail: params?.fail,
        });
      },
      resetHandle() {
        this.handleName = '';
        this.handleType = '';
      },
      processInitQueue() {
        if (this.isIniting || this.initQueue.length === 0) {
          return;
        }
        this.isIniting = true;
        const { handleName, handleType } = this.initQueue.shift();
        setTimeout(() => {
          this.resetHandle();
          this.handleName = handleName;
          this.handleType = handleType;
          this.$nextTick(() => {
            this.isIniting = false;
            this.processInitQueue();
          });
        }, 300);
      },
      initHandle({ params, ob, handleType, types = ['success', 'fail', 'onprogress'] }) {
        try {
          if (!params?.file || !params?.file?.name) {
            throw Error('ERROR: file is undefined');
          }
          const callFunctions = this.getCallBackFn(params, types);
          ob.set(params.file.name, {
            fileChunks: [], // 存储所有分片
            totalChunks: 0, // 总分片数
            receivedIndexes: new Set(), // 当前已接收的分片索引
            ...callFunctions, // 回调函数
          });

          this.initQueue.push({
            handleName: params.file?.name,
            handleType,
          });
          if (!this.isIniting) {
            this.processInitQueue();
          }
        } catch (error) {
          console.error(error.message);
        }
      },
      getCallBackFn(params, types) {
        let o = {};
        if (types.includes('success') && params?.success && typeof params?.success === 'function') {
          o['success'] = params?.success;
        }
        if (types.includes('fail') && params?.fail && typeof params?.fail === 'function') {
          o['fail'] = params?.fail;
        }
        if (types.includes('onprogress') && params?.onprogress && typeof params?.onprogress === 'function') {
          o['onprogress'] = params?.onprogress;
        }
        return o;
      },
      addChunks(result, item) {
        const { chunk, index, totalChunks } = result;
        if (item.totalChunks === 0) {
          item.totalChunks = totalChunks;
        }
        if (!item.receivedIndexes.has(index)) {
          item.fileChunks[index] = chunk;
          item.receivedIndexes.add(index);
        }
        return item;
      },
      executeEnd(e, maps) {
        this.executeCallBackFn(e, maps, ['success', 'fail']);
        this.baseEventMap.delete(e.name);
        this.resetHandle();
      },
      executeCallBackFn(e, maps, types) {
        const o = maps.get(e.name);
        if (!o) {
          return;
        }
        const [type] = types.filter(_ => e.status === _);
        if (type && typeof o[type] === 'function') {
          o[type](e);
        }
      },
      getArrayBuffer(params) {
        if (!params?.file || !params?.file?.name) {
          throw Error('ERROR: file is undefined');
        }
        // #ifndef MP
        this.initHandle({
          params,
          handleType: 'getArrayBuffer',
          ob: this.arrayBufferEventMap,
        });
        // #endif
        // #ifdef MP
        this.wxReadFile(params);
        // #endif
      },
      getArrayBufferCallBack(e) {
        if (e.status === 'fail') {
          return this.executeEnd(e, this.arrayBufferEventMap);
        }
        if (e.status === 'success') {
          const item = this.addChunks(e.result, this.arrayBufferEventMap.get(e.name));
          if (item.receivedIndexes.size === item.totalChunks) {
            this.debug && console.log('分片结束');
            const chunks = item.fileChunks.filter(chunk => chunk !== undefined);
            const base64 = chunks.join('');
            e.result = this.base64ToArrayBuffer(base64);
            e.chunks = chunks;
            return this.executeEnd(e, this.arrayBufferEventMap);
          }
        }
      },
      getBase(params) {
        if (!params?.file || !params?.file?.name) {
          throw Error('ERROR: file is undefined');
        }
        // #ifndef MP
        this.initHandle({
          params,
          handleType: 'getBase',
          ob: this.baseEventMap,
        });
        // #endif
        // #ifdef MP
        this.wxReadFile(params, 'base64');
        // #endif
      },
      /**
       * 将 base64 转换为 File 对象
       * @param {String} base64Data - base64字符串，可以是带前缀的，如 data:image/png;base64,iVBORw...
       * @param {String} fileName - 自定义文件名，比如 'image.png'
       * @returns {File} 返回 File 对象
       */
      base64ToFile(base64Data, fileName, fileType) {
        // 如果没有前缀，你可以手动传入 mime 或根据业务设置默认值
        let mime = fileType || 'application/octet-stream'; // 默认二进制流，也可以根据情况设置如 'image/png'
        let byteString = atob(base64Data);
        let arrayBuffer = new ArrayBuffer(byteString.length);
        let uint8Array = new Uint8Array(arrayBuffer);
        for (let i = 0; i < byteString.length; i++) {
          uint8Array[i] = byteString.charCodeAt(i);
        }
        let blob = new Blob([arrayBuffer], { type: mime });
        let file = new File([blob], fileName, { type: mime });
        return file;
      },
      base64ToArrayBuffer(base64) {
        const binaryString = atob(base64);
        const bytes = new Uint8Array(binaryString.length);
        for (let i = 0; i < binaryString.length; i++) {
          bytes[i] = binaryString.charCodeAt(i);
        }
        return bytes.buffer;
      },
      base64ToPath(base64, fileName) {
        return new Promise((resolve, reject) => {
          // #ifdef APP
          const docDir = plus.io.convertLocalFileSystemURL('_doc/');
          plus.io.resolveLocalFileSystemURL(
            docDir,
            dirEntry => {
              dirEntry.getFile(
                fileName,
                {
                  create: true,
                },
                fileEntry => {
                  fileEntry.createWriter(
                    writer => {
                      writer.onwrite = e => {
                        this.debug & console.log('文件写入成功:', `_doc/${fileName}`);
                        resolve(`_doc/${fileName}`);
                      };

                      writer.onerror = e => {
                        this.debug & console.error('文件写入失败:', e.toString());
                        reject(e);
                      };
                      writer.writeAsBinary(base64);
                    },
                    error => {
                      console.error('创建文件写入器失败:', error.toString());
                      reject(error);
                    }
                  );
                },
                error => {
                  console.error('获取文件条目失败:', error.toString());
                  reject(error);
                }
              );
            },
            error => {
              console.error('解析目录失败:', error.toString());
              reject(error);
            }
          );
          // #endif

          // #ifndef APP
          reject(new Error('该功能仅支持 App 端！'));
          // #endif
        });
      },
      getBaseCallBack(e) {
        if (e.status === 'fail') {
          return this.executeEnd(e, this.baseEventMap);
        }
        if (e.status === 'success') {
          const item = this.addChunks(e.result, this.baseEventMap.get(e.name));
          if (item.receivedIndexes.size === item.totalChunks) {
            this.debug && console.log('分片结束');
            const chunks = item.fileChunks.filter(chunk => chunk !== undefined);
            e.result = chunks.join('');
            e.chunks = chunks;
            return this.executeEnd(e, this.baseEventMap);
          }
        }
      },
      processTaskQueue() {
        if (this.isIniting) {
          setTimeout(() => {
            this.processTaskQueue();
          }, 500);
          return;
        }
        if (this.isProcessing || this.taskQueue.length === 0) {
          return;
        }
        this.isProcessing = true;
        const { handleName, handleType, params } = this.taskQueue.shift();
        setTimeout(() => {
          this.resetHandle();
          this.handleName = handleName;
          this.handleType = handleType;
          this.params = params;
          this.$nextTick(() => {
            this.isProcessing = false;
            this.processTaskQueue();
          });
        }, 200);
      },
      asyncFileTempFilePath(name, tempFilePath) {
        this.taskQueue.push({
          handleType: 'asyncFileTempFilePath',
          handleName: name,
          params: {
            tempFilePath,
          },
        });
        if (!this.isProcessing) {
          this.processTaskQueue();
        }
      },
      async getTempFilePathCallBack(e) {
        if (e.status === 'fail') {
          return this.executeEnd(e, this.tempFilePathEventMap);
        }
        try {
          if (e.status === 'success') {
            if (e?.result?.tempFilePath) {
              e.result = e?.result?.tempFilePath;
              return this.executeEnd(e, this.tempFilePathEventMap);
            }
            const item = this.addChunks(e.result, this.tempFilePathEventMap.get(e.name));
            if (item.receivedIndexes.size === item.totalChunks) {
              this.debug && console.log('分片结束');
              const chunks = item.fileChunks.filter(chunk => chunk !== undefined);
              const base64 = chunks.join('');
              e.chunks = chunks;
              // const arrayBuffer = this.base64ToArrayBuffer(base64);
              // #ifdef H5
              e.result = this.base64ToFile(base64, e.name, e.fileType);
              // #endif
              // #ifdef APP
              e.result = await this.base64ToPath(base64, e.name);
              this.asyncFileTempFilePath(e.name, e.result);
              // #endif
              return this.executeEnd(e, this.tempFilePathEventMap);
            }
          }
        } catch (error) {
          console.log(error);
        }
      },
      getTempFilePath(params) {
        if (!params?.file || !params?.file?.name) {
          throw Error('ERROR: file is undefined');
        }
        // #ifndef MP
        this.initHandle({
          params,
          handleType: 'getTempFilePath',
          ob: this.tempFilePathEventMap,
        });
        // #endif
        // #ifdef MP
        try {
          if (params?.success && typeof params?.success === 'function') {
            return params.success({
              name: params.file.name,
              result: params.file.path,
            });
          }
        } catch (error) {
          if (params?.fail && typeof params?.fail === 'function') {
            return params.fail(error);
          }
        }

        // #endif
      },
      processQueue() {
        if (this.isUpdating || this.uploadQueue.length === 0) {
          return;
        }
        this.isUpdating = true;
        const form = this.uploadQueue.shift();
        form._uuid = `${+new Date()}-${Math.random()}`;
        setTimeout(() => {
          this.uploadParams = JSON.stringify(form);
          this.$nextTick(() => {
            this.isUpdating = false;
            this.processQueue();
          });
        }, 300);
      },
      upload(params) {
        // #ifndef MP
        try {
          if (!params?.url) {
            throw Error('upload ERROR: url is undefined');
          }
          if (!params?.file || !params?.file?.name) {
            throw Error('upload ERROR: file is undefined');
          }
          const { success, onprogress, fail, ...form } = params;

          const callFunctions = this.getCallBackFn(params, ['success', 'fail', 'onprogress']);
          this.uploadEventMap.set(params.file.name, callFunctions);
          // 将当前上传任务加入队列
          this.uploadQueue.push(form);
          // 开始处理队列
          this.processQueue();
        } catch (error) {
          console.error(error.message);
          if (params?.fail && typeof params?.fail === 'function') {
            return params.fail(error);
          }
        }
        // #endif
        // #ifdef MP
        return this.wxUpload(params);
        // #endif
      },
      wxUpload(params) {
        // #ifdef MP
        try {
          if (!params?.url) {
            throw Error('upload ERROR: url is undefined');
          }
          if (!params?.file || !params.file?.name || !params?.file?.path) {
            throw Error('upload ERROR: file is undefined');
          }

          const { success, onprogress, fail, url, file, ...form } = params;

          let xmlRequest = uni.uploadFile({
            url,
            filePath: file.path,
            success: e => {
              this.uploadEventMap.delete(params.file.name);
              return success(e);
            },
            fail: e => {
              this.uploadEventMap.delete(params.file.name);
              return fail(e);
            },
            ...form,
          });
          this.uploadEventMap.set(params.file.name, {
            xmlRequest,
          });
          xmlRequest.onProgressUpdate(({ progress = 0 }) => {
            if (progress <= 100) {
              onprogress({
                name: params.file.name,
                progress,
              });
            }
          });
        } catch (error) {
          console.error(error.message);
          if (params?.fail && typeof params?.fail === 'function') {
            return params.fail(error);
          }
        }

        // #endif
      },
      uploadCallBack(e) {
        try {
          this.executeCallBackFn(e, this.uploadEventMap, ['success', 'fail']);
          this.uploadEventMap.delete(e.name);
        } catch (error) {
          console.error('upload', error);
        }
      },
      uploadProgress(e) {
        try {
          this.debug && console.log('上传中', e);
          this.executeCallBackFn(e, this.uploadEventMap, ['onprogress']);
        } catch (error) {
          console.error('Progress', error);
        }
      },
      changeFile(files) {
        if (files?.length) {
          if (this.chooseFileSuccess && typeof this.chooseFileSuccess === 'function') {
            this.chooseFileSuccess(files);
          }
        } else if (this.chooseFileFail && typeof this.chooseFileFail === 'function') {
          this.chooseFileFail({
            status: 'fail',
            files: [],
          });
        }
        this.inputHtml = '';
      },
      openDocument({ file, ...params }) {
        if (!file) {
          throw '未传入文件';
        }
        this.getTempFilePath({
          file,
          success: e => {
            // #ifndef H5
            uni.openDocument({
              filePath: e.result,
              success: res => {
                if (params?.success && typeof params?.success === 'function') {
                  return params.success(res);
                }
              },
              fail: error => {
                console.error('打开文档失败', error);
                if (params?.fail && typeof params?.fail === 'function') {
                  return params.fail(error);
                }
              },
            });
            // #endif
            // #ifdef H5
            window.open(e.result, '_blank');
            // #endif
          },
          fail: error => {
            console.error('打开文档失败', error);
            if (params?.fail && typeof params?.fail === 'function') {
              return params.fail(error);
            }
          },
        });
      },
      toToast(msg) {
        if (msg) {
          uni.showToast({
            title: msg,
            icon: 'none',
            duration: 3000,
          });
        }
      },
    },
  };
</script>

<script lang="renderjs" module="render">
  class DefOpitons {
  	constructor() {
  		// 本次选择文件个数上限
  		this.count = 9;
  		// 单个文件大小上限
  		this.size = 10;
  		// input file原生属性
  		this.accept = '';
  		// 是否允许多选
  		this.multiple = true;
  		// 允许选择的格式，空等于不限制,多个以逗号隔开,例如 'png,jpg,jpeg'
  		this.formats = '';
  	}
  }

  export default {
  	data() {
  		return {
  			configs: {},
  			options: {},
  			fileDom: null,
  			files: new Map(),
  			xmls: new Map()
  		}
  	},
  	methods: {
  		initConfig(config) {
  			this.configs = config;
  		},
  		generalFail({
  			name,
  			methodName
  		}, error) {
  			this.$ownerInstance.callMethod(methodName, {
  				name,
  				status: 'fail',
  				message: error?.message || error
  			});
  		},
  		renderFileHandle(e) {
  			switch (e?.type) {
  				case 'deleted':
  					if (e?.name) {
  						this.configs?.debug && console.log('删除文件：', e.name);
  						this.files.delete(e.name);
  					}
  					break;
  				case 'clear':
  					this.configs?.debug && console.log('清空文件');
  					this.files.clear();
  					break;
  				case 'getBase':
  					this.executeToBase(e);
  					break;
  				case 'getArrayBuffer':
  					this.executeToArrayBuffer(e);
  					break;
  				case 'getTempFilePath':
  					this.executeToTempFilePath(e);
  					break;
  				case 'asyncFileTempFilePath':
  					this.files.get(e.name)['tempFilePath'] = e?.params?.tempFilePath;
  					break;
  				case 'abort':
  					if (this.xmls.get(e.name)) {
  						this.xmls.get(e.name)?.abort();
  						console.log('已终止上传' + e.name);
  					}
  					break;
  				default:
  					break;
  			}
  		},
  		async executeToTempFilePath(e) {
  			const methodName = 'getTempFilePathCallBack';
  			try {
  				if (!this.files.has(e?.name)) {
  					throw '文件未找到';
  				}
  				if (this.files.get(e.name)?.tempFilePath) {
  					const result = {
  						tempFilePath: this.files.get(e.name).tempFilePath
  					}
  					this.$ownerInstance.callMethod(methodName, {
  						name: e.name,
  						status: 'success',
  						result
  					})
  					return;
  				}
  				this.executeToArrayBuffer(e, methodName);
  			} catch (error) {
  				console.error(error);
  				this.generalFail({
  					name: e.name,
  					methodName
  				}, error);
  			}

  		},
  		async executeToArrayBuffer(e, methodName = 'getArrayBufferCallBack') {
  			try {
  				const fileType = this.files.get(e.name)?.file?.type;
  				const result = await this.fileToArrayBuffer(e?.name);
  				// 分片参数配置
  				const chunkSize = 0.5 * 1204 * 1024; // 每片0.5M
  				const totalChunks = Math.ceil(result.length / chunkSize); // 总分片数
  				for (let i = 0; i < totalChunks; i++) {
  					const start = i * chunkSize;
  					const end = Math.min(start + chunkSize, result.length);
  					const chunk = result.slice(start, end); // 当前分片数据

  					this.$ownerInstance.callMethod(methodName, {
  						name: e.name,
  						fileType,
  						status: 'success',
  						result: {
  							chunk, // 当前分片数据
  							index: i, // 当前分片索引
  							totalChunks, // 总分片数
  						}
  					});
  				}
  			} catch (error) {
  				console.error(error);
  				this.generalFail({
  					name: e.name,
  					methodName
  				}, error);
  			}
  		},
  		async executeToBase(e, methodName = 'getBaseCallBack') {
  			try {
  				const fileType = this.files.get(e.name)?.file?.type;
  				const result = await this.fileToBase(e?.name);
  				// 分片参数配置
  				const chunkSize = 0.5 * 1204 * 1024; // 每片0.5M
  				const totalChunks = Math.ceil(result.length / chunkSize); // 总分片数

  				for (let i = 0; i < totalChunks; i++) {
  					const start = i * chunkSize;
  					const end = Math.min(start + chunkSize, result.length);
  					const chunk = result.slice(start, end); // 当前分片数据

  					this.$ownerInstance.callMethod(methodName, {
  						name: e.name,
  						fileType,
  						status: 'success',
  						result: {
  							chunk, // 当前分片数据
  							index: i, // 当前分片索引
  							totalChunks, // 总分片数
  						}
  					});
  				}
  			} catch (error) {
  				console.error(error);
  				this.generalFail({
  					name: e.name,
  					methodName
  				}, error);
  			}
  		},
  		chooseFileHandle(e) {
  			if (!e?.html) {
  				return;
  			}
  			this.options = Object.assign(new DefOpitons, e.option);
  			if (Number(this.options?.count) < 1) {
  				this.$ownerInstance.callMethod('changeFile', []);
  				return;
  			}
  			this.configs?.debug && console.log('chooseFile', this.options);

  			this.$nextTick(() => {
  				this.fileDom = document.getElementById(`lsj-file-${e.id}`);
  				this.fileDom.accept = this.options.accept;
  				this.fileDom.multiple = Number(this.options?.count) > 1 ? this.options.multiple : false;
  				this.fileDom.value = '';
  				this.fileDom.onchange = async (event) => {
  					const Files = event.target.files;
  					let newFiles = [];
  					for (let i = 0, len = Files.length; i < len; i++) {
  						const file = Files[i];
  						if (this.options?.count && newFiles.length >= Number(this.options.count)) {
  							this.$ownerInstance.callMethod('toToast', `只允许选择${this.options.count}个文件`);
  							this.fileDom.value = '';
  							break;
  						}
  						try {
  							newFiles.push(await this.addFile(file));

  						} catch (error) {
  							console.error(error);
  						}
  					}
  					this.$ownerInstance.callMethod('changeFile', newFiles);
  				};
  				this.fileDom.click();

  			});


  		},
  		addFile(file) {
  			return new Promise((resolve, reject) => {
  				if (file) {
  					let fileName = file.name;
  					let newFileName = fileName;
  					// 限制文件格式
  					const suffix = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
  					const formats = this.options.formats && (this.options.formats.toLowerCase());
  					this.configs?.debug && console.log('添加文件', fileName, '大小', file.size, '格式', suffix);
  					if (formats && !formats.includes(suffix)) {
  						this.$ownerInstance.callMethod('toToast', `不支持上传${suffix.toUpperCase()}格式文件`);
  						return reject();
  					}
  					// 限制文件大小
  					if (file.size > 1024 * 1024 * Math.abs(this.options.size)) {
  						this.$ownerInstance.callMethod('toToast', `文件大小请勿超过${this.options.size}M`);
  						return reject();
  					}
  					try {
  						if (!this.configs?.distinct) {
  							while (this.files.has(newFileName)) {
  								const lastDotIndex = fileName.lastIndexOf('.');
  								let nameWithoutExt = fileName.substring(0, lastDotIndex);
  								let extension = fileName.substring(lastDotIndex);
  								let counter = 1;
  								while (this.files.has(`${nameWithoutExt}(${counter})${extension}`)) {
  									counter++;
  								}
  								newFileName = `${nameWithoutExt}(${counter})${extension}`;
  							}
  						}
  					} catch (e) {
  						newFileName = Date.now() + '_' + newFileName;
  					}
  					this.files.set(newFileName, {
  						file,
  						name: newFileName,
  						size: file.size,
  						progress: 0,
  						status: 'waiting'
  					});
  					return resolve(this.files.get(newFileName));

  				}
  			})
  		},
  		fileToArrayBuffer(name) {
  			return new Promise((resolve, reject) => {
  				if (!this.files.has(name)) {
  					throw '文件未找到';
  				}
  				if (this.files.get(name)?.base64) {
  					this.configs?.debug && console.log('读取缓存二进制');
  					return resolve(this.files.get(name).base64.split(',')[1]);
  				}
  				const file = this.files.get(name).file;
  				const reader = new FileReader();
  				reader.onload = (e) => {
  					let base64 = e.target.result;
  					let item = this.files.get(name);
  					item['base64'] = base64;
  					this.files.set(name, item);
  					resolve(base64.split(',')[1])
  				};
  				reader.onerror = (e) => reject(e);
  				reader.readAsDataURL(file);
  			});
  		},
  		fileToBase(name) {
  			return new Promise((resolve, reject) => {
  				if (!this.files.has(name)) {
  					throw '文件未找到';
  				}
  				// 已转过base无需重复转换
  				if (this.files.get(name)?.base64) {
  					this.configs?.debug && console.log('读取缓存base64');
  					return resolve(this.files.get(name).base64.replace(/[\r\n]/g, ""));
  				}
  				const file = this.files.get(name).file;
  				const reader = new FileReader();
  				reader.onload = (e) => {
  					let base64 = e.target.result;
  					let item = this.files.get(name);
  					item['base64'] = base64;
  					this.files.set(name, item);
  					resolve(base64.replace(/[\r\n]/g, ""));
  				};
  				reader.onerror = (e) => reject(e);
  				reader.readAsDataURL(file);
  			})
  		},
  		isContentTypeJson(header) {
  			try {
  				const contentType = header && header['Content-Type'];
  				return contentType && contentType.includes('application/json');
  			} catch (error) {
  				return false
  			}
  		},
  		async uploadHandle(param) {
  			const params = JSON.parse(param);
  			if (!params.file?.name) {
  				return;
  			}
  			if (params.file?.status === 'onprogress') {
  				console.error('该文件状态为上传中，请勿重复提交，若不需要此过滤，请前往插件源码删除该判断');
  				return;
  			}
  			this.configs?.debug && console.log('params', params);

  			if (!this.files.has(params.file?.name)) {
  				this.$ownerInstance.callMethod('uploadCallBack', {
  					name: params.file.name,
  					status: 'fail',
  					message: '文件未找到',
  				});
  				return;
  			}
  			const {
  				url,
  				name = 'file',
  				method = 'post',
  				toBase = false,
  				header = {},
  				formData = {},
  			} = params;

  			let form = new FormData();
  			for (let keys in formData) {
  				form.append(keys, formData[keys]);
  			}

  			if (toBase) {
  				const base64 = await this.fileToBase(params.file.name);
  				form.append(name, base64);
  			} else {
  				const file = this.files.get(params.file.name).file;
  				form.append(name, file);
  			}
  			let xmlRequest = new XMLHttpRequest();
  			this.xmls.set(params.file.name, xmlRequest);
  			xmlRequest.upload.addEventListener(
  				'progress',
  				event => {
  					if (event.lengthComputable) {
  						let progress = Math.ceil((event.loaded * 100) / event.total)
  						if (progress <= 100) {
  							this.$ownerInstance.callMethod('uploadProgress', {
  								status: 'onprogress',
  								name: params.file.name,
  								progress
  							});
  						}
  					}
  				},
  				false
  			);
  			xmlRequest.ontimeout = () => {
  				console.error('请求超时')
  				this.$ownerInstance.callMethod('uploadCallBack', {
  					name: params.file.name,
  					status: 'fail',
  					message: '请求超时'
  				});
  				this.xmls.delete(params.file.name);
  			}
  			xmlRequest.onerror = event => {
  				console.error(event);
  			};
  			xmlRequest.onreadystatechange = event => {
  				if (xmlRequest.readyState == 4) {
  					this.configs?.debug && console.log('接口是否支持跨域', xmlRequest.withCredentials);
  					if (xmlRequest.status == 200) {
  						this.configs?.debug && console.log('上传完成：' + xmlRequest.responseText)
  						this.$ownerInstance.callMethod('uploadCallBack', {
  							name: params.file.name,
  							status: 'success',
  							message: '上传完成',
  							result: xmlRequest.responseText,
  						});
  						this.xmls.delete(params.file.name);
  						return;
  					}
  					console.error('ERROR：上传失败 HTTP status:' + xmlRequest.status)
  					this.xmls.delete(params.file.name);
  					this.$ownerInstance.callMethod('uploadCallBack', {
  						name: params.file.name,
  						status: 'fail',
  						message: '上传失败',
  						result: xmlRequest.status
  					});
  				}
  			}
  			xmlRequest.open(method, url, true);
  			for (let keys in header) {
  				xmlRequest.setRequestHeader(keys, header[keys])
  			}
  			if (this.isContentTypeJson(header)) {
  				const formObject = {};
  				for (let [key, value] of form.entries()) {
  					formObject[key] = value;
  				}
  				xmlRequest.send(JSON.stringify(formObject))
  			} else {
  				xmlRequest.send(form)
  			}
  		}
  	}
  }
</script>
<style scoped>
  .lsj-upload {
    position: fixed;
    z-index: -1;
    top: -100px;
  }
</style>
