Vue3 project skills (updating ing)

Article directory

  • axios package
    • http.js
    • testAPI.js
    • main.js test
    • If multiple baseURLs are required in the project
  • Import scss files automatically
    • case file
    • Use Cases
  • Introduce aliyun icon library
    • first look at the effect
    • View official website documents
    • import and use
  • Vueuse implementation – adsorption navigation interaction
    • Install
    • the case
  • Requests, data, and encapsulation shared by multiple components to pinia
    • the case
    • Called from parent component
    • Applied in subcomponents
  • Resource lazy loading case (vue3 custom instruction & amp;vueuse)
    • case simulation
      • main.js
      • components
    • Lazy loading module optimization
      • main.js
    • Lazy loading code optimization
      • Effect
  • When the routing url changes, the current routing is forced to be destroyed and rebuilt, which can be used for primary or secondary routing
    • The method of using key (simple and easy to use)
      • Effect demonstration
    • onBeforeRouteUpdate navigation hook
      • sample code
  • Drag the webpage to the bottom and load data resources wirelessly
    • v-infinite-scoll
    • Event case
    • Effect demonstration
  • Make the user view jump to the top when the route is switched
    • edit router.js
    • Effect view
  • Use of third-party components
    • Sku component case
      • Sku component concept
      • Component Usage Tips
  • token timeliness processing
    • Encounter problems
    • solution
    • Test token invalidation effect, manually modify token
    • Response interceptor handles 401
    • test

Axios package

http.js

//Axios basic encapsulation
import axios from 'axios'

const httpInstance = axios.create({<!-- -->
    baseURL: 'http://pcapi-xiaotuxian-front-devtest.iheima.net',
    timeout: 5000
})

//Interceptor
// add request interceptor
httpInstance.interceptors.request.use(function (config) {<!-- -->
    return config;
}, function (error) {<!-- -->
    return Promise. reject(error);
});

// add response interceptor
httpInstance.interceptors.response.use(function (response) {<!-- -->
    return response;
}, function (error) {<!-- -->
    return Promise. reject(error);
});

export default httpInstance

testAPI.js

import httpInstance from "@/utils/http";

export function getCategory(){<!-- -->
   return httpInstance({<!-- -->
        url:'home/category/head'
    })
}

main.js test

//Test interface function
import {<!-- -->getCategory} from "@/apis/testAPI";
getCategory().then (res => {<!-- -->
    console. log(res)
})

If multiple baseURLs are required in the project

Automatically import scss files

Case file

$xtxColor: #27ba9b;
$helpColor: #e26237;
$sucColor: #1dc779;
$warnColor: #ffb302;
$priceColor: #cf4444;

Use case

<!--setup-switch: Allow writing combined API in script-->
<script setup>

</script>

<template>
  <div>
    <el-button type="primary">Primary</el-button>
    <!--Level 1 routing exit-->
    <RouterView />
    <div class="test">
      test scss
    </div>
  </div>
</template>

<style scoped lang="scss">
.test{<!-- -->
  color:$warnColor;
}
</style>

Introduce aliyun icon library

Look at the effect first

View official website documents

Official document description



Import and use

If there is no index.html, just create a new one

Note that the content needs to be replaced

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vite App</title>
    <link rel="stylesheet" href="//at.alicdn.com/t/font_2143783_iq6z4ey5vu.css">
</head>

<body>
<div id="app">
</div>
<script type="module" src="/src/main.js"></script>
</body>

</html>

use


vueuse implementation – adsorption navigation interaction

Install

npm i @vueuse/core

Case

In this case, a new component is generated and rendered on the page according to certain conditions
Specifically, when the scroll position y is greater than 78, the element adds the show class.

In the CSS section, there is a CSS class definition named .show. This class is used to apply styles when the app-header-sticky element has the show class. These styles include a 0.3-second linear transition for the transition, a default value for the transform, and an opacity of 1.

Therefore, depending on whether the scroll position y is greater than 78, the app-header-sticky element will dynamically add or remove the show class, triggering transition effects and style changes.

core code

<script setup>
// vueUse
import {<!-- --> useScroll } from '@vueuse/core'
const {<!-- --> y } = useScroll(window)
</script>

<template>
  <div class="app-header-sticky" :class="{show:y > 78}">
    <div class="container">
      <RouterLink class="logo" to="/" />
      <!-- Navigation area -->
      <ul class="app-header-nav">
        <li class="home">
          <RouterLink to="/">Homepage</RouterLink>
        </li>
        <li>
          <RouterLink to="/">Home</RouterLink>
        </li>
        <li>
          <RouterLink to="/">Gourmet</RouterLink>
        </li>
        <li>
          <RouterLink to="/">Apparel</RouterLink>
        </li>
        <li>
          <RouterLink to="/">Mother and Child</RouterLink>
        </li>
        <li>
          <RouterLink to="/">personal care</RouterLink>
        </li>
        <li>
          <RouterLink to="/">Selection</RouterLink>
        </li>
        <li>
          <RouterLink to="/">Digital</RouterLink>
        </li>
        <li>
          <RouterLink to="/">Motion</RouterLink>
        </li>
        <li>
          <RouterLink to="/">Miscellaneous</RouterLink>
        </li>
      </ul>

      <div class="right">
        <RouterLink to="/">Brand</RouterLink>
        <RouterLink to="/">Theme</RouterLink>
      </div>
    </div>
  </div>
</template>


<style scoped lang='scss'>
.app-header-sticky {<!-- -->
  width: 100%;
  height: 80px;
  position: fixed;
  left: 0;
  top: 0;
  z-index: 999;
  background-color: #fff;
  border-bottom: 1px solid #e4e4e4;
  // This is the key style!!!
  // State 1: translate its own height up + fully transparent
  transform: translateY(-100%);
  opacity: 0;

  // state two: remove translation + full opacity
   &.show {<!-- -->
    transition: all 0.3s linear;
    transform: none;
    opacity: 1;
  }

  .container {<!-- -->
    display: flex;
    align-items: center;
  }

  .logo {<!-- -->
    width: 200px;
    height: 80px;
    background: url("@/assets/images/logo.png") no-repeat right 2px;
    background-size: 160px auto;
  }

  .right {<!-- -->
    width: 220px;
    display: flex;
    text-align: center;
    padding-left: 40px;
    border-left: 2px solid $xtxColor;

    a {<!-- -->
      width: 38px;
      margin-right: 40px;
      font-size: 16px;
      line-height: 1;

       &:hover {<!-- -->
        color: $xtxColor;
      }
    }
  }
}

.app-header-nav {<!-- -->
  width: 820px;
  display: flex;
  padding-left: 40px;
  position: relative;
  z-index: 998;

  li {<!-- -->
    margin-right: 40px;
    width: 38px;
    text-align: center;

    a {<!-- -->
      font-size: 16px;
      line-height: 32px;
      height: 32px;
      display: inline-block;

       &:hover {<!-- -->
        color: $xtxColor;
        border-bottom: 1px solid $xtxColor;
      }
    }

    .active {<!-- -->
      color: $xtxColor;
      border-bottom: 1px solid $xtxColor;
    }
  }
}
</style>

Requests, data shared by multiple components, encapsulated into pinia

Case

Encapsulate the following request

import {<!-- --> ref } from 'vue'
import {<!-- --> defineStore } from 'pinia'
import {<!-- --> getCategoryAPI } from '@/apis/layout'
export const useCategoryStore = defineStore('category', () => {<!-- -->
    // Data management of the navigation list
    // state navigation list data
    const categoryList = ref([])

    // action method to get navigation data
    const getCategory = async () => {<!-- -->
        const res = await getCategoryAPI()
        categoryList.value = res.data.result
    }

    return {<!-- -->
        categoryList,
        getCategory
    }
})

Called in parent component

//trigger the action to get the navigation list
import {<!-- -->useCategoryStore} from "@/stores/categoryStore";
import {<!-- -->onMounted} from "vue";

const categoryStore = useCategoryStore();

onMounted(() => categoryStore. getCategory())

Application in subcomponents

Use the state after changing the data

<script setup>
import {<!-- --> useCategoryStore } from '@/stores/categoryStore'
const categoryStore = useCategoryStore()
</script>


<template>
  <ul class="app-header-nav">
    <li class="home">
      <RouterLink to="/">Homepage</RouterLink>
    </li>
    <li class="home" v-for="item in categoryStore.categoryList" :key="item.id">
      <RouterLink active-class="active" :to="`/category/${item.id}`">
        {<!-- -->{<!-- --> item.name }}
      </RouterLink>
    </li>
  </ul>
</template>

Resource lazy loading case (vue3 custom instruction & amp;vueuse)

Case simulation

main.js

import {<!-- --> useIntersectionObserver } from '@vueuse/core'
//Define global directives
app.directive('img-lazy',{<!-- -->
    mounted(el,binding){<!-- -->
        //el: the element img bound by the instruction
        // binding: binding.value command is equal to the value of the expression bound after the sign image url
        console.log(el,binding.value)

        useIntersectionObserver(
            el,
            ([{<!-- --> isIntersecting }]) => {<!-- -->
                console. log(isIntersecting)
                if(isIntersecting){<!-- -->
                    // enter the viewport area
                    el.src = binding.value
                }
            },

        )
    }
})

Component

core content

<script setup>
import HomePanel from './HomePanel.vue'
import {<!-- -->getHotAPI} from '@/apis/home'
import {<!-- -->ref} from 'vue'

const hotList = ref([])
const getHotList = async () => {<!-- -->
  const res = await getHotAPI()
  console. log(res)
  hotList.value = res.data.result
}
getHotList()

</script>

<template>
  <HomePanel title="Popular recommendation" sub-title="Popular hot style, not to be missed">
    <template #main>
      <ul class="goods-list">
        <li v-for="item in hotList" :key="item.id">
          <RouterLink to="/">
<!-- <img :src="item.picture" alt="">-->
            <img v-img-lazy="item. picture" alt="">
            <p class="name">{<!-- -->{<!-- --> item.title }}</p>
            <p class="desc">{<!-- -->{<!-- --> item.alt }}</p>
          </RouterLink>
        </li>
      </ul>
    </template>
  </HomePanel>
</template>

<style scoped lang='scss'>
.goods-list {<!-- -->
  display: flex;
  justify-content: space-between;
  height: 426px;

  li {<!-- -->
    width: 306px;
    height: 406px;
    transition: all .5s;

     &:hover {<!-- -->
      transform: translate3d(0, -3px, 0);
      box-shadow: 0 3px 8px rgb(0 0 0 / 20%);
    }

    img {<!-- -->
      width: 306px;
      height: 306px;
    }

    p {<!-- -->
      font-size: 22px;
      padding-top: 12px;
      text-align: center;
    }

    .desc {<!-- -->
      color: #999;
      font-size: 18px;
    }
  }
}
</style>

Lazy loading module optimization

separate file

// Define lazy loading plugin

import {<!-- -->useIntersectionObserver} from "@vueuse/core";

export const lazyPlugin = {<!-- -->
    install(app) {<!-- -->
        //lazy loading instruction logic
        app.directive('img-lazy', {<!-- -->
            mounted(el, binding) {<!-- -->
                //el: the element img bound by the instruction
                // binding: binding.value command is equal to the value of the expression bound after the sign image url
                console. log(el, binding. value)

                const {<!-- -->stop} = useIntersectionObserver(
                    el,
                    ([{<!-- -->isIntersecting}]) => {<!-- -->
                        console. log(isIntersecting)
                        if (isIntersecting) {<!-- -->
                            // enter the viewport area
                            el.src = binding.value
                            // Don't re-judge after loading
                            stop()
                        }
                    },
                )
            }
        })
    }
}

main.js

Lazy loading code optimization

// Define lazy loading plugin

import {<!-- -->useIntersectionObserver} from "@vueuse/core";

export const lazyPlugin = {<!-- -->
    install(app) {<!-- -->
        //lazy loading instruction logic
        app.directive('img-lazy', {<!-- -->
            mounted(el, binding) {<!-- -->
                //el: the element img bound by the instruction
                // binding: binding.value command is equal to the value of the expression bound after the sign image url
                console. log(el, binding. value)

                const {<!-- -->stop} = useIntersectionObserver(
                    el,
                    ([{<!-- -->isIntersecting}]) => {<!-- -->
                        console. log(isIntersecting)
                        if (isIntersecting) {<!-- -->
                            // enter the viewport area
                            el.src = binding.value
                            // Don't re-judge after loading
                            stop()
                        }
                    },
                )
            }
        })
    }
}

Effect

No reloading after loading, saving resources

When the routing url changes, the current routing is forced to be destroyed and rebuilt, which can be used for primary or secondary routing

The method of using key (simple and easy to use)

 <RouterView :key="$route.fullPath"/>

Effect demonstration


onBeforeRouteUpdate navigation hook

  • Executed before each route update

Example code

 onBeforeRouteUpdate(
        (to) => {<!-- -->
            //to represents the target object
            console. log(to)
            getCategory(to.params.id)
        })

Our original method needs to re-accept parameters

 //id is the default parameter
    const getCategory = async (id = route.params.id) => {<!-- -->
        const res = await getTopCategoryAPI(id)
        categoryData.value = res.data.result
    }

Drag the webpage to the bottom, wirelessly load data resources

Use a property of element plus

v-infinite-scoll

 <div class="body" v-infinite-scroll="load" :infinite-scroll-disabled="disabled">
        <!-- product list -->
        <goods-item v-for="goods in goodList" :goods="goods" :key="goods.id"></goods-item>
      </div>

Event case

// load more
const disabled = ref(false)
const load = async () => {<!-- -->
  console.log('load more data')
  // Get the data of the next page
  reqData.value.page++
  const res = await getSubCategoryAPI(reqData. value)
  goodList.value = [...goodList.value, ...res.data.result.items]
  // After loading, stop listening
  if (res.result.items.length === 0) {<!-- -->
    disabled. value = true
  }
}

Effect demonstration

Make the user view jump to the top when the route is switched

Edit router.js

 scrollBehavior(){<!-- -->
        return {<!-- -->top:0}
    }

Effect view

Use of third-party components

Sku component case

Sku component concept

Stock keeping unit (English: stock keeping unit, SKU/s?keju?/), also translated as stock keeping unit, is an accounting term defined as inventory management
The smallest available unit, for example, a SKU in textiles usually refers to specifications, colors, and styles, while in chain retail stores, a single product is sometimes referred to as an SKU

The role of the SKU component: output the product specifications selected by the current user, and provide data information for the operation of adding to the shopping cart

Component usage skills

Question: In actual work, I often encounter components written by others, and I am familiar with a third-party component. What should I focus on first?
Answer: props and emit, props determines what data the current component receives, and emit determines what data will be produced

Token timeliness processing

I have a problem

The validity of the token can be maintained for a certain period of time. If the user does not perform any operations for a period of time, the token will become invalid. If the invalid token is used to request some interfaces, the interface will report a 401 status code error, which requires us to do additional processing

two questions to think about

  1. Can we determine which interface the user is accessing the 401 error? Where to intercept this 401?
  2. What should I do after detecting 401?

Solution

Do unified processing in the axios response interceptor

  • Intercept 401 in failure callback
  • Clear out expired user information
  • Jump to login page

Test token invalidation effect, manually modify token

refresh page

View console 401

Response interceptor handles 401

httpInstance.interceptors.response.use(function (response) {<!-- -->
    return response;
}, function (error) {<!-- -->
    ElMessage({<!-- -->
        type:'warning',
        message:error.response.data.msg
    })
    // 401 token invalidation processing
    // 1. Clear local user data
    // 2. Jump to the login page
    if(error.response.status === 401){<!-- -->
        const userStore = useUserStore()
        //Call action to change states and clear user data
        userStore. clearUserInfo()
        //jump to login page
        router. push('/login')
    }
    return Promise. reject(error);
});

Test

It is still the above steps, but here the background should only do token verification on the goods path of detail, so it can only be tested on the product details page

  1. Log in
  2. Manually modify the token
  3. Refresh the page at http://localhost:5173/detail/4020262