程序员书籍笔记 程序员书籍笔记
  • HTML
  • CSS
  • JavaScript
  • 前端知识
  • Vue
  • MarkDown
  • git
  • Node.js
  • Linux
  • 51单片机
  • 四级
  • 第一学期课程
  • 操作系统
  • 计算机网络
  • 数据结构
  • 计算机组成原理
  • HTML5
  • Electron
  • 日记便签
  • 前端导航
GitHub (opens new window)
  • HTML
  • CSS
  • JavaScript
  • 前端知识
  • Vue
  • MarkDown
  • git
  • Node.js
  • Linux
  • 51单片机
  • 四级
  • 第一学期课程
  • 操作系统
  • 计算机网络
  • 数据结构
  • 计算机组成原理
  • HTML5
  • Electron
  • 日记便签
  • 前端导航
GitHub (opens new window)
  • Vue

  • Nuxt

  • Echarts

  • Node

  • git

  • express

  • 微信小程序

  • Spring

  • 后端知识

  • Markdown

  • project

    • 黑马头条

    • 小兔鲜

      • 项目介绍
      • 项目搭建
      • 外观搭建
      • 首页主体
        • 首页主体
          • 左侧入口
          • 弹层展示
          • 处理品牌
        • 加载骨架效果
        • 轮播图
        • 公共面板
          • home-panel主面板
          • home-new
          • home-hot
        • Vue动画
          • 挂架组件
        • 数据懒加载
          • 官方示例
          • 实现
        • 热门品牌
          • 布局渲染
          • 加入骨架屏
        • 商品区块
          • 修改可视区阀值
        • 图片懒加载
          • IntersectionObserve
          • 封装
          • 懒加载失败情况
      • 面包屑组件封装
      • 分类模块
      • 登录模块
    • saas中台管理项目

  • 自用文档查询

  • 框架和软件
  • project
  • 小兔鲜
yuadh
2022-05-02
目录

首页主体

# 首页主体

创建轮播图和首页入口组件

home-category.vue

# 左侧入口

将发送请求的 topCategory 的数据 拿到

export default{
  setup(){
    const store = useStore();
    const brand = reactive({
        id:'brand',
        name:'品牌',
        children:[{id:'brand-children',name:'品牌推荐'}]
    })
    const menuList = computed((){
      const list = store.state.category.list.map(item=>{
      	return{
         id:item.id,  
         name:item.name,
         children:item.chilren&&item.children.slice(0,2)    
    	 goods:itea.goods
        }                        
      })
      list.push(brand)
	  return list
    })
    return {meanList}
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

使用

<ul class="menu">
    <li v-for="item in menuList" :key="item.id">
    	<RouterLink :to="`/category/${item.id}`">{{item.name}}</RouterLink>
        <template v-if="item.children">
        	<RouterLink
             v-for="sub in item.children"
             :key="sub.id"
             :to="`/category/${sub.id}`">
            	{{sub.name}}
            </RouterLink>
        </template>
    </li>
</ul>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 弹层展示

  1. 写样式

        <!-- 弹层 -->
        <div class="layer">
          <h4>分类推荐 <small>根据您的购买或浏览记录推荐</small></h4>
          <ul>
            <li v-for="i in 9" :key="i">
              <RouterLink to="/">
                <img src="https://yanxuan-item.nosdn.127.net/5a115da8f2f6489d8c71925de69fe7b8.png" alt="">
                <div class="info">
                  <p class="name ellipsis-2">【定金购】严选零食大礼包(12件)</p>
                  <p class="desc ellipsis">超值组合装,满足馋嘴欲</p>
                  <p class="price"><i>¥</i>100.00</p>
                </div>
              </RouterLink>
            </li>
          </ul>
        </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  2. 使用计算属性得到弹出层推荐商品数据

    const categoryId = ref(null)
    const currCategory = computed(()=>{
        return menuList.value.find(item=>item.id=category.value)
    })	
    return {menuList,categoryId,currCategory}
    
    1
    2
    3
    4
    5

    分类id CategoryId 传入

    <li v-for = "item in menuList" @mouseenter = "categoryId = item.id"
    
    1
  3. 铺设数据

        <!-- 弹层 -->
        <div class="layer">
          <h4>分类推荐 <small>根据您的购买或浏览记录推荐</small></h4>
          <ul v-if="currCategory && currCategory.goods && currCategory.goods.length">
            <li v-for="item in currCategory.goods" :key="item.id">
              <RouterLink to="/">
                <img :src="item.picture" alt="">
                <div class="info">
                  <p class="name ellipsis-2">{{item.name}}</p>
                  <p class="desc ellipsis">{{item.desc}}</p>
                  <p class="price"><i>¥</i>{{item.price}}</p>
                </div>
              </RouterLink>
            </li>
          </ul>
        </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

# 处理品牌

注意

因为 vue3 中的 setup() 函数是立即执行的,所以在发送请求时

async setup(){await API()} 会有问题 ,尽量避免使用 async

const brand = reactive({
    id:'brand',
    name:'品牌',
    children:[{id:'brand-children',name:'品牌推荐'}],
    brands:[]
})

findBrand().then(data=>{
    brand.brands = data.result
})
1
2
3
4
5
6
7
8
9
10

使用返回的 Promise

  1. 编写样式
<ul>
  <li class="brand" v-for="i in 6" :key="i">
    <RouterLink to="/">
      <img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/brand_goods_1.jpg" alt="">
      <div class="info">
        <p class="place"><i class="iconfont icon-dingwei"></i>北京</p>
        <p class="name ellipsis">DW</p>
        <p class="desc ellipsis-2">DW品牌闪购</p>
      </div>
    </RouterLink>
  </li>
</ul>
1
2
3
4
5
6
7
8
9
10
11
12
  1. 发送请求 ,修改显示逻辑

    <h4 v-if="currCategory">{{currCategory.id==='brand'?'品牌':'分类'}}推荐 <small>根据您的购买或浏览记录推荐</small></h4>
    
    1

    品牌分类显示逻辑

    <ul v-if="currCategory && currCategory.goods && currCategory.goods.length">
            <li v-for="item in currCategory.goods" :key="item.id">
              <RouterLink to="/">
                <img :src="item.picture" alt="">
                <div class="info">
                  <p class="name ellipsis-2">{{item.name}}</p>
                  <p class="desc ellipsis">{{item.desc}}</p>
                  <p class="price"><i>¥</i>{{item.price}}</p>
                </div>
              </RouterLink>
            </li>
          </ul>
    +      <ul v-if="currCategory && currCategory.brands && currCategory.brands.length">
    +        <li class="brand" v-for="item in currCategory.brands" :key="item.id">
    +          <RouterLink to="/">
    +            <img :src="item.picture" alt="">
    +            <div class="info">
    +              <p class="place"><i class="iconfont icon-dingwei"></i>{{item.place}}</p>
    +              <p class="name ellipsis">{{item.name}}</p>
    +              <p class="desc ellipsis-2">{{item.desc}}</p>
    +            </div>
    +          </RouterLink>
    +        </li>
    +      </ul>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    不同的页面展示处理

    激活样式逻辑编写

    &:hover,&:avtive{
        background:@xtxColor;
    }
    
    1
    2
    3

    激活条件

    <li :class="{active:categoryId===item.id}"
    
    1

    移除条件

    <div class="home-category" @mouseleave="categoryId=null"></div>
    
    1

# 加载骨架效果

为了在加载的过程中等待效果更好,封装一个骨架屏组件

使用插件注册的方式,对骨架进行注册使用

library/index.js

//在 vue2 中的写法
// 导入对象 - install函数 - 传入Vue的构造函数 - Vue的基础之上扩展
//在 vue3 中的写法
// 导入对象 - install函数 - 传入了app应用实例 - app基础之上扩展
1
2
3
4
import xtxSkeleton from './xtx-skeleton.vue'
export default{
    install(app){
        
    }
}
1
2
3
4
5
6

使用插件

import UI from '@/components/library'
createApp(APP).use(UI)
1
2

# 轮播图

home/components 下装载轮播图的 HomeBanner 容器作为组件

轮播图组件 carousel.vue

//HomeBanner

1
2

carousel.vue

接收的参数 props

sliders:{
    type:Array,
    default:()=>[]
},
autoPlay:{
    type:Boolean,
    default:false
},
duration:{
    type:Number,
    default:3000
}
1
2
3
4
5
6
7
8
9
10
11
12

控制轮播图的变量

  • index 控制图片显示的索引

  • timer

  • autoPlayFn 自动播放函数

    const autoPlayFn=()=>{
        clearInterval();
        timer = setInterval(()=>{
            index.value++;
            if(index.value>=props.sliders.length){
                index.value = 0
            }
        },props.duration)
    }
    // 监听 sliders 数据变化,判断如果有数据且 autoPlay 为true
    watch(()=>props.sliders,(newVal)=>{
        if(newVal.length&&props.autoPlay){
            autoPlayFn()
        }
    },{immediate:true})
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
  • stop 停止轮播图

    const stop = ()=>{
        if(timer) clearInterval(timer)
    }
    
    1
    2
    3
  • start 开始轮播图

    const start = ()=>{
        if(props.sliders.length&&props.length){
            autoPlayFn()
        }
    }
    
    1
    2
    3
    4
    5
  • toggle() 切换 index

    const toggle = (step) => {
        const newIndex = index.value + step
        if(newIndex>props.sliders.length-1){
           index.value = 0
            return
        }
        if(newIndex<0){
           index.value = props.sliders.length-1
            return
        }
        index.value = newIndex
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

组件卸载时清楚定时器

onUnmouted(()=>{
    clearInterval(timer)
})
1
2
3

# 公共面板

首页展示模块的面板封装 ,方便复用

# home-panel主面板

<slot name="right"> 具名插槽

<slot> 默认插槽

// 有 一级标题 和 二级标题
1

具名插槽对面板顶部标题栏的显示

默认插槽是对具体面板内容的显示

home-new 新鲜好物模块去套用 面板模块

# home-new

新鲜好物

# home-hot

人气推荐

# Vue动画

官方内置 transition 组件

<transition>
	<div>
        100px
    </div>
</transition>
    


<style>
    .v-enter-from{
        opacity:0;
    }
    .v-enter-active{
        transition:all 2s;
    }
    .v-enter-to{
        opacity:1;
    }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

如果需要多组动画 , 需要给transition加上名字

<transition name="xxx">

# 挂架组件

# 数据懒加载

新建文件 hooks ,为数据懒加载创建工具模块

使用 vueuse 第三方组合API模块,封装自己的数据懒加载的工具类

# 官方示例

<div ref="target">
  <h1>Hello world</h1>
</div>

<script>
import { ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'

export default {
  setup() {
    const target = ref(null)
    const targetIsVisible = ref(false)

    const { stop } = useIntersectionObserver(
      target,
      ([{ isIntersecting }], observerElement) => {
        targetIsVisible.value = isIntersecting
      },
    )

    return {
      target,
      targetIsVisible,
    }
  },
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

当内容移至到可视区域时触发 stop 停止监听

  • target 监听的目标 DOM 容器
  • isIntersecting 是否在可视区域
  • obseverElement 监听的元素
  • ref 通过对 targer 的放回,绑定了其容器的懒加载

# 实现

/**
 * 数据懒加载
 * @param {Element} target - Dom对象
 * @param {Funciton} apiFn - API函数
 */
import {useIntersectionObserve} from '@vueuse/core'
import {ref} from 'vue'
export const useLazyData = (apiFn)=>{
    const target = ref(null)
    const result = ref([])
    const {stop} = useIntersectionObserver(
    	target,
        ([isIntersecting],observerElement)=>{
            if(isIntersecting){//监听到内容移至到可视区域
                stop()		   //停止监听
                apiFn().then(data=>{//调用API
                    result.value = data.value
                })
            }
        }
    )
    return {target,result}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 热门品牌

# 布局渲染

# 加入骨架屏

# 商品区块

用到的组件

  • home-good 商品块组件
  • home-product 整个商品区块 里面使用了面板模块 home-panel

# 修改可视区阀值

threshold : 临界值

setIntersectionObserve({
    target,
    ()=>{
      //...
	},
    // 配置选项,相交的比例大于 0 就触发
    {
      threshold:0
    }
})
1
2
3
4
5
6
7
8
9
10

# 图片懒加载

# IntersectionObserve

# 封装

先存储图片地址,不放在 src 中 ,当图片进入可视区域,将存储的图片地址设置给图片元素

import defaultImg from '@/assets/images/200.png'
// 指令
const defineDirective = (app) => {
  // 图片懒加载指令
  app.directive('lazyload', {
    mounted (el, binding) {
      const observer = new IntersectionObserver(([{ isIntersecting }]) => {
        if (isIntersecting) {
          observer.unobserve(el)
          el.onerror = () => {
              el.src = defaultImg
          }  
          el.src = binding.value
        }
      }, {
        threshold: 0.01
      })
      observer.observe(el)
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 懒加载失败情况

给 el 元素加上默认的图片

编辑 (opens new window)
上次更新: 2023/02/07, 14:51:48
外观搭建
面包屑组件封装

← 外观搭建 面包屑组件封装→

Theme by Vdoing | Copyright © 2021-2023 yuadh
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×