你不知道的vue

 一、Vue语法 (vue2、vue3)

01.生命周期

02.组件注册

Vue2

方法1:全局注册。

// main.js

import HmButton from './components/HmButton.vue' 	  
Vue.component('HmButton', HmButton)  

方法2:局部注册。

// 组件内部

export default {
   components: {
      HelloWorld:  () => import('@/components/HelloWorld') 
   }
};

Vue3

只需导入组件,无需手动注册。


03.computed

 Vue2

写法1:简写。

// 任务:根据数据numA、numB, 计算出numC的值。

export default {
	data() {
        return {
            numA: 10,
            numB: 20
        }
    },
    // 计算属性
    computed: {
        numC() {
            return this.numA + this.numB
        }
    }
}

写法2:getter、setter写法。

export default {
    // 计算属性
    computed: {
        total: {
           get() {  
             ...
           },    
           set(val) {  
             ...
           }
        }
    }
}

Vue3

写法1:简写。

// (1)导入。
import { ref, computed } from "vue";

// (2)定义计算属性:computed(回调函数)
const count = ref([1, 2, 3, 4, 5, 6, 7, 8, 9])
const computed_count = computed(()=> count.value.filter(item => item > 2) })

 写法2:getter、setter写法。

// (1)导入
import { ref, computed } from "vue";

// (2)定义计算属性:computed(对象),且该对象包含get()和set()方法。
const computed_count = computed({
     get: () => count.value + 1,
     set: (val) => count.value = val - 1
})


04.watch

Vue2

情况1:监听一个简单数据类型。

// 任务:监听简单数据类型myData的改变。

export default {
	data() {
       myData: '猪妹'
    },
   
    // 定义监听器:watch: { 函数 }。 (函数名与数据名称一致)
	watch: {
       myData(newValue, oldValue) {
            console.log(newValue, oldValue)
       }
    }
}

情况2:监听多个简单数据类型。

// 任务:监听简单数据类型myData的改变。

export default {
	data() {
       myData: '猪妹',
       myName: '张益达'
    },
   
    // 定义监听器:watch: { 函数1, 函数2, ... }。 (函数名与数据名称一致)
	watch: {
       myData(newValue, oldValue) {
            console.log(newValue, oldValue)
       },
       myName(newValue, oldValue) {
            console.log(newValue, oldValue)
       }
    }
}

情况3:监听对象的一个属性。

export default {
	data() {
        obj: {
            word: '',
            lang: ''
        }
    },
   
    // 定义watch监听器:watch{ 函数是1, 函数2, ... } (函数名为[对象.数据])
	watch: {
        'obj.word'(newValue, oldValue){
            console.log(newValue, oldValue)
       },
        'obj.lang'(newValue, oldValue){
            console.log(newValue, oldValue)
       }
    }
}

 情况4:监听整个对象。

export default {
	data() {
        obj: {
            word: '',
            lang: ''
        }
    },
    // 定义watch监听器: watch: { 键值对 }
	watch: {
         obj: {
             deep: true,  	 // 监听整个对象
             immediate: true,  // 页面打开后自动执行一次该对象或属性的监听。
             handler(obj) {  
                 ...
             }
         }
    }
}

Vue3

情况1:监听一个简单数据类型。

// 任务:监听数据count的变化。

// (1) 准备数据count
const count = ref(1)
// (2) 定义watch监听器:watch(被监听的数据, 回调函数)
watch(count, (newValue, oldValue) => {
 	...
})

情况2:监听多个简单数据类型。

// 任务:同时监听数据count、name的变化。

// (1) 准备数据count、name
const count = ref(1);
const name = ref('思密达')
// (2) 定义watch监听器:watch([数据1,数据2,...], 回调函数)
watch([count, name], ([newCount, newName],[oldCount, oldName]) => {
 	...
})

情况3:监听整个对象。

// 任务:监听对象obj的改变。

// (1) 准备数据obj
const obj = ref({  
   age: 18,
   name: '张益达'
})

// (2) 定义watch监听器: watch(对象, 回调函数, { immediate:true, deep: true })
watch(obj, (newValue, oldValue) => 
{
 	console.log(newValue, oldValue)
}, 
{
 immediate: true,  // 表示页面一打开就执行一次watch
 deep: true 	   // 深度监视(默认是浅层监视,只能监视简单数据类型)
})

情况4:监听对象中的一个属性。

// 任务: 监听对象obj中的age属性的改变。

// (1) 准备数据obj
const obj = ref({  
    age: 18,
    name: '张益达',
})

// (2) 定义watch监听器:watch(回调函数, 回调函数)
watch(
 () => obj.value.age,  
 (newValue, oldValue) => { ... }
)


05.this.$nextTick

原理:onMounted()是同步方法,而this.$nextTick()是异步方法,会在mounted后执行。

Vue2

 vue源码:

class Vue {
	// 1.构造器
	constructor(options) {
        // (1) 获取data
		this.$data = options.data

        // (2) 修改生命周期created()的this指向, 并调用该函数
		options.created.bind(this)()

        // (3) 获取DOM元素
		this.$el = document.querySelector(options.el)

        // (4) 修改生命周期mounted()的this指向, 并调用该函数
		options.mounted.bind(this)()
	}
    
    // 2.提供$nextTick函数
    $nextTick(callback) {
        return Promise.resolve().then(() => {
            callback()
        })
    }
}

实操:

created() {
	// (1) 显示输入框
	this.isShowEdit = true

	// (2) 让输入框获取焦点: 等待DOM渲染完成后,才开始执行该行代码。
	this.$nextTick(() => this.$refs.inp.focus() )
}

Vue3

<script setup>
    // 导入nextTick函数
    import { nextTick, ref } from 'vue'

    // 准备数据
    let flag = ref(true)

    // Mounted生命周期
    onMounted(() => {
        // 延迟到DOM渲染后,才执行该代码
        nextTick(() => { flag.vlaue = false })
     }) 
</script>


06.自定义指定

Vue2

先学习四个钩子函数:

(1) bind()		// 当前指令被绑定到元素时调用
(2) inserted()    // 表示当前指令所绑定的元素,被渲染到页面时调用。  
(3) updated()     // 被绑定元素的DOM更新后调用。
(4) componentUpdated()	// 当binding.value的值改变时被调用。

情况1:全局注册。

// main.js中注册。

Vue.directive('loading', {
  bind(el, binding) {
     binding.value ? el.classList.add('loading') : el.classList.remove('loading')
  },
  updated(el, binding) {
     binding.value ? el.classList.add('loading') : el.classList.remove('loading')
  }
})

情况2:局部注册。

// 组件内部注册。

directives: {
  'loading': {
      bind(el, binding) {
         binding.value ? el.classList.add('loading') : el.classList.remove('loading')
      },
      updated(el, binding) {
         binding.value ? el.classList.add('loading') : el.classList.remove('loading')
      }
  }
}

使用方法如下:

Vue3

先学习几个钩子函数(没有学习成本,跟组件生命周期一致)

情况1:全局注册。

const app = createApp(App)

app.directive('指令名称', {
	// 钩子函数
})


07.自定义插件

Vue2

Vue3

调用:由于vue3没有this,不能直接调用this.$showLoginDialog()


08.自定义组件(手动)

 Vue2

步骤1:手动获取组件根DOM节点

//  功能:根据传入的组件comp,组件需要的属性props,就可以构建出组件的DOM节点。
function getComponentRootDom (comp, props) {
  const vm = new Vue({
    render: (h) => h(comp, { props })
  })
  vm.$mount()
  return vm.$el
}

// 使用示例:<Icon :type='home'>
import Icon from '@/components/Icon'
const dom = getComponentRootDom(Icon, { type: 'home' })
console.log(dom)

 步骤2: 挂载到vue原型

import Vue from 'vue'
const vm = new Vue({...})
    
// 方式1:在某个实例上扩展属性、方法。(只影响一个vue实例)
vm.$sayHello() = function(){}

// 方式2:在vue的原型上扩展属性、方法。(影响到每一个vue实例)
Vue.prototype.$sayHello() = function(){}

Vue3


09.插槽

Vue2

 情况1:默认插槽。

// 定制MyDialog组件
<div class="container">
    <slot></slot>  // 用slot占位,默认name:default。
</div>

// 使用方法
import MyDialog from '@/components/MyDialog'
<MyDialog>
    <div>我是用来填充插槽的</div>
</MyDialog>

情况2:具名插槽。(v-slot:插槽名称,简写为:#插槽名称)

情况3:作用域插槽。

// (1) 给slot标签,以添加属性的方式传值。
<slot :id="item.id" msg="测试文本"></slot>

// (2) 将来这些参数会被统一收集到一个对象中:{id: 3, msg: '测试文本'}

// (3) 在template中,通过"#插槽名=obj"来接收,默认插槽名称为default。
<MyTable>
    <template #default="obj">
        <button @click="delete(obj.id)">删除</button>
    </template>
</MyTable>

Vue3

跟vue2一致。


10.获取组件DOM实例

Vue2

 情况1:获取普通dom节点

// (1) 找到目标标签,添加ref属性
	<div ref="chartRef">我是渲染图表的容器</div>

// (2) 在mounted()中,获取DOM。(因为要等页面渲染出来才能获取的到) 
     mounted() {
       console.log(this.$refs.chartRef);
     }

情况2:获取组件的dom节点

// (1) 找到目标组件,添加ref属性
	<BaseForm ref='chartRef'></BaseForm>

// (2) 在mounted()中,获取DOM。(因为要等页面渲染出来才能获取的到)
     mounted(){
        console.log(this.$refs.chartRef);
     }    

情况3:获取父、子组件实例

Vue3

情况1:获取DOM节点实例

// 任务:获取h1这个DOM节点。

<template>
	<h1 ref='h1Ref'>我是h1标签</h1>
</template>


<script setup>
    // 调用ref函数,根据ref标识得到ref对象
    const h1Ref = ref(null)
</script>

情况2:获取组件实例使用方式如下:

A组件

<template>
	<h1 ref='h1Ref'>我是h1标签</h1>
</template>

<script setup>
// 2.准备数据testMessage
const testMessage = ref('我真的很帅')

// 3. 将数据testMessage暴露出去
defineExpose({
    testMessage
})
</script>

B组件

<template>
	<A ref='ARef'>我是h1标签</A>
</template>

<script setup>
import A from '@/components/A.vue'

//  1.如果B组件想调用A组件内提供的testMessage数据,
//    需要在A组件中通过defineExpose({})暴露后才能被访问。
const ARef = ref(null)
console.log(ARef.testMessage.value)
</script>


11.指令修饰符

 第一类:v-model修饰符

(1).lazy(输入框聚焦、失焦时触发)

(2).number(尝试将输入框的value值转为number类型)

(3).trim(去除输入框的value值首尾空格)

第二类:事件修饰符

(1).stop(阻止事件冒泡)

(2).prevent(防止触发默认事件)

(3).capture(将默认触发方式改为捕获触发)

(4).self(只有当事件发生在目标元素自身时才会触发)

(5).once(事件只触发一次)

第三类:键盘修饰符

(1).enter(回车键触发)

(2).tab(tab键触发)

(3).delete(通过该元素需要配合v-if使用,是指DOM节点显示、隐藏时触发)

(4).esc(esc键触发)


12.防抖、节流

Vue2

Vue3

 防抖的实现

// vue3实现防抖: 在utils目录下,新建tools.ts文件。这里用到vue3提供的customRef函数。
 import {customRef} from 'vue'

 export const debounceRef = (value:string, delay:number) => {
    let timer:any = null
    return customRef((track, trigger) => {
       return {
        get() {
            track() // 依赖收集
            return value
        },
        set(newValue) {
            clearTimeout(timer)
            timer = setTimeout(() => {
                value = newValue
                trigger() //派发更新
            }, delay)
        }
       }
    })
 }

// 使用方法:
<input type="text" v-model="text">

import { debounceRef, throttle } from '@/utils/tool.ts'
let text = debounceRef('1233', 3000)

节流的实现

// 节流的实现:在utils目录下,新建tools.ts文件。这里用到vue3提供的customRef函数。
 export const throttle = (func:Function, delay:number) => {
    let flag = false
    return function() {
        if(flag) return
        flag = true
        func.apply(this, arguments)
        setTimeout(() => {
            flag = false
        }, delay);
    }
 }

// 使用方法:
<button @click="btn">按钮</button>

const btn = throttle(() => {
  console.log(1)
}, 10000)


13.路由跳转

Vue2

 方法1:声明式导航。

// 第一种:根据路由路径path跳转。
(1) 动态路由:
    首先去路由模块中,配置{ path:'/home/:参数名?'}
    <router-link :to="/home/黑狗程序员">User</router-link>
(2) 查询参数:
    <router-link :to="/home?username=思密达&age=18">User</router-link>
    <router-link :to="{ path: '/home', query:{ username:'思密达', age: 18 }">

// 第二种:根据路由名称name跳转。
(1) 动态路由:
    <router-link :to="{ name: 'user', params: { id: 123 } }">
(2) 查询参数:
    <router-link :to="{ name: 'user', query: { id: 123, username: 'john' } }">

方法2:编程式导航。

//  方式1:根据path路径跳转。
(1)动态路由:
    this.$router.push('/home/黑狗程序员')
(2)查询参数:
    this.$router.push('/home?username=思密达&age=18')
    this.$router.push({ path: '/home', query{ username:'思密达', age: 18 } })

//  方式2:根据name命名路由跳转。
(1)动态路由:
    this.$router.push({ path: '/home', params:{username: '思密达'} })
(2)查询参数:
    this.$router.push({ path: '/home', query: { username: '思密达', age: 18 } })

方法3:点击返回上一页。

Vue3


14.keep-alive

(1) 找到不需要被销毁的组件,然后用<keep-alive>标签将该组件进行包裹起来。
(2) 如果用<keep-alive>标签来包裹一级路由, 则一级路由所匹配的组件都会被缓存。
<keep-alive>
  <router-view></router-view>  
</keep-alive>

(3) 对于不需要缓存的组件,比如是详细页面的组件,就可以使用如下方法进行排除掉。
    其实keep-alive标签,有三个属性是可以设置的,最常用的include。
   // 组件名怎么看的话,优先看export default{ name:组件名},如果没有配置name的话,就是这个组件的文件名称。
   // 属性前面记得加上冒号,才能识别组件名,从而识别组件。   
	1):include="['组件名1','组件名2',...]" ,表示只缓存数组中有写的组件。 // 数组中的都缓存,其他都不缓存。
	2):exclude="['组件名1','组件名2',...]",表示出现在数组内的组件,都不会被缓存。 // 数组中的都不缓存,其他的都缓存。
	3) max:值,表示最多可以缓存多少组件实例。


//  1. 被缓存的组件, 会多出两个生命周期
(1)  actived: 激活时, 表示组件被看到时触发.
(2)  deactived: 失活时, 离开该组件后触发.
    
//  2. 像created(), mounted(), destory()都不会被调用。   


15.动态组件(Vue3)

<template>
  <ul>
    <li v-for="(item, index) in tabList" :key="item.name" @click="changeCom(index)">
      {{ item.name }}
    </li>
  </ul>
  <!-- 动态组件 -->
  <keep-alive>
    <component :is="currentComponent.com"></component>
  </keep-alive>
</template>


<script setup>
import { ref, markRaw } from "vue"
// 导入组件
import componentA from '@/components/componentA.vue'
import componentB from '@/components/componentB.vue'
import componentC from '@/components/componentC.vue'


// 准备数据,markRaw只是为了让它不要浪费响应式,我们不需要使用响应式。
let tabList = ref([
  { name: 'A小伙子', com: markRaw(componentA) },
  { name: 'B小伙子', com: markRaw(componentB) },
  { name: 'C小伙子', com: markRaw(componentC) }
])

// 选中当前组件
const currentComponent = ref({
  com: componentA
})

// 改变组件
const changeCom = (index) => {
  currentComponent.value.com = tabList.value[index].com
  console.log(currentComponent.value.com, index);
}

</script>


16.

·······························

二、Vue模块配置

01.路由模块

Vue2

步骤1:安装router。

'npm i router@3'

步骤2:配置路由模块。(src => router => index.js)

import VueRouter from 'vue-router'
import Vue from 'vue'
Vue.use(VueRouter)

const router = new VueRouter({
mode: "hash",	// 一共有两种路由模式,分别是hash(默认,原理是a链接锚点跳转)、history(需要后端配置访问规则,原理未知)。
routes: [
    { path: '/login', component: Login },
    { path: '/', 
      component: Layout, 
      redirect: '/home',   // redirect重定向,遇到路径'/',就重定向到'/home'。(避免初次页面打开时空白)
      children: [
        { path: '/home', component: Home },
        { path: '/category', component: Category },
        { path: '/cart', component: Cart },
        { path: '/user', component: User }
      ]
    },
    { path: '/search', component: Search },
    { path: '/searchlist', component: SearchList },
    { path: '/prodetail', component: ProDetail },
    { path: '/pay', component: Pay },
    { path: '/myorder', component: MyOrder }
    { path: '*', component: NoFind}   // *表示任意路径都可以匹配。(当上面都匹配不到时,这里就匹配一个404页面给用户)
  ] 
})
    
export default router 

步骤3:挂载到vue实例。(main.js)

//  main.js
import router from '@/router/index.js'

new vue({
   render: h => h(app),
   router	// 注册一下即可
}).$mount('#app');     

步骤4:配置路由守卫。

情况1:全局路由守卫。

// 1.配置前置路由守卫
// 其中to、from是路由对象,next方法用于拦截,next()表示放行,next(路径)表示拦截到指定路径。
const authUrls = ['/pay', '/myorder']
router.beforeEach((to, from, next) => {
 if (!authUrls.includes(to.path)) {
   next()
   return
 }
 //如果是权限页面,需要判断token,如果存在就放行,如果不存在就跳到登录页面
 const token = store.getters.token
 	token ? next() : next('/login')
 })


// 2.配置后置路由守卫
router.afterEach(() => {
  nProgress.done()
})   

情况2:独享路由守卫。

Vue3

步骤1:安装路由模块。

'npm i vue-router@4'

步骤2:配置路由模块。(src => router => index.js)

import { createRouter, createWebHistory } from 'vue-router'

//  (1) 创建路由实例对象
//      hash模式:createWebHashHistory(基地址)。	
//      history模式:createWebHistory(基地址)。
const router = createRouter({
    // 【import.meta.env.BASE_URL】是vite.config.js中的环境变量。
     history: createWebHistory(import.meta.env.BASE_URL),  
     routes: [
        {
            path: '/',
            redirect: '/home',
        },
        {
            path: '/home',
            component: () => import('@/views/home.vue')
        },
        {
            path: '/about',
            component: () => import('@/views/about.vue')
        }
      ]
})
    
//  (2) 导出路由实例
export default router

步骤3:挂载到vue实例(main.js)。

//  导入路由模块
import router from '@/router/index.js' 

//  注册
app.use(router)

步骤4:配置路由守卫。

情况1:全局路由守卫(默认全部拦截,不过可以用数组来指定哪些不需要拦截的。)

//  1. 配置前置路由守卫
/*  (1)to:到哪里去,完整路由信息对象(路径,参数)      获取路径:to.path
	(2)from:从哪里来,完整路由信息对象(路径,参数)
	(3)next():默认直接放行,放行到to要去的路径。
		 next(路径):进行拦截,拦截到next里面配置的路径。
*/
const authUrls = ['/pay', '/myorder']
router.beforeEach((to, from, next) => {
 if (!authUrls.includes(to.path)) {
   next()
   return
 }
 //  如果是权限页面,需要判断token,如果存在就放行,如果不存在就跳到登录页面
 const token = store.getters.token
 	token ? next() : next('/login')
 })
    
//  2. 后置路由守卫    
router.afterEach(() => {
  nProgress.done()
})    

情况2:独享路由守卫(给单个路由配置路由守卫)


02.axios模块

Vue2

步骤1:安装axios。

'npm i axios'

步骤2:配置axios模块。(src => utils => request.js)

//  1.创建axios实例
const instance = axios.create({
    baseURL: 'https://study.duyiedu.com/',
    timeout: 2000
})

//  2.添加请求拦截器
instance.interceptors.request.use(function (config) {
  const token = localStorage.getItem('token');
  if(token){
    config.headers.authorization = token;
  }
  return config; // 返回处理后的配置
})

//  3.添加响应拦截器
instance.interceptors.response.use(function (resp) {
  // 2xx 范围内的状态码都会触发该函数。
  return resp.data.data; 
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  alert(error.message); // 弹出错误消息
})

export default instance

步骤3:配置环境变量。

//  1. 在哪配置环境变量
在项目根目录下,配置两个文件(这两个文件与src目录同级):
(1) .env.development // 开发环境会被运行
(2) .env.production  // 生产环境会被运行


//  2.怎么定义环境变量?
必须以'VUE_APP_'开头,来声明环境变量。 例如:声明一个baseURL: 'VUE_APP_BASE_URL = https://www.baidu.com'


//  3.怎么读取环境变量?
(1) 访问环境变量:'process.env.VUE_APP_变量名'。
(2) 判断是否开发环境:'process.env.NODE_ENV'。// production、development

步骤4:解决跨域(开发阶段)。

// 在vue.config.js中配置,因为代理只在开发阶段有效!

module.exports = {
  // 配置代理服务器
  devServer: {
    proxy: {
      '/commercial_api': {
        target: 'https://www.zhihu.com', // 要转发的目的服务器
        changeOrigin: true // 是否改变源地址
      }
    }
  }
}

// 二次封装的基地址写环境变量。
const instance = axios.create({
    baseURL: process.env.NODE_ENV === 'production' ? process.env.VUE_APP_BASE_URL : '',
    timeout: 2000
})

步骤5:封装所有的api。

import request from '@/utils/request.js'

// get请求的写法
export const getShopListApi = (data, config={}) => {
    request.get('https://www.baidu.com', {
         params: data,
         ...config
    })
}

// post请求的写法
export const changeListApi = (data, config={}) => {
    request.post('https://www.baidu.com', data, config)
}

Vue3

 步骤1:安装axios。

'npm i axios'

步骤2:配置axios模块。(src => utils => request.js)

import axios from 'axios'

//  1.创建axios实例
const instance = axios.create({
    baseURL: '',
    timeout: 10000
})
 
 
//  2.添加请求拦截器
instance.interceptors.request.use(function (config) {
  const token = localStorage.getItem('token');
  if(token){
    config.headers.authorization = token;
  }
  return config; // 返回处理后的配置
})
 
//  3.添加响应拦截器
instance.interceptors.response.use(function (resp) {
  return resp.data; 
}, function (error) {
  alert(error.message); // 弹出错误消息
})
 

// 4.统一get、post的传参方式
const http = {
  get(url:any, params:any, config:any) {
    return new Promise((resolve, reject) => {
      instance.get(url, {params, ...config}).then((res) => {
        resolve(res)
      }).catch((error) => {
        reject(error)
      })
    })
  },

  post(url:any, params:any, config:any) {
    return new Promise((resolve, reject) => {
      instance.post(url, params, config).then((res) => {
        resolve(res)
      }).catch((error) => {
        reject(error)
      })
    })
  }
}

// 导出
export default http

 步骤3:配置环境变量。

//  1. 在哪配置环境变量
在项目根目录下,配置两个文件(这两个文件与src目录同级):
(1) .env.development // 开发环境会被运行
(2) .env.production  // 生产环境会被运行


//  2.怎么定义环境变量?
必须以'VUE_APP_'开头,来声明环境变量。 例如:声明一个baseURL: 'VUE_APP_BASE_URL = https://www.baidu.com'


//  3.怎么读取环境变量?
(1) 访问环境变量:'process.env.VUE_APP_变量名'。
(2) 判断是否开发环境:'process.env.NODE_ENV'。// production、development

步骤4:解决跨域(开发阶段)。

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),

  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
      '@stores': fileURLToPath(new URL('./src/stores', import.meta.url)),
      '@views': fileURLToPath(new URL('./src/views', import.meta.url))
    }
  },
   // 代理
   server: {
    proxy: {
      '/api': {
        target: 'http://39.100.86.70:8088',
        rewrite: path => path.replace(/^\/api/, '')   
      }
    }
  },
})

步骤5:封装所有的api。

import request from '@/utils/request.js'

// get请求的写法
export const getShopListApi = (data, config={}) => {
    request.get('https://www.baidu.com', {
         params: data,
         ...config
    })
}

// post请求的写法
export const changeListApi = (data, config={}) => {
    request.post('https://www.baidu.com', data, config)
}


03.vuex模块

步骤1:安装vuex。

'npm i vuex@3'

步骤2:配置vuex核心模块。(src => store => index.js)

import Vue from 'vue'
import Vuex from 'vuex'
// (1)将Vuex在Vue中进行注册。
Vue.use(Vuex)
    
// (2)创建vuex的store仓库,store包含了state、actions、mutations、modules、getters五个部分。    
const store = new Vuex.Store({
// (2.1)定义state。    
  state: {
    username: '思密达',
    score: 100,
    age: 18
  },
// (3)定义actions。
//  actions中的方法:第一个参数是上下文参数context,一般表示store仓库,后面的才是形参。
//  actions是不能直接操作state的,它只能context.commit('方法名', 参数)提交给mutaions处理。
  actions: {
    changeUsername (context, n) {
      setTimeout(() => {
        context.commit('changeName', n)
      }, 2000)
    }
  },
// (4)定义mutations。
//  mutations中的方法,第一个参数是state,后面的才是形参。
  mutations: {
    addOne (state, n) {
      state.score += n
    },
    changeName (state, n) {
      state.username = n
    }
  },
// (5)定义modules。
  modules: {
    user
  }
})
    
export default store

步骤3:挂载到vue实例。(main.js)

// main.js文件  
import store from '@/store/index.js'
new Vue({
  render: h => h(App),
  store
}).$mount('#app') 

步骤4:配置分模块。(可选)

前提:需要开启namespace命名空间

export default {
  // (1)开启命名空间。
  namespaced: true,
  // (2)分模块数据。
  state: {
    token: 'ewsmdkawln',
    userInfo: {},
    array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  },
  mutations: {
    setToken (state, token) {
      state.token = token
    },
    changeArray (state) {
      state.array = ['金', '刚', '经']
    }

  },
  actions: {
    setToken (context, token) {
      context.commit('setToken', token)
    }
  },
  getters: {
    filterArray (state) {
      return state.array.filter(item => item > 5)
    }
  }
}

步骤5:调用主模块的数据、方法。

//  1.获取state
(1)手写:
	1)模板中:{{ $store.state.xxx }}	
	2)组件JS中:this.$store.state.xxx	
	3)JS文件中:store.state.xxx	
(2)映射:
      computed: {
      /* 原理:本质还是计算属性来的。
        computed: {
            count() {
                return this.$store.state.count
            }
        } */
        ...mapState(['username', 'score'])
      }


//  2.调用mutations(只能传递一个参数,想传多个可以用对象)
(1)手写:this.$store.commit('add', n)
(2)映射:
	methods: {
        /* 原理:本质还是普通方法,此时要确保触发的函数名 === mutaitons中的函数名
        addOne (n) {
            this.$store.commit('add', n)
        } */
        ...mapMutations(['addOne']),  
        }


//  3.调用actions(只能传递一个参数,想传多个可以用对象)
(1)手写:this.$store.dispatch('changeUsername', n)
(2)映射:
      methods: {
        /* 原理:本质还是普通方法,此时要确保触发的函数名 == actions中的函数名
        addOne (n) {
            this.$store.dispatch('changeUsername', n)
        } */
        ...mapActions(['changeUsername'])
      }

步骤6:调用分模块的数据、方法。

//  1.获取state
(1)手写:
	1)模板中:{{ $store.state.模块名称.xxx }}	
	2)组件中:this.$store.state.模块名称.xxx	
	3)JS模块中:store.state.模块名称.xxx	
(2)映射:
      computed: {
      /* 原理:本质还是计算属性来的。
        computed: {
            xxx() {
                return this.$store.state.模块名称.xxx
            }
        } */
        ...mapState('模块名称', ['xxx1', 'xxx2'])
      }


//  2.调用mutations(只能传递一个参数)
(1)手写:this.$store.commit('add', n)
(2)映射:
	methods: {
        /* 原理:本质还是普通方法,此时要确保触发的函数名 == mutaitons中的函数名
        addOne (n) {
            this.$store.commit('模块名称/add', n)
        } */
        ...mapMutations('模块名称', ['addOne']),  
        }


//  3.调用actions(只能传递一个参数)
(1)手写:this.$store.dispatch('changeUsername', n)
(2)映射:
      methods: {
        /* 原理:本质还是普通方法,此时要确保触发的函数名 == actions中的函数名
        addOne (n) {
            this.$store.dispatch('模块名称/changeUsername', n)
        } */
        ...mapActions('模块名称', ['changeUsername'])
      }


//  4.使用getters
(1)手写:{{ $store.getters['user/filterArray'] }} // 逆天设计
(2)映射:
      computed: {
      /* 原理:本质还是计算属性来的。
        computed: {
            filterArray() {
                return this.$store.state.模块名称.filterArray
            }
        } */
        ...mapGetters('模块名称', ['filterArray'])
      }	

步骤7:持久化数据。

安装插件:npm i --save vuex-persistedstate

import Vuex from 'vuex'
import Vue from 'vue'

// 导入持久化函数
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)

const store = new Vuex.Store({
  state () {
    return {
      num: 10
    }
  },
  mutations: {
    changeNum (state, value) {
      state.num += 1
    }
  },
  // 持久化配置
  plugins: [
    createPersistedState({
      key: 'simida',
      // render中写要存哪些东西,如果都要存,那么render函数可以不写。
      render (state) {
        return {
          ...state
        }
      }
    })
  ]
})

export default store


04.pinia模块

了解pinia的优势。

//  Pinia的优势

1. 去掉了mutaitons,只需写在actions即可。
   去掉了modules,每一个store都是一个独立的模块。 
   
2. 提供组合式API写法:普通数据就是state,普通函数就是actions,computed就是getters,确实有点牛逼。

3. 可以配合TypeScript,提供可靠的类型推荐。

4. pinia可以直接修改state,可以不通过mutations来修改。

步骤1:安装pinia。

'npm i pinia'

步骤2:创建pinia实例。(src => store => index.js)

//  导入createPinia函数,创建出pinia实例
import { createPinia } from 'pinia';
//  导入pinia持久化插件
import persist from 'pinia-plugin-persistedstate'

const pinia = createPinia() 
pinia.use(persist)
export default pinia

步骤3:挂载到vue实例。

import App from './App.vue'
import { createApp } from 'vue'
//  导入pinia实例
import pinia from '@/store/index.js'

const app = createApp(App)
//  把pinia实例挂载到vue
app.use(pinia)

步骤4:配置pinia模块。(src => store => user.js)

import { defineStore } from 'pinia';
import persist from 'pinia-plugin-persistedstate' // pinia持久化插件。

//  1. 组合式API写法。  优点:普通数据就是state,普通函数就是actions,computed就是getters,确实有点牛逼。
export const useUserStore = defineStore('user', () => {
    // (1) 维护数据count
    let count = ref(0)
    const setCount = (value) => count.value += value
    const getCount = () => count.value
    const doubleCount = computed(() => count.value * 2)

    // (2) 维护数据token
    const token = ref('123abc456bdc')
    const setToken = (value) => token.value += value
    const getToken = () => token.value

    // (3) 记得最后要导出这些state、actions、getters。
    return { 
        count, doubleCount, setCount, getCount,
        token, setToken, getToken 
    }
},
{
    // 2. 开启pinia持久化插件
    // 写法1:使用默认配置。
	persist: true	
    
    // 写法2:自定义配置。
    persist: {
    	// 默认是localStorage
    	storage: sessionStorage,	
    	// 默认持久化全部,可指定好哪些state、getters、actions要被持久化
    	paths: ['count', 'token', 'setToken', 'getCount'],	
         // 默认是以仓库的唯一标识'user'作为键名,值就是全部被持久化的state、getters、actions。                                
         key: 'hm-user'	
    }
})

步骤5:pinia持久化插件安装。

'npm i pinia-plugin-persistedstate --save-dev'

步骤6:使用pinia。

<script setup>
//  导入store的counter模块
import { useUserStore } from '@/store/counter'

//  (1) 不要对state、getters进行解构,会丢失响应式。
const userStore = useUserStore()	
//  (2) 如果真的需要解构,需要特殊处理后才能恢复响应式:需要使用storeToRefs()函数包一下。 
const { count } = storeToRefs(userStore) 

//  (3) 如果解构store中的函数,则不用任何处理,因为函数不需要响应式。
const { addCount, minusCount } = userStore	  
</script> 
    
<template>
 <div>
   <div>我是爷爷</div>
   <span>{{ userStore.count }} - {{ userStore.msg }} - {{ userStore.double }}</span>
   <button @click="userStore.addCount">+</button>
 </div>
</template>    


05.组件库

1.element-plus

 官方文档:一个 Vue 3 UI 框架 | Element Plus

步骤1:安装组件库

'npm i element-plus --save'

步骤2:导入组件库。

全部导入:

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

// 导入Element-Plus的组件和样式
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const app = createApp(App)

app.use(ElementPlus).mount('#app')

按需导入:

1)安装自动引入工具

'npm install -D unplugin-vue-components unplugin-auto-import'

2)去vite.config.js中配置自动引入工具。

// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

3)可能并没有完全搞定,可能还需要在main.js导入组件库的css样式。

import 'element-plus/dist/index.css'


06.组件封装

1.消息提示组件

步骤1:目录结构。

步骤2:index.js。

import { createApp, ref, watch } from 'vue'
import ToastComponent from './index.vue'

// 消息队列
const messageArr = ref([])

// 弹出消息提示框
const showMessage = (options) => {

    // 动态生成一个消息提示框
    const app = createApp(ToastComponent, options)
    const EL = document.createDocumentFragment() // 虚拟节点
    const VM = app.mount(EL)   // 真实的节点

    // 从组件接收一些东西进来用
    const { setIsShowMessage, setNewTop, top, height } = VM

    // 然后插入到body后面
    document.body.appendChild(EL)

    // 加入到消息队列。
    messageArr.value.push(VM)

    // 监视messageArr,刚调用方法时要执行一次,为了为弹框设置一次高度。
    watch(messageArr, () => {
        // 动态计算当前消息提示框的位置。当前VM的位置= (当前位置的index + 1) * top + 当前位置index * height
        const currentIndex = messageArr.value.findIndex(item => item === VM)
        const newTop = (currentIndex + 1) * top + currentIndex * height
        setNewTop(newTop)
    }, { immediate: true })

    // 几秒后,消息提示框消失、从消息队列中移除、删除DOM节点
    setTimeout(() => {
        setIsShowMessage(false)
        messageArr.value = messageArr.value.filter(item => item !== VM)
        VM.$el.addEventListener('transitionend', () => {
            app.unmount()
        })
    }, options.duration);

}


export default showMessage

步骤3:index.vue。


<script setup>
// 导入
import { ref, toRefs, onBeforeMount, onMounted, computed } from 'vue'

// 外界拿数据
const props = defineProps({
  type: {
    type: String,
    default: 'info'
  },
  content: {
    type: String,
    default: '这是一个消息提示内容'
  }
})

// 定义映射数据
const types = {
  success: 'success',
  error: 'error',
  warn: 'warn',
  info: 'info'
}
const resultType = computed(() => {
  console.log(types[props.type]);
  return types[props.type]
})

// 1.控制显示(默认不显示)
const isShowMessage = ref(false)
let timer = null
const setIsShowMessage = (flag) => {
  isShowMessage.value = flag
}

// 2.设置默认值
let top = ref(20) // 默认高度
const height = 40 // 弹框原始高度
// 设置top
const setNewTop = (newTop) => {
  top.value = newTop
}

// 导出
defineExpose({
  setIsShowMessage,
  setNewTop,
  height,
  top: 20
})
</script>


<template>
  <Transition name="slide-fade">
    <div v-show="isShowMessage" class='message-container' :class="[resultType]"
      :style="{top: top + 'px'}">
      {{ content }}
    </div>
  </Transition>
</template>


<style lang='scss' scoped>
.message-container {
  position: fixed;
  z-index: 9999;
  left: 50%;
  transform: translate(-50%);
  width: 360px;
  height: 40px;
  background-color: #edf2fc;
  border-radius: 15px;
  text-align: center;
  line-height: 40px;
  color: #91949a;
  transition: all 0.3s;

  // 不同样式
  &.success {
    color: #67c23a;
    background-color: #f0f9eb;
  }
  &.error {
    color: #f56c6c;
    background-color: #fef3f3;
  }
  &.warn {
    color: #e6a9a2;
    background-color: #fdf6ec;
  }
  &.info {
    color: #91949a;
    background-color: #edf2fc;
  }
}

.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(-50%) translateY(-20px);
  opacity: 0;
}
</style>

步骤4:使用方法如下。

// 步骤1:导入核心函数showMessage
import showMessage from '@/components/Message/index.vue'

// 步骤2:绑定好点击事件
const show = () => {
    showMessage({
        type: 'success', // 类型可以有:success、error、info、warn
        content: '这是一条提示消息',
        duration: 3000
    })
}


2.滑动验证组件

步骤1:目录结构如下。

步骤2:index.vue。

<template>
  <div class="slider-container" :style="{ width: w + 'px' }">
    <div class="slider-canvas" :style="{ width: w + 'px', height: h + 'px' }">
      <!-- 大图 -->
      <canvas :width="w" :height="h" ref="canvas" />
      <!-- 小图 -->
      <canvas class="slider-block" :width="l" :height="smallHeight" ref="block" />
      <!-- 结果遮罩层 -->
      <div
        :class="`result-mask ${resultMask.class}`"
        :style="{ height: `${resultMask.height}px` }"
      />
      <div class="btn" @click="refresh"></div>
    </div>
    <!-- 拖动的滑块内容 -->
    <div class="slider-square">
      <div class="box">
        <div
          class="slider-square-icon"
          @mousedown="sliderDown"
          :style="{ left: slideInfo.sliderLeft }"
        ></div>
        <span>{{ slideInfo.sliderText }}</span>
      </div>
    </div>
  </div>
</template>
  
<script>
import { ref, reactive, onMounted, watch, onDeactivated } from 'vue'

export default {
  props: {
    w: {
      // 大图的宽度
      type: Number,
      default: 320,
    },
    h: {
      // 大图的高度
      type: Number,
      default: 160,
    },
    l: {
      // 小图的宽度/高度(正方形的宽度,实际宽度需要加上圆弧的宽度)
      type: Number,
      default: 40,
    },
    sliderText: {
      // 滑块文字
      type: String,
      default: "拖动滑块向右>>",
    },
    bigImg: {
      // 大图源
      type: String,
      default: "",
    },
    smImg: {
      // 小图图源
      type: String,
      default: "",
    },
    r: {
      // 小图的拼图半径
      type: Number,
      default: 0,
    },
    accuracy: {
      // 拖动误差范围
      type: Number,
      default: 5,
    },
    posY: {
      // Y坐标
      type: Number,
      default: 0,
    },
    posX: {
      // X坐标
      type: Number,
      default: 0,
    },
    smallHeight: {
      // 小图的宽度/高度(正方形的宽度,实际宽度需要加上圆弧的宽度)
      type: Number,
      default: 40,
    }
  },
  setup (props, { emit }) {
    const canvas = ref(null); // 大图ref
    const canvasCtx = ref(null); // 大图canvas绘制容器
    const block = ref(null); // 小图ref
    const blockCtx = ref(null); // 小图canvas绘制容器
    const { w, h, l, r, sliderText, posY, posX, accuracy, smallHeight } = props;
    const slideInfo = reactive({
      sliderLeft: 0, // 可拖动滑块的left
      block_x: 0, // 拼图最终要滑动的位置x
      block_y: 0, // 拼图位置的y
      sw: l, // 拼图的实际宽度 小图宽度 + 2*半径 + 3(border)
      sliderText, // 滑块提示文字
      bigImg: "",
      smImg: "",
    });
    const origin = reactive({
      // 鼠标按下初始位置
      orginX: 0,
      originY: 0,
    });
    const resultMask = reactive({
      // 结果提示背景色
      class: "",
      height: 0,
    });

    // 初始化canvas dom
    const initDom = () => {
      canvasCtx.value = canvas.value.getContext("2d");
      blockCtx.value = block.value.getContext("2d");
    };
    // 初始化图片
    const initImg = () => {
      const bigImg = createImg(() => {
        canvasCtx.value.drawImage(bigImg, 0, 0, w, h);
      }, props.bigImg);
      const smImg = createImg(() => {
        blockCtx.value.drawImage(smImg, 0, 0, l, smallHeight);
      }, props.smImg);
    };

    const createImg = (onload, url) => {
      const img = document.createElement("img");
      img.crossOrigin = "Anonymous";
      img.onload = onload;
      img.onerror = () => {
        img.src = url;
      };
      img.src = url;
      return img;
    };

    onMounted(() => {
      initDom();
      initImg();
      bindEvents();
    });

    const slider = ref(false);

    // 鼠标按下
    const sliderDown = (e) => {
      slider.value = true;
      slideInfo.sliderText = "";
      origin.orginX = e.pageX;
      origin.originY = e.pageY;
    };

    // 鼠标移动
    const sliderMove = (e) => {
      if (slider.value) {
        const moveX = e.pageX - origin.orginX;
        const moveY = e.pageY - origin.originY;
        if (moveX < 0 || moveX + l > w) return false;
        slideInfo.sliderLeft = moveX + "px";
        let blockLeft = ((w - slideInfo.sw) / (w - 40)) * moveX;
        block.value.style.left = blockLeft + "px";
      }
    };

    // 鼠标松开
    const sliderUp = (e) => {
      if (!slider.value) return false;
      slider.value = false;
      resultMask.height = h / 2;
      const left = parseInt(block.value.style.left);
      emit("sliderJudge", left);
    };

    // 将鼠标移动,鼠标松开事件绑定在dom上,如果鼠标移出滑块范围,还可以继续移动
    const bindEvents = () => {
      document.addEventListener("mousemove", sliderMove);
      document.addEventListener("mouseup", sliderUp);
    };

    // 调用接口获取图片,异步,监听图片地址变动
    watch(
      () => props.bigImg,
      (newVal) => {
        reset(); // 重置图片状态
        initImg(); // 重新加载图片
      }
    );

    //刷新图片
    const refresh = () => {
      emit("refresh");
    }

    // 重置/刷新图片
    const reset = () => {
      canvasCtx.value.clearRect(0, 0, w, h);
      blockCtx.value.clearRect(0, 0, l, smallHeight);
      slideInfo.sliderText = sliderText;
      resultMask.height = 0;
      resultMask.class = "";
      slideInfo.sliderLeft = 0;
      block.value.style.left = 0;
    };

    // 组建销毁时,移除鼠标事件
    onDeactivated(() => {
      document.removeEventListener("mousemove", sliderMove);
      document.removeEventListener("mouseup", sliderUp);
    });

    return {
      canvas,
      canvasCtx,
      blockCtx,
      block,
      slideInfo,
      resultMask,
      refresh,
      sliderDown,
      sliderMove,
      sliderUp,
      reset,
      initDom,
      initImg
    };
  }
}
</script>
  
<style lang="scss" scoped>
.slider-container {
  .slider-canvas {
    position: relative;

    .slider-block {
      position: absolute;
      top: 0;
      left: 0;
    }

    .btn {
      position: absolute;
      right: 0;
      top: 0;
      width: 34px;
      height: 34px;
      cursor: pointer;
      background-image: url('../../assets/images/light.png');
      background-position: 34px 35px;
      z-index: 999;
    }

    @keyframes result-show {
      0% {
        opacity: 0;
      }

      100% {
        opacity: 0.7;
      }
    }

    .result-mask {
      position: absolute;
      left: 0;
      bottom: 0;
      width: 100%;
      opacity: 0.7;

      &.success {
        background-color: #52ccba;
        animation: result-show 1s;
      }

      &.fail {
        background-color: #f57a7a;
        animation: result-show 1s;
      }
    }
  }
}

.slider-square {
  background-color: #52a352;
  height: 40px;
  text-align: center;
  line-height: 40px;
  border: 1px solid #ddd;
  position: relative;
  margin-top: 12px;

  .slider-square-icon {
    position: absolute;
    top: 0;
    left: 0;
    height: 39px;
    width: 39px;
    background-color: #ee9016;
    box-shadow: blanchedalmond 0px 0px 1px 2px;
    cursor: pointer;

    &:hover {
      background-color: rgb(224, 147, 30);
    }
  }
  span {
    color: #fff;
  }
}
</style>
  

步骤3:组件使用方法。

// 步骤1:导入组件
import SlideSplit from '@/components/SlideSplit/index.vue'

// 步骤2:template声明组件
<template>
  <SlideSplit v-if="isShowSlideSplit" :w="slideInfo.bigWidth" :h="slideInfo.bigHeight"
    :l="slideInfo.smallWidth" :bigImg="slideInfo.bigImageBase64" :smImg="slideInfo.smallImageBase64"
    :r="slideInfo.smallWidth" :accuracy="slideInfo.accuracy" :smallHeight="slideInfo.smallHeight"
    @refresh="handlerRequest" @sliderJudge="onJudge"></SlideSplit>
</template>

// 步骤3:js中准备数据
const slideInfo = ref({
  bigWidth: 0,
  bigHeight: 0,
  bigImageBase64: '',
  smallImageBase64: '',
  smallWidth: 0,
  smallHeight: 0,
  accuracy: 8,
  bigImage: null, // 为了ts类型不报错,下面属性均无效。
  smallImage: null,
  posX: null,
  posY: null
})


3.虚拟滚动组件(等高)

方法1:手写。

组件源码:

// 组件源码virtualScroll.vue(每一项,是插槽+作用域插槽定义的,将来插入自定义组件即可。)

<script setup>
// 导入
import { ref, toRefs, onBeforeMount, onMounted, computed } from 'vue'

//  1.外部传入的数据
const props = defineProps({
  // 总数据
  items: {
    type: Array,
    default: () => []
  },
  // 每一项数据的高度
  itemHeight: {
    type: Number,
    default: 60
  },
  // items数组中,哪一个属性作为v-for中的key值
  keyField: {
    type: String,
    default: 'id'
  }
})


//  2.长列表的高度
const totalHeight = computed(() => {
  return props.items.length * props.itemHeight
})


//  3.渲染池:根据原始数据,构造出要渲染的10条数据
const pool = ref([])  // 要渲染的数据
const containerDOM = ref(null) // DOM节点
// 前面多显示10个,用缓存换取性能
let preBuff = 5
let nextBuff = 5
const setPool = () => {
  // 获取container滚动的高度
  const scrollTop = containerDOM.value.scrollTop
  // 获取容器的高度
  const containerHeight = containerDOM.value.clientHeight
  // 计算开始、结束的位置
  let startIndex = Math.floor(scrollTop / props.itemHeight)
  let endIndex = Math.ceil((scrollTop + containerHeight) / props.itemHeight)
  // 前面多显示10个,用缓存换取性能
  startIndex -= preBuff
  if (startIndex < 0) startIndex = 0
  endIndex += nextBuff
  // 计算起始位置的偏移量
  let startPos = startIndex * props.itemHeight
  pool.value = props.items.slice(startIndex, endIndex).map((item, i) => (
    {
      item: item,
      position: startPos + i * props.itemHeight
    }
  ))
}
onMounted(() => {
  setPool()
})
</script>


<template>
  <div class='virtualScroll-container' ref="containerDOM" @scroll="setPool">
    <div class="virtualScroll-wrapper" :style="{height: totalHeight+'px'}">
      <div class="virtualScroll-item"
        :style="{height: props.itemHeight+'px', transform: `translateY(${poolItem.position}px)`}"
        v-for="poolItem in pool" :key="poolItem.item[keyField]">
        <slot :item="poolItem.item"></slot>
      </div>
    </div>
  </div>

</template>


<style lang='scss' scoped>
// 最外层容器
.virtualScroll-container {
  overflow: auto;
  width: 100%;
  height: 100%;
  scrollbar-width: none; // 在火狐中,隐藏滚动条样式
  -ms-overflow-style: none; // 在ie10+、Edge中隐藏滚动条样式
  // 在webkit内核,隐藏滚动条样式
  &::-webkit-scrollbar {
    display: none;
  }

  // 长列表容器
  .virtualScroll-wrapper {
    position: relative;

    // 列表的每一项
    .virtualScroll-item {
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      border-bottom: 1px solid black;
    }
  }
}
</style>

测试组件:

// 测试组件test.vue

<script setup>
import { ref, toRefs, onBeforeMount, onMounted } from 'vue'
// 导入组件
import virtualScroll from './index.vue'
import listItem from './listItem.vue'

// 1.准备一万条模拟数据
const items = []
for (let i = 0; i < 10000; i++) {
  items.push({
    id: i,
    count: `我是第${i}条数据`
  })
}
// 2.准备数据的key值
const keyField = 'id'
</script>


<template>
  <div class='test-container'>
    <virtualScroll :items="items" :itemHeight="100" :keyField="keyField" v-slot="{item}">
      <listItem :item="item"></listItem>
    </virtualScroll>
  </div>
</template>


<style lang='scss' scoped>
// 最外层容器
.test-container {
  width: 500px;
  height: 500px;
  border: 1px solid #000;
  margin: 100px auto;
}
</style>

方法2:使用插件。

参考github地址:vue-virtual-scroller/packages/vue-virtual-scroller/README.md at master · Akryum/vue-virtual-scroller · GitHub

// 安装
npm install --save vue-virtual-scroller@next

// 导入(记得要导入css样式)
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

// 组件使用如下:
<template>
  <RecycleScroller
    class="scroller"
    :items="list"
    :item-size="32"
    key-field="id"
    v-slot="{ item }"
  >
    <div class="user">
      {{ item.name }}
    </div>
  </RecycleScroller>
</template>

<script>
export default {
  props: {
    list: Array,
  },
}
</script>

<style scoped>
.scroller {
  height: 100%;
}

.user {
  height: 32%;
  padding: 0 12px;
  display: flex;
  align-items: center;
}
</style>


·······························

三、Vue项目起步

01.搭建note环境

 步骤1:安装nvm插件。(安装前需要卸载原来的note.js)

网盘地址:'https://pan.baidu.com/s/1uoxlk8CVNHV2KTCwIGbQMQ?pwd=yi5m'
提取码:'yi5m'

步骤2:修改nvm源。(打开nvm配置文件,配置下面两条)

'nvm npm_mirror https://npmmirror.com/mirrors/npm/'
'nvm node_mirror https://npmmirror.com/mirrors/node/'

步骤3:配置npm源。

// 将npm源设置为淘宝镜像
'npm config set registry https://registry.npmmirror.com'

// 获取当前npm源
'npm config get registry'

步骤4:nvm的命令大全。

// 查看当前安装和使用的 node 版本
nvm list

// 安装某个 node 版本
nvm install v版本号

// 切换 node 版本
nvm use v版本号

// 设置默认版本
nvm alias v12.22.12


02.创建vue项目

Vue2

步骤1:安装vue-cli脚手架。

// 安装
'npm i -g @vue/cli@5.0.8'
    
// 检测
'vue --version'

步骤2:创建vue2项目。

// 创建项目
'vue create 项目名称'
    
// 启动项目
'yarn serve'  或者  'npm run serve'  

Vue3

步骤1:安装vue-cli脚手架。

// 安装
'npm i -g @vue/cli@5.0.8'
    
// 检测
'vue --version'

步骤2:创建vue3项目。

// 步骤1:确保电脑安装了node.js
`node --version`    // 查看node.js的版本

// 步骤2:创建vue3项目
`npm init vue@latest`  // 1.初始化vue3项目结构
`项目名称`             // 2.紧接着输入项目的名称
`npm install`         // 3.最后根据提示执行命令

// 步骤3:启动项目
`npm run dev`   


03.配置源码路径(Vue3)

步骤:在vite.config.js文件中,配置alias选项 

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
const path = require('path');

export default defineConfig({
  plugins: [
    vue()
  ],
  resolve: {
    // 配置路径别名
    alias: {
      '@': path.resolve(__dirname, './src'),
    }
  }
})


04.解决require is not defined

在vite环境下,就可能出现require is not defined的报错

 步骤1:安装插件

yarn add -D vite-plugin-require-transform
or 
npm i vite-plugin-require-transform --save-dev

步骤2:去vite.config.js中配置,配置plugins

import { defineConfig } from 'vite' 
import requireTransform from 'vite-plugin-require-transform'; 

plugins: [
    requireTransform({
      fileRegex: /.js$|.vue$/
    })
 ],

步骤3:如果还是解决不了,就去vite.config.js中修改如下配置


// 不用require('path')引入,而是使用import导入
import path from 'path'


05.配置环境变量

//  1. 在哪配置环境变量
在项目根目录下,配置两个文件(这两个文件与src目录同级):
(1) .env.development // 开发环境会被运行
(2) .env.production  // 生产环境会被运行


//  2.怎么定义环境变量?
必须以'VUE_APP_'开头,来声明环境变量。 
例如:声明一个baseURL: 'VUE_APP_BASE_URL = https://www.baidu.com'


//  3.怎么读取环境变量?
(1) 访问环境变量:'process.env.VUE_APP_变量名'。
(2) 判断是否开发环境:'process.env.NODE_ENV'。// production、development


06.解决跨域

方法1:配置代理解决跨域(仅开发阶段)。

// 代理解决跨域原理
前端请求到node代理的服务器,通过node代理服务器来向后端发起请求,
就可以成功的发起请求并获取数据。因为这是服务器与服务器之间的通信,不存在同源策略的影响,
同源策略只在浏览器中起作用。

import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 导入自动import插件
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  // 插件
  plugins: [
    vue(),
    AutoImport({
      imports: ['vue', 'vue-router']
    })
  ],
  // 代理
  server: {
    proxy: {
      '/api': 'http://testapi.xuexiluxian.cn'
    }
  },
  // 配置@符号
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

方法2:解决生产阶段跨域问题(需要后端解决好跨域问题)

// 根据环境不同,从而请求不同的基地址。
(1)获取环境变量:process.env.VUE_APP_变量名
(2)获取环境:process.env.NODE_ENV

// 代码演示
const instance = axios.create({
    baseURL: process.env.NODE_ENV === 'production' ? process.env.VUE_APP_BASE_URL : '',
    timeout: 2000
})


07.按需加载路由

不要一次性加载全部路由,用到才加载,方法如下:



 

  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值