首页 > 其他分享 >vue3项目-小兔鲜儿笔记-分类模块01

vue3项目-小兔鲜儿笔记-分类模块01

时间:2022-09-03 11:48:08浏览次数:54  
标签:01 const 鲜儿 value vue3 组件 false 排序 加载

1.二级类目-筛选区展示

获取数据进行品牌和属性的渲染

<template>
  <div class="sub-filter" v-if="filterData && !filterDataLoading">
    <div class="item">
      <div class="head">品牌:</div>
      <div class="body">
        <!-- 选中某个品牌 -->
        <a
          @click="changeBrand(brand.id)"
          :class="{ active: brand.id === filterData.selectedBrand }"
          href="javascript:;"
          v-for="brand in filterData.brands"
          :key="brand.id"
        >
          {{ brand.name }}
        </a>
      </div>
    </div>
    <div class="item" v-for="item in filterData.saleProperties" :key="item.id">
      <div class="head">{{ item.name }}</div>
      <div class="body">
        <!-- 选中某个属性 -->
        <a
          @click="changeProp(item, prop.id)"
          :class="{ active: prop.id === item.selectedProp }"
          href="javascript:;"
          v-for="prop in item.properties"
          :key="prop.id"
        >
          {{ prop.name }}
        </a>
      </div>
    </div>
  </div>
  <!-- 骨架屏处理等待 -->
  <div class="sub-filter" v-else>
    <xtx-skeleton class="item" width="800px" height="40px" />
    <xtx-skeleton class="item" width="600px" height="40px" />
    <xtx-skeleton class="item" width="600px" height="40px" />
    <xtx-skeleton class="item" width="600px" height="40px" />
    <xtx-skeleton class="item" width="600px" height="40px" />
  </div>
</template>

逻辑处理:

主要部分是要记住选中的品牌和选中的属性,这两点很重要。

1. 原生数据缺失 ‘全部’ 属性,要在品牌和属性的最前面添加 ‘全部’

2.怎么才能记住选中的品牌和属性呢?

  1.参考数据是:{brands:[{xxx}]},只需要在brands外层添加一个selectedBrand属性:{brands:[{xxx}], selectedBrand: xxx},这样查找时可以直接在外层filterData.value.selectedBrand找到。

  2.参考数据是:每一个属性item:{name: xxx, properties: [{xxx}]},在每个属性的外层添加一个selectedProp属性:{name:xxx, selectedProp: xxx, properties:[{xxx}]},这样查找时直接在外层item.selectedProp找到。

3.选中筛选条件的时候,需要获取到选中的品牌和属性,并通知父组件筛选条件变更。

<script setup>
import { findSubCategoryFilter } from '@/api/category'
import { useRoute } from 'vue-router'
import { ref, watch } from 'vue'

const emit = defineEmits(['filterChange'])

const route = useRoute()

// 加载中
const filterDataLoading = ref(false)

// 获取二级分类筛选数据
const filterData = ref([])
const getSubCateFilter = async (id) => {
  try {
    filterDataLoading.value = true
    const { result } = await findSubCategoryFilter(id)
    // 原生数据缺失 全部 ,需要处理原生数据
    // 1.品牌
    // 在外层添加选中品牌,这样找brand的时候直接brands.find(brand=>brand.id===result.selectedBrand)
    result.selectedBrand = null
    result.brands.unshift({ id: null, name: '全部' })
    // 2.属性
    // 在外层添加选中属性,这样找prop的时候直接saleProperty.properties.find(prop=>prop.id===saleProperty.selectedProp)
    result.saleProperties.forEach((item) => {
      item.selectedProp = null
      item.properties.unshift({ id: null, name: '全部' })
    })
    filterData.value = result
    filterDataLoading.value = false
  } catch (error) {
    console.log(error)
  }
}

// 监听路由变化,重新获取二级分类筛选条件
watch(
  () => route.params.id,
  async (newVal) => {
    // 要注意路由是从二级类目变化到二级类目才重新发送请求,否则不发送
    if (newVal && `/category/sub/${newVal}` === route.path) {
      await getSubCateFilter(newVal)
    }
  },
  { immediate: true }
)

// 获取筛选参数,参考数据:{brandId: '', attrs: [{groupName: '颜色', propName: '蓝色'}]}
const getFilterParams = () => {
  const filterParams = { brandId: null, attrs: [] }
  // 1.获取选中的品牌ID
  filterParams.brandId = filterData.value.selectedBrand
  // 2.获取选中的属性
  filterData.value.saleProperties.forEach((item) => {
    item.properties.find((prop) => {
      // 剔除 '全部'
      if (prop.id === item.selectedProp && prop.id !== null) {
        const obj = {
          groupName: item.name,
          propName: prop.name
        }
        filterParams.attrs.push(obj)
        return true
      }
    })
  })
  if ((filterParams.attrs.length = 0)) filterParams.attrs = null
  return filterParams
}

// 1.记录当前选择的品牌
const changeBrand = (brandId) => {
  if (filterData.value.selectedBrand === brandId) return
  filterData.value.selectedBrand = brandId
  // 通知父组件,筛选条件变更
  emit('filterChange', getFilterParams())
}
// 2.记录当前选择的属性
const changeProp = (item, propId) => {
  if (item.selectedProp === propId) return
  item.selectedProp = propId
  emit('filterChange', getFilterParams())
}
</script>

 

2.复选框组件封装

大致步骤:

  • 实现组件本身的选中和不选中的效果

  • 实现组件的v-model指令

  • 改造成@vueuse/core的函数写法

实现双向绑定:

  • v-model会拆解成属性 modelValue 和事件 update:modelValue

    const props = defineProps({
      modelValue: {
        type: Boolean,
        default: false
      }
    })
    const emits = defineEmits(['update:modelValue', 'change'])

     

  • 原生写法:
// 这是原生写法
const checked = ref(false)
const changeChecked = () => {
  checked.value = !checked.value
  emits('update:modelValue', checked.value)
}

watch(() => props.modelValue, (newVal) => {
  checked.value = newVal
}, {immediate: true})

组件中:
<xtx-checkbox v-model="isChecked" />
// 拆解写法
<xtx-checkbox :model-value="isChecked" @update:modelValue="handleCheck" />

 

@vueuse/core写法:

const checked = useVModel(props, 'modelValue', emits) // 封装父组件传来的数据
const changeChecked = () => {
  // 使用封装后的数据就是在使用父组件的数据
  // 这里修改值就已经通知了父组件修改数据,它会自动触发emit函数
  const newVal = !checked.value
  checked.value = newVal
  // 这里让组件再派发一个自定义事件消息
  emits('change', newVal)
}

 

 

3.排序组件封装

<template>
  <div class="sub-sort">
    <div class="sort">
      <a
        @click="changeSort(null)"
        :class="{ active: sortParams.sortField === null }"
        href="javascript:;"
      >
        默认排序
      </a>
      <a
        @click="changeSort('publishTime')"
        :class="{ active: sortParams.sortField === 'publishTime' }"
        href="javascript:;"
      >
        最新商品
      </a>
      <a
        @click="changeSort('orderNum')"
        :class="{ active: sortParams.sortField === 'orderNum' }"
        href="javascript:;"
      >
        最高人气
      </a>
      <a
        @click="changeSort('evaluateNum')"
        :class="{ active: sortParams.sortField === 'evaluateNum' }"
        href="javascript:;"
      >
        评论最多
      </a>
      <a @click="changeSort('price')" href="javascript:;">
        价格排序
        <i
          :class="{
            active:
              sortParams.sortField === 'price' &&
              sortParams.sortMethod === 'asc'
          }"
          class="arrow up"
        ></i>
        <i
          :class="{
            active:
              sortParams.sortField === 'price' &&
              sortParams.sortMethod === 'desc'
          }"
          class="arrow down"
        ></i>
      </a>
    </div>
    <div class="check">
      <xtx-checkbox @change="changeCheck" v-model="sortParams.inventory">
        仅显示有货商品
      </xtx-checkbox>
      <xtx-checkbox @change="changeCheck" v-model="sortParams.onlyDiscount">
        仅显示优惠商品
      </xtx-checkbox>
    </div>
  </div>
</template>

逻辑实现:

1. 数据要与后台需要的请求参数保持一致:

// sortField: publishTime, orderNum, price, evaluateNum // sortMethod: asc, desc 2.点击不同的排序要改变排序参数,同时通知父组件排序参数变更,让父组件重新向后台发送请求
<script setup>
import { reactive } from 'vue'
// 交互数据:与后台参数保持一致
// sortField: publishTime, orderNum, price, evaluateNum
// sortMethod: asc, desc

const emit = defineEmits(['sortChange'])

const sortParams = reactive({
  inventory: false,
  onlyDiscount: false,
  sortField: null,
  sortMethod: null
})

// 改变排序参数
const changeSort = (sortField) => {
  // 如果点击的是价格排序
  if (sortField === 'price') {
    sortParams.sortField = sortField
    // 如果是第一次点击价格排序,给设置降序
    if (sortParams.sortMethod === null) {
      sortParams.sortMethod = 'desc'
    } else {
      sortParams.sortMethod = sortParams.sortMethod === 'desc' ? 'asc' : 'desc'
    }
  } else {
    // 如果点击的是相同排序,就不工作
    if (sortField === sortParams.sortField) {
      return
    }
    // 如果是其他排序,重置升降序
    sortParams.sortField = sortField
    sortParams.sortMethod = null
  }
  // 更改了排序参数之后,通知父组件排序参数改变,重新发送请求
  emit('sortChange', sortParams)
}

const changeCheck = () => {
  emit('sortChange', sortParams)
}
</script>

 

4. 数据无限加载组件封装

<template>
  <div class="xtx-infinite-loading" ref="container">
    <div class="loading" v-if="loading">
      <span class="img"></span>
      <span class="text">正在加载...</span>
    </div>
    <div class="none" v-if="finished">
      <span class="img"></span>
      <span class="text">亲,没有更多了</span>
    </div>
  </div>
</template>

核心原理:

1.使用IntersectionObserver,创建观察者对象,观察组件是否进入可视区,进入即根据数据加载状态和数据是否加载完毕状态通知父组件发送数据请求

2.父组件每次发送请求都要改变请求参数,比如page++,当数据全部请求完毕(这次请求没有数据返回)之后,设置数据加载完毕状态finished为true,这样数据无限加载组件就会显示加载完图案

<script setup>
import {useIntersectionObserver} from "@vueuse/core"
import {ref, onMounted} from "vue";

const props = defineProps({
  loading: {
    type: Boolean,
    default: false
  },
  finished: {
    type: Boolean,
    default: false
  }
})
const emit = defineEmits(['infinite'])

// 当组件进入可视区时,根据loading状态和finished状态决定是否需要加载数据
// 触发条件:此次数据加载完毕且数据未全部加载完毕
const content = ref(null)
onMounted(() => {
  useIntersectionObserver(content, ([{isIntersecting}]) => {
    if(isIntersecting) {
      if(!props.loading && !props.finished) {
        emit('infinite')
      }
    }
  }, {threshold: 0})
})
</script>


// 组件中使用:
        <!--    列表    -->
        <ul>
          <li v-for="goods in goodsList" :key="goods.id">
            <goods-item :goods="goods" />
          </li>
        </ul>
        <xtx-infinite-loading
          :loading="loading"
          :finished="finished"
          @infinite="getData"
        />

 

5.筛选数据的实现

大致步骤:

  • 排序组件中,当点击了排序或者复选框改变后,触发自定义事件sort-change传出排序参数

  • 筛选组件中,当你改变了属性或者品牌,触发自定义事件filter-change传出筛选参数

  • 在sub组件中,分别监听sort-change和filter-change,得到参数与当前请求参数合并,回到第一页,清空商品列表数据,设置未加载完成finished=false,触发数据加载

// sub组件:
<script setup>
import SubBread from './sub-bread.vue'
import SubFilter from './sub-filter.vue'
import SubSort from './sub-sort.vue'
import GoodsItem from '@/views/category/goods-item'
import { findSuCategoryGoods } from '@/api/category'
import { useRoute } from 'vue-router'
import { ref, watch } from 'vue'

const route = useRoute()

const loading = ref(false)
const finished = ref(false)

const goodsList = ref([])
// 请求参数
let reqParams = {
  page: 1,
  pageSize: 20
}

const getData = async () => {
  // 正在加载数据
  loading.value = true
  reqParams.categoryId = route.params.id
  const { result } = await findSuCategoryGoods(reqParams)
  if (result.items.length) {
    // 如果还有数据加载进来,说明数据未加载完毕
    goodsList.value.push(...result.items)
    // 下次请求就加载下一页的数据
    reqParams.page++
  } else {
    // 如果没有数据了,就说明数据全部加载完毕
    finished.value = true
  }
  // 此次数据加载完毕
  loading.value = false
}

watch(
  () => route.params.id,
  async (newVal) => {
    // 要判断路由是在二级类目下变化的,才能重新发送二级类目的请求商品数据
    if (newVal && `/category/sub/${newVal}` === route.path) {
      // 重置请求参数,重新发送请求
      finished.value = false
      reqParams.page = 1
      goodsList.value = []
    }
  },
  { immediate: true }
)

// 监听排序组件的自定义事件,获取更改后的排序参数,重新发送请求
const changeSort = (sortParams) => {
  finished.value = false
  // 将排序参数一并发送到后台请求商品
  reqParams = { ...reqParams, ...sortParams }
  reqParams.page = 1
  // 置空商品列表,会进入infinite组件的可视区,自动触发发送请求
  goodsList.value = []
}

// 监听筛选组件的自定义事件,获取更改后的筛选参数,重新发送请求
const changeFilter = (filterParams) => {
  finished.value = false
  // 将筛选参数一并发送到后台请求商品
  reqParams = { ...reqParams, ...filterParams }
  reqParams.page = 1
  goodsList.value = []
}
</script>

 

标签:01,const,鲜儿,value,vue3,组件,false,排序,加载
From: https://www.cnblogs.com/jzhFlash/p/16652037.html

相关文章

  • vue3——初识setup
    1.理解:Vue3中一个新的配置项,值为一个函数。2.setup是所有CompositionAPI(组合API)表演的舞台3.组件中所用到的:数据、方法等等,均要配置在setup中。 4.setup函数的两种......
  • Nginx分布式框架详解61-71静态资源访问-01
    nginx跨域问题的原因分析跨域问题,我们主要从以下方面进行解决:什么情况下会出现跨域问题实例演示跨域问题具体的解决方案是什么同源策略浏览器的同源策略:是一种约定......
  • 2019ACM-ICPC 西安邀请赛 D.Miku and Generals——二分图染色+01背包
    目录题意思路代码目录题意将n个将军卡片分成两份,要求两份卡片之间的差值尽可能小,求最大的那一份卡片的和,这里有m组卡片是不能放在同一份的思路对有矛盾的组我们建图进......
  • vue3和angular的区别和联系(前端主题随笔一)
    众所周知,vue脱胎于angular,平常在工作中,尤其对全栈开发者,甚至工业上需要管业务的开发者来说,一次掌握多种前端框架是没有时间和精力的事,博主是一个3年angular开发从业者,5年C#......
  • P1966 [NOIP2013 提高组] 火柴排队做题笔记
    这题和P5677一样,是从树状数组题单里翻出来的,由于开始看时感觉题解代码写的不是很清晰,就先放进了做题计划里,后来几次看这道题,但由于第一次看题可能留下了一些心理阴影以及......
  • vue3 基础-全局组件和局部组件
    组件和页面的关系可以理解为,组件是页面的一部分.形象地理解组件就和盖房子一样的,可以将房子粗略拆分3个组件(组成部分)房顶,房身,地基.同时房顶又可以拆分...........
  • Day01-JavaScript
    0825:Day01JS编写位置1.外链式 外部文件夹JS文件中,然后通过script标签引入 <scriptsrc="js/script.js"></script>2.嵌入式 内部的script的标签中 <script>alert("......
  • 【题解】「COCI 2018.10」Teoretičar
    传送门题目大意有一个二分图,构造一种对边的染色方案,使得没有两个颜色相同的边共顶点。假设对于给定二分图的答案是\(C\),记\(X\)是大于等于\(C\)的最小的\(2\)的......
  • Mac mini 2018 系统升级 All In One
    Macmini2018系统升级AllInOneMacminihttps://www.apple.com.cn/mac-mini/设定Mac的睡眠和唤醒设置https://support.apple.com/zh-cn/guide/mac-help/mchle41......
  • 501 高精度加法
    视频链接:LuoguP1601A+BProblem(高精)#include<iostream>usingnamespacestd;constintN=505;intA[N],B[N],C[N];intla,lb,lc;voidadd(){lc=max(la,l......