|
|
@@ -32,11 +32,33 @@
|
|
|
<div class="left-sidebar-wrap" v-if="currentView !== 'list-mode'">
|
|
|
<TechTabs v-model="activeLeftTab" type="underline" @tab-click="handleTabClick">
|
|
|
<TechTabPane label="总览" name="overview" class="menu-scroll-view" :loading="menuData.length === 0">
|
|
|
- <MenuItem theme="tech" v-for="item in menuData" :key="item.id" :node="item" :level="0"
|
|
|
+ <div class="menu-search">
|
|
|
+ <input
|
|
|
+ v-model="menuQueries.overview"
|
|
|
+ class="menu-search-input"
|
|
|
+ placeholder="请输入路口名"
|
|
|
+ @compositionstart="menuSearchComposing = true"
|
|
|
+ @compositionend="menuSearchComposing = false"
|
|
|
+ />
|
|
|
+ <i v-if="menuQueries.overview" class="menu-search-clear" @click="menuQueries.overview = ''">✕</i>
|
|
|
+ </div>
|
|
|
+ <div v-if="menuQueries.overview && !menuSearchComposing && filteredOverviewMenu.length === 0" class="menu-search-empty">无匹配路口</div>
|
|
|
+ <MenuItem theme="tech" v-for="item in filteredOverviewMenu" :key="item.id" :node="item" :level="0"
|
|
|
@node-click="handleMenuClick" @folder-click="handleFolderClick"/>
|
|
|
</TechTabPane>
|
|
|
<TechTabPane label="路口" name="crossing" class="menu-scroll-view" :loading="menuData.length === 0">
|
|
|
- <MenuItem theme="tech" v-for="item in menuData" :key="item.id" :node="item" :level="0"
|
|
|
+ <div class="menu-search">
|
|
|
+ <input
|
|
|
+ v-model="menuQueries.crossing"
|
|
|
+ class="menu-search-input"
|
|
|
+ placeholder="请输入路口名"
|
|
|
+ @compositionstart="menuSearchComposing = true"
|
|
|
+ @compositionend="menuSearchComposing = false"
|
|
|
+ />
|
|
|
+ <i v-if="menuQueries.crossing" class="menu-search-clear" @click="menuQueries.crossing = ''">✕</i>
|
|
|
+ </div>
|
|
|
+ <div v-if="menuQueries.crossing && !menuSearchComposing && filteredCrossingMenu.length === 0" class="menu-search-empty">无匹配路口</div>
|
|
|
+ <MenuItem theme="tech" v-for="item in filteredCrossingMenu" :key="item.id" :node="item" :level="0"
|
|
|
@node-click="handleMenuClick" @folder-click="handleFolderClick" />
|
|
|
</TechTabPane>
|
|
|
<TechTabPane label="干线" name="trunkLine" class="menu-scroll-view" :loading="trunkLineMenuData.length === 0">
|
|
|
@@ -163,8 +185,19 @@ export default {
|
|
|
// 在线状态 & 设备状态数据
|
|
|
onlineStatusData: null,
|
|
|
deviceFaultData: null,
|
|
|
+ // 左侧菜单按路口名搜索:每个 tab 独立 query;composing 期间不触发过滤
|
|
|
+ menuQueries: { overview: '', crossing: '' },
|
|
|
+ menuSearchComposing: false,
|
|
|
};
|
|
|
},
|
|
|
+ computed: {
|
|
|
+ filteredOverviewMenu() {
|
|
|
+ return this.pruneMenu(this.menuData, this.menuQueries.overview);
|
|
|
+ },
|
|
|
+ filteredCrossingMenu() {
|
|
|
+ return this.pruneMenu(this.menuData, this.menuQueries.crossing);
|
|
|
+ },
|
|
|
+ },
|
|
|
watch: {
|
|
|
// 监听路由参数变化(解决多次从首页点击不同数据跳转过来,页面不刷新的问题)
|
|
|
'$route.query': {
|
|
|
@@ -202,6 +235,28 @@ export default {
|
|
|
|
|
|
},
|
|
|
methods: {
|
|
|
+ /**
|
|
|
+ * 按 query 递归 prune 菜单树:仅保留命中叶子.label.includes(q) 的整条祖先链,
|
|
|
+ * 给保留的中间节点打 forceExpand 标记(MenuItem 的 isExpanded computed 会读它)。
|
|
|
+ * composing 期间(中文输入未提交)不过滤,避免抖动。
|
|
|
+ */
|
|
|
+ pruneMenu(nodes, query) {
|
|
|
+ const q = (query || '').trim().toLowerCase();
|
|
|
+ if (!q || this.menuSearchComposing) return nodes;
|
|
|
+ const walk = (list) => {
|
|
|
+ const out = [];
|
|
|
+ for (const n of list) {
|
|
|
+ if (n.children && n.children.length) {
|
|
|
+ const kept = walk(n.children);
|
|
|
+ if (kept.length) out.push({ ...n, children: kept, forceExpand: true });
|
|
|
+ } else if ((n.label || '').toLowerCase().includes(q)) {
|
|
|
+ out.push(n);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ };
|
|
|
+ return walk(nodes);
|
|
|
+ },
|
|
|
// 处理地图鼠标滑入事件
|
|
|
handleMapCrossingMouseover(mapData, lnglat, pixel) {
|
|
|
console.log('父组件接收到了地图路口鼠标滑入事件:', mapData);
|
|
|
@@ -775,6 +830,57 @@ export default {
|
|
|
}
|
|
|
</script>
|
|
|
<style scoped>
|
|
|
+.menu-search {
|
|
|
+ position: relative;
|
|
|
+ padding: 8px 10px 6px;
|
|
|
+ background: #05142e;
|
|
|
+}
|
|
|
+.menu-search-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 32px;
|
|
|
+ padding: 0 28px 0 10px;
|
|
|
+ background: rgba(5, 22, 45, 0.9);
|
|
|
+ border: 1px solid #1e4d8e;
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 13px;
|
|
|
+ outline: none;
|
|
|
+ box-sizing: border-box;
|
|
|
+ transition: border-color 0.15s;
|
|
|
+}
|
|
|
+.menu-search-input::placeholder {
|
|
|
+ color: rgba(255, 255, 255, 0.4);
|
|
|
+}
|
|
|
+.menu-search-input:focus {
|
|
|
+ border-color: #3a7fd1;
|
|
|
+}
|
|
|
+.menu-search-clear {
|
|
|
+ position: absolute;
|
|
|
+ right: 18px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ line-height: 18px;
|
|
|
+ text-align: center;
|
|
|
+ color: rgba(255, 255, 255, 0.5);
|
|
|
+ font-size: 12px;
|
|
|
+ font-style: normal;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 50%;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+.menu-search-clear:hover {
|
|
|
+ color: #fff;
|
|
|
+ background: rgba(255, 255, 255, 0.1);
|
|
|
+}
|
|
|
+.menu-search-empty {
|
|
|
+ padding: 16px;
|
|
|
+ text-align: center;
|
|
|
+ color: rgba(255, 255, 255, 0.4);
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
.mode-switch {
|
|
|
display: flex;
|
|
|
flex-direction: row;
|