feat: 抽象Header组件

This commit is contained in:
digua
2025-12-13 13:33:58 +08:00
parent e91d926f05
commit dcf1347969
7 changed files with 67 additions and 59 deletions
+3 -3
View File
@@ -325,7 +325,7 @@ watch(
</div>
<!-- 输入框区域 -->
<div class="p-4 pt-0">
<div class="px-4 pb-2">
<div class="mx-auto max-w-3xl">
<ChatInput
:disabled="isAIThinking"
@@ -335,7 +335,7 @@ watch(
/>
<!-- 底部状态栏 -->
<div class="mt-2 flex items-center justify-between px-1">
<div class="flex items-center justify-between px-1">
<!-- 左侧预设选择器 -->
<UPopover v-model:open="isPresetPopoverOpen" :ui="{ content: 'p-0' }">
<button
@@ -384,7 +384,7 @@ watch(
:class="[hasLLMConfig ? 'text-gray-400' : 'text-amber-500 font-medium']"
>
<span class="h-1.5 w-1.5 rounded-full" :class="[hasLLMConfig ? 'bg-green-500' : 'bg-amber-500']" />
{{ hasLLMConfig ? '服务已连接' : '请在全局设置中配置 AI 服务' }}
{{ hasLLMConfig ? 'AI 已连接' : '请在全局设置中配置 AI 服务' }}
</div>
</div>
</div>
+1 -1
View File
@@ -40,7 +40,7 @@ function handleStop() {
</script>
<template>
<div class="shrink-0 border-t border-gray-200 py-4 dark:border-gray-800">
<div class="shrink-0 border-t border-gray-200 pt-4 pb-2 dark:border-gray-800">
<div class="w-full">
<UChatPrompt
v-model="inputValue"
+3 -4
View File
@@ -176,7 +176,7 @@ function isPrivateChat(session: AnalysisSession): boolean {
variant="ghost"
@click="router.push({ name: 'tools' })"
>
<UIcon name="i-heroicons-squares-2x2" class="h-5 w-5 shrink-0" :class="[isCollapsed ? '' : 'mr-2']" />
<UIcon name="i-heroicons-wrench-screwdriver" class="h-4 w-4 shrink-0" :class="[isCollapsed ? '' : 'mr-2']" />
<span v-if="!isCollapsed" class="truncate">实用工具</span>
</UButton>
</UTooltip>
@@ -231,9 +231,8 @@ function isPrivateChat(session: AnalysisSession): boolean {
isCollapsed ? '' : 'mr-3',
]"
>
<!-- 私聊显示用户图标群聊显示首字母 -->
<UIcon v-if="isPrivateChat(session)" name="i-heroicons-user" class="h-4 w-4" />
<template v-else>{{ session.name ? session.name.charAt(0) : '?' }}</template>
<!-- 私聊群聊显示名字首字母 -->
{{ session.name ? session.name.charAt(0) : '?' }}
</div>
<!-- Session Info -->
+40
View File
@@ -0,0 +1,40 @@
<script setup lang="ts">
/**
* 页面 Header 通用组件
* 包含标题、描述、可选图标,以及默认 slot 用于额外内容
*/
defineProps<{
title: string
description?: string
icon?: string
}>()
</script>
<template>
<div class="border-b border-gray-200 bg-white px-6 py-4 dark:border-gray-800 dark:bg-gray-900">
<!-- 标题区域 -->
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<!-- 可选图标 -->
<div
v-if="icon"
class="flex h-10 w-10 items-center justify-center rounded-xl bg-linear-to-br from-pink-400 to-pink-600"
>
<UIcon :name="icon" class="h-5 w-5 text-white" />
</div>
<div>
<h1 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ title }}
</h1>
<p v-if="description" class="text-xs text-gray-500 dark:text-gray-400">
{{ description }}
</p>
</div>
</div>
</div>
<!-- 额外内容 slot Tabs -->
<slot />
</div>
</template>
+9 -24
View File
@@ -12,6 +12,7 @@ import RankingTab from './components/RankingTab.vue'
import QuotesTab from './components/QuotesTab.vue'
import RelationshipsTab from './components/RelationshipsTab.vue'
import MemberTab from './components/MemberTab.vue'
import PageHeader from '@/components/layout/PageHeader.vue'
const route = useRoute()
const router = useRouter()
@@ -239,30 +240,14 @@ onMounted(() => {
<!-- Content -->
<template v-else-if="session">
<!-- Header -->
<div class="border-b border-gray-200 bg-white px-6 py-4 dark:border-gray-800 dark:bg-gray-900">
<div class="flex items-center justify-between">
<!-- Session Info -->
<div class="flex items-center gap-3">
<div
class="flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-pink-400 to-pink-600"
>
<UIcon name="i-heroicons-chat-bubble-left-right" class="h-5 w-5 text-white" />
</div>
<div>
<h1 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ session.name }}
</h1>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ dateRangeText }}{{ selectedYear ? filteredMemberCount : session.memberCount }} 位成员共聊了
{{ selectedYear ? filteredMessageCount : session.messageCount }} 条消息
</p>
</div>
</div>
</div>
<PageHeader
:title="session.name"
:description="`${dateRangeText}${selectedYear ? filteredMemberCount : session.memberCount} 位成员共聊了 ${selectedYear ? filteredMessageCount : session.messageCount} 条消息`"
icon="i-heroicons-chat-bubble-left-right"
>
<!-- Tabs -->
<div class="mt-4 flex items-center justify-between gap-4">
<div class="flex flex-shrink-0 items-center gap-1 overflow-x-auto scrollbar-hide">
<div class="flex shrink-0 items-center gap-1 overflow-x-auto scrollbar-hide">
<button
v-for="tab in tabs"
:key="tab.id"
@@ -284,10 +269,10 @@ onMounted(() => {
v-model="selectedYear"
:items="yearOptions"
size="sm"
class="min-w-0 flex-shrink"
class="min-w-0 shrink"
/>
</div>
</div>
</PageHeader>
<!-- Tab Content -->
<div class="relative flex-1 overflow-y-auto">
+9 -23
View File
@@ -10,6 +10,7 @@ import AITab from '@/components/analysis/AITab.vue'
import OverviewTab from './components/OverviewTab.vue'
import QuotesTab from './components/QuotesTab.vue'
import MemberTab from './components/MemberTab.vue'
import PageHeader from '@/components/layout/PageHeader.vue'
const route = useRoute()
const router = useRouter()
@@ -217,29 +218,14 @@ onMounted(() => {
<!-- Content -->
<template v-else-if="session">
<!-- Header -->
<div class="border-b border-gray-200 bg-white px-6 py-4 dark:border-gray-800 dark:bg-gray-900">
<div class="flex items-center justify-between">
<!-- Session Info -->
<div class="flex items-center gap-3">
<div
class="flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-pink-400 to-pink-600"
>
<UIcon name="i-heroicons-user" class="h-5 w-5 text-white" />
</div>
<div>
<h1 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ session.name }}
</h1>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ dateRangeText }} {{ selectedYear ? filteredMessageCount : session.messageCount }} 条消息
</p>
</div>
</div>
</div>
<PageHeader
:title="session.name"
:description="`${dateRangeText},共 ${selectedYear ? filteredMessageCount : session.messageCount} 条消息`"
icon="i-heroicons-user"
>
<!-- Tabs -->
<div class="mt-4 flex items-center justify-between gap-4">
<div class="flex flex-shrink-0 items-center gap-1 overflow-x-auto scrollbar-hide">
<div class="flex shrink-0 items-center gap-1 overflow-x-auto scrollbar-hide">
<button
v-for="tab in tabs"
:key="tab.id"
@@ -261,10 +247,10 @@ onMounted(() => {
v-model="selectedYear"
:items="yearOptions"
size="sm"
class="min-w-0 flex-shrink"
class="min-w-0 shrink"
/>
</div>
</div>
</PageHeader>
<!-- Tab Content -->
<div class="relative flex-1 overflow-y-auto">
+2 -4
View File
@@ -2,6 +2,7 @@
import { ref } from 'vue'
import { SubTabs } from '@/components/UI'
import MergeTab from '@/components/tools/MergeTab.vue'
import PageHeader from '@/components/layout/PageHeader.vue'
// Tab 配置
const tabs = [{ id: 'merge', label: '合并聊天记录', icon: 'i-heroicons-document-duplicate' }]
@@ -12,10 +13,7 @@ const activeTab = ref('merge')
<template>
<div class="flex h-full flex-col bg-gray-50 dark:bg-gray-950">
<!-- Header -->
<div class="border-b border-gray-200 bg-white px-6 py-4 dark:border-gray-800 dark:bg-gray-900">
<h1 class="text-xl font-semibold text-gray-900 dark:text-white">实用工具</h1>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">提供聊天记录处理的实用工具</p>
</div>
<PageHeader title="实用工具" description="提供聊天记录处理的实用工具" icon="i-heroicons-wrench-screwdriver" />
<!-- Tabs -->
<SubTabs v-model="activeTab" :items="tabs" />