feat: 解析器重构

This commit is contained in:
digua
2025-12-01 17:22:34 +08:00
parent e7de9cc57d
commit b30abc336d
26 changed files with 2366 additions and 676 deletions
+67 -4
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { FileDropZone } from '@/components/UI'
interface FileInfo {
@@ -8,8 +8,21 @@ interface FileInfo {
name: string
format: string
messageCount: number
fileSize?: number // 文件大小(字节)
status: 'pending' | 'parsed' | 'error'
error?: string
// 解析进度(用于大文件)
progress?: number
progressMessage?: string
}
// 格式化文件大小
function formatFileSize(bytes?: number): string {
if (!bytes) return ''
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`
}
interface MergeConflict {
@@ -35,6 +48,34 @@ const mergeProgress = ref(0)
const currentStep = ref<'select' | 'conflict' | 'done'>('select')
const outputFilePath = ref('')
// 解析进度监听
let unsubscribeProgress: (() => void) | null = null
onMounted(() => {
// 监听解析进度
unsubscribeProgress = window.mergeApi.onParseProgress(({ filePath, progress }) => {
const file = files.value.find((f) => f.path === filePath)
if (file && file.status === 'pending') {
// 使用 percentage 而不是 progress(更准确)
file.progress = progress.percentage ?? progress.progress ?? 0
// 构建更详细的进度消息
if (progress.messagesProcessed && progress.messagesProcessed > 0) {
file.progressMessage = `已处理 ${progress.messagesProcessed.toLocaleString()} 条消息`
} else if (progress.message) {
file.progressMessage = progress.message
} else {
file.progressMessage = '正在解析...'
}
}
})
})
onUnmounted(() => {
if (unsubscribeProgress) {
unsubscribeProgress()
}
})
// 计算总消息数
const totalMessages = computed(() => files.value.reduce((sum, f) => sum + (f.messageCount || 0), 0))
@@ -66,6 +107,7 @@ async function addFilesByPaths(filePaths: string[]) {
if (file) {
file.format = info.format
file.messageCount = info.messageCount
file.fileSize = info.fileSize
file.status = 'parsed'
// 设置默认输出名称(取第一个文件的群名)
@@ -293,13 +335,34 @@ const file2Name = computed(() => files.value[1]?.name || '文件 2')
<!-- 文件信息 -->
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-gray-900 dark:text-white">{{ file.name }}</p>
<div class="flex items-center gap-2">
<p class="truncate text-sm font-medium text-gray-900 dark:text-white">{{ file.name }}</p>
<span
v-if="file.fileSize && file.fileSize > 50 * 1024 * 1024"
class="shrink-0 rounded bg-amber-100 px-1.5 py-0.5 text-xs text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"
>
大文件
</span>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">
<span :class="getStatusColor(file.status)">
{{ file.status === 'pending' ? '解析中...' : file.status === 'error' ? file.error : file.format }}
{{
file.status === 'pending'
? file.progressMessage || '解析中...'
: file.status === 'error'
? file.error
: file.format
}}
</span>
<template v-if="file.status === 'parsed'">· {{ file.messageCount.toLocaleString() }} 条消息</template>
<template v-if="file.status === 'parsed'">
· {{ file.messageCount.toLocaleString() }} 条消息
<span v-if="file.fileSize" class="text-gray-400">· {{ formatFileSize(file.fileSize) }}</span>
</template>
</p>
<!-- 解析进度条大文件时显示 -->
<div v-if="file.status === 'pending' && file.progress !== undefined" class="mt-1.5">
<UProgress :model-value="file.progress" size="xs" />
</div>
</div>
<!-- 删除按钮 -->
+24 -4
View File
@@ -74,12 +74,14 @@ function openTutorial(type: 'wechat' | 'qq') {
function getProgressText(): string {
if (!importProgress.value) return ''
switch (importProgress.value.stage) {
case 'detecting':
return '正在检测格式...'
case 'reading':
return '正在读取...'
return '正在读取文件...'
case 'parsing':
return '解析器解析中...'
return '正在解析消息...'
case 'saving':
return '写入本地数据库...'
return '正在写入数据库...'
case 'done':
return '导入完成'
case 'error':
@@ -88,6 +90,24 @@ function getProgressText(): string {
return ''
}
}
function getProgressDetail(): string {
if (!importProgress.value) return ''
const { messagesProcessed, totalBytes, bytesRead } = importProgress.value
if (messagesProcessed && messagesProcessed > 0) {
return `已处理 ${messagesProcessed.toLocaleString()} 条消息`
}
if (totalBytes && bytesRead) {
const percent = Math.round((bytesRead / totalBytes) * 100)
const mbRead = (bytesRead / 1024 / 1024).toFixed(1)
const mbTotal = (totalBytes / 1024 / 1024).toFixed(1)
return `${mbRead} MB / ${mbTotal} MB (${percent}%)`
}
return importProgress.value.message || ''
}
</script>
<template>
@@ -165,7 +185,7 @@ function getProgressText(): string {
<UProgress v-model="importProgress.progress" size="md" />
</div>
<p class="mt-3 text-sm text-gray-500 dark:text-gray-400">
{{ importProgress.message }}
{{ getProgressDetail() }}
</p>
</template>
<template v-else>
+2 -2
View File
@@ -71,9 +71,9 @@ export const useChatStore = defineStore(
// 初始化状态
importProgress.value = {
stage: 'reading',
stage: 'detecting',
progress: 0,
message: '',
message: '准备导入...',
}
// 进度队列控制
+6 -1
View File
@@ -355,9 +355,13 @@ export interface MemberNameHistory {
* 导入进度回调
*/
export interface ImportProgress {
stage: 'reading' | 'parsing' | 'saving' | 'done' | 'error'
stage: 'detecting' | 'reading' | 'parsing' | 'saving' | 'done' | 'error'
progress: number // 0-100
message?: string
// 流式解析额外字段
bytesRead?: number
totalBytes?: number
messagesProcessed?: number
}
/**
@@ -756,6 +760,7 @@ export interface FileParseInfo {
platform: string // 平台
messageCount: number // 消息数量
memberCount: number // 成员数量
fileSize?: number // 文件大小(字节)
}
/**