电商前台项目小结

193 阅读10分钟

基于V3+TS+pinia开发

项目初始化:配置@符号
1.tsconfig.json文件下进行配置:
    目的:让v3认识@符号
    实现(在"compilerOptions"下添加两项):
        "compilerOptions": {
            "baseUrl": ".",
            "paths": {
                "@/*": ["src/*"]
            }
        }
2.vite.config.ts下进行配置:
    目的:配置绝对路径赋予真实意义
    实现:const path = require('path') // 导入node下的方法,获取绝对路径
    resolve: {
        alias: {
            "@": path.resolve(__dirname, "./src")
        }
    }
    
    
在.vue文件中自动导入less文件:
在vite.config.ts下进行配置:
    css: {
        preProcessorOptions: {
            less: {
                additionalData: `
                    // 默认.vue文件要导入的.less文件
                    @import: "@/xxxx.less";
                    @import: "@/xxxxxx.less"
                `
            }
        }
    }
    
    
 为vue3的构造器添加功能:封装全局组件插件;全局组件类型为any,解决办法:
     src目录下创建一个global.d.ts文件:
     import components1 from "./xxx/xxx.vue"
     declare module 'vue' {
         export interface GlobalComponents {
             // 导入的全局组件名: typeof 导入的全局组件 // 将该全局组件的类型赋给该组件
             components1: typeof components1;
         }
     }


吸顶效果:
    思路,隐藏一个导航栏,在滚动到指定位置淡入,离开淡出,此时主要时获取页面被卷曲的头部,
window.onscroll手写事件代码冗余.
    可下载插件:npm i @vueuse/core;
    在指定位置:
    import { useWindowScroll } from '@vueuse/core'
    const { y } = useWindowScroll()  // 此时y即被卷曲的头部
    
    
泛型设置默认值:
    vite.config.ts配置文件下配置:
    plugins: [vue({reactivityTransform: true})]
    
    
vue3中给标签定义ref属性并使用(忘了):
    <img ref="imgRef" src="#">
    import { ref } from 'vue'
    // const ref名 = ref<标签类型 | null>(null)
    const imgRef = ref<HTMLImageElement | null>(null)
    
    
图片懒加载:
    它是一种常见的性能优化的手段
    主要思路:src属性放一个假图片;自定义一个属性放真图片链接;
    监听图片是否再可视区域内,是则将真图片替换至src属性上。
    关键点:侦听元素标签是否在可视区域内
    实现:vue自带的IntersectionObserver接口;
    
    
    更方便的方法:
    导入:import { useIntersectionObserver } from '@vueuse/core'
    vueuse插件库的useIntersectionObserver(target,  回调函数)
    // const { stop } = useIntersectionObserver(target,  回调函数)  // stop()用于停止侦听
    // target为要监听的元素;isIntersecting为boolean值true时在可视区
    const { stop } = useIntersectionObserver(target,  ([{ isIntersecting }]) => {})
    当target可见时,触发函数,isIntersecting为true,将图片链接赋给指定元素的src属性,结束监听。
    
    // 此时封装全局自定义指令,通过调用全局自定义指令实现图片懒加载;
    // 若是图片链接有误,可使用默认图片替代,用onerror事件进行监听
    
    
数据懒加载:
    主要思路:当块级区域出现在可视区后,发送ajax请求,对数据实现渲染,若未达到则不发送请求;
    给块级区域定义一个ref属性,监听该区域是否在可视区域。
    关键点实现:
    导入:import { useIntersectionObserver } from '@vueuse/core'
    vueuse插件库的useIntersectionObserver(target,  回调函数)
    // const { stop } = useIntersectionObserver(target,  回调函数)  // stop()用于停止侦听
    // target为要监听的元素;isIntersecting为boolean值true时在可视区
    const { stop } = useIntersectionObserver(target,  ([{ isIntersecting }]) => {})
    当target可见时,触发函数,isIntersecting为true,发送ajax,结束监听(stop())。
    
    
vue内置组件(transition):
    场景:对需要渐变效果的标签使用,默认插槽获取<transition name="ph"> // 渐变效果的块级区域 
</transition>内的值,并对其实现渐变效果。
    触发条件:v-if、v-show、组件切换时
    使用方式:
        六个类(name值-xx-xx):ph-enter-active、ph-leave-active; 
        // 分别在显示过程、隐藏过程生效(全程)
        ph-enter-from、ph-enter-to // 分别对应:显示过程开始与完成时的类名
        ph-leave-from、ph-leave-to // 分别对应:隐藏过程开始与完成时的类名
        
        transition属性(渐变时间、方式、哪些要渐变)定义在ph-enter-active、ph-leave-active中;
        具体的变化写在:ph-xxx-from、ph-xxx-to (xxx: enter、leave)
        
        
自定义面包屑组件:
    关键点:每一项间的分隔符&每一项的路由路径
        1.分隔符:
        面包容器内容为默认插槽,分隔符先由父组件绑定动态属性进行传值(可不传值,此时设置默认为 ''),
    面包容器接收到该值后通过跨组件传值(provide、inject)实现向每一项面包屑进行(分隔符)传递,
    若此时数据为''则使用默认字体图标否则使用传递过来的值。
        2.路由路径
            面包屑容器内容为默认插槽,故面包屑<Bread><BreadItem /><BreadItem /></Bread>都是以内容
        形式存在于容器中,此时路径即在父组件中进行传递,子组件通过defineProps接收后写入
        <router-link to="传递过来的值" />中,若未传值则该项默认当作span,显示当前路径,不可跳转。
        
    
watch VS watchEffect:
    watch使用时相对复杂:
        watch(参数1, 参数2, 参数3)
        参数1:依赖;即要监听的数据:可以为数组、值、函数
        参数2:回调函数:参数有新、旧两个值,函数体可进行相应操作;数据更新是回调函数触发
        参数3:对象:两个属性(boolean值) immediate:是否自动触发一次、deep:是否开启深度侦听
        
    watchEffect的特点:
        // 1.不关心新旧值,回调函数无参数
        // 2.会自动调用一次,并收集自身依赖(依赖可为多个)
        // 3.依赖项变化时,自动执行回调
        
        
倒计时工具函数的封装(存放至utils目录下,多项目可用):
    使用到插件@vueuse/core
    import { useIntervalFn } from "@vueuse/core"
   // const { pause:暂停, resume:开始 } = useIntervalFn(() => { // 要执行的操作 },
   间隔时间, { immediate: 是否直接执行 })
    import { ref } from 'vue'
    
    // 倒计时工具
    function useCountDown() {
        const count = ref(0) // 初始值为0,渲染至页面的倒计时读秒
        const { pause, resume } = useIntervalFn(() => {
            if(count.value === 0) {
                // 停止
                pause()
            }
            // 倒计时效果
            count.value--
        }, 1000, {immediate: false})
        // 开始倒计时 num表示倒计时的时长(秒)
        function start(num: number) { 
            // 从何时开始倒数
            count.value = num
            // 开始计时
            resume()
        }
        return { count, start } // count为页面显示的动态数字(剩余时间);start为开启计时的函数
    }
    
    // 使用
    const { count, start } = useCountDown()
    start(60) // 开启60秒倒计时
    
    <span>{{ `剩余${count}秒` }}
    
    
商品放大镜组件的封装:
    关键点:商品图片+遮罩层+等比放大后的图片;放大镜要用到定位(position)
    技术:获取到鼠标在当前所在元素的(x, y)
    此时可使用@vueuse/core中封装的方法实现
    import { useMouseInElement } from '@vueuse/core'
    // isOutSide: 是否在元素区域外boolean值;elementX、elementY分别表示在该元素区域的x、y轴
    const { isOutside, elementX, elementY } = useMouseInElement(target) // target为当前元素
    // position计算属性
    const position = computed(() => {
      let x = elementX.value - 遮罩层宽度的一半
      let y = elementY.value - 遮罩层高度的一半
      x = x < 0 ? 0 : (x > (target元素宽度 - 遮罩层宽度的一半) ? 200 : x)
      y = y < 0 ? 0 : (y > (target元素高度 - 遮罩层高度的一半) ? 200 : y)
      return {x, y}
    })
    将计算属性值赋给遮罩层的(top、left)并等比放大赋值给大图的(left、top)此时为**负值**(反向移动)
    
    
弹出消息框组件:
    关键点:不同类型下的主题色,弹出框内容与弹出时机,过渡动画效果(transition标签的使用)
    难点:因为在弹出时机上,直接在template标签中使用,此时位置固定,使用限制较大
    (无法随时显示,比如拦截报错给提示就比较麻烦)
    在组件目录下新建index.ts文件
    实现:此时可以使用"vue"的内置api导入:import { h, render } from 'vue'
    import Message from './xx/xx.vue' // 导入弹出框组件
    const div = document.createElement('div') // 先创建一个标签
    div.setAttribute('class', '具体类名') // 为新增的标签绑定类等属性
    document.body.appendChild(div) // 将创建的元素添加至body的最后面
    export default function message(obj: {type: 'success' | 'error' | 'warning', text: string}) {
        // 创建一个虚拟dom;h(type, props:标签属性, children:标签内容)
        const vNode = h(Message, {type: obj.type, text: obj.text}) // 此时text作为属性传递给组件并渲染
        render(vNode, div) // 将虚拟dom即message组件转为真实dom并渲染至body的最后
    }
    
使用:
    import Message from '路径'
    将拦截器原本alert()报错提示改为Message({ type: "success", text: "成功" })等
    Message.vue组件中接收type与text并动态渲染相应样式及内容
    
    
第三方登录:
    思路:
        1.注册资格
        2.点击相应按钮,跳转至第三方登录页(第三方提供)
        3.登陆后回到自身页面(该页面与第三方进行约定)
        4.在约定页面进行判断(通过特定id):
            1.是否之前登陆过,若登陆过直接跳转至首页
            2.未登陆过但注册过账号,此时进行绑定,下次在登陆则为效果1
            3.未登陆过也未注册过帐号,此时可选择注册,并会将当前第三方账号与当前注册账号绑定,之后效果同1
    
    关键点(难点):在开发时未上线,无法直接访问第三方登录页,无法到达约定页面
    解决方式:C:\Windows\System32\drivers\etc的hosts文件添加:127.0.0.1 约定url
    原理:DNS域名解析过程中直接在本机将约定url路径解析为127.0.0.1,从而使其正常访问
    
    
省市区三项组件的封装:
    关键点:首先获取到一个树状结构的省市县数组;创建两个数组接收,一个用于渲染,一个用于在修改完
    成后恢复数据(恢复数据原因:使用v-show显示隐藏,故数据缓存),创建一个对象,获取每一次被点击的
    name与level,初次(level=0)渲染最外层数据,并在点击其中指定项时获取到item的areaList且为对象的
    省类赋值 ==> 当(level=1)重复操作 ==> 当(level=2)时子向父传值,将收集到的省市县名称与level全
    部传递,便于父级传递数据渲染到子级,若在点击过程中点击了外部区域,则隐藏省市县区域并恢复数据
    若父组件传递了地址,则用父组件的地址。
    
    刚需:后台或其他位置得到一个所有省市县的数组
    
    
单选框的封装:
    关键点:只有一项能被选中&每项的内容
        1.只有一项能被选中
            类似面包屑组件,创建一个大项,通过插槽装小项,而大项中的v-model则是获取被选中的
            值,此时被选中的值唯一,小项中的数据通过v-for循环渲染出来,借助点击事件监听并传递当
            前值(在大项中声明一个函数再将函数传递给小项,小项接收到被点击项的数据再调用大项中的
            函数,并将数据作为参数传递给大项,大项用defineEmits传递给父组件,从使被选中项唯一)
        2.每项的内容,都是通过默认插槽实现(在小项双标签的内容既是每项的内容)
            
        
      
    
    

踩过的坑

  1. V3中TS+pinia使用时:pinia的计算属性要先确定返回值类型,再return
  2. try、catch处理过的函数在被调用时,再次使用try、catch时即使出错了也不会走catch因为错误在之前已经被处理了
    1. function fn() {try { // 执行的操作 } catch(err) {}}
    2. function fn2() { try { fn() // 此时调用fn } catch(err) {} } // 若此时fn出错,也已经在fn中处理过了,所以fn2中不会捕捉到错误,故会往下执行。
  3. provide与inject在provide传递数据时,若非响应式的数据(非ref,reactive声明的变量)在inject中接收时均为常量,切无响应式特性,所以要想有动态切换效果,一定要使用到响应式

忘掉的点

  1. route获取路径时:path不携带参数;fullpath携带参数