You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1573 lines
51 KiB

4 weeks ago
<template>
<view class="content">
<!-- 聊天内容 -->
<scroll-view
class="chat"
scroll-y="true"
scroll-with-animation="true"
:scroll-into-view="scrollToView"
refresher-enabled="true"
:refresher-triggered="triggered"
:refresher-threshold="100"
refresher-background="#f0f0f0"
@refresherpulling="onPulling"
@refresherrefresh="onRefresh"
@refresherrestore="onRestore"
@refresherabort="onAbort"
>
<view class="chat-main" :style="{ paddingBottom: inputh + 'px' }">
<view
class="chat-ls"
v-for="(item, index) in messageList"
:key="index"
:id="'msg' + index"
>
<!-- 时间分隔 -->
<view class="chat-time" v-if="item.fromtime != ''">
{{ changeTime(item.fromtime) }}
</view>
<!-- 消息体左侧对方 -->
<view class="msg-m msg-left" v-if="item.fromuser !== userId">
<view class="user-img">
<image
class="uimg"
:src="item.headimg"
@error="handleImageError($event, index)"
mode="aspectFill"
/>
<text class="uname">{{ item.fromname }}</text>
</view>
<!-- 文本 -->
<view class="message" v-if="item.type == 'txt'">
<view class="msg-text"><text selectable>{{ item.content }}</text></view>
</view>
<!-- 图片 -->
<view class="message" v-if="item.type == 'image'" @tap="previewImg(item.content)">
<image :src="item.content" class="msg-img" mode="widthFix" />
</view>
<!-- 视频 -->
<view class="messagevideo" v-if="item.type == 'video'">
<myVideo :videoUrl="item.content" />
</view>
<!-- 音频 -->
<view class="message" v-if="item.type == 'audio'" @click="playVoice(item.content, item.ifaudio, index)">
<view class="msg-text voice" :style="{ width: item.time * 40 + 'rpx' }">
<view class="voicel" v-if="item.ifaudio"><div class="bg voicePlay"></div></view>
<image v-else src="../../static/icon/horn.png" class="voice-img" />
{{ item.time }}
</view>
</view>
<!-- 商品/订单 -->
<view class="message" v-if="item.type == 'product'">
<view v-if="item.send" class="msg-product" @click="gotoDetail(item)">
<view v-if="item.send.type == 3" class="zx">
<view class="zxname">订单咨询</view>
<view class="zxview">查看订单</view>
</view>
<view class="msg-con">
<image class="img" :src="item.send.pic" mode="aspectFit" />
<view class="info">
<view class="name">
<text class="ntxt">{{ item.send.name }}</text>
<text
v-if="item.send.spData"
v-for="(value, key) in item.send.spData"
:key="key"
class="txt"
>
{{ key }}: {{ value }}
</text>
</view>
<view class="xq">
<view class="list">
<view v-if="item.send.type == 3" class="ord">
购买金额:¥ <text style="font-size: 30rpx;">{{ item.send.price }}</text>
{{ item.send.unit ? '/' + item.send.unit : '' }}
</view>
<view v-else class="price">
¥{{ item.send.price }}{{ item.send.unit ? '/' + item.send.unit : '' }}
</view>
<view v-if="item.send.quantity" class="qty">
({{ item.send.quantity }}{{ item.send.unit ? item.send.unit : '件' }})
</view>
</view>
<button class="btn" type="primary" v-if="item.send.type == 1 || item.send.type == 2">
去购买
</button>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 消息体右侧自己 -->
<view class="msg-m msg-right" v-if="item.fromuser == userId">
<view class="user-img">
<image class="uimg" :src="item.headimg" @error="handleImageError($event, index)" mode="aspectFill" />
<text class="uname">{{ item.fromname }}</text>
</view>
<!-- 群聊才显示已读/未读按钮 -->
<template v-if="chatType === 'group'">
<view v-if="item.type == 'txt' || item.type == 'image' || item.type == 'video' || item.type == 'audio' || item.type == 'product'" class="message" style="flex-direction: column; align-items: flex-end;">
<view class="rcon">
<view class="read" @click="openRead(item)">{{ item.read ? item.read.length : 0 }}</view>
<view class="noread" @click="openNoRead(item)">{{ item.read ? 3 - item.read.length : 3 }}</view>
</view>
<!-- 内容显示与左侧类似但文字颜色为白色背景为绿色故复用部分结构 -->
<view v-if="item.type == 'txt'" class="msg-text" style="margin-right: 0;">
<text selectable>{{ item.content }}</text>
</view>
<image v-if="item.type == 'image'" :src="item.content" class="msg-img" mode="widthFix" style="margin-right: 0;" />
<myVideo v-if="item.type == 'video'" :videoUrl="item.content" style="margin-right: 0;" />
<view v-if="item.type == 'audio'" class="msg-text voice" :style="{ width: item.time * 40 + 'rpx' }">
{{ item.time }}
<view class="voicel" v-if="item.ifaudio"><div class="bg voicePlay"></div></view>
<image v-else src="../../static/icon/hornrw.png" class="voice-img" />
</view>
<view v-if="item.type == 'product'" class="msg-product" style="margin-right: 0;">
<!-- 商品卡片复用左侧相同结构但右侧需调整样式因为父级flex方向不同此处省略重复代码 -->
<!-- 实际开发中应抽离为组件这里为简洁略 -->
</view>
</view>
</template>
<!-- 私聊显示已读/未读状态仅文字和图片 -->
<template v-else>
<view v-if="item.type == 'txt'" class="message">
<view v-if="item.ifread" class="read"></view>
<view v-else class="noread">未读</view>
<view class="msg-text"><text selectable>{{ item.content }}</text></view>
</view>
<view v-if="item.type == 'image'" @tap="previewImg(item.content)" class="message">
<view v-if="item.ifread" class="read"></view>
<view v-else class="noread">未读</view>
<image :src="item.content" class="msg-img" mode="widthFix" />
</view>
<view v-if="item.type == 'video'" class="messagevideo">
<view v-if="item.ifread" class="read"></view>
<view v-else class="noread">未读</view>
<myVideo :videoUrl="item.content" />
</view>
<view v-if="item.type == 'audio'" @click="playVoice(item.content, item.ifaudio, index)" class="message">
<view v-if="item.ifread" class="read"></view>
<view v-else class="noread">未读</view>
<view class="msg-text voice" :style="{ width: item.time > 2 ? item.time * 40 + 'rpx' : 'auto' }">
{{ item.time }}
<view class="voicel" v-if="item.ifaudio"><div class="bg voicePlay"></div></view>
<image v-else src="../../static/icon/hornrw.png" class="voice-img" />
</view>
</view>
<view v-if="item.type == 'product'" class="message">
<view v-if="item.ifread" class="read"></view>
<view v-else class="noread">未读</view>
<view v-if="item.send" class="msg-product" @click="gotoDetail(item)">
<!-- 商品卡片 -->
</view>
</view>
</template>
</view>
</view>
</view>
</scroll-view>
<!-- 商品/订单发送弹窗 -->
<view class="popup" v-if="ifSend && sendInfo">
<view class="tipcon">
<image class="img" :src="sendInfo.pic" mode="aspectFit" />
<view class="info">
<view class="name">
<text class="ntxt">{{ sendInfo.name }}</text>
<text v-if="sendInfo.spData" v-for="(value, key) in sendInfo.spData" :key="key" class="txt">
{{ key }}: {{ value }}
</text>
</view>
<view class="xq">
<view class="list">
<view v-if="sendInfo.type == 3" class="ord">
购买金额:¥ <text style="font-size: 30rpx;">{{ sendInfo.price }}</text>
{{ sendInfo.unit ? '/' + sendInfo.unit : '' }}
</view>
<view v-else class="price">
¥{{ sendInfo.price }}{{ sendInfo.unit ? '/' + sendInfo.unit : '' }}
</view>
<view class="qty">
{{ sendInfo.quantity ? '共(' + sendInfo.quantity + (sendInfo.unit ? sendInfo.unit : '件') + ')' : '' }}
</view>
</view>
<button class="btn" type="primary" @click="sendServer">{{ sendInfo.type == 1 ? '' : sendInfo.type == 2 ? '' : '' }}</button>
</view>
<view class="close">
<uni-icons type="closeempty" color="#bebdbd" size="16" @click="closeSendPopup" />
</view>
</view>
</view>
</view>
<!-- 输入组件 -->
<submit @inputs="inputs" @heights="heights" />
<!-- 提示 Toast -->
<u-toast ref="uToast" />
<!-- 群公告弹窗仅群聊 -->
<uni-popup ref="popup" background-color="#fff" v-if="chatType === 'group'">
<view class="popup-content">
<view class="example">
<view class="order">
<view class="chatlist">
<view class="chatcell" v-for="(person, key) in groupPersons" :key="key">
<image class="user-img" :src="person.img" />
<text class="user-name">{{ person.name }}</text>
</view>
</view>
<view class="lcon">
<view class="lhtxt">群聊名称</view>
<view class="ltxt">{{ chatInfo.chatName || '未设置' }}</view>
</view>
<view class="lcon">
<view class="lhtxt">群公告</view>
<view class="ltxt">{{ chatInfo.notice || '未设置' }}</view>
</view>
<view class="lcon">
<view class="lhtxt">备注</view>
<view class="ltxt">{{ chatInfo.reason ? chatInfo.reason : '无' }}</view>
</view>
<view class="lcon">
<view class="lhtxt">在本群昵称</view>
<view class="ltxt">{{ userName || '' }}</view>
</view>
</view>
</view>
</view>
</uni-popup>
<!-- 已读/未读列表弹窗仅群聊 -->
<uni-popup ref="popupRead" background-color="#fff" v-if="chatType === 'group'">
<view class="popup-content">
<view class="example">
<view class="order">
<view class="qhbtn">
<view :class="ifRead ? 'btn cur' : 'btn'" @click="switchReadTab(1)">({{ readInfo.read.length }})</view>
<view :class="ifRead ? 'btn' : 'btn cur'" @click="switchReadTab(2)">({{ readInfo.noread.length }})</view>
</view>
<view class="readlist" v-if="ifRead">
<view class="readcell" v-for="(item, idx) in readInfo.read" :key="idx">
<text class="readname">{{ item.name }}</text>
<text class="readtime">{{ item.time }}</text>
</view>
</view>
<view class="readlist" v-else>
<view class="readcell" v-for="(item, idx) in readInfo.noread" :key="idx">
<text class="readname">{{ item.name }}</text>
</view>
</view>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import { myCache } from '../../utils/utils.js';
import dateTime from '@/common/dateTime.js';
import submit from '@/components/chat/submit.vue';
import myVideo from '@/components/myVideo.vue';
const innerAudioContext = uni.createInnerAudioContext();
export default {
components: { submit, myVideo },
data() {
return {
triggered: false,
userName: '',
userId: '',
userHeadImg: '',
chatInfo: {}, // 会话基本信息
// 聊天类型:'private' 或 'group'
chatType: 'private',
// 消息列表
messageList: [],
// 图片预览用
imgMsg: [],
scrollToView: '',
inputh: '90',
ifaudio: false,
// 商品/订单发送
ifSend: false,
sendInfo: null,
// 群聊特有
ifRead: true,
readInfo: { read: [], noread: [] },
// WebSocket相关页面内独立管理也可抽离为全局
isConnected: false,
heartbeatInterval: null,
heartbeatTimeout: 20000,
};
},
onLoad(options) {
// 从缓存获取用户信息
const user = myCache('user');
this.userName = user.nickName || '';
this.userId = user.userid || '';
this.userHeadImg = user.avatar || require('@/static/image/girl.png');
if (options.data) {
this.chatInfo = JSON.parse(decodeURIComponent(options.data));
// 根据会话类型设置 chatType
this.chatType = this.chatInfo.sort === 'groupchat' ? 'group' : 'private';
// 设置标题
uni.setNavigationBarTitle({ title: this.chatInfo.chatName || '聊天' });
// 是否有待发送的商品/订单
if (this.chatInfo.sendinfo) {
this.ifSend = true;
this.sendInfo = this.chatInfo.sendinfo;
}
}
// 群聊时获取教练、客服等头像
if (this.chatType === 'group') {
this.getGroupMemberInfo();
}
},
onShow() {
this.getMessageList();
// 重新连接WebSocket
this.reconnect();
// 滚动到底部
setTimeout(() => this.scrollToBottom(), 300);
},
methods: {
// ---------- 消息列表加载 ----------
getMessageList() {
const list = myCache(this.chatInfo.chatId) || [];
if (list.length === 0) {
// 首次会话显示欢迎语
const welcome = `Hi${this.userName},欢迎您!这里是${this.chatInfo.chatName},请问有什么可以帮助您的呢?`;
this.messageList = [{
fromname: this.chatInfo.chatName,
fromuser: this.chatInfo.friendId || this.chatInfo.ownerId,
headimg: this.chatInfo.chatAvatar,
toname: this.userName,
touser: this.userId,
content: welcome,
fromtime: new Date().toISOString(),
type: 'txt',
}];
// 立即缓存
myCache(this.chatInfo.chatId, this.messageList);
} else {
this.messageList = list;
// 处理时间显示(已由缓存保存时处理好)
}
this.$forceUpdate();
// 加载完成后滚动到底部
this.$nextTick(() => this.scrollToBottom());
},
// ---------- WebSocket 连接(合并后统一管理) ----------
socketInit() {
const that = this;
if (this.isConnected) return;
uni.connectSocket({
3 weeks ago
url: 'wss://www.sanduolantoyoga.com/yoga-imserver',
4 weeks ago
header: { Authorization: uni.getStorageSync('token') },
success: (res) => {
console.log('socket连接成功', res);
// 拉取离线消息
this.getReadedId();
},
fail: (err) => console.log('socket连接失败', err),
});
uni.onSocketOpen(() => {
that.isConnected = true;
// 登录包
uni.sendSocketMessage({
data: JSON.stringify({ cmd: 0, data: { accessToken: uni.getStorageSync('token') } }),
success: () => console.log('登录包发送成功'),
fail: () => console.log('登录包发送失败'),
});
that.startHeartbeat();
});
uni.onSocketClose(() => {
that.isConnected = false;
that.reconnect();
});
uni.onSocketError(() => {
that.isConnected = false;
that.reconnect();
});
uni.onSocketMessage((res) => {
if (!res.data) return;
const data = JSON.parse(res.data);
console.log('收到消息', data);
// 根据 cmd 分发
if (data.cmd === 3) {
// 私聊
this.handlePrivateMessage(data.data);
} else if (data.cmd === 4) {
// 群聊
this.handleGroupMessage(data.data);
}
});
},
handlePrivateMessage(data) {
// 仅处理发给自己的消息
if (data.recvId != this.userId) return;
if (!this.ifMsgExist(data.id)) {
this.appendMessage(data);
// 已读上报
this.readedPrivate(data.id);
// 更新会话列表
this.updateChatList(data);
}
},
handleGroupMessage(data) {
if (data.groupId != this.chatInfo.groupId) return;
if (!this.ifMsgExist(data.id)) {
this.appendMessage(data);
this.readedGroup();
this.updateChatList(data);
}
},
ifMsgExist(id) {
return this.messageList.some(item => item.id === id);
},
appendMessage(data) {
// 将消息转换为显示格式
const msg = {
fromname: data.sendNickName || '匿名',
fromuser: data.sendId,
headimg: data.headImage || require('@/static/image/girl.png'),
toname: this.userName,
touser: this.userId,
content: data.type === 0 ? decodeURIComponent(data.content) : data.content,
fromtime: data.sendTime,
type: this.mapMsgType(data.type),
id: data.id,
sendId: data.sendId,
recvId: data.recvId,
time: data.type === 3 ? 5 : 0,
ifaudio: false,
read: data.readedCount ? [] : undefined, // 群聊使用
ifread: false, // 私聊使用
send: data.type === 6 ? this.getProduct(data.content) : (data.type === 5 ? this.getOrder(data.content) : null),
};
this.messageList.push(msg);
this.$forceUpdate();
// 缓存
myCache(this.chatInfo.chatId, this.messageList);
this.scrollToBottom();
},
mapMsgType(type) {
const map = {
0: 'txt',
1: 'image',
3: 'audio',
4: 'video',
5: 'product',
6: 'product',
};
return map[type] || 'txt';
},
// ---------- 读写已读状态 ----------
async readedPrivate() {
await uni.$http.put(`/api/message/private/readed?friendId=${this.chatInfo.friendId}`);
},
async readedGroup() {
await uni.$http.put(`/api/message/group/readed?groupId=${this.chatInfo.groupId}`);
},
// ---------- 拉取离线消息 ----------
async getReadedId() {
let url, param;
if (this.chatType === 'private') {
url = '/api/message/private/maxReadedId';
param = { friendId: this.chatInfo.friendId };
} else {
url = '/api/message/group/maxReadedId';
param = { groupId: this.chatInfo.groupId };
}
const { data: res } = await uni.$http.get(url, param);
if (res.data) {
this.chatInfo.minId = res.data;
this.pullOfflineMessages();
}
},
async pullOfflineMessages() {
let url, param;
if (this.chatType === 'private') {
url = '/api/message/private/pullOfflineMessage';
param = { minId: this.chatInfo.minId };
} else {
url = '/api/message/group/pullOfflineMessage';
param = { minId: this.chatInfo.minId };
}
const { data: res } = await uni.$http.get(url, param);
if (res.data && res.data.length) {
// 倒序插入(因为消息按时间升序)
res.data.forEach(item => {
if (!this.ifMsgExist(item.id)) {
const msg = this.convertMsg(item);
this.messageList.push(msg);
}
});
this.messageList.sort((a, b) => a.fromtime - b.fromtime);
myCache(this.chatInfo.chatId, this.messageList);
this.$forceUpdate();
this.scrollToBottom();
}
},
// ---------- 发送消息 ----------
async inputs(e) {
const data = {
fromname: this.userName,
fromuser: this.userId,
headimg: this.userHeadImg,
toname: this.chatInfo.chatName,
touser: this.chatInfo.friendId || this.chatInfo.groupId,
content: e.type === 'audio' ? e.message.voice : e.message,
time: e.type === 'audio' ? e.message.time : 0,
ifaudio: false,
fromtime: new Date(),
type: e.type,
};
if (e.type !== 'video') {
this.messageList.push(data);
this.scrollToBottom();
myCache(this.chatInfo.chatId, this.messageList);
}
// 根据类型上传文件并发送
if (['txt', 'audio', 'image', 'video'].includes(e.type)) {
let content = data.content;
if (e.type !== 'txt') {
// 上传文件
const url = await this.uploadFile(e.message, e.type);
content = url;
}
// 发送消息
await this.sendMsg(content, e.type);
}
},
async uploadFile(filePath, type) {
let url = '';
const token = uni.getStorageSync('token');
const uploadUrl = uni.$http.baseUrl + (type === 'image' ? '/api/image/upload' : '/api/file/upload');
uni.showLoading({ title: '上传中...' });
try {
const res = await new Promise((resolve, reject) => {
uni.uploadFile({
url: uploadUrl,
filePath,
name: 'file',
header: { Authorization: token },
success: (res) => {
const data = JSON.parse(res.data);
if (data.data) resolve(data.data.originUrl || data.data);
else reject(data.msg);
},
fail: reject,
});
});
uni.hideLoading();
return res;
} catch (err) {
uni.hideLoading();
this.$refs.uToast.show({ title: err || '上传失败', type: 'error' });
throw err;
}
},
async sendMsg(content, type) {
const mtypeMap = { txt: '0', image: '1', audio: '3', video: '4' };
const mtype = mtypeMap[type] || '0';
let param = { content };
if (this.chatType === 'private') {
param.recvId = this.chatInfo.friendId;
} else {
param.groupId = this.chatInfo.groupId;
param.atUserIds = [this.chatInfo.friendId, this.chatInfo.teacherId].filter(Boolean);
}
param.type = mtype;
const url = this.chatType === 'private' ? '/api/message/private/send' : '/api/message/group/send';
try {
const res = await uni.$http.post(url, param);
if (res.data) {
const msg = res.data;
// 更新已发送的消息
const last = this.messageList[this.messageList.length - 1];
if (last && last.content === param.content) {
last.id = msg.id;
last.fromtime = msg.sendTime;
last.sendId = msg.sendId;
last.recvId = msg.recvId;
last.ifread = false;
myCache(this.chatInfo.chatId, this.messageList);
}
}
} catch (err) {
this.messageList.pop();
uni.showToast({ title: err.msg || '发送失败', icon: 'none' });
}
},
// ---------- 聊天列表更新(缓存) ----------
updateChatList(data) {
const list = myCache('chatlist-' + this.userId) || [];
const idx = list.findIndex(item => item.id === this.chatInfo.chatId);
const entry = {
id: this.chatInfo.chatId,
content: data.content || '',
type: data.type,
minId: data.id,
datetime: data.sendTime,
name: this.chatInfo.chatName,
userid: this.userId,
fromuser: data.sendId,
img: this.chatInfo.chatAvatar,
sort: this.chatType === 'private' ? 'privatechat' : 'groupchat',
groupId: this.chatInfo.groupId,
friendId: this.chatInfo.friendId,
teacherId: this.chatInfo.teacherId,
};
if (idx >= 0) {
list[idx] = entry;
} else {
list.unshift(entry);
}
myCache('chatlist-' + this.userId, list);
},
// ---------- 工具方法 ----------
changeTime(date) {
return dateTime.dateTime1(date);
},
previewImg(url) {
const idx = this.imgMsg.indexOf(url);
uni.previewImage({
current: idx >= 0 ? idx : 0,
urls: this.imgMsg,
});
},
playVoice(url, ifplay, index) {
innerAudioContext.stop();
this.messageList.forEach((item, i) => (item.ifaudio = false));
ifplay = !ifplay;
this.messageList[index].ifaudio = ifplay;
this.$forceUpdate();
if (ifplay) {
innerAudioContext.src = url;
innerAudioContext.play();
innerAudioContext.onEnded(() => {
this.messageList[index].ifaudio = false;
this.$forceUpdate();
});
}
},
handleImageError(e, index) {
this.messageList[index].headimg = require('@/static/image/girl.png');
},
scrollToBottom() {
const len = this.messageList.length;
if (len) {
this.scrollToView = '';
this.$nextTick(() => {
this.scrollToView = 'msg' + (len - 1);
});
}
},
// 下拉刷新
onPulling() {
this.triggered = true;
setTimeout(() => (this.triggered = false), 2000);
},
onRefresh() {
this.pullOfflineMessages();
},
onRestore() { this.triggered = false; },
onAbort() { this.triggered = false; },
// 输入框高度
heights(h) {
this.inputh = h;
this.scrollToBottom();
},
// 商品/订单弹窗
closeSendPopup() {
this.ifSend = false;
this.sendInfo = null;
},
sendServer() {
// 触发发送商品/订单消息,实际调用 inputs 或直接 sendMsg
const data = {
type: 'product',
message: this.sendInfo.id,
};
this.inputs(data);
},
// 跳转详情
gotoDetail(item) {
const send = item.send;
if (!send) return;
let url = '';
if (send.type === 1) url = `/pages/product/detail?id=${send.id}`;
else if (send.type === 2) url = `/pages/product/cdetail?id=${send.id}`;
else if (send.type === 3) url = `/pages/order/orderinfo?id=${send.orderId}`;
if (url) uni.navigateTo({ url });
},
// 群聊成员信息(客服、教练、咨询用户)
async getGroupMemberInfo() {
const ids = [this.chatInfo.customerService, this.chatInfo.instructor, this.chatInfo.ownerId];
// 实际项目中可批量请求,此处简化,只取当前用户信息
// 为了演示,直接设置占位
this.groupPersons = [
{ name: this.chatInfo.customername || '客服', img: this.chatInfo.customerimg || '/static/image/kfr.png' },
{ name: this.chatInfo.instructorname || '教练', img: this.chatInfo.instructorimg || '/static/image/jltx.png' },
{ name: this.chatInfo.ownername || '咨询用户', img: this.chatInfo.ownerimg || '/static/image/mr.png' },
];
},
// 群聊已读未读弹窗
openRead(item) {
this.ifRead = true;
this.buildReadInfo(item);
this.$refs.popupRead.open('right');
},
openNoRead(item) {
this.ifRead = false;
this.buildReadInfo(item);
this.$refs.popupRead.open('right');
},
buildReadInfo(item) {
// 假设 item.read 存储已读用户id列表此处仅为示例
// 实际需从服务器获取或本地维护,此处仅演示界面
const readList = item.read || [];
const allUsers = [
{ id: this.chatInfo.customerService, name: this.chatInfo.customername || '客服' },
{ id: this.chatInfo.instructor, name: this.chatInfo.instructorname || '教练' },
{ id: this.chatInfo.ownerId, name: this.chatInfo.ownername || '咨询用户' },
];
this.readInfo.read = allUsers.filter(u => readList.includes(u.id)).map(u => ({ ...u, time: new Date() }));
this.readInfo.noread = allUsers.filter(u => !readList.includes(u.id));
},
switchReadTab(type) {
this.ifRead = type === 1;
},
// WebSocket 心跳/重连(合并,与上述 socketInit 配合)
startHeartbeat() {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = setInterval(() => {
if (this.isConnected) {
uni.sendSocketMessage({
data: JSON.stringify({ cmd: 1, data: { accessToken: uni.getStorageSync('token') } }),
fail: () => this.reconnect(),
});
}
}, this.heartbeatTimeout);
},
reconnect() {
if (!this.isConnected) {
clearTimeout(this.heartbeatInterval);
this.heartbeatInterval = setTimeout(() => this.socketInit(), this.heartbeatTimeout);
}
},
// 获取商品/订单详情(略,实际调用接口)
async getProduct(id) {
// 模拟
return { type: 1, id, name: '商品名', pic: '/static/image/nopic.png', price: 100, unit: '元' };
},
async getOrder(id) {
return { type: 3, id, orderId: '123', name: '订单', pic: '/static/image/nopic.png', price: 200, quantity: 1 };
},
},
};
</script>
<style lang="scss">
page {
height: 100%;
}
.content {
height: 100%;
background-color: rgba(244, 244, 244, 1);
position: relative;
}
::v-deep .uni-noticebar{
margin-bottom: 0 !important;
padding: 10rpx 12rpx !important;
border-radius: 10rpx;
}
.chat {
height: 100%;
.chat-main {
padding-left: 20rpx;
padding-right: 20rpx;
padding-top: 20rpx;
// padding-bottom: 120rpx; //获取动态高度
display: flex;
flex-direction: column;
}
.chat-ls {
.chat-time {
font-size: 24rpx;
color: rgba(39, 40, 50, 0.3);
line-height: 34rpx;
padding: 10rpx 0rpx;
text-align: center;
}
.msg-m {
display: flex;
padding: 20rpx 0;
.user-img {
flex: none;
width: 92rpx;
height: 90rpx;
border-radius: 20rpx;
text-align: center;
.uimg{
width: 70rpx;
height: 70rpx;
margin: 0 auto;
border-radius: 35rpx;
}
.uname{
height: 30rpx;
display: block;
margin: 0 auto;
line-height: 30rpx;
color: rgba(39, 40, 50, 0.5);
font-size: 24rpx;
}
}
.message {
flex: none;
max-width: 540rpx;
display: flex;
flex-direction: row;
}
.messagevideo {
max-width: 540rpx;
height: 300rpx;
display: flex;
flex-direction: row;
}
.msg-text {
font-size: 32rpx;
color: rgba(39, 40, 50, 1);
line-height: 44rpx;
padding: 18rpx 24rpx;
}
.msg-img {
max-width: 400rpx;
border-radius: 20rpx;
}
.msg-video{
width: 400rpx;
height: 300rpx;
border-radius: 20rpx;
}
.msg-map {
background: #fff;
width: 464rpx;
height: 284rpx;
overflow: hidden;
margin: 0 20rpx;
.map-name {
font-size: 32rpx;
color: rgba(39, 40, 50, 1);
line-height: 44rpx;
padding: 18rpx 24rpx 0 24rpx;
//下面四行是单行文字的样式
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
.map-address {
font-size: 24rpx;
color: rgba(39, 40, 50, 0.4);
padding: 0 24rpx;
//下面四行是单行文字的样式
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
.map {
padding-top: 8rpx;
width: 464rpx;
height: 190rpx;
}
}
.voice {
// width: 200rpx;
min-width: 100rpx;
max-width: 400rpx;
}
.voice-img {
width: 36rpx;
height: 36rpx;
}
}
.msg-left {
flex-direction: row;
.feed-imgy{
display: flex;
flex-direction: column;
justify-content: center;
align-self: center;
margin-right: 0rpx;
width: 95rpx;
height: 75rpx;
}
.feed-img{
display: flex;
flex-direction: column;
justify-content: center;
align-self: center;
margin-right: 0rpx;
width: 75rpx;
height: 75rpx;
}
.msg-text {
margin-left: 16rpx;
background-color: #fff;
border-radius: 0rpx 20rpx 20rpx 20rpx;
}
.ms-img {
margin-left: 16rpx;
}
.msg-video{
margin-left: 16rpx;
}
.msh-map {
margin-left: 16rpx;
border-radius: 0rpx 20rpx 20rpx 20rpx;
}
.msg-img {
margin: 0 0 0 20rpx;
}
.voice {
text-align: left;
display: flex;
}
.voice-img {
transform: rotate(180deg);
width: 36rpx;
height: 36rpx;
padding-bottom: 4rpx;
margin-right: 10rpx;
}
.voicel{
height: 32rpx;
width: 32rpx;
}
.bg {
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAYCAYAAAAF6fiUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MEM0NzgwODc1MDY0MTFFRjk1QjhFRUUxQjYyOUQ5NDQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MEM0NzgwODg1MDY0MTFFRjk1QjhFRUUxQjYyOUQ5NDQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowQzQ3ODA4NTUwNjQxMUVGOTVCOEVFRTFCNjI5RDk0NCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowQzQ3ODA4NjUwNjQxMUVGOTVCOEVFRTFCNjI5RDk0NCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqxjhsEAAANzSURBVHjazJlLaNRQFIaTVIcRQUELoggVKYIgQvGFr40giLMRBAtjXblwUSmIK1vBTQUREUFB8NGdGwUXgoIVFwq6EGSkOAguXKiUQQq6kKK0Gv+LJ+HkTpLJfYUc+DnJ3M436Tn3eeKHYeiZ2NDQkGfZfCj03Nka6DzUgqZMYa1WS45HzIf15C/xqmeuE7ATOs3up1zxRefslYSgggn465j/GLpJ13ehfS75SEIu36/AFCQ6wQHoBbTgIOBZfBGow9AvaIXBb8d89PYFFpcEn7e5GAG+wXfPQc+g91DNQQKy+A3oB1SnkWDMR9BrbG0oxLeVgNAgCeLhF6FN0CuLiS3CP07+BDTgko/kbHC9BugG6w20ja63QxNSYk0tj/8EatP1uOYuKMFHoCdYW09+kDKnT0Kz0KjGKChik9AsxPkz0FnWvtIgsar8y2wUBDp8BDrBR+zS+CP4PCgyApZDa6Eb+ELDQQJiPs3DkV2FOnQ9atDjVfkPyS+DduvykQQtfpAypM7A3aPbB0iC7bNCgi+dRW6RP2owDanyf0Lv6HqPKl+KTxcf8czlBxnz2gjcPGXtiIOdSRb/Ofmt1KY7DanyZ8gP2uIjMYX4eXPeNPm9jg5Eafwv7IS+rkT+N/Kry+bnJeA3+T5HCUjjhxa3oCr8UOM3rfDzEnCQ/Fu2Q7K5be3i0+IWWadEfj/572Xzg4zywhW4VWwhs128y+LvJ9+mxbEs/mbyn2zxafHtyU87B4yzPe1JgOZZ8x8LwU/waTGL7BT5RwZTkSq/Rge06NCmxJfi08Wn8kQmP20ErKc56xIvpQJUx72NBMR8L1kKbkIb6fq6FKDQIb9BI1v8by91+YiPFj+1GgpYP4I9x+593IcZ05VOEsScOMfuxUuMr/Sg4uHHWNtSjUqlCv81HZDuQ8OqfPFChl7CxHx8NsbiE/Px+XChBKiYhXL0IPUMsYB9lopiddpthI74O9i0sIXVbbT5CPIAi02Cj7a2y2KcrjXp4UXpdpfUtmihIJfHjxbQp5rBL8xPC77NXY2JXaOeOS1t3fooAa74t9loaNrgI8gd1vsL8f0KvpSPdj4u3wuLncxFuj4mbSWVTH4pz3aSMR9/k8mv4jthz3HwD7HgXDAJfkaHTPDzgl+VKahs+wjdgT54/0vUzvhUos61fwIMANiWariLn8/jAAAAAElFTkSuQmCC);
width: 32rpx;
height: 32rpx;
background-size: 400%;
}
.msg-product{
display: flex;
flex-direction: column;
width: 100%;
background-color: #FFFFFF;
padding: 10rpx;
border-radius: 16rpx;
width: 480rpx;
margin-left: 12rpx;
.zx{
display: flex;
justify-content: space-between;
width: 100%;
font-size: 28rpx;
margin: 10rpx;
.zxview{
color: darkgrey;
font-size: 26rpx;
margin-right: 20rpx;
}
}
.msg-con{
display: flex;
justify-content: space-between;
width: 100%;
padding: 10rpx;
border-radius: 16rpx;
}
.img{
width: 120rpx;
height: 120rpx;
}
.info{
display: flex;
flex-direction: column;
flex: 1;
padding: 6rpx 8rpx;
position: relative;
margin-left: 10rpx;
background-color: #f4f4f4;
border-radius: 0 10rpx 10rpx 0;
.name{
width: 330rpx;
// white-space: nowrap; /* 防止文本换行 */
// overflow: hidden; /* 隐藏溢出的内容 */
// text-overflow: ellipsis; /* 溢出部分显示省略号 */
.txt{
font-size: 26rpx;
color: darkgrey;
margin-left: 6rpx;
}
.ntxt{
font-size: 28rpx;
color: #000;
}
}
.sku{
display: flex;
flex-direction: row;
.txt{
font-size: 26rpx;
color: darkgrey;
margin-right: 6rpx;
}
}
.xq{
width: 100%;
display: flex;
flex-direction: row;
.list{
display: flex;
flex: 1;
}
.price{
display: flex;
align-items: center;
font-size: 28rpx;
color: #ff007f;
}
.ord{
display: flex;
align-items: center;
font-size: 26rpx;
color: #232323;
}
.qty{
display: flex;
align-items: center;
font-size: 26rpx;
color: darkgrey;
margin-left: 8rpx;
}
.btn{
font-size: 26rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500;
color: #FCFCFD;
width: 110rpx;
height: 50rpx;
line-height: 50rpx;
background: #ff007f;
box-shadow: 0rpx 10rpx 30rpx 2rpx rgba(255,0,127,0.2);
border-radius: 60rpx;
opacity: 1;
margin-right: 10rpx;
margin-top: 10rpx;
margin-bottom: 10rpx;
padding-left:0 !important;
padding-right:0 !important;
&::after{
border:none;
}
}
}
}
}
}
.msg-right {
flex-direction: row-reverse;
.rcon{
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
}
.read{
font-size: 24rpx;
color: #888;
margin-right: 10rpx;
display: flex;
justify-content: flex-end;
align-items: flex-end;
}
.noread{
font-size: 24rpx;
color: #00a89b;
margin-right: 10rpx;
display: flex;
justify-content: flex-end;
align-items: flex-end;
}
.feed-imgy{
display: flex;
flex-direction: column;
justify-content: center;
align-self: center;
margin-right: 0rpx;
width: 95rpx;
height: 75rpx;
}
.feed-img{
display: flex;
flex-direction: column;
justify-content: center;
align-self: center;
margin-right: 0rpx;
width: 75rpx;
height: 75rpx;
}
.msg-text {
margin-right: 16rpx;
background-color: #00a89b;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 32rpx;
color: #FFFFFF;
line-height: 48rpx;
border-radius: 20rpx 0rpx 20rpx 20rpx;
}
.ms-img {
margin-right: 16rpx;
}
.msg-img {
margin: 0 20rpx 0 0;
}
.msg-video{
margin: 0 20rpx 0 0;
}
.msh-map {
margin-left: 16rpx;
border-radius: 20rpx 0rpx 20rpx 20rpx;
}
.voice {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
}
.voice-img {
display: block;
width: 36rpx;
height: 36rpx;
margin-left: 10rpx;
}
.voicel{
height: 32rpx;
width: 32rpx;
}
.bg {
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAYCAYAAAAF6fiUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REJBQzQ1RjQ1MDYzMTFFRjk5QjZEQkI5MDlENzE3RTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REJBQzQ1RjU1MDYzMTFFRjk5QjZEQkI5MDlENzE3RTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEQkFDNDVGMjUwNjMxMUVGOTlCNkRCQjkwOUQ3MTdFNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEQkFDNDVGMzUwNjMxMUVGOTlCNkRCQjkwOUQ3MTdFNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pm5p+OoAAAKmSURBVHja1Jk/SBxBFMbdy6mHSEARNYIkhU0ICCEkEEynEYmkUEiTxmBtZZHCgGVsAgEhQozYaAo7G0GwjgemC1ZBgzbaKKnEIsb1HbwjH8PM7szdfEQffMzyzexvhn2zM/snSdO0wSOmRfdFc6JfDfGDzb++UUlAjmbTfzHi0T5UbP61Vl6DV3BxZggDYPNRyU1LQDtcnC9GXY/ojeh2HZ2z+ahbZD4lAZt6cQ4Mv1v0W+tm6+icza+qSOZTEvAAZudjo+5Q/SNRX40ds/m47JSIfFoC1nSQ24Y/r/4fUZdR1xHQMZtfVSOZH7LndPgmoEl0oQMdA/8OzNrXxjlzokvRgsfA2HxUM5nvUsmXbzt5CGZJE/jv1d832k8GPsmw+TgDEyLfd8PP5NsAM9q4bPi76r8FrwXgHzwHyOa7loDY/Kw73JtvAyzpCcvgtQLoKfgT6p0GDJDNtyWAwXepEMIvWF6O27Q8Aa8bjo/h+JGWWwEv32y+Ldh8jMsQvi0B1Y9DicUz/b9aNod8/SDzs/pk8V2Ry7cl4FTLTvCORBd63Av+Ny2HAwbF5tuCzXdFLt+WgD0t+8E7F/3Q40Hw17WuRbTqOSg23zbrGXyfyOdbNoYB2LBaLU8vx0b7UWj/0WOTYvNdGzGD76NMvu0OKGvWKjEO/ifY0KbB3xBN6eZ25jEr2HxXxOQnAf1m8x1ZW9SM7Rr+O8hmfx2z4jOZ71IsfoH9LegeDPSFUfcd3mSf1NjxXTI/64UsBj/av4WC47Y5EK3o8VejbkD0U1QUPa9xOTgk812bcSx+GrgM1fRLsvIl8Vxny4albqjOW5HNz+ubyY/2S/IZLBULhAGz+Tf+n7D5Ne8lYRBsPmXzjKWixyq1rOVD0Q7hZYXNz9oP/ntcCTAAHScbp0OSlTgAAAAASUVORK5CYII=);
width: 32rpx;
height: 32rpx;
background-size: 400%;
}
.msg-product{
display: flex;
flex-direction: column;
width: 100%;
background-color: #FFFFFF;
padding: 10rpx;
border-radius: 16rpx;
width: 480rpx;
margin-right: 12rpx;
.zx{
display: flex;
justify-content: space-between;
width: 100%;
font-size: 28rpx;
margin: 10rpx;
.zxview{
color: darkgrey;
font-size: 26rpx;
margin-right: 20rpx;
}
}
.msg-con{
display: flex;
justify-content: space-between;
width: 100%;
padding: 10rpx;
border-radius: 16rpx;
}
.img{
width: 120rpx;
height: 120rpx;
}
.info{
display: flex;
flex-direction: column;
flex: 1;
padding: 6rpx 8rpx;
position: relative;
margin-left: 10rpx;
background-color: #f4f4f4;
border-radius: 0 10rpx 10rpx 0;
.name{
width: 330rpx;
// white-space: nowrap; /* 防止文本换行 */
// overflow: hidden; /* 隐藏溢出的内容 */
// text-overflow: ellipsis; /* 溢出部分显示省略号 */
.txt{
font-size: 26rpx;
color: darkgrey;
margin-left: 6rpx;
}
.ntxt{
font-size: 28rpx;
color: #000;
}
}
.sku{
display: flex;
flex-direction: row;
.txt{
font-size: 26rpx;
color: darkgrey;
margin-right: 6rpx;
}
}
.xq{
width: 100%;
display: flex;
flex-direction: row;
.list{
display: flex;
flex: 1;
}
.price{
display: flex;
align-items: center;
font-size: 28rpx;
color: #ff007f;
}
.ord{
display: flex;
align-items: center;
font-size: 26rpx;
color: #232323;
}
.qty{
display: flex;
align-items: center;
font-size: 26rpx;
color: darkgrey;
margin-left: 8rpx;
}
.btn{
font-size: 26rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500;
color: #FCFCFD;
width: 110rpx;
height: 50rpx;
line-height: 50rpx;
background: #ff007f;
box-shadow: 0rpx 10rpx 30rpx 2rpx rgba(255,0,127,0.2);
border-radius: 60rpx;
opacity: 1;
margin-right: 10rpx;
margin-top: 10rpx;
margin-bottom: 10rpx;
padding-left:0 !important;
padding-right:0 !important;
&::after{
border:none;
}
}
}
}
}
}
}
}
.voicePlay {
animation-name: voicePlay;
animation-duration: 1s;
animation-direction: normal;
animation-iteration-count: infinite;
animation-timing-function: steps(3);
}
@keyframes voicePlay {
0% {
background-position: 0;
}
100% {
background-position: 100%;
}
}
.popup{
position: fixed;
bottom: 180rpx;
/* #ifdef H5 */
bottom: 100rpx;
/* #endif */
left: 0;
width: 100%;
padding: 18rpx;
border-radius: 16rpx;
.tipcon{
display: flex;
justify-content: space-between;
width: 100%;
background-color: #FFFFFF;
padding: 16rpx;
border-radius: 16rpx;
.img{
width: 100rpx;
height: 100rpx;
}
.info{
display: flex;
flex-direction: column;
flex: 1;
margin-left: 16rpx;
position: relative;
.name{
width: 550rpx;
// white-space: nowrap; /* 防止文本换行 */
// overflow: hidden; /* 隐藏溢出的内容 */
// text-overflow: ellipsis; /* 溢出部分显示省略号 */
margin-right: 50rpx;
.txt{
font-size: 26rpx;
color: darkgrey;
margin-left: 6rpx;
}
.ntxt{
font-size: 28rpx;
color: #000;
}
}
.sku{
display: flex;
flex-direction: row;
.txt{
font-size: 26rpx;
color: darkgrey;
margin-right: 6rpx;
}
}
.xq{
width: 100%;
display: flex;
flex-direction: row;
.list{
display: flex;
flex: 1;
align-items: flex-end;
}
.price{
font-size: 28rpx;
color: #ff007f;
}
.ord{
display: flex;
align-items: center;
font-size: 26rpx;
color: #232323;
}
.qty{
font-size: 26rpx;
color: darkgrey;
margin-left: 6rpx;
}
.btn{
font-size: 26rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500;
color: #FCFCFD;
width: 150rpx;
height: 50rpx;
line-height: 50rpx;
background: #ff007f;
box-shadow: 0rpx 10rpx 30rpx 2rpx rgba(255,0,127,0.2);
border-radius: 10rpx;
opacity: 1;
margin-right: 10rpx;
padding-left:0 !important;
padding-right:0 !important;
&::after{
border:none;
}
}
}
.close{
position: absolute;
top: 0;
right: 6rpx;
}
}
}
}
.example {
width: 550rpx;
opacity: 1;
padding: 0;
box-sizing: border-box;
.order {
position: relative;
overflow: hidden;
display: block;
background-color: #ffffff;
margin: 0 0 10rpx;
box-sizing: border-box;
padding: 0rpx 20rpx;
}
.qhbtn{
width: 100%;
display: flex;
flex-direction: row;
.btn{
width: 120rpx;
height: 60rpx;
border-radius: 20rpx;
background-color: #eee;
color: #000;
font-size: 26rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
margin-top: 16rpx;
}
.cur{
background-color: #00a89b;
color: #fff;
}
}
.readlist{
display: flex;
flex-wrap: wrap;
padding: 20rpx 0 0 10rpx ;
}
.readcell{
display: flex;
justify-content: space-between;
width: 100%;
padding: 16rpx 0;
border-bottom: 1rpx solid #eee;
.readtime{
font-size: 24rpx;
color: #888;
}
.readname{
font-size: 26rpx;
color: #33383c;
display: flex;
flex: 1;
}
}
.chatlist{
display: flex;
flex-wrap: wrap;
padding: 20rpx 0 0 10rpx ;
}
.chatcell{
display: flex;
flex-direction: column;
min-width: 90rpx;
height: 150rpx;
align-items: center;
justify-content: center;
margin-right: 30rpx;
.user-img{
width: 90rpx;
height: 90rpx;
border-radius: 5rpx;
}
.user-name{
font-size: 26rpx;
color: #33383c;
margin-top: 16rpx;
}
}
.lcon{
padding: 20rpx 10rpx;
display: flex;
flex-direction: column;
border-bottom: 1rpx solid #eee;
.lhtxt{
font-size: 30rpx;
color: #33383c;
line-height: 40rpx;
}
.ltxt{
margin-top: 16rpx;
font-size: 28rpx;
color: #888;
line-height: 30rpx;
}
}
}
</style>