From ec696d51870cbe036be8c99689899fa042105577 Mon Sep 17 00:00:00 2001 From: chenxing Date: Thu, 8 Oct 2026 17:45:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(header):=20=E6=90=9C=E7=B4=A2=E6=A1=86?= =?UTF-8?q?=E6=94=B9=E5=B1=85=E4=B8=AD=E5=91=BD=E4=BB=A4=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=EF=BC=8C=E5=BA=94=E7=94=A8=E5=88=97=E8=A1=A8=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E4=B8=8E=20loading=20=E4=B8=8E=20AppList=20=E5=85=B1=E4=BA=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - header.scss: 删旧 .search-box 与 fade1/search-animation/icon-animation 动画段, 新增 app-cmdk-* 面板样式。样式段必须放顶层——面板 Teleport 到 body,嵌进 .default-header 会生成后代选择器而匹配不到 - Header.vue: 搜索 li 收敛为单图标 + openSearch;模板末尾追加 命令面板,支持实时过滤 / ↑↓ 键盘导航 / Enter 打开 / Esc 关闭 / 空结果态; 删 watch(isSearching) 与空函数 showSeachBox;挂/卸 window keydown - store/index.ts: 新增 appListLoading 字段与 setAppListLoading - utils/bschLinkPage.ts: 抽出 openAppPage(app, ifameAppIdArr),与点击应用图标同一路径 - AppList.vue: reloadAppData 写 store(数据 + loading,try/finally); linkPage(item) 委托 openAppPage 硬约束保持不动:watch(searchAppText) -> mittBus.emit("searchChange")(背景应用网格靠它过滤)、 isSearchIconShow(route.name === "apps")。 --- src/assets/styles/header.scss | 219 +++++++++++++++++++++------------- src/components/Header.vue | 150 +++++++++++++++++++---- src/store/index.ts | 6 + src/utils/bschLinkPage.ts | 15 +++ src/views/AppList.vue | 33 +++-- 5 files changed, 298 insertions(+), 125 deletions(-) diff --git a/src/assets/styles/header.scss b/src/assets/styles/header.scss index d432742..98f3eac 100644 --- a/src/assets/styles/header.scss +++ b/src/assets/styles/header.scss @@ -58,97 +58,148 @@ height: 20px; } - .search-box { - height: 36px; - margin-right: 2px; - background: #fff; - border-radius: 34px; - display: flex; - overflow: hidden; - align-items: center; - justify-content: space-between; - user-select: none; - input { - flex: 1; - outline: none; - border: none; - margin-left: 15px; - width: 210px; - } - .icon { - margin: 0 8px; - // animation: icon-turn 0.3s linear; - } - } + } +} - @keyframes fade-animation { - 0% { - display: none; - } +/* ===== Header 搜索命令面板(2026-10-08 定稿 B 方案)===== + ⚠️ 必须放在顶层(不可嵌进 .default-header 内):面板由 渲染到 body 下, + 若嵌在 .default-header 内会生成 `.default-header .app-cmdk-layer`,匹配不到 body 下的元素。 + 类名统一 app-cmdk- 前缀(本文件是全局样式,非 scoped)。 */ +.app-cmdk-layer { + position: fixed; + inset: 0; + z-index: 2000; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s linear; - 75% { - display: none; - } - 100% { - display: flex; - } - } - .fade1-enter-active, - .fade1-enter-to { - animation: fade-animation 0.5s; - } + &.is-open { + opacity: 1; + pointer-events: auto; + } +} +.app-cmdk-mask { + position: absolute; + inset: 0; + background: rgba(18, 26, 40, 0.34); + backdrop-filter: blur(10px) saturate(125%); + -webkit-backdrop-filter: blur(10px) saturate(125%); +} +.app-cmdk-panel { + position: absolute; + left: 50%; + top: 78px; + width: min(620px, calc(100% - 48px)); + background: #fff; + border-radius: 16px; + overflow: hidden; + box-shadow: 0 26px 68px rgba(18, 30, 54, 0.3), 0 2px 8px rgba(18, 30, 54, 0.1); + opacity: 0; + transform: translate(-50%, -14px) scale(0.965); + transition: opacity 0.26s linear, transform 0.48s cubic-bezier(0.2, 0.9, 0.24, 1); - .fade1-leave-from, - .fade1-leave-active { - display: none !important; - } + .app-cmdk-layer.is-open & { + opacity: 1; + transform: translate(-50%, 0) scale(1); + } +} +.app-cmdk-hd { + display: flex; + align-items: center; + gap: 12px; + padding: 0 18px; + height: 56px; + border-bottom: 1px solid #f0f3f8; + color: #98a2b0; - @keyframes search-animation { - 0% { - display: none; - width: 0; - } - 20% { - display: none; - width: 0; - } - 21% { - width: 0; - display: block; - } - 100% { - width: 210px; - } - } - - @keyframes icon-animation { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } - } - - .search-enter-active { - & input { - animation: search-animation 0.3s ease-in-out; - } - & .icon { - animation: icon-animation 0.4s; - } - } - - .search-leave-active { - & input { - animation: search-animation 0.35s ease-out reverse; - } - & .icon { - animation: icon-animation 0.4s reverse; - } + input { + flex: 1; + min-width: 0; + outline: none; + border: 0; + color: #252b35; + font-size: 15px; + background: none; + &::placeholder { + color: #aab3c0; } } } +.app-cmdk-kbd { + font-size: 11px; + color: #8b95a3; + background: #f2f5fa; + border: 1px solid #e4e9f2; + border-radius: 5px; + padding: 2px 6px; + font-family: ui-monospace, Menlo, monospace; +} +.app-cmdk-list { + padding: 8px; + max-height: min(58vh, 440px); + overflow: auto; +} +.app-cmdk-item { + display: flex; + align-items: center; + gap: 12px; + padding: 9px 12px; + border-radius: 10px; + cursor: pointer; + opacity: 0; + transform: translateY(9px); + transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), + transform 0.42s cubic-bezier(0.4, 0, 0.2, 1), background 0.16s; + + .app-cmdk-layer.is-open & { + opacity: 1; + transform: none; + transition-delay: calc(0.14s + var(--i) * 0.035s), calc(0.14s + var(--i) * 0.035s), 0s; + } + &.is-hl, + &:hover { + background: #f3f6fc; + } +} +.app-cmdk-list.is-instant .app-cmdk-item { + transition-duration: 0.15s; + transition-delay: 0s; +} +.app-cmdk-img { + width: 34px; + height: 34px; + border-radius: 9px; + flex-shrink: 0; +} +.app-cmdk-name { + flex: 1; + min-width: 0; + font-size: 13.5px; + color: #252b35; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.app-cmdk-empty { + display: none; + padding: 28px 18px 32px; + text-align: center; + color: #a8b1be; + font-size: 13px; + &.is-show { + display: block; + } +} +.app-cmdk-ft { + display: flex; + gap: 16px; + padding: 10px 18px; + border-top: 1px solid #f0f3f8; + font-size: 11.5px; + color: #98a2b0; + background: #fbfcfe; +} .role-info { height: 22px; diff --git a/src/components/Header.vue b/src/components/Header.vue index 46156db..a95c48e 100644 --- a/src/components/Header.vue +++ b/src/components/Header.vue @@ -8,17 +8,9 @@
  • - -
    - -
    -
    - - - +
    + +
  • + +
    +
    +
    +
    + + + Esc +
    +
    +
    + + + {{ item.name }} +
    +
    +
    + {{ store.appListLoading ? "正在加载应用…" : "未找到匹配的应用" }} +
    +
    + ↑↓ 选择↵ 打开Esc 关闭 +
    +
    +
    +