Files
pc_frontend/src/Views/Chat.vue
qingfeng1121 73cf25e586 feat: 添加分页组件和地址选择组件,优化样式和类型定义
refactor: 重构路由配置,移除订单页面路由

style: 统一使用CSS变量替换硬编码颜色值

docs: 添加前端数据需求分析文档

fix: 修复登录页面记住我功能,更新用户信息页面链接

perf: 优化搜索组件动画效果和响应式设计

chore: 更新TypeScript类型定义,添加订单和地址相关类型
2026-01-19 11:35:50 +08:00

703 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 聊天界面 -->
<template>
<div>
<div id="chat">
<!-- 左右结构 左边聊天导航栏用户1 用户2 用户3 从上到下分布 一个用户一个li标签 右边聊天内容根据导航栏切换 -->
<div id="chat-nav">
<div class="nav-header">
<h2>消息</h2>
<!-- 搜索框 -->
<div class="search-container">
<input type="text" placeholder="搜索用户" v-model="searchTerm" class="search-input">
<i class="fas fa-search search-icon"></i>
</div>
</div>
<div class="user-list-container">
<ul class="user-list">
<li v-for="user in chatUsers" :key="user.id" :class="{ active: activeUserId === user.id }"
@click="selectUser(user.id)">
<div class="user-avatar">
<img :src="user.avatar" :alt="user.name">
</div>
<div class="user-info">
<div class="user-header">
<span class="user-name">{{ user.name }}</span>
<span class="message-time">{{ user.lastMessageTime }}</span>
</div>
<div class="user-last-message">
{{ user.lastMessage }}
</div>
</div>
<div v-if="user.unreadCount > 0" class="unread-badge">
{{ user.unreadCount }}
</div>
</li>
</ul>
</div>
</div>
<!-- 右边聊天内容 -->
<div id="chat-content">
<!-- 聊天头部 -->
<div v-if="activeUser" class="chat-header">
<div class="chat-user-info">
<div>
<h3>{{ activeUser.name }}</h3>
<p class="user-status">在线</p>
</div>
</div>
</div>
<!-- 聊天历史 -->
<div v-if="activeUser" class="chat-history">
<div v-for="(message, index) in messages" :key="index"
:class="{ 'chat-message': true, 'sent': message.sender === 'me', 'received': message.sender !== 'me' }">
<div v-if="message.sender !== 'me'" class="user-avatar small">
<img :src="activeUser.avatar" :alt="activeUser.name">
</div>
<div class="message-content">
<div class="message-text">{{ message.text }}</div>
<div class="message-time">{{ message.time }}</div>
</div>
</div>
</div>
<!-- 消息输入区 -->
<div v-if="activeUser" class="chat-input-area">
<div class="input-container">
<button class="input-btn">😊</button>
<button class="input-btn">📎</button>
<input type="text" v-model="newMessage" placeholder="输入消息..." class="message-input"
@keyup.enter="sendMessage">
<button class="input-btn">🎤</button>
</div>
<button class="send-btn" @click="sendMessage">发送</button>
</div>
<!-- 空状态 -->
<div v-else class="empty-state">
<img src="#" alt="选择聊天">
<h3>选择一个聊天</h3>
<p>开始与您的联系人聊天</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
// 搜索用户的关键词
const searchTerm = ref('')
// 聊天用户数据
const chatUsers = ref([
{
id: 1,
name: '用户1',
avatar: '?prompt=user%20avatar%20friendly%20smile&image_size=square',
lastMessage: '你好我想购买商品1',
lastMessageTime: '10:00',
unreadCount: 2
},
{
id: 2,
name: '用户2',
avatar: '?prompt=user%20avatar%20professional%20business&image_size=square',
lastMessage: '订单什么时候发货?',
lastMessageTime: '09:30',
unreadCount: 0
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
},
{
id: 3,
name: '用户3',
avatar: '?prompt=user%20avatar%20casual%20young&image_size=square',
lastMessage: '商品质量怎么样?',
lastMessageTime: '昨天',
unreadCount: 1
}
])
// 消息数据
const messages = ref([
{
sender: 'user1',
text: '你好我想购买商品1',
time: '10:00'
},
{
sender: 'me',
text: '您好,欢迎咨询,请问有什么可以帮助您的?',
time: '10:01'
},
{
sender: 'user1',
text: '商品1有货吗',
time: '10:02'
}
])
// 当前活跃用户ID
const activeUserId = ref(1)
// 新消息
const newMessage = ref('')
// 计算当前活跃用户
const activeUser = computed(() => {
return chatUsers.value.find(user => user.id === activeUserId.value)
})
// 选择用户
const selectUser = (userId: number) => {
activeUserId.value = userId
// 清除未读消息数
const user = chatUsers.value.find(user => user.id === userId)
if (user) {
user.unreadCount = 0
}
}
// 发送消息
const sendMessage = () => {
if (newMessage.value.trim()) {
messages.value.push({
sender: 'me',
text: newMessage.value,
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
})
// 更新最后一条消息
if (activeUser.value) {
activeUser.value.lastMessage = newMessage.value
activeUser.value.lastMessageTime = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
}
// 清空输入框
newMessage.value = ''
}
}
</script>
<style scoped>
#chat {
background-color: var(--bg-color);
display: flex;
justify-content: center;
box-sizing: border-box;
border-radius: var(--border-radius-lg);
padding: var(--spacing-xl) 150px 0 150px;
}
/* 左侧聊天导航栏 */
#chat-nav {
width: 320px;
background-color: var(--bg-color);
display: flex;
flex-direction: column;
flex-shrink: 0;
border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
padding: var(--spacing-sm) 0 0 var(--spacing-sm);
border: 1px solid var(--border-color);
border-right: none;
}
.nav-header {
padding: var(--spacing-md);
align-items: center;
}
.nav-header h2 {
margin: 0;
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
}
.search-container {
margin-top: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
display: flex;
align-items: center;
border-radius: var(--border-radius-xl);
border: 1px solid var(--border-color);
}
.search-input {
flex: 1;
border: none;
outline: none;
background: transparent;
font-size: var(--font-size-sm);
color: var(--text-primary);
}
.search-icon {
font-size: var(--font-size-base);
color: var(--text-tertiary);
margin-left: var(--spacing-sm);
}
.user-list-container {
background-color: var(--card-bg);
overflow-x: auto;
max-height: 50vh;
}
.new-chat-btn {
width: 32px;
height: 32px;
border-radius: var(--border-radius-full);
background-color: var(--primary-color);
color: #ffffff;
border: none;
font-size: var(--font-size-lg);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all var(--transition-normal);
}
.new-chat-btn:hover {
background-color: var(--primary-hover);
transform: scale(1.1);
}
#chat-nav .user-list-container ul {
list-style: none;
padding: 0;
margin: 0;
flex: 1;
overflow-y: auto;
}
#chat-nav .user-list-container li {
display: flex;
align-items: center;
padding: var(--spacing-md) var(--spacing-md);
cursor: pointer;
transition: all var(--transition-normal);
}
#chat-nav .user-list-container li:hover {
background-color: var(--bg-color);
border-radius: var(--border-radius-sm);
}
#chat-nav .user-list-container li.active {
background-color: var(--bg-primary-lightest);
border-radius: var(--border-radius-sm);
}
.user-avatar {
width: 50px;
height: 50px;
border-radius: var(--border-radius-full);
overflow: hidden;
margin-right: var(--spacing-md);
flex-shrink: 0;
border-radius: var(--border-radius-lg);
}
.user-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.user-avatar.small {
width: 36px;
height: 36px;
margin-right: var(--spacing-sm);
}
.user-info {
flex: 1;
min-width: 0;
}
.user-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-xs);
}
.user-name {
font-weight: 600;
color: var(--text-primary);
font-size: var(--font-size-sm);
}
.message-time {
font-size: var(--font-size-xs);
color: var(--text-tertiary);
}
.user-last-message {
font-size: var(--font-size-xs);
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.unread-badge {
background-color: var(--primary-color);
color: #ffffff;
font-size: var(--font-size-xs);
font-weight: 600;
padding: 2px 8px;
border-radius: var(--border-radius-xl);
min-width: 20px;
text-align: center;
margin-left: var(--spacing-sm);
}
/* 右侧聊天内容 */
#chat-content {
flex: 1;
display: flex;
flex-direction: column;
background-color: var(--bg-color);
border-radius: 0 var(--border-radius-lg) var(--border-radius-lg) 0;
border: 1px solid var(--border-color);
border-left: none;
}
/* 聊天头部 */
.chat-header {
background-color: var(--bg-color);
padding: var(--spacing-md) var(--spacing-md);
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 0 var(--border-radius-lg) 0 0;
}
.chat-user-info {
display: flex;
align-items: center;
width: 100%;
padding: var(--spacing-sm);
border-bottom: 1px solid var(--border-color);
}
.chat-user-info h3 {
margin: 0 0 2px;
font-size: var(--font-size-base);
font-weight: 600;
color: var(--text-primary);
}
.user-status {
font-size: var(--font-size-xs);
color: var(--success-color);
margin: 0;
}
.chat-actions {
display: flex;
gap: var(--spacing-sm);
}
.action-btn {
width: 36px;
height: 36px;
border-radius: var(--border-radius-full);
background-color: var(--bg-light);
border: none;
cursor: pointer;
font-size: var(--font-size-base);
display: flex;
align-items: center;
justify-content: center;
transition: all var(--transition-normal);
}
.action-btn:hover {
background-color: var(--border-color);
}
/* 聊天历史 */
.chat-history {
flex: 1;
padding: var(--spacing-xl);
overflow-y: auto;
background-color: var(--bg-color);
background-image: url('?prompt=chat%20background%20pattern%20subtle%20light&image_size=landscape_16_9');
background-size: cover;
background-position: center;
}
.chat-message {
display: flex;
margin-bottom: var(--spacing-md);
align-items: flex-end;
}
.chat-message.sent {
flex-direction: row-reverse;
}
.chat-message.sent .user-avatar {
margin-right: 0;
margin-left: var(--spacing-sm);
}
.message-content {
max-width: 70%;
display: flex;
flex-direction: column;
}
.chat-message.sent .message-content {
align-items: flex-end;
}
.message-text {
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--message-radius);
margin-bottom: 4px;
line-height: 1.4;
}
.chat-message.received .message-text {
background-color: var(--card-bg);
border-bottom-left-radius: 4px;
}
.chat-message.sent .message-text {
background-color: var(--primary-color);
color: #ffffff;
border-bottom-right-radius: 4px;
}
.message-time {
font-size: var(--font-size-xs);
color: var(--text-tertiary);
}
/* 消息输入区 */
.chat-input-area {
background-color: var(--bg-color);
padding: var(--spacing-md) var(--spacing-xl);
border-top: 1px solid var(--border-color);
border-radius: 0 0 var(--border-radius-lg) 0;
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.input-container {
flex: 1;
display: flex;
align-items: center;
background-color: var(--bg-light);
border-radius: var(--border-radius-xl);
padding: var(--spacing-xs) var(--spacing-md);
gap: var(--spacing-sm);
}
.input-btn {
background: none;
border: none;
font-size: var(--font-size-base);
cursor: pointer;
padding: var(--spacing-xs);
}
.message-input {
flex: 1;
border: none;
background: none;
outline: none;
font-size: var(--font-size-sm);
color: var(--text-primary);
min-width: 0;
}
.message-input::placeholder {
color: var(--text-tertiary);
}
.send-btn {
padding: var(--spacing-xs) var(--spacing-xl);
background-color: var(--primary-color);
color: #ffffff;
border: none;
border-radius: var(--border-radius-xl);
cursor: pointer;
font-size: var(--font-size-sm);
font-weight: 600;
transition: all var(--transition-normal);
}
.send-btn:hover {
background-color: var(--primary-hover);
}
/* 空状态 */
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--bg-color);
padding: var(--spacing-xxl);
text-align: center;
}
.empty-state img {
width: 120px;
height: 120px;
margin-bottom: var(--spacing-xl);
opacity: 0.6;
}
.empty-state h3 {
margin: 0 0 var(--spacing-md);
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
}
.empty-state p {
margin: 0;
font-size: var(--font-size-sm);
color: var(--text-tertiary);
}
/* 响应式设计 */
@media (max-width: 768px) {
#chat-nav {
width: 280px;
}
.chat-history {
padding: 15px;
}
.message-content {
max-width: 80%;
}
}
@media (max-width: 480px) {
#chat-nav {
width: 100%;
position: absolute;
top: 0;
left: 0;
bottom: 0;
z-index: 100;
transform: translateX(-100%);
transition: transform 0.3s ease;
}
#chat-nav.active {
transform: translateX(0);
}
#chat-content {
width: 100%;
}
.chat-header {
padding: 15px;
}
.chat-input-area {
padding: 10px 15px;
}
}
</style>