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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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({
url: 'ws://192.168.13.9:8528/im',
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>