Explorar el Código

可视化修改

xueyiming hace 1 semana
padre
commit
3206210fd5
Se han modificado 1 ficheros con 186 adiciones y 58 borrados
  1. 186 58
      web/src/components/IcicleHeatTree.vue

+ 186 - 58
web/src/components/IcicleHeatTree.vue

@@ -103,6 +103,50 @@ function loadPathDockHeight(): number {
 const pathDockHeight = ref(loadPathDockHeight())
 const pathDockResizing = ref(false)
 
+const DEMAND_LIST_WIDTH_KEY = 'icicle-demand-list-width'
+const DEMAND_LIST_MIN = 300
+const DEMAND_LIST_DEFAULT = 390
+
+function loadDemandListWidth(): number {
+  const raw = localStorage.getItem(DEMAND_LIST_WIDTH_KEY)
+  const n = raw ? Number(raw) : NaN
+  return Number.isFinite(n) && n >= DEMAND_LIST_MIN ? n : DEMAND_LIST_DEFAULT
+}
+
+const demandListWidth = ref(loadDemandListWidth())
+const demandListResizing = ref(false)
+
+function demandListMaxWidth(): number {
+  return Math.max(DEMAND_LIST_MIN, Math.floor(window.innerWidth * 0.72))
+}
+
+function onDemandListResizeStart(event: PointerEvent) {
+  event.preventDefault()
+  demandListResizing.value = true
+  const startX = event.clientX
+  const startWidth = demandListWidth.value
+
+  const onMove = (e: PointerEvent) => {
+    const delta = startX - e.clientX
+    demandListWidth.value = Math.max(
+      DEMAND_LIST_MIN,
+      Math.min(demandListMaxWidth(), startWidth + delta),
+    )
+  }
+
+  const onUp = () => {
+    demandListResizing.value = false
+    localStorage.setItem(DEMAND_LIST_WIDTH_KEY, String(demandListWidth.value))
+    window.removeEventListener('pointermove', onMove)
+    window.removeEventListener('pointerup', onUp)
+    window.removeEventListener('pointercancel', onUp)
+  }
+
+  window.addEventListener('pointermove', onMove)
+  window.addEventListener('pointerup', onUp)
+  window.addEventListener('pointercancel', onUp)
+}
+
 function pathDockMaxHeight(): number {
   return Math.floor(window.innerHeight * PATH_DOCK_MAX_RATIO)
 }
@@ -858,9 +902,14 @@ onUnmounted(() => {
       <p class="subtitle">完整分类树 · 横向为层级,纵向为分支规模</p>
     </header>
 
-    <div class="content-grid">
-      <section class="map-panel">
-      <div class="dim-bar">
+    <div
+      class="content-grid"
+      :class="{ 'demand-list-resizing': demandListResizing }"
+      :style="{ gridTemplateColumns: `minmax(0, 1fr) ${demandListWidth}px` }"
+    >
+      <div class="left-panel">
+        <section class="map-panel">
+        <div class="dim-bar">
         <div class="tabs" role="tablist" aria-label="热度维度">
           <button
             v-for="tab in treeTabs"
@@ -977,61 +1026,72 @@ onUnmounted(() => {
           </div>
         </div>
       </div>
-      </section>
-
-      <aside class="demand-list-aside">
-        <DemandGradeListPanel
-          v-if="demandCards.length"
-          :demand-cards="demandCards"
-          :selected-demand-id="listSelectedDemandId"
-          :selected-category-id="listSelectedCategoryId"
-          @select-demand="onSelectDemandFromList"
-          @select-demand-category="onSelectDemandCategoryFromList"
-        />
-        <div v-else class="demand-empty">暂无需求数据</div>
-      </aside>
-    </div>
+        </section>
 
-    <div
-      v-if="inspectOpen && inspectNode"
-      class="path-dock"
-      :class="{ resizing: pathDockResizing }"
-      :style="{ height: `${pathDockHeight}px` }"
-    >
-      <div
-        class="path-dock-resize"
-        role="separator"
-        aria-orientation="horizontal"
-        aria-label="调整详情区域高度"
-        title="上下拖拽调整高度"
-        @pointerdown="onPathDockResizeStart"
-      />
-      <div class="path-dock-head">
-        <div class="path-dock-meta">
-          <span class="path-dock-label">节点详情</span>
-          <span v-if="inspectDemandName" class="path-dock-demand">{{ inspectDemandName }}</span>
-          <span class="path-dock-name">{{ inspectNode.name }}</span>
-        </div>
-        <button
-          type="button"
-          class="path-dock-close"
-          aria-label="关闭节点详情"
-          @click="closeInspect"
+        <div
+          v-if="inspectOpen && inspectNode"
+          class="path-dock"
+          :class="{ resizing: pathDockResizing }"
+          :style="{ height: `${pathDockHeight}px` }"
         >
-          关闭
-        </button>
+          <div
+            class="path-dock-resize"
+            role="separator"
+            aria-orientation="horizontal"
+            aria-label="调整详情区域高度"
+            title="上下拖拽调整高度"
+            @pointerdown="onPathDockResizeStart"
+          />
+          <div class="path-dock-head">
+            <div class="path-dock-meta">
+              <span class="path-dock-label">节点详情</span>
+              <span v-if="inspectDemandName" class="path-dock-demand">{{ inspectDemandName }}</span>
+              <span class="path-dock-name">{{ inspectNode.name }}</span>
+            </div>
+            <button
+              type="button"
+              class="path-dock-close"
+              aria-label="关闭节点详情"
+              @click="closeInspect"
+            >
+              关闭
+            </button>
+          </div>
+          <div class="path-dock-body">
+            <DemandPathPanel
+              :open="true"
+              :category-name="inspectNode.name"
+              :node-name="inspectNode.name"
+              :node-path="inspectNode.path.join(' / ')"
+              :items="inspectItems"
+              :highlight-demand-id="inspectHighlightDemandId"
+              hide-inline-close
+              @close="closeInspect"
+            />
+          </div>
+        </div>
       </div>
-      <div class="path-dock-body">
-        <DemandPathPanel
-          :open="true"
-          :category-name="inspectNode.name"
-          :node-name="inspectNode.name"
-          :node-path="inspectNode.path.join(' / ')"
-          :items="inspectItems"
-          :highlight-demand-id="inspectHighlightDemandId"
-          hide-inline-close
-          @close="closeInspect"
+
+      <div class="demand-list-shell" :class="{ resizing: demandListResizing }">
+        <div
+          class="demand-list-resize"
+          role="separator"
+          aria-orientation="vertical"
+          aria-label="调整全部需求区域宽度"
+          title="左右拖拽调整宽度"
+          @pointerdown="onDemandListResizeStart"
         />
+        <aside class="demand-list-aside">
+          <DemandGradeListPanel
+            v-if="demandCards.length"
+            :demand-cards="demandCards"
+            :selected-demand-id="listSelectedDemandId"
+            :selected-category-id="listSelectedCategoryId"
+            @select-demand="onSelectDemandFromList"
+            @select-demand-category="onSelectDemandCategoryFromList"
+          />
+          <div v-else class="demand-empty">暂无需求数据</div>
+        </aside>
       </div>
     </div>
   </div>
@@ -1092,20 +1152,75 @@ onUnmounted(() => {
 
 .content-grid {
   display: grid;
-  grid-template-columns: minmax(520px, 1fr) 360px;
-  gap: 12px;
+  gap: 0;
   flex: 1;
   min-height: 0;
   align-items: stretch;
 }
 
+.content-grid.demand-list-resizing {
+  user-select: none;
+}
+
+.left-panel {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  min-height: 0;
+  min-width: 0;
+  padding-right: 12px;
+}
+
+.demand-list-shell {
+  display: flex;
+  min-height: 0;
+  min-width: 0;
+  overflow: hidden;
+}
+
+.demand-list-resize {
+  flex-shrink: 0;
+  width: 10px;
+  cursor: ew-resize;
+  touch-action: none;
+  position: relative;
+  background: transparent;
+}
+
+.demand-list-resize::before {
+  content: '';
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  width: 4px;
+  height: 40px;
+  border-radius: 999px;
+  background: #cbd5e1;
+  transition: background 0.15s ease;
+}
+
+.demand-list-resize:hover::before,
+.demand-list-shell.resizing .demand-list-resize::before {
+  background: #64748b;
+}
+
 .demand-list-aside {
+  flex: 1;
+  min-width: 0;
   min-height: 0;
   height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.demand-list-aside :deep(.demand-list) {
+  flex: 1;
+  min-height: 0;
 }
 
 .demand-empty {
-  height: 100%;
+  flex: 1;
   min-height: 0;
   border: 1px solid #e2e8f0;
   border-radius: 10px;
@@ -1119,7 +1234,20 @@ onUnmounted(() => {
 
 @media (max-width: 980px) {
   .content-grid {
-    grid-template-columns: 1fr;
+    grid-template-columns: 1fr !important;
+    grid-template-rows: minmax(420px, 55vh) minmax(0, 1fr);
+  }
+
+  .left-panel {
+    padding-right: 0;
+  }
+
+  .demand-list-resize {
+    display: none;
+  }
+
+  .demand-list-shell {
+    min-height: 360px;
   }
 }