|
|
@@ -0,0 +1,688 @@
|
|
|
+<template>
|
|
|
+ <view class="chat-container">
|
|
|
+ <!-- 连接状态指示器 -->
|
|
|
+ <view class="connection-status" v-if="!isConnected">
|
|
|
+ <text class="status-text">{{ reconnectCount > 0 ? '重连中...' : '连接中...' }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 聊天消息列表 -->
|
|
|
+ <scroll-view class="message-list" :scroll-top="scrollTop" scroll-y :scroll-into-view="scrollIntoView" scroll-with-animation>
|
|
|
+ <view class="message-item" v-for="(message, index) in messageList" :key="index" :id="'message-' + index">
|
|
|
+ <!-- 左侧消息(商家/其他用户) -->
|
|
|
+ <view class="message-left" v-if="!message.isMine && !message.shopId">
|
|
|
+ <view class="avatar-wrapper">
|
|
|
+ <image class="avatar" :src="message.avatar" mode="aspectFill" />
|
|
|
+ </view>
|
|
|
+ <view class="message-content-wrapper">
|
|
|
+ <view class="username" v-if="isMultiUser">{{ message.username }}</view>
|
|
|
+ <view class="message-bubble left-bubble">
|
|
|
+ <text class="message-text">{{ message.message }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="message-time">{{ message.time }}</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 右侧消息(我的) -->
|
|
|
+ <view class="message-right" v-if="message.isMine || message.shopId">
|
|
|
+ <view class="message-content-wrapper">
|
|
|
+ <view class="username align-right" v-if="isMultiUser">{{ message.username }}</view>
|
|
|
+ <view class="message-bubble right-bubble">
|
|
|
+ <text class="message-text">{{ message.message }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="message-time align-right">{{ message.time }}</view>
|
|
|
+ </view>
|
|
|
+ <view class="avatar-wrapper">
|
|
|
+ <image class="avatar" :src="message.avatar" mode="aspectFill" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <view class="empty-state" v-if="messageList.length === 0">
|
|
|
+ <text class="empty-text">暂无聊天记录</text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+
|
|
|
+ <!-- 输入区域 -->
|
|
|
+ <view class="input-area">
|
|
|
+ <view class="input-wrapper">
|
|
|
+ <input
|
|
|
+ class="message-input"
|
|
|
+ v-model="inputText"
|
|
|
+ placeholder="请输入消息..."
|
|
|
+ :placeholder-style="placeholderStyle"
|
|
|
+ @confirm="sendMessage"
|
|
|
+ confirm-type="send"
|
|
|
+ />
|
|
|
+ <button class="send-button" :class="{ 'send-active': inputText.trim() }" @click="sendMessage" :disabled="!inputText.trim()">发送</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getChatHistory } from "@/api/chat";
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ inputText: '',
|
|
|
+ scrollTop: 0,
|
|
|
+ scrollIntoView: '',
|
|
|
+ placeholderStyle: 'color: #ccc; font-size: 28upx;',
|
|
|
+ // 是否多人聊天
|
|
|
+ isMultiUser: false,
|
|
|
+ // 当前用户信息
|
|
|
+ currentUser: {
|
|
|
+ customId: 0,
|
|
|
+ shopId: 0,
|
|
|
+ id: 0,
|
|
|
+ username: '',
|
|
|
+ avatar: ''
|
|
|
+ },
|
|
|
+ // 聊天消息列表
|
|
|
+ messageList: [],
|
|
|
+ // WebSocket相关
|
|
|
+ socket: null,
|
|
|
+ isConnected: false,
|
|
|
+ room: '',
|
|
|
+ reconnectCount: 0,
|
|
|
+ maxReconnectCount: 3,
|
|
|
+ isManualDisconnect: false // 添加标志位:是否主动断开连接
|
|
|
+ };
|
|
|
+ },
|
|
|
+ onLoad(options) {
|
|
|
+ console.log('---------- options: ', options);
|
|
|
+ // 根据传入参数判断是否多人聊天
|
|
|
+ if (options.isMultiUser) {
|
|
|
+ this.isMultiUser = options.isMultiUser === 'true';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果传入了用户信息,更新当前用户
|
|
|
+ if (options.customId) {
|
|
|
+ this.currentUser.customId = options.customId;
|
|
|
+ }
|
|
|
+ if (options.shopId) {
|
|
|
+ this.currentUser.shopId = options.shopId;
|
|
|
+ }
|
|
|
+ if (options.name) {
|
|
|
+ this.currentUser.username = options.name;
|
|
|
+ }
|
|
|
+ if (options.shopId) {
|
|
|
+ // 是门店,则使用shopId
|
|
|
+ this.currentUser.id = options.shopId;
|
|
|
+ }
|
|
|
+ if (options.avatar) {
|
|
|
+ this.currentUser.avatar = options.avatar;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取房间号
|
|
|
+ if (options.room) {
|
|
|
+ this.room = options.room;
|
|
|
+ } else {
|
|
|
+ if (this.currentUser.customId && this.currentUser.shopId) {
|
|
|
+ this.room = 'custom_id-' + this.currentUser.customId + 'AND' + 'shop_id-' + this.currentUser.shopId;
|
|
|
+ } else {
|
|
|
+ // 如果没有传入房间号,使用默认值或生成一个
|
|
|
+ this.room = 'default_room';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ console.log('---------- this.room: ', this.room);
|
|
|
+
|
|
|
+ // 加载聊天记录
|
|
|
+ this.loadChatHistory();
|
|
|
+
|
|
|
+ // 连接WebSocket
|
|
|
+ this.connectWebSocket();
|
|
|
+ },
|
|
|
+ onReady() {
|
|
|
+ // 页面加载完成后滚动到底部
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.scrollToBottom();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onUnload() {
|
|
|
+ // 页面卸载时关闭WebSocket连接
|
|
|
+ this.isManualDisconnect = true; // 标记为主动断开
|
|
|
+ this.disconnectWebSocket();
|
|
|
+ },
|
|
|
+ onHide() {
|
|
|
+ // 页面隐藏时可以选择性地关闭连接(根据业务需求)
|
|
|
+ // this.disconnectWebSocket()
|
|
|
+ },
|
|
|
+ onShow() {
|
|
|
+ // 页面显示时重新连接(如果之前断开了)
|
|
|
+ // if (!this.isConnected && this.room) {
|
|
|
+ // this.connectWebSocket();
|
|
|
+ // }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ init() {},
|
|
|
+ // 加载聊天历史记录
|
|
|
+ loadChatHistory() {
|
|
|
+ // 在实际应用中,这里应该从服务器加载历史聊天记录
|
|
|
+ // 可以通过API请求获取该房间的历史消息
|
|
|
+ getChatHistory({
|
|
|
+ roomName: this.room,
|
|
|
+ userId: this.currentUser.id,
|
|
|
+ }).then((res) => {
|
|
|
+ if (res.code == 1 && res.data) {
|
|
|
+ this.messageList = res.data;
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.scrollToBottom();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 临时保留一些示例数据用于开发测试
|
|
|
+ // if (process.env.NODE_ENV === "development") {
|
|
|
+ // const sampleMessages = [
|
|
|
+ // {
|
|
|
+ // id: "msg_001",
|
|
|
+ // content: "您好,请问有什么可以帮助您的吗?",
|
|
|
+ // time: "14:30",
|
|
|
+ // isMine: false,
|
|
|
+ // username: "花店客服",
|
|
|
+ // avatar: "https://cdn.uviewui.com/uview/demo/user/2.jpg",
|
|
|
+ // },
|
|
|
+ // {
|
|
|
+ // id: "msg_002",
|
|
|
+ // content: "我想订购一束玫瑰花",
|
|
|
+ // time: "14:31",
|
|
|
+ // isMine: true,
|
|
|
+ // username: "我",
|
|
|
+ // avatar: "https://cdn.uviewui.com/uview/demo/user/1.jpg",
|
|
|
+ // },
|
|
|
+ // ];
|
|
|
+ // this.messageList = sampleMessages;
|
|
|
+ // }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取主机地址
|
|
|
+ getHost() {
|
|
|
+ // 在实际项目中,这里应该返回你的WebSocket服务器地址
|
|
|
+ // 可以从配置文件中读取
|
|
|
+
|
|
|
+ // #ifdef H5
|
|
|
+ return window.location.host;
|
|
|
+ // #endif
|
|
|
+
|
|
|
+ // #ifdef MP-WEIXIN || APP-PLUS
|
|
|
+ // 小程序和App环境下,需要配置具体的服务器地址
|
|
|
+ return '192.168.10.57:8080'; // 替换为实际的服务器地址
|
|
|
+ // #endif
|
|
|
+ },
|
|
|
+
|
|
|
+ // 连接WebSocket
|
|
|
+ connectWebSocket() {
|
|
|
+ if (!this.room) {
|
|
|
+ console.error('房间号不能为空');
|
|
|
+ uni.showToast({
|
|
|
+ title: '房间号不能为空',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 构建WebSocket URL
|
|
|
+ const wsUrl = `ws://${this.getHost()}/room?room=${this.room}`;
|
|
|
+
|
|
|
+ // 创建WebSocket连接
|
|
|
+ this.socket = uni.connectSocket({
|
|
|
+ url: wsUrl,
|
|
|
+ success: () => {
|
|
|
+ console.log('WebSocket连接创建成功');
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ console.error('WebSocket连接创建失败:', error);
|
|
|
+ this.handleConnectionError();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听WebSocket连接打开
|
|
|
+ this.socket.onOpen(() => {
|
|
|
+ console.log('WebSocket连接已打开');
|
|
|
+ this.isConnected = true;
|
|
|
+ this.reconnectCount = 0;
|
|
|
+ this.isManualDisconnect = false; // 连接成功时重置标志位
|
|
|
+
|
|
|
+ // uni.showToast({
|
|
|
+ // title: "连接成功",
|
|
|
+ // icon: "success",
|
|
|
+ // duration: 1500,
|
|
|
+ // });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听WebSocket消息
|
|
|
+ this.socket.onMessage((event) => {
|
|
|
+ this.handleWebSocketMessage(event.data);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听WebSocket连接关闭
|
|
|
+ this.socket.onClose(() => {
|
|
|
+ console.log('WebSocket连接已关闭');
|
|
|
+ this.isConnected = false;
|
|
|
+
|
|
|
+ // 只有在非主动断开的情况下才尝试重连
|
|
|
+ if (!this.isManualDisconnect && this.reconnectCount < this.maxReconnectCount) {
|
|
|
+ setTimeout(() => {
|
|
|
+ this.reconnectWebSocket();
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听WebSocket错误
|
|
|
+ this.socket.onError((error) => {
|
|
|
+ console.error('WebSocket连接错误:', error);
|
|
|
+ this.isConnected = false;
|
|
|
+ this.handleConnectionError();
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('创建WebSocket连接异常:', error);
|
|
|
+ this.handleConnectionError();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 断开WebSocket连接
|
|
|
+ disconnectWebSocket() {
|
|
|
+ if (this.socket) {
|
|
|
+ this.isManualDisconnect = true; // 标记为主动断开
|
|
|
+ this.socket.close();
|
|
|
+ this.socket = null;
|
|
|
+ this.isConnected = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重连WebSocket
|
|
|
+ reconnectWebSocket() {
|
|
|
+ if (this.reconnectCount < this.maxReconnectCount) {
|
|
|
+ this.reconnectCount++;
|
|
|
+ console.log(`正在尝试第${this.reconnectCount}次重连...`);
|
|
|
+
|
|
|
+ // uni.showToast({
|
|
|
+ // title: `重连中(${this.reconnectCount}/${this.maxReconnectCount})`,
|
|
|
+ // icon: 'loading',
|
|
|
+ // duration: 1500
|
|
|
+ // });
|
|
|
+
|
|
|
+ this.connectWebSocket();
|
|
|
+ } else {
|
|
|
+ uni.showToast({
|
|
|
+ title: '连接失败,请检查网络',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 3000
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理WebSocket消息
|
|
|
+ handleWebSocketMessage(data) {
|
|
|
+ console.log('---------- data: ', data);
|
|
|
+ try {
|
|
|
+ // 尝试解析JSON,如果失败则认为是纯文本消息
|
|
|
+ let messageData;
|
|
|
+ try {
|
|
|
+ const temp = JSON.parse(data);
|
|
|
+ const msg = temp.message ? temp.message : temp; // TODO 测试兼容
|
|
|
+ messageData = JSON.parse(msg);
|
|
|
+ } catch (parseError) {
|
|
|
+ // 如果不是JSON格式,可能是纯文本消息,暂时忽略或创建简单消息对象
|
|
|
+ console.log('收到非JSON格式消息:', data);
|
|
|
+ try {
|
|
|
+ messageData = JSON.parse(data);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('解析WebSocket消息失败:', error, data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否是自己发送的消息,避免重复显示
|
|
|
+ // const isMyMessage = messageData.username === this.currentUser.username && messageData.userId === this.currentUser.id;
|
|
|
+ // 如果是自己发送的消息,不重复添加(因为发送时已经添加了)
|
|
|
+ // if (isMyMessage) {
|
|
|
+ // console.log("忽略自己发送的消息回显");
|
|
|
+ // return;
|
|
|
+ // }
|
|
|
+
|
|
|
+ // 创建消息对象
|
|
|
+ const newMessage = {
|
|
|
+ id: 'msg_' + Date.now() + '_' + Math.random(),
|
|
|
+ message: messageData.message || messageData || '',
|
|
|
+ time: this.getCurrentTime(),
|
|
|
+ isMine: false, // 既然不是自己的消息,就标记为false
|
|
|
+ username: messageData.name || messageData.username || '未知用户',
|
|
|
+ avatar: messageData.avatar || this.$constant.imgUrl + '/retail/default-img.png'
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log('收到 ---------- newMessage: ', newMessage);
|
|
|
+ // 添加到消息列表
|
|
|
+ this.messageList.push(newMessage);
|
|
|
+
|
|
|
+ // 滚动到底部
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.scrollToBottom();
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('解析WebSocket消息失败:', error, data);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 发送消息
|
|
|
+ sendMessage() {
|
|
|
+ if (!this.inputText.trim()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.isConnected) {
|
|
|
+ uni.showToast({
|
|
|
+ title: 'WebSocket未连接',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const messageContent = this.inputText.trim();
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 立即在本地显示发送的消息(右侧)
|
|
|
+ const myMessage = {
|
|
|
+ id: 'msg_' + Date.now() + '_local',
|
|
|
+ message: messageContent,
|
|
|
+ time: this.getCurrentTime(),
|
|
|
+ isMine: true,
|
|
|
+ username: this.currentUser.username,
|
|
|
+ avatar: this.currentUser.avatar
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加到消息列表
|
|
|
+ this.messageList.push(myMessage);
|
|
|
+
|
|
|
+ // 清空输入框
|
|
|
+ this.inputText = '';
|
|
|
+
|
|
|
+ // 滚动到底部
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.scrollToBottom();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 构建要发送的消息数据
|
|
|
+ const messageData = {
|
|
|
+ message: messageContent,
|
|
|
+ username: this.currentUser.username,
|
|
|
+ shopId: this.currentUser.shopId, // 门店使用shopId,客户使用customId
|
|
|
+ avatar: this.currentUser.avatar,
|
|
|
+ timestamp: Date.now()
|
|
|
+ };
|
|
|
+
|
|
|
+ const json = JSON.stringify(messageData);
|
|
|
+ console.log('send ---------- json: ', json);
|
|
|
+ // 发送到WebSocket服务器
|
|
|
+ this.socket.send({
|
|
|
+ data: json,
|
|
|
+ success: () => {
|
|
|
+ console.log('消息发送成功');
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ console.error('消息发送失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '发送失败',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ // 发送失败时,可以考虑移除刚添加的消息或标记为失败
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('发送消息异常:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '发送异常',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理连接错误
|
|
|
+ handleConnectionError() {
|
|
|
+ uni.showToast({
|
|
|
+ title: '连接失败',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 滚动到底部
|
|
|
+ scrollToBottom() {
|
|
|
+ if (this.messageList.length > 0) {
|
|
|
+ this.scrollIntoView = 'message-' + (this.messageList.length - 1);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取当前时间
|
|
|
+ getCurrentTime() {
|
|
|
+ const now = new Date();
|
|
|
+ const hours = String(now.getHours()).padStart(2, '0');
|
|
|
+ const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
|
+ return `${hours}:${minutes}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.chat-container {
|
|
|
+ height: 100vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.connection-status {
|
|
|
+ background-color: #ff9800;
|
|
|
+ color: #ffffff;
|
|
|
+ text-align: center;
|
|
|
+ padding: 16upx;
|
|
|
+ font-size: 24upx;
|
|
|
+
|
|
|
+ .status-text {
|
|
|
+ color: #ffffff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.message-list {
|
|
|
+ flex: 1;
|
|
|
+ padding: 20upx;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.message-item {
|
|
|
+ margin-bottom: 30upx;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.message-left {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-start;
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+.message-right {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-wrapper {
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin: 0 20upx;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar {
|
|
|
+ width: 80upx;
|
|
|
+ height: 80upx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: #e0e0e0;
|
|
|
+}
|
|
|
+
|
|
|
+.message-content-wrapper {
|
|
|
+ max-width: 520upx;
|
|
|
+ min-width: 100upx;
|
|
|
+}
|
|
|
+
|
|
|
+.username {
|
|
|
+ font-size: 24upx;
|
|
|
+ color: $fontColor3;
|
|
|
+ margin-bottom: 8upx;
|
|
|
+
|
|
|
+ &.align-right {
|
|
|
+ text-align: right;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.message-bubble {
|
|
|
+ padding: 20upx 24upx;
|
|
|
+ border-radius: 16upx;
|
|
|
+ position: relative;
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+
|
|
|
+ &.left-bubble {
|
|
|
+ background-color: #ffffff;
|
|
|
+ margin-left: 0;
|
|
|
+ border-top-left-radius: 8upx;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ left: -15upx;
|
|
|
+ top: 30upx;
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border: 6upx solid transparent;
|
|
|
+ border-right-color: #ffffff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.right-bubble {
|
|
|
+ background-color: rgb(27, 193, 66);
|
|
|
+ margin-right: 0;
|
|
|
+ border-top-right-radius: 8upx;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ right: -15upx;
|
|
|
+ top: 30upx;
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border: 6upx solid transparent;
|
|
|
+ border-left-color: rgb(27, 193, 66);
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-text {
|
|
|
+ color: #ffffff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.message-text {
|
|
|
+ font-size: 28upx;
|
|
|
+ line-height: 1.4;
|
|
|
+ color: $fontColorMain;
|
|
|
+}
|
|
|
+
|
|
|
+.message-time {
|
|
|
+ font-size: 20upx;
|
|
|
+ color: $fontColor3;
|
|
|
+ margin-top: 8upx;
|
|
|
+
|
|
|
+ &.align-right {
|
|
|
+ text-align: right;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.empty-state {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ height: 300upx;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-text {
|
|
|
+ font-size: 28upx;
|
|
|
+ color: $fontColor3;
|
|
|
+}
|
|
|
+
|
|
|
+.input-area {
|
|
|
+ background-color: #ffffff;
|
|
|
+ padding: 20upx;
|
|
|
+ border-top: 1upx solid $borderColor;
|
|
|
+ // 适配安全区域
|
|
|
+ padding-bottom: calc(20upx + env(safe-area-inset-bottom));
|
|
|
+}
|
|
|
+
|
|
|
+.input-wrapper {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ background-color: #f8f8f8;
|
|
|
+ border-radius: 50upx;
|
|
|
+ padding: 16upx 20upx;
|
|
|
+}
|
|
|
+
|
|
|
+.message-input {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 28upx;
|
|
|
+ border: none;
|
|
|
+ background-color: transparent;
|
|
|
+ outline: none;
|
|
|
+
|
|
|
+ &::placeholder {
|
|
|
+ color: #ccc;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.send-button {
|
|
|
+ margin-left: 20upx;
|
|
|
+ padding: 12upx 32upx;
|
|
|
+ background-color: $fontColor4;
|
|
|
+ color: #ffffff;
|
|
|
+ border: none;
|
|
|
+ border-radius: 50upx;
|
|
|
+ font-size: 26upx;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &.send-active {
|
|
|
+ background-color: $mainColor;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:disabled {
|
|
|
+ background-color: $fontColor4;
|
|
|
+ color: #ffffff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ border: none;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 适配不同设备
|
|
|
+/* #ifdef H5 */
|
|
|
+.chat-container {
|
|
|
+ height: calc(100vh - 44px); // 减去导航栏高度
|
|
|
+}
|
|
|
+/* #endif */
|
|
|
+
|
|
|
+/* #ifdef MP-WEIXIN */
|
|
|
+.input-area {
|
|
|
+ // 微信小程序底部安全区域
|
|
|
+ padding-bottom: calc(20upx + env(safe-area-inset-bottom));
|
|
|
+}
|
|
|
+/* #endif */
|
|
|
+
|
|
|
+/* #ifdef APP-PLUS */
|
|
|
+.input-area {
|
|
|
+ // App端底部安全区域
|
|
|
+ padding-bottom: calc(20upx + env(safe-area-inset-bottom));
|
|
|
+}
|
|
|
+/* #endif */
|
|
|
+</style>
|