feat: 重构TimeTab

This commit is contained in:
digua
2025-11-28 17:34:47 +08:00
parent 035db60645
commit 72605a6ef4
11 changed files with 354 additions and 115 deletions
+49
View File
@@ -7,6 +7,7 @@ import type {
MemberActivity,
HourlyActivity,
DailyActivity,
WeekdayActivity,
MessageType,
RepeatAnalysis,
RepeatStatItem,
@@ -520,6 +521,54 @@ export function getRepeatAnalysis(sessionId: string, filter?: TimeFilter): Repea
}
}
/**
* 获取星期活跃度分布
* 返回周一到周日的消息统计
*/
export function getWeekdayActivity(sessionId: string, filter?: TimeFilter): WeekdayActivity[] {
const db = openDatabase(sessionId)
if (!db) return []
try {
const { clause, params } = buildTimeFilter(filter)
const clauseWithSystem = buildSystemMessageFilter(clause)
// SQLite strftime('%w') 返回 0-60=周日
// 我们需要转换为 1-7,1=周一,7=周日
const rows = db
.prepare(
`
SELECT
CASE
WHEN CAST(strftime('%w', msg.ts, 'unixepoch', 'localtime') AS INTEGER) = 0 THEN 7
ELSE CAST(strftime('%w', msg.ts, 'unixepoch', 'localtime') AS INTEGER)
END as weekday,
COUNT(*) as messageCount
FROM message msg
JOIN member m ON msg.sender_id = m.id
${clauseWithSystem}
GROUP BY weekday
ORDER BY weekday
`
)
.all(...params) as Array<{ weekday: number; messageCount: number }>
// 补全所有星期(1-7
const result: WeekdayActivity[] = []
for (let w = 1; w <= 7; w++) {
const found = rows.find((r) => r.weekday === w)
result.push({
weekday: w,
messageCount: found ? found.messageCount : 0,
})
}
return result
} finally {
db.close()
}
}
/**
* 获取口头禅分析数据
* 统计每个成员最常说的内容(前5个)
+1
View File
@@ -12,6 +12,7 @@ export {
getMemberActivity,
getHourlyActivity,
getDailyActivity,
getWeekdayActivity,
getMessageTypeDistribution,
getTimeRange,
getMemberNameHistory,
+15
View File
@@ -315,6 +315,21 @@ const mainIpcMain = (win: BrowserWindow) => {
}
)
/**
* 获取星期活跃度分布
*/
ipcMain.handle(
'chat:getWeekdayActivity',
async (_, sessionId: string, filter?: { startTs?: number; endTs?: number }) => {
try {
return database.getWeekdayActivity(sessionId, filter)
} catch (error) {
console.error('获取星期活跃度失败:', error)
return []
}
}
)
/**
* 获取消息类型分布
*/
+2
View File
@@ -5,6 +5,7 @@ import type {
MemberNameHistory,
HourlyActivity,
DailyActivity,
WeekdayActivity,
MessageType,
ImportProgress,
RepeatAnalysis,
@@ -27,6 +28,7 @@ interface ChatApi {
getMemberNameHistory: (sessionId: string, memberId: number) => Promise<MemberNameHistory[]>
getHourlyActivity: (sessionId: string, filter?: TimeFilter) => Promise<HourlyActivity[]>
getDailyActivity: (sessionId: string, filter?: TimeFilter) => Promise<DailyActivity[]>
getWeekdayActivity: (sessionId: string, filter?: TimeFilter) => Promise<WeekdayActivity[]>
getMessageTypeDistribution: (
sessionId: string,
filter?: TimeFilter
+8
View File
@@ -6,6 +6,7 @@ import type {
MemberNameHistory,
HourlyActivity,
DailyActivity,
WeekdayActivity,
MessageType,
ImportProgress,
RepeatAnalysis,
@@ -111,6 +112,13 @@ const chatApi = {
return ipcRenderer.invoke('chat:getDailyActivity', sessionId, filter)
},
/**
* 获取星期活跃度分布
*/
getWeekdayActivity: (sessionId: string, filter?: { startTs?: number; endTs?: number }): Promise<WeekdayActivity[]> => {
return ipcRenderer.invoke('chat:getWeekdayActivity', sessionId, filter)
},
/**
* 获取消息类型分布
*/
+8 -5
View File
@@ -6,6 +6,7 @@ import type { AnalysisSession, MemberActivity, HourlyActivity, DailyActivity, Me
import UITabs from '@/components/UI/Tabs.vue'
import OverviewTab from './analysis/OverviewTab.vue'
import MembersTab from './analysis/MembersTab.vue'
import TimeTab from './analysis/TimeTab.vue'
import TimelineTab from './analysis/TimelineTab.vue'
const chatStore = useChatStore()
@@ -28,7 +29,8 @@ const selectedYear = ref<number>(0) // 0 表示全部
const tabs = [
{ id: 'overview', label: '总览', icon: 'i-heroicons-chart-pie' },
{ id: 'members', label: '成员', icon: 'i-heroicons-user-group' },
{ id: 'timeline', label: '时间', icon: 'i-heroicons-chart-bar' },
{ id: 'time', label: '规律', icon: 'i-heroicons-clock' },
{ id: 'timeline', label: '趋势', icon: 'i-heroicons-chart-bar' },
]
const activeTab = ref('overview')
@@ -241,12 +243,13 @@ onMounted(loadData)
:member-activity="memberActivity"
:time-filter="timeFilter"
/>
<TimelineTab
v-else-if="activeTab === 'timeline'"
:daily-activity="dailyActivity"
<TimeTab
v-else-if="activeTab === 'time'"
:session-id="currentSessionId!"
:hourly-activity="hourlyActivity"
:time-range="timeRange"
:time-filter="timeFilter"
/>
<TimelineTab v-else-if="activeTab === 'timeline'" :daily-activity="dailyActivity" :time-range="timeRange" />
</Transition>
</div>
</template>
+229
View File
@@ -0,0 +1,229 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type { HourlyActivity, WeekdayActivity } from '@/types/chat'
import { BarChart } from '@/components/charts'
import type { BarChartData } from '@/components/charts'
const props = defineProps<{
sessionId: string
hourlyActivity: HourlyActivity[]
timeFilter?: { startTs?: number; endTs?: number }
}>()
// 星期活跃度数据
const weekdayActivity = ref<WeekdayActivity[]>([])
const isLoadingWeekday = ref(false)
// 星期名称映射(周一开始)
const weekdayNames = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
// 加载星期活跃度数据
async function loadWeekdayActivity() {
if (!props.sessionId) return
isLoadingWeekday.value = true
try {
weekdayActivity.value = await window.chatApi.getWeekdayActivity(props.sessionId, props.timeFilter)
} catch (error) {
console.error('加载星期活跃度失败:', error)
} finally {
isLoadingWeekday.value = false
}
}
// 监听 sessionId 和 timeFilter 变化
watch(
() => [props.sessionId, props.timeFilter],
() => {
loadWeekdayActivity()
},
{ immediate: true, deep: true }
)
// 24小时分布图数据
const hourlyChartData = computed<BarChartData>(() => {
return {
labels: props.hourlyActivity.map((h) => `${h.hour}:00`),
values: props.hourlyActivity.map((h) => h.messageCount),
}
})
// 星期分布图数据
const weekdayChartData = computed<BarChartData>(() => {
return {
labels: weekdayActivity.value.map((w) => weekdayNames[w.weekday - 1]),
values: weekdayActivity.value.map((w) => w.messageCount),
}
})
// 分析指标
const peakHour = computed(() => {
if (!props.hourlyActivity.length) return null
return props.hourlyActivity.reduce((max, h) => (h.messageCount > max.messageCount ? h : max), props.hourlyActivity[0])
})
const peakWeekday = computed(() => {
if (!weekdayActivity.value.length) return null
return weekdayActivity.value.reduce((max, w) => (w.messageCount > max.messageCount ? w : max), weekdayActivity.value[0])
})
const lateNightRatio = computed(() => {
// 深夜定义为 0-6 点
const lateNight = props.hourlyActivity
.filter((h) => h.hour >= 0 && h.hour < 6)
.reduce((sum, h) => sum + h.messageCount, 0)
const total = props.hourlyActivity.reduce((sum, h) => sum + h.messageCount, 0)
return total > 0 ? Math.round((lateNight / total) * 100) : 0
})
const morningRatio = computed(() => {
// 早间定义为 6-12 点
const morning = props.hourlyActivity
.filter((h) => h.hour >= 6 && h.hour < 12)
.reduce((sum, h) => sum + h.messageCount, 0)
const total = props.hourlyActivity.reduce((sum, h) => sum + h.messageCount, 0)
return total > 0 ? Math.round((morning / total) * 100) : 0
})
const afternoonRatio = computed(() => {
// 下午定义为 12-18 点
const afternoon = props.hourlyActivity
.filter((h) => h.hour >= 12 && h.hour < 18)
.reduce((sum, h) => sum + h.messageCount, 0)
const total = props.hourlyActivity.reduce((sum, h) => sum + h.messageCount, 0)
return total > 0 ? Math.round((afternoon / total) * 100) : 0
})
const eveningRatio = computed(() => {
// 晚间定义为 18-24 点
const evening = props.hourlyActivity
.filter((h) => h.hour >= 18 && h.hour < 24)
.reduce((sum, h) => sum + h.messageCount, 0)
const total = props.hourlyActivity.reduce((sum, h) => sum + h.messageCount, 0)
return total > 0 ? Math.round((evening / total) * 100) : 0
})
// 工作日 vs 周末
const weekdayVsWeekend = computed(() => {
if (!weekdayActivity.value.length) return { weekday: 0, weekend: 0 }
const weekdaySum = weekdayActivity.value
.filter((w) => w.weekday >= 1 && w.weekday <= 5)
.reduce((sum, w) => sum + w.messageCount, 0)
const weekendSum = weekdayActivity.value
.filter((w) => w.weekday >= 6 && w.weekday <= 7)
.reduce((sum, w) => sum + w.messageCount, 0)
const total = weekdaySum + weekendSum
return {
weekday: total > 0 ? Math.round((weekdaySum / total) * 100) : 0,
weekend: total > 0 ? Math.round((weekendSum / total) * 100) : 0,
}
})
</script>
<template>
<div class="space-y-6">
<!-- 标题 -->
<div>
<h2 class="text-xl font-bold text-gray-900 dark:text-white">时间规律分析</h2>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">发现群聊的周期性活跃规律</p>
</div>
<!-- 统计卡片 -->
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">最活跃时段</p>
<p class="mt-1 text-2xl font-bold text-pink-600 dark:text-pink-400">{{ peakHour?.hour ?? 0 }}:00</p>
<p class="mt-1 text-xs text-gray-400">{{ peakHour?.messageCount ?? 0 }} 条消息</p>
</div>
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">最活跃星期</p>
<p class="mt-1 text-2xl font-bold text-pink-600 dark:text-pink-400">
{{ peakWeekday ? weekdayNames[peakWeekday.weekday - 1] : '-' }}
</p>
<p class="mt-1 text-xs text-gray-400">{{ peakWeekday?.messageCount ?? 0 }} 条消息</p>
</div>
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">夜猫子指数</p>
<p class="mt-1 text-2xl font-bold text-amber-600 dark:text-amber-400">{{ lateNightRatio }}%</p>
<p class="mt-1 text-xs text-gray-400">深夜活跃占比</p>
</div>
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">周末活跃度</p>
<p class="mt-1 text-2xl font-bold text-blue-600 dark:text-blue-400">{{ weekdayVsWeekend.weekend }}%</p>
<p class="mt-1 text-xs text-gray-400">周末消息占比</p>
</div>
</div>
<!-- 星期分布 -->
<div class="rounded-xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<h3 class="mb-4 font-semibold text-gray-900 dark:text-white">星期活跃分布</h3>
<div v-if="isLoadingWeekday" class="flex h-64 items-center justify-center">
<UIcon name="i-heroicons-arrow-path" class="h-6 w-6 animate-spin text-pink-500" />
</div>
<BarChart v-else :data="weekdayChartData" :height="256" />
<!-- 工作日 vs 周末 -->
<div class="mt-6 grid grid-cols-2 gap-4">
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">工作日周一至周五</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ weekdayVsWeekend.weekday }}%</div>
<div class="mx-auto mt-2 h-1 w-full max-w-24 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-500 transition-all" :style="{ width: `${weekdayVsWeekend.weekday}%` }" />
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">周末周六周日</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ weekdayVsWeekend.weekend }}%</div>
<div class="mx-auto mt-2 h-1 w-full max-w-24 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-blue-500 transition-all" :style="{ width: `${weekdayVsWeekend.weekend}%` }" />
</div>
</div>
</div>
</div>
<!-- 24小时分布 -->
<div class="rounded-xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<h3 class="mb-4 font-semibold text-gray-900 dark:text-white">24小时活跃分布</h3>
<BarChart
:data="hourlyChartData"
:height="256"
:x-label-filter="(_, index) => (index % 3 === 0 ? `${index}:00` : '')"
/>
<!-- 时段分析 -->
<div class="mt-6 grid grid-cols-4 gap-4">
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">凌晨 0-6点</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ lateNightRatio }}%</div>
<div class="mx-auto mt-2 h-1 w-full max-w-16 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-300 transition-all" :style="{ width: `${lateNightRatio}%` }" />
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">上午 6-12点</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ morningRatio }}%</div>
<div class="mx-auto mt-2 h-1 w-full max-w-16 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-400 transition-all" :style="{ width: `${morningRatio}%` }" />
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">下午 12-18点</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ afternoonRatio }}%</div>
<div class="mx-auto mt-2 h-1 w-full max-w-16 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-500 transition-all" :style="{ width: `${afternoonRatio}%` }" />
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">晚上 18-24点</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ eveningRatio }}%</div>
<div class="mx-auto mt-2 h-1 w-full max-w-16 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-600 transition-all" :style="{ width: `${eveningRatio}%` }" />
</div>
</div>
</div>
</div>
</div>
</template>
+32 -108
View File
@@ -1,13 +1,12 @@
<script setup lang="ts">
import { computed } from 'vue'
import type { HourlyActivity, DailyActivity } from '@/types/chat'
import type { DailyActivity } from '@/types/chat'
import dayjs from 'dayjs'
import { LineChart, BarChart } from '@/components/charts'
import type { LineChartData, BarChartData } from '@/components/charts'
import { LineChart } from '@/components/charts'
import type { LineChartData } from '@/components/charts'
const props = defineProps<{
dailyActivity: DailyActivity[]
hourlyActivity: HourlyActivity[]
timeRange: { start: number; end: number } | null
}>()
@@ -29,56 +28,6 @@ const dailyChartData = computed<LineChartData>(() => {
}
})
// 24小时分布图数据
const hourlyChartData = computed<BarChartData>(() => {
return {
labels: props.hourlyActivity.map((h) => `${h.hour}:00`),
values: props.hourlyActivity.map((h) => h.messageCount),
}
})
// 分析指标
const peakHour = computed(() => {
if (!props.hourlyActivity.length) return null
return props.hourlyActivity.reduce((max, h) => (h.messageCount > max.messageCount ? h : max), props.hourlyActivity[0])
})
const lateNightRatio = computed(() => {
// 深夜定义为 0-6 点
const lateNight = props.hourlyActivity
.filter((h) => h.hour >= 0 && h.hour < 6)
.reduce((sum, h) => sum + h.messageCount, 0)
const total = props.hourlyActivity.reduce((sum, h) => sum + h.messageCount, 0)
return total > 0 ? Math.round((lateNight / total) * 100) : 0
})
const morningRatio = computed(() => {
// 早间定义为 6-12 点
const morning = props.hourlyActivity
.filter((h) => h.hour >= 6 && h.hour < 12)
.reduce((sum, h) => sum + h.messageCount, 0)
const total = props.hourlyActivity.reduce((sum, h) => sum + h.messageCount, 0)
return total > 0 ? Math.round((morning / total) * 100) : 0
})
const afternoonRatio = computed(() => {
// 下午定义为 12-18 点
const afternoon = props.hourlyActivity
.filter((h) => h.hour >= 12 && h.hour < 18)
.reduce((sum, h) => sum + h.messageCount, 0)
const total = props.hourlyActivity.reduce((sum, h) => sum + h.messageCount, 0)
return total > 0 ? Math.round((afternoon / total) * 100) : 0
})
const eveningRatio = computed(() => {
// 晚间定义为 18-24 点
const evening = props.hourlyActivity
.filter((h) => h.hour >= 18 && h.hour < 24)
.reduce((sum, h) => sum + h.messageCount, 0)
const total = props.hourlyActivity.reduce((sum, h) => sum + h.messageCount, 0)
return total > 0 ? Math.round((evening / total) * 100) : 0
})
// 最活跃的一天
const peakDay = computed(() => {
if (!props.dailyActivity.length) return null
@@ -91,30 +40,37 @@ const avgDailyMessages = computed(() => {
const total = props.dailyActivity.reduce((sum, d) => sum + d.messageCount, 0)
return Math.round(total / props.dailyActivity.length)
})
// 活跃天数
const activeDays = computed(() => {
return props.dailyActivity.filter((d) => d.messageCount > 0).length
})
// 总天数(从第一条到最后一条消息)
const totalDays = computed(() => {
if (!props.timeRange) return 0
const start = dayjs.unix(props.timeRange.start)
const end = dayjs.unix(props.timeRange.end)
return end.diff(start, 'day') + 1
})
</script>
<template>
<div class="space-y-6">
<!-- 标题 -->
<div>
<h2 class="text-xl font-bold text-gray-900 dark:text-white">时间维度分析</h2>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">探索群聊的活跃规律</p>
<h2 class="text-xl font-bold text-gray-900 dark:text-white">时间分析</h2>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">追踪群聊的活跃趋势变化</p>
</div>
<!-- 统计卡片 -->
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">最活跃时段</p>
<p class="mt-1 text-2xl font-bold text-pink-600 dark:text-pink-400">{{ peakHour?.hour || 0 }}:00</p>
<p class="mt-1 text-xs text-gray-400">{{ peakHour?.messageCount || 0 }} 条消息</p>
</div>
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">最活跃日期</p>
<p class="mt-1 text-2xl font-bold text-pink-600 dark:text-pink-400">
{{ peakDay ? dayjs(peakDay.date).format('MM/DD') : '-' }}
</p>
<p class="mt-1 text-xs text-gray-400">{{ peakDay?.messageCount || 0 }} 条消息</p>
<p class="mt-1 text-xs text-gray-400">{{ peakDay?.messageCount ?? 0 }} 条消息</p>
</div>
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
@@ -126,9 +82,19 @@ const avgDailyMessages = computed(() => {
</div>
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">夜猫子指</p>
<p class="mt-1 text-2xl font-bold text-amber-600 dark:text-amber-400">{{ lateNightRatio }}%</p>
<p class="mt-1 text-xs text-gray-400">深夜活跃占比</p>
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">活跃天</p>
<p class="mt-1 text-2xl font-bold text-pink-600 dark:text-pink-400">
{{ activeDays }}
</p>
<p class="mt-1 text-xs text-gray-400">/ {{ totalDays }} </p>
</div>
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">活跃率</p>
<p class="mt-1 text-2xl font-bold text-pink-600 dark:text-pink-400">
{{ totalDays > 0 ? Math.round((activeDays / totalDays) * 100) : 0 }}%
</p>
<p class="mt-1 text-xs text-gray-400">有消息的天数占比</p>
</div>
</div>
@@ -137,47 +103,5 @@ const avgDailyMessages = computed(() => {
<h3 class="mb-4 font-semibold text-gray-900 dark:text-white">每日消息趋势</h3>
<LineChart :data="dailyChartData" :height="288" />
</div>
<!-- 24小时分布 -->
<div class="rounded-xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-800 dark:bg-gray-900">
<h3 class="mb-4 font-semibold text-gray-900 dark:text-white">24小时活跃分布</h3>
<BarChart
:data="hourlyChartData"
:height="256"
:x-label-filter="(_, index) => (index % 3 === 0 ? `${index}:00` : '')"
/>
<!-- 时段分析 -->
<div class="mt-6 grid grid-cols-4 gap-4">
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">凌晨 0-6点</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ lateNightRatio }}%</div>
<div class="mx-auto mt-2 h-1 w-full max-w-16 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-300 transition-all" :style="{ width: `${lateNightRatio}%` }" />
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">上午 6-12点</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ morningRatio }}%</div>
<div class="mx-auto mt-2 h-1 w-full max-w-16 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-400 transition-all" :style="{ width: `${morningRatio}%` }" />
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">下午 12-18点</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ afternoonRatio }}%</div>
<div class="mx-auto mt-2 h-1 w-full max-w-16 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-500 transition-all" :style="{ width: `${afternoonRatio}%` }" />
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">晚上 18-24点</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">{{ eveningRatio }}%</div>
<div class="mx-auto mt-2 h-1 w-full max-w-16 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-600 transition-all" :style="{ width: `${eveningRatio}%` }" />
</div>
</div>
</div>
</div>
</div>
</template>
+1 -1
View File
@@ -46,7 +46,7 @@ const formattedCount = computed(() => props.countTemplate.replace('{count}', Str
<slot name="headerRight" />
<!-- 完整列表弹窗 -->
<UModal v-model:open="isOpen" :ui="{ width: 'max-w-3xl' }">
<UModal v-model:open="isOpen" :ui="{ content: 'md:w-full max-w-4xl' }">
<UButton v-if="showViewAll" icon="i-heroicons-list-bullet" color="gray" variant="ghost">查看完整列表</UButton>
<template #header>
<div class="flex items-center justify-between">
+1 -1
View File
@@ -44,7 +44,7 @@ const showViewAll = computed(() => {
</div>
<!-- 完整排行榜 Dialog -->
<UModal v-model:open="isOpen" :ui="{ width: 'max-w-3xl' }">
<UModal v-model:open="isOpen" :ui="{ content: 'md:w-full max-w-3xl' }">
<UButton v-if="showViewAll" icon="i-heroicons-list-bullet" color="gray" variant="ghost">查看完整排行</UButton>
<template #header>
<div class="flex items-center justify-between">
+8
View File
@@ -133,6 +133,14 @@ export interface DailyActivity {
messageCount: number
}
/**
* 星期活跃度统计
*/
export interface WeekdayActivity {
weekday: number // 1-71=周一,7=周日
messageCount: number
}
/**
* 分析会话信息(用于会话列表展示)
*/