Files
xo_user_client/pages/contact/contact.vue
2025-06-16 17:51:09 +08:00

442 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>