first commit
This commit is contained in:
633
components/ChatSidebar.vue
Normal file
633
components/ChatSidebar.vue
Normal file
@@ -0,0 +1,633 @@
|
||||
<template>
|
||||
<view class="chat-sidebar-container">
|
||||
<view class="status-bar"></view>
|
||||
<view class="sidebar-header">
|
||||
<view class="user-profile-card" @click="openUserCardVisible">
|
||||
<view class="left">
|
||||
<view v-if="UserAvatar" class="avatar-preview">
|
||||
<image :src="UserAvatar" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view v-else class="avatar-placeholder">
|
||||
<uni-icons type="contact-filled" size="50" color="#007aff"></uni-icons>
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
<view class="center">
|
||||
<text>欢迎回来</text>
|
||||
<text>{{UserName}}</text>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="status-dot"></view>
|
||||
<view class="status-text"><text>在线</text></view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="new-chat-btn" @click="goContactPages">
|
||||
<text>+ 聊天列表</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chat-history">
|
||||
<view class="history-header">
|
||||
<view class="history-title-section">
|
||||
<text>历史会话</text>
|
||||
<text>{{UserConversations.length}}个对话</text>
|
||||
</view>
|
||||
<view class="history-management">
|
||||
<view v-if="!isSelectMode" class="history-management-btn iconfont icon-jia icon-jia-style"
|
||||
@click="openNewChatModal"></view>
|
||||
<view v-if="!isSelectMode" class="history-management-btn iconfont icon-duoxuan icon-duoxuan-style"
|
||||
@click="toggleSelectMode"></view>
|
||||
<view v-if="!isSelectMode" class="history-management-btn iconfont icon-saochu icon-saochu-style" @click="clearAllConversations">
|
||||
</view>
|
||||
<view v-if="isSelectMode && selectedHistoryIds.length > 0"
|
||||
class="history-management-btn iconfont icon-shanchu icon-shanchu-style"
|
||||
@click="deleteConversations"></view>
|
||||
<view v-if="isSelectMode"
|
||||
class="history-management-btn iconfont icon-total_selection icon-total_selection-style"
|
||||
:class="{active:isAllHistorySelected}" @click="toggleSelectAllHistory"></view>
|
||||
<view v-if="isSelectMode" class="history-management-btn iconfont icon-quxiao icon-quxiao-style"
|
||||
@click="toggleSelectMode"></view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!isSelectMode" class="history-search-wrap">
|
||||
<view class="history-search-inner" :class="{'has-value' : chatHistoryQuery || isHistorySearchFocused}">
|
||||
<uni-icons type="search"
|
||||
:class="[chatHistoryQuery || isHistorySearchFocused ? 'chat-search-icon' : 'search-icon']"></uni-icons>
|
||||
<input v-model="chatHistoryQuery" @focus="isHistorySearchFocused=true"
|
||||
@blur="isHistorySearchFocused=false" type="text" placeholder="搜索会话..." />
|
||||
</view>
|
||||
</view>
|
||||
<view class="chat-history-list">
|
||||
<!-- ai对话列表 -->
|
||||
<view v-if="chatType === 0" v-for="chat in UserConversations" :key="chat._id" :data-chat-id="chat._id"
|
||||
class="chat-history-card"
|
||||
:class="{'is-select-chat': currentChatId === chat._id || selectedHistoryIds.includes(chat._id)}"
|
||||
@click="isSelectMode ? toggleHistorySelect(chat._id) : selectChat(chat._id)">
|
||||
<view v-if="isSelectMode" class="history-chat-checkbox">
|
||||
<uni-icons v-if="selectedHistoryIds.includes(chat._id)" type="checkmarkempty" size="20"
|
||||
color="#007aff"></uni-icons>
|
||||
</view>
|
||||
<view class="history-chat-avatar">
|
||||
<uni-icons type="chat-filled" size="26" color="#007aff"></uni-icons>
|
||||
</view>
|
||||
<view v-if="chat.agent_id">
|
||||
{{chat.agent_data.title}}
|
||||
</view>
|
||||
<view v-else>{{chat.title}}</view>
|
||||
</view>
|
||||
<!-- 好友对话列表 -->
|
||||
<view v-if="chatType === 1" v-for="chat in UserConversations" :key="chat.sessionId"
|
||||
:data-chat-id="chat.sessionId" class="chat-history-card"
|
||||
:class="{'is-select-chat': currentChatId === chat.sessionId || selectedHistoryIds.includes(chat.sessionId)}"
|
||||
@click="isSelectMode ? toggleHistorySelect(chat.sessionId) : selectChat(chat.sessionId,chat.receiver)">
|
||||
<view v-if="isSelectMode" class="history-chat-checkbox">
|
||||
<uni-icons v-if="selectedHistoryIds.includes(chat.sessionId)" type="checkmarkempty" size="20"
|
||||
color="#007aff"></uni-icons>
|
||||
</view>
|
||||
<view class="history-chat-avatar">
|
||||
<image v-if="chat.avatar" :src="chat.avatar" class="friend-avatar" mode="aspectFill"></image>
|
||||
</view>
|
||||
|
||||
<view>{{chat.friendNickName}}</view>
|
||||
</view>
|
||||
<!-- 群聊对话列表 -->
|
||||
<view v-if="chatType === 2" v-for="chat in UserConversations" :key="chat.id" :data-chat-id="chat.id"
|
||||
class="chat-history-card"
|
||||
:class="{'is-select-chat': currentChatId === chat.id || selectedHistoryIds.includes(chat.id)}"
|
||||
@click="isSelectMode ? toggleHistorySelect(chat.id) : selectChat(chat.id)">
|
||||
<view v-if="isSelectMode" class="history-chat-checkbox">
|
||||
<uni-icons v-if="selectedHistoryIds.includes(chat.id)" type="checkmarkempty" size="20"
|
||||
color="#007aff"></uni-icons>
|
||||
</view>
|
||||
<view class="history-chat-avatar">
|
||||
<view class="iconfont icon-qunliao" style="font-size: 80rpx; color: #fff;"></view>
|
||||
</view>
|
||||
|
||||
<view>{{chat.name}}</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
toRef
|
||||
} from 'vue'
|
||||
|
||||
import {
|
||||
deleteConversation
|
||||
} from '@/utils/cloud-api.js'
|
||||
import {
|
||||
getToken
|
||||
} from '@/utils/user-info.js'
|
||||
|
||||
import {
|
||||
getUserInfo
|
||||
} from '@/utils/cloud-api.js'
|
||||
|
||||
defineOptions({
|
||||
name: "ChatSidebar"
|
||||
})
|
||||
|
||||
// 接收Chat.vue传过来的数据
|
||||
// 定义props,接收父组件传过来的数据
|
||||
const props = defineProps({
|
||||
chatList: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
showNewChatModal: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
currentSessionId: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
},
|
||||
chatType: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:showNewChatModal',
|
||||
'update:currentSessionId',
|
||||
'refresh-conversations',
|
||||
'update:chatType'
|
||||
])
|
||||
|
||||
const UserConversations = toRef(props, 'chatList')
|
||||
const showNewChatModal = toRef(props, 'showNewChatModal')
|
||||
const chatType = toRef(props, 'chatType')
|
||||
|
||||
// 打开用户资料卡
|
||||
const openUserCardVisible = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/UserProfileModal/UserProfileModal'
|
||||
})
|
||||
}
|
||||
|
||||
// 选择模式
|
||||
const isSelectMode = ref(false)
|
||||
|
||||
// 已选历史会话id列表
|
||||
const selectedHistoryIds = ref([])
|
||||
// 删除会话
|
||||
const deleteConversations = () => {
|
||||
if(selectedHistoryIds.value.length === 0) return
|
||||
uni.showModal({
|
||||
title:'确认删除',
|
||||
content:`确定要删除全部 ${selectedHistoryIds.value.length} 个对话吗?此操作不可恢复。`,
|
||||
confirmText: '确认删除',
|
||||
confirmColor: '#ff0000',
|
||||
success: async(res) => {
|
||||
if(res.confirm) {
|
||||
try {
|
||||
const userToken = getToken();
|
||||
await deleteConversation(userToken, selectedHistoryIds.value)
|
||||
// 4. 删除成功提示
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
selectedHistoryIds.value = []
|
||||
isSelectMode.value = false
|
||||
// 通知父组件刷新会话列表
|
||||
emit('refresh-conversations')
|
||||
} catch (error) {
|
||||
// 6. 统一捕获错误,防止崩溃
|
||||
console.error('删除会话失败:', error)
|
||||
uni.showToast({
|
||||
title: '删除失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 删除所有会话
|
||||
const clearAllConversations = () => {
|
||||
if (!UserConversations.value.length) {
|
||||
uni.showToast({ title: '没有可删除的会话', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除全部 ${UserConversations.value.length} 个对话吗?此操作不可恢复。`,
|
||||
confirmText: '删除全部',
|
||||
confirmColor: '#ff0000',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const userToken = getToken()
|
||||
const allIds = UserConversations.value.map(item => item._id)
|
||||
await deleteConversation(userToken, allIds)
|
||||
uni.showToast({ title: '已删除全部对话', icon: 'success' })
|
||||
emit('refresh-conversations')
|
||||
} catch (error) {
|
||||
console.error('清空全部对话失败:', error)
|
||||
uni.showToast({ title: '删除失败,请重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 是否全选历史会话
|
||||
const isAllHistorySelected = computed(() => {
|
||||
return UserConversations.value.length > 0 && selectedHistoryIds.value.length === UserConversations.value
|
||||
.length
|
||||
})
|
||||
|
||||
// 当前选择的聊天会话
|
||||
const currentChatId = toRef(props, 'currentSessionId')
|
||||
// 选择聊天会话
|
||||
const selectChat = (chatId, receiverId = '') => {
|
||||
console.log("选择的id是:", chatId);
|
||||
if (receiverId) {
|
||||
uni.setStorageSync('receiverId', receiverId)
|
||||
}
|
||||
uni.setStorageSync('currentSessionId', chatId)
|
||||
emit('update:currentSessionId', chatId)
|
||||
}
|
||||
|
||||
|
||||
// 新建会话
|
||||
const openNewChatModal = () => {
|
||||
console.log("点击了新建会话");
|
||||
emit('update:showNewChatModal', true)
|
||||
}
|
||||
|
||||
const goContactPages = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/ContactPages/ContactPages'
|
||||
})
|
||||
}
|
||||
|
||||
// 会话历史搜索
|
||||
const chatHistoryQuery = ref('')
|
||||
const isHistorySearchFocused = ref(false)
|
||||
|
||||
// ======批量处理历史会话操作方法=========
|
||||
// 切换多选模式
|
||||
const toggleSelectMode = () => {
|
||||
isSelectMode.value = !isSelectMode.value;
|
||||
if (!isSelectMode.value) {
|
||||
selectedHistoryIds.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 多选模式下选择历史会话
|
||||
const toggleHistorySelect = (chatId) => {
|
||||
const index = selectedHistoryIds.value.indexOf(chatId)
|
||||
if (index === -1) {
|
||||
selectedHistoryIds.value.push(chatId)
|
||||
} else {
|
||||
selectedHistoryIds.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换全选会话
|
||||
const toggleSelectAllHistory = () => {
|
||||
if (isAllHistorySelected.value) {
|
||||
selectedHistoryIds.value = []
|
||||
} else {
|
||||
selectedHistoryIds.value = UserConversations.value.map(chat => chat._id)
|
||||
}
|
||||
}
|
||||
const UserToken = ref('')
|
||||
const UserAvatar = ref('')
|
||||
const UserName = ref('')
|
||||
|
||||
onMounted(async () => {
|
||||
UserToken.value = getToken()
|
||||
const UserInfo = await getUserInfo(UserToken.value)
|
||||
UserAvatar.value = UserInfo.avatar || ''
|
||||
UserName.value = UserInfo.username || ''
|
||||
console.log("菜单收到的会话列表:",UserConversations.value);
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chat-sidebar-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f8f8f8;
|
||||
color: black;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.sidebar-header {
|
||||
padding: 40rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
border-bottom: 1rpx solid #bbbbbb;
|
||||
}
|
||||
|
||||
.user-profile-card {
|
||||
padding: 10rpx 20rpx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
border-radius: 30rpx;
|
||||
box-shadow: 0 0 8rpx #cccccc;
|
||||
}
|
||||
|
||||
.user-profile-card .left {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 10rpx;
|
||||
|
||||
.avatar-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.user-profile-card .center {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.user-profile-card .right {
|
||||
flex-shrink: 0;
|
||||
margin-left: 6rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.status-dot {
|
||||
width: 10rpx;
|
||||
height: 10rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #10B981;
|
||||
margin-right: 16rpx;
|
||||
animation: statusDotPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes statusDotPulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 15rpx rgba(59, 130, 246, 0.8);
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 0 30rpx rgba(59, 130, 246, 0.7);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-chat-btn {
|
||||
margin-top: 20rpx;
|
||||
width: 100%;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: linear-gradient(to right, #3b86ff, #2463df);
|
||||
border-radius: 30rpx;
|
||||
box-shadow: 0 0 8rpx rgba(59, 134, 255, 0.6);
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-history {
|
||||
width: 100%;
|
||||
padding: 20rpx 0;
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8rpx;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(59, 134, 255, 0.4);
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.history-header {
|
||||
width: 100%;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1rpx solid #bbbbbb;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.history-title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
.history-management {
|
||||
display: flex;
|
||||
|
||||
.history-management-btn {
|
||||
margin-left: 20rpx;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
background-color: rgba(221, 221, 221, 0.5);
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.icon-jia-style {
|
||||
background-color: rgba(16, 185, 129, 0.2);
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.icon-duoxuan-style {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.icon-saochu-style {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.icon-quxiao-style {
|
||||
color: #3b86ff;
|
||||
// border: 1rpx solid #3b86ff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.icon-total_selection-style {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.icon-total_selection-style.active {
|
||||
color: #2463df;
|
||||
background: rgba(59, 134, 255, 0.2);
|
||||
}
|
||||
|
||||
.icon-shanchu-style {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.history-search-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 20rpx 30rpx 6rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.history-search-inner {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 10rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
background-color: rgba(194, 191, 211, 0.1);
|
||||
border-radius: 30rpx;
|
||||
|
||||
.search-icon {
|
||||
color: #666666 !important;
|
||||
font-weight: bold;
|
||||
font-size: 36rpx !important;
|
||||
}
|
||||
|
||||
.chat-search-icon {
|
||||
color: #aaaaff !important;
|
||||
font-weight: bold;
|
||||
font-size: 36rpx !important;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-left: 10rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.history-search-inner.has-value {
|
||||
background-color: #ffffff;
|
||||
border: 2rpx solid #aaaaff;
|
||||
box-shadow: 0 0 15rpx #aaaaff;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-history-list {
|
||||
padding: 10rpx 30rpx;
|
||||
|
||||
.chat-history-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1rpx solid #3b86ff;
|
||||
background-color: #ffffff;
|
||||
border-radius: 30rpx;
|
||||
padding: 30rpx;
|
||||
margin-top: 10rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
.is-select-chat {
|
||||
box-shadow: 0 0 5rpx #2463df;
|
||||
background-color: rgba(59, 134, 255, 0.1);
|
||||
}
|
||||
|
||||
.history-chat-checkbox {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1rpx solid #3b86ff;
|
||||
}
|
||||
|
||||
.history-chat-avatar {
|
||||
width: 66rpx;
|
||||
height: 66rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(59, 134, 255, 0.4);
|
||||
margin-left: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.friend-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 20rpx;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user