小程序上传文字、小程序上传语音、小程序上传图片组件

设计图如下:

以下代码基于此图

公共样式app.wxss如下

/**app.wxss**/

/* .container {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding: 200rpx 0;
  box-sizing: border-box;
}  */
@import '/iconfont/iconfont.wxss';
@import "/miniprogram_npm/vant-weapp/common/index.wxss";
@import '/lib/weui.wxss';

page {
  padding-bottom: 100rpx;
}

.container {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-sizing: border-box;
}

image {
  width: 100%;
  height: 100%;
}

view {
  font-family: PingFangSC-Regular;
}

.title1 {
  font-size: 32rpx;
  font-weight: 400;
  color: rgba(50, 50, 50, 1);
  line-height: 44rpx;
}

.title2 {
  font-size: 26rpx;
  font-weight: 400;
  color: rgba(50, 50, 50, 1);
  line-height: 46rpx;
}

.title3 {
  font-size: 26rpx;
  font-weight: 400;
  color: rgba(50, 50, 50, 1);
  line-height: 46rpx;
}

.left-text {
  margin-right: 20rpx;
}

.right-text {
  margin-left: 20rpx;
}

.left-10 {
  margin-right: 10rpx;
}

.right-10 {
  margin-left: 10rpx;
}

.detail-text {
  font-size: 26rpx;
  font-weight: 400;
  color: rgba(255, 255, 255, 1);
  line-height: 60rpx;
  opacity: 0.8;
}

.head-img {
  width: 90rpx;
  height: 90rpx;
  border-radius: 50%;
}

.border {
  width: 650rpx;
  height: 2rpx;
  background: rgba(247, 247, 247, 1);
}

.phcolor {
  color: rgba(0, 34, 34, 0.5);
  font-size: 26rpx;
  font-weight: 400;
  line-height: 36rpx;
}

.box-top {
  height: 320rpx;
  /* background: #3a84ff; */
}

/* .box-bottom {
  padding: 0 30rpx;
} */

.panel-title {
  font-size: 32rpx;
  font-weight: 600;
}
.margin-title{
  margin: 40rpx 20rpx 20rpx 20rpx;
}
.gray{
  color: rgba(50, 50, 50, 0.7);
}
.van-tabbar-item__icon image {
  width: 54rpx !important;
  height: 54rpx !important;
}
.van-popup {
  /* background: transparent !important; */
  background:rgba(0,0,0,0.5) !important;
  }

1、suggest页面由三个组件组成。文字回复组件、语音回复组件、上传图片组件。以下是suggest页面具体代码

suggest.wxml


<view class='{{showVoiceBtn?"scroll-box":""}}' wx:if='{{type === "reply"}}'>

  <i-recordPanel title='文字回复' placeholder='请填写您的回复' disabled='{{false}}' bind:getSuggest="getSuggest" left='0'></i-recordPanel>
  <uploads-voice title='语音回复' bind:showVoice='showVoice' voiceList='{{voiceList}}' bind:getVoiceList='getVoiceList' voiceNumber='{{5-voiceList.length}}'></uploads-voice>
  <uploads-img bind:getUploaderList="getUploaderList" uploaderNum="{{5-uploaderNowNum}}" uploaderList='{{uploaderList}}'></uploads-img>
</view>
<view wx:else>
  <i-recordPanel title='{{title}}' placeholder='{{placeholder}}' disabled='{{false}}' bind:getSuggest="getSuggest" left='0'></i-recordPanel>
  <transfer wx:if="{{isInquery}}" bind:getTransfer="getTransfer"></transfer>
</view>
<commit-btn title='{{btnText}}' bind:commit='commit'></commit-btn>
<van-popup show="{{ showVoiceBtn }}" position="bottom" bind:close="onClose" catchtouchmove="true" overlay='{{false}}'>
  <voice bind:getStatus='getStatus' bind:showVoice="showVoice" bind:getVoiceList='getVoiceList' voiceList='{{voiceList}}' luStatu='{{luStatu}}'></voice>
</van-popup>

suggest.wxss

page {
  line-height: 1 !important;
}

.scroll-box {
  padding-bottom: 520rpx;
}

.btn {
  width: 100%;
  position: fixed;
  bottom: 0;
  right: 0;
  z-index: 999;
}

.van-button--large {
  height: 100rpx !important;
  line-height: 100rpx !important;
}

suggest.js.(注:这里的图片地址列表和语音列表都要先异步上传转化链接)

// pages/doctor/suggest/index.js
const app = getApp()
Page({

  /**
   * 页面的初始数据
   */
  data: {
    suggest: '',
    type: '',
    systemInfo: {},
    userInfo: {},
    isInquery: false,
    isNeed: false,
    btnText: '',
    title: '',
    placeholder: '',
    uploaderList: [],
    uploaderUrlArray: [],
    uploaderNowNum: 0,
    voiceList:[],
    uploaderVideoUrlArray:[],
    showVoiceBtn:false,
    closeStatus:true
  },

  //获取上传图片
  getUploaderList(e) {
    this.setData({
      uploaderList: e.detail.uploaderList,
      uploaderNowNum: e.detail.uploaderNowNum
    })
  },
  onClose(){
    this.setData({
      luStatu: false,
      showVoiceBtn:false
    })
  },
  // 获取语音列表
  getVoiceList(e) {
    this.setData({
      voiceList: e.detail.voiceList
    })
  },
  // 补充图片
  addImg(callBack) {
    // 先处理
    new Promise((res, jec) => {
      var uploaderList = this.data.uploaderList; //原数据
      this.data.uploaderUrlArray = []
      var proArr = []
      for (let i = 0; i < uploaderList.length; i++) {
        var pro = new Promise((res1, rej1) => {
          this.uploadImage(uploaderList[i], () => {
            res1()
          })
        })
        proArr.push(pro)
      }
      Promise.all(proArr).then(() => {
        res()
      })
    }).then(() => {
      callBack()
    })


  },
  // 补充语音
  addVoice(callBack) {
    console.log('this.data.voiceList', this.data.voiceList)
    new Promise((res, jec) => {
      var voiceList = this.data.voiceList; //原数据
      this.data.uploaderVoiceUrlArray = []
      var proArr = []
      for (let i = 0; i < voiceList.length; i++) {
        var pro = new Promise((res1, rej1) => {
          console.log('voiceList[i].audioUrl', voiceList[i].audioUrl)
          this.uploadVoice(voiceList[i].audioUrl, voiceList[i].audioSeconds, () => {
            res1()
          })
        })
        proArr.push(pro)
      }
      Promise.all(proArr).then(() => {
        res()
      })
    }).then(() => {
      callBack()
    })
  },
  //上传语音获取地址
  uploadVoice(tempFilePath, audioSeconds, callBack) {
    var that = this
    app.http.get(`/cl-system/medias/uploadToken?mimeType=audio/mpeg&type=VOICE`).then(result => {
      wx.uploadFile({
        url: 'https://up.qbox.me/',
        filePath: tempFilePath,
        name: 'file',
        formData: {
          'token': result.data.token,
          'key': result.data.fileName
        },
        success: function (res) {
          var data = res.data
          that.data.uploaderVideoUrlArray.push({ 'audioUrl': result.data.fileUrl, 'audioSeconds': audioSeconds })
          console.log('that.data.uploaderVideoUrlArray', that.data.uploaderVideoUrlArray)
          callBack()
        }
      })
    })
  },
  /**
  * 上传图片获取地址
  */
  uploadImage(tempFilePath, callBack) {
    var that = this
    app.http.get(`/cl-system/medias/uploadToken?mimeType=image/jpeg&type=IMAGE`).then(result => {
      wx.uploadFile({
        url: 'https://up.qbox.me/',
        filePath: tempFilePath,
        name: 'file',
        formData: {
          'token': result.data.token,
          'key': result.data.fileName
        },
        success: function (res) {
          var data = res.data
          that.data.uploaderUrlArray.push(result.data.fileUrl)
          callBack()
        }
      })
    })
  },
  // 是否显示录音按钮
  showVoice(e) {
    this.setData({
      showVoiceBtn: e.detail.showVoiceBtn
    })
  },
  // 是否转诊
  getTransfer(e) {
    this.setData({
      isNeed: e.detail.isNeed
    })
  },
  toTransfer() {
    if (this.data.isNeed) {
      wx.showLoading({
        title: '加载中...',
        mask: true
      })
      let data = {
        consultId: this.data.consultId
      }
      app.http.put(`/cl-hospital-eye/eye/professor/doctor/consults/consult/accept/referral`, data).then(res => {
        wx.hideLoading()
        wx.showToast({
          title: '已转诊',
          icon: 'none',
          duration: 2000
        })
        wx.navigateBack({
          delta: 1
        })
      })
    } else {
      wx.navigateBack({
        delta: 1
      })
    }
  },
  getSuggest(e) {
    this.setData({
      suggestion: e.detail.suggestion
    })
  },
  commit() {
    if (this.data.type === 'reply') {
      if (!this.data.suggestion && !this.data.voiceList.length) {
        wx.showToast({
          title: '请填写回复内容或者语音',
          icon: 'none',
          duration: 2000
        })
      } else {
        let p1 = new Promise((res, jec) => {
          this.addVoice(() => {
            res()
          })
        })
        let p2 = new Promise((res, jec) => {
          this.addImg(() => {
            res()
          })
        })
        Promise.all([p1, p2]).then(() => {
          let data = {
            appId: this.data.systemInfo.appId,
            consultId: this.data.consultId,
            content: this.data.suggestion,
            userId: this.data.userInfo.userId,
            audioList: this.data.uploaderVideoUrlArray,
            imgUrlList: this.data.uploaderUrlArray
          }
          app.http.post(this.data.isInquery ? `/cl-hospital-eye/eye/professor/doctor/consults/consult/dialog` : `/cl-hospital-eye/eye/patient/doctor/consults/consult/dialog`, data).then(res => {
            wx.navigateBack({
              delta: 1
            })
          })
        })
      }

    } else {
      if (!this.data.suggestion) {
        wx.showToast({
          title: '请填写建议内容',
          icon: 'none',
          duration: 2000
        })
      } else {
        wx.showLoading({
          title: '加载中...',
          mask: true
        })
        let data = {
          consultId: this.data.consultId,
          suggestion: this.data.suggestion
        }
        app.http.put(this.data.isInquery ? `/cl-hospital-eye/eye/professor/doctor/consults/consult/finish` : `/cl-hospital-eye/eye/patient/doctor/consults/consult/finish`, data).then(res => {
          wx.hideLoading()
          this.toTransfer()
        })
      }
    }
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
    this.setData({
      consultId: options.consultId,
      voiceList:[],
      type: options.type,
      isInquery: options.isInquery,
      systemInfo: wx.getStorageSync('EYE-USERINFO'),
      userInfo: wx.getStorageSync('userInfo'),
      btnText: options.type === "reply" ? "提交回复" : (options.isInquery ? "提交并结束会诊" : "提交并结束问诊")
    })
   
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function() {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function() {
    if (this.data.isInquery) {
      if (!this.data.type) {
        this.setData({
          title: "专家建议",
          placeholder: "请填写您的建议"
        })
      }
    } else {
      if (!this.data.type) {
        this.setData({
          title: "医生建议",
          placeholder: "请填写您的建议"
        })
      }
    }
  }
})

suggest.json

{
  "usingComponents": {
    "i-recordPanel": "/component/record-panel/index",
    "van-button": "/miniprogram_npm/vant-weapp/button/index",
    "transfer": "/component/transfer/index",
    "commit-btn": "/component/commitBtn/index",
    "voice": "/component/voice/index",
    "uploads-voice": "/component/uploads-voice/index",
    "uploads-img": "/component/uploadsImg/index",
    "van-popup": "/miniprogram_npm/vant-weapp/popup/index"
  },
  "navigationBarTitleText": "详情"
}

2、上传文字组件(record-panel),输入文字的时候使用的textarea组件,但是textarea具有层级最高性,这里对不输入文字的时候做了处理。不输入文字的时候,直接用text代替。避免出现由于textarea层级过高而出现一些遮挡页面的bug。而且这里对输入的时候进行了字数统计。代码如下

wxml

<!--components/record-panel/index.wxml-->
<view class="textarea-box">
  <view class='title-container'>
    <view class='title-tip'>
      <view class='panel-title' style='margin-left:{{left}}'>{{title}}</view>
      <view class='tip' wx:if='{{time}}'>{{time}}</view>
    </view>
  </view>
  <!-- <textarea value='{{content}}' disabled='{{disabled}}' placeholder='{{placeholder}}' class='content' style='min-height:{{disabled?"0":"180rpx"}}' maxlength='-1'></textarea> -->
  <textarea value='{{content}}' disabled='{{disabled}}' auto-height placeholder='{{placeholder}}' class='content' maxlength='256' wx:if="{{show}}" auto-focus="{{focus}}" bindblur="onRemarkInput" bindconfirm="onShowText" bindinput="getSuggest"></textarea>
  <view wx:else bindtap='{{placeholder?"onShowTextarea":""}}'>
    <view class='content-text' wx:if="{{content}}">
      <text>{{content}}</text>
    </view>
    <view wx:else class="placeholder content">
      <text>{{placeholder}}</text>
    </view>
  </view>
  <view class="phcolor text-length" wx:if='{{!disabled}}'>{{textLength?textLength:0}}/256</view>
</view>

wxss

/* components/record-panel/index.wxss */
@import '/app.wxss';

.title-container {
  display: flex;
  color: rgba(50, 50, 50, 1);
  margin: 40rpx 20rpx 20rpx 20rpx;
  font-size: 26rpx;
  align-items: center;
}

.title-tip {
  flex: 1;
  display: flex;
  /* align-items: flex-end; */
  justify-content: space-between;
}

.tip {
  font-size: 26rpx;
  font-weight: 400;
  color: rgba(50, 50, 50, 1);
}
.text-length{
  position: absolute;
  bottom: 30rpx;
  right: 50rpx;
}
.content {
  width: 650rpx;
  background: white;
  border-radius: 16rpx;
  font-size: 26rpx;
  padding: 30rpx;
  margin-left: 20rpx;
  margin-right: 20rpx;
  color: rgba(50, 50, 50, 1);
  box-shadow: 0 0 30rpx 0 rgba(0, 0, 0, 0.1);
  white-space: pre-wrap;
  line-height: 40rpx;
  min-height: 200rpx;
}

.content-text {
  width: 650rpx;
  background: white;
  border-radius: 16rpx;
  font-size: 26rpx;
  padding:30rpx;
  margin-left: 20rpx;
  margin-right: 20rpx;
  color: rgba(50, 50, 50, 1);
  box-shadow: 0 0 30rpx 0 rgba(0, 0, 0, 0.1);
  white-space: pre-wrap;
  line-height: 40rpx;
}

.placeholder {
  font-size: 26rpx;
  font-weight: 400;
  color: rgba(50, 50, 50, 0.7);
  height: 200rpx;
  line-height: 30rpx;
}
.textarea-box{
  position: relative;
}

js

// components/record-panel/index.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    title: {
      type: String,
      value: ''
    },
    content: {
      type: String,
      value: ''
    },
    disabled: {
      type: Boolean,
      value: true
    },
    placeholder: {
      type: String,
      value: ''
    },
    time: {
      type: String,
      value: ''
    },
    left:{
      type:String,
      value:''
    },
    show:{
      //是否显示
      type:Boolean,
      value:false
    },
    focus:{
      type:Boolean,
      value: false
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    textLength:''//输入字数
  },

  /**
   * 组件的方法列表
   */
  methods: {
    getSuggest(e){
      this.setData({
        textLength: e.detail.value.length
      })
      this.triggerEvent('getSuggest', {
        suggestion: e.detail.value
      })
    },
    onShowTextarea() {       //显示textare
      this.setData({
        show: true,
        focus: true
      })
    },
    onShowText(e) {   
      this.setData({
        show: false,
        focus: false   
      })
    },
    onRemarkInput(event) {               //保存输入框填写内容
      var value = event.detail.value;
      this.setData({
        content: value,
        show: false,
        focus: false
      });
      this.triggerEvent('getSuggest', {
        suggestion: event.detail.value
      })
    }
  }
})

json

{
  "component": true,
  "usingComponents": {}
}

3、上传语音组件(uploads-voice) 这里每次只上传五个语音,并做了个数限制和处理。主要利用this.innerAudioContext = wx.createInnerAudioContext()相关api进行处理。并且利用小程序组件所在页面的生命周期相关知识内容来停止语音的播放。

wxml:

<!--components/record-panel/index.wxml-->
<view class='panel-title margin-title'>
  {{title}}
  <text class="right-text">(还可回复{{voiceNumber}}条)</text>
</view>
<view class="upload-voice-box">
  <view class="upload-voice-content" catchtap="showVoiceBtn">
    <view class="phcolor">点击添加语音(2分钟/条)</view>
    <image src="/images/ic_call.png"></image>
  </view>
</view>
<view class='voice-list-box'>
  <view wx:for="{{voiceList}}" wx:key='index' class='voice-item'>
    <view style='width:{{item.width}}%' bindtap="audioPlay" data-src="{{item.audioUrl}}" class='myLuYin'>
      <view>
        <image src="/images/ic_voice2.png" animation="{{src===item.audioUrl?animationData:''}}"></image>
      </view>
      <view>{{item.duration}}</view>
    </view>
    <icon class='item-icon' bindtap='clearVoice' data-index="{{index}}" type="clear" size="20" color="#CCCCCC" />


  </view>
</view>

wxss:

/* components/record-panel/index.wxss */
@import '/app.wxss';

.upload-voice-box {
  width: 650rpx;
  border-radius: 16rpx;
  padding: 30rpx;
  margin: 0 20rpx;
  box-shadow: 0 0 30rpx 0 rgba(0, 0, 0, 0.1);
}
.upload-voice-content{
display: flex;
justify-content: space-between;
}
.upload-voice-content image{
  width: 30rpx;
  height: 38rpx;
}

.voice-list-box {
  /* position: absolute; */
  position: relative;
  padding-left: 20rpx;
  padding-right: 20rpx;
}

.voice-item {
  position: relative;
  margin-top: 20rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 690rpx;
  padding-right: 20rpx;
}

.myLuYin {
  padding: 20rpx;
  height: 40rpx;
  background: #007eff;
  border-radius: 16rpx;
  line-height: 40rpx;
  font-size: 26rpx;
  color: #fff;
  display: flex;
}

.myLuYin image {
  width: 40rpx;
  height: 40rpx;
  margin-right: 10rpx;
}

.item-icon {
  width: 20rpx;
  height: 20rpx;
}

js

const utils = require("../../utils/util.js");
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    title: {
      type: String,
      value: '语音回复'
    },
    show:{
      //是否显示
      type:Boolean,
      value:false
    },
    voiceList:{
      type:Array,
      value:[]
    },
    voiceNumber:{
      type:Number,
      value:5
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    showVoiceBtn:false,
    voiceList:[],
    src:''
  },

  /**
   * 组件的方法列表
   */
  methods: {
    clearVoice: function (e) {
    // 删除语音的时候要停止当前播放语音,并且关闭当前语音动画
      this.innerAudioContext.stop()
      clearInterval(this.data.playTimer)
      var nowList = []; //新数据

      var voiceList = this.data.voiceList; //原数据

      for (let i = 0; i < voiceList.length; i++) {
        if (i == e.currentTarget.dataset.index) {
          continue;
        } else {
          nowList.push(voiceList[i])
          continue;
        }
      }
      this.setData({
        voiceList: nowList
      })
      this.triggerEvent('getVoiceList', {
        voiceList: nowList
      })
    },
    //显示录音按钮
    showVoiceBtn(){
      if (this.data.voiceNumber){
         // 删除语音的时候要停止当前播放语音,并且关闭当前语音动画
        this.innerAudioContext.stop()
        clearInterval(this.data.playTimer)
        this.triggerEvent('showVoice', {
          showVoiceBtn: true
        })
      }else{
        wx.showToast({
          title: '语音数量已超',
          icon: 'none',
          duration: 2000
        })
      }   
    },
    playAnimation() {
      var animation = wx.createAnimation({
        duration: 500,
        timingFunction: 'ease',
      })
      this.animation = animation
      var next = true;
      //连续动画关键步骤
      this.setData({
        playTimer: setInterval(function () {
          if (next) {
            this.animation.scale(0.9).step()
            next = !next;
          } else {
            this.animation.scale(1.1).step()
            next = !next;
          }
          this.setData({
            animationData: animation.export()
          })
        }.bind(this), 500)
      })
    },
       // 播放录音
    audioPlay: function (e) {
      var that = this;
      var src = e.currentTarget.dataset.src;
      if (src == '') {
        utils.tip("失效")
        return;
      }else{
        this.setData({
          src:src
        })
      }
      this.innerAudioContext.src = src;
      this.innerAudioContext.play();
      this.playAnimation()
    }
  },
  lifetimes: {
    attached: function () {
      // 在组件实例进入页面节点树时执行
      var that = this;
      this.innerAudioContext = wx.createInnerAudioContext();
      this.innerAudioContext.onEnded((res) => {
        console.log('playEnd')
        clearInterval(that.data.playTimer)
      })
      this.innerAudioContext.onError((res) => {
        utils.tip("播放录音失败!")
      })
    },
    detached: function () {
      this.innerAudioContext.destroy()
      // 在组件实例被从页面节点树移除时执行
    }
  },
  pageLifetimes: {
    // 组件所在页面的生命周期
    hide: function () {
      // 页面被隐藏
      this.innerAudioContext.stop()
      clearInterval(this.data.playTimer)
    }
  }
})

4、录音弹出层组件(voice),这里利用了vant weapp的popup组件。并增加了倒计时功能。

wxml

<!--component/voice/index.wxml-->
<!-- <view class="voice-bg" catchtap="closeVoiceBtn" bindtouchmove="closeVoiceBtn"> -->
<view class="voice-bg" catchtap="closeVoiceBtn">
  <view class="volume-bg" wx:if='{{countDownNum>10&&luStatu}}'>
    <view class="volume-left">
      <image src='/images/ic_voice.png'></image>
    </view>
    <view class="volume-right">
      <!-- <view class="{{'volume' + (5 - index)}} volume-item {{currentIndex === index ? 'volume-aniamtion' : ''}}" wx:for="{{5}}" wx:key></view> -->
      <view class="volume5 volume-item {{currentIndex===1? 'volume-aniamtion' : ''}}"></view>
      <view class="volume4 volume-item {{(currentIndex===1||currentIndex ===2) ? 'volume-aniamtion' : ''}}"></view>
      <view class="volume3 volume-item {{(currentIndex===1||currentIndex ===2||currentIndex ===3) ? 'volume-aniamtion' : ''}}"></view>
      <view class="volume2 volume-item {{(currentIndex===1||currentIndex ===2||currentIndex ===3||currentIndex ===4) ? 'volume-aniamtion' : ''}}"></view>
      <view class="volume1 volume-item {{(currentIndex===1||currentIndex ===2||currentIndex ===3||currentIndex ===4||currentIndex ===0) ? 'volume-aniamtion' : ''}}"></view>
    </view>
  </view>
   <view class="volume-bg" wx:if='{{countDownNum<=10&&countDownNum>0&&luStatu}}'>
    {{countDownNum}}
  </view>
  <view class="voice-box">
    <view class='voice-btn {{luStatu?"active":""}}' catchtap="toVoice">
      <text wx:if='{{luStatu}}'>单击结束</text>
      <text wx:else>单击说话</text>
    </view>
  </view>
</view>

wxss

/* component/voice/index.wxss */

.voice-bg {
  width: 100%;
  overflow: hidden;
  display: flex;
  justify-content: center;
  height: 100vh;
}

.volume-bg {
  width: 340rpx;
  height: 340rpx;
  background: #393939;
  border-radius: 16rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 9;
  margin-top: 200rpx;
   font-size: 118rpx;
   color:#ffffff;
}

.volume-left {
  width: 88rpx;
  height: 118rpx;
}

.volume-right {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  margin-left: 20rpx;
}

.volume-item {
  height: 10rpx;
  background: #fff;
  opacity: 0.2;
  border-radius: 5rpx;
  margin-bottom: 16rpx;
}

.volume-aniamtion {
  opacity: 1;
}

.volume1 {
  width: 32rpx;
}

.volume2 {
  width: 52rpx;
}

.volume3 {
  width: 72rpx;
}

.volume4 {
  width: 92rpx;
}

.volume5 {
  width: 112rpx;
}

.voice-box {
  position: absolute;
  bottom: 0;
  width: 100vw;
  height: 520rpx;
  background: rgba(241, 241, 243, 1);
  display: flex;
  justify-content: center;
  align-items: center;
}

.voice-btn {
  width: 200rpx;
  height: 200rpx;
  background: #007eff;
  border-radius: 50%;
  /* border:22rpx #D8E5F4; */
  color: #fff;
  display: flex;
  justify-content: center;
  align-items: center;
}

.active {
  border: 22rpx solid #d8e5f4;
}

js

// component/voice/index.js
const utils = require("../../utils/util.js");
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    voiceList: {
      type: Array,
      value: []
    },
    luStatu: {
      type: Boolean,
      value: false
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    luStatu: false, //di'bu
    list: [],
    width: 0,
    timer: '', //定时器名字
    countDownNum: 120, //倒计时初始值
    duration: 0,
    currentIndex: '',
    animationData: '',
    isCancle:false //是否取消录音
  },

  /**
   * 组件的方法列表
   */
  methods: {
    closeVoiceBtn() {
      if (!this.data.luStatu) {
        this.triggerEvent('showVoice', {
          showVoiceBtn: false
        })
      } else {
        var that = this
        wx.showModal({
          // title: '提示',
          content: '确认取消录音吗?',
          success(res) {
            if (res.confirm) {
              that.setData({
                isCancle:true
              })
              that.toVoice()
            } else if (res.cancel) {
              console.log('用户点击取消')
            }
          }
        })
      }
    },
    //显示录音按钮
    showVoiceBtn() {
      this.triggerEvent('showVoice', {
        showVoiceBtn: true
      })
    },

    // 触摸开始
    toVoice: function() {
  
      if (!this.data.luStatu) {
        this.setData({
          luStatu: true,
        })
        this.recorderManager.start({
          duration: 120000,
          format: 'mp3'
        });
        this.countDown()
      } else {
        this.recorderManager.stop();
        clearInterval(this.data.timer);
        this.setData({
          luStatu: false,
          countDownNum: 120
        })
        this.closeVoiceBtn()
      }
    },
    // 倒计时
    countDown: function() {
      if (this.data.timer) {
        clearInterval(this.data.timer)
      }
      let that = this;
      let countDownNum = 120; //获取倒计时初始值
      //如果将定时器设置在外面,那么用户就看不到countDownNum的数值动态变化,所以要把定时器存进data里面
      that.setData({
        timer: setInterval(function() { //这里把setInterval赋值给变量名为timer的变量
          console.log(that.data.countDownNum)
          //每隔一秒countDownNum就减一,实现同步
          countDownNum--;
          //然后把countDownNum存进data,好让用户知道时间在倒计着
          that.setData({
            countDownNum: countDownNum
          })
          //在倒计时还未到0时,这中间可以做其他的事情,按项目需求来
          if (countDownNum <= 120 && countDownNum >= 10) {
            that.setData({
              currentIndex: countDownNum % 5
            })
          } else {

          }
          if (countDownNum == 0) {
            //这里特别要注意,计时器是始终一直在走的,如果你的时间为0,那么就要关掉定时器!不然相当耗性能
            //因为timer是存在data里面的,所以在关掉时,也要在data里取出后再关闭
            that.toVoice()
            //关闭定时器之后,可作其他处理codes go here
          }
        }, 1000)
      })
    }
  },
  lifetimes: {
    attached: function() {
      // 在组件实例进入页面节点树时执行
      var that = this;

      //  初始化录音对象
      this.recorderManager = wx.getRecorderManager();
      this.recorderManager.onError(function() {
        utils.tip("录音失败!")
      });

      // 录音结束
      this.recorderManager.onStop(function(res) {
        // 不取消录音与取消录音
        if(!that.data.isCancle){
          console.log('that.properties.voiceList', that.properties.voiceList)
          console.log('this.data.list', that.data.list)
          var list = that.properties.voiceList ? that.properties.voiceList : that.data.list
          var audioSeconds = (res.duration / 1000 > 120) ? 120 : Math.ceil(res.duration / 1000)
          var duration = utils.s_to_hs(audioSeconds)
          var width = Math.floor((audioSeconds / 96 + 0.2) * 100)
          var audioUrl = res.tempFilePath;
          console.log('list的1是', list)
          var aa = {
            audioUrl: audioUrl,
            width: width,
            play: false,
            audioSeconds: audioSeconds,
            duration: duration
          }
          list.push(aa);
          console.log('list的2是', list)
          that.setData({
            list: list
          })
          that.triggerEvent('getVoiceList', {
            voiceList: that.data.list
          })
        }else{
          that.setData({
            isCancle:false
          })
        }
        
      });
      this.innerAudioContext = wx.createInnerAudioContext();
      this.innerAudioContext.onError((res) => {
        utils.tip("播放录音失败!")
      })
    },
    detached: function() {
      this.innerAudioContext.destroy()
      // 在组件实例被从页面节点树移除时执行
    }
  }
})

5、上传图片组件(uploadsImg)每次最多只能上传五个,可删除图片

wxml:

<!--components/case-imgs/index.wxml-->
<view class="margin-title">
  <view class="panel-title">
    {{title}}
    <text class="right-text">(还可添加{{uploaderNum}}张)</text>
  </view>
  <view class="{{uploaderList.length === 0 ? 'ui-uploader-cell':'ui-uploader-cell-other'}}">
    <!-- 根据已选择的图片临时路径数组展示图片-->
    <view class='ui-uploader-item' wx:for="{{uploaderList}}" wx:key="{{index}}">
      <!-- 删除-->
      <icon class='ui-uploader-item-icon' bindtap='clearImg' data-index="{{index}}" type="clear" size="20" color="#666666" />
      <!-- 图片-->
      <image bindtap='showImg' data-index="{{index}}" src='{{item}}'></image>
    </view>
    <!-- 上传按钮+框 -->
    <view class='ui-uploader' bindtap='upload'>
      <image src="/images/update_img.png"></image>
    </view>
    <view wx:if="{{uploaderList.length === 0}}" class="title2">{{desc}}</view>
  </view>
</view>

wxss

@import '/app.wxss';
.panel-title{
  margin-bottom: 20rpx;
}
.ui-uploader-cell-other {
  width: 690rpx;
  padding: 40rpx 0;
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  /* justify-content: flex-start; *//* align-items: center; */
}

.ui-uploader-cell {
  width: 690rpx;
  /* padding: 20rpx 0; */
  display: flex;
  /* justify-content: flex-start; */
  flex-wrap: wrap;
  align-content: flex-start;
}

.ui-uploader-item {
  width: 118rpx;
  height: 118rpx;
  margin-right: 18rpx;
  margin-bottom: 18rpx;
  padding-bottom: 2rpx;
  padding-right: 2rpx;
  position: relative;
}

.title2 {
  width: 520rpx;
  display: flex;
  align-items: center;
  color: rgba(50,50,50,0.6)
}

.ui-uploader-item-icon {
  position: absolute;
  right: 0;
}

.ui-uploader {
  width: 120rpx;
  height: 120rpx;
  margin-right: 20rpx;
  background: #fff;
  border-radius: 16rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  box-shadow: 0 0rpx 40rpx 0 rgba(0, 0, 0, 0.08);
}

js

Component({
  /**
   * 组件的属性列表
   */
  properties: {
    uploaderList: {
      type: Array,
      value: []
    },
    uploaderNum: {
      type: Number,
      value: 0
    },
    desc: {
      type: String,
      value: '拍照化验单、检查资料报告单、药品处方单、患处照片等(最多5张)'
    },
    title: {
      type: String,
      value: '补充图片'
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    uploaderList: [],
    uploaderNum: 0,
    uploaderNowNum:0
  },

  /**
   * 组件的方法列表
   */
  methods: {
    // 删除图片
    clearImg: function(e) {
      var nowList = []; //新数据

      var uploaderList = this.data.uploaderList; //原数据

      for (let i = 0; i < uploaderList.length; i++) {
        if (i == e.currentTarget.dataset.index) {
          continue;
        } else {
          nowList.push(uploaderList[i])
          continue;
        }
      }
      this.setData({
        uploaderNum: 5 - nowList.length,
        uploaderList: nowList
      })
      this.triggerEvent('getUploaderList', {
        uploaderList: nowList
      })
    },
    //展示图片
    showImg: function(e) {
      var that = this;
      wx.previewImage({
        urls: that.data.uploaderList,
        current: that.data.uploaderList[e.currentTarget.dataset.index]
      })
    },
    //上传图片
    upload: function(e) {
    
      if (this.data.uploaderList.length>=5){
        wx.showToast({
          title: '图片数量已超',
          icon: 'none',
          duration: 2000
        })
      }else{
        var that = this;
        wx.chooseImage({
          count: 5 - that.data.uploaderList.length, // 默认5
          sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
          sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
          success: function (res) {
            console.log(res)
            // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
            let tempFilePaths = res.tempFilePaths;
            console.log(tempFilePaths)

            let uploaderList = that.data.uploaderList.concat(tempFilePaths);
              that.setData({
                uploaderList: uploaderList,
                uploaderNowNum: uploaderList.length
              })
              that.triggerEvent('getUploaderList', {
                uploaderList: uploaderList,
                uploaderNowNum: uploaderList.length
              })
            

          }
        })
      }
    },
  }
})

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值