This commit is contained in:
lr 2025-02-18 18:09:05 +08:00
parent 0084fe8aa1
commit b1b68d68ef
22 changed files with 2714 additions and 681 deletions

View File

@ -7,6 +7,8 @@ const config = {
// #endif
// #ifndef H5
host: 'http://91f.xyz:8080',
// host: 'https://b433d23.r24.cpolar.top/',
// host: 'http://hvw2rn.natappfree.cc',
// #endif
// 支付方式配置
payType: {

View File

@ -1766,6 +1766,12 @@ const util = {
success: (res) => {
if (res && res.result) {
let result = JSON.parse(res.result)
if (result.type == 'ADDFRIEND') {
uni.navigateTo({
url: `/pages/news/addFriend?account=${result.account}`
});
return
}
if (result.key == 'user') {
// 跳转申请添加好友界面
uni.navigateTo({

View File

@ -99,7 +99,6 @@ function toCustomer() {
util.toChat(param)
uni.navigateTo({
url: util.setUrl('/pages/news/chat/chat', param)
})
@ -131,10 +130,10 @@ function toCustomer() {
</view>
</view>
<!-- <view class="option ver" @click="toCustomer">
<view class="option ver" @click="toCustomer">
<image class="wh30" src="/static/customer-service.png" mode="aspectFit" />
<text class="text mt10">客服</text>
</view> -->
</view>
</view>
<!-- 下单 -->

View File

@ -18,6 +18,8 @@
import {
useStore,
} from 'vuex'
// api
import api from '@/api/index.js'
//
import util from '@/common/js/util';
//

View File

@ -1,57 +1,71 @@
<script setup>
/**
* 个人中心 商城
*/
//
import {
ref,
computed,
} from 'vue'
import {
onReachBottom,
onPullDownRefresh,
onShow,
onLoad,
onPageScroll
} from '@dcloudio/uni-app';
import {
useStore
} from 'vuex'
//
import statusBar from '@/components/header/statusBar'
/**
* 个人中心 商城
*/
//
import {
ref,
computed,
} from 'vue'
import {
onReachBottom,
onPullDownRefresh,
onShow,
onLoad,
onPageScroll
} from '@dcloudio/uni-app';
import {
useStore
} from 'vuex'
//
import statusBar from '@/components/header/statusBar'
//
import productList from '@/components/shop/productList/productList';
//
import footerMneu from '@/components/footerMenu/footerMenu'
//
import util from '@/common/js/util';
//
const store = useStore()
//
const userinfo = computed(() => {
let result = store.state.userinfo || {}
return result
//
import productList from '@/components/shop/productList/productList';
//
import footerMneu from '@/components/footerMenu/footerMenu'
//
import util from '@/common/js/util';
//
const store = useStore()
//
const userinfo = computed(() => {
let result = store.state.userinfo || {}
return result
})
//
const showHeader = ref(false)
onPageScroll((ev) => {
if (ev.scrollTop > 44) showHeader.value = true
else showHeader.value = false
})
//
function link(url) {
uni.navigateTo({
url,
})
//
const showHeader = ref(false)
}
function toCustomer() {
let param = {};
param.type = 'C2C'
param.name = `${props.detail.merName}`
param.msgId = `${rs.data.serviceId}`
param.isCustomer = true
onPageScroll((ev) => {
if (ev.scrollTop > 44) showHeader.value = true
else showHeader.value = false
util.toChat(param)
uni.navigateTo({
url: util.setUrl('/pages/news/chat/chat', param)
})
//
function link(url) {
uni.navigateTo({
url,
})
}
}
</script>
<template>
<view class="app">
<view class="apex" :class="{'active': showHeader}">
<view class="apex" :class="{ 'active': showHeader }">
<statusBar />
<view class="head rows">
<view class=""></view>
@ -80,8 +94,7 @@
<view class="df">
<!-- 客服 -->
<image class="ml20 wh40" @click="link('customer_service')" src="@/static/mine-kefu.png"
mode="aspectFill" />
<image class="ml20 wh40" @click="toCustomer" src="@/static/mine-kefu.png" mode="aspectFill" />
<!-- 设置 -->
<image class="ml20 wh40" @click="link('/pages/mine/setting/setting')" src="@/static/mine-shezhi.png"
mode="aspectFill" />
@ -202,55 +215,55 @@
</template>
<style lang="scss" scoped>
//
.apex {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 10;
opacity: 0;
transition: .2s;
//
.apex {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 10;
opacity: 0;
transition: .2s;
&.active {
opacity: 1;
background-color: #fff;
}
}
//
.header {
.avatar {
background: #D8D8D8;
}
}
//
.wallet {
position: relative;
.right {
text {
font-size: 24rpx;
color: #999999;
margin-right: 20rpx;
}
}
}
//
.container {
position: relative;
overflow: hidden;
margin: 20rpx 20rpx;
padding: 0 20rpx;
&.active {
opacity: 1;
background-color: #fff;
border-radius: 20rpx;
}
}
.main {
display: grid;
grid-template-columns: repeat(5, 1fr);
//
.header {
.avatar {
background: #D8D8D8;
}
}
//
.wallet {
position: relative;
.right {
text {
font-size: 24rpx;
color: #999999;
margin-right: 20rpx;
}
}
}
//
.container {
position: relative;
overflow: hidden;
margin: 20rpx 20rpx;
padding: 0 20rpx;
background-color: #fff;
border-radius: 20rpx;
.main {
display: grid;
grid-template-columns: repeat(5, 1fr);
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,9 @@ import {
} from 'vite';
import uni from '@dcloudio/vite-plugin-uni';
let target = 'http://91f.xyz:8080'
// let target = 'http://91f.xyz:8080'
let target = 'https://b433d23.r24.cpolar.top/'
// let target = 'http://hvw2rn.natappfree.cc'
export default defineConfig({
plugins: [uni()],

View File

@ -8,7 +8,7 @@ export default {
//
const token = uni.getStorageSync('token')
//
const userinfo = JSON.parse(uni.getStorageSync('userinfo'))
const userinfo = JSON.parse(uni.getStorageSync('userinfo'))
//
if (token) {
@ -19,8 +19,7 @@ export default {
value: userinfo
})
// IM
// IM
// util.loginTencent(userinfo)
}
} else {

View File

@ -1759,11 +1759,11 @@ const util = {
*/
toChat(option) {
uni.navigateTo({
url: util.setUrl('/pages/news/chat/chat', {
url: util.setUrl('/pages/chat/chat', {
name: option.name,
msgId: option.msgId,
type: option.type,
num: option.num,
// num: option.num,
})
})
},

View File

@ -0,0 +1,46 @@
// 状态栏高度
export const statusBarHeight = (isUnit) => {
let h = uni.getSystemInfoSync().statusBarHeight
h = h ? h : 0
return isUnit ? h : `${h}px`
}
// 底部安全区域高度
export const bottomSafeAreaHeight = (isUnit) => {
let b = uni.getSystemInfoSync().safeAreaInsets.bottom
b = b ? b : 0
return isUnit ? b : `${b}px`
}
// 获取屏幕宽
export const screenWidth = (isUnit) => {
let w = uni.getSystemInfoSync().screenWidth
w = w ? w : 0
return isUnit ? w : `${w}px`
}
// 顶部导航栏的高度
export const windowTop = (isUnit) => {
let h = uni.getSystemInfoSync().windowTop
h = h ? h : 0
return isUnit ? h : `${h}px`
}
// 获取屏幕高 x 小数
export const screenHeight = (isUnit) => {
let h = uni.getSystemInfoSync().screenHeight
h = h ? h : 0
return isUnit ? h : `${h}px`
}
// 判断是否有上一页 返回上一页 否则跳转首页
export const goBack = (url) => {
// 获取当前页面栈
const pages = getCurrentPages();
// 判断是否存在上一页
if (pages.length > 1) {
return uni.navigateBack({
delta: 1
})
} else {
return uni.reLaunch({
url: url
})
}
}

View File

@ -0,0 +1,187 @@
<!-- 页面描述 -->
<template>
<view class="commodity-information" @click="emit('click', orderInfo)">
<uni-section>
<template v-slot:decoration>
<!-- 商品图 -->
<image class="shop-image" :src="orderInfo.productImage" mode="aspectFill"></image>
</template>
<template v-slot:content>
<!-- 订单 | 聊天-->
<template v-if="showType == 1 || showType == 4">
<view class="commodity-name commodity-name-1">
<text class="commodity-name-title t2hd">{{ orderInfo.productName }}</text>
<text class="commodity-name-ks thd">{{ orderInfo.status_text }}</text>
<text v-if="showType == 4">{{ orderInfo.status }}</text>
</view>
</template>
<!-- 历史浏览 | 收藏 -->
<template v-if="showType == 2 || showType == 3">
<view class="commodity-name commodity-name-2">
<text class="t2hd">耳钉耳钉耳钉耳钉耳钉耳钉耳钉耳钉耳钉耳钉耳钉</text>
<view class="df aic" v-if="showType == 2">
<view class="df aic price mr10">
<text class="f20 jg"></text>
<text class="f28">{{ orderInfo.price }}</text>
</view>
<view class="df xl">
<text>销量</text> <text>{{ orderInfo.sales }}</text> <text>+</text>
</view>
</view>
<view class="df aic" v-if="showType == 3">
<view class="df xl thd">
<text>店铺名称</text>
</view>
</view>
</view>
</template>
<!-- 店铺管理 -->
<template v-if="showType == 5">
<view class="df fdc jcsb hohp">
<text class="t2hd c333 f28 fw600">耳钉耳钉耳钉耳钉耳钉耳钉耳钉耳钉耳钉耳钉耳钉</text>
<text class="c333 f24">销量156151</text>
<view class="df aic">
<text class="f24 cFF9B27">价格123</text>
<text class="f24 ml40 c999">库存x1</text>
</view>
</view>
</template>
</template>
<template v-slot:right v-if="showType != 3 && right">
<template v-if="showType == 1 || showType == 4">
<view class="df fdc jcsb aife" style="height: 100%;align-items: flex-end">
<!-- 价格 -->
<view class="price-num">
<view class="df aic price">
<text class="f20 jg"></text>
<text class="f28">{{ orderInfo.productPrice }}</text>
</view>
<!-- 数量 -->
<text class="c999 f24">x{{ orderInfo.totalNum }}</text>
</view>
<!-- 发送到聊天 -->
<view @click.stop="emit('consult', orderInfo)" class="go-buy" v-if="showType == 4">
<uni-tag :inverted="true" text="去咨询" type="warning" />
</view>
</view>
</template>
<template v-if="showType == 2">
<view class="go-buy" @click.stop="emit('buy', orderInfo)">
<uni-tag :inverted="true" text="去购买" type="warning" />
</view>
</template>
</template>
</uni-section>
</view>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
const emit = defineEmits(['consult', 'click', 'buy'])
const props = defineProps({
//
orderInfo: {
type: Object,
default: () => {
return {
shop_logo: '',
shop_name: '123',
order_status_text: 6,
}
},
},
// 1
// 2
// 3
// 4
showType: {
type: Number,
default: 1
},
// 4
right: {
type: Boolean,
default: true
}
})
</script>
<style scoped lang="scss">
.commodity-information {
padding: 0 20rpx;
}
$h : 164rpx;
.shop-image {
height: $h;
width: 164rpx;
background-color: #D8D8D8;
border-radius: 10rpx;
margin-right: 10rpx;
}
::v-deep .uni-section-header {
padding: 0 !important;
.uni-section-header__content {
height: $h;
}
.uni-section-header__slot-right {
height: $h;
}
}
.commodity-name {
height: 100%;
display: flex;
flex-direction: column;
&-title {
color: #333333;
font-size: 28rpx;
font-weight: normal;
margin-bottom: 8rpx;
}
&-ks {
font-size: 24rpx;
color: #999999;
}
}
.commodity-name-2 {
justify-content: space-between;
}
.commodity-name-1 {
justify-content: flex-start;
}
.go-buy {
height: 100%;
display: flex;
flex-direction: column-reverse;
}
.price-num {
display: flex;
flex-direction: column;
align-items: flex-end;
margin-left: 20rpx;
.price {
color: #3D3D3D;
}
}
.jg {
margin-right: 4rpx;
}
.xl {
color: #8C8C8C;
}
</style>

View File

@ -19,6 +19,12 @@
"navigationBarTitleText": "订单"
}
},
{
"path": "pages/chat/chat",
"style": {
"navigationBarTitleText": "聊天"
}
},
{
"path": "pages/index/article",
"style": {

712
service/pages/chat/chat.vue Normal file
View File

@ -0,0 +1,712 @@
<script setup>
/**
* 聊天页面
*/
//
import TencentCloudChat from '@tencentcloud/chat';
import {
ref,
reactive,
nextTick,
onUnmounted,
onMounted,
computed,
getCurrentInstance,
watch,
} from 'vue'
// api
import api from '@/api/index.js'
//
import util from '@/common/js/util.js'
import {
onLoad,
onReady,
onPageScroll,
onUnload
} from "@dcloudio/uni-app"
//
import newsTemplate from './components/news-temp'
//
import emoji from './emoji.vue'
//
import JyVoice from './jy-voice.vue'
//
import JyPlus from './jy-plus.vue'
import {
useStore
} from 'vuex'
const {
proxy
} = getCurrentInstance()
const store = useStore()
//
const msg = reactive({
//
id: '',
// C2C GROUP
type: '',
//
num: '',
//
isCustomer: false,
})
//
const content = ref('')
//
const loading = ref(false)
//
const userinfo = computed(() => {
let result = store.state.userinfo
return result
})
//
const list = reactive({
//
limit: 20,
//
data: [],
//
total: 0,
})
//
const top = ref(0)
//
const toolHeight = ref(0)
//
const messageItem = ref({})
// voice input emoji plus
const toolStatus = ref('input')
// video
const videoUrl = ref('')
//
const videoContext = ref(null)
//
const redPacket = reactive({})
onLoad(option => {
//
let title = ''
//
if (option.type) msg.type = option.type
//
if (option.name) title = option.name
// id
if (option.msgId) msg.id = option.msgId
//
if (option.type == 'GROUP') {
msg.num = option.num
title = `(${option.num})${option.name}`
}
//
if (title) uni.setNavigationBarTitle({
title,
})
//
if (option.isCustomer) msg.isCustomer = option.isCustomer
//
addListener()
//
getHistory({
callback: scrollToBottom
})
// #ifdef APP
uni.onKeyboardHeightChange((rs) => {
ghostBox.value.height = rs.height + 'px'
nextTick(() => {
scrollToBottom()
})
})
// #endif
})
onReady(() => {
uni.createSelectorQuery().in(proxy).select('#tool').boundingClientRect((rect) => {
toolHeight.value = rect.height
}).exec();
//
videoContext.value = uni.createVideoContext('video')
})
onPageScroll((ev) => {
onContentScroll(ev)
})
onUnload(() => {
// #ifdef APP
uni.offKeyboardHeightChange(() => { })
// #endif
videoContext.value.stop()
})
//
function addListener() {
let onMessageReceived = function (event) {
console.log('TencentCloudChat.EVENT.MESSAGE_RECEIVED', event)
setTimeout(() => {
//
getHistory({
msgId: '',
limit: 1,
})
}, 200)
}
uni.$chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
}
//
function removeListener() {
uni.$chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED);
}
//
function getMoreHistroy() {
//
if (list.total <= list.data.length) return
getHistory({
msgId: list.data[0].id
})
}
/**
* 获取历史记录
* @param {Object} param
*/
function getHistory(param = {}) {
// sdk
let isReady = uni.$chat.isReady();
//
if (!isReady && userinfo.value.id) {
setTimeout(function () {
getHistory(param)
}, 200);
return
}
//
loading.value = true
//
let request = api.news.getUserMsgHistory
// //
// if (msg.type == 'GROUP') request = api.news.getGroupMsgHistory
//
request({
query: {
msgId: param.msgId || '',
fromId: userinfo.value.serviceId,
toId: msg.id,
// groupId: msg.id,
limit: param.limit || list.limit,
},
}).then(res => {
if (res.code === 200) {
//
const result = res.data
//
if (param.limit == 1) {
list.data.push(...result.list.map(item => {
item.callbackData = JSON.parse(item.callbackJson)
return item
}))
} else {
//
list.data.unshift(...result.list.map(item => {
item.callbackData = JSON.parse(item.callbackJson)
return item
}))
}
console.log('getHostory', list.data)
//
list.total = result.total
nextTick(() => {
param.callback && param.callback()
})
return
}
util.alert({
content: res.msg,
showCancel: false,
})
}).catch(rs => {
console.log('err', rs)
}).finally(() => {
loading.value = false
})
}
//
function scrollToBottom() {
uni.createSelectorQuery().in(proxy).select('#scroll-content').boundingClientRect((res) => {
top.value = res.height
uni.pageScrollTo({
scrollTop: top.value,
duration: 0
})
// console.log('top.value', top.value)
}).exec();
}
//
function debounce(func, wait = 500) {
let timeout = null;
return function (...args) {
clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(this, args)
}, wait);
}
}
//
function onContentScroll(ev) {
if (ev.scrollTop == 50) getMoreHistroy()
debounce(() => {
top.value = ev.detail.scrollTop
})
}
//
function handleSend() {
//
sendMsg({
query: {
formId: userinfo.value.id,
toUserId: msg.id,
msgType: TencentCloudChat.TYPES.MSG_TEXT,
},
data: {
text: content.value
},
success: () => {
//
content.value = ''
}
})
}
/**
* 加号菜单发送
* @param {Object} message 消息对象
*/
function handlePlusSend(message) {
sendMsg(message)
}
/**
* 发送消息
* @param {Object} param
*/
function sendMsg(param) {
//
let request = api.news.sendUserMsg
//
if (msg.type == 'GROUP') request = api.news.sendGroupMsg
//
request({
query: param.query,
data: param.data,
}).then((rs) => {
if (rs.code == 200) {
param.success ? param.success() : ''
return
}
util.alert({
content: rs.msg,
showCancel: false,
})
}).catch((rs) => {
console.log('sendMsg error:', rs);
})
}
/**
* 打开红包详情
* @param {Object} ev
*/
function handleRedPacket(ev) {
messageItem.value = ev
api.news.getRedPacketInfo({
query: {
// id
redPacketId: ev.callbackData.callback_json[0].businessId
}
}).then(rs => {
if (rs.code == 200) {
proxy.$refs.RedPacketRef.open()
Object.assign(redPacket, rs.data)
return
}
util.alert({
content: rs.msg,
showCancel: false,
})
})
}
//
function handleOpenReadPacket() {
//
if (redPacket.redStatus == false) return
//
// if (redPacket.isStale == 1) return
//
api.news.getRedPacket({
query: {
// id
redPacketId: redPacket.id,
// id
userId: userinfo.value.id,
//
sendType: {
'C2C': '1',
'GROUP': '2',
}[msg.type],
}
}).then(rs => {
if (rs.code == 200) {
//
redPacket.redStatus = false
//
redPacket.amount = rs.data
return
}
util.alert({
content: rs.msg,
showCancel: false,
})
})
}
// emoji
function emojiTap(val) {
content.value = content.value + val
}
//
function handleTool(val) {
if (toolStatus.value === val) {
toolStatus.value = 'input'
return
}
toolStatus.value = val
}
//
function onFocus() {
handleTool('input')
}
//
function voiceSend(message) {
console.log('handlePlusSend', message)
sendMsg({
message,
})
}
//
const handleScroll = (e) => {
if (e.detail.scrollTop === 0) {
getHistory()
}
}
//
const showGhost = ref(false)
//
const ghostBox = ref({
height: '0px',
duration: '0.25s'
})
//
function keyboardheightchange(res) {
ghostBox.value = res.detail
nextTick(() => {
showGhost.value = res.detail.height > 0 ? true : false
})
}
/**
* 看视频
* @param {Object} item 聊天消息对象
*/
function handleViewVideo(item) {
videoUrl.value = item.payload.videoUrl
//
videoContext.value.requestFullScreen()
}
//
function onScreenChange(ev) {
console.log('onScreenChange', ev)
if (!ev.fullScreen) videoContext.value.pause()
}
</script>
<template>
<view class="app">
<scroll-view class="scroll-view" scroll-y :scroll-with-animation="true" :scroll-top="top"
@scroll="onContentScroll" @scrolltoupper="getMoreHistroy">
<view id="scroll-content" style="padding: 30rpx 30rpx">
<view v-for="(item, index) in list.data" :key="index">
<!-- 普通消息 -->
<view class="message"
:class="[(item.From_Account || item.fromId) == userinfo.id ? 'self' : 'friend']">
<!-- 如果是我自己 -->
<view v-if="msg.type == 'C2C'">
<image :src="list.faceUrl" class="avatar wh80" mode="aspectFill" />
</view>
<view v-else>
<image :src="item.fromFaceUrl" class="avatar wh80" mode="aspectFill" />
</view>
<view class="df fdc mlr20">
<!-- 昵称 -->
<view class="df fdc" v-if="item.from != userinfo.userId">
<view class="name">{{ item.callbackData.from_name }}</view>
</view>
<!-- 消息 -->
<newsTemplate :item="item" :msg="msg" @openRedBag="handleRedPacket"
@viewVideo="handleViewVideo" />
</view>
</view>
</view>
<view v-if="showGhost" :style="{ height: `${ghostBox.height}px`, transition: `${ghostBox.duration}s` }">
</view>
<view class="ghost" :style="{ height: toolHeight + 'px' }"></view>
</view>
</scroll-view>
</view>
<view class="tool" id="tool">
<view class="tool-group" style="background: #F6F6F6;">
<!-- 语音 -->
<image src="/static/news-voice.png" mode="widthFix" class="thumb" @click="handleTool('voice')"></image>
<!-- 摁住说话 -->
<template v-if="toolStatus == 'voice'">
<JyVoice @send="voiceSend" :msg="msg" />
</template>
<!-- 输入框 -->
<template v-if="toolStatus != 'voice'">
<uni-easyinput @focus="onFocus" type="text" v-model="content" :clearable="false" class="input"
:adjust-position="false" @keyboardheightchange="keyboardheightchange" placeholder="请输入你的问题"
confirmType="发送" />
</template>
<!-- 表情 -->
<image src="/static/news-emoji.png" mode="widthFix" class="thumb" @click="handleTool('emoji')"></image>
<!-- 加号 -->
<template v-if="!content">
<image src="/static/news-plus.png" mode="widthFix" class="thumb" @click="handleTool('plus')" />
</template>
<!-- 文本发送按钮 -->
<template v-else>
<view class="send" @click="handleSend">发送</view>
</template>
</view>
<view v-if="showGhost" :style="{ height: `${ghostBox.height}px`, transition: `${ghostBox.duration}s` }"></view>
<!-- 表情 -->
<template v-if="toolStatus == 'emoji'">
<emoji @setEmoj="emojiTap"></emoji>
</template>
<!-- 加号 -->
<template v-if="toolStatus == 'plus'">
<JyPlus @send="handlePlusSend" :msg="msg"></JyPlus>
</template>
</view>
<!-- 视频 -->
<video :src="videoUrl" id="video" @fullscreenchange="onScreenChange" />
<!-- 红包封面 -->
<uni-popup ref="RedPacketRef" type="center">
<view class="red-bag br20" @touchmove.stop.prevent="">
<view class="rbag_top">
<view class="user fmid">
<view class="avatar">
<image class="wh80 cir" :src="redPacket.fromUrl" mode="scaleToFill" />
</view>
<view class="ml15 f32">{{ redPacket.fromName }}的红包</view>
</view>
<view class="app_name mt15 mlr30 tac f40">{{ redPacket.blessing }}</view>
<!-- -->
<view class="cfff f32 tac mt50" v-if="redPacket.isStale == 1">红包已过期</view>
<view class="cfff f32 tac mt50" v-else-if="redPacket.remainingCount == 0">来晚啦红包已被抢完</view>
<!-- redPacket.redStatus true可以领取 false不可领取 -->
<view class="amount f32" v-if="!redPacket.redStatus">
<text class="">已领取</text>
<text class="value">{{ redPacket.amount }}</text>
<text class="unit" v-if="redPacket.payType == 1">余额</text>
<text class="unit" v-else-if="redPacket.payType == 2">积分</text>
</view>
</view>
<view class="open_rbag_btn pr fmid" @click="handleOpenReadPacket">
<text v-if="redPacket.amount"></text>
<text></text>
</view>
</view>
<view class="tac mt35" @click.stop="$refs.RedPacketRef.close()">
<uni-icons type="close" color="#fbd977" size="32" />
</view>
</uni-popup>
</template>
<style lang="scss" scoped>
@import './index.scss';
//
#video {
position: fixed;
top: 100%;
left: 0;
}
//
.red-bag {
position: relative;
width: 528rpx;
height: 60vh;
color: #ECCD97;
background-color: #e0534a;
box-shadow: 0 0 20rpx #00000033;
.rbag_top {
padding-top: 60rpx;
height: 70%;
background-color: #e0534a;
border-radius: 0 0 500rpx 500rpx / 0 0 200rpx 200rpx;
box-shadow: 0 5rpx 5rpx rgba(0, 0, 0, 0.2);
.amount {
margin-top: 120rpx;
text-align: center;
letter-spacing: 1rpx;
.value {
font-size: 60rpx;
font-weight: bold;
}
}
}
.open_rbag_btn {
width: 180rpx;
height: 180rpx;
margin: -90rpx auto 0;
color: #fef5e8;
font-size: 74rpx;
font-weight: bold;
background-color: #ffd287;
box-shadow: 2rpx 2rpx 6rpx rgba(0, 0, 0, 0.2);
border-radius: 50%;
z-index: 1;
}
//
.open_rbag_model {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.3);
z-index: 1000;
.rbag_conbg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 80%;
height: 840rpx;
margin: auto;
z-index: 1001;
}
.open_rbag_con {
z-index: 1002;
.open_title {
height: 120rpx;
line-height: 120rpx;
text-align: center;
font-size: 38rpx;
letter-spacing: 2rpx;
color: #e46965;
}
.rbag_detail {
margin-top: 90rpx;
.open_money {
text-align: center;
font-size: 80rpx;
color: #c95948;
font-weight: bold;
display: flex;
justify-content: center;
.danwei {
font-size: 30rpx;
margin-left: 16rpx;
margin-top: 24rpx;
}
}
.open_tips {
text-align: center;
font-size: 30rpx;
color: #d26762;
margin-top: 30rpx;
}
}
.lookbag_box {
margin-top: 300rpx;
display: flex;
justify-content: center;
.lookbag_btn {
width: 70%;
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 32rpx;
color: #c95948;
letter-spacing: 2rpx;
background-color: #ffd356;
border-radius: 50rpx;
box-shadow: 0rpx 0rpx 4rpx rgba(0, 0, 0, 0.2);
}
}
.hide_btn {
position: absolute;
bottom: -110rpx;
left: 0;
right: 0;
width: 80rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
margin: 0 auto;
}
}
}
}
</style>

View File

@ -0,0 +1,223 @@
<script setup>
/**
* 聊天框内容
*/
//
import TencentCloudChat from '@tencentcloud/chat';
import {
nextTick,
watch,
computed,
defineEmits,
} from 'vue'
import NewsAudio from '@/static/audio.png'
import JyCommodityInformation from '@/components/public/jy-commodity-information'
const props = defineProps({
item: {
type: Object,
default: () => {
{}
}
},
msg: {
type: Object,
}
})
import {
useStore
} from 'vuex'
const store = useStore()
const emit = defineEmits(['openRedBag', 'viewVideo'])
//
const formatData = computed(() => props.item.callbackData.callback_json[0])
//
const innerAudioContext = uni.createInnerAudioContext();
//
const payAudio = (item) => {
innerAudioContext.src = item.payload.url
nextTick(() => {
innerAudioContext.play();
})
}
//
const userinfo = computed(() => {
let result = store.state.userinfo
return result
})
//
function handleCall() {
uni.$TUICallKit.call({
userID: props.msg.id,
callMediaType: formatData.value.data.call_type,
}, res => {
console.log('[TUICallKit] call params: ', JSON.stringify(res));
});
}
//
function handleOpenRedBag() {
emit('openRedBag', props.item)
}
//
function handleViewVideo(item) {
emit('viewVideo', item)
}
</script>
<template>
<view class="content pr">
<!-- 图片 -->
<template v-if="item.callbackData.MsgType === TencentCloudChat.TYPES.MSG_IMAGE">
<image class="br10" :src="formatData.MsgContent.ImageInfoArray[0].URL" mode="widthFix" />
</template>
<!-- 文字 -->
<template v-else-if="item.callbackData.MsgType === TencentCloudChat.TYPES.MSG_TEXT">
<view class="p25">
{{ formatData.MsgContent.Text }}
</view>
</template>
<!-- 自定义消息 -->
<template v-else-if="item.callbackData.MsgType === TencentCloudChat.TYPES.MSG_CUSTOM">
<!-- 判断业务字段 -->
<template v-if="formatData.businessType">
<!-- 音视频通话 -->
<template v-if="formatData.businessType == 1">
<!-- 拨打电话 -->
<view class="p25" @click="handleCall">
<template v-if="formatData.actionType == TencentCloudChat.TSignaling.ACTION_TYPE_INVITE">
<text>[ </text>
<uni-icons type="videocam" color="#333" size="32rpx"
v-if="formatData.data.call_type == 1" />
<uni-icons type="phone" color="#333" size="32rpx"
v-else-if="formatData.data.call_type == 2" />
<text>发起</text>
<text v-if="formatData.data.call_type == 1">语音</text>
<text v-else-if="formatData.data.call_type == 2">视频</text>
<text>通话</text>
<text> ]</text>
</template>
<template v-if="formatData.actionType == TencentCloudChat.TSignaling.ACTION_TYPE_CANCEL_INVITE">
<text>[</text>
<uni-icons type="videocam" color="#333" size="32rpx"
v-if="formatData.data.call_type == 1" />
<uni-icons type="phone" color="#333" size="32rpx"
v-else-if="formatData.data.call_type == 2" />
<text>已取消通话 ]</text>
</template>
<template v-if="formatData.actionType == TencentCloudChat.TSignaling.ACTION_TYPE_ACCEPT_INVITE">
<text>[ </text>
<uni-icons type="videocam" color="#333" size="32rpx"
v-if="formatData.data.call_type == 1" />
<uni-icons type="phone" color="#333" size="32rpx"
v-else-if="formatData.data.call_type == 2" />
<text>已接通 ]</text>
<!-- <text>[ 通话时长 {{formatData.data.call_end}} ]</text> -->
</template>
<template v-if="formatData.actionType == TencentCloudChat.TSignaling.ACTION_TYPE_REJECT_INVITE">
<text>[ </text>
<uni-icons type="videocam" color="#333" size="32rpx"
v-if="formatData.data.call_type == 1" />
<uni-icons type="phone" color="#333" size="32rpx"
v-else-if="formatData.data.call_type == 2" />
<text>已拒绝通话 ]</text>
</template>
<template
v-if="formatData.actionType == TencentCloudChat.TSignaling.ACTION_TYPE_INVITE_TIMEOUT">
<text>[ </text>
<uni-icons type="videocam" color="#333" size="32rpx"
v-if="formatData.data.call_type == 1" />
<uni-icons type="phone" color="#333" size="32rpx"
v-else-if="formatData.data.call_type == 2" />
<text>超时未接听 ]</text>
</template>
</view>
</template>
<!-- 红包消息 -->
<template v-else-if="formatData.businessType == 'redPacket'">
<!-- 红包 -->
<view class="redPacket br10" :class="{'disabled': 0}"
@click="handleOpenRedBag">
<view class="df aic">
<image class="img fs0 mr10" src="/static/image/red-envelope.png" />
<view class="red-packet-text cfff">{{ formatData.data.blessing }}</view>
</view>
<!-- 分割线 -->
<view class="line"></view>
<view class="f20" style="color:#FBD3A4">{{ formatData.data.payType == 1 ? '余额红包' : '积分红包' }}</view>
</view>
</template>
</template>
</template>
<!-- 音频文件 -->
<template v-if="item.callbackData.MsgType == TencentCloudChat.TYPES.MSG_AUDIO">
<div class="df aic p25" style="width: 100rpx" @click="payAudio(item)">
<image class="mr20" style="width: 30rpx;height: 30rpx;z-index: 1;" :src="NewsAudio" />
<text>{{ item.payload.downloadFlag }}''</text>
</div>
</template>
<!-- 视频消息 -->
<template v-if="item.callbackData.MsgType == TencentCloudChat.TYPES.MSG_VIDEO">
<image :src="item.payload.snapshotUrl" mode="widthFix" />
<view class="window pfull" @click="handleViewVideo(item)">
<image class="pause pmid wh40" src="/static/pause.png" mode="aspectFit" />
</view>
</template>
<!-- 咨询订单 -->
<template v-if="item.callbackData.MsgType === 'order'">
<JyCommodityInformation :showType="4" :right="false"></JyCommodityInformation>
</template>
<!-- 咨询商品-->
<template v-if="item.callbackData.MsgType === 'shop'">
<JyCommodityInformation :showType="3"></JyCommodityInformation>
</template>
</view>
</template>
<style scoped lang="scss">
.content {
box-sizing: border-box;
min-height: 80rpx;
max-width: 60vw;
font-size: 28rpx;
image {
width: 200rpx;
}
}
//
.redPacket {
width: 360rpx;
padding: 24rpx;
background-color: rgb(248, 162, 60);
color: #FCFEFD;
&.disabled {
opacity: .6;
}
.img {
width: 50rpx;
height: 60rpx;
}
.line {
display: inline-block;
width: 100%;
height: 1px;
background: #EF9B45;
margin: 10rpx 0;
}
}
//
.window {
background-color: rgba(0, 0, 0, 0.2);
}
</style>

View File

@ -0,0 +1,33 @@
export default [
"😀", "😁", "😃", "😄", "😅", "😆", "😉", "😊", "😋", "😎", "😍",
"😘", "😗", "😙", "😚", "☺", "😇", "😐", "😑", "😶", "😏", "😣", "😥", "😮", "😯", "😪",
"😫", "😴", "😌", "😛", "😜", "😝", "😒", "😓", "😔", "😕", "😲", "😷", "😖", "😞", "😟",
"😤", "😢", "😭", "😦", "😧", "😨", "😬", "😰", "😱", "😳", "😵", "😡", "😠",
"👦", "👧", "👨", "👩", "👴", "👵", "👶", "👱", "👮", "👲", "👳", "👷", "👸", "💂", "🎅", "👰", "👼",
"💆", "💇", "🙍", "🙎", "🙅", "🙆", "💁", "🙋", "🙇", "🙌", "🙏", "👤", "👥", "🚶", "🏃", "👯",
"💃", "👫", "👬", "👭", "💏", "💑", "👪", "💪", "👈", "👉", "☝", "👆", "👇", "✌", "✋", "👌",
"👍", "👎", "✊", "👊", "👋", "👏", "👐", "✍", "👣", "👀", "👂", "👃", "👅", "👄", "💋", "👓",
"👔", "👙", "👛", "👜", "👝", "🎒", "💼", "👞", "👟", "👠", "👡", "👢", "👑",
"👒", "🎩", "🎓", "💄", "💅", "💍", "🌂", "📶", "📳", "📴", "♻", "🏧","🚮", "🚰", "♿", "🚹", "🚺",
"🚻", "🚼", "🚾", "⚠", "🚸", "⛔", "🚫", "🚳", "🚭", "🚯", "🚱", "🚷", "🔞", "💈",
"🙈", "🐒", "🐶", "🐕", "🐩", "🐺", "🐱","🐈", "🐯", "🐅", "🐆", "🐴", "🐎", "🐮", "🐂",
"🐃","🐄","🐷","🐖","🐗","🐽","🐏","🐑","🐐","🐪","🐫","🐘","🐭",
"🐁","🐀","🐹","🐰","🐇","🐻","🐨","🐼","🐾","🐔","🐓","🐣","🐤","🐥",
"🐦", "🐧", "🐸", "🐊","🐢", "🐍", "🐲", "🐉", "🐳", "🐋", "🐬", "🐟", "🐠", "🐡",
"🐙", "🐚", "🐌", "🐛", "🐜", "🐝", "🐞", "🦋", "💐", "🌸", "💮", "🌹", "🌺",
"🌻", "🌼", "🌷", "🌱", "🌲", "🌳", "🌴", "🌵", "🌾", "🌿", "🍀", "🍁", "🍂", "🍃",
"🌍","🌎","🌏","🌐","🌑","🌒","🌓","🌔","🌕","🌖","🌗","🌘","🌙","🌚",
"🌛","🌜","☀","🌝","🌞","⭐","🌟","🌠","☁","⛅","☔","⚡","❄","🔥","💧","🌊",
"🏀", "🏈", "🏉", "🎾", "🎱", "🎳", "⛳", "🎣", "🎽", "🎿",
"😈", "👿", "👹", "👺", "💀", "☠", "👻", "👽", "👾", "💣",
"🌋", "🗻", "🏠", "🏡", "🏢", "🏣", "🏤", "🏥", "🏦", "🏨",
"⛲", "🌁", "🌃", "🌆", "🌇", "🎠", "🎡", "🎢", "🚂",
"🚌", "🚍", "🚎", "🚏", "🚐", "🚑", "🚒", "🚓", "🚔", "🚕", "🚖", "🚗", "🚘",
"💌", "💎", "🔪", "💈", "🚪", "🚽", "🚿", "🛁", "⌛", "⏳", "⌚", "⏰", "🎈", "🎉",
"💤", "💢", "💬", "💭", "♨", "🌀", "🔔", "🔕", "✡", "✝", "🔯", "📛", "🔰", "🔱", "⭕", "✅",
"☑", "✔", "✖", "❌", "❎", "", "", "➗", "➰", "➿", "〽", "✳", "✴", "❇", "‼", "⁉", "❓", "❔", "❕", "❗",
"🕛", "🕧", "🕐", "🕜", "🕑", "🕝", "🕒", "🕞", "🕓", "🕟", "🕔", "🕠", "🕕", "🕡",
"🕖", "🕢", "🕗", "🕣", "🕘", "🕤", "🕙", "🕥", "🕚", "🕦", "⏱", "⏲", "🕰",
"💘", "❤", "💓", "💔", "💕", "💖", "💗", "💙", "💚", "💛", "💜", "💝", "💞", "💟❣",
"🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "🍎", "🍏", "🍐", "🍑", "🍒", "🍓",
]

View File

@ -0,0 +1,42 @@
<template>
<view class="NewsPlus">
<scroll-view scroll-y scroll-with-animation>
<view append="tree" class="emoj_box">
<template v-for="(item, index) in emojiList" :key="index">
<text @click="emit('setEmoj', item)" class="emoj_box_img">{{ item }}</text>
</template>
</view>
</scroll-view>
</view>
</template>
<script setup>
import {
defineEmits
} from 'vue'
import emojiList from './emoji.js'
const emit = defineEmits(['setEmoj'])
</script>
<style scoped>
.NewsPlus {
background-color: #f6f6f6;
}
.scroller {
flex: 1;
}
.emoj_box {
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.emoj_box_img {
font-size: 45rpx;
width: 83rpx;
height: 83rpx;
text-align: center;
line-height: 83rpx;
}
</style>

30
service/pages/chat/fn.js Normal file
View File

@ -0,0 +1,30 @@
// 获取历史记录
// C2C${userID}(单聊)
// GROUP${groupID}(群聊)
// GROUP${topicID}(话题)
// @TIM#SYSTEM系统通知会话
/**
* 获取历史记录
* @param {String} option 聊天属性
* @param {String} option.chatType C2C单聊 GROUP群聊/话题 @TIM#SYSTEM系统通知会话
* @param {String} option.msgId 聊天对象id 单聊/群聊/话题
* @param {String} option.nextReqMessageID 用于分页续拉的消息 ID第一次拉取时不要传入 nextReqMessageID续拉时填入上次调用 getMessageList 接口返回的该字段的值
*/
export const getHistoryMsg = async (option) => {
let options = {
// 对方的用户 ID 或者群组 ID 或聊天室 ID。
conversationID: `${option.chatType}${option.msgId}`,
nextReqMessageID: option.nextReqMessageID,
}
console.log('options', options);
return new Promise((resolve, reject) => {
uni.$chat.getMessageList(options).then((res) => {
resolve(res)
}).catch((e) => {
console.log('Promise reject', e);
reject(e)
});
})
}

View File

@ -0,0 +1,118 @@
.scroll-view {
box-sizing: border-box;
flex: 1;
height: 100%;
}
.message {
display: flex;
align-items: flex-start;
flex-direction: row;
margin-bottom: 40rpx;
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 10rpx;
}
.content {
min-height: 80rpx;
max-width: 60vw;
font-size: 28rpx;
image {
width: 200rpx;
}
}
// 我自己
&.self {
flex-direction: row-reverse;
// justify-content: flex-end;
.content {
background: #d9e1ff;
border-radius: 24rpx 0rpx 24rpx 24rpx;
}
}
// 对方
&.friend {
.name {
font-size: 20rpx;
color: '#000000';
margin-bottom: 8rpx;
}
.content {
border-radius: 0rpx 24rpx 24rpx 24rpx;
background: #ffffff;
}
}
}
.tool {
position: fixed;
width: 100%;
left: 0;
bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: calc(0 + env(safe-area-inset-bottom));
z-index: 1;
.tool-group {
display: flex;
flex-direction: row;
}
&-group {
height: 120rpx;
display: flex;
align-items: center;
}
::v-deep .uni-easyinput__content {
border-radius: 192rpx;
}
.xf-for {
height: 80rpx;
padding-bottom: 12rpx;
background: #eee;
.xfBtn {
box-sizing: border-box;
border: 2px solid #dcdcdc;
background: #ffffff;
padding: 12rpx 32rpx;
font-size: 28rpx;
color: rgba(0, 0, 0, 0.9);
border-radius: 200rpx;
width: fit-content;
margin: 10rpx 20rpx;
}
}
.NewsPlus {
width: 100%;
height: 486rpx;
}
.thumb {
width: 44rpx;
height: 44rpx;
margin: 0 10rpx;
}
.send {
border-radius: 10rpx;
margin: 0 10rpx;
padding: 10rpx 20rpx;
background: #ff3737;
color: #fff;
font-size: small;
}
}

View File

@ -0,0 +1,344 @@
<script setup>
// +
//
import TencentCloudChat from '@tencentcloud/chat';
import {
ref,
reactive,
nextTick,
computed,
} from 'vue'
import {
screenHeight
} from '@/components/public/Mixins'
import JyCommodityInformation from '@/components/public/jy-commodity-information'
// api
import api from '@/api/index.js'
//
import util from '@/common/js/util.js'
import { inject } from 'vue'
const { checkLink } = inject('util');
//
const props = defineProps({
msg: {
type: Object,
},
})
//
const emit = defineEmits(['plusClick', 'send'])
//
const plusList = computed(() => {
let result = [
{
type: 'picture.png',
label: '照片',
value: 'chooseImage',
}, {
type: 'photograph.png',
label: '拍摄',
value: 'takePhoto',
}, {
type: 'red-envelope.png',
label: '红包',
value: 'redEnvelope',
}
]
const isCustomer = props.msg.isCustomer;
//
if (props.msg.type == 'C2C' && !isCustomer) result.push({
type: 'news-voice.png',
label: '音视频',
value: 'voice'
})
//
if (isCustomer) result.unshift({
type: 'order.png',
label: '订单',
value: 'chooseOrder',
}, {
type: 'shop.png',
label: '商品',
value: 'chooseShop',
})
return result
})
const popupRef = ref(null)
const popupRE = ref(null)
const formData = reactive({
name: ''
})
const popupData = reactive({
show: false,
title: '选择订单'
})
//
const list = reactive([])
//
const plusClickObj = {
voice: () => {
//
const menu = [{
name: '语音通话',
type: 1,
},
{
nanme: '视频通话',
type: 2,
}
]
uni.showActionSheet({
itemList: ['语音通话', '视频通话'],
success: rs => {
uni.$TUICallKit.call({
userID: props.msg.id,
callMediaType: menu[rs.tapIndex].type,
// callParams: {
// roomID: 234,
// strRoomID: '2323423',
// timeout: 30
// },
}, res => {
console.log('[TUICallKit] call params: ', JSON.stringify(res));
});
}
})
},
//
chooseOrder: () => {
let param = {
pageSize: 10,
pageNum: 1,
merId: props.msg.id,
}
api.shop.getShopOrderList(param).then(rs => {
if (rs.code == 200) {
list.length = 0
list.push(...rs.rows.map(item => {
//
item.status = Number(item.status)
//
item.status_text = {
'0': '待支付',
'1': '待发货',
'4': '待收货',
'5': '已收货',
'6': '已完成',
'9': '已取消',
}[item.status]
return item
}))
nextTick().then(() => {
popupRef.value.open()
});
return
}
util.alert({
content: rs.msg,
showCancel: false,
})
})
},
//
chooseShop: () => {
uni.navigateTo({
url: `/pages/news/goodsList/index?merId=${props.msg.id}`
});
},
//
redEnvelope() {
uni.navigateTo({
url: util.setUrl('/pages/news/redPacket', {
//
msgId: props.msg.id,
//
sendType: {
'C2C': 1,
'GROUP': 2,
}[props.msg.type],
num: props.msg.num,
})
})
// popupRE.value.open()
},
//
takePhoto: () => {
console.log('takePhoto')
sendMsgImg({
sourceType: ['album'],
})
},
//
chooseImage: () => {
console.log('chooseImage')
sendMsgImg({
sourceType: ['album'],
})
}
}
//
function sendMsgImg(option) {
util.upload_image({
count: 1,
type: 1,
success: rs => {
emit('send', {
query: {
toUserId: props.msg.id,
msgType: TencentCloudChat.TYPES.MSG_IMAGE,
},
data: {
imgUrl: rs.value
},
})
}
})
return
//
uni.chooseImage({
count: 1,
sourceType: option.sourceType, //
success: (res) => {
let message = uni.$chat.createImageMessage({
to: props.msg.id,
conversationType: props.msg.type,
payload: {
file: res
},
onProgress: function (event) {
console.log('file uploading:', event)
}
})
return
}
});
}
//
const consult = (content) => {
let obj = {
content,
type: 'order'
}
popupRef.value.close()
}
</script>
<template>
<view class="jy-plus">
<view class="NewsPlus ptb20" style="background: #F6F6F6;">
<view class="df fdc aic" v-for="(item, index) in plusList" :key="index" @click="plusClickObj[item.value]">
<view class="imageBox fmid wh100">
<image class="image wh50" :src="`/static/new-${item.type}`" mode="aspectFit" />
</view>
<text class="mt20">{{ item.label }}</text>
</view>
</view>
</view>
<!-- 咨询订单的弹窗 -->
<uni-popup ref="popupRef" type="bottom">
<view class="jy-popup bfff" :style="{ height: `${screenHeight(true) * 0.7}px` }">
<view class="title">
<text>咨询订单</text>
</view>
<view class="close-btn" @click="popupRef.close()">×</view>
<view class="input-view">
<uni-easyinput class="easyinput" placeholder="搜索你要查询的订单">
<template v-slot:left>
<uni-icons class="ml20" type="search" size="20"></uni-icons>
</template>
</uni-easyinput>
</view>
<scroll-view scroll-y scroll-with-animation>
<view v-for="(item, index) in list" :key="index">
<view class="time">
下单时间{{ item.createTime }}
</view>
<JyCommodityInformation :showType="4" :orderInfo="item" @consult="consult"></JyCommodityInformation>
</view>
</scroll-view>
</view>
</uni-popup>
</template>
<style scoped lang="scss">
.NewsPlus {
//
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 20rpx;
.imageBox {
background-color: #fff;
border-radius: 20rpx;
}
}
.jy-popup {
width: 100%;
border-radius: 20px 20px 0px 0px;
.title {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 20rpx 0;
//
color: #3D3D3D;
font-size: 32rpx;
}
.close-btn {
position: absolute;
top: 40rpx;
right: 40rpx;
height: 52rpx;
line-height: 52rpx;
width: 52rpx;
background: #D8D8D8;
color: #999999;
font-size: 30rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
transform: translate(50%, -50%);
}
.input-view {
//
border-top: 1px solid #E5E5E5;
border-bottom: 1px solid #E5E5E5;
padding: 20rpx;
}
.easyinput {
flex: 1;
}
.time {
padding: 24rpx 20rpx 0 20rpx;
font-size: 22rpx;
color: #3D3D3D;
}
}
</style>

View File

@ -0,0 +1,280 @@
<!-- 语音 -->
<template>
<view class="voice_box" @touchstart="sv.touchstartVoice" @touchmove.stop.prevent="sv.touchmoveVoice"
@touchend="sv.touchendVoice" @touchcancel="sv.touchcancelVoice">
<text class="voice_text c000">{{ voiceText }}</text>
</view>
<!-- 语音状态显示 -->
<template v-if="voiceFlg">
<!-- 录音UI效果 -->
<view class="record" :class="voiceFlg ? '' : 'hidden'">
<uni-icons :class="[voiceStop ? 'cancel' : 'ing']" :type="voiceStop ? 'micoff' : 'mic'" size="100" />
<view class="tis" :class="voiceStop ? 'change' : ''">{{ voiceTis }}</view>
</view>
</template>
</template>
<script setup>
import {
ref,
defineEmits
} from 'vue'
//
import TencentCloudChat from '@tencentcloud/chat';
const props = defineProps({
msg: {
type: Object
},
})
const emit = defineEmits(['send'])
//
// #ifdef APP-PLUS
const recorderManager = uni.getRecorderManager();
// #endif
//
const voiceLength = ref(0);
//
const voiceTimer = ref(null);
//
const voiceText = ref('按住 说话');
//
const voiceTis = ref('手指上滑 取消发送');
//
const voiceFlg = ref(false);
// Y
const voicePageY = ref(0);
//
const voiceStop = ref(false);
const str = '';
//
const sv = {
//
touchstartVoice: (e) => {
voicePageY.value = (e.changedTouches[0].pageY).toFixed(2);
recorderManager.start({
duration: 60000, // ms 60000010
sampleRate: 44100, //
numberOfChannels: 1, //
encodeBitRate: 192000, //
format: "mp3"
});
voiceLength.value = 0;
voiceFlg.value = true
console.log('recorder start success');
//,
voiceTimer.value = setInterval(() => {
voiceLength.value += 0.1;
}, 100);
console.log('touchstartVoice', voicePageY.value);
},
//
touchmoveVoice: (e) => {
// UI
if (!voiceFlg.value) {
return;
}
let numTemp = voicePageY.value - ((e.changedTouches[0].pageY).toFixed(2));
if (numTemp >= 60) {
console.log('取消发送');
voiceStop.value = true
voiceTis.value = '松开手指 取消发送'
} else {
console.log('继续发送');
voiceStop.value = false
voiceTis.value = '手指上滑 取消发送'
}
},
//
touchendVoice: () => {
// UI
if (!voiceFlg.value) {
return;
}
clearInterval(voiceTimer.value);
voiceText.value = '按住 说话'
voiceTis.value = "手指上滑 取消发送"
console.log('touchendVoice');
sv.stop();
},
//
touchcancelVoice: () => {
clearInterval(voiceTimer.value);
// UI
voiceText.value = '按住 说话'
voiceTis.value = "手指上滑 取消发送"
//
voiceStop.value = true
console.log('touchcancelVoice');
sv.stop();
},
stop: () => {
voiceTimer.value = null;
voiceFlg.value = false
recorderManager.stop(); //
console.log('录音结束');
}
};
// #ifdef APP-PLUS
//
recorderManager.onStop((res) => {
//
if (voiceStop.value) {
return
}
//
if (voiceStop.value) {
uni.showToast({
icon: "none",
title: "取消发送",
duration: 2000
})
return
}
if (voiceLength.value < 1) {
uni.showToast({
icon: "none",
title: "语音时长过短",
duration: 2000
})
return
}
if (voiceLength.value > 60) {
uni.showToast({
icon: "none",
title: "语音时长过长",
duration: 2000
})
return
}
console.log('file', res)
try {
let message = uni.$chat.createAudioMessage({
to: props.msg.id,
conversationType: props.msg.type,
payload: {
file: res
},
//
onProgress: function(event) {
console.log('file uploading:', event)
}
})
emit('send', message)
} catch (e) {
console.log('message catch', e)
}
//
})
// #endif
</script>
<style scoped lang="scss">
.voice_box {
padding: 20rpx 0;
margin: 0 20rpx;
border-radius: 50rpx;
background: #fff;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.hidden {
display: none !important;
}
.record {
width: 40vw;
height: 40vw;
position: fixed;
top: 55%;
left: 30%;
background-color: rgba(0, 0, 0, .6);
border-radius: 20rpx;
.ing {
width: 100%;
height: 30vw;
display: flex;
justify-content: center;
align-items: center;
//
@keyframes volatility {
0% {
background-position: 0% 130%;
}
20% {
background-position: 0% 150%;
}
30% {
background-position: 0% 155%;
}
40% {
background-position: 0% 150%;
}
50% {
background-position: 0% 145%;
}
70% {
background-position: 0% 150%;
}
80% {
background-position: 0% 155%;
}
90% {
background-position: 0% 140%;
}
100% {
background-position: 0% 135%;
}
}
background-image: linear-gradient(to bottom, #f09b37, #fff 50%);
background-size: 100% 200%;
animation: volatility 1.5s ease-in-out -1.5s infinite alternate;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 150rpx;
color: #f09b37;
}
.cancel {
width: 100%;
height: 30vw;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 150rpx;
}
.tis {
width: 100%;
height: 10vw;
display: flex;
justify-content: center;
font-size: 28rpx;
color: #fff;
&.change {
color: #f09b37;
}
}
}
</style>

View File

@ -73,7 +73,7 @@ function getList() {
if (rs.code == 200) {
chatList.push(...rs.data.map(item => {
item.callbackData = JSON.parse(item.callbackJson)
if (item.callbackData.callback_json.length) {
let msgType = item.callbackData.callback_json[0].MsgType;
if (msgType == TencentCloudChat.TYPES.MSG_TEXT) {
@ -143,20 +143,15 @@ function handleLogout() {
* @param {Object} item
*/
function handleChat(item) {
let param = {};
//
if (item.groupId == null) {
param.type = 'C2C'
param.name = `${item.callbackJson.from_name}`
param.msgId = `${item.callbackJson.from_id}`
} else {
//
param.type = 'GROUP'
param.name = `${item.groupChatDTO.name}`
param.msgId = `${item.groupId}`
param.num = `${item.groupChatDTO.memberCount}`
}
//
console.log(111111111, item);
let param = {
type: 'C2C',
name: `${item.callbackData.from_name}`,
msgId: `${item.callbackData.from_id}`
};
console.log(22222222, param);
util.toChat(param)
}
/**

BIN
service/static/audio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB