微信小程序实现附件(图片/视频)上传及附件(图片/视频)预览

2023-11-04

微信小程序的图片/视频上传功能,小程序官网是提供了相关的API语法。本例使用了 wx.chooseMedia 选择或拍摄图片/视频附件,通过 wx.uploadFile 方法上传至服务器,在需要的地方将服务器存储的附件地址查询出来提供展示预览。预览主要实现了图片的手势缩放及托动,视频的可全屏播放等功能。

本例主要使用了taro及taro ui 组件开发,与小程序官方语言基本兼容,定义了附件的上传及预览taro组件,具体代码如下:

1.部分样式代码(customAnnex.styl):

.at-row {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  width: 100%; }
.at-row__direction--row {
  -webkit-box-orient: horizontal;
  -webkit-box-direction: normal;
  -webkit-flex-direction: row;
  -ms-flex-direction: row;
  flex-direction: row; }
.at-row__direction--column {
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -webkit-flex-direction: column;
  -ms-flex-direction: column;
  flex-direction: column; }
.at-row__direction--row-reverse {
  -webkit-box-orient: horizontal;
  -webkit-box-direction: reverse;
  -webkit-flex-direction: row-reverse;
  -ms-flex-direction: row-reverse;
  flex-direction: row-reverse; }
.at-row__direction--column-reverse {
  -webkit-box-orient: vertical;
  -webkit-box-direction: reverse;
  -webkit-flex-direction: column-reverse;
  -ms-flex-direction: column-reverse;
  flex-direction: column-reverse; }
.at-row__align--start {
  -webkit-align-items: flex-start;
  -ms-flex-align: start;
  align-items: flex-start;
  -webkit-box-align: start; }
.at-row__align--end {
  -webkit-align-items: flex-end;
  -ms-flex-align: end;
  align-items: flex-end;
  -webkit-box-align: end; }
.at-row__align--center {
  -webkit-align-items: center;
  -ms-flex-align: center;
  align-items: center;
  -webkit-box-align: center; }
.at-row__align--stretch {
  -webkit-align-items: stretch;
  -ms-flex-align: stretch;
  align-items: stretch;
  -webkit-box-align: stretch; }
.at-row__align--baseline {
  -webkit-align-items: baseline;
  -ms-flex-align: baseline;
  align-items: baseline;
  -webkit-box-align: baseline; }
.at-row__justify--start {
  -webkit-justify-content: flex-start;
  -ms-flex-pack: start;
  justify-content: flex-start;
  -webkit-box-pack: start; }
.at-row__justify--end {
  -webkit-justify-content: flex-end;
  -ms-flex-pack: end;
  justify-content: flex-end;
  -webkit-box-pack: end; }
.at-row__justify--center {
  -webkit-justify-content: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-pack: center; }
.at-row__justify--between {
  -webkit-justify-content: space-between;
  -ms-flex-pack: justify;
  justify-content: space-between;
  -webkit-box-pack: justify; }
.at-row__justify--around {
  -webkit-justify-content: space-around;
  -ms-flex-pack: distribute;
  justify-content: space-around;
  -webkit-box-pack: space-around; }
.at-row__align-content--start {
  -webkit-align-content: flex-start;
  -ms-flex-line-pack: start;
  align-content: flex-start; }
.at-row__align-content--end {
  -webkit-align-content: flex-end;
  -ms-flex-line-pack: end;
  align-content: flex-end; }
.at-row__align-content--center {
  -webkit-align-content: center;
  -ms-flex-line-pack: center;
  align-content: center; }
.at-row__align-content--between {
  -webkit-align-content: space-between;
  -ms-flex-line-pack: justify;
  align-content: space-between; }
.at-row__align-content--around {
  -webkit-align-content: space-around;
  -ms-flex-line-pack: distribute;
  align-content: space-around; }
.at-row__align-content--stretch {
  -webkit-align-content: stretch;
  -ms-flex-line-pack: stretch;
  align-content: stretch; }
.at-row--no-wrap {
  -webkit-flex-wrap: nowrap;
  -ms-flex-wrap: nowrap;
  flex-wrap: nowrap; }
.at-row--wrap {
  -webkit-flex-wrap: wrap;
  -ms-flex-wrap: wrap;
  flex-wrap: wrap; }
.at-row--wrap-reverse {
  -webkit-flex-wrap: wrap-reverse;
  -ms-flex-wrap: wrap-reverse;
  flex-wrap: wrap-reverse; }

.at-col {
  -webkit-flex: 1;
  -ms-flex: 1;
  flex: 1;
  -webkit-box-flex: 1;
  width: 100%;
  display: block;
  white-space: nowrap;
  -webkit-box-sizing: border-box;
  box-sizing: border-box; }
.at-col-1 {
  -webkit-flex: 0 0 8.33333%;
  -ms-flex: 0 0 8.33333%;
  flex: 0 0 8.33333%;
  -webkit-box-flex: 0;
  max-width: 8.33333%; }
.at-col__offset-1 {
  margin-left: 8.33333%; }
.at-col-2 {
  -webkit-flex: 0 0 16.66667%;
  -ms-flex: 0 0 16.66667%;
  flex: 0 0 16.66667%;
  -webkit-box-flex: 0;
  max-width: 16.66667%; }
.at-col__offset-2 {
  margin-left: 16.66667%; }
.at-col-3 {
  -webkit-flex: 0 0 25%;
  -ms-flex: 0 0 25%;
  flex: 0 0 25%;
  -webkit-box-flex: 0;
  max-width: 25%; }
.at-col__offset-3 {
  margin-left: 25%; }
.at-col-4 {
  -webkit-flex: 0 0 33.33333%;
  -ms-flex: 0 0 33.33333%;
  flex: 0 0 33.33333%;
  -webkit-box-flex: 0;
  max-width: 33.33333%; }
.at-col__offset-4 {
  margin-left: 33.33333%; }
.at-col-5 {
  -webkit-flex: 0 0 41.66667%;
  -ms-flex: 0 0 41.66667%;
  flex: 0 0 41.66667%;
  -webkit-box-flex: 0;
  max-width: 41.66667%; }
.at-col__offset-5 {
  margin-left: 41.66667%; }
.at-col-6 {
  -webkit-flex: 0 0 50%;
  -ms-flex: 0 0 50%;
  flex: 0 0 50%;
  -webkit-box-flex: 0;
  max-width: 50%; }
.at-col__offset-6 {
  margin-left: 50%; }
.at-col-7 {
  -webkit-flex: 0 0 58.33333%;
  -ms-flex: 0 0 58.33333%;
  flex: 0 0 58.33333%;
  -webkit-box-flex: 0;
  max-width: 58.33333%; }
.at-col__offset-7 {
  margin-left: 58.33333%; }
.at-col-8 {
  -webkit-flex: 0 0 66.66667%;
  -ms-flex: 0 0 66.66667%;
  flex: 0 0 66.66667%;
  -webkit-box-flex: 0;
  max-width: 66.66667%; }
.at-col__offset-8 {
  margin-left: 66.66667%; }
.at-col-9 {
  -webkit-flex: 0 0 75%;
  -ms-flex: 0 0 75%;
  flex: 0 0 75%;
  -webkit-box-flex: 0;
  max-width: 75%; }
.at-col__offset-9 {
  margin-left: 75%; }
.at-col-10 {
  -webkit-flex: 0 0 83.33333%;
  -ms-flex: 0 0 83.33333%;
  flex: 0 0 83.33333%;
  -webkit-box-flex: 0;
  max-width: 83.33333%; }
.at-col__offset-10 {
  margin-left: 83.33333%; }
.at-col-11 {
  -webkit-flex: 0 0 91.66667%;
  -ms-flex: 0 0 91.66667%;
  flex: 0 0 91.66667%;
  -webkit-box-flex: 0;
  max-width: 91.66667%; }
.at-col__offset-11 {
  margin-left: 91.66667%; }
.at-col-12 {
  -webkit-flex: 0 0 100%;
  -ms-flex: 0 0 100%;
  flex: 0 0 100%;
  -webkit-box-flex: 0;
  max-width: 100%; }
.at-col__offset-12 {
  margin-left: 100%; }
.at-col__align--top {
  -webkit-align-self: flex-start;
  -ms-flex-item-align: start;
  align-self: flex-start; }
.at-col__align--bottom {
  -webkit-align-self: flex-end;
  -ms-flex-item-align: end;
  align-self: flex-end; }
.at-col__align--center {
  -webkit-align-self: center;
  -ms-flex-item-align: center;
  align-self: center; }
.at-col--auto {
  max-width: initial;
  word-break: keep-all; }
.at-col--wrap {
  white-space: normal;
  word-wrap: break-word; }

.annex {
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  visibility: hidden;
  -webkit-transition: visibility 200ms ease-in;
  -o-transition: visibility 200ms ease-in;
  transition: visibility 200ms ease-in;
  z-index: 1000;
}
.annex--active {
  visibility: visible;
}
.annex--active .annex__overlay,
.annex--active .annex__container {
  opacity: 1;
}
.annex__overlay {
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  position: absolute;
  background-color: rgba(0, 0, 0, 0.3);
}
.annex__container {
  position: absolute;
  top: 50%;
  left: 50%;
  -webkit-transform: translate(-50%, -50%);
  -ms-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
  width: 600rpx;
  max-height: calc(100vh - 300rpx);
  overflow: hidden;
  border-radius: 12rpx;
}
.annex__overlay, .annex__container {
  opacity: 0;
  -webkit-transition: opacity 200ms ease-in;
  -o-transition: opacity 200ms ease-in;
  transition: opacity 200ms ease-in;
}


.at-relative {
  position: relative;
}
.at-col-class {
  width: 222rpx !important;
  height: 222rpx !important;
  margin: 5rpx;
  text-align: center;
  border: 2rpx #d6e4ef solid;
  position: relative;
  display: block;
  white-space: nowrap;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.at-col-img {
  width: 218rpx !important;
  height: 218rpx !important;
}
.at-col-close {
  width: 36rpx;
  height: 36rpx;
  position: absolute;
  top: 2rpx;
  right: 2rpx;
  border-radius: 100%;
  background-color: #ffffff;
  z-index: 99;
}
.at-col-add-img {
  width:47rpx;
  height:47rpx;
  margin: 69rpx 85rpx auto 85rpx;
}
.at-col-add-text {
  margin-top: 30rpx;
  color: #999999;
}
.resultFont {
  font-size:32rpx;
  margin-top:30rpx;
}
.count_color {
  color: #CCCCCC;
}
.hide {
  display: none !important;
}
.show {
  display: block;
}
.w100 {
  width: 100%;
}

2.部分逻辑代码(customAnnex.tsx):

import Taro, { Config, Component } from '@tarojs/taro'
import './customAnnex.styl'
import {View, Image, Video} from '@tarojs/components'
import annexAdd from '../../../asset/images/index/annex-add.png'
import annexClose from '../../../asset/images/index/annex-close.png'
import blankImg from '../../../asset/images/index/blank.png'
import TaroReq from '../../../constants/tokenHandle.js'
import api from "../../../constants/api.json"
export default class customAnnex extends Component {

    /**自定义的组件
     * 指定config的类型声明为: Taro.Config
     *
     * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型
     * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string
     * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型
     */
    /**
     * *************重要API*************
     * 作用:附件的上传
     * 其余的需要的时候再扩展
     * 使用:1、导入组件:import Cannex from '../../components/custom-annex/customAnnex'
     *      2、使用组件: <Cannex></Cannex>
     *
     */
    config: Config = { };

    maxNum: number = 3; // 最大附件上传数量
    maxWidth: number = 300; // 最大预览宽度

    constructor(props) {
        super(props);
        this.state = {
          annexFiles: [], // 附件信息列表
          addShow: true, // 是否展示添加设备
          isOpened: false, // 展示的附件区域
          annexShow: null, // 展示的附件内容
          touch: {
            distance: 0,
            scale: 1,
            baseWidth: null,
            baseHeight: null,
            scaleWidth: null,
            scaleHeight: null,
            startX: null,
            startY: null,
            moveTop: null,
            moveLeft: null,
            previousTwoFinger: false //此变量防止双指离开时,后离开的手指会触发图片移动
          }
        };
    }

    componentDidMount() {
      try {
        setTimeout(() => {
          this.requstAnnex();
        }, 500);
      } catch (error) { }
    }

    //输入框赋值
    touchSetHandle(valueMap: object) {
      if (valueMap) {
        for (let key in valueMap) {
          this.state.touch[key]=valueMap[key];
        }
        this.setState({
          touch: this.state.touch
        });
      }
    }

    // 结束
    touchEndHandle() {
      setTimeout(()=>{
        this.touchSetHandle({
          previousTwoFinger: false
        })
      }, 1000);
    }

    touchStartHandle(e) {
      // 单手指缩放开始
      if (e.touches.length == 1) {
        if (!this.state.touch.previousTwoFinger){
          let startX = e.touches[0].clientX;
          let startY = e.touches[0].clientY;
          this.touchSetHandle({ startX: startX, startY: startY });
        }
      } else {
        // 注意touchstartCallback 真正代码的开始
        // 当两根手指放上去的时候,就将distance 初始化。
        let xMove = e.touches[1].clientX - e.touches[0].clientX;
        let yMove = e.touches[1].clientY - e.touches[0].clientY;
        let distance = Math.sqrt(xMove * xMove + yMove * yMove);
        this.touchSetHandle({ distance: distance });
      }
    }

    touchMoveHandle(e) {
      let touch = this.state.touch;
      // 单手指移动
      if (e.touches.length == 1) {
        // 一只手指触摸触发移动
        if (!this.state.touch.previousTwoFinger){
          let moveX = this.state.touch.moveLeft + e.touches[0].clientX - this.state.touch.startX;
          let moveY = this.state.touch.moveTop + e.touches[0].clientY - this.state.touch.startY;
          let deffWidth = this.state.touch.baseWidth - this.state.touch.scaleWidth;
          let deffHeight = this.state.touch.baseHeight - this.state.touch.scaleHeight;
          let calcFun = function (deff, move) {
            if (deff >= 0 ) {
              if (deff <= move) {
                move = deff;
              } else {
                move = move > 0 ? move : 0;
              }
            } else {
              if (move <= deff) {
                move = deff;
              } else {
                move = move < 0 ? move : 0;
              }
            }
            return move;
          };
          moveX = calcFun(deffWidth, moveX);
          moveY = calcFun(deffHeight, moveY);
          let startX = e.touches[0].clientX;
          let startY = e.touches[0].clientY;
          this.touchSetHandle({ moveLeft: moveX, moveTop: moveY, startX: startX, startY: startY });
        }
      } else {
        let xMove = e.touches[1].clientX - e.touches[0].clientX;
        let yMove = e.touches[1].clientY - e.touches[0].clientY;
        // 新的 ditance
        let distance = Math.sqrt(xMove * xMove + yMove * yMove);
        let distanceDiff = distance - touch.distance;
        let newScale = touch.scale + 0.005 * distanceDiff;
        // 为了防止缩放得太大,所以scale需要限制,同理最小值也是
        if (newScale >= 4) {
          newScale = 4;
        }
        if (newScale <= 1) {
          newScale = 1;
        }
        let scaleWidth = (newScale * touch.baseWidth).toFixed(2);
        let scaleHeight = (newScale * touch.baseHeight).toFixed(2);
        // 赋值 新的 => 旧的
        this.touchSetHandle({
          distance: distance,
          scale: newScale,
          scaleWidth: scaleWidth,
          scaleHeight: scaleHeight,
          diff: distanceDiff,
          previousTwoFinger: true
        });
      }
    }

    imgLoad(e) {
      // 这个api是<image>组件的api类似<img>的onload属性
      let width = this.maxWidth; // 预览最大宽度
      let height = (width / e.detail.width * e.detail.height).toFixed(2);
      this.touchSetHandle({
        baseWidth: width,
        baseHeight: height,
        scaleWidth: width,
        scaleHeight: height,
        moveTop: 0,
        moveLeft: 0,
        previousTwoFinger: false
      });
    }

    /*控制附件新增按钮*/
    setAddShow() {
      if (this.state.annexFiles && this.state.annexFiles.length >= this.maxNum) {
        this.setState({
          addShow: false
        });
      } else {
        this.setState({
          addShow: true
        });
      }
    }

    /*添加附件*/
    addAnnex() {
      let that = this;
      wx.chooseMedia({
        count: 1,
        mediaType: ['image','video'],
        sourceType: ['album', 'camera'],
        sizeType: ['original', 'compressed'],
        maxDuration: 20,
        camera: 'back',
        success(res) {
          if (res && res.tempFiles && res.tempFiles.length > 0) {
            let annexFiles: any[] = [...that.state.annexFiles];
            let image = res.type=="video" ? res.tempFiles[0].thumbTempFilePath : res.tempFiles[0].tempFilePath;
            annexFiles.push({ type: res.type, path: res.tempFiles[0].tempFilePath, iconPath: image });
            that.setState({
              annexFiles: annexFiles
            }, () => {
              that.setAddShow();
            });
          }
        },
        fail() {
          Taro.showToast({
            title: '附件添加失败!',
            icon: 'none',
            duration: 3000
          })
        }
      });
    }

    /*删除附件*/
    delAnnex(index) {
      let annexFiles: any[] = this.state.annexFiles.filter((val, i) => i != index);
      this.setState({
        annexFiles: annexFiles
      }, () => {
        this.setAddShow();
      });
    }

    /*展示附件*/
    clickAnnexShow(annex) {
      if (annex && annex.type) {
        this.setState({
          annexShow: annex,
          isOpened: true
        });
      }
    }

    /*控制展示*/
    clickAnnexHandle(flag) {
      this.setState({
        annexShow: null,
        isOpened: flag
      });
    }

    /*请求附件*/
    requstAnnex() {
      const { isUpload, idName, idValue } = this.props;
      if(isUpload) { return; }
      this.setState({
        addShow: false,
      });
      if(idName && idValue) {
        let formData: object = {page: 1, rows: 100};
        formData[idName] = idValue;
        TaroReq.get(api.url + api.attachMentList, formData).then(res => {
          if (res.statusCode == 200) {
            const data = res.data.rows || [];
            let annexFiles: any = [];
            for (let index=0; index<data.length; index++) {
              let type = data[index].type=="mp4" ? "video":data[index].type;
              annexFiles.push({ type: type, path:  api.url + data[index].path, iconPath:  api.url + data[index].iconPath });
            }
            this.setState({
              annexFiles: annexFiles
            });
          }
        });
      }
    }

  //多附件上传
  uploadMuliteFile(data) {
    let that = this,
      i = data.i ? data.i : 0,
      success = data.success ? data.success : 0,
      fail = data.fail ? data.fail : 0;
    wx.uploadFile({
      header: data.header,
      url: data.url,
      filePath: data.path[i],
      name: 'file',
      formData: data.formData,
      success: (res) => {
        success++;
      },
      fail: (res) => {
        fail++;
      },
      complete: () => {
        i++;
        if (i == data.path.length) { //当图片传完时,停止调用
          Taro.hideLoading();
          const tip = fail > 0 ? (success+'个附件上传成功,' + fail + '个附件上传失败!') : ('附件上传成功!');
          Taro.showToast({
            title: tip,
            icon: 'none',
            duration: 3000
          });
          if (data.callFun && typeof data.callFun == "function") {
            data.callFun();
          }
        } else { //若图片还没有传完,则继续调用函数
          data.i = i;
          data.success = success;
          data.fail = fail;
          that.uploadMuliteFile(data);//递归,回调自己
        }
      }
    });
  }

    /*上传附件*/
    uploadAnnex(params) {
      const annexFilePaths: any[] = [];
      for (let i = 0; i < this.state.annexFiles.length; i++) {
        annexFilePaths.push(this.state.annexFiles[i].path);
      }
      if (annexFilePaths && annexFilePaths.length > 0) {
        Taro.showLoading({
          title: '附件上传中...',
          mask: true
        });
        let reqToken = Taro.getStorageSync('access_token_' + 'TM-WXapplet');
        let reqHeader = { 'content-type': params.contentType || 'multipart/form-data', Authorization: 'Bearer ' + reqToken };
        this.uploadMuliteFile({
          header: reqHeader,
          url: params.url || (api.url + api.attachMentUpload),
          path: annexFilePaths,
          formData: params.formData || null,
          callFun: params.callFun || null
        });
      }
    }

    render() {
      const { isUpload, idName, idValue } = this.props;
      const { annexFiles, addShow, isOpened, annexShow, touch } = this.state;
      return (
        <View>

          {isUpload
            ? <View>

                <View className="at-row resultFont">
                  <View className="at-col at-col-11">上传附件</View>
                  <View className="at-col at-col-1 count_color">{annexFiles.length}/{this.maxNum}</View>
                </View>

                <View className="at-row at-relative at-row--wrap resultFont">
                  {annexFiles.map((annexFile, index) =>
                    <View className="at-col-class">
                      <Image className="at-col-img" mode='aspectFit' src={annexFile.iconPath} onClick={this.clickAnnexShow.bind(this, annexFile)}></Image>
                      <Image className="at-col-close" src={annexClose} onClick={this.delAnnex.bind(this, index)}></Image>
                    </View>
                  )}
                  <View className="at-col-class {{addShow?'show':'hide'}}" onClick={this.addAnnex.bind(this)}>
                    <Image className="at-col-add-img" src={annexAdd}></Image>
                    <View className="at-col-add-text">视频/图片</View>
                  </View>
                </View>

              </View>
            : <View className="at-row at-relative at-row--wrap resultFont">
                {annexFiles.map((annexFile) =>
                  <View className="at-col-class">
                    <Image className="at-col-img" mode='aspectFit' src={annexFile.iconPath} onClick={this.clickAnnexShow.bind(this, annexFile)}></Image>
                  </View>
                )}
              </View>
          }

          <View className="annex {{isOpened?'annex--active':''}}">
            <View className="annex__overlay" onClick={this.clickAnnexHandle.bind(this, false)}></View>
            {(annexShow.type && annexShow.path)
              ? <View className="annex__container">
                {annexShow.type=="video"
                  ? <View style="position: relative;">
                    <Video
                      objectFit="cover"
                      src={annexShow.path}
                      controls={true}
                      autoplay={true}
                      initialTime={0}
                      loop={false}
                      muted={false}
                    />
                    <Image className="at-col-close" src={annexClose} onClick={this.clickAnnexHandle.bind(this, false)}></Image>
                  </View>
                  : <View style="position: relative; min-height: {{touch.baseHeight}}px;">
                    <Image mode='aspectFit' src={annexShow.path}
                           onTouchStart={this.touchStartHandle.bind(this)}
                           onTouchMove={this.touchMoveHandle.bind(this)}
                           onTouchEnd={this.touchEndHandle.bind(this)}
                           onLoad={this.imgLoad.bind(this)}
                           style="position: absolute; width: {{touch.scaleWidth}}px; height: {{touch.scaleHeight}}px; top: {{touch.moveTop}}px; left: {{touch.moveLeft}}px;"></Image>
                    <Image className="at-col-close" src={annexClose} onClick={this.clickAnnexHandle.bind(this, false)}></Image>
                  </View>
                }
                </View>
              : <View className="annex__container"><Image className="at-col-img" mode='scaleToFill' src={blankImg}></Image></View>
            }
          </View>

        </View>
      )
    }
}

3.其他组件引用代码:

上传的时候调用:

import Cannex from '../../components/custom-annex/customAnnex' // 引入组件

cannex: any;
refCannex = (node) => { this.cannex = node };  // 获取组件实例

cannex.uploadAnnex({              // 调用组件上传方法
     formData: { 'xxx': 'xxx'},
     callFun: function () { }
});

<Cannex ref={this.refCannex} isUpload={true}></Cannex> // 组件视图

上传的时候效果:

预览时候的调用:

import Cannex from '../../components/custom-annex/customAnnex' // 引入组件

<Cannex isUpload={false} idName='xxx' idValue={xxx} ></Cannex> // 预览加载组件

预览时候的效果:

注意:wx.chooseMedia在临时拍照上传时,文件可能为 .unknown 后缀,需要服务器端处理替换为.jpg处理。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

微信小程序实现附件(图片/视频)上传及附件(图片/视频)预览 的相关文章

  • Javascript 函数查找数字的倍数

    创建一个名为的函数multiplesOf 它将接受两个参数 第一个参数是数字数组 第二个参数是数字 该函数应返回一个新数组 该数组由参数数组中的每个数字组成 该数字是参数数字的倍数 So multiplesOf 5 6 7 8 9 10 3
  • Chart.js 在初始化时设置活动段

    我正在使用 Chart js v2 并且尝试在加载图表时模拟圆环图上某个段的 悬停状态 因此看起来有一个部分已突出显示 我已经搜索和梳理了代码一天 但找不到一个好的方法来做到这一点 提前致谢 设置片段的悬停样式有点令人困惑 因为它没有真正记
  • JavaScript 中的埃拉托斯特尼筛法对大量数据无限运行

    我一直在尝试写埃拉托斯特尼筛法 http en wikipedia org wiki Sieve of EratosthenesJavaScript 中的算法 基本上我只是按照以下步骤操作 创建从 2 到 n 1 的连续整数列表 令第一个素
  • 为什么我的淘汰单选按钮在另一个具有点击绑定的元素内时会失败?

    我有一个单选按钮列表 我想要点击 li 他们还检查单选按钮 这一切都有效 直到我放了一个name单选元素上的属性 然后我的代码停止工作 我的代码如下所示 ul li li ul li
  • IE从哪个版本开始支持Object.create(null)?

    您可以通过多种方式在 JavaScript 中创建对象 creates an object which makes the Object prototype of data var data1 new Object Object liter
  • 如何在react-bootstrap中禁用表单提交的

    在下面的代码片段中 我有许多文本类型的输入表单 如果用户点击 我似乎会得到相同的合成事件 就像他们按下提交按钮一样 我想忽略作为表单提交 只允许一个人按下 提交 按钮 我删除了一些表单组以减少示例 在所有情况下 按钮或 ENTER 键 e
  • Chrome 中的性能问题

    我目前正在从事一个相对较大的项目 使用 AngularJs 构建 应用程序的一部分是一个表单 您可以向其中添加任意数量的页面 不幸的是 添加了很多不必要的垃圾 即表示表单模型的对象可能会变得非常大 在某些时候 Chrome 基本上无法处理它
  • 如何将内联 JavaScript 与 Express/Node.js 中动态生成的内容分开?

    对于具有几年 Web 开发经验但没有找到答案的人来说 这是一个有点菜鸟的问题程序员堆栈交换 or Google 我决定在这里问一下 我在用Express网络框架Node js 但这个问题并不特定于任何 Web 框架或编程语言 以下是从数据库
  • 图像无法在带有 DOM 的 IE 中加载:控制台中的 7009 错误(无法解码)

    当在 IE 中的单个页面上加载许多图像时 在 IE11 中重现 其中一些图像开始加载失败 并在控制台中出现类似以下警告的内容 DOM7009 无法解码 URL 处的图像 某些唯一的 url 当我查看网络流量时 似乎确实从服务器收到了每个图像
  • 在打字稿中导入 json

    我是 typescript 的新手 在我的项目中 我们使用 typescript2 在我的要求之一中 我需要导入 json 文件 所以我创建了 d ts 文件如下 test d ts declare module json const va
  • 使用 CSS 或 Javascript 填充动画

    我只是想知道是否可以使用 CSS 或 javascript 创建填充动画 基本上我想创建一个填充动画 如下图所示 http i40 tinypic com eit6ia png http i40 tinypic com eit6ia png
  • 在 HTML5 画布中,如何用我选择的背景遮盖图像?

    我试图用画布来实现这一点 globalCompositeOperation 但没有运气 所以我在这里问 这里有类似的问题 但我没有在其中找到我的案例 我的画布区域中有图层 从下到上的绘制顺序 画布底座填充纯白色 fff 用fillRect
  • 检查 jQuery 1.7 中是否存在基于文本的选择选项

    所以我有以下 HTML 片段
  • 为什么我们在打字稿中使用 HTMLInputElement ?

    我们为什么使用 document getElementById ipv as HTMLInputElement value 代替 document getElementById ipv value 功能getElementById返回具有类
  • 正则表达式 - 从 markdown 字符串中提取所有标题

    我在用灰质 https www npmjs com package gray matter 以便将文件系统中的 MD 文件解析为字符串 解析器产生的结果是这样的字符串 n Clean er ReactJS Code Conditional
  • 在移动设备上滚动

    这个问题更多的是一个建议研究 我确实希望它对其他人有帮助 并且它不会关闭 因为我不太确定在哪里寻求有关此事的建议 在过去的 6 个月里 我一直在进行移动开发 我有机会处理各种设备上的各种情况和错误 最麻烦的是滚动问题 当涉及到在网站的多个区
  • 使用 Vue 的多模式组件

    我在 Vue 中实现动态模式组件时遇到问题 A common approach I follow to display a set of data fetched from the db is I dump each of the rows
  • 在 Shopify 商店中嵌入 Vue 组件

    在产品页面中 我尝试显示自定义 Vue 组件 为简洁起见 该组件根据给定的产品 ID 显示 Firebase 数据库中的一些信息 我最初尝试将其制作为 Shopify 应用程序 以便我可以访问他们的 API 我实现了 OAuth 并且可以检
  • 在 CKEditor 中设置字体大小和字体系列

    我正在使用 ckeditor 我想问一下这个插件如何设置font family和font size 我尝试过使用 CKEDITOR config font defaultLabel Arial CKEDITOR config fontSiz
  • 如何在react-highcharts中使用图表工具提示格式化程序?

    如何使用图表工具提示格式化程序 我正在使用高图表的反应包装器 我有这样的配置 const CHART CONFIG tooltip formatter tooltip gt var s b this x b each this points

随机推荐

  • 论文阅读:Improved Denoising Diffusion Probabilistic Models

    本文是对ddpm简单的修改 但是能提高ddpm的性能 论文下载地址 https proceedings mlr press v139 nichol21a html 我们发现反向过程中可学习的方差允许一个数量级的采样 样本质量的差异可以忽略不
  • AOP中的代理对象

    先要了解spring容器初始化过程中Bean的生命周期 如果spring在启动过程中加上了 Transiation注释的话 spring会生成一个代理对象 来做事务控制 我们从容器中取出来的对象是代理对象 代理对象在执行方法之前会开启事务管
  • 浏览器兼容性问题解决

    样式兼容性 css 方面 解决方法 可以通过 Normalize css 抹平差异 也可以定制自己的 reset css 全局重置样式 firefox浏览器不支持cursor hand显示手型 解决方法 统一使用cursor pointer
  • C# 用Microsoft.Office.Interop.PowerPoint类库操作PPT

    前言 最近由于项目需求 需要使用此类库对PPT进行操作 1 引用 Microsoft Office Interop PowerPoint和 Microsoft Office Core 2 PPT操作 打开PPT PPT应用程序变量 Appl
  • vue如何获取当前路径url及参数

    有时候开发需要获取当前url的参数 完整url可以用 window location href 路由路径可以用 this route path 路由路径参数 this route params params是参数名称 this route
  • Django-模型层(单表操作)

    目录 1 ORM简介 2 单表操作 2 1创建表 2 2添加表纪录 2 3查询表纪录 2 4删除表纪录 2 5修改表纪录 1 ORM简介 MVC或者MVC框架中包括一个重要的部分 就是ORM 它实现了数据模型与数据库的解耦 即数据模型的设计
  • 2019 前端框架对比及评测

    Jacek Schae 原作 授权 LeanCloud 翻译 我们将基于 RealWorld 示例应用对比前端框架 RealWorld 示例应用的特点 RealWorld 应用 比待办事项类应用更复杂 通常待办事项类应用不足以传达足够多的知
  • 最详细的Python接单思路和方法

    首先讲下python爬虫怎么接单挣钱 Python爬虫挣钱方法大致如下 有些需要的技术并不难 这种大部分都可以做 有些可能对技术要求比较高 门槛相对就高一点 爬虫技术挣钱方法1 接外包爬虫项目 这是网络爬虫最通常的的挣钱方式 通过外包网站
  • LeetCode-环形链表-简单

    标题 141环形链表 简单 题目 给你一个链表的头节点 head 判断链表中是否有环 如果链表中有某个节点 可以通过连续跟踪 next 指针再次到达 则链表中存在环 为了表示给定链表中的环 评测系统内部使用整数 pos 来表示链表尾连接到链
  • IDEA中配置Maven常见问题每次都需要更改setting设置,否则使用默认Maven,完美解决Maven的配置问题!

    废话不说 直接上图 你是否也遇到这种情况呢 创建maven无模板项目时 maven总是idea自带 如何解决呢 点开系统setting 1 取消默认打开上一次项目 2 重启IDEA 在全局设置就可以了 完美解决 创作不易 问题解决的给个鼓励
  • 520,我会处理回文数了,你会了么?(dp+中心扩散)

    给定一个字符串 s 找到 s 中最长的回文子串 你可以假设 s 的最大长度为 1000 示例 1 输入 babad 输出 bab 注意 aba 也是一个有效答案 示例 2 输入 cbbd 输出 bb 方法一 暴力匹配 Brute Force
  • git commit-m 与 git commit -a -m

    转自 https segmentfault com q 1010000005900988 字面解释的话 git commit m用于提交暂存区的文件 git commit am用于提交跟踪过的文件 要理解它们的区别 首先要明白git的文件状
  • python二级第一套答案

    python二级第一套答案 46 考生文件夹下存在3个Python源文件 分别对应3个问题 1个文本文件 作为本题目输入数据 请按照源文件内部说明修改代码 实现以下功能 命运 是著名科幻作家倪匡的作品 这里给出 命运 的一个网络版本文件 文
  • 华为笔记本转轴坏了修复指南记录

    华为笔记本转轴坏了修复指南记录 前言 记录一下 华为笔记本MeteBookD MRC W50 转轴破裂导致屏幕翻盖时 转轴出开裂 记录维修全过程 由于之前电脑坏掉的图片找不到了 我这里就先大致描述一下吧 看下图 上面我就是简单的描述了一些我
  • 行列式的英文翻译

    行列式的英文翻译 行列式的英文翻译是 determinant 行列式的英文定义为 从定义可以看出 只有方阵才会有行列式的定义
  • applicationContext.xml文件如何调用外部properties等配置文件

  • 记一次计算机20春招的过程

    今年秋招没有参加 因为考研 等到考研成绩出来 发现无缘复试 便开始准备春招 基本是从2月初就开始准备了 刚开始没有确定企业类型 胡乱投了一下 有百度大厂等 最后还是决定投银行 对于女生来说 还是比较稳定 主要投了中信银行 上海银行 苏州银行
  • C++继承和多态

    C 继承和多态 继承 继承的本质 代码的复用 在基类中给所有派生类提供统一的虚函数接口 让派生类进行重写 然后就可以使用多态了 类和类的关系 a part of 一部分关系 继承 a kind of 一种的关系 继承引入了一些概念 基类 父
  • es6~解构赋值

    ES6允许按照一定模式 从数组和对象中提取值 对变量进行赋值 这被称为解构 以前 为变量赋值 只能直接指定值 let a 1 let b 2 let c 3 ES6 允许写成下面这样 let a b c 1 2 3 上面代码表示 可以从数组
  • 微信小程序实现附件(图片/视频)上传及附件(图片/视频)预览

    微信小程序的图片 视频上传功能 小程序官网是提供了相关的API语法 本例使用了 wx chooseMedia 选择或拍摄图片 视频附件 通过 wx uploadFile 方法上传至服务器 在需要的地方将服务器存储的附件地址查询出来提供展示预