细节调整及优化(26.8.3)

This commit is contained in:
zhongluofeng
2026-08-21 17:25:47 +08:00
parent 0a19b4b38a
commit 4dd60f42a1
72 changed files with 4779 additions and 1395 deletions
+63 -29
View File
@@ -34,6 +34,8 @@ const explorerDir = ref('')
const actionMode = ref<'none' | 'extract' | 'rename' | 'delete'>('none')
// 面板模式下的窗口高度(比搜索态更高,容纳列表+输入)
const ACTION_HEIGHT = 620
// 跳过下一次 query 变更触发的防抖搜索(面板 show/hide 重置输入时使用,避免重复搜索)
let skipNextSearch = false
// 批量解压进度事件负载:specta 不导出事件类型,需在此与 Rust 端 actions.rs 同名结构体保持同步
interface ExtractProgress {
@@ -190,7 +192,8 @@ async function doSearch() {
const seq = ++searchSeq
const q = query.value.trim()
if (!q) {
// 空查询:当前目录文件操作(若检测到 Explorer 目录)+ 命令快捷入口 + 历史
// 空查询:当前目录文件操作(若检测到 Explorer 目录)+ 历史置顶 + 系统相关条目
// (程序相关设置不参与默认展示;所有 Provider 空查询零 IPC,首屏即时)
const items = await aggregateSearch('')
if (seq !== searchSeq) return // 过期请求丢弃
const dirItems = getExplorerActions()
@@ -227,9 +230,14 @@ async function doSearch() {
// 防抖搜索
watch(query, () => {
// 面板模式下输入搜索词:先退出面板,恢复窗口高度
// show/hide 重置输入时跳过(由事件处理器显式搜索/清空)
if (skipNextSearch) {
skipNextSearch = false
return
}
// 面板模式下输入搜索词:先退出面板,恢复窗口高度(不立即搜索,由下方防抖统一触发)
if (actionMode.value !== 'none') {
exitMode()
exitMode(true)
}
if (searchTimer) clearTimeout(searchTimer)
collapseSubActions()
@@ -309,14 +317,15 @@ async function enterDeleteMode() {
document.querySelector<HTMLInputElement>('.qp-fa-delete-filter')?.focus()
}
async function exitMode() {
async function exitMode(skipSearch = false) {
if (actionMode.value === 'none') return
actionMode.value = 'none'
extractReset()
renameReset()
deleteReset()
await setWindowHeight(420)
await doSearch()
// watch(query) 路径跳过:稍后防抖会用新 query 搜索,避免双重搜索
if (!skipSearch) await doSearch()
}
function extractReset() {
@@ -804,7 +813,7 @@ function onSubActionHover(idx: number) {
/** 应用类条目(含历史中的应用)不显示 subtitle(路径),让布局更紧凑 */
const isAppLike = (item: QPItem) => !!item.iconPath
const groupIcon = (group: string) => {
if (group === '命令') return Command
if (group === '设置') return Settings
if (group === '计算') return Calculator
if (group === '网页') return Globe
if (group === '系统') return Lock
@@ -818,7 +827,7 @@ const groupIcon = (group: string) => {
/** 类型 badge 颜色映射(柔和色块风格,跟随亮/暗主题) */
const GROUP_COLORS: Record<string, string> = {
命令: 'bg-indigo-500/15 text-indigo-600 dark:text-indigo-400',
设置: 'bg-indigo-500/15 text-indigo-600 dark:text-indigo-400',
计算: 'bg-amber-500/15 text-amber-600 dark:text-amber-400',
网页: 'bg-rose-500/15 text-rose-600 dark:text-rose-400',
系统: 'bg-slate-500/15 text-slate-600 dark:text-slate-400',
@@ -910,6 +919,16 @@ async function applyTheme() {
}
}
/** 检查文件索引状态(后台自动构建完成后启用文件搜索)。首次调用需打开 SQLite,宜后台执行 */
async function refreshIndexReady() {
try {
const stats = await commands.quickpanelFileIndexStats()
setFileIndexReady((stats?.total ?? 0) > 0)
} catch {
/* 索引未初始化,忽略 */
}
}
onMounted(async () => {
await applyTheme()
@@ -929,10 +948,12 @@ onMounted(async () => {
window.addEventListener('storage', onStorage)
unlistenFns.push(() => window.removeEventListener('storage', onStorage))
// 监听弹窗显示事件:同步主题 + 更新 Explorer 当前目录 + 清空输入 + 加载初始结果
// 监听弹窗显示事件:更新 Explorer 当前目录 + 清空输入 + 加载初始结果
// 主题同步与索引状态检查均不阻塞首屏结果(索引首次查询需打开 SQLite)
unlistenFns.push(await listen<{ dir: string | null }>(EVENTS.quickpanelShow, async (e) => {
await applyTheme()
explorerDir.value = e.payload?.dir ?? ''
void applyTheme()
void refreshIndexReady()
// 若上次关闭时停留在面板模式,恢复搜索态和窗口高度
if (actionMode.value !== 'none') {
actionMode.value = 'none'
@@ -941,7 +962,15 @@ onMounted(async () => {
deleteReset()
await setWindowHeight(420)
}
query.value = ''
// 丢弃残留的防抖搜索;重置输入不触发新搜索(下方显式搜索)
if (searchTimer) {
clearTimeout(searchTimer)
searchTimer = null
}
if (query.value !== '') {
skipNextSearch = true
query.value = ''
}
await doSearch()
await nextTick()
inputRef.value?.focus()
@@ -953,27 +982,32 @@ onMounted(async () => {
}))
unlistenFns.push(await listen(EVENTS.quickpanelHide, () => {
query.value = ''
// 取消未触发的防抖搜索;重置输入时跳过搜索(面板已隐藏,避免无效搜索)
if (searchTimer) {
clearTimeout(searchTimer)
searchTimer = null
}
if (query.value !== '') {
skipNextSearch = true
query.value = ''
}
results.value = []
}))
// 初始加载(空查询显示快捷入口)
// 独立窗口需自行检查文件索引状态(主窗口的 setFileIndexReady 不共享到此上下文)
try {
const stats = await commands.quickpanelFileIndexStats()
setFileIndexReady((stats?.total ?? 0) > 0)
} catch {
/* 索引未初始化,忽略 */
}
await doSearch()
await nextTick()
inputRef.value?.focus()
// 先显示窗口(兜底重建路径依赖此调用才真正显示):让面板尽快出现,
// 后续数据加载(索引统计/初始搜索)不阻塞显示,避免 dev 下首屏渲染慢导致"唤不出"。
try {
await commands.quickpanelShowWindow()
} catch {
/* 忽略 */
}
// 初始加载(空查询显示历史置顶 + 系统条目;Provider 空查询零 IPC,即时渲染)
// 独立窗口需自行检查文件索引状态(主窗口的 setFileIndexReady 不共享到此上下文)
void refreshIndexReady()
await doSearch()
await nextTick()
inputRef.value?.focus()
})
onUnmounted(() => {
@@ -992,7 +1026,7 @@ onUnmounted(() => {
ref="inputRef"
v-model="query"
class="qp-input"
placeholder="搜索命令、应用、文件…"
placeholder="搜索应用、文件、系统命令…"
spellcheck="false"
autocomplete="off"
/>
@@ -1007,7 +1041,7 @@ onUnmounted(() => {
<!-- 批量解压面板 -->
<template v-if="actionMode === 'extract'">
<div class="qp-action-head">
<button class="qp-back-btn" title="返回搜索" @click="exitMode"><ChevronLeft class="size-4" /></button>
<button class="qp-back-btn" title="返回搜索" @click="exitMode()"><ChevronLeft class="size-4" /></button>
<div class="min-w-0 flex-1">
<p class="qp-action-title">批量解压</p>
<p class="qp-action-dir truncate" :title="explorerDir">{{ explorerDir }}</p>
@@ -1108,7 +1142,7 @@ onUnmounted(() => {
<!-- 批量重命名面板 -->
<template v-else-if="actionMode === 'rename'">
<div class="qp-action-head">
<button class="qp-back-btn" title="返回搜索" @click="exitMode"><ChevronLeft class="size-4" /></button>
<button class="qp-back-btn" title="返回搜索" @click="exitMode()"><ChevronLeft class="size-4" /></button>
<div class="min-w-0 flex-1">
<p class="qp-action-title">批量重命名</p>
<p class="qp-action-dir truncate" :title="explorerDir">{{ explorerDir }}</p>
@@ -1215,7 +1249,7 @@ onUnmounted(() => {
<!-- 批量删除面板 -->
<template v-else-if="actionMode === 'delete'">
<div class="qp-action-head">
<button class="qp-back-btn" title="返回搜索" @click="exitMode"><ChevronLeft class="size-4" /></button>
<button class="qp-back-btn" title="返回搜索" @click="exitMode()"><ChevronLeft class="size-4" /></button>
<div class="min-w-0 flex-1">
<p class="qp-action-title">批量删除</p>
<p class="qp-action-dir truncate" :title="explorerDir">{{ explorerDir }}</p>
@@ -1324,7 +1358,7 @@ onUnmounted(() => {
<div v-else-if="!hasResults()" class="qp-empty">
<Command class="size-10 mb-3 opacity-40" />
<p class="text-sm">输入关键词开始搜索</p>
<p class="text-xs mt-1 opacity-60">命令 · 计算 · 系统 · 网页</p>
<p class="text-xs mt-1 opacity-60">设置 · 应用 · 文件 · 系统 · 网页</p>
</div>
<template v-else>
<!-- 目录操作批量解压/重命名/删除置顶可键盘导航 -->
@@ -1434,7 +1468,7 @@ onUnmounted(() => {
</AccordionItem>
</Accordion>
<!-- 其他结果命令/应用/系统等可键盘导航索引偏移 dirActionItems.length + historyItems.length -->
<!-- 其他结果设置/应用/系统等可键盘导航索引偏移 dirActionItems.length + historyItems.length -->
<template v-for="(item, idx) in otherItems" :key="item.id">
<div
class="qp-item"