初始化接口返回了全部数据,但只展示一级分类,点击一级分类出现二级分类,点击二级分类出现对应三级分类,效果如下:

专辑分类.gif

再次点击一级分类,去取消二三级分类选中效果,但一级分类仍保持选中和展示,隐藏三级分类;点击二级分类,取消三级分类的选中和隐藏效果

专辑分类取消选择.gif

如果子级分类列表为空,则点击父级分类,子级列表展示输入框

image.png

各级分类可以编辑与删除

1731661406879.jpg

image.png

1731661427428.jpg

封装思路:

三个分类列表一模一样,所以封装成一个组件,通过title传入名称,level来定该分类的级;pid为0表示一级,二级和三级数据的pid为其父级的id并传入。此外,还需要将一二三级不同的数据传入,并加入点击方法和数据更新方法。此外能看到,当鼠标移入或点击时,出现复选框。

另外,编辑与删除都是前端进行,只有一个保存接口,只将前端操作后的数据进行保存或更新。我们用name-level-pid来确定分类的唯一性,通过它进行删除或编辑,由于操作都在前端,所以需要克隆一份数据用来更新。

主要代码

<div
          v-if="selectedCategories.length > 0 && !isGoogleCategory"
          class="bulk-actions flex items-center justify-between py-2"
        >
          <span>{{ selectedCategories.length }} 已选</span>
          <el-button
            type="danger"
            size="small"
            @click="deleteSelectedCategories"
            >删除</el-button
          >
        </div>
        <!-- 分类列表 -->
        <el-row>
          <el-col :span="24">
            <ul>
              <li
                v-for="category in categories"
                :key="category.id"
                :class="[
                  'category-item',
                  {
                    selected: category.selected,
                    'category-selected': category.id === props.selectedId
                  }
                ]"
                @mouseover="hover = category.id"
                @mouseleave="hover = null"
                @click="selectCategory(category.id)"
              >
                <div class="item-content">
                  <el-checkbox
                    v-if="
                      (hover === category.id || category.selected) &&
                      editingId !==
                        `${category.name}-${category.level}-${category.pid}`
                    "
                    v-model="category.selected"
                    @click.stop
                  />
                  <template
                    v-if="
                      editingId ===
                      `${category.name}-${category.level}-${category.pid}`
                    "
                  >
                    <div class="flex justify-between gap-4">
                      <el-form
                        :ref="
                          el =>
                            (editFormRefs[
                              `${category.name}-${category.level}-${category.pid}`
                            ] = el)
                        "
                        class="flex flex-wrap gap-1"
                        :model="editForm"
                        :rules="rules"
                        style="width: 100%"
                      >
                        <el-form-item prop="name" class="w-10/12 !mb-0">
                          <el-input
                            :id="`edit-input-${category.name}-${category.level}-${category.pid}`"
                            v-model="editForm.name"
                            clearable
                            placeholder="类目名称"
                            @click.stop
                            @keyup.enter="saveEdit(category)"
                            @keyup.esc="cancelEdit"
                          />
                        </el-form-item>
                        <div class="icon-buttons flex justify-end gap-2">
                          <el-icon :size="16" @click.stop="saveEdit(category)"
                            ><Check
                          /></el-icon>
                          <el-icon :size="16" @click.stop="cancelEdit"
                            ><Delete
                          /></el-icon>
                        </div>
                        <el-form-item prop="googleName" class="!mb-0 w-6/12">
                          <el-input
                            v-model="editForm.googleName"
                            size="small"
                            clearable
                            placeholder="Google类目"
                            @click.stop
                          />
                        </el-form-item>
                        <el-form-item prop="typeName" class="!mb-0 w-5/12">
                          <el-input
                            v-model="editForm.typeName"
                            clearable
                            size="small"
                            placeholder="Product Type"
                            @click.stop
                          />
                        </el-form-item>
                      </el-form>
                    </div>
                  </template>
                  <span v-else class="category-name">{{ category.name }}</span>
                </div>

                <el-dropdown
                  v-show="
                    hover === category.id &&
                    !isGoogleCategory &&
                    editingId !== category.id
                  "
                  trigger="hover"
                  :teleported="false"
                >
                  <el-icon :size="20" class="more-icon"><More /></el-icon>
                  <template #dropdown>
                    <el-dropdown-menu
                      @mouseenter="menuVisible = true"
                      @mouseleave="menuVisible = false"
                      @click.stop
                    >
                      <!-- <el-dropdown-item @click="viewRelatedProducts(category)">
                        查看已关联商品
                      </el-dropdown-item> -->
                      <el-dropdown-item @click="editCategory(category)">
                        编辑分类
                      </el-dropdown-item>
                      <el-dropdown-item
                        style="color: red"
                        @click="openDeleteDialog(category)"
                      >
                        删除分类
                      </el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </li>
            </ul>
          </el-col>
        </el-row>
      </el-col>
    </el-row>
 <!-- 删除确认对话框 -->
    <el-dialog
      v-model="deleteDialogVisible"
      width="30%"
      align-center
      :show-close="false"
      :before-close="handleClose"
    >
      <template #header="{ close, titleId, titleClass }">
        <div
          class="my-header"
          style="display: flex; justify-content: space-between"
        >
          <h4 :id="titleId" :class="titleClass">
            确认删除
            {{
              selectedCategories.length > 1
                ? `${selectedCategories.length} 个分类`
                : "分类"
            }}
            ?
          </h4>
          <el-icon class="el-icon--left" style="cursor: pointer" @click="close"
            ><CloseBold
          /></el-icon>
        </div>
        <ElDivider />
      </template>
      <div>
        如果删除
        <span style="color: black">
          {{
            selectedCategories.length > 1
              ? `${selectedCategories.length} 个所选分类`
              : `${categoryToDelete?.name || ""}`
          }}
        </span>
        ,则该分类下的所有子分类也将被删除且删除分类将与产品解除关联。
      </div>
      <template v-slot:footer>
        <span class="dialog-footer">
          <ElDivider />
          <el-button @click="handleClose">取消</el-button>
          <el-button
            type="danger"
            :loading="deleting"
            @click="confirmDeleteCategory"
            >删除</el-button
          >
        </span>
      </template>
    </el-dialog>

使用方法

 <div class="flex gap-2">
      <CategoryComponent
        title="一级分类"
        :level="1"
        pid="0"
        :categoriesData="firstLevelCategories"
        :isGoogleCategory="isGoogleCategory"
        :parentSelected="true"
        :selectedId="selectedFirstLevelId"
        @updateCategories="updateFirstLevelCategories"
        @categorySelected="handleFirstCategorySelected"
      />
      <CategoryComponent
        ref="secondCategoryComponent"
        title="二级分类"
        :level="2"
        :pid="selectedFirstLevelId"
        :categoriesData="secondLevelCategories"
        :isGoogleCategory="false"
        :parentSelected="!!selectedFirstLevelId"
        :selectedId="selectedSecondLevelId"
        @updateCategories="updateSecondLevelCategories"
        @categorySelected="handleSecondCategorySelected"
      />
      <CategoryComponent
        ref="thirdCategoryComponent"
        title="三级分类"
        :level="3"
        :pid="selectedSecondLevelId"
        :isGoogleCategory="false"
        :parentSelected="!!selectedSecondLevelId"
        :selectedId="selectedThirdLevelId"
        :categoriesData="thirdLevelCategories"
        @updateCategories="updateThirdLevelCategories"
        @categorySelected="handleThirdCategorySelected"
      />
    </div>