352 lines
6.6 KiB
Vue
352 lines
6.6 KiB
Vue
<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>
|