Browse Source

[development] 随笔添加语音功能

master
JC 1 year ago
parent
commit
aa8ee11137
  1. 1
      .gitignore
  2. 53
      components/audio-recorder/recorder.vue
  3. 219
      components/h-custom-audio/index.vue
  4. 74
      components/im-chat/chatinput.vue
  5. 61
      node_modules/mescroll-uni/README.md
  6. 4
      node_modules/mescroll-uni/components/mescroll-down.vue
  7. 2
      node_modules/mescroll-uni/components/mescroll-empty.vue
  8. 12
      node_modules/mescroll-uni/components/mescroll-top.vue
  9. 3
      node_modules/mescroll-uni/components/mescroll-up.css
  10. 4
      node_modules/mescroll-uni/components/mescroll-up.vue
  11. 14
      node_modules/mescroll-uni/mescroll-body.css
  12. 152
      node_modules/mescroll-uni/mescroll-body.vue
  13. 11
      node_modules/mescroll-uni/mescroll-mixins.js
  14. 3
      node_modules/mescroll-uni/mescroll-uni-option.js
  15. 13
      node_modules/mescroll-uni/mescroll-uni.css
  16. 166
      node_modules/mescroll-uni/mescroll-uni.js
  17. 196
      node_modules/mescroll-uni/mescroll-uni.vue
  18. 7
      node_modules/mescroll-uni/mixins/mescroll-more-item.js
  19. 35
      node_modules/mescroll-uni/package.json
  20. 11
      pages/easy/easy.vue
  21. 1
      static/icons/pause.svg
  22. 1
      static/icons/play.svg
  23. 1
      static/img/keyboard.svg
  24. 1
      static/img/sound.svg
  25. 34
      uni_modules/nb-voice-record/changelog.md
  26. 425
      uni_modules/nb-voice-record/components/nb-voice-record/nb-voice-record.vue
  27. 272
      uni_modules/nb-voice-record/js_sdk/wa-permission/permission.js
  28. 81
      uni_modules/nb-voice-record/package.json
  29. 86
      uni_modules/nb-voice-record/readme.md

1
.gitignore

@ -10,3 +10,4 @@ unpackage/
.DS_Store
#日志text
Logs.txt
/node_modules/

53
components/audio-recorder/recorder.vue

@ -0,0 +1,53 @@
<template>
<view>
<h-audio
v-if="audioPath"
:audio="audioPath"
:time="duration"
sliderColor="#f44336"
activeColor="#f44336"
backgroundColor="#fff"
/>
<slot></slot>
<nb-voice-record
@startRecord="start"
@endRecord="end"
@cancelRecord="cancel"
>
</nb-voice-record>
</view>
</template>
<script>
import HAudio from '@/components/h-custom-audio/index.vue';
export default {
name: 'audio-recorder',
components: {
HAudio
},
data() {
return {
audioPath: null,
duration: 0
}
},
methods: {
start() {
//
},
end(event) {
//
// eventapptempFilePathdurationfileSize
this.audioPath = event.tempFilePath;
this.duration = event.duration / 1000;
this.$emit('end', this.audioPath);
},
cancel() {
//
}
}
}
</script>
<style lang="scss">
</style>

219
components/h-custom-audio/index.vue

@ -0,0 +1,219 @@
<template>
<view :class="['audio-card', 'audio-toolbar']">
<view v-if="title" class="title">{{ title }}</view>
<view class="audio">
<image
v-if="!isPlay"
class="play-icon"
mode="scaleToFill"
:src="playIcon"
@click="audioPlay"/>
<image
v-if="isPlay"
class="play-icon"
mode="scaleToFill"
:src="pauseIcon"
@click="audioPause"/>
<view class="audio-main">
<slider
max="100"
block-size="16"
class="progress"
:block-color="sliderColor"
:activeColor="activeColor"
:backgroundColor="backgroundColor"
:value="progress"
@change="handleChange"
@changing="handleProgress"/>
<view class="time">
<view>{{ dateFormart(currentTime) }}</view>
<view>{{ dateFormart(duration) }}</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
classname: {
type: String,
default: ''
},
audio: {
type: String,
default: ''
},
time: {
type: Number,
default: 0
},
title: {
type: String,
default: ''
},
playIcon: {
type: String,
default: require('@/static/icons/play.svg')
},
pauseIcon: {
type: String,
default: require('@/static/icons/pause.svg')
},
sliderColor: {
type: String,
default: '#009EFF'
},
activeColor: {
type: String,
default: '#009EFF'
},
backgroundColor: {
type: String,
default: '#EFEFEF'
}
},
data () {
return {
innerAudioContext: null,
isPlay: false,
timer: null,
currentTime: 0,
duration: 0,
progress: 0
}
},
watch: {
audio: {
handler(val) {
this.creatAudio();
this.progress = 0;
this.currentTime = 0;
},
deep: true,
immediate: true
}
},
destroyed () {
this.innerAudioContext.destroy()
},
methods: {
creatAudio () {
this.innerAudioContext = uni.createInnerAudioContext()
this.innerAudioContext.src = encodeURI(this.audio)
this.innerAudioContext.sessionCategory = "soloAmbient"
this.innerAudioContext.startTime = 0
this.innerAudioContext.onTimeUpdate(() => {})
this.innerAudioContext.onCanplay(() => {
this.duration = this.innerAudioContext.duration || this.time
})
this.innerAudioContext.play()
this.innerAudioContext.volume = 0
setTimeout(() => {
this.innerAudioContext.pause()
this.innerAudioContext.volume = 1
}, 100)
},
//
audioPlay () {
this.innerAudioContext.src = encodeURI(this.audio)
this.innerAudioContext.sessionCategory = "soloAmbient"
this.innerAudioContext.startTime = Number(this.progress) === 100 ? 0 : (this.currentTime ? this.currentTime : 0)
this.innerAudioContext.play()
this.onCanplay()
this.isPlay = true
},
onCanplay () {
this.innerAudioContext.onCanplay(() => {
this.currentTime = this.innerAudioContext.currentTime
})
this.innerAudioContext.onTimeUpdate(() => {
this.currentTime = this.innerAudioContext.currentTime
this.progress = (this.innerAudioContext.currentTime / this.innerAudioContext.duration * 100).toFixed(2)
})
this.innerAudioContext.onEnded(() => {
if (this.isPlay) {
this.isPlay = false
}
})
},
//
audioPause () {
this.innerAudioContext.pause()
this.isPlay = !this.isPlay
},
//
handleProgress (event) {
this.innerAudioContext.pause()
},
//
handleChange (event) {
this.progress = event.detail.value
this.currentTime = this.duration * (event.detail.value / 100)
this.audioPlay()
},
// ()
dateFormart (value) {
value = Number(value).toFixed(0)
let hour = Math.floor(value / 3600)
let minute = Math.floor((value - hour * 3600) / 60)
let second = value - hour * 3600 - minute * 60
hour = hour < 10 ? `0${hour}` : hour
minute = minute < 10 ? `0${minute}` : minute
second = second < 10 ? `0${Math.floor(second)}` : Math.floor(second)
return `${minute}:${second}`
}
}
}
</script>
<style lang="scss" scoped>
.audio-card {
height: 100rpx;
font-family: PingFangSC-Medium, PingFang SC;
display: flex;
flex-direction: column;
padding: 20rpx 24rpx;
background-color: #F4F5F6;
border-top: 1px solid #bbb;
.title {
color: #333333;
font-size: 26rpx;
font-weight: 500;
}
.audio {
margin-top: 15rpx;
display: flex;
.play-icon {
width: 40rpx;
height: 40rpx;
}
.audio-main {
height: 48rpx;
flex: 1;
margin-left: 15rpx;
}
.progress {
margin: 0;
}
.time {
width: 100%;
height: 50rpx;
font-size: 24rpx;
font-weight: 400;
color: #666666;
margin-top: -15rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
}
}
.audio-toolbar{
position: fixed;
left: 0;
right: 0;
bottom: 200rpx;
}
</style>

74
components/im-chat/chatinput.vue

@ -3,29 +3,63 @@
<view class="footer" >
<view class="footer-center">
<!-- <input class="input-text" type="text" @blur="blur" @confirm="sendMessge" v-model="inputValue" :focus="focus" :placeholder="placeholder"></input> -->
<textarea class="input-text" @blur="blur" :fixed="true" :cursor-spacing="100" @confirm="sendMessge" :show-confirm-bar="false" v-model="inputValue" :focus="focus" maxlength="-1" :placeholder="placeholder"></textarea>
<textarea
v-if="isKeyboard"
class="input-text"
@blur="blur"
:fixed="true"
:cursor-spacing="100"
@confirm="sendMessge"
:show-confirm-bar="false"
v-model="inputValue"
:focus="focus"
maxlength="-1"
:placeholder="placeholder"
/>
<audio-recorder v-else @end="handleAudioEnd" />
</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="footer" v-clickoutside='blur'>
<view class="footer-center">
<!-- <input class="input-text" type="text" @confirm="sendMessge" v-model="inputValue" :focus="focus" :placeholder="placeholder"></input> -->
<textarea class="input-text" @confirm="sendMessge" v-model="inputValue" :focus="focus" maxlength="-1" :placeholder="placeholder"></textarea>
<textarea
v-if="isKeyboard"
class="input-text"
@confirm="sendMessge"
v-model="inputValue"
:focus="focus"
maxlength="-1"
:placeholder="placeholder"
/>
<audio-recorder v-else @end="handleAudioEnd" />
</view>
<!-- #endif -->
<view class="footer-right">
<image
v-if="hasVideo && !isKeyboard"
class="sound-logo"
src="../../static/img/sound.svg"
@click="isKeyboard=true"
/>
<image
v-else-if="hasVideo && isKeyboard"
class="keyboard-logo"
src="../../static/img/keyboard.svg"
@click="isKeyboard=false"
/>
<view id='msg-type' class="send-comment" @tap="sendMessge">发送</view>
</view>
</view>
</template>
<script>
import AudioRecorder from '@/components/audio-recorder/recorder.vue';
const clickoutside = {
//
bind(el, binding, vnode) {
function documentHandler(e) {
//
if (el.contains(e.target)) {
@ -51,10 +85,15 @@
};
export default {
name: "chat-input",
components: {
AudioRecorder
},
data() {
return {
inputValue: '',
isShow:false
isShow: false,
isKeyboard: true,
audioSource: ''
}
},
props:{
@ -66,9 +105,13 @@
type:Boolean,
required: true
},
show:{
show: {
type:Boolean,
required:true
},
hasVideo: {
type: Boolean,
default: false
}
},
watch:{
@ -84,15 +127,15 @@
// #endif
if(!this.isShow){
this.$emit('blur')
this.$emit('blur')
}
else{
this.isShow=!this.isShow;
this.isShow=!this.isShow;
}
},
sendMessge: function (e) {
// TODO
if (!this.inputValue) {
uni.showModal({
content:"还没有输入内容哦!",
@ -107,6 +150,10 @@
content: that.inputValue
});
that.inputValue = '';//
},
//
handleAudioEnd(audioSource) {
this.audioSource = audioSource;
}
}
@ -115,7 +162,6 @@
<style lang="scss">
.footer {
display: flex;
width: 100%;
min-height: 200rpx;
@ -124,6 +170,7 @@
background-color: #F4F5F6;
overflow-y: hidden;
}
.footer-left {
width: 80upx;
height: 200rpx;
@ -151,6 +198,7 @@
align-items: center;
flex-grow: 0;
flex-shrink: 0;
z-index: 1;
}
.footer-center .input-text {
background: #fff;
@ -176,6 +224,12 @@
font-size: 25rpx;
position: absolute;
bottom: 5rpx;
}
.footer-right .sound-logo,
.footer-right .keyboard-logo{
width: 60rpx;
height: 60rpx;
position: absolute;
top: 20rpx;
}
</style>

61
node_modules/mescroll-uni/README.md

@ -1,4 +1,4 @@
## mescroll : 支持uni-app的下拉刷新和上拉加载组件,支持原生页面和局部区域滚动
## mescroll --【wxs+renderjs实现】高性能的下拉刷新上拉加载组件
1. mescroll的uni版本 是专门用在uni-app的下拉刷新和上拉加载的组件, 支持一套代码编译到iOS、Android、H5、小程序等多个平台
2. mescroll的uni版本 继承了mescroll.js的实用功能: 自动处理分页, 自动控制无数据, 空布局提示, 回到顶部按钮 ..
@ -7,13 +7,66 @@
<br/>
## 最新文档(1.2.5版本): <a href="http://www.mescroll.com/uni.html?v=20200315">http://www.mescroll.com/uni.html?v=20200315</a>
(文档可能会有缓存,建议每次打开时刷新一下)
---
## 最新文档(1.3.0版本): <a href="http://www.mescroll.com/uni.html?v=20200710">http://www.mescroll.com/uni.html?v=20200710</a>
2020-07-10 by mescroll (文档可能会有缓存,建议每次打开时刷新一下)
<br/>
#### 下版本预告 (by 小瑾同学):
1. wxs工具类的提取,使自定义wxs的下拉刷新更清晰明了
2. 瀑布流的示例
#### 近期新版本已更新优化的内容:
1. 微信小程序, app, h5使用高性能wxs和renderjs, 下拉刷新更流畅丝滑, 尤其能明显解决Android小程序下拉卡顿的问题
2. 使用wxs和renderjs优化所有案例, 尤其是中高级案例, 建议大家重新下载最新的案例
3. 新增 "局部区域滚动" 的案例: mescroll-body-part.vue 和 mescroll-uni-part.vue
4. 新增 me-tabs 组件,tabs支持水平滑动; 优化mescroll-more和mescroll-swiper的案例, 顶部tab支持水平滑动
5. mescroll-uni 和 mescroll-body 的 scrollTo 正式支持 scroll-into-view (传入的 y 为view的id即可生效)
6. topbar 顶部是否预留状态栏的高度, 默认false; 还可支持设置状态栏背景: 如 '#ffff00', 'url(xxx)', 'linear-gradient(xx)'
<br/><a href="https://ext.dcloud.net.cn/plugin?id=343&update_log">查看更多 ... </a>
### 更新记录:
---
#### 1.3.0版本 (2020/07/10)
1. 微信小程序, app, h5使用高性能wxs和renderjs, 下拉刷新更流畅丝滑, 尤其能明显解决Android小程序下拉卡顿的问题
2. 使用wxs和renderjs优化所有案例, 尤其是中高级案例, 建议大家重新下载最新的案例
3. 废弃down的isBounce配置, 已通过renderjs自动判断, 无需配置mescroll-touch
4. 废弃down的fps配置, 已通过wxs提高性能, 无需手动节流
5. 新增 "局部区域滚动" 的案例: mescroll-body-part.vue 和 mescroll-uni-part.vue
6. 解决swiper切换时,有时会触发下拉刷新的问题, 已避免swiper和下拉刷新相互冲突
7. 解决钉钉小程序mescroll-uni下拉刷新有时无法触发的问题
8. 解决上拉加载进度在部分Android手机显示不全的问题
9. 提高 me-tabs 组件在部分Android手机的兼容性
-by 小瑾同学
---
#### 1.2.8版本 (2020/06/28)
1. 解决 mescroll-uni 再某些情况下列表数据渲染不完全的问题 ( mescroll-body无此问题 )
2. 优化 me-tabs 组件, 使用支付宝小程序可隐藏滚动条, 同时修复字节跳动小程序tab切换时渲染延迟的问题
-by 小瑾同学
---
#### 1.2.7版本 (2020/06/24)
1. 上拉加载结束隐藏底部加载区域,避免加载区域占位
2. h5端的tab页默认偏移TabBar的高度,避免h5端列表被TabBar遮住 (如不想偏移,可通过配置 :bottombar="false" 取消)
3. 新增 me-tabs 组件,tabs支持水平滑动; 优化mescroll-more和mescroll-swiper的案例, 顶部tab支持水平滑动
---
#### 1.2.6版本 (2020/06/16)
1. mescroll-uni 和 mescroll-body 的 scrollTo 正式支持 scroll-into-view (传入的 y 为view的id即可生效)
2. topbar 顶部是否预留状态栏的高度, 默认false; 这个版本还可支持设置状态栏背景: 如 '#ffff00', 'url(xxx)', 'linear-gradient(xx)'
3. down.bgColor 和 up.bgColor 加载区域的背景,不仅支持色值, 而且还是支持背景图和渐变: 如 'url(xxx)', 'linear-gradient(xx)'
4. 通过css方式适配iPhoneX, 比之前通过style方式具有更好的兼容性, 也同时消除了edge浏览器重复设置相同属性的警告
5. 移除非必须的标签选择器,避免微信小程序提示组件内不可使用标签选择器的警告
6. 修复当配置up的use为false时,默认的下拉刷新有时候无法自动隐藏的问题
7. 修复当配置down的native为true时,auto失效的问题
8. 修复空布局在某些情况下图片和文本错位的问题
---
#### 1.2.5版本 (2020/03/15)
1. mescroll-body 的 props 支持 safearea 的配置 (需要适配iPhoneX时,配置为 true 即可, 默认 false)
2. mescroll-uni 的 scrollTo 支持 scroll-into-view (当传入的 y 为view的id时, 即可生效)

4
node_modules/mescroll-uni/components/mescroll-down.vue

@ -1,8 +1,8 @@
<!-- 下拉刷新区域 -->
<template>
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
<view class="downwarp-content">
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform':downRotate}"></view>
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mOption.textColor, 'transform':downRotate}"></view>
<view class="downwarp-tip">{{downText}}</view>
</view>
</view>

2
node_modules/mescroll-uni/components/mescroll-empty.vue

@ -7,7 +7,7 @@ import MescrollEmpty from '@/components/mescroll-uni/components/mescroll-empty.v
-->
<template>
<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
<image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" />
<view> <image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> </view>
<view v-if="tip" class="empty-tip">{{ tip }}</view>
<view v-if="option.btnText" class="empty-btn" @click="emptyClick">{{ option.btnText }}</view>
</view>

12
node_modules/mescroll-uni/components/mescroll-top.vue

@ -3,7 +3,7 @@
<image
v-if="mOption.src"
class="mescroll-totop"
:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-safe-bottom': mOption.safearea}]"
:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-totop-safearea': mOption.safearea}]"
:style="{'z-index':mOption.zIndex, 'left': left, 'right': right, 'bottom':addUnit(mOption.bottom), 'width':addUnit(mOption.width), 'border-radius':addUnit(mOption.radius)}"
:src="mOption.src"
mode="widthFix"
@ -62,10 +62,12 @@ export default {
margin-bottom: var(--window-bottom); /* css变量 */
}
/* 适配 iPhoneX */
.mescroll-safe-bottom{
margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
/* 适配 iPhoneX */
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
.mescroll-totop-safearea {
margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
}
}
/* 显示 -- 淡入 */

3
node_modules/mescroll-uni/components/mescroll-up.css

@ -1,6 +1,7 @@
/* 上拉加载区域 */
.mescroll-upwarp {
min-height: 60rpx;
box-sizing: border-box;
min-height: 110rpx;
padding: 30rpx 0;
text-align: center;
clear: both;

4
node_modules/mescroll-uni/components/mescroll-up.vue

@ -1,9 +1,9 @@
<!-- 上拉加载区域 -->
<template>
<view class="mescroll-upwarp" :style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<view class="mescroll-upwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="isUpLoading">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mOption.textColor}"></view>
<view class="upwarp-tip">{{ mOption.textLoading }}</view>
</view>
<!-- 无数据 -->

14
node_modules/mescroll-uni/mescroll-body.css

@ -1,10 +1,14 @@
page {
-webkit-overflow-scrolling: touch; /* 使iOS滚动流畅 */
}
.mescroll-body {
position: relative; /* 下拉刷新区域相对自身定位 */
height: auto; /* 不可固定高度,否则overflow: hidden, 可通过设置最小高度使列表不满屏仍可下拉*/
height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/
overflow: hidden; /* 遮住顶部下拉刷新区域 */
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
}
/* 适配 iPhoneX */
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
.mescroll-safearea {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}

152
node_modules/mescroll-uni/mescroll-body.vue

@ -1,11 +1,23 @@
<template>
<view class="mescroll-body" :style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom, 'padding-bottom': padBottomConstant, 'padding-bottom': padBottomEnv }" @touchstart="touchstartEvent" @touchmove="touchmoveEvent" @touchend="touchendEvent" @touchcancel="touchendEvent" >
<view class="mescroll-body-content" :style="{ transform: translateY, transition: transition }">
<view
class="mescroll-body mescroll-render-touch"
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}"
@touchstart="wxsBiz.touchstartEvent"
@touchmove="wxsBiz.touchmoveEvent"
@touchend="wxsBiz.touchendEvent"
@touchcancel="wxsBiz.touchendEvent"
:change:prop="wxsBiz.propObserver"
:prop="wxsProp"
>
<!-- 状态栏 -->
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content">
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content" :change:prop="renderBiz.propObserver" :prop="wxsProp">
<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
<view class="downwarp-tip">{{downText}}</view>
</view>
</view>
@ -17,8 +29,8 @@
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view v-if="mescroll.optUp.use && !isDownLoading" class="mescroll-upwarp" :style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType===1">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
@ -28,12 +40,35 @@
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
</view>
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
<!-- #ifdef H5 -->
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
<!-- #endif -->
<!-- 适配iPhoneX -->
<view v-if="safearea" class="mescroll-safearea"></view>
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
</view>
</template>
<!-- 微信小程序, app, h5使用wxs -->
<!-- #ifdef MP-WEIXIN || APP-PLUS || H5-->
<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
<!-- #endif -->
<!-- app, h5使用renderjs -->
<!-- #ifdef APP-PLUS || H5 -->
<script module="renderBiz" lang="renderjs">
import renderBiz from './wxs/renderjs.js';
export default {
mixins: [renderBiz]
}
</script>
<!-- #endif -->
<script>
// mescroll-uni.js,
import MeScroll from './mescroll-uni.js';
@ -43,8 +78,11 @@
import MescrollEmpty from './components/mescroll-empty.vue';
//
import MescrollTop from './components/mescroll-top.vue';
// wxs(renderjs)mixins
import WxsMixin from './wxs/mixins.js';
export default {
mixins: [WxsMixin],
components: {
MescrollEmpty,
MescrollTop
@ -54,23 +92,27 @@
mescroll: {optDown:{},optUp:{}}, // mescroll
downHight: 0, //:
downRate: 0, // (inOffset: rate<1; outOffset: rate>=1)
downLoadType: 4, // inOffset1 outOffset2 showLoading3 endDownScroll4
upLoadType: 0, // 0loading1loading2
downLoadType: 0, // : 0(loading), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
upLoadType: 0, // 0loading1loading2,END3,END
isShowEmpty: false, //
isShowToTop: false, //
windowHeight: 0, // 使
statusBarHeight: 0, //
isSafearea: false //
windowBottom: 0, // 使
statusBarHeight: 0 //
};
},
props: {
down: Object, //
up: Object, //
top: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
topbar: Boolean, // top, false (使:,)
topbar: [Boolean, String], // top, false (使:,, ,,,)
bottom: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
safearea: Boolean, // bottom, false (iPhoneX使)
height: [String, Number] // mescroll,windowHeight,使
height: [String, Number], // mescroll,windowHeight,使
bottombar:{ // TabBar(H5tab)
type: Boolean,
default: true
}
},
computed: {
// mescroll,windowHeight,使
@ -79,7 +121,7 @@
},
// (px)
numTop() {
return this.toPx(this.top) + (this.topbar ? this.statusBarHeight : 0);
return this.toPx(this.top)
},
padTop() {
return this.numTop + 'px';
@ -91,19 +133,13 @@
padBottom() {
return this.numBottom + 'px';
},
padBottomConstant() {
return this.isSafearea ? 'calc(' + this.padBottom + ' + constant(safe-area-inset-bottom))' : this.padBottom;
},
padBottomEnv() {
return this.isSafearea ? 'calc(' + this.padBottom + ' + env(safe-area-inset-bottom))' : this.padBottom;
},
//
isDownReset() {
return this.downLoadType === 3 || this.downLoadType === 4;
},
//
transition() {
return this.isDownReset ? 'transform 300ms' : this.downTransition;
return this.isDownReset ? 'transform 300ms' : '';
},
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform使fixed,fixedmescroll
@ -118,6 +154,7 @@
},
//
downText(){
if(!this.mescroll) return ""; //
switch (this.downLoadType){
case 1: return this.mescroll.optDown.textInOffset;
case 2: return this.mescroll.optDown.textOutOffset;
@ -150,18 +187,6 @@
}
return num ? uni.upx2px(Number(num)) : 0;
},
//touchstart,
touchstartEvent(e) {
this.mescroll.touchstartEvent(e);
},
//touchmove,
touchmoveEvent(e) {
this.mescroll.touchmoveEvent(e);
},
//touchend,
touchendEvent(e) {
this.mescroll.touchendEvent(e);
},
//
emptyClick() {
this.$emit('emptyclick', this.mescroll);
@ -179,10 +204,10 @@
let diyOption = {
//
down: {
inOffset(mescroll) {
inOffset() {
vm.downLoadType = 1; // offset (mescroll,)
},
outOffset(mescroll) {
outOffset() {
vm.downLoadType = 2; // offset (mescroll,)
},
onMoving(mescroll, rate, downHight) {
@ -194,9 +219,13 @@
vm.downLoadType = 3; // (mescroll,)
vm.downHight = downHight; // (mescroll,)
},
endDownScroll(mescroll) {
endDownScroll() {
vm.downLoadType = 4; // (mescroll,)
vm.downHight = 0; // (mescroll,)
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} //
vm.downResetTimer = setTimeout(()=>{ // ,0,inOffsettextInOffset
if(vm.downLoadType === 4) vm.downLoadType = 0
},300)
},
//
callback: function(mescroll) {
@ -214,8 +243,8 @@
vm.upLoadType = 2;
},
//
hideUpScroll() {
vm.upLoadType = 0;
hideUpScroll(mescroll) {
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
},
//
empty: {
@ -239,12 +268,7 @@
};
MeScroll.extend(diyOption, GlobalOption); //
let myOption = JSON.parse(
JSON.stringify({
down: vm.down,
up: vm.up
})
); // ,props
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
@ -255,31 +279,37 @@
//
const sys = uni.getSystemInfoSync();
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// mescroll-bodyAndroid,mescroll-uni"disableScroll":true,
// #ifdef MP
if(sys.platform == "android") vm.downTransition = 'transform 200ms'
// #endif
// 使pagescroll,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
uni.pageScrollTo({
scrollTop: y,
duration: t
})
if(typeof y === 'string'){
// view (yid,#)
setTimeout(()=>{ // view; 使$nextTick
uni.createSelectorQuery().select('#'+y).boundingClientRect(function(rect){
let top = rect.top
top += vm.mescroll.getScrollTop()
uni.pageScrollTo({
scrollTop: top,
duration: t
})
}).exec()
},30)
} else{
// (y)
uni.pageScrollTo({
scrollTop: y,
duration: t
})
}
});
// up.toTop.safearea,vuesafearea
if(sys.platform == "ios"){
vm.isSafearea = vm.safearea;
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
}else{
vm.isSafearea = false
vm.mescroll.optUp.toTop.safearea = false
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
}
};

11
node_modules/mescroll-uni/mescroll-mixins.js

@ -38,10 +38,15 @@ const MescrollMixin = {
if(mescrollRef) this.mescroll = mescrollRef.mescroll
}
},
// 下拉刷新的回调
// 下拉刷新的回调 (mixin默认resetUpScroll)
downCallback() {
// mixin默认resetUpScroll
this.mescroll.resetUpScroll()
if(this.mescroll.optUp.use){
this.mescroll.resetUpScroll()
}else{
setTimeout(()=>{
this.mescroll.endSuccess();
}, 500)
}
},
// 上拉加载的回调
upCallback() {

3
node_modules/mescroll-uni/mescroll-uni-option.js

@ -14,7 +14,6 @@ const GlobalOption = {
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
offset: 80, // 距底部多远时,触发upCallback
isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
toTop: {
// 回到顶部按钮,需配置src才显示
src: "http://www.mescroll.com/img/mescroll-totop.png?v=1", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
@ -26,7 +25,7 @@ const GlobalOption = {
empty: {
use: true, // 是否显示空布局
icon: "http://www.mescroll.com/img/mescroll-empty.png?v=1", // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
tip: '~ 暂无相关数据 ~' // 提示
tip: '~ 空空如也 ~' // 提示
}
}
}

13
node_modules/mescroll-uni/mescroll-uni.css

@ -1,9 +1,8 @@
page {
.mescroll-uni-warp{
height: 100%;
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
}
.mescroll-uni-warp{
.mescroll-uni-content{
height: 100%;
}
@ -27,3 +26,11 @@ page {
width: auto; /* 使right生效 */
height: auto; /* 使bottom生效 */
}
/* 适配 iPhoneX */
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
.mescroll-safearea {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}

166
node_modules/mescroll-uni/mescroll-uni.js

@ -1,12 +1,12 @@
/* mescroll
* version 1.2.5
* 2020-03-15 wenju
* version 1.3.0
* 2020-07-10 wenju
* http://www.mescroll.com
*/
export default function MeScroll(options, isScrollBody) {
let me = this;
me.version = '1.2.5'; // mescroll版本号
me.version = '1.3.0'; // mescroll版本号
me.options = options || {}; // 配置
me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view
@ -22,7 +22,7 @@ export default function MeScroll(options, isScrollBody) {
// 自动加载
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
// 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新)
if (me.optDown.use && me.optDown.auto && hasDownCallback) {
if ((me.optDown.use || me.optDown.native) && me.optDown.auto && hasDownCallback) {
if (me.optDown.autoShowLoading) {
me.triggerDownScroll(); // 显示下拉进度,执行下拉回调
} else {
@ -30,9 +30,11 @@ export default function MeScroll(options, isScrollBody) {
}
}
// 自动触发上拉加载
setTimeout(function(){ // 延时确保先执行down的callback,再执行up的callback,因为部分小程序emit是异步,会导致isUpAutoLoad判断有误
me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll();
},100)
if(!me.isUpAutoLoad){ // 部分小程序(头条小程序)emit是异步, 会导致isUpAutoLoad判断有误, 先延时确保先执行down的callback,再执行up的callback
setTimeout(function(){
me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll();
},100)
}
}, 30); // 需让me.optDown.inited和me.optUp.inited先执行
}
@ -46,8 +48,7 @@ MeScroll.prototype.extendDownScroll = function(optDown) {
autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false
isLock: false, // 是否锁定下拉刷新,默认false;
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
startTop: 100, // scroll-view滚动到顶部时,此时的scroll-top不一定为0, 此值用于控制最大的误差
fps: 80, // 下拉节流 (值越大每秒刷新频率越高)
startTop: 100, // scroll-view快速滚动到顶部时,此时的scroll-top可能大于0, 此值用于控制最大的误差
inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行
@ -63,8 +64,10 @@ MeScroll.prototype.extendDownScroll = function(optDown) {
onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】
showLoading: null, // 显示下拉刷新进度的回调
afterLoading: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
afterLoading: null, // 显示下拉刷新进度的回调之后,马上要执行的代码 (如: 在wxs中使用)
beforeEndDownScroll: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
endDownScroll: null, // 结束下拉刷新的回调
afterEndDownScroll: null, // 结束下拉刷新的回调,马上要执行的代码 (如: 在wxs中使用)
callback: function(mescroll) {
// 下拉刷新的回调;默认重置上拉加载列表为第一页
mescroll.resetUpScroll();
@ -80,7 +83,6 @@ MeScroll.prototype.extendUpScroll = function(optUp) {
auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
isLock: false, // 是否锁定上拉加载,默认false;
isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发;
isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
callback: null, // 上拉加载的回调;function(page,mescroll){ }
page: {
num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
@ -185,6 +187,7 @@ MeScroll.prototype.touchstartEvent = function(e) {
this.startPoint = this.getPoint(e); // 记录起点
this.startTop = this.getScrollTop(); // 记录此时的滚动条位置
this.startAngle = 0; // 初始角度
this.lastPoint = this.startPoint; // 重置上次move的点
this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
this.inTouchend = false; // 标记不是touchend
@ -192,23 +195,9 @@ MeScroll.prototype.touchstartEvent = function(e) {
/* 列表touchmove事件 */
MeScroll.prototype.touchmoveEvent = function(e) {
// #ifdef H5
window.isPreventDefault = false // 标记不需要阻止window事件
// #endif
if (!this.optDown.use) return;
if (!this.startPoint) return;
let me = this;
// 节流
let t = new Date().getTime();
if (me.moveTime && t - me.moveTime < me.moveTimeDiff) { // 小于节流时间,则不处理
return;
} else {
me.moveTime = t
if(!me.moveTimeDiff) me.moveTimeDiff = 1000 / me.optDown.fps
}
let scrollTop = me.getScrollTop(); // 当前滚动条的距离
let curPoint = me.getPoint(e); // 当前点
@ -217,7 +206,7 @@ MeScroll.prototype.touchmoveEvent = function(e) {
// 向下拉 && 在顶部
// mescroll-body,直接判定在顶部即可
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
// scroll-view滚动到顶部时,scrollTop不一定为0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
// scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
if (moveY > 0 && (
(me.isScrollBody && scrollTop <= 0)
||
@ -227,9 +216,9 @@ MeScroll.prototype.touchmoveEvent = function(e) {
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
me.optUp.isBoth))) {
// 下拉的角度是否在配置的范围内
let angle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
if (angle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
// 下拉的初始角度是否在配置的范围内
if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
if (me.startAngle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
@ -238,9 +227,6 @@ MeScroll.prototype.touchmoveEvent = function(e) {
return;
}
// #ifdef H5
window.isPreventDefault = true // 标记阻止window事件
// #endif
me.preventDefault(e); // 阻止默认事件
let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
@ -262,12 +248,13 @@ MeScroll.prototype.touchmoveEvent = function(e) {
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
}
if (diff > 0) { // 向下拉
me.downHight += Math.round(diff * me.optDown.outOffsetRate); // 越往下,高度变化越小
me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
} else { // 向上收
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
}
}
me.downHight = Math.round(me.downHight) // 取整
let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
}
@ -287,7 +274,7 @@ MeScroll.prototype.touchendEvent = function(e) {
} else {
// 不符合的话 则重置
this.downHight = 0;
this.optDown.endDownScroll && this.optDown.endDownScroll(this);
this.endDownScrollCall(this);
}
this.movetype = 0;
this.isMoveDown = false;
@ -349,7 +336,7 @@ MeScroll.prototype.triggerDownScroll = function() {
//return true则处于完全自定义状态
} else {
this.showDownScroll(); // 下拉刷新中...
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
!this.optDown.native && this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
}
}
@ -358,17 +345,22 @@ MeScroll.prototype.showDownScroll = function() {
this.isDownScrolling = true; // 标记下拉中
if (this.optDown.native) {
uni.startPullDownRefresh(); // 系统自带的下拉刷新
this.optDown.showLoading && this.optDown.showLoading(this, 0); // 仍触发showLoading,因为上拉加载用到
this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
} else{
this.downHight = this.optDown.offset; // 更新下拉区域高度
this.optDown.showLoading && this.optDown.showLoading(this, this.downHight); // 下拉刷新中...
this.showDownLoadingCall(this.downHight); // 下拉刷新中...
}
}
MeScroll.prototype.showDownLoadingCall = function(downHight) {
this.optDown.showLoading && this.optDown.showLoading(this, downHight); // 下拉刷新中...
this.optDown.afterLoading && this.optDown.afterLoading(this, downHight); // 下拉刷新中...触发之后马上要执行的代码
}
/* 显示系统自带的下拉刷新时需要处理的业务 */
MeScroll.prototype.onPullDownRefresh = function() {
this.isDownScrolling = true; // 标记下拉中
this.optDown.showLoading && this.optDown.showLoading(this, 0); // 仍触发showLoading,因为上拉加载用到
this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
}
@ -376,7 +368,7 @@ MeScroll.prototype.onPullDownRefresh = function() {
MeScroll.prototype.endDownScroll = function() {
if (this.optDown.native) { // 结束原生下拉刷新
this.isDownScrolling = false;
this.optDown.endDownScroll && this.optDown.endDownScroll(this);
this.endDownScrollCall(this);
uni.stopPullDownRefresh();
return
}
@ -385,12 +377,15 @@ MeScroll.prototype.endDownScroll = function() {
let endScroll = function() {
me.downHight = 0;
me.isDownScrolling = false;
me.optDown.endDownScroll && me.optDown.endDownScroll(me);
!me.isScrollBody && me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
me.endDownScrollCall(me);
if(!me.isScrollBody){
me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
me.scrollTo(0,0) // scroll-view需重置滚动条到顶部,避免startTop大于0时,对下拉刷新的影响
}
}
// 结束下拉刷新时的回调
let delay = 0;
if (me.optDown.afterLoading) delay = me.optDown.afterLoading(me); // 结束下拉刷新的延时,单位ms
if (me.optDown.beforeEndDownScroll) delay = me.optDown.beforeEndDownScroll(me); // 结束下拉刷新的延时,单位ms
if (typeof delay === 'number' && delay > 0) {
setTimeout(endScroll, delay);
} else {
@ -398,6 +393,11 @@ MeScroll.prototype.endDownScroll = function() {
}
}
MeScroll.prototype.endDownScrollCall = function() {
this.optDown.endDownScroll && this.optDown.endDownScroll(this);
this.optDown.afterEndDownScroll && this.optDown.afterEndDownScroll(this);
}
/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */
MeScroll.prototype.lockDownScroll = function(isLock) {
if (isLock == null) isLock = true;
@ -418,8 +418,6 @@ MeScroll.prototype.initUpScroll = function() {
if(!me.optUp.textColor && me.hasColor(me.optUp.bgColor)) me.optUp.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
me.extendUpScroll(me.optUp);
if (!me.optUp.isBounce) me.setBounce(false); // 不允许bounce时,需禁止window的touchmove事件
if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
me.startNum = me.optUp.page.num + 1; // 记录page开始的页码
@ -783,80 +781,8 @@ MeScroll.prototype.setBodyHeight = function(h) {
/* 阻止浏览器默认滚动事件 */
MeScroll.prototype.preventDefault = function(e) {
// 小程序不支持e.preventDefault
// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止
// 小程序不支持e.preventDefault, 已在wxs中禁止
// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止, 或使用renderjs禁止
// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
if (e && e.cancelable && !e.defaultPrevented) e.preventDefault()
}
/* 是否允许下拉回弹(橡皮筋效果); true或null为允许; false禁止bounce */
MeScroll.prototype.setBounce = function(isBounce) {
// #ifdef H5
if (isBounce === false) {
this.optUp.isBounce = false; // 禁止
// 标记当前页使用了mescroll (需延时,确保page已切换)
setTimeout(function() {
let uniPageDom = document.getElementsByTagName('uni-page')[0];
uniPageDom && uniPageDom.setAttribute('use_mescroll', true)
}, 30);
// 避免重复添加事件
if (window.isSetBounce) return;
window.isSetBounce = true;
// 需禁止window的touchmove事件才能有效的阻止bounce
window.bounceTouchmove = function(e) {
if(!window.isPreventDefault) return; // 根据标记判断是否阻止
let el = e.target;
// 当前touch的元素及父元素是否要拦截touchmove事件
let isPrevent = true;
while (el !== document.body && el !== document) {
if (el.tagName === 'UNI-PAGE') { // 只扫描当前页
if (!el.getAttribute('use_mescroll')) {
isPrevent = false; // 如果当前页没有使用mescroll,则不阻止
}
break;
}
let cls = el.classList;
if (cls) {
if (cls.contains('mescroll-touch')) { // 采用scroll-view 此处不能过滤mescroll-uni,否则下拉仍然有回弹
isPrevent = false; // mescroll-touch无需拦截touchmove事件
break;
} else if (cls.contains('mescroll-touch-x') || cls.contains('mescroll-touch-y')) {
// 如果配置了水平或者垂直滑动
let curX = e.touches ? e.touches[0].pageX : e.clientX; // 当前第一个手指距离列表顶部的距离x
let curY = e.touches ? e.touches[0].pageY : e.clientY; // 当前第一个手指距离列表顶部的距离y
if (!this.preWinX) this.preWinX = curX; // 设置上次移动的距离x
if (!this.preWinY) this.preWinY = curY; // 设置上次移动的距离y
// 计算两点之间的角度
let x = Math.abs(this.preWinX - curX);
let y = Math.abs(this.preWinY - curY);
let z = Math.sqrt(x * x + y * y);
this.preWinX = curX; // 记录本次curX的值
this.preWinY = curY; // 记录本次curY的值
if (z !== 0) {
let angle = Math.asin(y / z) / Math.PI * 180; // 角度区间 [0,90]
if ((angle <= 45 && cls.contains('mescroll-touch-x')) || (angle > 45 && cls.contains('mescroll-touch-y'))) {
isPrevent = false; // 水平滑动或者垂直滑动,不拦截touchmove事件
break;
}
}
}
}
el = el.parentNode; // 继续检查其父元素
}
// 拦截touchmove事件:是否可以被禁用&&是否已经被禁用 (这里不使用me.preventDefault(e)的方法,因为某些情况下会报找不到方法的异常)
if (isPrevent && e.cancelable && !e.defaultPrevented && typeof e.preventDefault === "function") e.preventDefault();
}
window.addEventListener('touchmove', window.bounceTouchmove, {
passive: false
});
} else {
this.optUp.isBounce = true; // 允许
if (window.bounceTouchmove) {
window.removeEventListener('touchmove', window.bounceTouchmove);
window.bounceTouchmove = null;
window.isSetBounce = false;
}
}
// #endif
}
}

196
node_modules/mescroll-uni/mescroll-uni.vue

@ -1,33 +1,52 @@
<template>
<view class="mescroll-uni-warp">
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'padding-bottom':padBottomConstant,'padding-bottom':padBottomEnv,'top':fixedTop,'bottom':fixedBottom,'bottom':fixedBottomConstant,'bottom':fixedBottomEnv}" :scroll-top="scrollTop" :scroll-into-view="scrollToViewId" :scroll-with-animation="scrollAnim" @scroll="scroll" @touchstart="touchstartEvent" @touchmove="touchmoveEvent" @touchend="touchendEvent" @touchcancel="touchendEvent" :scroll-y='isDownReset' :enable-back-to-top="true">
<view class="mescroll-uni-content" :style="{'transform': translateY, 'transition': transition}">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content">
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
<view class="downwarp-tip">{{downText}}</view>
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-into-view="scrollToViewId" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true">
<view class="mescroll-uni-content mescroll-render-touch"
@touchstart="wxsBiz.touchstartEvent"
@touchmove="wxsBiz.touchmoveEvent"
@touchend="wxsBiz.touchendEvent"
@touchcancel="wxsBiz.touchendEvent"
:change:prop="wxsBiz.propObserver"
:prop="wxsProp">
<!-- 状态栏 -->
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content" :change:prop="renderBiz.propObserver" :prop="wxsProp">
<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
<view class="downwarp-tip">{{downText}}</view>
</view>
</view>
</view>
<!-- 列表内容 -->
<slot></slot>
<!-- 列表内容 -->
<slot></slot>
<!-- 空布局 -->
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
<!-- 空布局 -->
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view v-if="mescroll.optUp.use && !isDownLoading" class="mescroll-upwarp" :style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType===1">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType===1">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
<!-- 无数据 -->
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
<!-- #ifdef H5 -->
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
<!-- #endif -->
<!-- 适配iPhoneX -->
<view v-if="safearea" class="mescroll-safearea"></view>
</view>
</scroll-view>
@ -36,6 +55,21 @@
</view>
</template>
<!-- 微信小程序, app, h5使用wxs -->
<!-- #ifdef MP-WEIXIN || APP-PLUS || H5-->
<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
<!-- #endif -->
<!-- app, h5使用renderjs -->
<!-- #ifdef APP-PLUS || H5 -->
<script module="renderBiz" lang="renderjs">
import renderBiz from './wxs/renderjs.js';
export default {
mixins:[renderBiz]
}
</script>
<!-- #endif -->
<script>
// mescroll-uni.js,
import MeScroll from './mescroll-uni.js';
@ -45,8 +79,11 @@
import MescrollEmpty from './components/mescroll-empty.vue';
//
import MescrollTop from './components/mescroll-top.vue';
// wxs(renderjs)mixins
import WxsMixin from './wxs/mixins.js';
export default {
mixins: [WxsMixin],
components: {
MescrollEmpty,
MescrollTop
@ -54,11 +91,11 @@
data() {
return {
mescroll: {optDown:{},optUp:{}}, // mescroll
viewId: 'id_' + Math.random().toString(36).substr(2), // mescrollid(,)
viewId: 'id_' + Math.random().toString(36).substr(2,16), // mescrollid(,)
downHight: 0, //:
downRate: 0, // (inOffset: rate<1; outOffset: rate>=1)
downLoadType: 4, // inOffset1 outOffset2 showLoading3 endDownScroll4
upLoadType: 0, // 0loading1loading2
downLoadType: 0, // : 0(loading), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
upLoadType: 0, // : 0(loading), 1loading, 2,END, 3(,END)
isShowEmpty: false, //
isShowToTop: false, //
scrollTop: 0, //
@ -67,7 +104,6 @@
windowBottom: 0, // 使
windowHeight: 0, // 使
statusBarHeight: 0, //
isSafearea: false, //
scrollToViewId: '' // viewid
}
},
@ -75,16 +111,18 @@
down: Object, //
up: Object, //
top: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
topbar: Boolean, // top, false (使:,)
topbar: [Boolean, String], // top, false (使:,, ,,,)
bottom: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
safearea: Boolean, // bottom, false (iPhoneX使)
fixed: { // fixedmescroll, true
type: Boolean,
default () {
return true
}
default: true
},
height: [String, Number] // mescroll, ,使fixed. (20, "20rpx", "20px", "20%", rpx, windowHeight)
height: [String, Number], // mescroll, ,使fixed. (20, "20rpx", "20px", "20%", rpx, windowHeight)
bottombar:{ // TabBar(H5tab)
type: Boolean,
default: true
}
},
computed: {
// 使fixed (height,使)
@ -103,7 +141,7 @@
},
// (px)
numTop() {
return this.toPx(this.top) + (this.topbar ? this.statusBarHeight : 0)
return this.toPx(this.top)
},
fixedTop() {
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
@ -118,21 +156,9 @@
fixedBottom() {
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
},
fixedBottomConstant(){
return this.isSafearea ? "calc("+this.fixedBottom+" + constant(safe-area-inset-bottom))" : this.fixedBottom
},
fixedBottomEnv(){
return this.isSafearea ? "calc("+this.fixedBottom+" + env(safe-area-inset-bottom))" : this.fixedBottom
},
padBottom() {
return !this.isFixed ? this.numBottom + 'px' : 0
},
padBottomConstant(){
return this.isSafearea ? "calc("+this.padBottom+" + constant(safe-area-inset-bottom))" : this.padBottom
},
padBottomEnv(){
return this.isSafearea ? "calc("+this.padBottom+" + env(safe-area-inset-bottom))" : this.padBottom
},
//
isDownReset(){
return this.downLoadType===3 || this.downLoadType===4
@ -144,6 +170,10 @@
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform使fixed,fixedmescroll
},
//
scrollable(){
return this.downLoadType===0 || this.isDownReset
},
//
isDownLoading(){
return this.downLoadType === 3
@ -154,6 +184,7 @@
},
//
downText(){
if(!this.mescroll) return ""; //
switch (this.downLoadType){
case 1: return this.mescroll.optDown.textInOffset;
case 2: return this.mescroll.optDown.textOutOffset;
@ -189,18 +220,6 @@
this.$emit('scroll', this.mescroll) // this.mescroll.scrollTop; this.mescroll.isScrollUp
})
},
//touchstart,
touchstartEvent(e) {
this.mescroll.touchstartEvent(e);
},
//touchmove,
touchmoveEvent(e) {
this.mescroll.touchmoveEvent(e);
},
//touchend,
touchendEvent(e) {
this.mescroll.touchendEvent(e);
},
//
emptyClick() {
this.$emit('emptyclick', this.mescroll)
@ -215,7 +234,11 @@
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
this.isExec = true; //
this.$nextTick(() => { // dom
let view = uni.createSelectorQuery().in(this).select('#' + this.viewId);
let query = uni.createSelectorQuery();
// #ifndef MP-ALIPAY
query = query.in(this) // in(this),in(this),
// #endif
let view = query.select('#' + this.viewId);
view.boundingClientRect(data => {
this.isExec = false;
if (data) {
@ -238,10 +261,10 @@
let diyOption = {
//
down: {
inOffset(mescroll) {
inOffset() {
vm.downLoadType = 1; // offset (mescroll,)
},
outOffset(mescroll) {
outOffset() {
vm.downLoadType = 2; // offset (mescroll,)
},
onMoving(mescroll, rate, downHight) {
@ -253,9 +276,13 @@
vm.downLoadType = 3; // (mescroll,)
vm.downHight = downHight; // (mescroll,)
},
endDownScroll(mescroll) {
endDownScroll() {
vm.downLoadType = 4; // (mescroll,)
vm.downHight = 0; // (mescroll,)
vm.downResetTimer && clearTimeout(vm.downResetTimer)
vm.downResetTimer = setTimeout(()=>{ // ,0,便this.transition,iOS
if(vm.downLoadType===4) vm.downLoadType = 0
},300)
},
//
callback: function(mescroll) {
@ -273,8 +300,8 @@
vm.upLoadType = 2;
},
//
hideUpScroll() {
vm.upLoadType = 0;
hideUpScroll(mescroll) {
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
},
//
empty: {
@ -298,10 +325,7 @@
}
MeScroll.extend(diyOption, GlobalOption); //
let myOption = JSON.parse(JSON.stringify({
'down': vm.down,
'up': vm.up
})) // ,props
let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
@ -323,7 +347,33 @@
vm.mescroll.resetScrollTo((y, t) => {
vm.scrollAnim = (t !== 0); // t0,使
if(typeof y === 'string'){ // ,使scroll-into-view
vm.scrollToViewId = y;
// #ifdef MP-WEIXIN
// slotscroll-into-view,
uni.createSelectorQuery().select('#'+vm.viewId).boundingClientRect(function(rect){
let mescrollTop = rect.top // mescroll
uni.createSelectorQuery().select('#'+y).boundingClientRect(function(rect){
let curY = vm.mescroll.getScrollTop()
let top = rect.top - mescrollTop
top += curY
if(!vm.isFixed) top -= vm.numTop
vm.scrollTop = curY;
vm.$nextTick(function() {
vm.scrollTop = top
})
}).exec()
}).exec()
// #endif
// #ifndef MP-WEIXIN
if (vm.scrollToViewId != y) {
vm.scrollToViewId = y;
} else{
vm.scrollToViewId = ''; // scrollToViewId,
vm.$nextTick(function(){
vm.scrollToViewId = y;
})
}
// #endif
return;
}
let curY = vm.mescroll.getScrollTop()
@ -340,14 +390,8 @@
})
// up.toTop.safearea,vuesafearea
if(sys.platform == "ios"){
vm.isSafearea = vm.safearea;
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
}else{
vm.isSafearea = false
vm.mescroll.optUp.toTop.safearea = false
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
},
mounted() {

7
node_modules/mescroll-uni/mixins/mescroll-more-item.js

@ -2,6 +2,8 @@
* mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例)
*/
const MescrollMoreItemMixin = {
// 支付宝小程序不支持props的mixin,需写在具体的页面中
// #ifndef MP-ALIPAY
props:{
i: Number, // 每个tab页的专属下标
index: { // 当前tab的下标
@ -11,6 +13,7 @@ const MescrollMoreItemMixin = {
}
}
},
// #endif
data() {
return {
downOption:{
@ -32,10 +35,10 @@ const MescrollMoreItemMixin = {
}
},
methods: {
// mescroll组件初始化的回调,可获取到mescroll对象
// mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit)
mescrollInit(mescroll) {
this.mescroll = mescroll;
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序 (mescroll-mixins.js)
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序
// 自动加载当前tab的数据
if(this.i === this.index){
this.isInit = true; // 标记为true

35
node_modules/mescroll-uni/package.json

@ -1,32 +1,35 @@
{
"_from": "mescroll-uni@^1.2.5",
"_id": "mescroll-uni@1.2.5",
"_args": [
[
"mescroll-uni@1.3.0",
"D:\\lehuo_ketang_test"
]
],
"_from": "mescroll-uni@1.3.0",
"_id": "mescroll-uni@1.3.0",
"_inBundle": false,
"_integrity": "sha512-U/ifYhAeFNo9km5ez+/hvvMpNgKxuueuEfqtN7oltDYmGbe5OMFQ4YCdrpQ3lPQ6KHmmoDEy1QaWwAum5lHehQ==",
"_location": "/cnyanglao_uni_app/mescroll-uni",
"_integrity": "sha1-wRVG2klZPBgIpkWa3+NnN2Tnr/s=",
"_location": "/mescroll-uni",
"_phantomChildren": {},
"_requested": {
"type": "range",
"type": "version",
"registry": true,
"raw": "mescroll-uni@^1.2.5",
"raw": "mescroll-uni@1.3.0",
"name": "mescroll-uni",
"escapedName": "mescroll-uni",
"rawSpec": "^1.2.5",
"rawSpec": "1.3.0",
"saveSpec": null,
"fetchSpec": "^1.2.5"
"fetchSpec": "1.3.0"
},
"_requiredBy": [
"/cnyanglao_uni_app"
"/"
],
"_resolved": "https://registry.npmjs.org/mescroll-uni/-/mescroll-uni-1.2.5.tgz",
"_shasum": "695806682797319f4cd1f2a7222e6e491af4a0a1",
"_spec": "mescroll-uni@^1.2.5",
"_where": "/Users/xinyihao/源代码/cnyanglao_uni_app",
"_resolved": "https://registry.npm.taobao.org/mescroll-uni/download/mescroll-uni-1.3.0.tgz",
"_spec": "1.3.0",
"_where": "D:\\lehuo_ketang_test",
"author": {
"name": "wenju"
},
"bundleDependencies": false,
"deprecated": false,
"description": "mescroll的uni版本 -支持uni-app的下拉刷新上拉加载组件,支持原生页面和局部区域滚动",
"license": "MIT",
"main": "mescroll-uni.vue",
@ -34,5 +37,5 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"version": "1.2.5"
"version": "1.3.0"
}

11
pages/easy/easy.vue

@ -68,7 +68,14 @@
<mescroll-empty v-if="noteList.length==0" :options="emptyOption"></mescroll-empty>
</mescroll-body>
<view class="foot" v-show="showInput">
<chat-input @send-message="send_comment" :show="showInput" @blur="blur" :focus="focus" :placeholder="input_placeholder"></chat-input>
<chat-input
@send-message="send_comment"
:show="showInput"
@blur="blur"
:focus="focus"
:placeholder="input_placeholder"
:hasVideo="true"
/>
</view>
<yxyl-fab @trigger="gotoPublish"><image src="https://img-ali.cnyanglao.com/easyAdd.png-w100" mode="widthFix"></image></yxyl-fab>
<yxyl-fab @trigger="gotoOwn" v-if="!isOwn" vertical="middle"><image src="https://img-ali.cnyanglao.com/easyOwn.png-w100" mode="widthFix"></image></yxyl-fab>
@ -244,7 +251,7 @@ export default {
this.init_input();
},
init_input() {
this.showInput = false;
// this.showInput = false;
this.focus = false;
this.input_placeholder = '评论';
this.is_reply = false;

1
static/icons/pause.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670316877806" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2172" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 1024A512 512 0 1 1 512 0a512 512 0 0 1 0 1024zM320 320v384h128V320H320z m256 0v384h128V320H576z" fill="#f44336" p-id="2173"></path></svg>

After

Width:  |  Height:  |  Size: 475 B

1
static/icons/play.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670316906752" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3121" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 1024A512 512 0 1 1 512 0a512 512 0 0 1 0 1024zM383.232 287.616v448l384-223.104-384-224.896z" fill="#f44336" p-id="3122"></path></svg>

After

Width:  |  Height:  |  Size: 471 B

1
static/img/keyboard.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670293227085" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3639" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 74.666667C270.933333 74.666667 74.666667 270.933333 74.666667 512S270.933333 949.333333 512 949.333333 949.333333 753.066667 949.333333 512 753.066667 74.666667 512 74.666667z m0 810.666666c-204.8 0-373.333333-168.533333-373.333333-373.333333S307.2 138.666667 512 138.666667 885.333333 307.2 885.333333 512 716.8 885.333333 512 885.333333z" p-id="3640" fill="#515151"></path><path d="M448 437.333333c17.066667 0 32-14.933333 32-32v-42.666666c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v42.666666c0 17.066667 14.933333 32 32 32zM576 437.333333c17.066667 0 32-14.933333 32-32v-42.666666c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v42.666666c0 17.066667 14.933333 32 32 32zM320 437.333333c17.066667 0 32-14.933333 32-32v-42.666666c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v42.666666c0 17.066667 14.933333 32 32 32zM704 330.666667c-17.066667 0-32 14.933333-32 32v42.666666c0 17.066667 14.933333 32 32 32s32-14.933333 32-32v-42.666666c0-17.066667-14.933333-32-32-32zM448 586.666667c17.066667 0 32-14.933333 32-32v-42.666667c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v42.666667c0 17.066667 14.933333 32 32 32zM576 586.666667c17.066667 0 32-14.933333 32-32v-42.666667c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v42.666667c0 17.066667 14.933333 32 32 32zM352 554.666667v-42.666667c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v42.666667c0 17.066667 14.933333 32 32 32s32-14.933333 32-32zM704 480c-17.066667 0-32 14.933333-32 32v42.666667c0 17.066667 14.933333 32 32 32s32-14.933333 32-32v-42.666667c0-17.066667-14.933333-32-32-32zM682.666667 650.666667H341.333333c-17.066667 0-32 14.933333-32 32s14.933333 32 32 32h341.333334c17.066667 0 32-14.933333 32-32s-14.933333-32-32-32z" p-id="3641" fill="#515151"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

1
static/img/sound.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670292421411" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512.128 960.544c-247.04 0-448-200.96-448-448s200.96-448 448-448 448 200.96 448 448S759.168 960.544 512.128 960.544zM512.128 128.544c-211.744 0-384 172.256-384 384 0 211.744 172.256 384 384 384 211.744 0 384-172.256 384-384C896.128 300.8 723.872 128.544 512.128 128.544zM575.136 768c-8.384 0-16.704-3.264-23.008-9.76-12.288-12.672-11.968-32.96 0.736-45.248 55.584-53.824 86.208-125.184 86.208-200.992 0-75.84-30.624-147.232-86.24-200.992-12.704-12.288-13.056-32.544-0.736-45.248 12.288-12.672 32.544-13.056 45.248-0.736 68.192 65.92 105.76 153.664 105.76 247.008 0 93.312-37.536 181.024-105.728 247.008C591.168 764.992 583.168 768 575.136 768zM480.288 703.296c-7.936 0-15.84-2.912-22.048-8.8-12.8-12.16-13.344-32.416-1.152-45.216 34.72-36.576 53.856-85.312 53.856-137.248 0-52.672-19.52-101.824-55.008-138.464-12.288-12.672-11.968-32.96 0.736-45.248 12.672-12.256 32.928-11.968 45.248 0.736 47.072 48.64 72.992 113.632 72.992 182.976 0 68.448-25.376 132.8-71.456 181.312C497.184 699.936 488.736 703.296 480.288 703.296zM384.96 621.088c-9.376 0-18.72-4.128-25.024-12.032-11.008-13.824-8.736-33.984 5.056-44.96 12.864-10.272 19.968-24.128 19.968-39.008 0-14.656-7.264-28.896-19.904-39.008-13.824-11.04-16.064-31.168-5.024-44.96 11.008-13.824 31.168-16.064 44.96-5.024 27.936 22.304 43.968 54.752 43.968 88.992s-16.032 66.688-44.032 88.992C399.008 618.816 391.968 621.088 384.96 621.088z" p-id="2696" fill="#515151"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

34
uni_modules/nb-voice-record/changelog.md

@ -0,0 +1,34 @@
## 1.0.8(2022-10-24)
### 修复小程序平台报错
## 1.0.7(2022-10-14)
### 修改获取权限时机
1.修改获取权限时机,避免由于未获得权限导致首次长按时无法取消录音;
2.优化APP端提示文案(APP端触发longpress事件后,如果手指没有任何移动,则此时松手无法监听到touchEnd事件,小程序端无此问题)
## 1.0.6(2022-09-26)
### 优化震动反馈
## 1.0.5(2022-09-25)
### 新增录音配置recordOptions
- 该配置各端支持情况不同,请自行查看官方说明
- 其中duration为录音时长(最大10分钟),在超限时将自动结束动画并返回录音文件地址
## 1.0.4(2022-09-25)
### 内置发起录音
- H5端不支持录音,故无法使用,有需要可自行在gitee拉取老的纯动画版本
- 已支持多端录音,不再仅是动画效果
- 多端自动判断是否拥有权限(无权限时进行toast提示)
- endRecord回调附带录音文件临时地址(详见下方示例)
## 1.0.3(2022-09-25)
### 增加震动反馈
- 已条件编译、增加支持微信小程序
## 1.0.2(2022-09-25)
### 增加震动反馈
- 已条件编译、仅支持app
## 1.0.1(2022-09-25)
### 新增主动通知组件结束方法
- 如:当录音时长超限时,可主动通知组件结束动画,此时组件会自动回调endRecord事件,正常处理即可。
## 1.0.0(2022-09-25)
### 首次发布
- 下边写不确定的只是没测试,请自行测试

425
uni_modules/nb-voice-record/components/nb-voice-record/nb-voice-record.vue

@ -0,0 +1,425 @@
<template>
<view>
<view
class="record-btn"
@longpress="startRecord"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="endRecord"
hover-class="record-btn-hover"
hover-start-time="200"
hover-stay-time="150"
:style="[btnStyle, { '--btn-hover-fontcolor': btnHoverFontcolor, '--btn-hover-bgcolor': btnHoverBgcolor }]"
>
{{ btnTextContent }}
</view>
<view
class="record-popup"
:style="{ '--popup-height': popupHeight, '--popup-width': upx2px(popupMaxWidth), '--popup-bottom': upx2px(popupFixBottom), '--popup-bg-color': popupBgColor }"
>
<view class="inner-content" v-if="recordPopupShow">
<view class="title">{{ popupTitle }}</view>
<view
class="voice-line-wrap"
v-if="recording"
:style="{ '--line-height': upx2px(lineHeight), '--line-start-color': lineStartColor, '--line-end-color': lineEndColor }"
>
<view class="voice-line one"></view>
<view class="voice-line two"></view>
<view class="voice-line three"></view>
<view class="voice-line four"></view>
<view class="voice-line five"></view>
<view class="voice-line six"></view>
<view class="voice-line seven"></view>
<view class="voice-line six"></view>
<view class="voice-line five"></view>
<view class="voice-line four"></view>
<view class="voice-line three"></view>
<view class="voice-line two"></view>
<view class="voice-line one"></view>
</view>
<view class="cancel-icon" v-if="!recording">+</view>
<view class="tips">{{ recording ? popupDefaultTips : popupCancelTips }}</view>
</view>
</view>
</view>
</template>
<script>
var that;
const recorderManager = uni.getRecorderManager();
// #ifdef APP-PLUS
//
import permision from '../../js_sdk/wa-permission/permission.js';
// #endif
export default {
name: 'nbVoiceRecord',
/**
* 录音交互动效组件
* @property {Object} recordOptions 录音配置
* @property {Object} btnStyle 按钮样式
* @property {Object} btnHoverFontcolor 按钮长按时字体颜色
* @property {String} btnHoverBgcolor 按钮长按时背景颜色
* @property {String} btnDefaultText 按钮初始文字
* @property {String} btnRecordingText 录制时按钮文字
* @property {Boolean} vibrate 弹窗时是否震动
* @property {String} popupTitle 弹窗顶部文字
* @property {String} popupDefaultTips 录制时弹窗底部提示
* @property {String} popupCancelTips 滑动取消时弹窗底部提示
* @property {String} popupMaxWidth 弹窗展开后宽度
* @property {String} popupMaxHeight 弹窗展开后高度
* @property {String} popupFixBottom 弹窗展开后距底部高度
* @property {String} popupBgColor 弹窗背景颜色
* @property {String} lineHeight 声波高度
* @property {String} lineStartColor 声波波谷时颜色色值
* @property {String} lineEndColor 声波波峰时颜色色值
* @event {Function} startRecord 开始录音回调
* @event {Function} endRecord 结束录音回调
* @event {Function} cancelRecord 滑动取消录音回调
* @event {Function} stopRecord 主动停止录音
*/
props: {
recordOptions: {
type: Object,
default() {
return {
duration: 600000
}; // 使
}
},
btnStyle: {
type: Object,
default() {
return {
width: '580rpx',
height: '180rpx',
borderRadius: '20rpx',
backgroundColor: '#fff',
border: '1rpx solid whitesmoke',
permisionState: false
};
}
},
btnHoverFontcolor: {
type: String,
default: '#000' // 16
},
btnHoverBgcolor: {
type: String,
default: 'whitesmoke' // 16
},
btnDefaultText: {
type: String,
default: '长按开始录音'
},
btnRecordingText: {
type: String,
default: '录音中'
},
vibrate: {
type: Boolean,
default: true
},
popupTitle: {
type: String,
default: '正在录制音频'
},
popupDefaultTips: {
type: String,
default: '松手完成录音'
},
popupCancelTips: {
type: String,
default: '松手取消录音'
},
popupMaxWidth: {
type: Number,
default: 600 // rpx
},
popupMaxHeight: {
type: Number,
default: 180 // rpx
},
popupFixBottom: {
type: Number,
default: 300 // rpx
},
popupBgColor: {
type: String,
default: 'whitesmoke'
},
lineHeight: {
type: Number,
default: 50 // rpx
},
lineStartColor: {
type: String,
default: 'royalblue' // 16
},
lineEndColor: {
type: String,
default: 'indianred' // 16
}
},
data() {
return {
stopStatus: false, //
btnTextContent: this.btnDefaultText,
startTouchData: {},
popupHeight: '0px', //
recording: true, //
recordPopupShow: false,
recordTimeout: null //
};
},
created() {
that = this;
//
this.checkPermission();
recorderManager.onStop(res => {
//
if (that.recordTimeout !== null) {
//
clearTimeout(that.recordTimeout);
that.recordTimeout = null; //
}
//
if (that.recording) {
that.$emit('endRecord', res);
} else {
//
that.recording = true; //
that.$emit('cancelRecord');
}
});
recorderManager.onError(err => {
console.log('err:', err);
});
},
computed: {},
methods: {
upx2px(upx) {
return uni.upx2px(upx) + 'px';
},
async checkPermission() {
// #ifdef APP-PLUS
// os
let os = uni.getSystemInfoSync().osName;
if (os == 'ios') {
this.permisionState = await permision.judgeIosPermission('record');
} else {
this.permisionState = await permision.requestAndroidPermission('android.permission.RECORD_AUDIO');
}
if (this.permisionState !== true && this.permisionState !== 1) {
uni.showToast({
title: '请先授权使用录音',
icon: 'none'
});
return;
}
// #endif
// #ifndef APP-PLUS
uni.authorize({
scope: 'scope.record',
success: () => {
this.permisionState = true;
},
fail() {
uni.showToast({
title: '请授权使用录音',
icon: 'none'
});
}
});
// #endif
},
startRecord() {
if (!this.permisionState) {
this.checkPermission();
return;
}
this.stopStatus = false;
setTimeout(() => {
this.popupHeight = this.upx2px(this.popupMaxHeight);
setTimeout(() => {
this.recordPopupShow = true;
this.btnTextContent = this.btnRecordingText;
if (this.vibrate) {
// #ifdef APP-PLUS
plus.device.vibrate(35);
// #endif
// #ifdef MP-WEIXIN
uni.vibrateShort();
// #endif
}
//
recorderManager.start(this.recordOptions);
//
this.recordTimeout = setTimeout(
() => {
//
this.stopRecord(); // end
this.recordTimeout = null; //
},
this.recordOptions.duration ? this.recordOptions.duration : 600000
);
this.$emit('startRecord');
}, 100);
}, 200);
},
endRecord() {
if (this.stopStatus) {
return;
}
this.popupHeight = '0px';
this.recordPopupShow = false;
this.btnTextContent = this.btnDefaultText;
recorderManager.stop();
},
stopRecord() {
//
this.endRecord();
this.stopStatus = true;
},
touchStart(e) {
this.startTouchData.clientX = e.changedTouches[0].clientX; //X
this.startTouchData.clientY = e.changedTouches[0].clientY; //Y
},
touchMove(e) {
let touchData = e.touches[0]; // Objcet
let moveX = touchData.clientX - this.startTouchData.clientX;
let moveY = touchData.clientY - this.startTouchData.clientY;
if (moveY < -50) {
if (this.vibrate && this.recording) {
// #ifdef APP-PLUS
plus.device.vibrate(35);
// #endif
// #ifdef MP-WEIXIN
uni.vibrateShort();
// #endif
}
this.recording = false;
} else {
this.recording = true;
}
}
}
};
</script>
<style lang="scss">
.record-btn {
color: #000;
font-size: 24rpx;
display: flex;
align-items: center;
justify-content: center;
transition: 0.25s all;
border: 1rpx solid whitesmoke;
}
.record-btn-hover {
color: var(--btn-hover-fontcolor) !important;
background-color: var(--btn-hover-bgcolor) !important;
}
.record-popup {
// position: absolute;
// bottom: var(--popup-bottom);
// left: calc(50vw - calc(var(--popup-width) / 2));
z-index: 1;
width: var(--popup-width);
height: var(--popup-height);
display: flex;
align-items: center;
justify-content: center;
border-radius: 10rpx;
// box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
background: var(--popup-bg-color);
color: #000;
transition: 0.2s height;
position: relative;
top: -100rpx;
.inner-content {
height: var(--popup-height);
font-size: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
.title {
font-weight: bold;
padding: 20rpx 0;
}
.tips {
color: #999;
padding: 20rpx 0;
}
}
}
.cancel-icon {
width: 100rpx;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 44rpx;
line-height: 44rpx;
background-color: pink;
border-radius: 50%;
transform: rotate(45deg);
}
.voice-line-wrap {
display: flex;
align-items: center;
.voice-line {
width: 5rpx;
height: var(--line-height);
border-radius: 3rpx;
margin: 0 5rpx;
}
.one {
animation: wave 0.4s 1s linear infinite alternate;
}
.two {
animation: wave 0.4s 0.9s linear infinite alternate;
}
.three {
animation: wave 0.4s 0.8s linear infinite alternate;
}
.four {
animation: wave 0.4s 0.7s linear infinite alternate;
}
.five {
animation: wave 0.4s 0.6s linear infinite alternate;
}
.six {
animation: wave 0.4s 0.5s linear infinite alternate;
}
.seven {
animation: wave 0.4s linear infinite alternate;
}
}
@keyframes wave {
0% {
transform: scale(1, 1);
background-color: var(--line-start-color);
}
100% {
transform: scale(1, 0.2);
background-color: var(--line-end-color);
}
}
</style>

272
uni_modules/nb-voice-record/js_sdk/wa-permission/permission.js

@ -0,0 +1,272 @@
/**
* 本模块封装了AndroidiOS的应用权限判断打开应用权限设置界面以及位置系统服务是否开启
*/
var isIos
// #ifdef APP-PLUS
isIos = (plus.os.name == "iOS")
// #endif
// 判断推送权限是否开启
function judgeIosPermissionPush() {
var result = false;
var UIApplication = plus.ios.import("UIApplication");
var app = UIApplication.sharedApplication();
var enabledTypes = 0;
if (app.currentUserNotificationSettings) {
var settings = app.currentUserNotificationSettings();
enabledTypes = settings.plusGetAttribute("types");
console.log("enabledTypes1:" + enabledTypes);
if (enabledTypes == 0) {
console.log("推送权限没有开启");
} else {
result = true;
console.log("已经开启推送功能!")
}
plus.ios.deleteObject(settings);
} else {
enabledTypes = app.enabledRemoteNotificationTypes();
if (enabledTypes == 0) {
console.log("推送权限没有开启!");
} else {
result = true;
console.log("已经开启推送功能!")
}
console.log("enabledTypes2:" + enabledTypes);
}
plus.ios.deleteObject(app);
plus.ios.deleteObject(UIApplication);
return result;
}
// 判断定位权限是否开启
function judgeIosPermissionLocation() {
var result = false;
var cllocationManger = plus.ios.import("CLLocationManager");
var status = cllocationManger.authorizationStatus();
result = (status != 2)
console.log("定位权限开启:" + result);
// 以下代码判断了手机设备的定位是否关闭,推荐另行使用方法 checkSystemEnableLocation
/* var enable = cllocationManger.locationServicesEnabled();
var status = cllocationManger.authorizationStatus();
console.log("enable:" + enable);
console.log("status:" + status);
if (enable && status != 2) {
result = true;
console.log("手机定位服务已开启且已授予定位权限");
} else {
console.log("手机系统的定位没有打开或未给予定位权限");
} */
plus.ios.deleteObject(cllocationManger);
return result;
}
// 判断麦克风权限是否开启
function judgeIosPermissionRecord() {
var result = false;
var avaudiosession = plus.ios.import("AVAudioSession");
var avaudio = avaudiosession.sharedInstance();
var permissionStatus = avaudio.recordPermission();
console.log("permissionStatus:" + permissionStatus);
if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
console.log("麦克风权限没有开启");
} else {
result = true;
console.log("麦克风权限已经开启");
}
plus.ios.deleteObject(avaudiosession);
return result;
}
// 判断相机权限是否开启
function judgeIosPermissionCamera() {
var result = false;
var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
console.log("authStatus:" + authStatus);
if (authStatus == 3) {
result = true;
console.log("相机权限已经开启");
} else {
console.log("相机权限没有开启");
}
plus.ios.deleteObject(AVCaptureDevice);
return result;
}
// 判断相册权限是否开启
function judgeIosPermissionPhotoLibrary() {
var result = false;
var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
var authStatus = PHPhotoLibrary.authorizationStatus();
console.log("authStatus:" + authStatus);
if (authStatus == 3) {
result = true;
console.log("相册权限已经开启");
} else {
console.log("相册权限没有开启");
}
plus.ios.deleteObject(PHPhotoLibrary);
return result;
}
// 判断通讯录权限是否开启
function judgeIosPermissionContact() {
var result = false;
var CNContactStore = plus.ios.import("CNContactStore");
var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
if (cnAuthStatus == 3) {
result = true;
console.log("通讯录权限已经开启");
} else {
console.log("通讯录权限没有开启");
}
plus.ios.deleteObject(CNContactStore);
return result;
}
// 判断日历权限是否开启
function judgeIosPermissionCalendar() {
var result = false;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
if (ekAuthStatus == 3) {
result = true;
console.log("日历权限已经开启");
} else {
console.log("日历权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return result;
}
// 判断备忘录权限是否开启
function judgeIosPermissionMemo() {
var result = false;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
if (ekAuthStatus == 3) {
result = true;
console.log("备忘录权限已经开启");
} else {
console.log("备忘录权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return result;
}
// Android权限查询
function requestAndroidPermission(permissionID) {
return new Promise((resolve, reject) => {
plus.android.requestPermissions(
[permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
function(resultObj) {
var result = 0;
for (var i = 0; i < resultObj.granted.length; i++) {
var grantedPermission = resultObj.granted[i];
console.log('已获取的权限:' + grantedPermission);
result = 1
}
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
var deniedPresentPermission = resultObj.deniedPresent[i];
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
result = 0
}
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
var deniedAlwaysPermission = resultObj.deniedAlways[i];
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
result = -1
}
resolve(result);
// 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限
// if (result != 1) {
// gotoAppPermissionSetting()
// }
},
function(error) {
console.log('申请权限错误:' + error.code + " = " + error.message);
resolve({
code: error.code,
message: error.message
});
}
);
});
}
// 使用一个方法,根据参数判断权限
function judgeIosPermission(permissionID) {
if (permissionID == "location") {
return judgeIosPermissionLocation()
} else if (permissionID == "camera") {
return judgeIosPermissionCamera()
} else if (permissionID == "photoLibrary") {
return judgeIosPermissionPhotoLibrary()
} else if (permissionID == "record") {
return judgeIosPermissionRecord()
} else if (permissionID == "push") {
return judgeIosPermissionPush()
} else if (permissionID == "contact") {
return judgeIosPermissionContact()
} else if (permissionID == "calendar") {
return judgeIosPermissionCalendar()
} else if (permissionID == "memo") {
return judgeIosPermissionMemo()
}
return false;
}
// 跳转到**应用**的权限页面
function gotoAppPermissionSetting() {
if (isIos) {
var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL");
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
var setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
// console.log(plus.device.vendor);
var Intent = plus.android.importClass("android.content.Intent");
var Settings = plus.android.importClass("android.provider.Settings");
var Uri = plus.android.importClass("android.net.Uri");
var mainActivity = plus.android.runtimeMainActivity();
var intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri);
mainActivity.startActivity(intent);
}
}
// 检查系统的设备服务是否开启
// var checkSystemEnableLocation = async function () {
function checkSystemEnableLocation() {
if (isIos) {
var result = false;
var cllocationManger = plus.ios.import("CLLocationManager");
var result = cllocationManger.locationServicesEnabled();
console.log("系统定位开启:" + result);
plus.ios.deleteObject(cllocationManger);
return result;
} else {
var context = plus.android.importClass("android.content.Context");
var locationManager = plus.android.importClass("android.location.LocationManager");
var main = plus.android.runtimeMainActivity();
var mainSvr = main.getSystemService(context.LOCATION_SERVICE);
var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);
console.log("系统定位开启:" + result);
return result
}
}
module.exports = {
judgeIosPermission: judgeIosPermission,
requestAndroidPermission: requestAndroidPermission,
checkSystemEnableLocation: checkSystemEnableLocation,
gotoAppPermissionSetting: gotoAppPermissionSetting
}

81
uni_modules/nb-voice-record/package.json

@ -0,0 +1,81 @@
{
"id": "nb-voice-record",
"displayName": "nbVoiceRecord长按录音动画组件,多端权限判断,可监听开始、结束、取消事件",
"version": "1.0.8",
"description": "无多余依赖、纯css动画,支持多端权限判断,自动发起录音并返回录音文件地址,自定义项目多达16个,基本满足你所有需求",
"keywords": [
"长按,录音,动画组件"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://gitee.com/imboya/nb-voice-record"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "u"
},
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "n",
"Android Browser": "n",
"微信浏览器(Android)": "n",
"QQ浏览器(Android)": "n"
},
"H5-pc": {
"Chrome": "n",
"IE": "n",
"Edge": "n",
"Firefox": "n",
"Safari": "n"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

86
uni_modules/nb-voice-record/readme.md

@ -0,0 +1,86 @@
### nbVoiceRecord概述
- 这是个基于uni-app 符合uni_modules 的插件
- 无任何依赖、纯css动画
- nb是NeverBug的意思
### 主要功能
- 长按组件后弹出录音弹窗,松手完成录音,手指向上滑动可取消;
- 支持各种自定义,如弹窗高度、宽度、各处文字甚至声纹波形的尺寸和颜色;
- 已完成多端适配,自动根据授权情况提示完成授权、已获得授权才开始录音
- endRecord回调事件附带录音文件
### 动画预览
- 默认样式
![默认样式](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-613ff9e2-568b-4845-987f-93626e21bcde/84cf3c4f-f4f2-41e6-bb82-1414465a944d.gif)
- 自定义按钮为圆形(红背景、白字)、弹窗为正方形
![正方形](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-613ff9e2-568b-4845-987f-93626e21bcde/893bf1d6-593f-40e6-aeff-12afe4ebbc37.gif)
### 基本用法:
```
<template>
<view>
<nb-voice-record @startRecord="start" @endRecord="end" @cancelRecord="cancel"></nb-voice-record>
</view>
</template>
<script>
methods: {
start() {
// 开始录音
},
end(event) {
// 结束录音并处理得到的录音文件
// event中,app端仅有tempFilePath字段,微信小程序还有duration和fileSize两个字段
},
cancel() {
// 用户取消录音
}
}
</script>
```
### 全部支持参数
| 参数名 | 类型 | 默认值 | 作用 | 注意事项 |
| ----- | ----- | ------ | ------- | --- |
| recordOptions | Object | {duration:60000} | 录音配置 |各端支持情况不同,请自行查看[官方说明](https://uniapp.dcloud.net.cn/api/media/record-manager.html#getrecordermanager) |
| btnStyle | Object | 请查看源码 | 按钮样式 |对象格式 |
| btnHoverFontcolor | String | #000 | 按钮长按时文字颜色 | |
| btnHoverBgcolor | String | whitesmoke | 按钮长按时背景颜色 | |
| btnDefaultText | String | 长按开始录音 | 初始按钮文字 | |
| btnRecordingText | String | 录音中 | 录制时按钮文字 | |
| vibrate | Boolean | true | 震动反馈 | 弹窗、滑动取消时 |
| popupTitle | String | 正在录制音频 | 弹窗顶部文字 | |
| popupDefaultTips | String | 松手完成录音 | 录制时弹窗底部提示 | |
| popupCancelTips | String | 松手取消录音 | 滑动取消时弹窗底部提示 | |
| popupMaxWidth | Number | 600 | 弹窗展开后宽度 |注意这里几个单位都是rpx |
| popupMaxHeight | Number | 300 | 弹窗展开后高度 | |
| popupFixBottom | Number | 200 | 弹窗展开后距底部高度 | |
| popupBgColor | String | whitesmoke | 弹窗背景颜色 | |
| lineHeight | Number | 50 | 声波高度 | |
| lineStartColor | String | royalblue | 声波波谷时颜色色值 | 色值或者颜色名均可 |
| lineEndColor | String | indianred | 声波波峰时颜色色值 | |
### 作者其他插件
- [bwinBrand多端自适应企业官网、uniCloud云端一体【用户端】](https://ext.dcloud.net.cn/plugin?id=7821)
- [bwinBrand多端自适应企业官网、uniCloud云端一体【管理端】](https://ext.dcloud.net.cn/plugin?id=7822)
- [bwinAgent多端、多项目全民经纪人、uniCloud云端一体【经纪人端】](https://ext.dcloud.net.cn/plugin?id=8606)
- [bwinAgent多端、多项目全民经纪人、uniCloud云端一体【管理员端】](https://ext.dcloud.net.cn/plugin?id=8607)
- [必闻优学,教育培训机构模板(单校区版,纯模板)](https://ext.dcloud.net.cn/plugin?id=7709)
### 一个有趣的社区
- [NeverBug.cn 弹幕式互动社区](https://neverbug.cn)
### 联系作者
- QQ:123060128
- Email:karma.zhao@gmail.com
- 官网:https://brand.neverbug.cn
Loading…
Cancel
Save