用户三点修订:
1. 面板已是模态框,去掉背景应用列表的同步过滤(解开 mittBus.searchChange 整条链)
2. 结果右侧灰色小字显示应用类型(来源 BoaAppType.name,与 /apps 分组标题同源)
3. 类型呈现暂按方案 A(右侧灰字);方案 B(类型分组)对照稿见 design/07
- Header.vue: watch(searchAppText) 不再 emit("searchChange");cmdkResult 由
getFlattenAppList(纯 flatten,会丢类型名)改为遍历 curUserAppList 分组展开并挂
appTypeName;模板加 .app-cmdk-type
- AppList.vue: 删 searchAppText / searchAppList / isSearch 与模板搜索态分支、
.no-search-box 样式、孤儿导入 flatten(lodash) 与 requireImg;删 mittBus 监听
- utils/mitt.ts: 删 searchChange 事件类型
- assets/styles/header.scss: 加 .app-cmdk-type 样式
- harness-main.ts: mock curUserAppList 补齐 BoaAppType 分组结构(id + name),
原 mock 丢了 name 导致类型名取不到
注:本仓工作区另有并行会话 WIP(StatsBarModal 迁入 Settings / 主题预览),
本提交只含本案 5 个文件与 hunks,未触碰对方改动。
This commit is contained in:
@@ -181,6 +181,14 @@
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* 应用类型:右侧灰字(来源 BoaAppType.name,与 /apps 分组标题同源) */
|
||||
.app-cmdk-type {
|
||||
flex-shrink: 0;
|
||||
font-size: 11.5px;
|
||||
color: #98a2b0;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.app-cmdk-empty {
|
||||
display: none;
|
||||
padding: 28px 18px 32px;
|
||||
|
||||
@@ -208,6 +208,7 @@
|
||||
/>
|
||||
<img v-else :src="$utils.appIconPath(item.icon)" class="app-cmdk-img" alt="" />
|
||||
<span class="app-cmdk-name">{{ item.name }}</span>
|
||||
<span class="app-cmdk-type" v-if="item.appTypeName">{{ item.appTypeName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-cmdk-empty" :class="{ 'is-show': !cmdkResult.length }">
|
||||
@@ -409,9 +410,10 @@ onUnmounted(() => {
|
||||
window.removeEventListener("keydown", onCmdkKeydown);
|
||||
closeMsgWs();
|
||||
});
|
||||
watch(searchAppText, val => {
|
||||
// 面板已是模态框,输入只驱动面板自身过滤;
|
||||
// 不再 emit("searchChange") —— 背景应用网格保持原样(2026-10-08 用户修订 1)
|
||||
watch(searchAppText, () => {
|
||||
cmdkStagger.value = false; // 输入过滤时不重播逐条入场
|
||||
mittBus.emit("searchChange", val);
|
||||
});
|
||||
// ===== 搜索命令面板(2026-10-08 定稿 B 方案)=====
|
||||
const refCmdkList = ref<any>();
|
||||
@@ -425,12 +427,21 @@ function vectorIconOf(item: any): string {
|
||||
return appiconKeyOf(item?.name || "");
|
||||
}
|
||||
|
||||
/** 结果取自 store(由 AppList.reloadAppData 写入),与背景应用网格同源同口径 */
|
||||
/** 结果取自 store.curUserAppList(写入方 = AppList.reloadAppData)。
|
||||
* 它是 BoaAppType 分组结构 [{ id, name, apps }],name 就是应用类型名;
|
||||
* 展开时把类型名挂到每条上供右侧灰字展示。
|
||||
* 注意别用 store.getFlattenAppList —— 它是纯 flatten,会把类型名丢掉 */
|
||||
const cmdkResult = computed(() => {
|
||||
const all = store.getFlattenAppList;
|
||||
const q = (searchAppText.value || "").trim();
|
||||
if (!q) return all;
|
||||
return all.filter((x: any) => x.name.indexOf(q) !== -1);
|
||||
const out: any[] = [];
|
||||
(store.curUserAppList || []).forEach((group: any) => {
|
||||
(group.apps || []).forEach((app: any) => {
|
||||
if (!q || (app.name || "").indexOf(q) !== -1) {
|
||||
out.push({ ...app, appTypeName: group.name });
|
||||
}
|
||||
});
|
||||
});
|
||||
return out;
|
||||
});
|
||||
|
||||
function openSearch() {
|
||||
|
||||
+7
-1
@@ -78,12 +78,18 @@ function applyMockState() {
|
||||
],
|
||||
curUserAppList: [
|
||||
{
|
||||
id: 1,
|
||||
name: "常用应用",
|
||||
apps: [
|
||||
{ appKey: "100019", appCode: "flow", name: "流程中心" },
|
||||
{ appKey: "100617", appCode: "notice", name: "通知公告" },
|
||||
{ appKey: "100611", appCode: "leave", name: "请假" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "教学应用",
|
||||
apps: [{ appKey: "100611", appCode: "leave", name: "请假" }],
|
||||
},
|
||||
],
|
||||
unreadCount: 5,
|
||||
curUserSettingInfo: {
|
||||
|
||||
@@ -11,7 +11,6 @@ type MittType<T = any> = {
|
||||
openBschIframePage: IframePageParmas; //打开iframe页面
|
||||
showLightAppModal: LightAppModal; //打开lightapp模态框
|
||||
searchReset: void; //重置搜索框
|
||||
searchChange: string | null;
|
||||
};
|
||||
|
||||
// 类型
|
||||
|
||||
+2
-58
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="app-list-wrapper base-mask">
|
||||
<vuescroll :ops="scrollOps">
|
||||
<template v-if="!isSearch" v-for="(item, index) in appList">
|
||||
<template v-for="(item, index) in appList">
|
||||
<h3 class="app-type-box">{{ item.name }}</h3>
|
||||
<div
|
||||
:class="{
|
||||
@@ -39,42 +39,6 @@
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!!isSearch">
|
||||
<div class="app-list-box" v-if="searchAppList.length > 0">
|
||||
<template v-for="item in searchAppList">
|
||||
<ContextMenu
|
||||
class="app-container app-container__mask"
|
||||
:title="item.name"
|
||||
@on-change="onMenuChange"
|
||||
@select="onMenuSel"
|
||||
:menu="[
|
||||
{
|
||||
label: '添加到桌面',
|
||||
appId: item.id,
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
label: '新窗口打开',
|
||||
appId: item.id,
|
||||
url: item.webAddr,
|
||||
id: '2',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a @click="linkPage(item)">
|
||||
<AppIcon v-if="vectorIconOf(item)" :name="vectorIconOf(item)" scene="tile"
|
||||
:tint="item.iconColor" />
|
||||
<img v-else :src="requireImg('defappicon.png')"
|
||||
v-real-img="$utils.appIconPath(item.icon)" />
|
||||
<div class="app-name">{{ item.name }}</div>
|
||||
</a>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
</div>
|
||||
<div class="no-search-box" v-else>
|
||||
<img :src="requireImg('no-search-bg.png')" alt="" />
|
||||
</div>
|
||||
</template>
|
||||
</vuescroll>
|
||||
</div>
|
||||
</template>
|
||||
@@ -86,11 +50,9 @@ import vuescroll from "vuescroll";
|
||||
import { ref, onMounted, onUnmounted, computed, watch } from "vue";
|
||||
import BaseApi from "@/api/BaseApi";
|
||||
import { useDefaultStore } from "@/store";
|
||||
import { requireImg } from "@/utils/hook/useImg";
|
||||
import mittBus from "@/utils/mitt";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { BschLoadTypeEnum, DesktopAppTypeEnum, LinkOpenTypeEnum } from "@/types";
|
||||
import { flatten } from "lodash";
|
||||
import UserSettingApi from "@/api/UserSettingApi";
|
||||
import { Message } from "view-ui-plus";
|
||||
import { bschLinkPage, openAppPage } from "@/utils/bschLinkPage";
|
||||
@@ -120,15 +82,8 @@ const router = useRouter();
|
||||
const store = useDefaultStore();
|
||||
const { $conf, $utils } = useCurrentInstance();
|
||||
|
||||
const searchAppText = ref<string | null>(null),
|
||||
appList = ref<any[]>([]);
|
||||
const appList = ref<any[]>([]);
|
||||
|
||||
const searchAppList = computed(() => {
|
||||
const allAppList = flatten(appList.value.map(x => x.apps));
|
||||
return allAppList.filter(x => x.name.indexOf(searchAppText.value) !== -1);
|
||||
});
|
||||
|
||||
const isSearch = computed(() => !!searchAppText.value);
|
||||
const compMenuId = computed(() => {
|
||||
let menuList = store.curUserMenuList.find(x => x.url == route.path);
|
||||
return menuList ? menuList.id : -1;
|
||||
@@ -137,13 +92,9 @@ const compMenuId = computed(() => {
|
||||
onMounted(() => {
|
||||
reloadAppData();
|
||||
mittBus.emit("searchReset");
|
||||
mittBus.on("searchChange", val => {
|
||||
searchAppText.value = val;
|
||||
});
|
||||
});
|
||||
onUnmounted(() => {
|
||||
mittBus.emit("searchReset");
|
||||
mittBus.off("searchChange");
|
||||
});
|
||||
|
||||
watch(() => store.curRoleUid, reloadAppData);
|
||||
@@ -237,11 +188,4 @@ function linkPage(item: any) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
.no-search-box {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
top: -50%;
|
||||
-webkit-transform: translateY(40%);
|
||||
transform: translateY(40%);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user