Browse Source

[development] 随笔添加语音功能02

master
JC 1 year ago
parent
commit
e88be2dfd1
  1. 44
      components/audio-recorder/recorder.vue
  2. 32
      components/h-custom-audio/index.vue
  3. 334
      components/im-chat/chatinput.vue
  4. 21
      pages.json
  5. 11
      pages/easy/easy.vue
  6. 8
      pages/easy/publish.vue
  7. 360
      pages/qa/publish.vue
  8. 585
      pages/qa/qa.vue
  9. 20
      uni_modules/nb-voice-record/components/nb-voice-record/nb-voice-record.vue

44
components/audio-recorder/recorder.vue

@ -1,36 +1,31 @@
<template>
<view>
<h-audio
v-if="audioPath"
:audio="audioPath"
:time="duration"
sliderColor="#f44336"
activeColor="#f44336"
backgroundColor="#fff"
/>
<view class="audio-recorder-container">
<slot></slot>
<nb-voice-record
@startRecord="start"
@endRecord="end"
@cancelRecord="cancel"
:popupFixBottom="220"
:btnStyle="{
width: '200rpx',
height: '200rpx',
borderRadius: '50%',
backgroundColor: '#fff',
border: '1rpx solid #f44336',
color: '#f44336',
permisionState: false,
position: 'absolute',
left: 'calc((100% - 200rpx) / 2)',
bottom: '0'
}"
>
</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() {
//
@ -38,9 +33,7 @@
end(event) {
//
// eventapptempFilePathdurationfileSize
this.audioPath = event.tempFilePath;
this.duration = event.duration / 1000;
this.$emit('end', this.audioPath);
this.$emit('end', event);
},
cancel() {
//
@ -49,5 +42,10 @@
}
</script>
<style lang="scss">
<style lang="scss" scoped>
.audio-recorder-container {
width: 100%;
position: fixed;
bottom: 120rpx;
}
</style>

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

@ -1,5 +1,5 @@
<template>
<view :class="['audio-card', 'audio-toolbar']">
<view class="audio-card" :style="style">
<view v-if="title" class="title">{{ title }}</view>
<view class="audio">
<image
@ -17,7 +17,7 @@
<view class="audio-main">
<slider
max="100"
block-size="16"
block-size="10"
class="progress"
:block-color="sliderColor"
:activeColor="activeColor"
@ -41,6 +41,12 @@ export default {
type: String,
default: ''
},
style: {
type: Object,
default() {
return {};
}
},
audio: {
type: String,
default: ''
@ -93,7 +99,13 @@ export default {
},
deep: true,
immediate: true
}
},
time: {
handler(val) {
this.duration = val < 1 ? 1 : val;
},
immediate: true
}
},
destroyed () {
this.innerAudioContext.destroy()
@ -106,7 +118,7 @@ export default {
this.innerAudioContext.startTime = 0
this.innerAudioContext.onTimeUpdate(() => {})
this.innerAudioContext.onCanplay(() => {
this.duration = this.innerAudioContext.duration || this.time
this.duration = this.innerAudioContext.duration || this.time;
})
this.innerAudioContext.play()
this.innerAudioContext.volume = 0
@ -175,8 +187,6 @@ export default {
display: flex;
flex-direction: column;
padding: 20rpx 24rpx;
background-color: #F4F5F6;
border-top: 1px solid #bbb;
.title {
color: #333333;
font-size: 26rpx;
@ -185,9 +195,13 @@ export default {
.audio {
margin-top: 15rpx;
display: flex;
align-items: center;
.play-icon {
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
position: relative;
top: -6rpx;
}
.audio-main {
height: 48rpx;
@ -210,10 +224,4 @@ export default {
}
}
}
.audio-toolbar{
position: fixed;
left: 0;
right: 0;
bottom: 200rpx;
}
</style>

334
components/im-chat/chatinput.vue

@ -1,65 +1,90 @@
<template>
<!-- #ifdef MP-WEIXIN -->
<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
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 class="footer">
<view class="record-list">
<view v-for="audio in recordList" :key="audio.tempFilePath" class="record-item">
<h-audio
:audio="audio.tempFilePath"
:time="audio.duration / 1000"
sliderColor="#f44336"
activeColor="#f44336"
backgroundColor="#fff"
:style="{
flex: 1
}"
/>
<view class="record-delete" @tap="handleDeleteRecord(audio)">删除</view>
</view>
</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
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 class="content-container">
<!-- #ifdef MP-WEIXIN -->
<view class="footer-center">
<textarea
v-if="isKeyboard"
class="input-text"
:fixed="true"
:cursor-spacing="100"
@blur="blur"
@confirm="sendMessge"
:show-confirm-bar="false"
v-model="inputValue"
:focus="focus"
:maxlength="maxlength"
:placeholder="placeholder"
/>
<nb-voice-record
v-else
@endRecord="handleAudioEnd"
/>
</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="footer-center" v-clickoutside='blur'>
<textarea
v-if="isKeyboard"
class="input-text"
@blur="blur"
@confirm="sendMessge"
v-model="inputValue"
:focus="focus"
:maxlength="maxlength"
:placeholder="placeholder"
/>
<nb-voice-record
v-else
@endRecord="handleAudioEnd"
/>
</view>
<!-- #endif -->
<view class="footer-right">
<image
id="sound"
v-if="hasVideo && !isKeyboard"
class="sound-logo"
src="../../static/img/sound.svg"
@tap="isKeyboard=true"
/>
<image
id="keyboard"
v-else-if="hasVideo && isKeyboard"
class="keyboard-logo"
src="../../static/img/keyboard.svg"
@tap="isKeyboard=false"
/>
<view id='msg-type' class="send-comment" @tap="sendMessge">发送</view>
</view>
</view>
</view>
</template>
<script>
import AudioRecorder from '@/components/audio-recorder/recorder.vue';
import HAudio from '@/components/h-custom-audio/index.vue';
const clickoutside = {
//
bind(el, binding, vnode) {
function documentHandler(e) {
//
if (el.contains(e.target)) {
@ -83,20 +108,26 @@
delete el.__vueClickOutside__;
},
};
export default {
name: "chat-input",
components: {
AudioRecorder
HAudio
},
directives: {clickoutside},
data() {
return {
inputValue: '',
recordList: [],
isShow: false,
isKeyboard: true,
audioSource: ''
}
},
props:{
maxlength: {
type: Number,
default: -1
},
placeholder: {
type: String,
required: true
@ -119,24 +150,26 @@
this.isShow=newVal
}
},
directives: {clickoutside},
methods: {
blur: function() {//
// #ifdef MP-WEIXIN
this.isShow=!this.isShow;
// #endif
//
blur: function() {
if (this.hasVideo) {
return;
}
// #ifdef MP-WEIXIN
this.isShow=!this.isShow;
// #endif
if(!this.isShow){
this.$emit('blur')
}
else{
this.isShow=!this.isShow;
this.isShow=!this.isShow;
}
},
},
sendMessge: function (e) {
// TODO
if (!this.inputValue) {
if (!this.inputValue
&& this.recordList.length === 0) {
uni.showModal({
content:"还没有输入内容哦!",
showCancel:false
@ -147,84 +180,112 @@
//
this.$emit('send-message', {
type: 'text',
content: that.inputValue
content: that.inputValue,
recordList: that.recordList
});
that.inputValue = '';//
that.recordList = [];
},
//
handleAudioEnd(audioSource) {
this.audioSource = audioSource;
handleAudioEnd(source) {
const limit = 3;
if (this.recordList.length === limit) {
uni.showToast({
title: `音频总数限制为${limit}`,
icon: 'none',
mask: false,
duration: 2000
});
return;
}
this.recordList = this.recordList.concat(source);
const availableRecordNumber = limit - this.recordList.length;
uni.showToast({
title: `音频总数限制为${limit}个,当前还可以发送${availableRecordNumber}`,
icon: 'none',
mask: false,
duration: 2000
});
},
//
handleDeleteRecord({tempFilePath}) {
this.recordList = this.recordList.filter(item => item.tempFilePath !== tempFilePath);
}
}
}
</script>
<style lang="scss">
.footer {
display: flex;
width: 100%;
min-height: 200rpx;
border-top: solid 1px #bbb;
padding: 5upx;
background-color: #F4F5F6;
overflow-y: hidden;
}
.footer-left {
width: 80upx;
height: 200rpx;
display: flex;
justify-content: center;
align-items: center;
}
.footer-right {
width: 20%;
height: 200rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
color: #1482D1;
flex-grow: 0;
flex-shrink: 0;
}
.footer-center {
padding-left: 20upx;
width: 80%;
height: 200rpx;
display: flex;
justify-content: center;
align-items: center;
flex-grow: 0;
flex-shrink: 0;
z-index: 1;
}
.footer-center .input-text {
background: #fff;
/* border: solid 1upx #ddd; */
padding: 10upx !important;
font-family: verdana !important;
overflow: hidden;
border-radius: 15upx;
height: 80%;
width: 100%;
}
.footer-right .send-comment{
background-color: $uni-color-main-dark;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
color: #FFFFFF;
width: 60%;
height: 60upx;
border-radius: 5px;
font-size: 25rpx;
position: absolute;
bottom: 5rpx;
}
width: 100%;
border-top: solid 1px #bbb;
}
.content-container{
display: flex;width: 100%;
min-height: 200rpx;
padding: 5upx;
background-color: #F4F5F6;
overflow-y: hidden;
}
.footer-left {
width: 80upx;
height: 200rpx;
display: flex;
justify-content: center;
align-items: center;
}
.footer-right {
width: 20%;
height: 200rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
color: #1482D1;
flex-grow: 0;
flex-shrink: 0;
}
.footer-center {
padding-left: 20upx;
width: 80%;
height: 200rpx;
display: flex;
justify-content: center;
align-items: center;
flex-grow: 0;
flex-shrink: 0;
}
.footer-center .input-text {
background: #fff;
/* border: solid 1upx #ddd; */
padding: 10upx !important;
font-family: verdana !important;
overflow: hidden;
border-radius: 15upx;
height: 80%;
width: 100%;
}
.footer-right .send-comment{
background-color: $uni-color-main-dark;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
color: #FFFFFF;
width: 60%;
height: 60upx;
border-radius: 5px;
font-size: 25rpx;
position: absolute;
bottom: 5rpx;
}
.footer-right .upload{
width: 60rpx;
height: 60rpx;
position: absolute;
bottom: 5rpx;
}
.footer-right .sound-logo,
.footer-right .keyboard-logo{
width: 60rpx;
@ -232,4 +293,19 @@
position: absolute;
top: 20rpx;
}
.record-list {
.record-item {
background: #F4F5F6;
display: flex;
align-items: center;
box-sizing: border-box;
padding: 0 10rpx;
.record-delete{
margin-left: 20rpx;
color: #6699cc;
font-size: 28rpx;
}
}
}
</style>

21
pages.json

@ -3,7 +3,7 @@
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "乐活课堂",
"navigationBarTitleText": "乐活课堂",
"enablePullDownRefresh": true,
"h5": {
"titleNView": false
@ -11,6 +11,25 @@
}
},
{
"path": "pages/qa/qa",
"style": {
"navigationBarTitleText": "问答",
"enablePullDownRefresh": true,
"h5": {
"titleNView": false
}
}
},
{
"path": "pages/qa/publish",
"style": {
"navigationBarTitleText": "发布",
"h5": {
"titleNView": false
}
}
},
{
"path": "pages/category/category",
"style": {
"navigationBarTitleText": "分类",

11
pages/easy/easy.vue

@ -68,14 +68,7 @@
<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"
:hasVideo="true"
/>
<chat-input @send-message="send_comment" :show="showInput" @blur="blur" :focus="focus" :placeholder="input_placeholder"></chat-input>
</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>
@ -251,7 +244,7 @@ export default {
this.init_input();
},
init_input() {
// this.showInput = false;
this.showInput = false;
this.focus = false;
this.input_placeholder = '评论';
this.is_reply = false;

8
pages/easy/publish.vue

@ -1,7 +1,9 @@
<template>
<view class="container" @touchstart="touchStart" @touchend="touchEnd">
<form>
<view class="uni-textarea"><textarea class="textarea" placeholder="这一刻的想法..." v-model="input_content" maxlength="-1"/></view>
<view class="uni-textarea">
<textarea class="textarea" placeholder="这一刻的想法..." v-model="input_content" maxlength="-1"/>
</view>
<view class="uni-list list-pd">
<view class="uni-list-cell cell-pd">
<view class="uni-uploader">
@ -10,10 +12,8 @@
<view class="uni-uploader-info">{{ imageList.length }}/9</view>
</view>
<view class="uni-uploader-body">
<robby-image-upload v-model="imageList" :limit="9"></robby-image-upload>
</view>
</view>
</view>
</view>
</view>

360
pages/qa/publish.vue

@ -0,0 +1,360 @@
<template>
<view class="container" @touchstart="touchStart" @touchend="touchEnd">
<form>
<view class="uni-textarea">
<textarea
class="textarea"
placeholder="这一刻的想法..."
v-model="input_content"
maxlength="200"
/>
</view>
<view class="uni-record-list">
<view v-for="audio in recordList" :key="audio.tempFilePath" class="uni-record-item">
<h-audio
:audio="audio.tempFilePath"
:time="audio.duration / 1000"
sliderColor="#f44336"
activeColor="#f44336"
backgroundColor="#eee"
:style="{
flex: 1
}"
/>
<view class="uni-record-delete" @tap="handleDeleteRecord(audio)">删除</view>
</view>
</view>
<view class="uni-list list-pd">
<view class="uni-list-cell cell-pd">
<view class="uni-uploader">
<view class="uni-uploader-head">
<view class="uni-uploader-title"></view>
<view class="uni-uploader-info">{{ imageList.length }}/9</view>
</view>
<view class="uni-uploader-body">
<robby-image-upload v-model="imageList" :limit="9"></robby-image-upload>
</view>
</view>
</view>
</view>
<view class="footer"><button type="default" class="feedback-submit" @click="publish">提交</button></view>
</form>
<audio-recorder @end="handleRecordEnd"></audio-recorder>
</view>
</template>
<script>
import AudioRecorder from '@/components/audio-recorder/recorder.vue';
import HAudio from '@/components/h-custom-audio/index.vue';
import robbyImageUpload from '@/components/robby-image-upload/robby-image-upload.vue';
import { upload, uploadByBase64 } from '@/common/upload.js';
import Request from '@/util/pocky-request/index';
export default {
components: { robbyImageUpload, AudioRecorder, HAudio },
data() {
return {
imageList: [],
recordList: [],
input_content: '',
sourceTypeIndex: 2,
sizeTypeIndex: 2,
countIndex: 8,
count: [1, 2, 3, 4, 5, 6, 7, 8, 9],
//start
startX: 0, //
movedX: 0, //
endX: 0 //
//end
};
},
onLoad() {
// #ifdef H5
this.$wechat.hideMenu();
// #endif
},
methods: {
publish: async function() {
if (!this.input_content) {
uni.showModal({ content: '内容不能为空', showCancel: false });
return;
}
var that = this;
var uploadImages = [];
if (this.imageList.length > 0) {
// #ifdef MP-WEIXIN
var uploadImages = await upload(1, this.imageList);
// #endif
// #ifdef H5
var uploadImages = await uploadByBase64(1, this.imageList);
// #endif
}
if (this.recordList.length > 0) {
const _recordList = this.recordList.map(({tempFilePath}) => tempFilePath);
// #ifdef MP-WEIXIN
await upload(2, _recordList);
// #endif
// #ifdef H5
await uploadByBase64(2, _recordList);
// #endif
}
Request()
.request({
url: 'api/v1/QA/Create',
method: 'POST',
data: {
qaText: that.input_content,
imageUrlList: uploadImages,
recordUrlList: this.recordList.map(({duration, fileSize, tempFilePath}) => ({
url: tempFilePath,
size: fileSize,
duration
}))
}
})
.then(res => {
uni.showToast({
title:'发布成功',
icon:"success",
success() {
setTimeout(()=>{
uni.redirectTo({
url:'qa'
});
},1500)
}
})
});
},
close(e) {
this.imageList.splice(e, 1);
},
chooseImage: async function() {
if (this.imageList.length === 9) {
let isContinue = await this.isFullImg();
console.log('是否继续?', isContinue);
if (!isContinue) {
return;
}
}
uni.chooseImage({
sourceType: sourceType[this.sourceTypeIndex],
sizeType: sizeType[this.sizeTypeIndex],
count: this.imageList.length + this.count[this.countIndex] > 9 ? 9 - this.imageList.length : this.count[this.countIndex],
success: res => {
// #ifdef APP-PLUS
//,使H5+ Api,APP
var compressd = cp_images => {
this.imageList = this.imageList.concat(cp_images); //
};
image.compress(res.tempFilePaths, compressd);
// #endif
// #ifndef APP-PLUS
this.imageList = this.imageList.concat(res.tempFilePaths); //APP,,uni-appsizeType
// #endif
}
});
},
isFullImg: function() {
return new Promise(res => {
uni.showModal({
content: '已经有9张图片了,是否清空现有图片?',
success: e => {
if (e.confirm) {
this.imageList = [];
res(true);
} else {
res(false);
}
},
fail: () => {
res(false);
}
});
});
},
previewImage: function(e) {
var current = e.target.dataset.src;
uni.previewImage({
current: current,
urls: this.imageList
});
},
touchStart: function(e) {
this.startX = e.mp.changedTouches[0].pageX;
},
touchEnd: function(e) {
this.endX = e.mp.changedTouches[0].pageX;
if (this.endX - this.startX > 200) {
uni.navigateBack();
}
},
//
handleRecordEnd: function(source) {
const limit = 3;
if (this.recordList.length === limit) {
uni.showToast({
title: `音频总数限制为${limit}`,
icon: 'none',
mask: false,
duration: 2000
});
return;
}
this.recordList = this.recordList.concat(source);
const availableRecordNumber = limit - this.recordList.length;
uni.showToast({
title: `音频总数限制为${limit}个,当前还可以选择${availableRecordNumber}`,
icon: 'none',
mask: false,
duration: 2000
});
},
//
handleDeleteRecord: function({tempFilePath}) {
this.recordList = this.recordList.filter(item => item.tempFilePath !== tempFilePath);
}
}
};
</script>
<style lang="scss" scoped>
.footer {
position: fixed;
bottom: 0;
width: 100%;
}
.cell-pd {
padding: 20rpx 30rpx;
}
.uni-textarea {
width: auto;
padding: 20rpx 25rpx;
line-height: 1.6;
height: 250rpx;
}
.textarea{
height: 100%;
}
.uni-list::before {
height: 0;
}
.uni-list:after {
height: 0;
}
.list-pd {
margin-top: 0;
}
.close-view {
text-align: center;
line-height: 30rpx;
height: 35rpx;
width: 35rpx;
background: #ef5350;
color: #ffffff;
position: absolute;
top: 1rpx;
right: 1rpx;
font-size: 35rpx;
border-radius: 8rpx;
}
.container {
width: 750rpx;
height: 100%;
}
.uni-uploader {
flex: 1;
flex-direction: column;
}
.uni-uploader-head {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.uni-uploader-info {
color: #b2b2b2;
}
.uni-uploader-body {
margin-top: 16upx;
}
.uni-uploader__files {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.uni-uploader__file {
margin: 10upx;
width: 210upx;
height: 210upx;
}
.uni-uploader__img {
display: block;
width: 210upx;
height: 210upx;
}
.uni-uploader__input-box {
position: relative;
margin: 10upx;
width: 208upx;
height: 208upx;
border: 2upx solid #d9d9d9;
}
.uni-uploader__input-box:before,
.uni-uploader__input-box:after {
content: ' ';
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
background-color: #d9d9d9;
}
.uni-uploader__input-box:before {
width: 4upx;
height: 79upx;
}
.uni-uploader__input-box:after {
width: 79upx;
height: 4upx;
}
.uni-uploader__input-box:active {
border-color: #999999;
}
.uni-uploader__input-box:active:before,
.uni-uploader__input-box:active:after {
background-color: #999999;
}
.uni-uploader__input {
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
}
.feedback-submit {
background: $uni-color-main-dark;
border: none;
border-radius: 0;
color: #fff;
}
.uni-record-list {
}
.uni-record-item {
display: flex;
align-items: center;
margin: 10rpx 20rpx;
}
.uni-record-delete{
margin-left: 20rpx;
color: #6699cc;
font-size: 28rpx;
}
</style>

585
pages/qa/qa.vue

@ -0,0 +1,585 @@
<template>
<view @tap="handleGlobalClick">
<view class="container">
<mescroll-body :down="downOption" @init="mescrollInit" @down="downCallback" :up="upOption" @up="upCallback">
<yxyl-swiper :swiperItems="swiperItems"></yxyl-swiper>
<view class="easy-item" v-for="(item, index) in qaList" :key="index">
<view class="avatar">
<image :src="item.userLogo" mode="widthFix" @tap="goToUsers(item.userSN)"></image>
</view>
<view class="content">
<view class="content-title">
<text class="nick-name">{{ item.userName }}</text>
<view v-if="item.isReply" class="replay-marker">已答疑</view>
</view>
<text class="content-text" selectable="true">{{ item.qaText }}</text>
<view class="thumbnails" v-if="item.imageList && item.imageList.length > 0">
<view :class="item.imageList.length === 1 ? 'my-gallery' : 'thumbnail'" v-for="(image, index_images) in item.imageList" :key="index_images">
<image class="gallery_img" lazy-load mode="aspectFill" :src="image.url" :data-src="image" @tap="previewImage(item.imageList, index_images)"></image>
</view>
</view>
<view>
<view v-for="audio in item.userRecordList" :key="audio.url" class="audio-item">
<h-audio
:audio="audio.url"
:time="audio.duration / 1000"
sliderColor="#f44336"
activeColor="#f44336"
backgroundColor="#eee"
:style="{
flex: 1
}"
/>
</view>
</view>
<view class="sub">
<view class="datetime">{{ item.createTime }}</view>
<view v-if="isManage" class="operation">
<view class="delete" @tap="deleteItem(item.qasn, index)">
删除
</view>
<view class="mutiple" @tap="comment(item.qasn, index)">
评论
</view>
</view>
</view>
<view class="comment">
<view class="top-arrow" v-if="!!item.adminReply || item.adminRecordList.length"></view>
<view class="comment-list" v-if="!!item.adminReply || item.adminRecordList.length > 0">
<view class="comment-item">
<text class="comment-name">{{ item.adminName || '管理员' }}:</text>
<text class="comment-content">{{ item.adminReply }}</text>
<view v-for="audio in item.adminRecordList" :key="audio.url" class="audio-item">
<h-audio
:audio="audio.url"
:time="audio.duration / 1000"
sliderColor="#f44336"
activeColor="#f44336"
backgroundColor="#fff"
:style="{
flex: 1
}"
/>
</view>
<view class="comment-delete">
<text
class="comment-delete-btn"
v-if="isManage"
@tap="commentDelete(item.qasn, index)"
>
删除
</text>
</view>
</view>
</view>
</view>
</view>
</view>
<mescroll-empty v-if="qaList.length==0" :options="emptyOption"></mescroll-empty>
</mescroll-body>
<view class="foot" v-show="showInput">
<chat-input
@send-message="send_comment"
:show="showInput"
:focus="focus"
:placeholder="input_placeholder"
:hasVideo="true"
:maxlength="200"
/>
</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>
</view>
</view>
</template>
<script>
import yxylSwiper from '@/components/yxyl-swiper/yxyl-swiper.vue';
import faIcon from '@/components/kilvn-fa-icon/fa-icon.vue';
import MescrollBody from 'mescroll-uni/mescroll-body.vue'
import MescrollMixin from "mescroll-uni/mescroll-mixins.js";
import MescrollEmpty from 'mescroll-uni/components/mescroll-empty.vue';
import chatInput from '@/components/im-chat/chatinput.vue';
import HAudio from '@/components/h-custom-audio/index.vue';
import yxylFab from '@/components/yxyl-fab/yxyl-fab.vue';
import { getSwiper } from '@/api/swiper.js';
import { upload, uploadByBase64 } from '../../common/upload';
export default {
mixins: [MescrollMixin],
components: {
faIcon,
MescrollBody,
MescrollEmpty,
chatInput,
yxylFab,
yxylSwiper,
HAudio
},
data() {
return {
swiperItems: [],
qaList: [],
qaListIndex: -1,
isManage: false,
emptyOption:{
icon : null
},
//
downOption: {
use: true, // ; true,
auto: false,
native:true
},
//
upOption: {
use: true, // ; true
auto: false, // ; true
noMoreSize: 10, // 5'-- END --'
empty: false,
textNoMore: '---没有了---',
toTop: {
bottom: 250
}
},
input_placeholder: '评论',
is_reply: false, //
showInput: false, //
focus: false, //
focusItemSN: '',
fabPattern: {
buttonColor: '#F44336'
},
userSN: ''
};
},
computed: {
isOwn() {
return this.userSN == this.$store.state.user.userSn;
}
},
onLoad(e) {
// #ifndef H5
const updateManager = uni.getUpdateManager();
updateManager.onUpdateReady(function(res) {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success(res) {
if (res.confirm) {
// applyUpdate
updateManager.applyUpdate();
}
}
});
});
// #endif
var that = this;
if (e.userSN) {
this.userSN = e.userSN;
}
this.bindList().then(() => {
this.bindSwiper();
// #ifdef H5
that.$wechat.share({
link: that.handlerAppMessagePath()
});
// #endif
});
},
onShow() {},
onShareAppMessage(e) {
return {
path: this.handlerAppMessagePath()
};
},
methods: {
handleGlobalClick(e) {
const target = e.target.dataset.eventOpts?.[0]?.[0];
const clickoutside = target !== '^sendMessage' && target !== 'tap';
if (clickoutside) {
this.blur();
}
},
gotoOwn() {
uni.navigateTo({
url: 'qa?userSN=' + this.$store.state.user.userSn
});
},
bindSwiper() {
return getSwiper({ pageType: 5 }).then(res => {
this.swiperItems = res.swiperList;
});
},
goToUsers(userSN) {
uni.navigateTo({
url: 'easy?userSN=' + userSN
});
},
handlerAppMessagePath() {
return this.$handlerAppMessagePath();
},
previewImage(imageList, image_index) {
var current = imageList[image_index];
var urls = new Array();
imageList.forEach(item => {
urls.push(item.ourl);
});
uni.previewImage({
current: current.ourl,
urls: urls
});
},
downCallback() {
this.qaList = [];
Promise.all([this.bindList(), this.bindSwiper()])
.then(() => {
this.mescroll.endSuccess();
})
.catch(() => {
this.mescroll.endErr();
});
},
async upCallback(mescroll) {
try {
var LastItemSN = this.qaList[this.qaList.length - 1].noteSN;
var Count = await this.bindList(LastItemSN);
this.mescroll.endSuccess(Count, Count > 0);
} catch (e) {
//
this.mescroll.endErr();
}
},
blur: function() {
this.init_input();
},
init_input() {
this.showInput = false;
this.focus = false;
this.input_placeholder = '评论';
this.is_reply = false;
},
comment(noteSN, index) {
this.showInput = true; //input
this.focus = true;
this.qaListIndex = index;
this.focusItemSN = noteSN;
},
async send_comment(message) {
var that = this;
this.init_input();
const _recordList = message.recordList.map(({tempFilePath}) => tempFilePath);
console.log('_recordList: ', _recordList);
// #ifdef MP-WEIXIN
await upload(2, _recordList);
// #endif
// #ifdef H5
await uploadByBase64(2, _recordList);
// #endif
//
const recordList = message?.recordList?.map(({duration, fileSize, tempFilePath}) => ({
url: tempFilePath,
size: fileSize,
duration
}));
this.$http
.request({
url: 'api/v1/QA/AdminReply',
method: 'POST',
data: {
qasn: that.focusItemSN,
replyText: message.content,
record: recordList
// isManage: that.isManage
// TODO
}
})
.then(res => {
that.qaList[that.qaListIndex].adminReply = message.content;
that.qaList[that.qaListIndex].adminRecordList = recordList;
that.qaList = that.qaList;
})
.finally(() => {
that.focusItemSN = '';
});
},
deleteItem(qaSN, index) {
var that = this;
uni.showModal({
title: '确认删除吗?',
success: function(res) {
if (res.confirm) {
that.qaList.splice(index, 1);
that.qaList = that.qaList;
that.$http.request({
url: 'api/v1/QA/DelNote',
method: 'DELETE',
data: {
itemSN: qaSN
}
});
}
}
});
},
commentDelete(qaReplySN, index) {
var that = this;
uni.showModal({
title: '确认删除吗?',
success: function(res) {
if (res.confirm) {
//
that.qaList[index].adminReply = '';
that.qaList[index].adminRecordList = [];
that.qaList = that.qaList;
//
that.$http.request({
url: 'api/v1/QA/DelReply',
method: 'DELETE',
data: {
itemSN: qaReplySN
}
});
}
}
});
},
gotoPublish() {
uni.redirectTo({
url: 'publish'
});
},
async bindList(LastItemSN) {
var that = this;
var result = await this.$http.request({
url: 'api/v1/QA/GetListLoop',
data: {
LastItemSN: LastItemSN || '',
UserSN: that.userSN || ''
}
});
if (!LastItemSN) {
uni.setNavigationBarTitle({
title: result.pageTitle
});
}
this.isManage = result.isManage;
result.qaList.forEach(function(item) {
item.isAuthor = item.userSN == that.$store.state.user.userSn;
});
console.log('qa list: ', result.qaList);
this.qaList = this.qaList.concat(result.qaList);
return result.qaList.length;
}
}
};
</script>
<style lang="scss" scoped>
.easy-item {
width: 95%;
margin: 0 auto;
display: flex;
padding: 20rpx 0;
.avatar {
flex-basis: 15vw;
flex-shrink: 0;
flex-grow: 0;
image {
width: 15vw;
height: 15vw;
border-radius: 15px;
}
}
.content {
flex-basis: 75vw;
display: flex;
flex-direction: column;
flex-shrink: 0;
flex-grow: 0;
padding-left: 15rpx;
.content-title {
display: flex;
justify-content: space-between;
align-items: center;
.nick-name {
font-weight: 600;
color: #6699cc;
font-size: 42rpx;
}
.replay-marker{
padding: 5rpx 10rpx;
background-color: #F4F5F6;
color: #6699cc;
font-size: 28rpx;
}
}
.content-text {
width: 100%;
margin-top: 10rpx;
font-size: 38rpx;
line-height: 56rpx;
}
}
.thumbnails {
display: flex;
flex-wrap: wrap;
width: 100%;
margin-top: 20rpx;
.my-gallery {
width: 100%;
.gallery_img {
width: 100%;
}
}
.thumbnail {
flex-basis: 22vw;
height: 22vw;
flex-grow: 0;
flex-shrink: 0;
padding-left: 10rpx;
padding-bottom: 10rpx;
.gallery_img {
width: 100%;
height: 100%;
}
}
}
.audio-item {
border: 1px solid #eee;
border-radius: 10rpx;
margin: 15rpx 0;
}
.sub {
width: 100%;
display: flex;
justify-content: space-between;
margin-top: 20rpx;
.datetime {
font-size: 24rpx;
color: #c0c0c0;
height: 50rpx;
line-height: 50rpx;
flex: 1;
}
.operation {
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
flex-grow: 0;
flex-basis: 40%;
.delete{
font-size: 28rpx;
color: #6699cc;
flex-basis: 80rpx;
display: flex;
justify-content: center;
}
.mutiple {
display: flex;
align-items: center;
justify-content: center;
background: #fff;
text-align: center;
font-weight: 800;
border: 1px solid #ebedf0;
padding: 5rpx 20rpx;
border-radius: 10rpx;
color: #999999;
flex-basis: 100rpx;
flex-grow: 0;
margin-left: 10rpx;
height: 50rpx;
font-size: 25rpx;
.icon {
margin-right: 10rpx;
}
}
}
}
.comment {
.top-arrow {
width: 0;
height: 0;
border: 20rpx solid transparent;
border-bottom-color: #eeeeee;
margin-left: 20rpx;
}
.up-list {
width: 100%;
background: #eeeeee;
padding: 10rpx;
display: flex;
flex-wrap: wrap;
padding-bottom: 10rpx;
border-bottom: 1px solid #dddddd;
.up-item {
color: #607d8b;
font-size: 30rpx;
padding-left: 10rpx;
font-weight: 800;
line-height: 50rpx;
height: 50rpx;
&:after {
content: ',';
}
&:last-child:after {
content: '';
}
}
}
.comment-list {
width: 100%;
background: #eeeeee;
padding: 10rpx;
.comment-item {
width: 100%;
.comment-name {
color: #496f94;
font-size: 32rpx;
}
.comment-content {
font-size: 32rpx;
line-height: 44rpx;
}
.comment-delete {
width: 100%;
display: flex;
justify-content: flex-end;
.comment-delete-btn {
font-size: 28rpx;
color: #6699cc;
margin-right: 20rpx;
}
}
}
}
}
}
.foot {
position: fixed;
bottom: var(--window-bottom);
z-index: 9999;
width: 100%;
}
.add-btn {
font-size: 40px;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
}
.gotoOwn {
color: #ffffff;
}
</style>

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

@ -15,7 +15,12 @@
</view>
<view
class="record-popup"
:style="{ '--popup-height': popupHeight, '--popup-width': upx2px(popupMaxWidth), '--popup-bottom': upx2px(popupFixBottom), '--popup-bg-color': popupBgColor }"
: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>
@ -47,6 +52,7 @@
<script>
var that;
const recorderManager = uni.getRecorderManager();
// #ifdef APP-PLUS
//
import permision from '../../js_sdk/wa-permission/permission.js';
@ -141,7 +147,7 @@ export default {
},
popupFixBottom: {
type: Number,
default: 300 // rpx
default: 100 // rpx
},
popupBgColor: {
type: String,
@ -184,9 +190,12 @@ export default {
clearTimeout(that.recordTimeout);
that.recordTimeout = null; //
}
//
if (that.recording) {
// TODO
// uni.downloadFile({url: res.tempFilePath, success: source => {
// console.log('source: ', source);
// }});
that.$emit('endRecord', res);
} else {
//
@ -199,7 +208,6 @@ export default {
console.log('err:', err);
});
},
computed: {},
methods: {
upx2px(upx) {
return uni.upx2px(upx) + 'px';
@ -330,7 +338,6 @@ export default {
.record-popup {
// position: absolute;
// bottom: var(--popup-bottom);
// left: calc(50vw - calc(var(--popup-width) / 2));
z-index: 1;
width: var(--popup-width);
@ -344,7 +351,8 @@ export default {
color: #000;
transition: 0.2s height;
position: relative;
top: -100rpx;
bottom: var(--popup-bottom);
left: calc((100% - var(--popup-width)) / 2);
.inner-content {
height: var(--popup-height);

Loading…
Cancel
Save