feat: 样式优化

This commit is contained in:
digua
2025-12-02 21:55:24 +08:00
parent 9d1a19b634
commit 77b6035b13
9 changed files with 142 additions and 88 deletions
+1 -1
View File
@@ -65,7 +65,7 @@ watch(
</script>
<template>
<div class="space-y-6">
<div class="mx-auto max-w-3xl space-y-6">
<!-- 口头禅分析模块 -->
<LoadingState v-if="isLoadingCatchphrase" text="正在分析口头禅数据..." />
+1 -1
View File
@@ -74,7 +74,7 @@ const memberRankData = computed<RankItem[]>(() => {
<template>
<div ref="contentRef" class="flex gap-6">
<!-- 主内容区 -->
<div class="min-w-0 flex-1 space-y-6">
<div class="min-w-0 flex-1 px-8 mx-auto max-w-3xl space-y-6">
<!-- 赛季大标题 -->
<div class="mb-8 mt-4">
<h1
@@ -26,9 +26,11 @@ const props = defineProps<{
// 使用类型断言绕过 Pinia persist 插件的类型推断问题
const chatStore = useChatStore() as ReturnType<typeof useChatStore> & {
customKeywordTemplates: BaseKeywordTemplate[]
deletedPresetTemplateIds: string[]
addCustomKeywordTemplate: (template: BaseKeywordTemplate) => void
updateCustomKeywordTemplate: (id: string, updates: Partial<Omit<BaseKeywordTemplate, 'id'>>) => void
removeCustomKeywordTemplate: (id: string) => void
addDeletedPresetTemplateId: (id: string) => void
}
// 颜色模式:false = 单色,true = 多色
@@ -78,7 +80,7 @@ const PRESET_TEMPLATES: KeywordTemplate[] = [
{
id: 'laugh',
name: '含笑量',
keywords: ['哈哈', 'xswl', 'lol', 'ww', '笑死', '233'],
keywords: ['哈哈', 'xswl', 'lol', '笑死', '233'],
description: '统计群内的快乐指数',
},
{
@@ -87,6 +89,36 @@ const PRESET_TEMPLATES: KeywordTemplate[] = [
keywords: ['想死', '难受', '哭了', '崩溃', '裂开', '无语', '累了'],
description: '统计群内的负面情绪',
},
{
id: 'praise',
name: '捧哏',
keywords: ['牛逼', '666', '厉害', '强', 'nb', '大佬', '羡慕', '好强'],
description: '统计群内最会夸人的成员',
},
{
id: 'slacker',
name: '摸鱼',
keywords: ['摸鱼', '下班', '饿了', '困了', '不想上班', '什么时候下班'],
description: '统计群内最想下班的打工人',
},
{
id: 'gossip',
name: '吃瓜',
keywords: ['吃瓜', '细说', '真的假的', '展开说说', '尊嘟假嘟', '卧槽', '离谱'],
description: '统计群内最爱吃瓜的成员',
},
{
id: 'polite',
name: '礼貌',
keywords: ['谢谢', '麻烦', '收到', '好的', '辛苦', '打扰', '请教'],
description: '统计群内最客气的成员',
},
{
id: 'curious',
name: '疑问',
keywords: ['为什么', '啥', '怎么', '不懂', '求教'],
description: '统计群内问题最多的成员',
},
]
// 合并预设和自定义模板
@@ -95,7 +127,9 @@ const allTemplates = computed<KeywordTemplate[]>(() => {
...t,
isCustom: true,
}))
return [...PRESET_TEMPLATES, ...custom]
// 过滤掉已删除的预设模板
const activePresets = PRESET_TEMPLATES.filter((t) => !chatStore.deletedPresetTemplateIds.includes(t.id))
return [...activePresets, ...custom]
})
// 当前选中的模板
@@ -104,6 +138,12 @@ const selectedTemplateId = ref<string>('laugh')
// 当前关键词(可编辑)
const currentKeywords = ref<string[]>([...PRESET_TEMPLATES[0].keywords])
// 获取当前模板名称
const currentTemplateName = computed(() => {
const template = allTemplates.value.find((t) => t.id === selectedTemplateId.value)
return template ? template.name : ''
})
// 分析结果
const analysis = ref<LaughAnalysis | null>(null)
const isLoading = ref(false)
@@ -154,6 +194,8 @@ function removeTemplateKeyword(keyword: string) {
function selectTemplate(template: KeywordTemplate) {
selectedTemplateId.value = template.id
currentKeywords.value = [...template.keywords]
// 切换模板时先清空数据,触发 loading 状态
analysis.value = null
loadAnalysis()
}
@@ -161,6 +203,7 @@ function selectTemplate(template: KeywordTemplate) {
function clearAllKeywords() {
currentKeywords.value = []
analysis.value = null
selectedTemplateId.value = ''
}
// 当前关键词输入
@@ -227,11 +270,21 @@ function saveTemplate() {
showTemplateModal.value = false
}
// 删除自定义模板
// 删除模板(支持预设和自定义
function deleteTemplate(templateId: string) {
chatStore.removeCustomKeywordTemplate(templateId)
if (isPresetTemplate(templateId)) {
chatStore.addDeletedPresetTemplateId(templateId)
} else {
chatStore.removeCustomKeywordTemplate(templateId)
}
if (selectedTemplateId.value === templateId) {
selectTemplate(PRESET_TEMPLATES[0])
// 如果删除的是当前选中的模板,尝试选中第一个可用模板,否则清空
if (allTemplates.value.length > 0) {
selectTemplate(allTemplates.value[0])
} else {
clearAllKeywords()
}
}
}
@@ -309,14 +362,8 @@ watch(
<template>
<ListPro
:items="rankData"
title="🔍 关键词分析"
:description="
isLoading
? '加载中...'
: analysis
? `共检测到 ${analysis.totalLaughs} 次关键词,群整体词频率 ${analysis.groupLaughRate}%`
: '配置关键词后开始分析'
"
title="🔍 关键词排行"
description="分析群聊关键词使用排行,这里可以自定义多种榜单"
:topN="10"
countTemplate="共 {count} 位成员"
>
@@ -330,21 +377,22 @@ watch(
<UContextMenu
v-for="template in allTemplates"
:key="template.id"
:items="
template.isCustom
? [
[
{ label: '编辑', icon: 'i-lucide-pencil', onSelect: () => openEditModal(template) },
{
label: '删除',
icon: 'i-lucide-trash',
color: 'error' as const,
onSelect: () => deleteTemplate(template.id),
},
],
]
: [[{ label: '编辑', icon: 'i-lucide-pencil', onSelect: () => openEditModal(template) }]]
"
:items="[
[
{
label: '编辑',
icon: 'i-lucide-pencil',
disabled: !template.isCustom,
onSelect: () => openEditModal(template),
},
{
label: '删除',
icon: 'i-lucide-trash',
color: 'error' as const,
onSelect: () => deleteTemplate(template.id),
},
],
]"
>
<button
class="rounded-md border px-2.5 py-1 text-sm transition-all"
@@ -373,7 +421,7 @@ watch(
<div class="space-y-3">
<div>
<label class="mb-1 block text-xs text-gray-500">模板名称</label>
<UInput v-model="templateName" placeholder="如:正能量" size="sm" />
<UInput v-model="templateName" placeholder="如:正能量" />
</div>
<div>
<label class="mb-1 block text-xs text-gray-500">关键词</label>
@@ -381,7 +429,6 @@ watch(
<UBadge
v-for="keyword in templateKeywords"
:key="keyword"
color="amber"
variant="soft"
class="cursor-pointer"
@click="removeTemplateKeyword(keyword)"
@@ -392,7 +439,6 @@ watch(
<UInput
v-model="newTemplateKeyword"
placeholder="输入后回车添加"
size="sm"
class="w-32"
@keydown.enter.prevent="addTemplateKeyword"
/>
@@ -400,9 +446,8 @@ watch(
</div>
</div>
<div class="mt-4 flex justify-end gap-2">
<UButton size="sm" color="gray" variant="soft" @click="showTemplateModal = false">取消</UButton>
<UButton color="gray" variant="soft" @click="showTemplateModal = false">取消</UButton>
<UButton
size="sm"
color="primary"
:disabled="!templateName.trim() || templateKeywords.length === 0"
@click="saveTemplate"
@@ -420,21 +465,13 @@ watch(
<UBadge
v-for="keyword in currentKeywords"
:key="keyword"
variant="soft"
class="cursor-pointer"
:color="getKeywordColor(keyword).badge"
@click="removeKeyword(keyword)"
>
{{ keyword }}
<span class="ml-0.5 hover:text-red-500">×</span>
</UBadge>
<UInput
v-model="newKeyword"
placeholder="输入后回车添加"
size="sm"
class="w-32"
@keydown.enter.prevent="addKeyword"
/>
<UInput v-model="newKeyword" placeholder="输入后回车添加" class="w-32" @keydown.enter.prevent="addKeyword" />
<button
v-if="currentKeywords.length > 0"
class="text-xs text-gray-400 hover:text-red-500"
@@ -452,7 +489,11 @@ watch(
class="border-b border-gray-100 px-5 py-4 dark:border-gray-800"
>
<div class="mb-3 flex items-center justify-between">
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">关键词分布</span>
<span class="text-base font-medium text-gray-700 dark:text-gray-300">
{{
currentTemplateName ? currentTemplateName : currentKeywords.length === 1 ? currentKeywords[0] : '关键词'
}}排行榜
</span>
<label class="flex cursor-pointer items-center gap-1.5 text-xs text-gray-500">
<span>多色模式</span>
<USwitch v-model="isMultiColor" size="md" />
@@ -462,7 +503,7 @@ watch(
<div
v-for="item in analysis.typeDistribution"
:key="item.type"
class="flex items-center gap-2 rounded-lg px-3 py-1.5"
class="flex items-center gap-2 rounded-lg px-2 py-2 text-xs"
:class="getKeywordColor(item.type).wrapBg"
>
<span class="h-2.5 w-2.5 shrink-0 rounded-full" :class="getKeywordColor(item.type).bg" />
@@ -516,5 +557,14 @@ watch(
</div>
</div>
</template>
<!-- 自定义空状态 -->
<template #empty>
<div v-if="!isLoading" class="flex h-64 flex-col items-center justify-center text-gray-400">
<UIcon name="i-heroicons-magnifying-glass" class="mb-2 h-8 w-8 opacity-50" />
<p class="text-sm">暂无数据请尝试添加关键词或切换模板</p>
</div>
<div v-else class="h-64" />
</template>
</ListPro>
</template>
@@ -144,7 +144,7 @@ watch(
</div>
<!-- 最晚下班 & 最早上班 -->
<div class="grid gap-6 lg:grid-cols-2">
<div class="grid gap-6">
<!-- 最晚下班排名 -->
<div>
<RankListPro
@@ -123,7 +123,7 @@ watch(
</div>
<!-- 复读排行榜 Grid -->
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<div class="grid grid-cols-1 gap-6">
<RankListPro
v-if="originatorRankData.length > 0"
:members="originatorRankData"
+5 -1
View File
@@ -76,6 +76,10 @@ const formattedCount = computed(() => props.countTemplate.replace('{count}', Str
</div>
<!-- 空状态 -->
<div v-if="items.length === 0" class="px-5 py-8 text-center text-sm text-gray-400">暂无数据</div>
<div v-if="items.length === 0">
<slot name="empty">
<div class="px-5 py-8 text-center text-sm text-gray-400">暂无数据</div>
</slot>
</div>
</div>
</template>
+26 -24
View File
@@ -35,8 +35,8 @@ const isInitialLoad = ref(true) // 用于跳过初始加载时的 watch 触发
const tabs = [
{ id: 'overview', label: '总览', icon: 'i-heroicons-chart-pie' },
{ id: 'ranking', label: '群榜单', icon: 'i-heroicons-trophy' },
{ id: 'relationships', label: '群关系', icon: 'i-heroicons-heart' },
{ id: 'quotes', label: '群语录', icon: 'i-heroicons-chat-bubble-bottom-center-text' },
{ id: 'relationships', label: '群关系', icon: 'i-heroicons-heart' },
{ id: 'timeline', label: '群趋势', icon: 'i-heroicons-chart-bar' },
]
@@ -267,23 +267,25 @@ onMounted(() => {
</div>
<!-- Tabs -->
<div class="mt-4 flex items-center gap-1">
<button
v-for="tab in tabs"
:key="tab.id"
class="flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-all"
:class="[
activeTab === tab.id
? 'bg-pink-500 text-white dark:bg-pink-900/30 dark:text-pink-300'
: 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800',
]"
@click="activeTab = tab.id"
>
<UIcon :name="tab.icon" class="h-4 w-4" />
{{ tab.label }}
</button>
<!-- 年份选择器靠右 -->
<UITabs v-model="selectedYear" :items="yearOptions" size="sm" class="max-w-100 ml-auto" />
<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">
<button
v-for="tab in tabs"
:key="tab.id"
class="flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-all"
:class="[
activeTab === tab.id
? 'bg-pink-500 text-white dark:bg-pink-900/30 dark:text-pink-300'
: 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800',
]"
@click="activeTab = tab.id"
>
<UIcon :name="tab.icon" class="h-4 w-4" />
<span class="whitespace-nowrap">{{ tab.label }}</span>
</button>
</div>
<!-- 年份选择器靠右允许收缩 -->
<UITabs v-model="selectedYear" :items="yearOptions" size="sm" class="min-w-0 flex-shrink" />
</div>
</div>
@@ -324,18 +326,18 @@ onMounted(() => {
:selected-year="selectedYear"
:available-years="availableYears"
/>
<RelationshipsTab
v-else-if="activeTab === 'relationships'"
:key="'relationships-' + selectedYear"
:session-id="currentSessionId!"
:time-filter="timeFilter"
/>
<QuotesTab
v-else-if="activeTab === 'quotes'"
:key="'quotes-' + selectedYear"
:session-id="currentSessionId!"
:time-filter="timeFilter"
/>
<RelationshipsTab
v-else-if="activeTab === 'relationships'"
:key="'relationships-' + selectedYear"
:session-id="currentSessionId!"
:time-filter="timeFilter"
/>
<TimelineTab
v-else-if="activeTab === 'timeline'"
:key="'timeline-' + selectedYear"
+12 -1
View File
@@ -248,6 +248,15 @@ export const useChatStore = defineStore(
}
}
// ==================== 已删除的预设模板 ====================
const deletedPresetTemplateIds = ref<string[]>([])
function addDeletedPresetTemplateId(id: string) {
if (!deletedPresetTemplateIds.value.includes(id)) {
deletedPresetTemplateIds.value.push(id)
}
}
return {
// State
sessions,
@@ -257,6 +266,7 @@ export const useChatStore = defineStore(
isInitialized,
isSidebarCollapsed,
customKeywordTemplates,
deletedPresetTemplateIds,
// Computed
currentSession,
// Actions
@@ -271,6 +281,7 @@ export const useChatStore = defineStore(
addCustomKeywordTemplate,
updateCustomKeywordTemplate,
removeCustomKeywordTemplate,
addDeletedPresetTemplateId,
}
},
{
@@ -282,7 +293,7 @@ export const useChatStore = defineStore(
},
{
// 自定义模板:localStorage(持久保存)
pick: ['customKeywordTemplates'],
pick: ['customKeywordTemplates', 'deletedPresetTemplateIds'],
storage: localStorage,
},
],