web123456

Vite+v UE3+typescript

The latest and most complete Vite+Vue3+TypeScript basic knowledge case in 2021 <1>

1. Learning background

As the demand for front-end web applications continues to develop and change, the vue ecosystem is also keeping pace with developers and constantly evolving. Although vue2.0 has been very perfect, many people can't learn vue2.0. Unexpectedly, Mr. You continued to update vue to version 3.0 to supplement the shortcomings of vue2.0. With the advent of vue 3.0, vite2.5.1 also arises naturally. vue has never given up on its pursuit of project response speed and compilation speed. The arrival of vite is simply too happy for front-end developers. vue3.0 not only fully supports TypeScript syntax, but also optimizes and eliminates life cycle hooks. With the syntax sugar of tool setup, the extension of multiple root elements of vue single page, not only the code is simplified, but also very organized. The emergence of vue3.0 has once again improved the developer's coding experience and sense of happiness. In addition, vue3 integrates the typescript language, which is an inevitable trend in the future development of the front-end, and being born as a new member of the vue family, vite is also the learning goal of front-end technology enthusiasts. Win at the starting point and start with learning new technologies. Living until old age and learning until old age is the lifelong belief of a qualified front-end developer.

2. Introduction to vite

Vite was born to improve the performance of web projects and display application pages to users at a faster speed. ViteNative ESMProvide source code. This is actually a part of the job of letting the browser take over the package program: Vite simply needs to convert and provide the source code as the browser requests the source code. Import the code dynamically according to the situation, that is, it will be processed only when it is actually used on the current screen.

Drivers provided:

2.1. Optimize slow server startup (cold startup development server and formal environment response speed);

2.2. Slowly optimized project updates (vite server);

[The external link image is transferred to the public for failure. The source site may have an anti-theft link mechanism. It is recommended to save the image and upload it directly (img-znQlBkN0-1642383363135) (E:\vue_project\javascript-demo\front-end knowledge point\vite+vue3 latest technology stack\vue3.)]

[External link image transfer failed, the source site may have an anti-theft link mechanism. It is recommended to save the image and upload it directly (img-Nro6Ko6e-1642383363136) (E:\vue_project\javascript-demo\front-end knowledge point\vite+vue3 latest technology stack\vue3.)]

3. Vite creates a project

Compatibility Notes:

Vite requiresVersion >= 12.0.0.

Volar plugin must be installed, using vscode editor

// Install vite
1、npm init vite@latest

// Install vite and create vite projects
2、npm init vite@latest my-vue-app --template vue
{
  "scripts": {
    "dev": "vite", // Start the development server
    "build": "vite build", // Build products for the production environment
    "serve": "vite preview" // Local preview of production and construction products
  }
}

4. Version dependency compatibility and project directory introduction

Version dependency instructions, here is the configuration of the entire project dependent version. The relevant installation instructions will be taught one by one in the video below.

Notice:vuex and router are both versions 4.0 and above, otherwise when using vue3, the exposed API cannot be found.

{
    "name": "vite-ts-vue3-plus-demo",
    "version": "0.0.0",
    "scripts": {
        "dev": "vite",
        "build": "vue-tsc --noEmit && vite build",
        "serve": "vite preview"
    },
    "dependencies": {
        "@element-plus/icons": "0.0.11",
        "dotenv": "^10.0.0",
        "element-plus": "^1.1.0-beta.7",
        "vue": "^3.0.5",
        "vue-router": "^4.0.11",
        "vuex": "^4.0.2"
    },
    "devDependencies": {
        "@types/node": "^16.7.1",
        "@vitejs/plugin-vue": "^1.3.0",
        "@vue/compiler-sfc": "^3.0.5",
        "node-sass": "^6.0.1",
        "sass": "^1.38.1",
        "sass-loader": "^12.1.0",
        "typescript": "^4.3.2",
        "vite": "^2.4.4",
        "vue-tsc": "^0.2.2"
    }
}

5. Setup syntax sugar use

5.1 Setup predecessor combined API (basic usage)

Note: This cannot be used in setup()

exist`setup`You should avoid using`this`, because it won't find component instances.`setup`The call occurs when`data` property、`computed`property or`methods`Before being parsed, they cannot be`setup` is retrieved, which is also to avoid setup()and other optionsAPIConfused.
/* Parameter description
  * props are responsive, and when a new prop is passed, it will be updated
  * context is a normal context JavaScript object that exposes the three properties of the component (including properties, slots, methods),
  * As shown in Example 1 below
  */
 // Example 1
 <script>
     export default {
       setup(props, context) {
         // Attribute (non-responsive object)
         ()
         // Slot (non-responsive object)
         ()
         // Trigger event (method)
         ()
       }
     }
 </script>

5.2 setup later generations (advanced usage), recommended usage

Note: defineProps does not need to be introduced, and the API is automatically exposed inside a single file of vue

<script setup lang="ts"><script>is in single file component (SFC) Use combinationAPIsyntax sugar at compile time.  It has more advantages than ordinary syntax:-Less boilerplate content, more concise code, such as: omitting the component registration statement, object exposurereturn,methods,-Ability to declare props and issue events using pure Typescript.- Better runtime performance (Its template will be compiled into a rendering function with the same scope as it, without any intermediate proxy)-BetterIDE Type Inference Performance (Reduce the work of language servers to extract types from code)

Note:

1. You do not need to register a declaration to import components in setup syntax sugar, just use it directly in the view;

2. The structure of the vue file has changed. JS is placed at the top of the page by default, and the view template is placed below JS and the style is placed at the bottom of the page;

3. When importing vue files, you must write the file suffix name.vue, otherwise ts cannot recognize vue files.

Example comparison:

// Basic usage
 <script lang="ts">
     export default {
         props: {
             title: {
                 type: String,
                 default:()=>{return 'test information'}
             }
         },
         setup(props:any) {
             ()
         }
     }
 </script>
 // Advanced usage
 <script setup lang="ts">
     const props = defineProps({
         title: {
             type: String,
             default:()=>{return 'test information'}
         }
     })
     ();
 </script>

6. defineProps and defineEmits

Note: definePropsanddefineEmitsAll are only here<script setup>Compiler macros that can only be used in

For the declaration`props`and`emits`Options and complete type inference can be used`defineProps`and`defineEmits` API, they are`<script setup>`All are automatically available:- **`defineProps`and`defineEmits`All are only here`<script setup>`Only available****Compiler macros**.  They don't need to import and will be processed`<script setup>`It is compiled and processed.- `defineProps`Receive and`props`The same value as the option,`defineEmits`Also received`emits`The same value for options.- `defineProps`and`defineEmits`After the option is passed in, the appropriate type inference is provided.-Incoming to`defineProps`and`defineEmits`The options are promoted from the setup to the scope of the module.  Therefore, the passed option cannot refer to local variables declared in the setup scope.  Doing so will cause a compilation error.  But, it*Can*Reference imported bindings, as they are also within module scope.

6.1. Subcomponent vue

<template>
    <p>{{}}</p>
     <button @click="handleClick">Click me to call the parent component method</button>
 </template>
 <script setup lang="ts">
     const props = defineProps({
          msg:{
             type: String,
             default: () => 'Default'
          }
     })
     const emit = defineEmits(['on-change', 'update'])
     const handleClick = () =>{
         emit('on-change', 'The parent component method is called')
     }
 </script>

6.2, parent component vue

<script setup lang="ts">
     import TestPropsPmit from './components/test-props-emit/';
     import { ref } from 'vue';

     // Define string variables
     const msg = ref('Welcome to vite!')
	 // Call event
     const handleChange = (params:string) =>{
         (params);
     }
 </script>
 <template>
	 <TestPropsPmit :msg="msg" @on-change="handleChange"></TestPropsPmit>
 </template>

Warm reminder: Here is an introduction to the role of the small icon of the volar plug-in in the vue file:

Click this triangle icon to classify the file and display it to facilitate writing code;

[External link image transfer failed, the source site may have an anti-theft link mechanism. It is recommended to save the image and upload it directly (img-EbrgSaUR-1642383363137) (E:\vue_project\demos\vite-vue3-ts-volar\vite+vue3 latest technology stack document\)]

7. Use defineExpose correctly

use<script setup>The component isClosed by default, that is, through the template ref or$parentThe public instance of the component obtained by the chain will not be exposed in<script setup>binding declared in .

In order to<script setup>The properties to be exposed in the component are specified and useddefineExposeCompiler macros:

7.1. Children components expose properties and methods and reference them to parent components

<script setup lang="ts">
 function testChild():void{
     ('Subcomponent method testChild is called');
 }
 const b = ref(2)
 // Unified exposure of attributes
 defineExpose({
     obj:{name: 'Zhang San', age: 2300},
     b,
     testChild
 })
 </script>

7.2. Parent component calls child component methods and properties

<template>
     <TestPropsEmit ref="propsEmitRef" :msg='msg' @on-change="handleChange"> </TestPropsEmit>
 </template>
 <script setup lang="ts">
     import TestPropsEmit from './components/test-props-emit/';
     import {ref, onMounted} from 'vue';

     const msg = ref('Welcome to learn vite')

     const handleChange = (params:string)=>{
         (params);
     }

     const propsEmitRef = ref()
     onMounted(()=>{
         ();
     })
 </script>

7.3 How to define variables (strings, objects, arrays) in setup

<template>
    <h2>{{count}} {{}}</h2>
    <span v-for="(item, index) in arr" :key="index">{{item}}</span>
     <button @click="setName">Click me to add</button>
 </template>
 <script setup lang="ts">
     import { ref, reactive } from 'vue';
     // String variable
     const count = ref(0)
     // Object
     let user = reactive({
         name: 'Zhang San'
     })
     // Array
     let arr = reactive(['1', '2', '3'])
    
     // Comprehensive definition plan
     const originData = reactive({
         count: 0,
         user:{
             name: 'Zhang San'
         },
         arr: ['1', '2', '3']
     })
    
     // method
     const setName = ()=>{
         ++
          = 'Li Si'
     }
 </script>

8. Watch and WatchEffect

1. Basic usage method:

<template>
    <p>{{}}  {{}}</p>
    <p v-for="(item, index) in " :key="index">{{item}}</p>
     <button @click="incriment">Click on me to increase</button>
 </template>
 <script setup lang="ts">
     import { ref, reactive, watchEffect, watch } from 'vue';

     const count = ref(0)
     const user = reactive({name: 'Zhang San'})
     const arr = reactive([1,2,3,4])

     // Comprehensive definition plan
     const originData = reactive({
         count: 0,
         user:{
             name: 'Zhang San'
         },
         arr:[1,2,3,4]
     })
     const incriment = ()=>{
         ++
         ++
          = 'Li Si'
     }
	 // Execute listening immediately before the default page is updated, lazy execution starts
     watchEffect(() => ())
     // The default listening value after data changes is monitored, and the page will not be executed immediately after update.
     watch(count, (n, o) => {
         ('watch', n, o);
     })
    
     // Listen to multiple values
     watch([count, ], (newValues, prevValues) => {
         (newValues[0], newValues[1].name)
     })

     // Listen immediately
     watch([count, ], (newValues, prevValues) => {
         (newValues[0], newValues[1].name)
     }, {deep: true, immediate: true})
   
 </script>

2. Comparison of watch and watchEffect, and watch monitoring is recommended

watch: The page will not be executed immediately after it is updated, and watchEffect will be executed;

If you want to implement: watch is executed after the page is updated, you need to add the attributes to execute immediately;

watch([count,], (n, o)=> {(n[0],n[1].name)}, {deep: true, immediate: true})
1. Both watch and watchEffect are lazy to perform side effects, but watchEffect is more frequent and is executed before the vue component is updated;
 2. Watch more specifically explains what state should trigger the listener to rerun, watchEffect is not so friendly;
 3. Watch access to the value before and after the listening state changes.

9. Lifecycle hook in setup

becausesetupIt's aroundbeforeCreateandcreatedLifecycle hooks run, so they don't need to be explicitly defined. In other words, any code written in these hooks should be directly insetupWrite in the function.

The following table contains how tosetup ()Internal call lifecycle hook:

Optional API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMountBefore mount
mounted onMountedExecute when the page loads
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmountedExecute when the page is destroyed
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated
<script setup lang="ts">
import { onMounted, onActivated, onUnmounted, onUpdated, onDeactivated } from 'vue';
// Read environment variables
const mode = import.meta.env;
//   import HeadMenu from '@/components/head-menu/';
 onMounted(() => {
 console.log("Component Mount")
 })

 onUnmounted(() => {
 console.log("Component Uninstall")
 })

 onUpdated(() => {
 console.log("Component Update")
 })
 onActivated(() => {
 console.log("keepAlive component activation")
 })

 onDeactivated(() => {
 console.log("keepAlive component non-activated")
 })
</script>

10. Use Ts to limit the define(Emits|Props) parameter type

Notice:

1. Introducing components in setup syntax sugar can be used directly without registering a declaration.

2. ts restricts the parameter type of component transmission. By default, the value must be passed. Otherwise, a warning will appear on the console, and a red reminder will appear where the component is introduced. Don’t you want to upload it after the bound parameters?

3. Ts parameter transmission supports multiple types of verification, and one parameter can pass strings, arrays, Booleans, etc.

4. Use the ts method to limit the defineEmits and defineProps parameter types

1. Subcomponents

<template >
    <h1>{{msg}}</h1>
    <h3>{{title}}</h3>
     <input v-model="inputValue" type="text" @blur="handleUpdate($event)">
 </template>
 <script setup lang="ts">
     import { ref } from "vue";
     defineProps<{
             msg?:(string | number),
             title?: string
         }>()
    
          // Provide default value method 1
         interface Props{
             msg?: (string | number | boolean),
             title?: string[]
         }

         withDefaults(definedProps<Props>(), {
             msg: 'hello',
             title: () => ['one', 'two']
         })

         // Provide default method 2
         withDefaults(defineProps<{
             msg?: (string | number | boolean)
             title?: string
         }>(), {
             msg: 3,
             title: 'Default title'
         })

     // const emit = defineEmits(['updateValue'])
     const emit = defineEmits<{
                         (event: 'change'): void,
                         (event: 'update', data: string): void
                 }>()

     const inputValue = ref<any>()

     const handleUpdate = (event: any) =>{
         const { target } = event
         // (, 1111);
         emit('update', )
     }
 </script>

2. Parent component

<script setup lang="ts">
    import CellSample from "./components/cell-samples/";
    
    const update = (data: any) =>{
        (data);
    }
</script>

<template>
    <CellSample @update="update"></CellSample>
</template>


11. Recursive components

A single file component can be referenced by its file name. For example: nameComponents can be used in their templates<FooBar/>Quote it yourself.

Please note that this method has lower priority than import components imported. If a named import and component inferred name conflict, you can use the import alias to import:

import { FooBar as FooBarChild } from './components'

Note: There is a small problem here. When a single file is introduced, memory overflow will occur.

12. component dynamic components

Since the component is referenced as a variable rather than as a string key to register,<script setup>When you want to use dynamic components, you should use dynamic components:isTo bind:

<script setup lang='ts'>
import Foo from './'
import Bar from './'
</script>

<template>
  <component :is="Foo" />
  <component :is="someCondition ? Foo : Bar" />
</template>

13. Ts restricts normal function/arrow function parameter types

13.1 Normal functions

<script setup lang="ts">
   	function test(params:(string|boolean)):void {
        (params);
    }
    test('5555')
</script>

13.2 Arrow function, recommended usage

<script setup lang="ts">
    const test = (params:(string|boolean))=>{
        (params)
    }
    test('5555')
</script>

14. Introduce vuex configuration and use

14.1 When creating the project, we have introduced the vuex4.0 version. Next, we will start configuring vuex4.0 and testing.

// Note that vuex4.0 and above must be installed
npm install vuex@next --save
#or
yarn add vuex@next --save

14.2 Create a store folder in the src directory and create a new file, the content is as follows:

import { InjectionKey } from 'vue'
/**
  * Introduce InjectionKey and pass it to anywhere you have used the useStore,
  * It will soon become a repetitive work.  To simplify the problem, you can define yourself
  * Combination function to retrieve typed store
  */
// Unsimplified useStore version
// import { createStore, Store } from 'vuex'
// Simplified useStore version
import { useStore as baseUseStore, createStore, Store} from 'vuex'

// Declare the type for store state
export interface State {
    username: string,
    count: number
}

// Define injection key
export const key: InjectionKey<Store<State>> = Symbol()

// Export the store module
export const store = createStore<State>({
    state: {
        username: "Test store",
        count: 0
    },
    getters:{
        getName: state =>{
            return state.username
        }
    },
    mutations: {
        // Reset the name
        SET_NAME(state, params:string) {
            state.username = params
        }
    },
    actions:{}
})

// Define your own `useStore` combination function
export function useStore () {
    return baseUseStore(key)
}

14.3 Create a new file in the root directory, the content is as follows:

// A declaration file to declare the custom type of Vue ComponentCustomProperties
import { ComponentCustomProperties } from 'vue';
import { Store } from 'vuex';

declare module '@vue/runtime-core' {
    // Declare your store state
    interface State {
        count: number,
        username: string
    }
    // Provide a type declaration for `this.$store`
    interface ComponentCustomProperties {
        $store: Store<State>
    }
}

14.4 Inject store module in

import { createApp } from 'vue';
import App from './';
// Import the store module and pass the injection key
import { store, key } from '@/store';
const app = createApp(App)
app.use(store, key)
app.mount('#app')

14.5 Reference tests whether the vuex configuration is correct

<el-button @click="changeName" size="small">Click to modify the name</el-button>
 <script setup lang="ts">
 // vue component
 import { useStore } from '@/store';
 const store = useStore()
 // Test store reset name
 // ('increment', 10)
 function changeName():void{
     ('SET_NAME', 10)
     ('Modified name:'+);
 }
 (,)
 </script>

15. Detailed explanation of router configuration and usage

15.1 Installation

When creating the project, we have introduced the router4.0 version, and then we will start configuring router4.0 and testing.

// Note: The installation router must be 4.0 or above
npm install vue-router@4

15.2 Page preparation

Directory structure

[External link image transfer failed, the source site may have an anti-theft link mechanism. It is recommended to save the image and upload it directly (img-O1byer9d-1642383363137) (E:\vue_project\demos\vite-vue3-ts-volar\vite+vue3 latest technology stack document\)]

Page specific content:

1、layout/

<template>
    <Header/>
    <router-view></router-view>
</template>
<script setup lang="ts">
    import Header from './header/';
</script>

2、layout/header/

<template>
     <div class="action">
         <h2 @click="handleClick(1)">Home</h2>
         <h2 @click="handleClick(0)">About</h2>
     </div>
 </template>
 <script setup lang="ts">
     import { useRouter } from 'vue-router';
     const router = useRouter()

     const handleClick = (num: number)=>{
         if (num) {
             ({name: 'home'})
         }else ({name: 'about'})
     }
 </script>
 <style >
     .action{
         display: flex;
     }
     h2{
         padding: 0px 10px;
         cursor: pointer;
     }
     h2:hover{
         color: red;
     }
 </style>

3、pages/home/

<template>
    <h2>home</h2>
</template>

4、pages/about/

<template>
    <h2>about</h2>
</template>

15.3 Create a router folder in the src directory, and then create a file, the content is as follows:

import { createRouter, createWebHashHistory } from "vue-router";
import LayOut from "../components/layout/";

const routes = [
    {
        path: '/',
        component: LayOut,
        redirect: '/home',
        children:[
            {
                path: '/home',
                name: 'home',
                component: ()=> import("../pages/home/"),
                meta:{
                    title: 'front page',
                    icon: ''
                }
            },
            {
                path: '/about',
                name: 'about',
                component: ()=> import("../pages/about/"),
                meta:{
                    title: 'about',
                    icon: ''
                }
            }
        ]
    }
]
const router = createRouter({
    history: createWebHashHistory(),
    routes
})
export default router

15.4 Inject the router module into it, restart the project, access the route, and see if it is correct

import { createApp } from 'vue'
import App from './'
import { store, key } from './store';
import router from './router';

const app = createApp(App);
app.use(store, key);
app.use(router);
app.mount('#app');

15.5 Using Routing

<template>
     <div class="action">
         <h2 @click="handleClick(1)">Home</h2>
         <h2 @click="handleClick(0)">About</h2>
     </div>
 </template>
 <script setup lang="ts">
     import { useRouter } from 'vue-router';
     const router = useRouter()

     const handleClick = (num: number)=>{
         if (num) {
             ({name: 'home'})
         }else ({name: 'about'})
     }
 </script>
 <style >
     .action{
         display: flex;
     }
     h2{
         padding: 0px 10px;
         cursor: pointer;
     }
     h2:hover{
         color: red;
     }
 </style>

16. Introducing element-plus and precautions

16.1 Installation

npm install element-plus --save
# or
yarn add element-plus

 # Install icon icon dependency library
 npm install @element-plus/icons
# or
yarn add @element-plus/icons

16.2 Introducing configuration in the file

import { createApp } from 'vue'
import App from './'
import { store, key } from './store';
// Inject routing
import router from './router';

// Introduce ui component
import ElementPlus from 'element-plus'
import 'element-plus/dist/'

const app = createApp(App);
app.use(store, key);
app.use(router);
app.use(ElementPlus);
app.mount('#app');

16.3 Reference ui component in vue file

1. Single icon reference

<template>
    <el-icon :size="20" :color="'blue'">
        <edit />
    </el-icon>
</template>
<script setup lang="ts">
    import { Edit } from '@element-plus/icons'
</script>

2. Use global registration icon

import { createApp } from 'vue'
import App from './'

import { Edit,Search } from '@element-plus/icons'

const app = createApp(App);
app.component("edit", Edit)
app.component("search", Search)
app.mount('#app');

2.1 Use icons

<template>
     <h2>home page</h2>
     <el-button type="primary" >Main button</el-button>
     <el-button type="success" >Success button</el-button>
     <el-icon :size="20" :color="'blue'">
         <edit />
     </el-icon>
     <el-icon :size="20">
         <search></search>
     </el-icon>
 </template>
 <script setup lang="ts">
 </script>

17. Configuration

Here, the main configuration of the vite server's packaging and saving address, packaging and decomposition, port number, whether to automatically open the browser, remote request address proxy target, directory alias, global style configuration, etc.

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { loadEnv } from 'vite';
// Nodejs writing method, get the project directory
import path from 'path';

// /config/
export default({ command, mode }) => {
    return defineConfig({
        plugins: [vue()],
        server:{
            host: '127.0.0.1',
            port: Number(loadEnv(mode, process.cwd()).VITE_APP_PORT),
            strictPort: true, // The port is occupied and exits directly
            https: false,
            open: true,// Automatically open the application in the browser when the development server starts
            proxy: {
                // Abbreviation of strings
                // '/foo': '',
                // Option writing
                '/api': {
                    target: loadEnv(mode, process.cwd()).VITE_APP_BASE_URL,
                    changeOrigin: true,
                    rewrite: (path) => path.replace(/^\/api/, '')
                },
                // Regular expression writing
                // '^/fallback/.*': {
                //   target: '',
                //   changeOrigin: true,
                //   rewrite: (path) => (/^\/fallback/, '')
                // },
            },
            hmr:{
                overlay: true // Blocking the server error
            }
        },
        resolve:{
            alias:{
                '@': path.resolve(__dirname,'./src')
            }
        },
        css:{
            // css preprocessor
            preprocessorOptions: {
                //Introduce this way you can use predefined variables in the global
                // Add ;
                scss: {
                    additionalData: '@import "@/assets/styles/";'
                }
            }
        },
        build:{
            chunkSizeWarningLimit: 1500, // Package in chunks, decompose blocks, and break down large blocks into smaller blocks
            rollupOptions: {
                output:{
                    manualChunks(id) {
                        if (id.includes('node_modules')) {
                            return id.toString().split('node_modules/')[1].split('/')[0].toString();
                        }
                    }
                }
            }
        }
    })   
}

18. Configuration

This is the identification configuration file of typescript, and it is also the intermediate station for the conversion of ts to js. There are many file types and policy methods about ts.

{
    "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "baseUrl": ".",
    "paths": {
        "@/*":["src/*"]
    }
  },
  	"include": ["src/**/*.ts", "src/**/*.", "src/**/*.tsx", "src/**/*.vue"]
}

19. Configure, declare the global module of vue file

Here is the configuration declaration, such as css, vue and other files. Ts cannot be recognized and the declaration module type needs to be configured.

declare module '*.vue' {
  import { DefineComponent } from 'vue'
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>
  export default component
}

20. Install scss and configure global style files

// Note that to use scss, you must install the dependency, otherwise an error will be reported
npm install node-sass sass-loader sass -D

20.1 You need to add a global file to the assets static directory of the src directory, and other style files can be imported into this file for global use and modification.

[External link image transfer failed, the source site may have an anti-theft link mechanism. It is recommended to save the image and upload it directly (img-OMnks85U-1642383363138)(E:\vue_project\demos\vite-vue3-ts-volar\vite+vue3 latest technology stack document\)]

Or writing a separate attribute test

$color-primary: #007aff;

vue uses global style variables

<template>
     <h2 class="test-color">home page</h2>
 </template>
<style lang="scss" scoped>
    .test-color{
        color: $color-primary;
    }
</style>

20.2 Configure in the file

 css:{
     // css preprocessor
     preprocessorOptions: {
         //Introduce this way you can use predefined variables in the global
         // Add ;
         scss: {
             additionalData: '@import "@/assets/styles/";'
         }
     }
 }

21. Responsive API

21.1 、ref

Responsive state needs to be used explicitlyResponsive APIsto create. and fromsetup()The return value in the function is the same. The ref value will be automatically unpacked when used in the template:

<script setup lang='ts'>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>

21.2、toRefs

Convert a responsive object to a normal object, where each property of the result object is pointed to the corresponding property of the original objectref

When returning a responsive object from a combined function,toRefsVery useful so that the consuming component can deconstruct/expand the returned object without losing responsiveness:

function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  // Logic of operating state

  // Convert to ref when returned
  return toRefs(state)
}

export default {
  setup() {
    // Can be deconstructed without losing responsiveness
    const { foo, bar } = useFeatureX()

    return {
      foo,
      bar
    }
  }
}

toRefsRefs will only be generated for properties contained in the source object. If you want to create a ref for a specific property, you should usetoRef

21.3、roRef

Can be used to create a new property on the source responsive objectref. The ref can then be passed, which maintains a responsive connection to its source property.

const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) // 2

state.foo++
console.log(fooRef.value) // 3

Even if the source property does not exist,toRefA available ref will also be returned. This makes it particularly useful when using optional props, which will not betoRefsdeal with.

22. .env. Environment variable configuration and reading

Environment variables are recommended to be placed in the project root directory to facilitate file reading and use

. // Production environment configuration file
 . // Development environment configuration file

22.1 The contents of the production and development environment configuration files are as follows:

When writing variables, you must start with VITE_ to be exposed to external reading.

# Development Environment/#Production EnvironmentVITE_APP_TITLE = "Front-end technology stack"
VITE_APP_PORT = 3001# Request interfaceVITE_APP_BASE_URL = "http://39.12.29.12:8080"

//In the file, the environment variables are intelligently prompted to configure
interface ImportMetaEnv {
     VITE_APP_TITLE: string,
     VITE_APP_PORT: string,
     VITE_APP_BASE_URL: string
}

22.2 Reading the configuration file

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { loadEnv } from 'vite';
// Nodejs writing method, get the project directory
import path from 'path';

// /config/
export default({ command, mode }) => {
    // (command, mode, 5555);
    return defineConfig({
        plugins: [vue()],
        server:{
            host: '127.0.0.1',
            port: Number(loadEnv(mode, process.cwd()).VITE_APP_PORT),
            strictPort: true, // The port is occupied and exits directly
            https: false,
            open: true,// Automatically open the application in the browser when the development server starts
            proxy: {
                // Abbreviation of strings
                // '/foo': '',
                // Option writing
                '/api': {
                    target: loadEnv(mode, process.cwd()).VITE_APP_BASE_URL,
                    changeOrigin: true,
                    rewrite: (path) => path.replace(/^\/api/, '')
                },
                // Regular expression writing
                // '^/fallback/.*': {
                //   target: '',
                //   changeOrigin: true,
                //   rewrite: (path) => (/^\/fallback/, '')
                // },
                // Use proxy instance
                // '/api': {
                //   target: '',
                //   changeOrigin: true,
                //   configure: (proxy, options) => {
                // // proxy is an instance of 'http-proxy'
                //   },
                // }
            },
            hmr:{
                overlay: true // Blocking the server error
            }
        },
        resolve:{
            alias:{
                '@': path.resolve(__dirname,'./src')
            }
        },
        css:{
            // css preprocessor
            preprocessorOptions: {
                //Introduce this way you can use predefined variables in the global
                // Add ;
                scss: {
                    additionalData: '@import "@/assets/styles/";'
                },
                sass: {
                    additionalData: '@import "@/assets/styles/";'
                }
            }
        },
        build:{
            chunkSizeWarningLimit: 1500, // Package in chunks, decompose blocks, and break down large blocks into smaller blocks
            rollupOptions: {
                output:{
                    manualChunks(id) {
                        if (id.includes('node_modules')) {
                            return id.toString().split('node_modules/')[1].split('/')[0].toString();
                        }
                    }
                }
            }
        }
    })   
}

22.3 Other files read environment variables

<template>
     Home page content display
 </template>
 <script setup lang="ts">
 import { onMounted } from 'vue';
 // Read environment variables
 const mode = ;
 onMounted(()=>{
     (mode,555);
 })
 </script>

23. [Supplement] Mixin Mixed Mode

Mixin in vue2.0 provides a very flexible way to distribute reusable features in Vue components. A mixin object can contain any component options. When a component uses a mixin object, the options for all mixin objects will be "mixed" into the options for the component itself.

Mixin’s disadvantages reminder:

In Vue 2, mixin is the main tool for abstracting some component logic into reusable blocks. However, they have several problems:

  • Mixins are prone to conflicts: because the properties of each mixin are merged into the same component, in order to avoid property name conflicts, you still need to know every other feature.
  • Reusability is limited: we cannot pass any arguments to mixin to change its logic, which reduces their flexibility in abstract logic.

To solve these problems, we have added a new way of organizing code through logical concerns:Combination API

In other words: it is not recommended to use mixin to develop in vue3.0. It is more recommended to use a combination API to split the function of page manipulation data, and better share and reuse code under large projects, so as to survive the maintainability and scalability of the code.

24. [Supplement] Top-level binding will be exposed to the template

When using<script setup>Whenever, any<script setup>The top-level bindings of the declaration (including variables, function declarations, and what import is introduced) can be used directly in the template.

<script setup lang="ts">
 import { capitalize } from './helpers'
 // Variable
 const msg = 'Hello!'

 // Function
 function log() {
   (msg)
 }
 </script>

 <template>
   <div @click="log">{{ msg }}</div>
  <div>{{ capitalize('hello') }}</div>
</template>

25. 【Supplement】teleport portal

Teleport must be a valid query selector or HTMLElement (if used in a browser environment). Specifies that it will move there<teleport>The target element of the content, disabled-boolean This optional attribute can be used to disable<teleport>The function of this means that its slot content will not move to any location, but instead you specify in the surrounding parent component<teleport>rendering of location.

25.1 Creating Components with Portals

<template>
     <teleport to=".teleport-one">
         <div>
             I'm Teleport Content
         </div>
     </teleport>
 </template>

25.2 Teleport hybrid component use

<template>
     <div class="teleport-one">
         Portal 1
     </div>
     <div class="teleport-two">
         Portal 2
     </div>
     <TestTeleport></TestTeleport>
 </template>
 <script setup lang="ts">
     import TestTeleport from "@/components/test-teleport/";
 </script>

26. 【Supplement】Computed use

andrefandwatchSimilarly, you can also use imported from VuecomputedFunctions create computed properties outside of Vue components. Let's go back to the counter example:

import { ref, computed } from 'vue'

const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)

counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2

Notice:

Accept a getter function and return an immutable response based on the return value of the getterrefObject.

// This is not available
twiceTheCounter.value++ //Incorrect usage

Vite+Vue3+Setup+TypeScript Buyi Blog Project Practical Battle<2>

Preface:

​ First of all, I would like to thank all the front-end friends for their support and encouragement. Your encouragement and understanding are the source of motivation on my way forward. I learned more from this project about the details and precautions during the development process.

​ In order to connect the fragmented basic knowledge points, we have a systematic understanding and understanding of setup, vite, typescript and vue3, which is closer to network application projects and close to the needs of real life information research and development, and further analyze various technical knowledge points from the details. Buyi Frontend is hereby produced a web page design called "Buyi Blog". The purpose is to better master the latest web technology stacks of Vite+Vue3+setup+TypeScript and to use them proficiently to develop your own or enterprise-level application projects. This blog is specifically developed for setup+ts, omitting the traditional Vue2 API. For friends who do not have the basic knowledge of vue2, it is recommended that you learn more after reading vue3.

Warm reminder: The Buyi Blog project relies on the basic knowledge case part for transformation. The creation of the project and the installation of each dependency will not be repeated or repeated, so friends who have not finished learning the basic knowledge should be best to browse the actual project part after learning it.

Channels to obtain basic knowledge projects:

1. After watching the basic knowledge case video, you will have it. Address: /video/BV1QP4y1p748/

2. You can create a project based on the vite official website, but some things need to be supplemented by yourself. Address: /guide/#scaffolding-your-first-vite-project

1. Project dependency upgrade

Since the project wants to play the latest, of course, dependencies must be updated to the latest version.

Older version dependencies:

"dependencies": {
    "@element-plus/icons": "0.0.11",
    "element-plus": "^1.1.0-beta.12",
    "vue": "^3.2.11",
    "vue-router": "^4.0.11",
    "vuex": "^4.0.2"
  },
  "devDependencies": {
    "@types/node": "^16.9.2",
    "@vitejs/plugin-vue": "^1.6.0",
    "@vue/compiler-sfc": "^3.2.6",
    "node-sass": "^6.0.1",
    "sass": "^1.41.1",
    "sass-loader": "^12.1.0",
    "typescript": "^4.3.2",
    "vite": "^2.5.2",
    "vue-tsc": "^0.2.2"
  }

New version dependencies:

"dependencies": {
    "@element-plus/icons": "0.0.11",
    "element-plus": "^1.2.0-beta.2", // Original version 1.1.0-beta.12
    "vue": "^3.2.21", // Original version 3.2.11
    "vue-router": "^4.0.12",
    "vuex": "^4.0.2"
  },
  "devDependencies": {
    "@types/node": "^16.9.2",
    "@vitejs/plugin-vue": "^1.6.0",
    "@vue/compiler-sfc": "^3.2.6",
    "node-sass": "^6.0.1",
    "sass": "^1.41.1",
    "sass-loader": "^12.1.0",
    "typescript": "^4.3.2",
    "vite": "^2.6.13", // Original version 2.5.2
    "vue-tsc": "^0.28.10" //Original version 0.2.2
  }

2. Create the top menu component

2.1 Create a new menu folder in the component folder layout directory, and then create a new menu folder. The complete content is as follows.

<template>
    <el-menu
        :default-active="activeIndex2"
        class="el-menu-demo"
        mode="horizontal"
        background-color="#545c64"
        text-color="#fff"
        active-text-color="#ffd04b"
        @select="handleSelect"
    >
        <template v-for="(item, index) in menuList" :key="">
            <template v-if="!">
                <el-menu-item :index="?.index" @click="handleRoute(item)">{{?.title}}</el-menu-item>
            </template>
            <template v-else>
                <el-sub-menu :index="?.index" :popper-append-to-body="false">
                    <template #title>{{?.title}}</template>
                    <el-menu-item :index="?.index" v-for="(childItem, subIndex) in " :key=""  @click="handleRoute(childItem)">{{?.title}}</el-menu-item>
                 </el-sub-menu>
             </template>
         </template>
   </el-menu>
 </template>
 <script lang="ts" setup>
     import {ref, computed} from 'vue';
     import {useRoute, useRouter} from "vue-router";
     import { mapState } from 'vuex';
     import { useStore } from '@/store';

     const store = useStore()
     const router = useRouter()
    
     // Define variables
     const activeIndex2 = computed(mapState(['currentMenu']).({ $store: store })) || ref<string>('1')
     // Get menu route
     const menuList = [0].children;

     // Define events
     // Click on the menu event
     const handleSelect =(key:string, keyPath:string)=>{
         (key, keyPath)
         ('SET_CURRENT_MENU', key)
     }
     // Jump about the page
     const handleRoute = (route:any) => {
         ()
     }

 </script>
 <style lang="scss">
     @import "./";
 </style>

2.2 Create a new menu folder at the same level, the content is as follows:



2.3 Introduced

<template>
    <menu-bar></menu-bar>
</template>
<script setup lang="ts">
    import MenuBar from '@/components/layout/menu/';
</script>
<style lang="scss" scoped>
</style>

2.4 Add a new file in the styles directory in the assets directory, the contents are as follows

body{
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    color: #2c3e50;
    width: 100%;
    margin: 0;
    padding: 0;
    background-color: #ececec;
}

Then import internally

@import "./";

3. Fixed the problem of missing menu refresh when checked

Here we use the persistence solution of store state management, and add the currentMenu property to it

import { InjectionKey } from 'vue';

import { useStore as baseUseStore, createStore, Store} from 'vuex';

interface State{
    username: string,
    currentMenu: string  // Current menu
}

export const key: InjectionKey<Store<State>> = Symbol();

export const store = createStore<State>({
    state:{
        username: 'Zhang San',
        currentMenu: localStorage.getItem('currentMenu') || '1'
    },
    getters:{
        getName: (state:State)=>{
            return state.username
        },
        getCurrentMenu: (state:State) =>{
            return state.currentMenu || localStorage.getItem('currentMenu')
        }
    },
    mutations:{
        SET_USERNAME(state:State, username: string){
            state.username = username;
        },
        SET_CURRENT_MENU(state:State, params: string){
            localStorage.setItem('currentMenu', params)
            state.currentMenu = params
        }
    },
    actions:{}
})

export function useStore () {
    return baseUseStore(key)
}

Note: When an error is reported by quoting the @/store directory, please change the project file content as follows

{
  "compilerOptions": {
    "target": "esnext", // The version of the target language
    "module": "esnext", // Specify the template standard for generating code
    "moduleResolution": "node", // node module analysis
    "strict": true, // Enable strict mode
    "jsx": "preserve", // jsx template analysis
    "noImplicitAny": true, // Implicit any type is not allowed
    "removeComments": true, // Delete comments
    "sourceMap": true, // Generate the sourceMap file of the target file
    "strictNullChecks": true, // Assign null and undefined to other types of variables are not allowed
    "resolveJsonModule": true, // Allow import of .json files
    "esModuleInterop": true,  // Allow additional ts files to be imported support
    "suppressImplicitAnyIndexErrors": true, // Allow string subscript expressions
    "lib": ["esnext", "dom"],
    "baseUrl": ".", // parse the base address of non-relative modules, the default is the current directory to prevent errors from being reported by introducing files
    "paths": {  // Path map, relative to baseUrl
        "@/*":["src/*"]
    },
    "skipLibCheck":true // Type check of all declaration files (*.), solution: no error is reported for packaging
  },
  // Specify a matching list (belongs to all ts-related files under the path that are automatically specified)
  "include": ["src/**/*.ts", "src/**/*.", "src/**/*.tsx", "src/**/*.vue"]
}

4. RouteRecordRaw type verification

In order to standardize typescript development, increase the routing object type limitation,Benefits: Allows you to add developer custom attributes to the basic route

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import LayOut from '@/components/layout/';

const routes:Array<RouteRecordRaw> = [
    {
        path: "/",
        component: LayOut,
        redirect: "/home",
        hidden: true,
        children:[
            {
                path: "/home",
                name: "home",
                hidden: false,
                meta:{
                    title: 'front page',
                    index: '1'
                },
                component:()=> import("@/pages/home/")
            },
            {
                path: "/about",
                name: "about",
                meta:{
                    title: 'about',
                    index: '2'
                },
                component:()=> import("@/pages/about/")
            }
        ]
    }
]

const router = createRouter({
    // Specify routing mode
    history: createWebHashHistory(),
    // Routing address
    routes
})

export default router;

Note: After adding a new type to the routing object, adding hidden or other attributes to each routing object will cause an error. Solution: Add a new route declaration file in the src directory to expand the basic route object properties.

, the content is as follows:

import { _RouteRecordBase } from 'vue-router';

declare module 'vue-router'{
    interface _RouteRecordBase{
        hidden?: boolean | string | number
    }
}

5. Modify the browser title through beforeEach

Solution: Just set it in the method in the file.

 
router.beforeEach((to, from, next) =>{
    // Set browser title
    if(to.meta.title){
        document.title = String(to.meta.title)
    }else {
        document.title = "Buyi Blog"
    }
    // (to, from, router);
    next()
})

Note: When using it, the next() method must be added, otherwise the route will not be redirected or updated.

6. Routing navigation error handling

In actual project development, if you often encounter routing navigation errors or problems cannot be found, it becomes necessary to deal with this problem. The solution is to judge in the method in the file.

import { createApp } from 'vue'
import App from './'
import { store, key } from './store';
// Inject routing
import router from './router';

// Inject ui component library
import ElementPlus,{ElNotification} from 'element-plus'
import 'element-plus/dist/'

import { Edit, Camera } from '@element-plus/icons'

const app = createApp(App);

app.component('edit', Edit);
app.component('camera', Camera);

app.use(ElementPlus);

app.use(store, key);
app.use(router);
app.mount('#app');

router.beforeEach((to, from, next) =>{
    // Set browser title
    if(to.meta.title){
        document.title = String(to.meta.title)
    }else {
        document.title = "Buyi Blog"
    }

7. Use nprogress to set the route loading progress bar

When we open a project written in vue, we often see a page loading progress bar at the top. In fact, this is a progress bar set according to the routing matching speed, in order to increase the user's browsing project experience. The plug-in used here is: nprogress, which needs to be configured in the method in the file.

7.1 Install nprogress:

npm install --save nprogress

7.2 Encapsulation nprogress:

// Introduce progress bar
import NProgress from 'nprogress';
import 'nprogress/';

//Configuration of global progress bar
NProgress.configure({ 
    easing: 'ease',  // Animation method
    speed: 1000,  // Increasing the speed of the progress bar
    showSpinner: false, // Whether to display loading ico
    trickleSpeed: 200, // Automatic incremental interval
    minimum: 0.3 // Minimum percentage at initialization
})

// Open the progress bar
export const start = ()=>{
    NProgress.start()
}

// Close the progress bar
export const close = ()=>{
    NProgress.done()
}

Note: If an error is reported when importing nprogress, please add the nprogress module declaration to the file or create a new declaration file under src.

Regardless of that method, the content is as follows:

// Declare the progress bar dependency module
 declare module 'nprogress';

7.3 Using the tool nprogress

It needs to be configured in the method in the file.

8. Responsive design top menu bar

8.1 General global style, required injection

* {
     -moz-box-sizing: border-box;
     box-sizing: border-box;
 }
 ul,
 li {
	 list-style: none;
 }
 html,
 body,
 div,
 span,
 object,
 iframe,
 h1,
 h2,
 h3,
 h4,
 h5,
 h6,
 p,
 blockquote,
 pre,
 abbr,
 address,
 cite,
 Code,
 del,
 dfn,
 em,
 img,
 ins,
 kbd,
 q,
 samp,
 Small,
 strong,
 Sub,
 sup,
 var,
 b,
 i,
 dl,
 dt,
 dd,
 ol,
 ul,
 li,
 fieldset,
 form,
 label,
 legend,
 table,
 caption,
 // tbody,
 // tfoot,
 // thead,
 // tr,
 // th,
 // td,
 article,
 Aside,
 canvas,
 details,
 figcaption,
 Figure,
 footer,
 header,
 hgroup,
 menu,
 nav,
 section,
 Summary,
 time,
 mark,
 audio,
 video {
	 margin: 0;
	 padding: 0;
	 // border: 0;
	 outline: 0;
	 list-style: none;
	 vertical-align: baseline;
	 // background: transparent;
	 font-size: 100%;
	 font-weight: normal;
 }
 .layout-sider-bar{
   &::-webkit-scrollbar {
    display:none;
   }
   /* IE kernel */
   -ms-scroll-chaining: chained;
   -ms-overflow-style: none;
   -ms-content-zooming: zoom;
   -ms-scroll-rails: none;
   -ms-content-zoom-limit-min: 100%;
   -ms-content-zoom-limit-max: 500%;
   -ms-scroll-snap-type: proximity;
   -ms-scroll-snap-points-x: snapList(100%, 200%, 300%, 400%, 500%);
   -ms-overflow-style: none;
 }
 ::-webkit-scrollbar {
 width: 6px;
 height: 12px;
 background:#F7F7F7;
 }
 
 ::-webkit-scrollbar-thumb {
 background-color: #ccc;
 }

8.2 Added a global layout style file, and the file needs to be injected

Note: Set position: fixed; after the element is fixed, it will be separated from the document flow and the height will be invalid. Solution: Increase the parent fixed height.

.set-common-head-fixed-container{
     height:90px;// The total height of the menu is reserved, which may be modified later
 }
 // .common-head-fixed-container{
 // position: fixed;
 // top: 0px;
 // left: 0px;
 // width: 100%;
 // z-index: 9;
 // }

 // The header menu does media query to adapt to all screen sizes, custom width and style changes
 // 1. Under the ultra-small screen, the width of the layout container is less than 768px is 100%
 @media screen and (max-width:767px){
     .show-pc{
         display: none;
     }
     .show-mobile{
         display: block;
         background-color: red;
     }
 }
 // 2. Under the small screen, the layout container is greater than or equal to 768px and is changed to 750px
 @media screen and (min-width: 750px){
     .show-pc{
         display: none;
     }
     .show-mobile{
         display: block;
         background-color: red;
     }

 }
 // 3. Under medium screen, the layout container is greater than or equal to 992px and is changed to 970
 @media screen and (min-width:992px){
     .show-pc{
         display: block;
         background-color: orange;
     }
     .show-mobile{
         display: none;
     }
 }
 // 4. Under the large screen, greater than or equal to 1200px, the layout container is changed to 1170px
 @media screen and (min-width: 1200px){
     .show-pc{
         display: block;
         background-color: yellow;
     }
     .show-mobile{
         display: none;
     }
 }

8.3 In the vue file of the layout component header

<template>
	<div class="set-common-head-fixed-container">
        <el-row class="common-head-fixed-container">
            <el-col :span="24" class="show-pc">
                <menu-bar></menu-bar>
   			</el-col>
            <el-col :span="24" class="show-mobile">
                mobile
    		</el-col>
        </el-row>
    </div>
</template>

9. Add a new logo to the top menu bar

Everyone designs this logo by themselves. In fact, just don’t use my logo for online projects.

<template>
     <div class="nav-menu-wrapper">
         <div class="el-menu el-menu--horizontal search-box">
             <img class="set-img" src="@/assets/" alt="picture" title="Welcome to Buyi Blog">
         </div>
         <el-menu
             :default-active="activeIndex2"
             class="menu-box"
             mode="horizontal"
             background-color="#2c3e50"
             text-color="#fff"
             active-text-color="#ffd04b"
             @select="handleSelect"
         >
             <template v-for="(item, index) in menuList" :key="">
                 <template v-if="!">
                     <el-menu-item :index="?.index" @click="handleRoute(item)">{{?.title}}</el-menu-item>
                </template>
                <template v-else>
                    <el-sub-menu :index="?.index">
                    <template #title>{{?.title}}</template>
                    <el-menu-item :index="?.index" v-for="(subItem, index) in " :key="" @click="handleRoute(subItem)">{{?.title}}</el-menu-item>
                    </el-sub-menu>
                </template>
            </template>
        </el-menu>
    </div>
</template>
<style lang="scss">
    .nav-menu-wrapper{
        display: flex;
        justify-content: space-between;
        align-items: center;
        .menu-box{
            flex: 1;
        }
        .search-box{
            color: #fff;
            background-color: #2c3e50;
            padding: 0 7px 0 6px;
            .set-img{
                width: 200px;
                height: 60px;
            }
        }
    }
</style>

10. Design the navigation bar under the small screen

10.1 Designed under the header/small screen

<template>
     <div class="set-common-head-fixed-container">
         <el-row class="common-head-fixed-container">
             <el-col :span="24" class="show-pc">
                 <menu-bar></menu-bar>
   			 </el-col>
             <el-col :span="24" class="show-mobile">
                 <el-icon class="same-cell" :size="30"><expand @click="handleOpenMenu"/></el-icon>
             </el-col>
         </el-row>
     </div>

     <el-drawer
         v-model="drawer"
         :direction="'rtl'"
         :before-close="handleClose"
         :withHeader="false"
         :size="290"
         >
         <menu-bar :directionType="'vertical'" :showLogo="false" :showSearch="false"></menu-bar>
     </el-drawer>
 </template>
 <script setup lang="ts">
     import { ref } from "vue";
     import { useRouter } from 'vue-router';
     import MenuBar from '@/components/layout/menu/';
     import {Expand} from "@element-plus/icons";
     // router === $router(vue2.0)
     const router = useRouter()
     // Menu pop-up window
     const drawer = ref<boolean>(false)
     const handleClose = (done:any) => {
         done()
     }
     // Open menu event
     const handleOpenMenu =()=>{
          = !
     }
 </script>
 <style lang="scss" >
    
 </style>

Note: After using media query, the style name cannot be written in the page style again, otherwise the media query effect will fail and need to be modified in the global file.

// Set basic styles under the small screen
 .show-mobile{
     display: flex !important;
     justify-content: flex-start;
     padding: 0 5px;
     background-color: #2c3e50;
     .same-cell{
         color: #ffff;
     }
 }

Note: If the icon icon size does not take effect, it may be affected by other styles. You need to set the global font size to solve the problem.

10.2 Renovated menu components

<template>
     <div class="nav-menu-wrapper">
         <div v-if="showLogo" class="el-menu el-menu--horizontal logo-box">
             <img class="set-img" src="@/assets/" alt="picture" title="Welcome to Buyi Blog">
         </div>
         <el-menu
             :default-active="activeIndex2"
             class="menu-box"
             :mode="directionType"
             background-color="#2c3e50"
             text-color="#fff"
             active-text-color="#ffd04b"
             @select="handleSelect"
         >
             <template v-for="(item, index) in menuList" :key="">
                 <template v-if="!">
                     <el-menu-item :index="?.index" @click="handleRoute(item)">{{?.title}}</el-menu-item>
                </template>
                <template v-else>
                    <el-sub-menu :index="?.index">
                    <template #title>{{?.title}}</template>
                    <el-menu-item :index="?.index" v-for="(subItem, index) in " :key="" @click="handleRoute(subItem)">{{?.title}}</el-menu-item>
                     </el-sub-menu>
                 </template>
             </template>
         </el-menu>
     </div>
 </template>
 <script lang="ts" setup>
     import { ref, calculated } from "vue";
     import { useRouter } from "vue-router";
     import { useStore } from "@/store";
     import { mapState } from "vuex";

     const router = useRouter();
     const store = useStore();

     interface Props {
         showLogo?: boolean,
         showSearch?: boolean,
         directionType?: string // Direction type, vertical and horizontal, default horizontal
     }
     withDefaults(definedProps<Props>(), {
         directionType: 'horizontal',
         showLogo: true,
         showSearch: true
     })

     // const activeIndex2 = ||ref<string>('1')
     const activeIndex2 = computed(mapState(['currentMenu']).({ $store: store }))||ref<string>('1')
     const menuList = [0].children
    
     const handleSelect = (key:string, keyPath:string) => {
         // (key, keyPath)
         ('SET_CURRENT_MENU', key);
     }
     const handleRoute = (item:any)=>{
         ()
     }
 </script>
 <style lang="scss">
 @import "./";
 </style>

11. Medium batch injection @element-plus/icons icon

import * as ElIcons from '@element-plus/icons';

const app = createApp(App);
Object.keys(ElIcons).forEach(key =>{
    app.component(key, ElIcons[key]);
})

Note: As here means a different alias, for fear of component names conflicting

12. Add login and register button to the top navigation bar

Added in menu component

// vue
 <div class="el-menu el-menu--horizontal combine-btn-box">
     <span class="child-item">Login</span>
     <span class="child-item">Register</span>
 </div>
 // ts
 <script lang="ts" setup>
     import { ref, calculated } from "vue";
 </script>
 <style lang="scss">
	 .combine-search-box{
         color: #ffff;
         background-color: #2c3e50;
         height: 61px;
         line-height: 61px;
         .child-item{
             padding: 0 5px;
             cursor: pointer;
             &:hover{
                 color: #ffd04b;
             }
         }
     }
 </style>

13. Breadcrumb breadcrumb displays the current routing location

13. 1 Add a new bread-crumb folder under the layout folder, and the content is as follows:

<template>
     <el-row>
         <el-col :span="16" class="bread-list">
             <p>Current location:</p>
             <el-breadcrumb separator="/">
                 <!-- <el-breadcrumb-item :to="{ path: '/' }">homepage</el-breadcrumb-item> -->
                 <el-breadcrumb-item v-for="(item, index) in breads" :key="index" :to="">{{}}</el-breadcrumb-item>
             </el-breadcrumb>
         </el-col>
         <el-col :span="8" class="bread-list">
             <el-icon>
                 <wind-power />
             </el-icon>Message display
         </el-col>
     </el-row>
 </template>
 <script lang="ts" setup>
     import {ref, Ref, watch } from 'vue';
     import {useRoute, RouteLocationMatched } from 'vue-router';

     const route = useRoute()
     // Ref interface restriction type
     const breads:Ref<RouteLocationMatched[]> = ref([])

     const getBreadList = ()=>{
         let list = (e => &&)
         //Judge homepage
         const one = list[0]
         if( !== '/home'){
             let arr = [{path: '/home', meta:{title: 'Home'}} as any]
             list = [...arr, ...list]
         }
         // Assign crumbs
          = list
     }
     getBreadList()
     watch(()=>, ()=>{
         getBreadList()
     })
 </script>
 <style lang="scss">
     .bread-list{
         display: flex;
         align-items: center;
         padding: 4px;
     }
 </style>

13.2 Use bread crumbs in the header/

Note: If the navigation node is a parent node, the route will not jump. You need to set the default route of the parent node.

For example:

{
    path: "/program",
    name: "program",
    redirect: "/program/front",
    meta:{
         title: 'Programming related',
         index: '2'
          },
     component:()=> import("@/pages/program/"),
          children:[
                 {
                      path: "/program/front",
                      name: "program-front",
                      meta:{
                         title: 'Front-end programming',
                         index: '2-1'
                       },
                       component:()=> import("@/pages/program/front/")
                   },
                   {
                        path: "/program/applet",
                        name: "program-applet",
                        meta:{
                              title: 'WeChat Mini Program',
                              index: '2-3'
                        },
                        component:()=> import("@/pages/program/applet/")
                    },
            ]
},

14. Test whether the breadcrumb guide is correct

14.1 Prepare two pages and put them in the pages/program folder behind and front

Added a new secondary route exit under the program folder, the content is as follows

<template>
    <router-view></router-view>
</template>
<script setup lang="ts">
</script>
<style lang="scss">
</style>

// Put the front-end page vue in the front folder

<template>
     <div class="common-layout-container">frontend</div>
 </template>
 <script setup lang="ts">
    
 </script>
 <style lang="scss" scoped>
   
 </style>

// Put the backend page vue in the behind folder

<template>
     <div class="common-layout-container">Backend</div>
 </template>
 <script setup lang="ts">
    
 </script>
 <style lang="scss" scoped>

 </style>

14.2 The route of the new program file is added

{
    path: "/program",
    name: "program",
    redirect: "/program/front",
    meta:{
         title: 'Programming related',
         index: '2'
          },
     component:()=> import("@/pages/program/"),
          children:[
                 {
                      path: "/program/front",
                      name: "program-front",
                      meta:{
                         title: 'Front-end programming',
                         index: '2-1'
                       },
                       component:()=> import("@/pages/program/front/")
                   },
                   {
                        path: "/program/behind",
                        name: "program-behind",
                        meta:{
                              title: 'rear end',
                              index: '2-3'
                        },
                        component:()=> import("@/pages/program/behind/")
                    },
            ]
},

15. Solve the problem of misalignment of parent menu arrows

Solution: Reduce the element-plust version

// In previous versions, the menu arrows were misaligned
"element-plus": "^1.2.0-beta.2"
// Reduced version, stable version
"element-plus": "^1.1.0-beta.20"

16. Design carousel diagram components

Preparation:

  • Comment out the style of the file
  • Added a common box style, all page parent elements are based on it
// Set global general box basic style
 .common-layout-container{
     padding: 0px 10px 5px;
 }

1. Add new components, carousels/, the content is as follows

<template>
     <el-carousel indicator-position="outside" class="carousels-box">
         <el-carousel-item v-for="item in carousels" :key="item">
             <img :src="item" :alt="item" class="set-el-img">
         </el-carousel-item>
     </el-carousel>
 </template>
 <script lang='ts' setup>
     import {ref, reactive} from 'vue';
     // Define the marionette effect diagram
     const carousels = reactive([
         '/it/u=3569419905,626536365&fm=193&f=GIF',
         '/it/u=1285847167,3193778276&fm=193&f=GIF',
         '/it/u=4162611394,4275913936&fm=193&f=GIF',
         '/it/u=3655946603,4193416998&fm=193&f=GIF'
     ])
 </script>
 <style lang="scss" scoped>
     .carousels-box{
         .set-el-img{
             width: 100%;
             height: 100%;
         }
     }
 </style>

17. Design article components

1. Add new components, article-item/, the content is as follows

<template>
     <div class="container-item">
         <span class="span-label"></span>
         <div class="item-info">
             <div class="img-left">
                 <img src="/it/u=3655946603,4193416998&fm=193&f=GIF" alt="pic">
             </div>
             <div class="resume-right">
                 <h3 class="resume-title" @click="goToInfo">Use the mixins module of vue to improve the reuse of component functions and data vue to improve the reuse of component functions and data</h3>
                 <div class="resume-detail">mixins: The order of execution: priority is to execute the method of the page with the same name that references it, and then execute the method of referencing the page</div>
             </div>
         </div>
         <div class="node-list-btn">
             <span>Author: Buyi Frontend</span>
             <span>Time:2021-10-20</span>
             <span>Views: 1555(times)</span>
             <span>Comments: 87(items)</span>
             <vxe-button icon="fa fa-eye" status="success" @click="goToInfo">Read the full text</vxe-button>
         </div>
     </div>
 </template>
 <script lang="ts" setup>
 import { useRouter } from "vue-router";
 const router = useRouter()
 const goToInfo = ()=>{
     ('/article/info')
 }
 </script>
 <style lang="scss" scoped>
     .container-item{
         background-color: #fff;
         padding: 15px 15px 8px 15px;
         margin: 0 0 10px;
         border-radius: 4px;
         position: relative;
         .span-label{
             display: block;
             width: 7px;
             height: 40px;
             position: absolute;
             left: 0px;
             top: 15px;
             background-color: #017E66;
         }
         .item-info{
             // min-height: 170px;
             display: flex;
             .img-left{
                 flex: 0 0 150px;
                 width: 150px;
                 height: 94px;
                 border-radius: 4px;
                 margin: 1px 20px 0 0;
                 overflow: hidden;
                 @include globalTransition();
                 &:hover{
                     height: 105px;
                 }
                 img{
                     width: 100%;
                     height: 100%;
                     @include globalTransition();
                     &:hover{
                         transform: scale(2);
                     }
                 }
             }
             .resume-right{
                 line-height: 1;
                 // Article title ellipsis
                 .resume-title{
                     line-height: 20px;
                     padding: 0px !important;
                     margin: 0 0 5px 0;
                     @include muit-ellipsis(2);
                     @include common-title(left);
                     overflow: hidden;
                     &:hover{
                         color: $text-color-main;
                         cursor: pointer;
                     }
                 }
                 .resume-detail{
                     @include muit-ellipsis(3)
                 }
             }
         }
         .node-list-btn{
             display: flex;
             justify-content: flex-end;
             align-items: center;
             flex-wrap: wrap;
             span{
                 padding: 0 10px;
                 font-size: 14px;
             }
         }
     }
 </style>

2. Add styles/, which specifically places project general styles and function styles

// Global general functions
 @mixin common-title($center: center){
     font-weight: bold;
     text-align: $center;
     padding: 5px 0px;
 }

 /* Single line exceeds hidden */
  @mixin ellipsis() {
      word-wrap: break-word;
      word-break: break-all;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
  }
 
  /* Multiple lines exceed hidden */
  @mixin muit-ellipsis($line) {
      word-break: break-all;
      overflow: hidden;
      text-overflow: ellipsis;
      display: -webkit-box;
      -webkit-line-clamp: $line;
      -webkit-box-orient: vertical;
  }

 
  @mixin globalTransition( $time:0.3s) {
      @if $time==0.3s {
          -ms-transition: all 0.3s ease-in; /* IE 9 */
          -webkit-transition: all 0.3s ease-in; /* Safari and Chrome */
          -moz-transition: all 0.3s ease-in; /* Firefox */
          -o-transition: all 0.3s ease-in; /* Opera */
          transition: all 0.3s ease-in;
      }@else{
          -ms-transition: all $time ease-in; /* IE 9 */
          -webkit-transition: all $time ease-in; /* Safari and Chrome */
          -moz-transition: all $time ease-in; /* Firefox */
          -o-transition: all $time ease-in; /* Opera */
          transition: all $time ease-in;
      }
  }

3. Article details page

<!-- Article details -->
 <template>
     <div class="common-page-warpper set-wrapper-info">
         <div class="common-content-left-box set-left-box">
             <div class="header-labels">
                 <el-tag size="mini">Original</el-tag>
                 <el-tag size="mini">Category:web</el-tag>
                 <span>Author: Buyi Frontend</span>
                 <span>Time:2021-10-20</span>
                 <span>Views: 1555(times)</span>
                 <span>Comments: 87(items)</span>
             </div>
             <div class="content-continer">
                 Another difference is that for..in can manipulate any object; it provides a way to view object properties.  But for..of focuses on the value of iterated objects.
                 The built-in objects Map and Set have implemented methods so that we can access the values ​​they hold.
                 Another difference is that for..in can manipulate any object; it provides a way to view object properties.  But for..of focuses on the value of iterated objects.
                 The built-in objects Map and Set have implemented methods so that we can access the values ​​they hold.
                 Another difference is that for..in can manipulate any object; it provides a way to view object properties.  But for..of focuses on the value of iterated objects.
                 The built-in objects Map and Set have implemented methods so that we can access the values ​​they hold.
                 Another difference is that for..in can manipulate any object; it provides a way to view object properties.  But for..of focuses on the value of iterated objects.
                 The built-in objects Map and Set have implemented methods so that we can access the values ​​they hold.
                 Another difference is that for..in can manipulate any object; it provides a way to view object properties.  But for..of focuses on the value of iterated objects.
                 The built-in objects Map and Set have implemented methods so that we can access the values ​​they hold.
                 Another difference is that for..in can manipulate any object; it provides a way to view object properties.  But for..of focuses on the value of iterated objects.
                 The built-in objects Map and Set have implemented methods so that we can access the values ​​they hold.
                 Another difference is that for..in can manipulate any object; it provides a way to view object properties.  But for..of focuses on the value of iterated objects.
                 The built-in objects Map and Set have implemented methods so that we can access the values ​​they hold.
                 Another difference is that for..in can manipulate any object; it provides a way to view object properties.  But for..of focuses on the value of iterated objects.
                 The built-in objects Map and Set have implemented methods so that we can access the values ​​they hold.
                 Another difference is that for..in can manipulate any object; it provides a way to view object properties.  But for..of focuses on the value of iterated objects.
                 The built-in objects Map and Set have implemented methods so that we can access the values ​​they hold.
                 Another difference is that for..in can manipulate any object; it provides a way to view object properties.  But for..of focuses on the value of iterated objects.
                 The built-in objects Map and Set have implemented methods so that we can access the values ​​they hold.
             </div>
             <div class="commit-area-box">
                 <h2 class="commit-text"> <el-icon><message/</el-icon>Comments section</h2>
                 <el-button type="primary" @click="releaseCommit('ruleForm')">Leave a comment</el-button>
             </div>
             <div class="commit-area-box">
                 <h2 class="commit-text"> <el-icon><message/</el-icon>Total {{}}Message</h2>
                 <div class="commit-info" v-for="item in list">
                     <p><span class="commit-text">{{}}</span>:{{}}</p>
                     <div v-html=""></div>
                 </div>
               
             </div>
         </div>
         <!-- <div class="common-content-right-box set-right-box">
             <article-label/>
             <web-site-info/>
         </div> -->
     </div>
 </template>
 <script lang="ts" setup>
     import {ref, onMounted, reactive} from "vue";
     import defineGlobalStance from "@/script/utils/defineGlobalStance";
     import { ElMessage } from 'element-plus';
     const { globalConfig } = defineGlobalStance();
     interface CommitType {
         date?: string,
         username?: string,
         content?: string,
         qq?: string
     }
     const commitObj = reactive<CommitType>({
         date: "",
         username: "",
         content: "",
         qq: ""
     })
    
     const list = ref<Array<CommitType>>([])
     const i = ref<number>(0)
     const editorRef = ref()
     const releaseCommit =(form:string)=>{
         ++
         if(()){
             // = ()
             ((), 1111);
             // = globalConfig.$currentFormat(())
             // = 'Anonymous'
             ({username:`anonymous ${}`,content:(), date:globalConfig.$currentFormat(())})
             ()
         }else ('Comment content cannot be empty...')
        
     }

     onMounted(()=>{
         ({date: globalConfig.$currentFormat(()), username: 'Anonymous', content: 'Default Comment', qq:'1766226354'})
     })
 </script>
 <style lang="scss">
     @import "./";
 </style>

19. Design blogger information components

1. Added the new component author-info/, the content is as follows

<template>
     <div class="author-info">
         <h2 class="author">Buyi Blog</h2>
         <el-divider></el-divider>
         <div class="info-item">
             <p>Author: Buyi Frontend</p>
             <p>QQ: 1766226354</p>
             <p>Address: Guiyang, Guizhou</p>
             <p>Project description: Buddy front</p>
             <p>Motto: No one can reach a thousand miles without accumulating silicon, and no one can form a river or sea without accumulating small streams</p>
             <p>Incoming: personal website, web design, web outsourcing, app production, system research and development, etc.</p>
             <p class="label-item">Project information</p>
         </div>
     </div>
 </template>
 <style lang="scss" scoped>
     .author-info{
         background-color: $bg-color;
         width: 100%;
         margin-bottom: 15px;
         padding: 10px 12px;
         border-radius: 8px;
         .author,.info-item{
             box-sizing: border-box;
         }
         .author{
             color: $text-color-main;
             @include common-title();
         }
         .info-item{
             // display: flex;
             // flex-wrap: wrap;
             padding: 4px;
             overflow: hidden;
             position: relative;
             p{
                 padding: 4px;
             }
             .label-item{
                 color: #ffff;
                 font-size: 12px;
                 background-color: $label-color;
                 min-width: 120px;
                 text-align: center;
                 transform: rotate(50deg);
                 -ms-transform:rotate(50deg); /* IE 9 */
                 -moz-transform:rotate(50deg); /* Firefox */
                 -webkit-transform:rotate(50deg); /* Safari and Chrome */
                 -o-transform:rotate(50deg); /* Opera */
                 position: absolute;
                 right: -31px;
                 top: 27px;
             }
         }
     }
 </style>

20. Design copyright information components

Added the new component copyright-info/, the content is as follows

<template>
     <div class="website-footer">
         <h2 class="title">Buyi Front-end Copyright, violators will be prosecuted</h2>
         <el-divider></el-divider>
         <div class="info-item">
             <p>Copyright | Powered by Buyi Frontend © 2021</p>
         </div>
     </div>
 </template>
 <style lang="scss" scoped>
     .website-footer{
         background-color: $bg-color;
         width: 100%;
         margin-bottom: 15px;
         padding: 10px 12px;
         .title{
             color: $text-color-main;
             @include common-title();
         }
         .title,.info-item{
             box-sizing: border-box;
         }
         .info-item{
             padding: 4px;
             overflow: hidden;
             text-align: center;
         }
     }
    
 </style>

21. Tag Component

<template>
     <div class="label-info">
         <h2 class="title">Tag Cloud</h2>
         <el-divider></el-divider>
         <div class="info-item">
             <span>Vue2</span>
             <span>Vue3</span>
             <span>Vite</span>
             <span>Axios</span>
             <span>Linux</span>
             <span>Java</span>
             <span>JavaScript</span>
             <span>TypeScript</span>
             <span>Spring</span>
             <span>Spring Boot</span>
         </div>
     </div>
 </template>
 <style lang="scss" scoped>
     .label-info{
         background-color: $bg-color;
         width: 100%;
         margin-bottom: 15px;
         padding: 10px 12px;
         border-radius: 8px;
         .title,.info-item{
             box-sizing: border-box;
         }
         .title{
             color: $text-color-main;
             @include common-title();
         }
         .info-item{
             display: flex;
             flex-wrap: wrap;
             padding: 4px;
             overflow: hidden;
             span{
                 padding: 4px;
                 color: $text-color-main;
             }
         }
     }
 </style>

22. Website Information Component

<template>
     <div class="website-info">
         <h2 class="title">Website information</h2>
         <el-divider></el-divider>
         <div class="info-item">
             <p>Total number of articles: 555 articles</p>
             <p>Total number of tags: 45</p>
             <p>Total number of categories: 15</p>
             <p>Total number of visits: 2455 times</p>
             <p>Number of online users: 24</p>
             <p>Number of days of operation of the website: 264 days</p>
         </div>
     </div>
 </template>
 <style lang="scss" scoped>
     .website-info{
         background-color: $bg-color;
         width: 100%;
         margin-bottom: 15px;
         padding: 10px 12px;
         border-radius: 8px;
         .title{
             color: $text-color-main;
             @include common-title();
         }
         .title,.info-item{
             box-sizing: border-box;
         }
         .info-item{
             padding: 4px;
             overflow: hidden;
         }
     }
    
 </style>

Note: The above author component, article tag component, and website information component are globally injected and can be used according to your own needs; in addition, the specific content layout style refers to the actual project code style, and will not be listed here. You can find the style by searching according to the class name.

The complete file content is as follows:

import { createApp } from 'vue';
import App from './';
import { store, key } from './store';
// Inject routing
import router from './router';

// Inject ui component library
import ElementPlus,{ElNotification} from 'element-plus';
import 'element-plus/dist/';

import 'xe-utils'
import VXETable from 'vxe-table'
import 'vxe-table/lib/'

// Introduce font-awesome
import '@/assets/font-awesome/css/';

import * as ElIcons from '@element-plus/icons';

import { start, close} from "@/script/utils/nprogress";

import AuthorInfo from '@/components/author-info/';
import ArticleLabel from '@/components/article-label/';
import WebSiteInfo from '@/components/website-info/';

// Current time format
import {currentFormat} from '@/script/utils';

const app = createApp(App);

// Batch injection icon component
Object.keys(ElIcons).forEach(key =>{
    app.component( key, ElIcons[key]);
})

app.component('AuthorInfo', AuthorInfo)
app.component('ArticleLabel', ArticleLabel)
app.component('WebSiteInfo', WebSiteInfo)

app.use(ElementPlus);
app.use(VXETable);

app.use(store, key);
app.use(router);
app.mount('#app');

// Mounting method
app.config.globalProperties.$currentFormat = currentFormat;

// Set up the road navigation
router.beforeEach((to, from, next)=>{
    start();
    // (to, from, next,111);
    if(to.meta.title){
        document.title = String(to.meta.title)
    }else document.title = 'Buyi Blog'

    //Route error handling
    if(to.matched.length===0&&!to.name){
        ElNotification({
            title: 'Error prompt!  ',
            message: `${to.fullPath}】The corresponding page cannot be found on the path, and the default switch to the homepage...`,
            type: 'error',
            duration: 3000
        })
        router.push('/home')
    }
    next()
})

// Execute after the route is loaded
router.afterEach(()=>{
    close()
})

Summary 【Important】

The Buyi blog case ends here. The content layout part does not involve any vue3 knowledge points, but is purely a front-end basic knowledge part, so we did not lead everyone to code one component at a time. Remember to follow [Buyi Frontend]'s official account, and the warehouse links to obtain later projects are all put here. Buyi Front Terminal Thank you for your attention and encouragement again. Continue to work hard to record more high-quality front-end courses, continue to pay attention to Buyi's front-end, and have surprises. Thank you all for your family for reviewing and watching the works of Buyi, and wish you all a bright future and all the best.