Vue2构建企业级实时聊天应用:从开发到部署 | 完整教程

2025-08-05 0 920

从开发到部署的完整教程 – 使用Vue2、Vuex和WebSocket技术

Vue2实时聊天应用开发指南

本教程将指导您使用Vue2构建一个功能完整的实时聊天应用,包含用户认证、联系人列表、实时消息传递和通知系统。

架构设计: 采用模块化设计,分离UI组件、状态管理和网络通信层。

1. 项目结构设计

src/
├── assets/
├── components/
│   ├── ChatContainer.vue
│   ├── ContactList.vue
│   ├── MessageList.vue
│   ├── MessageInput.vue
│   └── UserStatus.vue
├── store/
│   ├── index.js          # Vuex主文件
│   ├── modules/
│   │   ├── auth.js       # 认证模块
│   │   ├── chat.js       # 聊天模块
│   │   └── contacts.js   # 联系人模块
├── services/
│   ├── auth.service.js   # 认证服务
│   ├── chat.service.js   # 聊天服务
│   └── websocket.js      # WebSocket服务
├── router/
│   └── index.js          # 路由配置
├── views/
│   ├── Login.vue
│   └── Chat.vue
├── App.vue
└── main.js

2. Vuex状态管理设计

使用Vuex管理应用状态:

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './modules/auth'
import chat from './modules/chat'
import contacts from './modules/contacts'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    auth,
    chat,
    contacts
  }
})

// store/modules/chat.js
export default {
  state: {
    currentConversation: null,
    messages: [],
    unreadCount: 0
  },
  mutations: {
    SET_CURRENT_CONVERSATION(state, conversation) {
      state.currentConversation = conversation
      state.unreadCount = 0
    },
    ADD_MESSAGE(state, message) {
      state.messages.push(message)
      
      // 如果是当前会话的消息,不增加未读计数
      if (state.currentConversation?.id !== message.sender) {
        state.unreadCount++
      }
    },
    CLEAR_MESSAGES(state) {
      state.messages = []
    }
  },
  actions: {
    sendMessage({ commit, state }, content) {
      const message = {
        id: Date.now(),
        sender: state.auth.user.id,
        receiver: state.currentConversation.id,
        content,
        timestamp: new Date().toISOString(),
        status: 'sent'
      }
      
      // 发送消息到WebSocket服务器
      chatService.sendMessage(message)
      
      // 立即添加到本地状态
      commit('ADD_MESSAGE', message)
    },
    receiveMessage({ commit }, message) {
      commit('ADD_MESSAGE', message)
    }
  }
}

3. WebSocket服务实现

创建WebSocket服务处理实时通信:

// services/websocket.js
class WebSocketService {
  constructor() {
    this.socket = null
    this.listeners = []
  }
  
  connect(token) {
    this.socket = new WebSocket(`wss://api.example.com/chat?token=${token}`)
    
    this.socket.onopen = () => {
      console.log('WebSocket connected')
    }
    
    this.socket.onmessage = (event) => {
      const message = JSON.parse(event.data)
      this.listeners.forEach(listener => listener(message))
    }
    
    this.socket.onclose = () => {
      console.log('WebSocket disconnected')
      // 尝试重新连接
      setTimeout(() => this.connect(token), 3000)
    }
  }
  
  send(data) {
    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
      this.socket.send(JSON.stringify(data))
    }
  }
  
  addListener(listener) {
    this.listeners.push(listener)
  }
  
  removeListener(listener) {
    this.listeners = this.listeners.filter(l => l !== listener)
  }
  
  disconnect() {
    if (this.socket) {
      this.socket.close()
    }
  }
}

export default new WebSocketService()

4. 核心组件实现

聊天容器组件:

<template>
  <div class="chat-app">
    <div class="app-header">
      <div class="user-info">
        <div class="avatar">{{ userInitials }}</div>
        <div>{{ userName }}</div>
      </div>
      <button @click="logout">退出</button>
    </div>
    
    <div class="chat-container">
      <ContactList />
      <div class="chat-window">
        <div class="chat-header" v-if="currentConversation">
          <div class="avatar small">{{ currentContactInitials }}</div>
          <div>
            <div>{{ currentConversation.name }}</div>
            <div class="status">
              <span :class="['status-indicator', currentConversation.online ? 'online' : 'offline']"></span>
              {{ currentConversation.online ? '在线' : '离线' }}
            </div>
          </div>
        </div>
        
        <MessageList v-if="currentConversation" />
        <MessageInput v-if="currentConversation" @send="sendMessage" />
      </div>
    </div>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex'
import ContactList from './ContactList.vue'
import MessageList from './MessageList.vue'
import MessageInput from './MessageInput.vue'

export default {
  components: { ContactList, MessageList, MessageInput },
  computed: {
    ...mapState('auth', ['user']),
    ...mapState('chat', ['currentConversation']),
    ...mapGetters('contacts', ['getContactById']),
    userName() {
      return this.user ? this.user.name : '未登录'
    },
    userInitials() {
      return this.user ? this.user.name.charAt(0) : '?'
    },
    currentContact() {
      return this.currentConversation 
        ? this.getContactById(this.currentConversation.id) 
        : null
    },
    currentContactInitials() {
      return this.currentContact 
        ? this.currentContact.name.charAt(0) 
        : '?'
    }
  },
  methods: {
    ...mapActions('auth', ['logout']),
    ...mapActions('chat', ['sendMessage'])
  }
}
</script>

5. 消息列表组件

<template>
  <div class="chat-messages">
    <div 
      v-for="message in messages" 
      :key="message.id"
      class="message"
      :class="message.sender === userId ? 'sent' : 'received'"
    >
      <div class="message-content">{{ message.content }}</div>
      <div class="message-time">{{ formatTime(message.timestamp) }}</div>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex'
import moment from 'moment'

export default {
  computed: {
    ...mapState('chat', ['messages']),
    ...mapState('auth', ['user']),
    userId() {
      return this.user ? this.user.id : null
    }
  },
  methods: {
    formatTime(timestamp) {
      return moment(timestamp).format('HH:mm')
    }
  },
  watch: {
    messages() {
      // 滚动到底部
      this.$nextTick(() => {
        const container = this.$el
        container.scrollTop = container.scrollHeight
      })
    }
  }
}
</script>

6. 部署指南

使用Docker容器化部署:

# Dockerfile
FROM node:14 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Nginx配置示例:

# nginx.conf
server {
    listen 80;
    server_name yourdomain.com;
    
    root /usr/share/nginx/html;
    index index.html;
    
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    location /api {
        proxy_pass http://backend:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    
    location /socket.io {
        proxy_pass http://backend:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

实时聊天应用演示

{{ contact.name.charAt(0) }}
{{ contact.name }}
{{ getLastMessage(contact.id) }}

{{ unreadCounts[contact.id] }}

{{ currentConversation.name.charAt(0) }}
{{ currentConversation.name }}

{{ currentConversation.online ? ‘在线’ : ‘离线’ }}

{{ message.content }}
{{ formatTime(message.timestamp) }}

请从左侧选择一个联系人开始聊天

// 模拟WebSocket服务
class MockWebSocket {
constructor() {
this.listeners = []
this.connected = false
this.messages = []
this.interval = null
}

connect() {
this.connected = true
console.log(‘Mock WebSocket connected’)

// 模拟接收消息
this.interval = setInterval(() => {
if (Math.random() > 0.7 && this.listeners.length > 0) {
const senders = [2, 3, 4].filter(id => id !== this.currentUserId)
const senderId = senders[Math.floor(Math.random() * senders.length)]
const sender = this.contacts.find(c => c.id === senderId)

if (sender) {
const message = {
id: Date.now(),
sender: senderId,
receiver: this.currentUserId,
content: this.getRandomMessage(),
timestamp: new Date().toISOString()
}

this.listeners.forEach(listener => listener(message))
}
}
}, 3000)
}

disconnect() {
this.connected = false
clearInterval(this.interval)
console.log(‘Mock WebSocket disconnected’)
}

send(data) {
this.messages.push(data)
console.log(‘Message sent:’, data)

// 模拟消息发送成功
setTimeout(() => {
const message = {
…data,
status: ‘delivered’,
timestamp: new Date().toISOString()
}

this.listeners.forEach(listener => listener(message))
}, 300)
}

addListener(listener) {
this.listeners.push(listener)
}

removeListener(listener) {
this.listeners = this.listeners.filter(l => l !== listener)
}

getRandomMessage() {
const messages = [
“你好,最近怎么样?”,
“你看到我发的文件了吗?”,
“我们明天什么时候开会?”,
“那个项目进展如何?”,
“周末有什么计划吗?”,
“我找到了解决那个问题的方法”,
“需要帮忙吗?”,
“记得查看邮件”,
“这个功能什么时候能完成?”,
“客户反馈很不错”
]
return messages[Math.floor(Math.random() * messages.length)]
}
}

// 聊天应用Vue实例
new Vue({
el: ‘#app’,
data: {
loginForm: {
username: ”,
password: ”
},
currentUser: null,
contacts: [
{ id: 2, name: ‘张经理’, online: true },
{ id: 3, name: ‘李设计师’, online: false },
{ id: 4, name: ‘王开发’, online: true },
{ id: 5, name: ‘赵测试’, online: true },
{ id: 6, name: ‘孙产品’, online: false }
],
currentConversation: null,
messages: [],
newMessage: ”,
unreadCounts: {},
websocket: new MockWebSocket()
},
computed: {
currentUserId() {
return this.currentUser ? this.currentUser.id : null
}
},
methods: {
login() {
if (this.loginForm.username) {
this.currentUser = {
id: 1,
name: this.loginForm.username
}

// 连接WebSocket
this.websocket.currentUserId = this.currentUser.id
this.websocket.contacts = this.contacts
this.websocket.connect()
this.websocket.addListener(this.handleWebSocketMessage)

// 初始化未读计数
this.contacts.forEach(contact => {
this.unreadCounts[contact.id] = 0
})
}
},
register() {
if (this.loginForm.username) {
alert(`用户 ${this.loginForm.username} 注册成功!`)
this.login()
}
},
logout() {
this.currentUser = null
this.currentConversation = null
this.messages = []
this.websocket.disconnect()
},
selectConversation(contact) {
this.currentConversation = contact
this.messages = this.getConversationMessages(contact.id)

// 重置未读计数
this.unreadCounts[contact.id] = 0
},
getConversationMessages(contactId) {
// 模拟从服务器获取消息
return [
{ id: 1, sender: contactId, receiver: 1, content: ‘你好,最近项目进展如何?’, timestamp: ‘2023-07-15T10:30:00Z’ },
{ id: 2, sender: 1, receiver: contactId, content: ‘一切顺利,下周可以完成第一版’, timestamp: ‘2023-07-15T10:32:00Z’ },
{ id: 3, sender: contactId, receiver: 1, content: ‘太好了!客户很期待看到成果’, timestamp: ‘2023-07-15T10:35:00Z’ }
]
},
getLastMessage(contactId) {
const messages = this.getConversationMessages(contactId)
return messages.length > 0 ? messages[messages.length – 1].content : ‘没有消息’
},
sendMessage() {
if (this.newMessage.trim() && this.currentConversation) {
const message = {
id: Date.now(),
sender: this.currentUser.id,
receiver: this.currentConversation.id,
content: this.newMessage,
timestamp: new Date().toISOString()
}

// 通过WebSocket发送
this.websocket.send(message)

// 添加到本地消息列表
this.messages.push(message)

// 清空输入框
this.newMessage = ”
}
},
handleWebSocketMessage(message) {
// 如果消息是发给当前用户的
if (message.receiver === this.currentUser.id) {
// 如果当前正在查看该联系人的聊天,直接添加消息
if (this.currentConversation && this.currentConversation.id === message.sender) {
this.messages.push(message)
} else {
// 否则增加未读计数
this.unreadCounts[message.sender] = (this.unreadCounts[message.sender] || 0) + 1
}
}
},
formatTime(timestamp) {
return new Date(timestamp).toLocaleTimeString([], { hour: ‘2-digit’, minute: ‘2-digit’ })
}
},
mounted() {
// 初始化模拟数据
this.contacts.forEach(contact => {
this.unreadCounts[contact.id] = Math.floor(Math.random() * 3)
})
}
});

Vue2构建企业级实时聊天应用:从开发到部署 | 完整教程
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 vue2 Vue2构建企业级实时聊天应用:从开发到部署 | 完整教程 https://www.taomawang.com/web/vue2/759.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务