feat: 文件选择器 + 信息解析

This commit is contained in:
H3CoF6
2026-04-08 16:43:02 +08:00
Unverified
parent 96685490ac
commit 0ccd2016e7
4 changed files with 70 additions and 14 deletions
+9
View File
@@ -617,7 +617,16 @@ export const useApi = () => {
return `${base}/biz/proxy_image?${query.toString()}`
}
const pickSystemDirectory = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.title) query.set('title', params.title)
if (params && params.initial_dir) query.set('initial_dir', params.initial_dir)
const url = '/system/pick_directory' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
return {
pickSystemDirectory,
detectWechat,
detectCurrentAccount,
decryptDatabase,
+24 -14
View File
@@ -17,7 +17,6 @@
</div>
</div>
<!-- 导入说明 -->
<div class="bg-blue-50 border border-blue-100 rounded-2xl p-6 mb-8">
<h3 class="text-blue-800 font-bold mb-3 flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -26,9 +25,9 @@
标准目录结构要求
</h3>
<ul class="text-sm text-blue-700 space-y-2 list-disc list-inside opacity-90">
<li><strong>预期目标</strong>请选择形如 <strong>/output/wxid_xxxxx/</strong> 这一级目录</li>
<li><strong>databases/</strong> 目录存放扁平化的 .db 文件</li>
<li><strong>account.json</strong> 文件包含昵称wxid等信息</li>
<li><strong>resource/</strong> 目录存放解密后的资源文件可选</li>
<li><strong>account.json</strong> 文件系统会自动生成</li>
</ul>
</div>
@@ -119,10 +118,8 @@
</template>
<script setup>
import { ref } from 'vue'
import { useApi } from '~/composables/useApi'
const { importDecryptedPreview, importDecrypted } = useApi()
import {ref} from 'vue'
import {useApi} from '~/composables/useApi'
const importing = ref(false)
const importPreview = ref(null)
@@ -140,13 +137,15 @@ const resetImport = () => {
selectedImportPath.value = ''
}
const { importDecryptedPreview, importDecrypted, pickSystemDirectory } = useApi()
const handlePickDirectory = async () => {
let path = ''
if (isDesktopShell()) {
try {
const res = await window.wechatDesktop.chooseDirectory({
title: '选择解密数据所在目录'
title: '选择解密输出目录 (如: output/wxid_xxxxx)'
})
if (!res || res.canceled || !res.filePaths?.length) return
path = res.filePaths[0]
@@ -155,17 +154,28 @@ const handlePickDirectory = async () => {
return
}
} else {
path = window.prompt('请输入已解密目录的绝对路径:')
if (!path) return
try {
const res = await pickSystemDirectory({ title: '请选择解密输出目录 (需选到 wxid_xxx 层级)' })
if (!res || !res.path) return
path = res.path
} catch (e) {
console.error('唤起目录选择器失败:', e)
path = window.prompt('无法唤起选择器,请输入已解密目录的绝对路径:')
if (!path) return
}
}
if (path && !path.includes('wxid_')) {
const isOk = window.confirm(`你选择的目录为:\n${path}\n\n该目录似乎不符合 "wxid_xxxxx" 的格式。确定要继续吗?`)
if (!isOk) return
}
selectedImportPath.value = path
importError.value = ''
importPreview.value = null
try {
const res = await importDecryptedPreview({ import_path: path })
importPreview.value = res
importPreview.value = await importDecryptedPreview({import_path: path})
} catch (e) {
importError.value = e.message || '目录格式不正确,请确保包含 databases 目录和 account.json'
}
+2
View File
@@ -38,6 +38,7 @@ from .request_logging import log_server_errors_middleware
from .sns_stage_timing import add_sns_stage_timing_headers
from .wcdb_realtime import WCDB_REALTIME, shutdown as _wcdb_shutdown
from .routers.biz import router as _biz_router
from .routers.system import router as _system_router
app = FastAPI(
title="微信数据库解密工具",
@@ -100,6 +101,7 @@ app.include_router(_sns_router)
app.include_router(_sns_export_router)
app.include_router(_wrapped_router)
app.include_router(_biz_router)
app.include_router(_system_router)
class _SPAStaticFiles(StaticFiles):
+35
View File
@@ -0,0 +1,35 @@
from fastapi import APIRouter
from pydantic import BaseModel
import asyncio
from concurrent.futures import ThreadPoolExecutor
router = APIRouter()
def _open_folder_dialog(title: str, initial_dir: str) -> str:
# 延迟导入并放在独立线程运行,避免阻塞 FastAPI 主线程或发生 GUI 线程冲突
import tkinter as tk
from tkinter import filedialog
root = tk.Tk()
root.withdraw() # 隐藏主窗口
root.attributes('-topmost', True) # 确保弹窗在最前
folder_path = filedialog.askdirectory(
parent=root,
title=title,
initialdir=initial_dir
)
root.destroy()
return folder_path
@router.get("/api/system/pick_directory", summary="唤起本地原生目录选择器")
async def pick_directory(title: str = "请选择目录", initial_dir: str = ""):
loop = asyncio.get_running_loop()
with ThreadPoolExecutor() as pool:
# 在子线程中执行 GUI 操作
folder_path = await loop.run_in_executor(pool, _open_folder_dialog, title, initial_dir)
return {"path": folder_path}