后台三级类目实现(vue3+ts)

225 阅读1分钟

前提:因为ui展示的关系,用组件库不太方便

实现功能:

  1. 搜索
  2. 全选(取消全选)
    • 全不选
    • 全选
  3. 禁用
  4. 三级提示

界面

menu.gif

代码

1. 结构

<template>
  <div class="search-box">
    搜索类目:
    <el-autocomplete
      style="width: 300px"
      class="inline-input"
      v-model="searchVal"
      :fetch-suggestions="querySearch"
      placeholder="请输入内容"
      :trigger-on-focus="false"
      clearable
      size="small"
      @select="handleSelect"
    />
  </div>
  <div class="list">
    <div class="item">
      <div class="name">一级类目{{ chooseArr1.length }}</div>
      <el-scrollbar class="content">
        <ul>
          <li
            v-for="(item, index) in list1"
            :key="item.id"
            @click="check1(item, index)"
            :class="{ active: index == index1 }"
          >
            <span @click.stop>
              <el-checkbox
                :indeterminate="isIndeterminate(item, 1)"
                v-model="item.checked"
                :disabled="!item.select"
                @change="select1($event, item, index)"
              />
            </span>
            <span class="name">{{ item.name }}</span>
            <el-icon><ArrowRight /></el-icon>
          </li>
        </ul>
      </el-scrollbar>
    </div>
    <div class="item">
      <div class="name">二级类目{{ chooseArr2.length }}</div>
      <el-scrollbar class="content">
        <ul>
          <li
            v-for="(item, index) in list2"
            :key="item.id"
            @click="check2(item, index)"
            :class="{ active: index == index2 }"
          >
            <span @click.stop>
              <el-checkbox
                :indeterminate="isIndeterminate(item, 2)"
                v-model="item.checked"
                :disabled="!item.select"
                @change="select2($event, item, index)"
              />
            </span>
            <span class="name">{{ item.name }}</span>
            <el-icon><ArrowRight /></el-icon>
          </li>
        </ul>
      </el-scrollbar>
    </div>
    <div class="item">
      <div class="name">三级类目{{ chooseArr3.length }}</div>
      <el-scrollbar class="content">
        <ul>
          <li
            v-for="(item, index) in list3"
            :key="item.id"
            @click="check3(item, index)"
            :class="{ active: index == index3 }"
          >
            <el-checkbox
              v-model="item.checked"
              :disabled="!item.select"
              @change="select3($event, item, index)"
            />
            <span class="name">{{ item.name }}</span>
            <el-popover
              v-if="item.appReminder"
              placement="bottom-end"
              width="200"
              trigger="hover"
              :content="item.appReminder.reason"
            >
              <template #reference>
                <el-icon><QuestionFilled /></el-icon>
              </template>
            </el-popover>
          </li>
        </ul>
      </el-scrollbar>
    </div>
  </div>
</template>

2. ts

<script lang="ts" setup name="menu">
import { ArrowRight, QuestionFilled } from "@element-plus/icons-vue";
import { getData } from "@/utils/getDate";
import { ref, watch } from "vue";
import { ElMessage } from "element-plus";

// 搜索
const searchVal = ref("");
const restaurants = ref<any>([]);
function querySearch(queryString: any, cb: any) {
  let curRestaurants = restaurants.value;
  let results = queryString
    ? curRestaurants.filter(createFilter(queryString))
    : curRestaurants;
  // 调用 callback 返回建议列表的数据
  cb(results);
}
function createFilter(queryString: any) {
  return (restaurant: any) => {
    return (
      restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) !== -1
    );
  };
}
function loadAll() {
  let list: any[] = [];
  list1.value.forEach((item1: any, index1: any) => {
    item1.subCategories.forEach((item2: any, index2: any) => {
      item2.subCategories.forEach((item3: any, index3: any) => {
        list.push({
          value: `${item1.name}>${item2.name}>${item3.name}`,
          indexArr: [index1, index2, index3],
          id: item3.id,
        });
      });
    });
  });
  return list;
}
function handleSelect(item: any) {
  index1.value = item.indexArr[0];
  index2.value = item.indexArr[1];
  index3.value = item.indexArr[2];
  list2.value = list1.value[item.indexArr[0]].subCategories;
  list3.value =
    list1.value[item.indexArr[0]].subCategories[item.indexArr[1]].subCategories;
}
const list1 = ref<any>([]);
list1.value = getData();
restaurants.value = loadAll();
const list2 = ref<any>([]);
const list3 = ref<any>([]);
const index1 = ref(-1);
const index2 = ref(-1);
const index3 = ref(-1);
const chooseArr1 = ref<number[]>([]);
const chooseArr2 = ref<number[]>([]);
const chooseArr3 = ref<number[]>([]);
// 一级类目
function check1(item: any, idx: number) {
  if (!item.select) {
    ElMessage.closeAll();
    return ElMessage.warning("已禁用");
  }
  index1.value = idx;
  list2.value = item.subCategories;
  list3.value = [];
}
function select1(e: any, item: any, idx: number) {
  index1.value = idx;
  item.checked = e;
  item.subCategories.forEach((child: any) => {
    if (child.select) {
      child.checked = e;
      child.subCategories.forEach((i: any) => {
        if (i.select) {
          i.checked = e;
        } else {
          i.checked = false;
        }
      });
    } else {
      child.checked = false;
    }
    child.subCategories = child.subCategories;
  });
  list2.value = item.subCategories;
  list3.value = [];
}
function isIndeterminate(item: any, level: number) {
  if (level == 1) {
    let count1 = 0;
    let count2 = 0;
    item.subCategories.forEach((item1: any) => {
      item1.subCategories.forEach((item2: any) => {
        count1++;
        if (item2.checked) {
          count2++;
        }
      });
    });
    if (count1 === count2 || count2 === 0) {
      count1 === count2 && count2 > 0
        ? (item.checked = true)
        : (item.checked = false);
      item.indeterminate = false;
      return false;
    } else {
      item.indeterminate = true;
      return true;
    }
  } else if (level == 2) {
    let count1 = 0;
    let count2 = 0;
    item.subCategories.forEach((item1: any) => {
      count1++;
      if (item1.checked) {
        count2++;
      }
    });
    if (count1 === count2 || count2 === 0) {
      count1 === count2 && count2 > 0
        ? (item.checked = true)
        : (item.checked = false);
      item.indeterminate = false;
      return false;
    } else {
      item.indeterminate = true;
      return true;
    }
  }
}
// 二级类目;
function check2(item: any, idx: number) {
  if (!item.select) {
    ElMessage.closeAll();
    return ElMessage.warning("已禁用");
  }
  index2.value = idx;
  list3.value = item.subCategories;
}
function select2(e: any, item: any, idx: number) {
  index2.value = idx;
  item.checked = e;
  item.subCategories.forEach((child: any) => {
    if (child.select) {
      child.checked = e;
    } else {
      child.checked = false;
    }
  });
  item.subCategories = item.subCategories;
  list3.value = item.subCategories;
}
// 三级类目
function check3(item: any, idx: number) {
  if (!item.select) {
    ElMessage.closeAll();
    return ElMessage.warning("已禁用");
  }
  index3.value = idx;
}
function select3(e: any, item: any, idx: number) {
  if (!list2.value[index2.value].select) {
    item.checked = false;
    ElMessage.closeAll();
    ElMessage.warning("已禁用");
    return;
  }
  index3.value = idx;
}
// 处理选择的level数量
watch(
  list1,
  (newVal: any) => {
    chooseArr1.value = [];
    chooseArr2.value = [];
    chooseArr3.value = [];
    newVal.forEach((item1: any) => {
      item1.subCategories.forEach((item2: any) => {
        item2.subCategories.forEach((item3: any) => {
          if (item3.checked) {
            if (!chooseArr3.value.includes(item3.id)) {
              chooseArr3.value.push(item3.id);
            }
            if (!chooseArr2.value.includes(item2.id)) {
              chooseArr2.value.push(item2.id);
            }
            if (!chooseArr1.value.includes(item1.id)) {
              chooseArr1.value.push(item1.id);
            }
          }
        });
      });
    });
  },
  { deep: true, immediate: true }
);
</script>

3. scss

<style lang="scss" scoped>
.list {
  display: flex;
  margin: auto;
  width: 1000px;
  justify-content: space-between;
  .item {
    width: calc((100% - 40px) / 3);
    height: 100%;
    .content {
      border: 1px solid #ccc;
      border-radius: 4px;
      margin-top: 10px;
      height: 200px;
      box-sizing: border-box;
      overflow-x: hidden;
      overflow-y: auto;
    }
  }
}
ul {
  list-style: none;
  padding: 0;
  margin: 0;
  li {
    display: flex;
    height: 42px;
    font-size: 14px;
    align-items: center;
    cursor: pointer;
    padding: 0 10px;
    border-bottom: 1px solid #e8e8e8;
    &.active {
      background: #ebf0ff;
    }
    .name {
      margin-left: 10px;
      margin-right: auto;
    }
  }
}
</style>

4. 模拟数据结构

export function getData() {
  const data = [
    {
      fid: 0,
      view: true,
      select: true, //ture可选 false不可选
      level: 1,
      name: "珠宝首饰",
      id: 6144,
      checked: false, // 当前选中状态,绑定的值
      subCategories: [
        {
          checked: false,
          msg: null,
          fid: 6144,
          reminderCode: null,
          select: true,
          code: null,
          level: 2,
          pcReminder: null,
          leaf: false,
          subCategories: [
            {
              checked: false,
              msg: null,
              fid: 6145,
              reminderCode: "open",
              select: true,
              code: null,
              level: 3,
              pcReminder: {
                reason: "此类目不面向该店铺类型招商,$$$",
                redirectList: [
                  {
                    redirectUrl:
                      "https://helpcenter.jd.com/vender/issue/44329.html",
                    redirectText: "查看规则",
                    redirectType: 1,
                  },
                ],
              },
              leaf: false,
              subCategories: [
                {
                  msg: null,
                  fid: 28212,
                  reminderCode: null,
                  select: true,
                  code: null,
                  level: 4,
                  pcReminder: null,
                  leaf: true,
                  subCategories: [],
                  view: true,
                  name: "宝宝转运珠",
                  appReminder: null,
                  risk: true,
                  id: 28213,
                },
                {
                  msg: null,
                  fid: 28212,
                  reminderCode: null,
                  select: false,
                  code: null,
                  level: 4,
                  pcReminder: null,
                  leaf: true,
                  subCategories: [],
                  view: true,
                  name: "宝宝金手链/手镯",
                  appReminder: null,
                  risk: true,
                  id: 28214,
                },
                {
                  msg: null,
                  fid: 28212,
                  reminderCode: null,
                  select: false,
                  code: null,
                  level: 4,
                  pcReminder: null,
                  leaf: true,
                  subCategories: [],
                  view: true,
                  name: "宝宝金锁",
                  appReminder: null,
                  risk: true,
                  id: 28215,
                },
                {
                  msg: null,
                  fid: 28212,
                  reminderCode: null,
                  select: false,
                  code: null,
                  level: 4,
                  pcReminder: null,
                  leaf: true,
                  subCategories: [],
                  view: true,
                  name: "宝宝金片",
                  appReminder: null,
                  risk: true,
                  id: 28216,
                },
              ],
              view: true,
              name: "宝宝金饰",
              appReminder: {
                reason: "此类目不面向该店铺类型招商。",
                redirectList: [
                  {
                    redirectUrl:
                      "https://helpcenter.jd.com/vender/issue/44329.html",
                    redirectText: "查看规则",
                    redirectType: 1,
                  },
                ],
              },
              risk: true,
              id: 28212,
            },
            {
              checked: false,
              msg: null,
              fid: 6145,
              reminderCode: "open",
              select: true,
              code: null,
              level: 3,
              pcReminder: {
                reason: "此类目不面向该店铺类型招商,$$$",
                redirectList: [
                  {
                    redirectUrl:
                      "https://helpcenter.jd.com/vender/issue/44329.html",
                    redirectText: "查看规则",
                    redirectType: 1,
                  },
                ],
              },
              leaf: true,
              subCategories: [],
              view: true,
              name: "黄金转运珠",
              appReminder: {
                reason: "此类目不面向该店铺类型招商。",
                redirectList: [
                  {
                    redirectUrl:
                      "https://helpcenter.jd.com/vender/issue/44329.html",
                    redirectText: "查看规则",
                    redirectType: 1,
                  },
                ],
              },
              risk: true,
              id: 13065,
            },
          ],
          view: true,
          name: "黄金",
          appReminder: null,
          risk: true,
          id: 6145,
        },
        {
          checked: false,
          msg: null,
          fid: 6144,
          reminderCode: null,
          select: true,
          code: null,
          level: 2,
          pcReminder: null,
          leaf: false,
          subCategories: [
            {
              checked: false,
              msg: null,
              fid: 6146,
              reminderCode: "open",
              select: true,
              code: null,
              level: 3,
              pcReminder: {
                reason: "此类目不面向该店铺类型招商,$$$",
                redirectList: [
                  {
                    redirectUrl:
                      "https://helpcenter.jd.com/vender/issue/44329.html",
                    redirectText: "查看规则",
                    redirectType: 1,
                  },
                ],
              },
              leaf: true,
              subCategories: [],
              view: true,
              name: "金片/金摆件",
              appReminder: {
                reason: "此类目不面向该店铺类型招商。",
                redirectList: [
                  {
                    redirectUrl:
                      "https://helpcenter.jd.com/vender/issue/44329.html",
                    redirectText: "查看规则",
                    redirectType: 1,
                  },
                ],
              },
              risk: true,
              id: 25579,
            },
            {
              checked: false,
              msg: null,
              fid: 6146,
              reminderCode: "open",
              select: true,
              code: null,
              level: 3,
              pcReminder: {
                reason: "此类目不面向该店铺类型招商,$$$",
                redirectList: [
                  {
                    redirectUrl:
                      "https://helpcenter.jd.com/vender/issue/44329.html",
                    redirectText: "查看规则",
                    redirectType: 1,
                  },
                ],
              },
              leaf: true,
              subCategories: [],
              view: true,
              name: "特殊商品",
              appReminder: {
                reason: "此类目不面向该店铺类型招商。",
                redirectList: [
                  {
                    redirectUrl:
                      "https://helpcenter.jd.com/vender/issue/44329.html",
                    redirectText: "查看规则",
                    redirectType: 1,
                  },
                ],
              },
              risk: true,
              id: 13220,
            },
          ],
          view: true,
          name: "金银投资",
          appReminder: null,
          risk: true,
          id: 6146,
        },
        {
          checked: false,
          msg: null,
          fid: 6144,
          reminderCode: null,
          select: true,
          code: null,
          level: 2,
          pcReminder: null,
          leaf: false,
          subCategories: [
            {
              checked: false,
              msg: null,
              fid: 6155,
              reminderCode: "success",
              select: false,
              code: null,
              level: 3,
              pcReminder: null,
              leaf: true,
              subCategories: [],
              view: true,
              name: "银手镯",
              appReminder: null,
              risk: false,
              id: 11238,
            },
            {
              checked: false,
              msg: null,
              fid: 6155,
              reminderCode: "success",
              select: false,
              code: null,
              level: 3,
              pcReminder: null,
              leaf: true,
              subCategories: [],
              view: true,
              name: "银耳饰",
              appReminder: null,
              risk: false,
              id: 13070,
            },
          ],
          view: true,
          name: "银饰",
          appReminder: null,
          risk: false,
          id: 6155,
        },
      ],
    },
    {
      fid: 0,
      view: true,
      select: false, //不可选
      level: 1,
      name: "医药",
      id: 13314,
      subCategories: [
        {
          msg: null,
          fid: 13314,
          reminderCode: null,
          select: false,
          code: null,
          level: 2,
          pcReminder: null,
          leaf: false,
          subCategories: [
            {
              msg: null,
              fid: 30725,
              reminderCode: "open",
              select: true,
              code: null,
              level: 3,
              pcReminder: {
                reason: "此类目不面向该店铺类型招商,$$$",
                redirectList: [
                  {
                    redirectUrl:
                      "https://helpcenter.jd.com/vender/issue/44329.html",
                    redirectText: "查看规则",
                    redirectType: 1,
                  },
                ],
              },
              leaf: true,
              subCategories: [],
              view: true,
              name: "特殊商品",
              appReminder: {
                reason: "此类目不面向该店铺类型招商。",
                redirectList: [
                  {
                    redirectUrl:
                      "https://helpcenter.jd.com/vender/issue/44329.html",
                    redirectText: "查看规则",
                    redirectType: 1,
                  },
                ],
              },
              risk: false,
              id: 30726,
            },
          ],
          view: true,
          name: "特殊商品",
          appReminder: null,
          risk: false,
          id: 30725,
        },
      ],
    },
  ];
  return data;
}