ai-chat-ui/src/components/chat/ChatHeader.vue

352 lines
6.6 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>
<header class="chat-header">
<!-- 左侧侧边栏切换和标题 -->
<div class="header-left">
<button
v-if="showSidebarToggle"
class="toggle-sidebar-btn"
title="切换侧边栏 (Ctrl+B)"
@click="$emit('toggle-sidebar')"
>
<Menu :size="20" />
</button>
<div class="conversation-info">
<h1 class="title">{{ title }}</h1>
<span v-if="messageCount > 0" class="message-count">
{{ messageCount }} 条消息
</span>
</div>
</div>
<!-- 右侧:操作按钮 -->
<div class="header-right">
<!-- 对话大小切换 -->
<!-- <button
class="header-btn"
:title="isWideMode ? '标准视图' : '宽屏视图'"
@click="$emit('toggle-wide-mode')"
>
<Maximize2 v-if="!isWideMode" :size="18" />
<Minimize2 v-else :size="18" />
</button> -->
<!-- 清空对话 -->
<button
class="header-btn"
title="清空对话"
:disabled="messageCount === 0"
@click="handleClear"
>
<Trash2 :size="18" />
</button>
<!-- 导出对话 -->
<button
class="header-btn"
title="导出对话"
:disabled="messageCount === 0"
@click="$emit('export')"
>
<Download :size="18" />
</button>
<!-- 更多操作 -->
<!-- <button
class="header-btn"
title="更多选项"
@click="showMoreMenu = !showMoreMenu"
>
<MoreHorizontal :size="18" />
</button> -->
<Transition name="dropdown">
<div v-if="showMoreMenu" class="more-menu">
<button class="menu-item" @click="handleShare">
<ExternalLink :size="16" />
<span>分享对话</span>
</button>
<button class="menu-item" @click="handlePin">
<Pin :size="16" />
<span>{{ isPinned ? "取消置顶" : "置顶对话" }}</span>
</button>
<button class="menu-item" @click="handleArchive">
<Archive :size="16" />
<span>归档对话</span>
</button>
<div class="menu-divider"></div>
<button class="menu-item" @click="handleSettings">
<Settings :size="16" />
<span>对话设置</span>
</button>
</div>
</Transition>
</div>
</header>
</template>
<script setup lang="ts">
import { ref } from "vue";
import {
Menu,
Maximize2,
Minimize2,
Trash2,
Download,
MoreHorizontal,
ExternalLink,
Pin,
Archive,
Settings,
} from "@/components/icons";
import { useSettingsStore } from "@/stores/settings.ts";
const props = withDefaults(
defineProps<{
title?: string;
messageCount?: number;
showSidebarToggle?: boolean;
isWideMode?: boolean;
isPinned?: boolean;
}>(),
{
title: "新对话",
messageCount: 0,
showSidebarToggle: true,
isWideMode: true,
isPinned: false,
},
);
const emit = defineEmits<{
"toggle-sidebar": [];
"toggle-wide-mode": [];
clear: [];
export: [];
share: [];
pin: [];
archive: [];
settings: [];
"conversation-settings": [];
}>();
const showMoreMenu = ref(false);
const settingsStore = useSettingsStore();
function handleClear() {
if (confirm("确定要清空当前对话吗?此操作不可恢复。")) {
emit("clear");
}
}
function handleShare() {
showMoreMenu.value = false;
emit("share");
}
function handlePin() {
showMoreMenu.value = false;
emit("pin");
}
function handleArchive() {
showMoreMenu.value = false;
emit("archive");
}
// 点击外部关闭菜单
function handleClickOutside(event: MouseEvent) {
const target = event.target as HTMLElement;
if (!target.closest(".header-btn") && !target.closest(".more-menu")) {
showMoreMenu.value = false;
}
}
function handleSettings() {
showMoreMenu.value = false;
settingsStore.openConversationSettingsModal();
}
if (typeof window !== "undefined") {
document.addEventListener("click", handleClickOutside);
}
</script>
<style lang="scss" scoped>
.chat-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
padding: 0 20px;
background: white;
border-bottom: 1px solid #e2e8f0;
flex-shrink: 0;
.dark & {
background: #1e1e2e;
border-bottom-color: #2d2d3d;
}
}
.header-left {
display: flex;
align-items: center;
gap: 12px;
}
.toggle-sidebar-btn {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border: none;
border-radius: 10px;
background: transparent;
color: #6b7280;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: #f3f4f6;
color: #374151;
.dark & {
background: #2d2d3d;
color: #e5e7eb;
}
}
}
.conversation-info {
display: flex;
flex-direction: column;
}
.title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #1f2937;
.dark & {
color: #f3f4f6;
}
}
.message-count {
font-size: 12px;
color: #9ca3af;
}
.header-right {
display: flex;
align-items: center;
gap: 6px;
position: relative;
}
.header-btn {
display: flex;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
border: none;
border-radius: 10px;
background: transparent;
color: #6b7280;
cursor: pointer;
transition: all 0.2s ease;
&:hover:not(:disabled) {
background: #f3f4f6;
color: #374151;
.dark & {
background: #2d2d3d;
color: #e5e7eb;
}
}
&:disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
.more-menu {
position: absolute;
top: calc(100% + 8px);
right: 0;
min-width: 180px;
padding: 8px;
background: white;
border: 1px solid #e2e8f0;
border-radius: 14px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
z-index: 100;
.dark & {
background: #1e1e2e;
border-color: #2d2d3d;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
}
}
.menu-item {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 10px 12px;
border: none;
border-radius: 8px;
background: transparent;
color: #374151;
font-size: 14px;
text-align: left;
cursor: pointer;
transition: all 0.15s ease;
.dark & {
color: #e5e7eb;
}
&:hover {
background: #f3f4f6;
.dark & {
background: #2d2d3d;
}
}
svg {
color: #6b7280;
}
}
.menu-divider {
height: 1px;
margin: 6px 0;
background: #e2e8f0;
.dark & {
background: #374151;
}
}
// 下拉动画
.dropdown-enter-active,
.dropdown-leave-active {
transition: all 0.2s ease;
}
.dropdown-enter-from,
.dropdown-leave-to {
opacity: 0;
transform: translateY(-8px);
}
</style>