gaoluyang
10 小时以前 3d14e2ef3644ab878c91b3252067ab53dfd4f1ad
src/views/basicData/product/index.vue
@@ -25,18 +25,16 @@
          :data="list"
          @node-click="handleNodeClick"
          :expand-on-click-node="false"
          default-expand-all
          @node-expand="handleNodeExpand"
          @node-collapse="handleNodeCollapse"
          :key="treeKey"
          :default-expanded-keys="expandedKeys"
          :draggable="true"
          :filter-node-method="filterNode"
          :props="{ children: 'children', label: 'label' }"
          highlight-current
          node-key="id"
          style="
            height: calc(100vh - 190px);
            overflow-y: scroll;
            scrollbar-width: none;
          "
          class="product-tree-scroll"
          style="height: calc(100vh - 190px); overflow-y: auto"
        >
          <template #default="{ node, data }">
            <div class="custom-tree-node">
@@ -45,7 +43,7 @@
                  <component :is="data.children && data.children.length > 0
                  ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
                </el-icon>
                {{ data.label }}
                <span class="tree-node-label">{{ data.label }}</span>
              </span>
              <div>
                <el-button
@@ -55,7 +53,7 @@
                >
                  编辑
                </el-button>
                <el-button type="primary" link @click="openProDia('add', data)" :disabled="node.level >= 3">
                <el-button type="primary" link @click="openProDia('add', data)">
                  添加产品
                </el-button>
                <el-button
@@ -78,7 +76,7 @@
        <el-button type="primary" @click="openModelDia('add')">
          新增规格型号
        </el-button>
        <ImportExcel @uploadSuccess="getModelList" />
        <ImportExcel :product-id="currentId" @uploadSuccess="getModelList" />
        <el-button
          type="danger"
          @click="handleDelete"
@@ -113,6 +111,8 @@
              <el-input
                v-model="form.productName"
                placeholder="请输入产品名称"
                maxlength="20"
                show-word-limit
                clearable
                @keydown.enter.prevent
              />
@@ -177,7 +177,7 @@
</template>
<script setup>
import { ref } from "vue";
import { nextTick, ref } from "vue";
import { ElMessageBox } from "element-plus";
import {
  addOrEditProduct,
@@ -192,6 +192,92 @@
const { proxy } = getCurrentInstance();
const tree = ref(null);
const containerRef = ref(null);
const treeKey = ref(0);
const expandedKeySet = new Set();
const EXPANDED_STORAGE_KEY = "basicData_product_tree_expanded_keys_v2";
const loadExpandedKeys = () => {
  if (typeof window === "undefined") {
    return [];
  }
  try {
    const saved = localStorage.getItem(EXPANDED_STORAGE_KEY);
    return saved ? JSON.parse(saved) : [];
  } catch (error) {
    console.error(error);
    return [];
  }
};
const saveExpandedKeys = () => {
  if (typeof window === "undefined") {
    return;
  }
  localStorage.setItem(
    EXPANDED_STORAGE_KEY,
    JSON.stringify(Array.from(expandedKeySet))
  );
};
loadExpandedKeys().forEach((key) => expandedKeySet.add(key));
const syncExpandedKeysFromTree = () => {
  const keys = [];
  const walk = (nodes) => {
    (nodes || []).forEach((item) => {
      if (item.expanded && item.data?.id !== undefined) {
        keys.push(item.data.id);
      }
      if (item.childNodes && item.childNodes.length) {
        walk(item.childNodes);
      }
    });
  };
  walk(tree.value?.root?.childNodes);
  expandedKeySet.clear();
  keys.forEach((key) => expandedKeySet.add(key));
  expandedKeys.value = keys;
  saveExpandedKeys();
};
const normalizeExpandedKeys = (treeData) => {
  const parentMap = new Map();
  const walk = (nodes, parentId = null) => {
    (nodes || []).forEach((item) => {
      parentMap.set(item.id, parentId);
      if (item.children && item.children.length) {
        walk(item.children, item.id);
      }
    });
  };
  walk(treeData);
  const normalizedKeys = Array.from(expandedKeySet).filter((key) => {
    if (!parentMap.has(key)) {
      return false;
    }
    let currentId = key;
    while (parentMap.has(currentId)) {
      const parentId = parentMap.get(currentId);
      if (!parentId) {
        return true;
      }
      if (!expandedKeySet.has(parentId)) {
        return false;
      }
      currentId = parentId;
    }
    return true;
  });
  if (normalizedKeys.length !== expandedKeySet.size) {
    expandedKeySet.clear();
    normalizedKeys.forEach((key) => expandedKeySet.add(key));
    saveExpandedKeys();
  }
};
const productDia = ref(false);
const modelDia = ref(false);
@@ -241,7 +327,10 @@
    productName: "",
  },
  rules: {
    productName: [{ required: true, message: "请输入", trigger: "blur" }],
    productName: [
      { required: true, message: "请输入", trigger: "blur" },
      { max: 20, message: "产品名称不能超过20个字符", trigger: "blur" },
    ],
  },
  modelForm: {
    model: "",
@@ -258,15 +347,29 @@
  treeLoad.value = true;
  productTreeList()
    .then((res) => {
      list.value = res;
      list.value.forEach((a) => {
        expandedKeys.value.push(a.label);
      list.value = res || [];
      normalizeExpandedKeys(list.value);
      expandedKeys.value = Array.from(expandedKeySet);
      treeKey.value += 1;
      nextTick(() => {
        tree.value?.setDefaultExpandedKeys?.(expandedKeys.value);
      });
      treeLoad.value = false;
    })
    .catch((err) => {
      console.error(err);
    })
    .finally(() => {
      treeLoad.value = false;
    });
};
const handleNodeExpand = (data) => {
  nextTick(syncExpandedKeysFromTree);
};
const handleNodeCollapse = (data, node) => {
  node?.eachNode?.((item) => {
    item.collapse();
  });
  nextTick(syncExpandedKeysFromTree);
};
// 过滤产品树
const searchFilter = () => {
@@ -469,18 +572,21 @@
  display: flex;
}
.left {
  width: 380px;
  width: 450px;
  min-width: 450px;
  padding: 16px;
  background: #ffffff;
}
.right {
  width: calc(100% - 380px);
  flex: 1;
  min-width: 0;
  padding: 16px;
  margin-left: 20px;
  background: #ffffff;
}
.custom-tree-node {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
@@ -488,13 +594,42 @@
  padding-right: 8px;
}
.tree-node-content {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center; /* 垂直居中 */
  align-items: center;
  height: 100%;
  overflow: hidden;
}
.tree-node-content .orange-icon {
  flex-shrink: 0;
}
.tree-node-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.orange-icon {
  color: orange;
  font-size: 18px;
  margin-right: 8px; /* 图标与文字之间加点间距 */
}
.product-tree-scroll {
  scrollbar-width: thin;
  scrollbar-color: #c0c4cc #f5f7fa;
}
.product-tree-scroll::-webkit-scrollbar {
  width: 8px;
}
.product-tree-scroll::-webkit-scrollbar-track {
  background: #f5f7fa;
  border-radius: 4px;
}
.product-tree-scroll::-webkit-scrollbar-thumb {
  background: #c0c4cc;
  border-radius: 4px;
}
.product-tree-scroll::-webkit-scrollbar-thumb:hover {
  background: #909399;
}
</style>