<template>
|
<div class="layoutNav___jOCfX">
|
<div class="layoutNav__item___1y99z">
|
<div class="nav__content">
|
<div class="agent_img"></div>
|
<div class="agent_line"></div>
|
<el-tooltip content="开启新对话" placement="right">
|
<div class="nav__chat" @click="newChatRoomClick()">
|
<div class="nav__chat-icon">
|
<span class="chat_img ywifont ywicon-weixin !text-[26px] text-[#fff]"></span>
|
</div>
|
</div>
|
</el-tooltip>
|
<el-tooltip content="历史会话" placement="right">
|
<div class="nav__chat" @click="historyChatRoomClick()">
|
<div class="nav__chat-icon">
|
<span class="chat_img ywifont ywicon-cshy-shizhong !text-[28px] text-[#fff]"></span>
|
</div>
|
</div>
|
</el-tooltip>
|
<el-tooltip content="公司信息" placement="right">
|
<div class="nav__chat" @click="companyClick()">
|
<div class="nav__chat-icon">
|
<span class="chat_img ywifont ywicon-gongsijieshao !text-[22px] text-[#fff]"></span>
|
</div>
|
</div>
|
</el-tooltip>
|
|
<div class="agent_line"></div>
|
<div class="cursor-pointer m-0" @click="toggleShowExitLogin" v-if="isLoginStatus" ref="toggleExitLoginBtnRef">
|
<div class="nav__profile">
|
<span class="use_name">{{ firstUserCharacter }}</span>
|
</div>
|
<div class="isShow_Profile" v-show="isShowExitLogin">
|
<div class="exit" @click="feedbackClick"><i class="ywifont ywicon-youxiang"></i> 用户反馈</div>
|
<div class="exit" @click="logoutClick"><i class="ywifont ywicon-tuichu"></i> 退出登录</div>
|
</div>
|
</div>
|
<div v-else class="cursor-pointer m-0">
|
<div class="nav__profile" @click="openLoginDlg">
|
<span class="use_name">登</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="nav_history_list bg-[#1c1e1d]" v-show="isShowHistoryChatRoom" ref="historyChatRoomRef">
|
<div class="flex flex-col flex-auto w-[210.98px] rounded-t-lg box-border relative opacity-100 overflow-y-auto h100 p-[12px]">
|
<div class="group flex-0 relative w100 h-[34px] bg-[#2b2c30]">
|
<el-input clearable v-model="queryParams.title" placeholder="搜索" class="set-input">
|
<template #prefix>
|
<el-icon><search /></el-icon>
|
</template>
|
</el-input>
|
<div
|
class="absolute hidden top-[100%] w-[84px] z-[1001] left-0 group-hover:block overflow-hidden rounded-md text-sm text-gray-500 bg-[#fff] py-1.5"
|
>
|
<div
|
class="w100 relative hover:bg-[#e6f1ff]"
|
v-for="item in Object.keys(dateFilterMap)"
|
:key="item"
|
@click="filterDateClick(Number(item))"
|
>
|
<div
|
class="size-2 absolute left-2 rounded-full bg-[#2a82e4]"
|
:style="{ display: item === activeDateFilter + '' ? 'block' : 'none' }"
|
style="top: calc(50% - 0.25rem)"
|
></div>
|
<div class="w100 relative h-[28px] flex items-center justify-center cursor-pointer">{{ dateFilterMap[item] }}</div>
|
</div>
|
</div>
|
</div>
|
|
<div class="flex-auto text-[#ccc] flex flex-col items-center mt-6 overflow-y-auto" ref="chatRoomRef">
|
<div
|
:class="{ 'bg-[#41424a]': item.id === activeRoomId }"
|
class="group flex items-center w-full h-10 rounded-md cursor-pointer px-2 py-2 flex-0"
|
v-for="(item, index) in queryData"
|
:key="index"
|
@click="roomClick(item)"
|
>
|
<div class="ywifont ywicon-xiaoxi1 flex-0 mr-2.5"></div>
|
<div class="flex-auto text-ellipsis text-nowrap text-sm group-hover:text-[#0084ff]">{{ item.title }}</div>
|
<div class="text-gray-100 flex items-center space-x-2 ml-1">
|
<div class="ywifont invisible ywicon-bianji group-hover:visible" @click.stop="editChat(item)"></div>
|
<el-popconfirm title="确定删除聊天记录?" @confirm.stop="confirmDeleteChatRoom(item)" width="180">
|
<template #reference>
|
<div class="ywifont invisible ywicon-shanchu3 group-hover:visible"></div>
|
</template>
|
</el-popconfirm>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="expand-sidebar" @click="toggleSidebar" v-if="!isSharePage">
|
<i class="text-[#fff] transition-all ywifont ywicon-zuoyoujiantou1"></i>
|
</div>
|
<el-dialog v-model="userFeedbackVisible" title="用户反馈" width="500" :before-close="handleCloseFeedback">
|
<el-input v-model="userFeedbackText" :rows="8" type="textarea" placeholder="欢迎说说你的想法" />
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="handleCloseFeedback">取消</el-button>
|
<el-button type="primary" @click="confirmFeedback" :disabled="is_input_title"> 确 定 </el-button>
|
</div>
|
</template>
|
</el-dialog>
|
<!-- <el-dialog v-model="userRenameVisible" title="重命名" width="500" :before-close="handleCloseRename" class="el-dialog-rename">
|
<el-input v-model="userRenameText" :rows="8" type="textarea" />
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="handleCloseRename">取消</el-button>
|
<el-button type="primary" @click="confirmRename"> 确 定 </el-button>
|
</div>
|
</template>
|
</el-dialog> -->
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { onClickOutside } from '@vueuse/core';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
import moment from 'moment';
|
import { computed, onMounted, ref, watch, watchEffect } from 'vue';
|
import type { ChatRoomItem } from './components/types';
|
import { DeleteHistoryGroups } from '/@/api/ai/chat';
|
import { useSearch } from '/@/hooks/useSearch';
|
import { DateFilter, dateFilterMap } from '/@/model/types/date';
|
import {
|
activeRoomId,
|
chatRoomList,
|
gotoAnswerPage,
|
isLoginStatus,
|
isSharePage,
|
isShowLogin,
|
newChatRoomClick,
|
} from '/@/stores/chatRoom';
|
import emitter from '/@/utils/mitt';
|
import { accessSessionKey, userNameKey } from '/@/utils/request';
|
import { gotoRoute } from '/@/utils/route';
|
import { Local, LoginInfo } from '/@/utils/storage';
|
import { debounce, getRecentDateRange } from '/@/utils/util';
|
const emit = defineEmits(['toggleSidebar']);
|
const prop = defineProps(['isShow']);
|
const userName = ref('');
|
const firstUserCharacter = computed(() => userName.value?.[0]?.toUpperCase());
|
const isShowExitLogin = ref(false);
|
isLoginStatus.value = !!Local.get(accessSessionKey);
|
const toggleSidebar = () => {
|
emit('toggleSidebar', true);
|
};
|
//#region ====================== 历史会话 ===================
|
const isShowHistoryChatRoom = ref(false);
|
const historyChatRoomRef = ref(null);
|
const chatRoomRef = ref<HTMLDivElement>(null);
|
const queryParams = ref({
|
title: '',
|
});
|
const userRenameVisible = ref(false);
|
const userRenameText = ref('');
|
const historyChatRoomClick = () => {
|
isShowHistoryChatRoom.value = true;
|
};
|
const roomClick = (room: ChatRoomItem) => {
|
activeRoomId.value = room.id;
|
gotoAnswerPage(room);
|
};
|
const confirmDeleteChatRoom = async (room: ChatRoomItem) => {
|
const res = await DeleteHistoryGroups({
|
history_group_id: room.id,
|
});
|
|
const foundIndex = chatRoomList.value.findIndex((item) => item === room);
|
chatRoomList.value.splice(foundIndex, 1);
|
if (chatRoomList.value.length === 0) {
|
newChatRoomClick();
|
return;
|
}
|
roomClick(chatRoomList.value[0]);
|
chatRoomRef.value.firstElementChild?.scrollIntoView();
|
};
|
const editChat = (room: ChatRoomItem) => {
|
ElMessageBox.prompt('', '重命名', {
|
confirmButtonText: '确认',
|
cancelButtonText: '取消',
|
inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/,
|
inputValue: room.title,
|
inputErrorMessage: '请输入新的名称',
|
})
|
.then((value) => {
|
const foundIndex = chatRoomList.value.findIndex((item) => item.id === activeRoomId.value);
|
chatRoomList.value[foundIndex].title = userRenameText.value;
|
userRenameVisible.value = false;
|
})
|
.catch(({ value }) => {
|
ElMessage({
|
type: 'info',
|
message: '取消修改',
|
});
|
});
|
};
|
onClickOutside(
|
historyChatRoomRef,
|
() => {
|
isShowHistoryChatRoom.value = false;
|
},
|
{
|
ignore: ['.el-message-box'],
|
}
|
);
|
//#endregion
|
//#region ====================== 公司信息 ======================
|
const companyClick = () => {
|
gotoRoute({ name: 'AboutUs' });
|
};
|
//#endregion
|
//#region ====================== 用户反馈 ======================
|
const userFeedbackVisible = ref(false);
|
const userFeedbackText = ref('');
|
const is_input_title = computed(() => {
|
return userFeedbackText.value == '' ? true : false;
|
});
|
const feedbackClick = () => {
|
userFeedbackVisible.value = true;
|
};
|
const handleCloseFeedback = () => {
|
userFeedbackVisible.value = false;
|
};
|
const confirmFeedback = () => {
|
const data = {
|
content: userFeedbackText.value,
|
contact: '用户反馈',
|
}; //TODO 发送用户反馈
|
handleCloseFeedback();
|
};
|
|
//#endregion
|
//#region ====================== 显示/退出登录 ======================
|
//登录
|
const openLoginDlg = async () => {
|
// 分享页不需要
|
if (isSharePage.value) return;
|
isShowLogin.value = true;
|
};
|
//是否显示退出登录弹窗cpolar.top/login
|
const toggleShowExitLogin = () => {
|
isShowExitLogin.value = !isShowExitLogin.value;
|
};
|
//退出登录
|
const logoutClick = () => {
|
isShowExitLogin.value = false;
|
isLoginStatus.value = false;
|
LoginInfo.remove();
|
};
|
|
const toggleExitLoginBtnRef = ref<HTMLDivElement>(null);
|
|
onClickOutside(toggleExitLoginBtnRef, () => {
|
isShowExitLogin.value = false;
|
});
|
//#endregion
|
//#region ====================== 日期筛选 ======================
|
const activeDateFilter = ref<DateFilter>(DateFilter.All);
|
const filterDateClick = (dateFilter: DateFilter) => {
|
activeDateFilter.value = dateFilter;
|
};
|
const filteredChatRoomList = computed(() => {
|
if (activeDateFilter.value === DateFilter.All) return chatRoomList.value;
|
else {
|
let dayCount = null;
|
switch (activeDateFilter.value) {
|
case DateFilter.AWeek:
|
dayCount = 7;
|
break;
|
|
case DateFilter.AMonth:
|
dayCount = 30;
|
|
break;
|
case DateFilter.ThreeMonth:
|
dayCount = 90;
|
break;
|
}
|
const [startDay, endDay] = getRecentDateRange(dayCount);
|
const data = chatRoomList.value.filter((item) => moment(item.createTime).isBetween(startDay, endDay));
|
return data;
|
}
|
});
|
//#endregion
|
//#region ====================== 搜索聊天室 ======================
|
const { query, queryData } = useSearch(filteredChatRoomList, queryParams);
|
const debounceQuery = debounce(query);
|
watch(
|
() => queryParams.value.title,
|
(val) => {
|
debounceQuery();
|
}
|
);
|
//#endregion
|
watchEffect(() => {
|
if (!isLoginStatus.value) return;
|
userName.value = Local.get(userNameKey);
|
});
|
onMounted(async () => {
|
emitter.on('openLoginDlg', () => {
|
if (isShowLogin.value || isLoginStatus.value) return;
|
openLoginDlg();
|
});
|
emitter.on('logout', () => {
|
logoutClick();
|
});
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.layoutNav___jOCfX {
|
width: 68px;
|
z-index: 1030;
|
height: 100%;
|
position: relative;
|
transition: width 0.2s ease;
|
.layoutNav__item___1y99z {
|
position: absolute;
|
top: 0;
|
z-index: 101;
|
.nav__content {
|
width: 60px;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
padding: 14px 0 1px;
|
transition: all 0.3s ease;
|
transform: all ease 0.3s;
|
.agent_img {
|
width: 28px;
|
height: 28px;
|
background: url('/static/images/logo/logoWithNoName.png') no-repeat;
|
background-size: 100% 100%;
|
|
margin-bottom: 16px;
|
}
|
.agent_line {
|
width: 24px;
|
height: 1px;
|
background-color: #e5e5e5;
|
border-radius: 12px;
|
margin-bottom: 16px;
|
cursor: pointer;
|
}
|
.nav__chat {
|
width: 40px;
|
height: 40px;
|
border-radius: 12px;
|
margin-bottom: 10px;
|
cursor: pointer;
|
&:hover {
|
background-color: #41424a;
|
}
|
.nav__chat-icon {
|
background-position: 8px 8px;
|
font-size: 25px;
|
line-height: 40px;
|
text-align: center;
|
color: #fff;
|
&:hover {
|
color: #0084ff;
|
}
|
.chat_img {
|
display: inline-flex;
|
align-items: center;
|
color: inherit;
|
font-style: normal;
|
line-height: 0;
|
text-align: center;
|
text-transform: none;
|
height: 40px;
|
text-rendering: optimizeLegibility;
|
-webkit-font-smoothing: antialiased;
|
}
|
}
|
}
|
|
.nav__profile {
|
width: 40px;
|
height: 40px;
|
border-radius: 6px;
|
display: flex;
|
-webkit-box-pack: center;
|
justify-content: center;
|
-webkit-box-align: center;
|
align-items: center;
|
.use_name {
|
width: 30px;
|
height: 30px;
|
border-radius: 50%;
|
overflow: hidden;
|
background-color: #1d86ff;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
color: #fff;
|
}
|
}
|
}
|
}
|
.isShow_Profile {
|
color: rgba(0, 0, 0, 0.87);
|
border-radius: 8px;
|
background: #ffffff;
|
border-width: 1px;
|
border-style: solid;
|
border-color: #e5e5e5;
|
position: absolute;
|
overflow: hidden auto;
|
min-width: 140px;
|
min-height: 50px;
|
max-width: calc(100% - 32px);
|
outline: 0px;
|
max-height: calc(100% - 96px);
|
opacity: 1;
|
transform: none;
|
transition: opacity 274ms cubic-bezier(0.4, 0, 0.2, 1), transform 182ms cubic-bezier(0.4, 0, 0.2, 1);
|
// top: 105px;
|
top: 204px;
|
left: 72px;
|
transform-origin: 0px 181px;
|
.exit {
|
height: 44px;
|
padding: 16px 12px;
|
gap: 8px;
|
cursor: pointer;
|
&:hover {
|
background-color: rgba(0, 0, 0, 0.04);
|
}
|
}
|
}
|
}
|
.nav_history_list {
|
overflow: hidden;
|
border-left: 1px solid rgba(237, 239, 245, 0.45);
|
width: 211px;
|
height: calc(100% - 42px);
|
position: absolute;
|
right: 0px;
|
left: 65px;
|
z-index: 100;
|
top: 42px;
|
opacity: 1;
|
transition: opacity 0.2s;
|
}
|
.set-input {
|
:deep(.el-input__wrapper) {
|
width: 100%;
|
height: 100%;
|
font-size: 12px;
|
font-weight: 400;
|
letter-spacing: 0;
|
color: #e5e5e5;
|
border-radius: 6px;
|
border: 1px solid transparent;
|
box-sizing: border-box;
|
line-height: 34px;
|
// padding-left: 31px;
|
padding-right: 10px;
|
background-color: transparent;
|
cursor: pointer;
|
transition: color 0.2s ease-in-out;
|
box-shadow: unset;
|
}
|
:deep(.el-input__inner) {
|
&::placeholder {
|
color: white;
|
}
|
color: white;
|
}
|
}
|
::-webkit-scrollbar {
|
height: 0;
|
width: 0;
|
color: transparent;
|
}
|
.expand-sidebar {
|
width: 20px;
|
height: 48px;
|
background: rgba(0, 0, 0, 0.2);
|
position: absolute;
|
top: 50%;
|
right: 0px;
|
transform: translate(100%, -50%);
|
z-index: 9;
|
cursor: pointer;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 0 3px;
|
border-radius: 0px 5px 5px;
|
}
|
</style>
|