新增聊天系统
This commit is contained in:
441
pages/contact/contact.vue
Normal file
441
pages/contact/contact.vue
Normal file
@@ -0,0 +1,441 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="header-wrap">
|
||||
<view class="header">
|
||||
<view class="item" @click="back()">
|
||||
<uni-icons type="left" color="#111" size="22"></uni-icons>
|
||||
</view>
|
||||
<view class="title">
|
||||
<text class="t">咨询</text>
|
||||
</view>
|
||||
<view class="item"></view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view class="message-wrap" id="message-wrap">
|
||||
<view class="more" v-if="list.length >= 10">
|
||||
<text class="t" v-if="!finish">下拉加载更多历史消息</text>
|
||||
<text class="t" v-else>没有更多消息了~</text>
|
||||
</view>
|
||||
<view class="more" v-if="status">
|
||||
<text class="t">{{ contactInfo }}</text>
|
||||
</view>
|
||||
<view class="item" :class="[`item${item.user_type}`]" v-for="item in list" :key="item.id">
|
||||
<image class="avatar" src="/static/icon_contact_avatar.svg" mode="aspectFill" v-if="item.user_type == 2"></image>
|
||||
<view class="msg-wrap">
|
||||
<view class="msg" v-if="item.type == 1">
|
||||
<text class="t">{{ item.content }}</text>
|
||||
</view>
|
||||
<image class="img" :src="item.content" mode="heightFix" @click="previewImageHandle([item.content])" v-else></image>
|
||||
<view class="time-wrap">
|
||||
<text class="t">{{ item.add_time | formatTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="focus-wrap" v-if="iosFocus"></view>
|
||||
<view class="footer-wrap" :class="{ focus: iosFocus }">
|
||||
<view class="footer">
|
||||
<view class="item" @click="selectImg">
|
||||
<uni-icons type="image-filled" size="30" color="#666"></uni-icons>
|
||||
</view>
|
||||
<view class="input-wrap">
|
||||
<input :focus="focus" adjust-position class="input" type="text" placeholder="输入消息..." v-model.trim="messageValue" @confirm="sendMessageHandle" @focus="inputFocus" @blur="inputBlur" />
|
||||
</view>
|
||||
<view class="item" style="transform: rotate(280deg)" @click="sendMessageHandle">
|
||||
<uni-icons type="paperplane-filled" size="30" color="#5C6BC0"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const app = getApp();
|
||||
import dayjs from 'dayjs';
|
||||
import socket from '@/utils/socket';
|
||||
import { uploadImage, previewImage } from '@/utils/uploadFile.js';
|
||||
|
||||
export default {
|
||||
mixins: [socket.createSocketMixin()],
|
||||
data() {
|
||||
return {
|
||||
iosFocus: false,
|
||||
focus: false,
|
||||
messageValue: '',
|
||||
userInfo: '',
|
||||
status: 0,
|
||||
contactInfo: '',
|
||||
finish: false,
|
||||
page: 1,
|
||||
list: [],
|
||||
platform: '',
|
||||
};
|
||||
},
|
||||
filters: {
|
||||
formatTime(timeStr) {
|
||||
const now = dayjs(); // 当前时间
|
||||
const targetTime = dayjs(timeStr); // 目标时间
|
||||
|
||||
// 获取日期部分(忽略时分秒)
|
||||
const nowDate = now.startOf('day');
|
||||
const targetDate = targetTime.startOf('day');
|
||||
|
||||
// 计算日期差(按天计算)
|
||||
const diffDays = nowDate.diff(targetDate, 'day');
|
||||
|
||||
// 星期几的中文映射
|
||||
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
const targetWeekDay = weekDays[targetTime.day()];
|
||||
|
||||
// 获取当前周的第一天(周日)
|
||||
const firstDayOfWeek = now.startOf('week');
|
||||
|
||||
// 根据天数差判断格式化规则
|
||||
if (diffDays === 0) {
|
||||
// 今天
|
||||
return targetTime.format('HH:mm');
|
||||
} else if (diffDays === 1) {
|
||||
// 昨天
|
||||
return '昨天 ' + targetTime.format('HH:mm');
|
||||
} else if (targetDate.isSame(firstDayOfWeek, 'week')) {
|
||||
// 本周内(但不是今天和昨天)
|
||||
return targetWeekDay + ' ' + targetTime.format('HH:mm');
|
||||
} else {
|
||||
// 更早之前
|
||||
return targetTime.format('YYYY年M月D日 HH:mm');
|
||||
}
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
this.userInfo = uni.getStorageSync('cache_shop_user_info_key');
|
||||
|
||||
if (this.userInfo.id) {
|
||||
// 自动管理生命周期的消息监听
|
||||
this.socketOnMessage((data) => {
|
||||
this.messageListener(data);
|
||||
});
|
||||
socket.sendMessage({
|
||||
operate_type: 'chat_list',
|
||||
type: 'user_msg',
|
||||
user_id: this.userInfo.id,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取系统信息
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.platform = systemInfo.platform;
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.page++;
|
||||
this.getHistoryMessage();
|
||||
},
|
||||
methods: {
|
||||
// 发消息
|
||||
sendMessageHandle() {
|
||||
if (this.messageValue) {
|
||||
this.sendMessageSocket(this.messageValue);
|
||||
this.messageValue = '';
|
||||
|
||||
setTimeout(() => {
|
||||
this.focus = true;
|
||||
}, 10);
|
||||
}
|
||||
},
|
||||
// 发送消息 1文本 2图文
|
||||
sendMessageSocket(content = '', type = 1) {
|
||||
socket.sendMessage({
|
||||
operate_type: 'send_msg',
|
||||
type: 'user_msg',
|
||||
user_id: this.userInfo.id,
|
||||
content: content,
|
||||
content_type: type,
|
||||
});
|
||||
},
|
||||
// 监听消息
|
||||
messageListener(data) {
|
||||
// console.log('监听消息===', data);
|
||||
if (data.operate_type == 'chat_list' && this.page == 1) {
|
||||
this.list = data.data.chat_list.reverse();
|
||||
this.status = 1;
|
||||
this.contactInfo = data.service;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
}
|
||||
if (data.operate_type == 'chat_list' && this.page != 1) {
|
||||
if (data.data.chat_list.length) {
|
||||
this.list.unshift(...data.data.chat_list.reverse());
|
||||
|
||||
uni.showToast({
|
||||
title: '获取成功',
|
||||
icon: 'none',
|
||||
});
|
||||
} else {
|
||||
this.finish = true;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
uni.stopPullDownRefresh();
|
||||
}, 300);
|
||||
|
||||
// this.$nextTick(() => {
|
||||
// uni.pageScrollTo({
|
||||
// scrollTop: 0, // 滚动到页面顶部
|
||||
// duration: 100, // 滚动动画时长(毫秒)
|
||||
// });
|
||||
// });
|
||||
}
|
||||
if (data.operate_type == 'service_msg') {
|
||||
this.list.push(data.data);
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
}
|
||||
|
||||
if (data.operate_type == 'user_msg') {
|
||||
this.list.push(data.data[0]);
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
}
|
||||
|
||||
if (data.operate_type == 'error') {
|
||||
uni.showModal({
|
||||
title: '注意',
|
||||
content: data.msg,
|
||||
showCancel: false,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateBack();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
// 获取历史消息
|
||||
getHistoryMessage() {
|
||||
socket.sendMessage({
|
||||
operate_type: 'chat_list',
|
||||
type: 'user_msg',
|
||||
user_id: this.userInfo.id,
|
||||
page: this.page,
|
||||
});
|
||||
},
|
||||
// 上传图片
|
||||
async selectImg() {
|
||||
try {
|
||||
const [url] = await uploadImage(1, true);
|
||||
console.log('selectImg===', url);
|
||||
this.sendMessageSocket(url, 2);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
// 预览图片
|
||||
previewImageHandle(urls) {
|
||||
previewImage(urls);
|
||||
},
|
||||
// 返回上一页
|
||||
back() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
inputBlur() {
|
||||
this.focus = false;
|
||||
this.iosFocus = false;
|
||||
},
|
||||
inputFocus() {
|
||||
if (this.platform == 'ios') {
|
||||
this.iosFocus = true;
|
||||
}
|
||||
|
||||
this.scrollToBottom();
|
||||
},
|
||||
// 滚动到页面底部
|
||||
scrollToBottom() {
|
||||
// 创建节点查询器
|
||||
const query = uni.createSelectorQuery();
|
||||
|
||||
// 获取底部元素的位置(相对于页面顶部的距离)
|
||||
query.select('#message-wrap').boundingClientRect();
|
||||
query.exec((res) => {
|
||||
console.log('scrollToBottom===', res);
|
||||
if (res && res[0]) {
|
||||
const bottomHeight = res[0].height; // 元素顶部距离页面顶部的距离
|
||||
|
||||
// 滚动到指定位置(注意:不同平台参数可能不同)
|
||||
uni.pageScrollTo({
|
||||
scrollTop: bottomHeight + 1000, // 滚动距离(单位:px)
|
||||
duration: 100, // 滚动动画时长(可选)
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
--header-height: 44px;
|
||||
--footer-height: 60px;
|
||||
$height: 40px;
|
||||
/* #ifdef H5 */
|
||||
padding: calc(var(--header-height) + var(--status-bar-height)) 0 calc(var(--footer-height) + env(safe-area-inset-bottom)) 0;
|
||||
/* #endif */
|
||||
/* #ifdef MP-ALIPAY */
|
||||
padding: 28upx 0 calc(var(--footer-height) + env(safe-area-inset-bottom)) 0;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
|
||||
.header-wrap {
|
||||
width: 100%;
|
||||
padding-top: var(--status-bar-height);
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99;
|
||||
background-color: #fff;
|
||||
.header {
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.item {
|
||||
width: var(--header-height);
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.title {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.t {
|
||||
font-size: 32upx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.message-wrap {
|
||||
padding: 28upx;
|
||||
.more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-bottom: 28upx;
|
||||
.t {
|
||||
font-size: 24upx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.item {
|
||||
display: flex;
|
||||
gap: 20upx;
|
||||
padding-bottom: 36upx;
|
||||
&.item1 {
|
||||
flex-direction: row-reverse;
|
||||
.msg-wrap {
|
||||
.msg {
|
||||
background-color: #8e99f3;
|
||||
.t {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.item2 {
|
||||
.msg-wrap {
|
||||
.time-wrap {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
.avatar {
|
||||
$size: 80upx;
|
||||
width: $size;
|
||||
height: $size;
|
||||
margin-top: 7upx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.msg-wrap {
|
||||
.msg {
|
||||
padding: 28upx;
|
||||
border-radius: 28upx;
|
||||
background-color: #fff;
|
||||
.t {
|
||||
display: block;
|
||||
max-width: 450upx;
|
||||
font-size: 28upx;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.img {
|
||||
display: block;
|
||||
max-width: 500upx;
|
||||
max-height: 200upx;
|
||||
border-radius: 12upx;
|
||||
background-color: #fefefe;
|
||||
}
|
||||
.time-wrap {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 12upx;
|
||||
.t {
|
||||
font-size: 24upx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.focus-wrap {
|
||||
height: calc(var(--footer-height) + env(safe-area-inset-bottom));
|
||||
}
|
||||
.footer-wrap {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background-color: #fcfcfc;
|
||||
transform: translateY(0);
|
||||
&.focus {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
.footer {
|
||||
height: var(--footer-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.item {
|
||||
width: $height + 20px;
|
||||
height: $height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.input-wrap {
|
||||
flex: 1;
|
||||
.input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: $height;
|
||||
border-radius: $height;
|
||||
background-color: #efefef;
|
||||
padding-left: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user