diff --git a/apps/desktop/src/components/nacos/NacosAdminConsole.vue b/apps/desktop/src/components/nacos/NacosAdminConsole.vue
index 522689c24f..7f44fee450 100644
--- a/apps/desktop/src/components/nacos/NacosAdminConsole.vue
+++ b/apps/desktop/src/components/nacos/NacosAdminConsole.vue
@@ -13,6 +13,7 @@ import EditorSearchPanel from "@/components/editor/EditorSearchPanel.vue";
import NacosConfigDiffDialog from "@/components/nacos/NacosConfigDiffDialog.vue";
import NacosConfigHistoryDialog from "@/components/nacos/NacosConfigHistoryDialog.vue";
import { useToast } from "@/composables/useToast";
+import { useNacosConfigListColumnResize } from "@/composables/useNacosConfigListColumnResize";
import { useConnectionStore } from "@/stores/connectionStore";
import { useI18n } from "vue-i18n";
import * as api from "@/lib/backend/api";
@@ -113,6 +114,7 @@ const NACOS_SPLIT_SIZE_KEY = "dbx-nacos-admin-split-size";
const savedNacosSplitSize = Number(safeLocalStorageGet(NACOS_SPLIT_SIZE_KEY));
const nacosSplitSize = ref(savedNacosSplitSize >= 20 && savedNacosSplitSize <= 80 ? savedNacosSplitSize : 42);
const CONNECTION_NOT_FOUND_RETRY_DELAYS_MS = [150, 350, 700];
+const { gridTemplateColumns: configListGridTemplate, minWidth: configListMinWidth, resizingColumnIndex: configListResizingColumnIndex, onResizeStart: onConfigListColumnResizeStart } = useNacosConfigListColumnResize();
const namespace = computed(() => props.namespace ?? connectionInfo.value?.namespace ?? "");
const namespaceLabel = computed(() => props.namespaceName || namespace.value || "public");
@@ -909,28 +911,43 @@ onBeforeUnmount(() => {
-
-
dataID
-
{{ t("nacos.group") }}
-
{{ t("nacos.application") }}
-
{{ t("nacos.format") }}
+
+
+
+
+
{{ t("nacos.group") }}
+
+
+
+
{{ t("nacos.application") }}
+
+
+
+
{{ t("nacos.format") }}
+
+
+
+
-
{{ t("nacos.noConfigs") }}
diff --git a/apps/desktop/src/composables/useNacosConfigListColumnResize.ts b/apps/desktop/src/composables/useNacosConfigListColumnResize.ts
new file mode 100644
index 0000000000..4577f01c35
--- /dev/null
+++ b/apps/desktop/src/composables/useNacosConfigListColumnResize.ts
@@ -0,0 +1,79 @@
+import { computed, ref } from "vue";
+import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
+
+export const NACOS_CONFIG_LIST_COLUMN_WIDTHS_STORAGE_KEY = "dbx-nacos-config-list-column-widths";
+export const DEFAULT_NACOS_CONFIG_LIST_COLUMN_WIDTHS = [280, 180, 180, 96] as const;
+const MIN_NACOS_CONFIG_LIST_COLUMN_WIDTHS = [180, 120, 120, 72] as const;
+
+function minWidthForColumn(index: number) {
+ return MIN_NACOS_CONFIG_LIST_COLUMN_WIDTHS[index] ?? MIN_NACOS_CONFIG_LIST_COLUMN_WIDTHS[MIN_NACOS_CONFIG_LIST_COLUMN_WIDTHS.length - 1];
+}
+
+function normalizeNacosConfigListColumnWidths(value: unknown): number[] | null {
+ if (!Array.isArray(value) || value.length !== DEFAULT_NACOS_CONFIG_LIST_COLUMN_WIDTHS.length) return null;
+ const widths = value.map((item) => Number(item));
+ if (widths.some((item) => !Number.isFinite(item))) return null;
+ return widths.map((width, index) => Math.max(minWidthForColumn(index), width));
+}
+
+function loadNacosConfigListColumnWidths(): number[] {
+ const raw = safeLocalStorageGet(NACOS_CONFIG_LIST_COLUMN_WIDTHS_STORAGE_KEY);
+ if (!raw) return [...DEFAULT_NACOS_CONFIG_LIST_COLUMN_WIDTHS];
+ try {
+ const normalized = normalizeNacosConfigListColumnWidths(JSON.parse(raw));
+ return normalized ?? [...DEFAULT_NACOS_CONFIG_LIST_COLUMN_WIDTHS];
+ } catch {
+ return [...DEFAULT_NACOS_CONFIG_LIST_COLUMN_WIDTHS];
+ }
+}
+
+function saveNacosConfigListColumnWidths(widths: readonly number[]) {
+ const normalized = normalizeNacosConfigListColumnWidths([...widths]);
+ if (!normalized) return;
+ safeLocalStorageSet(NACOS_CONFIG_LIST_COLUMN_WIDTHS_STORAGE_KEY, JSON.stringify(normalized));
+}
+
+function gridTemplateColumnsForWidths(widths: readonly number[]) {
+ return widths.map((width) => `${width}px`).join(" ");
+}
+
+export function useNacosConfigListColumnResize() {
+ const columnWidths = ref(loadNacosConfigListColumnWidths());
+ const resizingColumnIndex = ref(null);
+ const gridTemplateColumns = computed(() => gridTemplateColumnsForWidths(columnWidths.value));
+ const totalWidth = computed(() => columnWidths.value.reduce((sum, width) => sum + width, 0));
+ const minWidth = computed(() => `${totalWidth.value}px`);
+
+ function onResizeStart(columnIndex: number, event: MouseEvent) {
+ if (columnIndex < 0 || columnIndex >= columnWidths.value.length) return;
+ event.preventDefault();
+ const startX = event.clientX;
+ const startWidth = columnWidths.value[columnIndex] ?? minWidthForColumn(columnIndex);
+ resizingColumnIndex.value = columnIndex;
+
+ const onMove = (moveEvent: MouseEvent) => {
+ const delta = moveEvent.clientX - startX;
+ columnWidths.value[columnIndex] = Math.max(minWidthForColumn(columnIndex), startWidth + delta);
+ };
+
+ const onUp = (moveEvent: MouseEvent) => {
+ onMove(moveEvent);
+ resizingColumnIndex.value = null;
+ saveNacosConfigListColumnWidths(columnWidths.value);
+ document.removeEventListener("mousemove", onMove);
+ document.removeEventListener("mouseup", onUp);
+ };
+
+ document.addEventListener("mousemove", onMove);
+ document.addEventListener("mouseup", onUp);
+ }
+
+ return {
+ columnWidths,
+ gridTemplateColumns,
+ totalWidth,
+ minWidth,
+ resizingColumnIndex,
+ onResizeStart,
+ };
+}
diff --git a/packages/app-tests/nacosConfigListLayout.test.ts b/packages/app-tests/nacosConfigListLayout.test.ts
new file mode 100644
index 0000000000..dbd4101727
--- /dev/null
+++ b/packages/app-tests/nacosConfigListLayout.test.ts
@@ -0,0 +1,112 @@
+import assert from "node:assert/strict";
+import { afterEach, beforeEach, test } from "vitest";
+import {
+ NACOS_CONFIG_LIST_COLUMN_WIDTHS_STORAGE_KEY,
+ DEFAULT_NACOS_CONFIG_LIST_COLUMN_WIDTHS,
+ useNacosConfigListColumnResize,
+} from "../../apps/desktop/src/composables/useNacosConfigListColumnResize.ts";
+
+function installLocalStorage() {
+ const original = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
+ const store = new Map();
+ Object.defineProperty(globalThis, "localStorage", {
+ configurable: true,
+ value: {
+ getItem: (key: string) => store.get(key) ?? null,
+ setItem: (key: string, value: string) => {
+ store.set(key, value);
+ },
+ removeItem: (key: string) => {
+ store.delete(key);
+ },
+ clear: () => {
+ store.clear();
+ },
+ },
+ });
+ return () => {
+ if (original) Object.defineProperty(globalThis, "localStorage", original);
+ else Reflect.deleteProperty(globalThis, "localStorage");
+ };
+}
+
+function installDocument() {
+ const original = Object.getOwnPropertyDescriptor(globalThis, "document");
+ const listeners = new Map void>>();
+ Object.defineProperty(globalThis, "document", {
+ configurable: true,
+ value: {
+ addEventListener: (type: string, listener: (event: MouseEvent) => void) => {
+ const set = listeners.get(type) ?? new Set();
+ set.add(listener);
+ listeners.set(type, set);
+ },
+ removeEventListener: (type: string, listener: (event: MouseEvent) => void) => {
+ listeners.get(type)?.delete(listener);
+ },
+ },
+ });
+ return {
+ dispatchMouseEvent(type: string, clientX: number) {
+ const event = { type, clientX } as MouseEvent;
+ listeners.get(type)?.forEach((listener) => listener(event));
+ },
+ restore() {
+ if (original) Object.defineProperty(globalThis, "document", original);
+ else Reflect.deleteProperty(globalThis, "document");
+ },
+ };
+}
+
+let restoreLocalStorage: (() => void) | undefined;
+let documentHarness: ReturnType | undefined;
+
+beforeEach(() => {
+ restoreLocalStorage = installLocalStorage();
+ documentHarness = installDocument();
+});
+
+afterEach(() => {
+ documentHarness?.restore();
+ documentHarness = undefined;
+ restoreLocalStorage?.();
+ restoreLocalStorage = undefined;
+});
+
+test("Nacos config list keeps overflow tied to the computed width and updates it after drag resizing", () => {
+ const layout = useNacosConfigListColumnResize();
+
+ assert.deepEqual(layout.columnWidths.value, [...DEFAULT_NACOS_CONFIG_LIST_COLUMN_WIDTHS]);
+ assert.equal(layout.gridTemplateColumns.value, "280px 180px 180px 96px");
+ assert.equal(layout.totalWidth.value, 736);
+ assert.equal(layout.minWidth.value, "736px");
+ assert.equal(layout.totalWidth.value > 700, true);
+ assert.equal(layout.totalWidth.value > 820, false);
+
+ let prevented = false;
+ layout.onResizeStart(0, {
+ clientX: 280,
+ preventDefault() {
+ prevented = true;
+ },
+ } as MouseEvent);
+
+ assert.equal(prevented, true);
+ assert.equal(layout.resizingColumnIndex.value, 0);
+
+ documentHarness!.dispatchMouseEvent("mousemove", 400);
+
+ assert.deepEqual(layout.columnWidths.value, [400, 180, 180, 96]);
+ assert.equal(layout.gridTemplateColumns.value, "400px 180px 180px 96px");
+ assert.equal(layout.totalWidth.value, 856);
+ assert.equal(layout.minWidth.value, "856px");
+ assert.equal(layout.totalWidth.value > 820, true);
+
+ documentHarness!.dispatchMouseEvent("mouseup", 400);
+
+ assert.equal(layout.resizingColumnIndex.value, null);
+ assert.equal(localStorage.getItem(NACOS_CONFIG_LIST_COLUMN_WIDTHS_STORAGE_KEY), JSON.stringify([400, 180, 180, 96]));
+
+ const restoredLayout = useNacosConfigListColumnResize();
+ assert.deepEqual(restoredLayout.columnWidths.value, [400, 180, 180, 96]);
+});