Lenny-Hu / note

blog
5 stars 1 forks source link

表单验证、文件上传、切片上传 #112

Open Lenny-Hu opened 2 years ago

Lenny-Hu commented 2 years ago

简单的分片上传 axios

// 自定义axios实例,可以用来上传multipart/form-data数据
const fileHttp = axios.create({
  baseURL: axios.defaults.baseURL,
  timeout: 1000 * 60
});

// utils.js
export default {
  /**
   * 请求参数封装为 FormData 对象
   * @param params 参数
   * @returns {FormData}
   */
   packageFormData (params) {
    const data = new FormData();
    Object.keys(params).forEach(function(key) {
      data.append(key, params[key]);
    });
    return data;
  },
  /**
   * 上传文件
   * @param $fileHttp 自定义的axios实列, $http.js中已定义(this.$fileHttp)
   * @param options 配置参数
   * @param options.url 上传接口地址
   * @param options.params 请求参数
   * @param options.file File文件对象
   * @param options.onUploadProgress 上传进度事件
   * @param options.fileKey File文件键名,默认 file
   * @returns {Promise}
   */
  uploadFile ($fileHttp, options) {
    const params = options.params || {};  // 没有参数默认为空JSON对象
    params[options.fileKey || 'file'] = options.file;  // 没有规定file键名,默认为file
    const axiosOptions = {
      url: options.url,
      method: 'post',
      data: this.packageFormData(params)
    };

    // 判断是否需要上传进度反馈事件
    if (options.onUploadProgress) axiosOptions.onUploadProgress = options.onUploadProgress;
    return $fileHttp(axiosOptions);
  },
  /**
   * 切片上传
   * @param options 配置参数
   * @param options.url 上传接口地址
   * @param options.params 请求参数
   * @param options.file File文件对象
   * @param options.onUploadProgress 上传进度事件
   * @param options.fileKey File文件键名,默认 file
   * @param options.curKey 本次传输的分片编号键名,默认 blob_num
   * @param options.totalKey 分片总数键名,默认 total_blob_num
   * @returns {Promise<[]>}
   */
  async uploadSection ($fileHttp, options) {
    const sectionSize = 1 * 1024 * 1024; // 切片大小,超过该值将进行切片上传,字节(b)单位
    const sectionTotal = Math.ceil(options.file.size / sectionSize);  // 切片次数
    const fileSize = options.file.size; // 文件大小
    const results = []; // 切片结果返回集
    // console.log('切片总数', sectionTotal);

    for(let i = 0; i < sectionTotal; i++) {
      // console.log('切片编号', i);
      const startSize = i * sectionSize;  // 切片开始位置
      // 切片结尾位置,判断如果是最后一次直则是文件大小
      const endSize = i === sectionSize - 1 ? fileSize : (i + 1) * sectionSize;

      let new_options = {...options};  // 复制原有的 options
      new_options.params[options.curKey || 'blob_num'] = (i + 1);
      new_options.params[options.totalKey || 'total_blob_num'] = sectionTotal;

      new_options.file = options.file.slice(startSize, endSize);

      if (options.onUploadProgress) {
        new_options.onUploadProgress = (progressEvent) => {
          let loaded = startSize + progressEvent.loaded;
          if (loaded > fileSize) {
            loaded = fileSize;
          }
          let per = parseInt(loaded / fileSize * 100);
          options.onUploadProgress({
            loaded,
            total: fileSize,
            per
          });
        };
      }
      results.push(await this.uploadFile($fileHttp, new_options));
    }
    return results;
  }
};
// 使用
let queu = await utils.uploadSection(this.$fileHttp, {
    url: '/api',
    params: data,
    file: this.selected.file,
    onUploadProgress (data) {
      console.log(data.per); // 百分比进度
    }
  });
// queu 每一个分片上传的结果
Lenny-Hu commented 2 years ago

简单的文件上传检查函数

不支持files的浏览器使用正则检查文件名,不检查大小


# ele: file元素
# k name名称
const checkFile: function (ele, k) {
var map = {
'.doc': 'application/msword',
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.mp4': 'video/mp4',
'.jpg': 'image/jpeg',
'.png': 'image/png',
'.jpeg': 'image/jpeg',
};
var res = { file: '', err: '' };
var support = !!ele.files;
var flag = false;
res.file = support ? ele.files[0] : { name: ele.value };
console.log(res.file);
var ext = this.$options.fileLimit[k].ext;
if (ext.length) {
  flag = ext.some(function (v, i) {
    if (res.file.type) {
      return map[v] == res.file.type;
    } else {
      return new RegExp('\\' + v + '$', 'i').test(res.file.name);
    }
  });

  if (!flag) {
    res.err = '请选择后缀为' + ext.join('、') + '的文件';
    return res;
  }
}

var limit = this.$options.fileLimit[k].limit;
if (res.file.size && limit) {
  flag = res.file.size > limit;

  if (flag) {
    res.err = '文件大小超出' + parseInt(limit / 1024 / 1024) + 'Mb';
    return res;
  }
}

return res;

}


vue中使用(部分关键代码)

new Vue({ fileLimit: { // 限制参数 file: { // name,就能适合多个需要检查的对象 limit: 1024 1024 1, ext: ['.doc', '.docx'] } }, methods: { onSelectFile: function (e) { // console.log(e.target.value, e.target); if (!e.target.value) { return this.form.file = ''; }

  var res = this.checkFile(e.target, 'file');
  if (res.err) {
    this.form.file = '';
    return alert( res.err );
  }

  this.form.file = res.file.name; // 用来显示文件名称的input
  // this.validateForm(this.form, 'file');
},

} });

Lenny-Hu commented 2 years ago

JS数据校验函数

简单对对象数据的检查

window._rules = {
    required: {
      validate: function (v) {
        return !!($.trim(v));
      },
      message: '%s不能为空'
    },
    mobile: {
      validate: function (v) {
        return /^1[3-9]\d{9}$/.test(v);
      },
      message: '%s不合法'
    }
  };

  // 直接混入
  window._commonMixins = {
    data: function () {
      return {
        errorMsg: {} // 错误信息对象,用来在页面上展示或者提示
      }
    },
    methods: {
      checkFile: function (ele, k) {
        var map = {
          '.doc': 'application/msword',
          '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
          '.mp4': 'video/mp4',
          '.jpg': 'image/jpeg',
          '.png': 'image/png',
          '.jpeg': 'image/jpeg',
        };
        var res = { file: '', err: '' };
        var support = !!ele.files;
        var flag = false;
        res.file = support ? ele.files[0] : { name: ele.value };
        // console.log(res.file);

        var ext = this.$options.fileLimit[k].ext;
        if (ext.length) {
          flag = ext.some(function (v, i) {
            if (res.file.type) {
              return map[v] == res.file.type;
            } else {
              return new RegExp('\\' + v + '$', 'i').test(res.file.name);
            }
          });

          if (!flag) {
            res.err = '请选择后缀为' + ext.join('、') + '的文件';
            return res;
          }
        }

        var limit = this.$options.fileLimit[k].limit;
        if (res.file.size && limit) {
          flag = res.file.size > limit;

          if (flag) {
            res.err = '文件大小超出' + parseInt(limit / 1024 / 1024) + 'MB';
            return res;
          }
        }

        return res;
      },
      validate: function (form, fileds) {
        if (!(fileds instanceof Array)) {
          fileds = [];
        }
        var res = {
          isValid: false,
          errorMsg: {},
          message: '',
          messages: [],
          valid: [],
          invalid: []
        };
        var validations = this.$options.validations;
        var rules = this.$options.rules;
        var _this = this;

        var keys = fileds.length ? Object.keys(form).filter(function (name) {
          return fileds.indexOf(name) !== -1;
        }) : Object.keys(form);

        // keys
        keys.forEach(function (name, i) {
          var val = form[name];
          var verifyConf = validations[name];
          var validators = [];
          if (!verifyConf) {
            return false;
          }

          if (verifyConf.validator) {
            validators = verifyConf.validator.split(',').map(function (v) { 
              var n = v.trim(); 
              return rules[n] || verifyConf[n];
            });
          }

          if (verifyConf.validate instanceof Function) {
            validators.push(verifyConf);
          }

          for (var i = 0; i < validators.length; i++) {
            var _cur = validators[i];

            if (_cur.validate.bind(_this, val)()) {
              res.valid.push(name);

            } else {
              res.valid = res.valid.filter(function (v) { return v !== name }); // 不合法,从数组中移除字段名

              var _msg = _cur.message.replace(/%s/ig, verifyConf.label);
              res.invalid.push(name);
              res.messages.push(_msg);
              if (!res.message) res.message = _msg; // 第一个校验字段的错误信息
              res.errorMsg[name] = _msg;
              break;
            }
          }
        });

        res.isValid = res.invalid.length === 0;
        return res;
      },
      validateForm: function (form, k) {
        var _this = this;
        var checkFiled = k ? (k instanceof Array ? k : [k]) : Object.keys(form);

        // 检查字段
        // console.log('检查的字段', checkFiled);
        var validateResult = this.validate(form, checkFiled);
        // console.log(validateResult);

        if (k) {
          _this.$set(_this.errorMsg, k, '');
        } else {
          _this.errorMsg = {};
        }

        if (!validateResult.isValid) {
          $.each(validateResult.errorMsg, function (k, v) {
            _this.$set(_this.errorMsg, k, v);
          });
        }
        return validateResult;
      },
    }
  };

vue 中使用

// html 部分
<div class="form-item f-cb">
            <label class="form-tt f-fl" for="">用户名:</label>
            <div class="form-ipt-box f-fl">
              <input class="form-ipt" v-model.trim="form.name" name="name" type="text" placeholder="请输入用户名称" @blur="validateForm(form, 'name')"/>
              <div class="form-tip">{{errorMsg.name}}</div>
            </div>
          </div>
// js 部分
new Vue({
      rules: window._rules,
      mixins: [window._commonMixins],
      fileLimit: {
        file: {
          limit: 1024 * 1024 * 1,
          ext: ['.doc', '.docx']
        }
      },
      validations: {
        title: {
          label: '标题',
          validator: 'required'
        },
        name: {
          label: '用户姓名',
          validator: 'required'
        },
        mobile: {
          label: '手机号',
          validator: 'required,mobile',
        },
        level: {
          label: '等级',
          validator: 'required'
        },
        file: {
          label: '文件',
          validator: 'required'
        }
      },
     data: function () {
         return {
            form: {
              title: '',
              name: '',
              mobile: '',
              level: '',
              file: ''
            },
         };
     },
     methods: {
       onSelectFile: function (e) {
          // console.log(e.target.value, e.target);
          if (!e.target.value) {
            return this.form.file = '';
          }

          var res = this.checkFile(e.target, 'file');
          if (res.err) {
            this.form.file = '';
            return alert(res.err);
          }

          this.form.file = res.file.name;
          this.validateForm(this.form, 'file'); // 检查单个字断
        },
        postInfo: function () {
          // 检查字段
          var validateResult = this.validateForm(this.form);
          if (!validateResult.isValid) {
            return false;
          }
          // 通过验证, ajax 提交数据
       }
     }
}