element-ui Upload 上傳組件源碼分析整理筆記(十四)

来源:https://www.cnblogs.com/fangnianqin/archive/2019/09/02/11447906.html
-Advertisement-
Play Games

簡單寫了部分註釋,upload dragger.vue(拖拽上傳時顯示此組件)、upload list.vue(已上傳文件列表)源碼暫未添加多少註釋,等有空再補充,先記下來... index.vue upload.vue pythod import ajax from './ajax'; impor ...


簡單寫了部分註釋,upload-dragger.vue(拖拽上傳時顯示此組件)、upload-list.vue(已上傳文件列表)源碼暫未添加多少註釋,等有空再補充,先記下來...

index.vue

<script>
import UploadList from './upload-list';
import Upload from './upload';
import ElProgress from 'element-ui/packages/progress';
import Migrating from 'element-ui/src/mixins/migrating';

function noop() {}

export default {
  name: 'ElUpload',

  mixins: [Migrating],

  components: {
    ElProgress,
    UploadList,
    Upload
  },

  provide() {
    return {
      uploader: this
    };
  },

  inject: {
    elForm: {
      default: ''
    }
  },

  props: {
    action: { //必選參數,上傳的地址
      type: String,
      required: true
    },
    headers: { //設置上傳的請求頭部
      type: Object,
      default() {
        return {};
      }
    },
    data: Object, //上傳時附帶的額外參數
    multiple: Boolean, //是否支持多選文件
    name: { //上傳的文件欄位名
      type: String,
      default: 'file'
    },
    drag: Boolean, //是否啟用拖拽上傳
    dragger: Boolean,
    withCredentials: Boolean, //支持發送 cookie 憑證信息
    showFileList: { //是否顯示已上傳文件列表
      type: Boolean,
      default: true
    },
    accept: String, //接受上傳的文件類型(thumbnail-mode 模式下此參數無效)
    type: {
      type: String,
      default: 'select'
    },
    beforeUpload: Function, //上傳文件之前的鉤子,參數為上傳的文件,若返回 false 或者返回 Promise 且被 reject,則停止上傳。
    beforeRemove: Function, //刪除文件之前的鉤子,參數為上傳的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,則停止上傳。
    onRemove: { //文件列表移除文件時的鉤子
      type: Function,
      default: noop
    },
    onChange: { //文件狀態改變時的鉤子,添加文件、上傳成功和上傳失敗時都會被調用
      type: Function,
      default: noop
    },
    onPreview: { //點擊文件列表中已上傳的文件時的鉤子
      type: Function
    },
    onSuccess: { //文件上傳成功時的鉤子
      type: Function,
      default: noop
    },
    onProgress: { //文件上傳時的鉤子
      type: Function,
      default: noop
    },
    onError: { //文件上傳失敗時的鉤子
      type: Function,
      default: noop
    },
    fileList: { //上傳的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]
      type: Array,
      default() {
        return [];
      }
    },
    autoUpload: { //是否在選取文件後立即進行上傳
      type: Boolean,
      default: true
    },
    listType: { //文件列表的類型
      type: String,
      default: 'text' // text,picture,picture-card
    },
    httpRequest: Function, //覆蓋預設的上傳行為,可以自定義上傳的實現
    disabled: Boolean, //是否禁用
    limit: Number, //最大允許上傳個數
    onExceed: { //文件超出個數限制時的鉤子
      type: Function,
      default: noop
    }
  },

  data() {
    return {
      uploadFiles: [],
      dragOver: false,
      draging: false,
      tempIndex: 1
    };
  },

  computed: {
    uploadDisabled() {
      return this.disabled || (this.elForm || {}).disabled;
    }
  },

  watch: {
    fileList: {
      immediate: true,
      handler(fileList) {
        this.uploadFiles = fileList.map(item => {
          item.uid = item.uid || (Date.now() + this.tempIndex++);
          item.status = item.status || 'success';
          return item;
        });
      }
    }
  },

  methods: {
    //文件上傳之前調用的方法
    handleStart(rawFile) {
      rawFile.uid = Date.now() + this.tempIndex++;
      let file = {
        status: 'ready',
        name: rawFile.name,
        size: rawFile.size,
        percentage: 0,
        uid: rawFile.uid,
        raw: rawFile
      };
      //判斷文件列表類型
      if (this.listType === 'picture-card' || this.listType === 'picture') {
        try {
          file.url = URL.createObjectURL(rawFile);
        } catch (err) {
          console.error('[Element Error][Upload]', err);
          return;
        }
      }
      this.uploadFiles.push(file);
      this.onChange(file, this.uploadFiles);
    },
    handleProgress(ev, rawFile) {
      const file = this.getFile(rawFile);
      this.onProgress(ev, file, this.uploadFiles);
      file.status = 'uploading';
      file.percentage = ev.percent || 0;
    },
    //文件上傳成功後改用該方法,在該方法中調用用戶設置的on-success和on-change方法,並將對應的參數傳遞出去
    handleSuccess(res, rawFile) {
      const file = this.getFile(rawFile);

      if (file) {
        file.status = 'success';
        file.response = res;

        this.onSuccess(res, file, this.uploadFiles);
        this.onChange(file, this.uploadFiles);
      }
    },
    //文件上傳失敗後改用該方法,在該方法中調用用戶設置的on-error和on-change方法,並將對應的參數傳遞出去
    handleError(err, rawFile) {
      const file = this.getFile(rawFile);
      const fileList = this.uploadFiles;

      file.status = 'fail';

      fileList.splice(fileList.indexOf(file), 1);

      this.onError(err, file, this.uploadFiles);
      this.onChange(file, this.uploadFiles);
    },
    //文件列表移除文件時調用該方法
    handleRemove(file, raw) {
      if (raw) {
        file = this.getFile(raw);
      }
      let doRemove = () => {
        this.abort(file);
        let fileList = this.uploadFiles;
        fileList.splice(fileList.indexOf(file), 1);
        this.onRemove(file, fileList);
      };

      if (!this.beforeRemove) {
        doRemove();
      } else if (typeof this.beforeRemove === 'function') {
        const before = this.beforeRemove(file, this.uploadFiles);
        if (before && before.then) {
          before.then(() => {
            doRemove();
          }, noop);
        } else if (before !== false) {
          doRemove();
        }
      }
    },
    getFile(rawFile) {
      let fileList = this.uploadFiles;
      let target;
      fileList.every(item => {
        target = rawFile.uid === item.uid ? item : null;
        return !target;
      });
      return target;
    },
    abort(file) {
      this.$refs['upload-inner'].abort(file);
    },
    clearFiles() {
      this.uploadFiles = [];
    },
    submit() {
      this.uploadFiles
        .filter(file => file.status === 'ready')
        .forEach(file => {
          this.$refs['upload-inner'].upload(file.raw);
        });
    },
    getMigratingConfig() {
      return {
        props: {
          'default-file-list': 'default-file-list is renamed to file-list.',
          'show-upload-list': 'show-upload-list is renamed to show-file-list.',
          'thumbnail-mode': 'thumbnail-mode has been deprecated, you can implement the same effect according to this case: http://element.eleme.io/#/zh-CN/component/upload#yong-hu-tou-xiang-shang-chuan'
        }
      };
    }
  },

  beforeDestroy() {
    this.uploadFiles.forEach(file => {
      if (file.url && file.url.indexOf('blob:') === 0) {
        URL.revokeObjectURL(file.url);
      }
    });
  },

  render(h) {
    let uploadList;
    //如果用戶設置showFileList為true,則顯示上傳文件列表
    if (this.showFileList) {
      uploadList = (
        <UploadList
          disabled={this.uploadDisabled}
          listType={this.listType}
          files={this.uploadFiles}
          on-remove={this.handleRemove}
          handlePreview={this.onPreview}>
        </UploadList>
      );
    }

    const uploadData = {
      props: {
        type: this.type,
        drag: this.drag,
        action: this.action,
        multiple: this.multiple,
        'before-upload': this.beforeUpload,
        'with-credentials': this.withCredentials,
        headers: this.headers,
        name: this.name,
        data: this.data,
        accept: this.accept,
        fileList: this.uploadFiles,
        autoUpload: this.autoUpload,
        listType: this.listType,
        disabled: this.uploadDisabled,
        limit: this.limit,
        'on-exceed': this.onExceed,
        'on-start': this.handleStart,
        'on-progress': this.handleProgress,
        'on-success': this.handleSuccess,
        'on-error': this.handleError,
        'on-preview': this.onPreview,
        'on-remove': this.handleRemove,
        'http-request': this.httpRequest
      },
      ref: 'upload-inner'
    };

    const trigger = this.$slots.trigger || this.$slots.default;
    const uploadComponent = <upload {...uploadData}>{trigger}</upload>;

    return (
      <div>
        { this.listType === 'picture-card' ? uploadList : ''}
        {
          this.$slots.trigger
            ? [uploadComponent, this.$slots.default]
            : uploadComponent
        }
        {this.$slots.tip}
        { this.listType !== 'picture-card' ? uploadList : ''}
      </div>
    );
  }
};
</script>

upload.vue

<script>
import ajax from './ajax';
import UploadDragger from './upload-dragger.vue';

export default {
  inject: ['uploader'],
  components: {
    UploadDragger
  },
  props: {
    type: String,
    action: { //必選參數,上傳的地址
      type: String,
      required: true
    },
    name: { //上傳的文件欄位名
      type: String,
      default: 'file'
    },
    data: Object, //上傳時附帶的額外參數
    headers: Object, //設置上傳的請求頭部
    withCredentials: Boolean, //支持發送 cookie 憑證信息
    multiple: Boolean, //是否支持多選文件
    accept: String, //接受上傳的文件類型(thumbnail-mode 模式下此參數無效)
    onStart: Function,
    onProgress: Function, //文件上傳時的鉤子
    onSuccess: Function, //文件上傳成功時的鉤子
    onError: Function, //文件上傳失敗時的鉤子
    beforeUpload: Function, //上傳文件之前的鉤子,參數為上傳的文件,若返回 false 或者返回 Promise 且被 reject,則停止上傳。
    drag: Boolean, //是否啟用拖拽上傳
    onPreview: { //點擊文件列表中已上傳的文件時的鉤子
      type: Function,
      default: function() {}
    },
    onRemove: { //文件列表移除文件時的鉤子
      type: Function,
      default: function() {}
    },
    fileList: Array, //上傳的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]
    autoUpload: Boolean, //是否在選取文件後立即進行上傳
    listType: String, //文件列表的類型
    httpRequest: { //覆蓋預設的上傳行為,可以自定義上傳的實現
      type: Function,
      default: ajax
    },
    disabled: Boolean,//是否禁用
    limit: Number,//最大允許上傳個數
    onExceed: Function //文件超出個數限制時的鉤子
  },

  data() {
    return {
      mouseover: false,
      reqs: {}
    };
  },

  methods: {
    isImage(str) {
      return str.indexOf('image') !== -1;
    },
    handleChange(ev) {
      const files = ev.target.files;

      if (!files) return;
      this.uploadFiles(files);
    },
    uploadFiles(files) {
      //文件超出個數限制時,調用onExceed鉤子函數
      if (this.limit && this.fileList.length + files.length > this.limit) {
        this.onExceed && this.onExceed(files, this.fileList);
        return;
      }
      //將files轉成數組
      let postFiles = Array.prototype.slice.call(files);
      if (!this.multiple) { postFiles = postFiles.slice(0, 1); }

      if (postFiles.length === 0) { return; }

      postFiles.forEach(rawFile => {
        this.onStart(rawFile);
        //選取文件後調用upload方法立即進行上傳文件
        if (this.autoUpload) this.upload(rawFile);
      });
    },
    upload(rawFile) {
      this.$refs.input.value = null;
      //beforeUpload 上傳文件之前的鉤子不存在就直接調用post上傳文件
      if (!this.beforeUpload) {
        return this.post(rawFile);
      }
      // beforeUpload 上傳文件之前的鉤子,參數為上傳的文件,若返回 false 或者返回 Promise 且被 reject,則停止上傳
      const before = this.beforeUpload(rawFile);
      // 在調用beforeUpload鉤子後返回的是true,則繼續上傳
      if (before && before.then) {
        before.then(processedFile => {
          //processedFile轉成對象
          const fileType = Object.prototype.toString.call(processedFile);

          if (fileType === '[object File]' || fileType === '[object Blob]') {
            if (fileType === '[object Blob]') {
              processedFile = new File([processedFile], rawFile.name, {
                type: rawFile.type
              });
            }
            for (const p in rawFile) {
              if (rawFile.hasOwnProperty(p)) {
                processedFile[p] = rawFile[p];
              }
            }
            this.post(processedFile);
          } else {
            this.post(rawFile);
          }
        }, () => {
          this.onRemove(null, rawFile);
        });
      } else if (before !== false) { //調用beforeUpload之後沒有返回值,此時before為undefined,繼續上傳
        this.post(rawFile);
      } else {  //調用beforeUpload之後返回值為false,則不再繼續上傳並移除文件
        this.onRemove(null, rawFile);
      }
    },
    abort(file) {
      const { reqs } = this;
      if (file) {
        let uid = file;
        if (file.uid) uid = file.uid;
        if (reqs[uid]) {
          reqs[uid].abort();
        }
      } else {
        Object.keys(reqs).forEach((uid) => {
          if (reqs[uid]) reqs[uid].abort();
          delete reqs[uid];
        });
      }
    },
    //上傳文件過程的方法
    post(rawFile) {
      const { uid } = rawFile;
      const options = {
        headers: this.headers,
        withCredentials: this.withCredentials,
        file: rawFile,
        data: this.data,
        filename: this.name,
        action: this.action,
        onProgress: e => { //文件上傳時的鉤子函數
          this.onProgress(e, rawFile);
        },
        onSuccess: res => { //文件上傳成功的鉤子函數
          //上傳成功調用onSuccess方法,即index.vue中的handleSuccess方法
          this.onSuccess(res, rawFile);
          delete this.reqs[uid];
        },
        onError: err => { //文件上傳失敗的鉤子函數
          this.onError(err, rawFile);
          delete this.reqs[uid];
        }
      };
      //httpRequest可以自定義上傳文件,如果沒有定義,預設通過ajax文件中的方法上傳
      const req = this.httpRequest(options);
      this.reqs[uid] = req;
      if (req && req.then) {
        req.then(options.onSuccess, options.onError);
      }
    },
    handleClick() {
      //點擊組件時調用input的click方法
      if (!this.disabled) {
        this.$refs.input.value = null;
        this.$refs.input.click();
      }
    },
    handleKeydown(e) {
      if (e.target !== e.currentTarget) return;
      //如果當前按下的是回車鍵和空格鍵,調用handleClick事件
      if (e.keyCode === 13 || e.keyCode === 32) {
        this.handleClick();
      }
    }
  },

  render(h) {
    let {
      handleClick,
      drag,
      name,
      handleChange,
      multiple,
      accept,
      listType,
      uploadFiles,
      disabled,
      handleKeydown
    } = this;
    const data = {
      class: {
        'el-upload': true
      },
      on: {
        click: handleClick,
        keydown: handleKeydown
      }
    };
    data.class[`el-upload--${listType}`] = true;
    return (
      //判斷是否允許拖拽,允許的話顯示upload-dragger組件,不允許就顯示所有插槽中的節點
      <div {...data} tabindex="0" >
        {
          drag
            ? <upload-dragger disabled={disabled} on-file={uploadFiles}>{this.$slots.default}</upload-dragger>
            : this.$slots.default
        }
        <input class="el-upload__input" type="file" ref="input" name={name} on-change={handleChange} multiple={multiple} accept={accept}></input>
      </div>
    );
  }
};
</script>

ajax.js

function getError(action, option, xhr) {
  let msg;
  if (xhr.response) {
    msg = `${xhr.response.error || xhr.response}`;
  } else if (xhr.responseText) {
    msg = `${xhr.responseText}`;
  } else {
    msg = `fail to post ${action} ${xhr.status}`;
  }

  const err = new Error(msg);
  err.status = xhr.status;
  err.method = 'post';
  err.url = action;
  return err;
}

function getBody(xhr) {
  const text = xhr.responseText || xhr.response;
  if (!text) {
    return text;
  }

  try {
    return JSON.parse(text);
  } catch (e) {
    return text;
  }
}
//預設的上傳文件的方法
export default function upload(option) {
  //XMLHttpRequest 對象用於在後臺與伺服器交換數據。
  if (typeof XMLHttpRequest === 'undefined') {
    return;
  }
  //創建XMLHttpRequest對象
  const xhr = new XMLHttpRequest();
  const action = option.action; //上傳的地址

  //XMLHttpRequest.upload 屬性返回一個 XMLHttpRequestUpload對象,用來表示上傳的進度。這個對象是不透明的,但是作為一個XMLHttpRequestEventTarget,可以通過對其綁定事件來追蹤它的進度。
  if (xhr.upload) {
    //上傳進度調用方法,上傳過程中會頻繁調用該方法
    xhr.upload.onprogress = function progress(e) {
      if (e.total > 0) {
        // e.total是需要傳輸的總位元組,e.loaded是已經傳輸的位元組
        e.percent = e.loaded / e.total * 100;
      }
      //調文件上傳時的鉤子函數
      option.onProgress(e);
    };
  }
  // 創建一個FormData 對象
  const formData = new FormData();
  //用戶設置了上傳時附帶的額外參數時
  if (option.data) {
    Object.keys(option.data).forEach(key => {
      // 添加一個新值到 formData 對象內的一個已存在的鍵中,如果鍵不存在則會添加該鍵。
      formData.append(key, option.data[key]);
    });
  }

  formData.append(option.filename, option.file, option.file.name);
  //請求出錯
  xhr.onerror = function error(e) {
    option.onError(e);
  };
  //請求成功回調函數
  xhr.onload = function onload() {
    if (xhr.status < 200 || xhr.status >= 300) {
      return option.onError(getError(action, option, xhr));
    }
    //調用upload.vue文件中的onSuccess方法,將上傳介面返回值作為參數傳遞
    option.onSuccess(getBody(xhr));
  };
  //初始化請求
  xhr.open('post', action, true);

  if (option.withCredentials && 'withCredentials' in xhr) {
    xhr.withCredentials = true;
  }

  const headers = option.headers || {};

  for (let item in headers) {
    if (headers.hasOwnProperty(item) && headers[item] !== null) {
      //設置請求頭
      xhr.setRequestHeader(item, headers[item]);
    }
  }
  //發送請求
  xhr.send(formData);
  return xhr;
}

upload-dragger.vue

<template>
  <!--拖拽上傳時顯示此組件-->
  <div
    class="el-upload-dragger"
    :class="{
      'is-dragover': dragover
    }"
    @drop.prevent="onDrop"
    @dragover.prevent="onDragover"
    @dragleave.prevent="dragover = false"
  >
    <slot></slot>
  </div>
</template>
<script>
  export default {
    name: 'ElUploadDrag',
    props: {
      disabled: Boolean
    },
    inject: {
      uploader: {
        default: ''
      }
    },
    data() {
      return {
        dragover: false
      };
    },
    methods: {
      onDragover() {
        if (!this.disabled) {
          this.dragover = true;
        }
      },
      onDrop(e) {
        if (this.disabled || !this.uploader) return;
        //接受上傳的文件類型(thumbnail-mode 模式下此參數無效),此處判斷該文件是都符合能上傳的類型
        const accept = this.uploader.accept;
        this.dragover = false;
        if (!accept) {
          this.$emit('file', e.dataTransfer.files);
          return;
        }
        this.$emit('file', [].slice.call(e.dataTransfer.files).filter(file => {
          const { type, name } = file;
          //獲取文件名尾碼,與設置的文件類型進行對比
          const extension = name.indexOf('.') > -1
            ? `.${ name.split('.').pop() }`
            : '';
          const baseType = type.replace(/\/.*$/, '');
          return accept.split(',')
            .map(type => type.trim())
            .filter(type => type)
            .some(acceptedType => {
              if (/\..+$/.test(acceptedType)) {
                //文件名尾碼與設置的文件類型進行對比
                return extension === acceptedType;
              }
              if (/\/\*$/.test(acceptedType)) {
                return baseType === acceptedType.replace(/\/\*$/, '');
              }
              if (/^[^\/]+\/[^\/]+$/.test(acceptedType)) {
                return type === acceptedType;
              }
              return false;
            });
        }));
      }
    }
  };
</script>

upload-list.vue

<template>
  <!--這裡主要顯示已上傳文件列表-->
  <transition-group
    tag="ul"
    :class="[
      'el-upload-list',
      'el-upload-list--' + listType,
      { 'is-disabled': disabled }
    ]"
    name="el-list">
    <li
      v-for="file in files"
      :class="['el-upload-list__item', 'is-' + file.status, focusing ? 'focusing' : '']"
      :key="file.uid"
      tabindex="0"
      @keydown.delete="!disabled && $emit('remove', file)"
      @focus="focusing = true"
      @blur="focusing = false"
      @click="focusing = false"
    >
      <img
        class="el-upload-list__item-thumbnail"
        v-if="file.status !== 'uploading' && ['picture-card', 'picture'].indexOf(listType) > -1"
        :src="file.url" alt=""
      >
      <a class="el-upload-list__item-name" @click="handleClick(file)">
        <i class="el-icon-document"></i>{{file.name}}
      </a>
      <label class="el-upload-list__item-status-label">
        <i :class="{
          'el-icon-upload-success': true,
          'el-icon-circle-check': listType === 'text',
          'el-icon-check': ['picture-card', 'picture'].indexOf(listType) > -1
        }"></i>
      </label>
      <i class="el-icon-close" v-if="!disabled" @click="$emit('remove', file)"></i>
      <i class="el-icon-close-tip" v-if="!disabled">{{ t('el.upload.deleteTip') }}</i> <!--因為close按鈕只在li:focus的時候 display, li blur後就不存在了,所以鍵盤導航時永遠無法 focus到 close按鈕上-->
      <el-progress
        v-if="file.status === 'uploading'"
        :type="listType === 'picture-card' ? 'circle' : 'line'"
        :stroke-width="listType === 'picture-card' ? 6 : 2"
        :percentage="parsePercentage(file.percentage)">
      </el-progress>
      <span class="el-upload-list__item-actions" v-if="listType === 'picture-card'">
        <span
          class="el-upload-list__item-preview"
          v-if="handlePreview && listType === 'picture-card'"
          @click="handlePreview(file)"
        >
          <i class="el-icon-zoom-in"></i>
        </span>
        <span
          v-if="!disabled"
          class="el-upload-list__item-delete"
          @click="$emit('remove', file)"
        >
          <i class="el-icon-delete"></i>
        </span>
      </span>
    </li>
  </transition-group>
</template>
<script>
  import Locale from 'element-ui/src/mixins/locale';
  import ElProgress from 'element-ui/packages/progress';

  export default {

    name: 'ElUploadList',

    mixins: [Locale],

    data() {
      return {
        focusing: false
      };
    },
    components: { ElProgress },

    props: {
      files: {
        type: Array,
        default() {
          return [];
        }
      },
      disabled: {
        type: Boolean,
        default: false
      },
      handlePreview: Function,
      listType: String
    },
    methods: {
      parsePercentage(val) {
        return parseInt(val, 10);
      },
      handleClick(file) {
        this.handlePreview && this.handlePreview(file);
      }
    }
  };
</script>

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、當導入ReactiveObjC.framework後,進行編譯時報錯:framework not found xxx 報錯如下圖: 解決辦法: Targets — Build Settings — Search Paths 在 Framework Search Paths 中添加當前framew ...
  • **本篇只列出零碎的jQuery基礎知識點,個人記錄自己的學習進度,無序排列,謹慎查看。** 1.jQuery入口函數的四種寫法2.jQuery與JS遍曆數組的區別3.jQuery符號衝突問題4.jQuery與JS的map遍歷方法5.each方法和map方法的區別6.jQuery各種靜態方法的使用7 ...
  • 一、背景屬性縮寫的格式 1.backgound:背景顏色 背景圖片 平鋪方式 關聯方式 定位方式 2.註意點: 這裡的所有值都可以省略,但是至少需要一個 3.什麼是背景關聯方式 預設情況下,背景圖片會隨著滾動條的滾動而滾動,如果不想這樣,那麼我們可以修改它們的關聯方式 4.格式: backgroun ...
  • css實現: JS代碼一: 註:正則表達式\b會把英文縮寫,譬如I'm拆分成兩個部分,導致輸出為I'M,所以不能使用\b JS代碼二: JS代碼三: JS代碼四: 註:\b:匹配一個單詞邊界,也就是指單詞和空格間的位置。例如, 'erb' 可以匹配"never" 中的 'er',但不能匹配 "ver ...
  • HTTP中get和post的區別 GET 從指定的資源請求數據。 POST 向指定的資源提交要被處理的數據 | | GET | POST | | | | | | 後退/刷新 | 無害的 | 數據會被重新提交 | | 書簽 | 可收藏為書簽 | 不可收藏為書簽 | | 緩存 | 能被緩存 | 不能緩存 ...
  • 事件委托還有一個名字叫事件代理。JavaScript高級程式設計上講:事件委托就是利用事件冒泡,只指定一個事件處理程式,就可以管理某一類型的所有事件。 ...
  • CSS簡介 1.CSS介紹 層疊樣式表(英文全稱:Cascading Style Sheets)是一種用來表現HTML(標準通用標記語言的一個應用)或XML(標準通用標記語言的一個子集)等文件樣式的電腦語言。CSS不僅可以靜態地修飾網頁,還可以配合各種腳本語言動態地對網頁各元素進行格式化。 2.C ...
  • 由於使用的是vue開發,所以就展示一下繪製函數好了,上圖是效果圖 drawMain(drawing_elem, percent, forecolor, bgcolor) { /* @drawing_elem: 繪製對象 @percent:繪製圓環百分比, 範圍[0, 100] @forecolor: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...