日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

前端自学Vue笔记干货(第一版,持续更新中~~~)

發布時間:2024/3/13 vue 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 前端自学Vue笔记干货(第一版,持续更新中~~~) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

學習筆記

Vue筆記

nprogress使用

npm i nprogress -S 基本上都是在對axios進行二次封裝、前置守衛路由或者封裝成工具函數的.js文件中用到 import nprogress from 'nprogress' //引入進度條的樣式 import 'nprogress/nprogress.css' //start:進度條開始 done:進度條結束//進度條開始動nprogress.start() //進度條結束nprogress.done() 法一: // 顯示全屏loading export function showFullLoading(){nprogress.start() }// 隱藏全屏loading export function hideFullLoading(){nprogress.done() } 法二: import router, { asyncRoutes } from './router' import NProgress from 'nprogress' // progress bar import 'nprogress/nprogress.css' // progress bar style import store from '@/store' import getPageTitle from '@/utils/get-page-title'NProgress.configure({ showSpinner: false }) // NProgress Configurationconst whiteList = ['/login', '/404'] // 白名單: 無需登錄, 可以跳轉查看的路由地址(在路由表里)// 問題: 為何動態路由添加后, 在動態路由地址上刷新會404? // 前提1: 刷新時, 所有代碼重新執行, 回歸初始化 // 前提2: 刷新時, 路由會從/ 跳轉到瀏覽器地址欄所在的路由地址 (走一次路由守衛代碼) // 動態的還未添加, 所以404了// 問題: 右上角退出登錄+重新登錄, 進入到首頁時, 網頁刷新不? (不刷新) // 網頁本身是不刷新的, 完全依賴路由業務場景的切換 (單頁面應用好處: 用戶的體驗更好, 切換業務場景更快) // 內存里路由表, 之前添加的篩選后路由規則對象還在不? (在) // 問題2: 為何重新登錄, 路由定義重復了? // 退出登錄的時候, 把token和用戶信息清除了 // 登錄的時候, 先獲取到token保存到vuex和本地, 然后才是跳轉路由, 才執行路由守衛(所以判斷token有值) // 但是用戶信息沒有, 重新請求, 再添加一遍篩選后的路由對象, 所以導致了路由重復// 解決: 退出登錄的時候, 讓路由也回歸初始化// 問題: 什么是路由(導航)守衛? // 答案: 當路由發生跳轉的時候, 會觸發一個鉤子"函數", 在函數中可以通過跳轉或取消或強制切換跳轉地址來守衛導航 // 路由守衛里必須要有一個next()調用, 出口, 讓路由頁面跳轉 router.beforeEach(async(to, from, next) => {NProgress.start()const token = store.getters.token// 登錄了->不能去登錄頁// 非登錄->只能去登錄頁if (token) { // 登陸了if (to.path === '/login') { // 去登錄頁// 中斷要跳轉/login這次導航, 重新跳轉到/(首頁)next('/')NProgress.done()} else { // 去別的頁面next() // 如果手動讓cookie里token改錯誤, 刷新以后, vuex才會從本地取出錯誤token// 刷新時, 路由守衛會從 / 跳轉到地址欄里路由地址, 所以先讓頁面跳轉進去// 執行下面請求會401, 被動退出時, 才能拿到跳轉后的路由地址(未遂地址給登錄頁面, 否則next在下面, 未遂地址一直是/)if (!store.getters.name) {await store.dispatch('user/getUserInfoActions')// const menus = await store.dispatch('user/getUserInfoActions')// 用menus權限點英文字符串, 和路由規則對象name匹配// 把所有準備好的8個路由規則對象, 取出, 看看名字和menus里是否匹配, 匹配就證明// 此登錄的用戶有這個頁面的訪問權限, 讓filter收集此路由規則對象到新數組里// const filterList = asyncRoutes.filter(routeObj => {// const routeName = routeObj.children[0].name.toLowerCase()// return menus.includes(routeName)// })// filterList.push({ path: '*', redirect: '/404', hidden: true })// 始終都動態添加先8個路由規則對象// 知識點: 路由切換匹配的路由規則對象數組存在于內存中的// new Router時, 有一些初始的路由規則對象// addRoutes, 會給路由表, 再額外的增加一個規則對象// 現象: 路由規則對象添加成功, 但是左側的導航不見了const filterList = asyncRoutesrouter.addRoutes(filterList)// 給vuex也同步一份store.commit('permission/setRoutes', filterList)// 路由再跳轉一次, 因為上面next() 會導致白屏(因為放行時, 動態路由還沒有加入到內存中路由表里)// 添加完, 立刻再跳轉一次next({path: to.path,replace: true // 不讓回退 類似于this.$router.replace() 防止進入剛才的白屏})}}} else { // 沒有登錄if (whiteList.includes(to.path)) { // 要去的路由地址字符串, 是否在白名單數組里出現過, 出現過就放行next()} else { // 去別的頁面(內部項目, 不登錄別的頁面不能去)next('/login')NProgress.done()}} }) // 驗證: 把本地cookie里token手動刪除掉, 刷新, 看看是否走最后一個else內 router.afterEach((to, from) => {// 正常next()放行了跳轉了, 才會走后置守衛, 關閉正常流程進度條//動態改變titledocument.title = getPageTitle(to.meta.title)NProgress.done() }) 法三: //對于axios進行二次封裝 import axios from 'axios' import nprogress from 'nprogress' //引入進度條的樣式 import 'nprogress/nprogress.css' //start:進度條開始 done:進度條結束//1:利用axios對象的方法create,去創建一個axios實例 //2:requests就是axios,只不過稍做配置一下 const requests = axios.create({//配置對象//基礎路徑,發請求URL攜帶api【發現:真實服務器接口都攜帶/api】baseURL: '/mock',//超時的設置timeout: 3000 }) //請求攔截器:將來項目中【N個請求】,只要發請求,會觸發請求攔截器!!! requests.interceptors.request.use(config => {//請求攔截器:請求頭【header】,請求頭能否給服務器攜帶參數//請求攔截器:其實項目中還有一個重要的作用,給服務器攜帶請求們的公共的參數//config:配置對象,對象里面有一個屬性很重要,headers請求頭//進度條開始動nprogress.start()return config }) //響應攔截器:請求數據返回會執行 requests.interceptors.response.use(res => {//res:實質就是項目中發請求、服務器返回的數據//進度條結束nprogress.done()return res.data},err => {//溫馨提示:某一天發請求,請求失敗,請求失敗的信息打印出來//終止Promise鏈return Promise.reject(new Error('failed'))} )//最后需要暴露:暴露的是添加新的功能的axios,即為requests export default requests

dayjs使用

import dayjs from 'dayjs' <el-table-column prop="timeOfEntry" label="入職時間" :formatter="timeFormatter" /> // 時間格式化// 后臺返回的時間格式不一定是什么?(后端沒有做數據的驗證, 錄入新員工不同的同學, 錄入的時間格式不同)timeFormatter(row) {return dayjs(row.timeOfEntry).format('YYYY-MM-DD') },

vue2實現上傳本地照片

***npm i cos-js-sdk-v5 --save"core-js": "3.6.5","cos-js-sdk-v5": "^1.3.5",<template><div class="user-info"><!-- 個人信息 --><el-form label-width="220px"><!-- 工號 入職時間 --><el-row class="inline-info"><el-col :span="12"><el-form-item label="工號"><el-input v-model="userInfo.workNumber" class="inputW" /></el-form-item></el-col><el-col :span="12"><el-form-item label="入職時間"><!--數據 "2018-01-01" -> 影響視圖顯示視圖選擇 -> 默認綁定日期對象 -> v-model變量type="date" (選擇年-月-日) 控制選擇日期格式 (組件渲染內容)value-format 選擇的值綁定格式(默認不寫, v-model綁定的是日期對象)--><el-date-pickerv-model="userInfo.timeOfEntry"style="width: 300px"type="date"class="inputW"/></el-form-item></el-col></el-row><!-- 姓名 部門 --><el-row class="inline-info"><el-col :span="12"><el-form-item label="姓名"><el-input v-model="userInfo.username" class="inputW" /></el-form-item></el-col><el-col :span="12"><el-form-item label="部門"><el-input v-model="userInfo.departmentName" class="inputW" readonly /></el-form-item></el-col></el-row><!--手機 聘用形式 --><el-row class="inline-info"><el-col :span="12"><el-form-item label="手機"><el-input v-model="userInfo.mobile" style="width: 300px" readonly /></el-form-item></el-col><el-col :span="12"><el-form-item label="聘用形式"><el-select v-model="userInfo.formOfEmployment" class="inputW"><el-optionv-for="item in EmployeeEnum.hireType":key="item.id":label="item.value":value="item.id"/></el-select></el-form-item></el-col></el-row><!-- 員工照片 --><el-row class="inline-info"><el-col :span="12"><el-form-item label="員工頭像"><!-- 放置上傳圖片 --><upload-img ref="uploadImg" /></el-form-item></el-col></el-row><!-- 保存個人信息 --><el-row class="inline-info" type="flex" justify="center"><el-col :span="12"><el-button type="primary" @click="saveUser">保存更新</el-button><el-button @click="$router.back()">返回</el-button></el-col></el-row></el-form></div> </template><script> import { getUserPhotoAPI, updateEmployeesAPI } from '@/api' import EmployeeEnum from '@/api/constant' export default {name: 'UserInfo',data() {return {userInfo: {}, // 個人信息-對象(提前聲明屬性為了見名知意)EmployeeEnum// 知識點: v-model="userInfo.workNumber"// 當輸入框有值的時候// 如果對象里有這個屬性, 則賦值// 如果對象里無這個屬性, 則會添加屬性并賦值}},created() {// 請求-個人信息this.getUserInfoFn()},methods: {async getUserInfoFn() {const res = await getUserPhotoAPI(this.$route.query.id)this.userInfo = res.datathis.$refs.uploadImg.imageUrl = res.data.staffPhoto// 額外加入一個聘用形式// 問題: 下面這樣寫, 為何點擊頁面下拉菜單, 標簽里顯示的值不變, vue里數據名里值變了// 問題: 視圖 -> 數據(v), 但是數據 -> 響應沒有更新給視圖// Vue框架原理: 響應式原理// Vue內部會檢測data里每個變量(如果變量本身改變了->上面那句話, 響應式更新視圖所有)// 檢測userInfo里每個屬性(檢測到變化, 會更新數據+視圖)// 上面數據劫持已經綁定完畢// 走到這句話的時候, 數據->視圖 (但是沒有綁定數據劫持)// 給"對象后續添加一個屬性"的時候, "還想雙向綁定好用" 不會應該對象本身的響應式觸發// this.userInfo.formOfEmployment = parseInt(this.$route.query.form)// 解決: 如果你要后續給對象添加屬性// $set() Vue內部提供的一個專門添加數組/對象某個值的(并且額外添加數據劫持)// 參數1: 數組/對象 目標// 參數2: 下標/屬性名// 參數3: 值this.$set(this.userInfo, 'formOfEmployment', parseInt(this.$route.query.form))},// 保存更新按鈕->點擊事件async saveUser() {// 把頭像地址保存到userInfo里一起帶給后臺this.userInfo.staffPhoto = this.$refs.uploadImg.imageUrlconst res = await updateEmployeesAPI(this.userInfo)this.$message.success(res.message)this.$router.back()}} } </script><style lang="scss" scoped></style> ***組件中: <!-- 放置上傳圖片 --> <upload-img ref="uploadImg" />this.$refs.uploadImg.imageUrl = res.data.staffPhoto

uuid使用

****uuid.js //利用uuid生成未登錄用戶臨時標識符 import { v4 as uuidv4 } from 'uuid' //封裝函數:只能生成一次用戶臨時身份 export const getUUID = () => {let uuid_token = localStorage.getItem('UUIDTOKEN')//如果沒有if (!uuid_token) {//生成一個隨機的臨時身份uuid_token = uuidv4()//本地存儲一次localStorage.setItem('UUIDTOKEN', uuid_token)}return uuid_token }***store.js //封裝游客身份模塊uuid 生成一個隨機字符串(不能在變了) import { getUUID } from '@/utils/uuid_token' const state = {//游客臨時身份uuid_token: getUUID() }

VueRouter中重寫push和replace方法

在vue中如果我們使用編程是跳轉路由,然后跳轉的還是同一個路由頁面,那么控制臺會出現報錯 //先把VueRouter原型對象的push,先保存一份 let originPush = VueRouter.prototype.push let originReplace = VueRouter.prototype.replace //重寫push|replace //第一個參數:告訴原來push方法,你往哪里跳轉(傳遞哪些參數) VueRouter.prototype.push = function (location, resolve, reject) {if (resolve && reject) {//call||apply區別:// 相同點,都可以調用函數一次,都可以篡改函數的上下文一次//不同點: call與apply傳遞參數: call傳遞參數用逗號隔開,apply方法執行,傳遞數組originPush.call(this, location, resolve, reject)} else {originPush.call(this,location,() => {},() => {})} } VueRouter.prototype.replace = function (location, resolve, reject) {if (resolve && reject) {//call||apply區別:// 相同點,都可以調用函數一次,都可以篡改函數的上下文一次//不同點: call與apply傳遞參數: call傳遞參數用逗號隔開,apply方法執行,傳遞數組originReplace.call(this, location, resolve, reject)} else {originReplace.call(this,location,() => {},() => {})} }

全局前置守衛

import { router, addRoutes } from '@/router' import { getToken } from '@/composables/auth' import { toast, showFullLoading, hideFullLoading } from '@/composables/util' import store from './store' // 全局前置守衛 let hasGetInfo = false router.beforeEach(async (to, from, next) => {//顯示loadingshowFullLoading()const token = getToken()// 沒有登錄,強制跳轉回登錄頁if (!token && to.path != '/login') {toast('請先登錄', 'error')return next({ path: '/login' })}// 防止重復登錄if (token && to.path == '/login') {toast('請勿重復登錄', 'error')return next({ path: from.path ? from.path : '/' })}// 如果用戶登錄了,自動獲取用戶信息,并存儲在vuex當中let hasNewRoutes = falseif (token && !hasGetInfo) {let { menus } = await store.dispatch('getInfo')hasGetInfo = true//動態添加路由hasNewRoutes = addRoutes(menus)}// 設置頁面標題let title = (to.meta.title ? to.meta.title : '') + '-帝莎編程商城后臺'document.title = titlehasNewRoutes ? next(to.fullPath) : next() })// 全局后置守衛 router.afterEach((to, from) => hideFullLoading()) ************************************************************************************************************ import router, { asyncRoutes } from './router' import NProgress from 'nprogress' // progress bar import 'nprogress/nprogress.css' // progress bar style import store from '@/store' import getPageTitle from '@/utils/get-page-title'NProgress.configure({ showSpinner: false }) // NProgress Configurationconst whiteList = ['/login', '/404'] // 白名單: 無需登錄, 可以跳轉查看的路由地址(在路由表里)// 問題: 為何動態路由添加后, 在動態路由地址上刷新會404? // 前提1: 刷新時, 所有代碼重新執行, 回歸初始化 // 前提2: 刷新時, 路由會從/ 跳轉到瀏覽器地址欄所在的路由地址 (走一次路由守衛代碼) // 動態的還未添加, 所以404了// 問題: 右上角退出登錄+重新登錄, 進入到首頁時, 網頁刷新不? (不刷新) // 網頁本身是不刷新的, 完全依賴路由業務場景的切換 (單頁面應用好處: 用戶的體驗更好, 切換業務場景更快) // 內存里路由表, 之前添加的篩選后路由規則對象還在不? (在) // 問題2: 為何重新登錄, 路由定義重復了? // 退出登錄的時候, 把token和用戶信息清除了 // 登錄的時候, 先獲取到token保存到vuex和本地, 然后才是跳轉路由, 才執行路由守衛(所以判斷token有值) // 但是用戶信息沒有, 重新請求, 再添加一遍篩選后的路由對象, 所以導致了路由重復// 解決: 退出登錄的時候, 讓路由也回歸初始化// 問題: 什么是路由(導航)守衛? // 答案: 當路由發生跳轉的時候, 會觸發一個鉤子"函數", 在函數中可以通過跳轉或取消或強制切換跳轉地址來守衛導航 // 路由守衛里必須要有一個next()調用, 出口, 讓路由頁面跳轉 router.beforeEach(async(to, from, next) => {NProgress.start()const token = store.getters.token// 登錄了->不能去登錄頁// 非登錄->只能去登錄頁if (token) { // 登陸了if (to.path === '/login') { // 去登錄頁// 中斷要跳轉/login這次導航, 重新跳轉到/(首頁)next('/')NProgress.done()} else { // 去別的頁面next() // 如果手動讓cookie里token改錯誤, 刷新以后, vuex才會從本地取出錯誤token// 刷新時, 路由守衛會從 / 跳轉到地址欄里路由地址, 所以先讓頁面跳轉進去// 執行下面請求會401, 被動退出時, 才能拿到跳轉后的路由地址(未遂地址給登錄頁面, 否則next在下面, 未遂地址一直是/)if (!store.getters.name) {await store.dispatch('user/getUserInfoActions')// const menus = await store.dispatch('user/getUserInfoActions')// 用menus權限點英文字符串, 和路由規則對象name匹配// 把所有準備好的8個路由規則對象, 取出, 看看名字和menus里是否匹配, 匹配就證明// 此登錄的用戶有這個頁面的訪問權限, 讓filter收集此路由規則對象到新數組里// const filterList = asyncRoutes.filter(routeObj => {// const routeName = routeObj.children[0].name.toLowerCase()// return menus.includes(routeName)// })// filterList.push({ path: '*', redirect: '/404', hidden: true })// 始終都動態添加先8個路由規則對象// 知識點: 路由切換匹配的路由規則對象數組存在于內存中的// new Router時, 有一些初始的路由規則對象// addRoutes, 會給路由表, 再額外的增加一個規則對象// 現象: 路由規則對象添加成功, 但是左側的導航不見了const filterList = asyncRoutesrouter.addRoutes(filterList)// 給vuex也同步一份store.commit('permission/setRoutes', filterList)// 路由再跳轉一次, 因為上面next() 會導致白屏(因為放行時, 動態路由還沒有加入到內存中路由表里)// 添加完, 立刻再跳轉一次next({path: to.path,replace: true // 不讓回退 類似于this.$router.replace() 防止進入剛才的白屏})}}} else { // 沒有登錄if (whiteList.includes(to.path)) { // 要去的路由地址字符串, 是否在白名單數組里出現過, 出現過就放行next()} else { // 去別的頁面(內部項目, 不登錄別的頁面不能去)next('/login')NProgress.done()}} }) // 驗證: 把本地cookie里token手動刪除掉, 刷新, 看看是否走最后一個else內 router.afterEach((to, from) => {// 正常next()放行了跳轉了, 才會走后置守衛, 關閉正常流程進度條//動態改變titledocument.title = getPageTitle(to.meta.title)NProgress.done() }) ************************************************************************************************************ //配置路由的地方 import Vue from 'vue' import VueRouter from 'vue-router' //使用插件 Vue.use(VueRouter) import routes from './routes' //引入倉庫 import store from '@/store' //先把VueRouter原型對象的push,先保存一份 let originPush = VueRouter.prototype.push let originReplace = VueRouter.prototype.replace //重寫push|replace //第一個參數:告訴原來push方法,你往哪里跳轉(傳遞哪些參數) VueRouter.prototype.push = function (location, resolve, reject) {if (resolve && reject) {//call||apply區別:// 相同點,都可以調用函數一次,都可以篡改函數的上下文一次//不同點: call與apply傳遞參數: call傳遞參數用逗號隔開,apply方法執行,傳遞數組originPush.call(this, location, resolve, reject)} else {originPush.call(this,location,() => {},() => {})} } VueRouter.prototype.replace = function (location, resolve, reject) {if (resolve && reject) {//call||apply區別:// 相同點,都可以調用函數一次,都可以篡改函數的上下文一次//不同點: call與apply傳遞參數: call傳遞參數用逗號隔開,apply方法執行,傳遞數組originReplace.call(this, location, resolve, reject)} else {originReplace.call(this,location,() => {},() => {})} } //配置路由 let router = new VueRouter({//配置路由//第一:路徑的前面需要有/(不是二級路由)//路徑中單詞都是小寫的// component右側v別給我加單引號【字符串:組件是對象(VueComponent類的實例)】routes,//滾動行為scrollBehavior(to, from, savedPosition) {//y代表滾動條在最上方return { y: 0 }} }) //全局守衛:前置守衛(在路由跳轉之間進行判斷) //全局守衛:只要項目中有任何路由變化,全局守衛都會進行攔截【符合條件走你,不符合條件不能訪問】 //全局守衛:全局前置守衛【訪問之前進行觸發】 router.beforeEach(async (to, from, next) => {//to:可以獲取到你要跳轉到哪個路由信息//from:可以獲取到你從哪個路由而來的信息//next:放行函數 next()放行//第一種:next(),放行函數,全部放行!!!//第二種:next(path),守衛指定放行到那個路由去//token//用戶登錄了,才會有token,未登錄一定不會有tokenlet hasToken = store.state.user.token//用戶信息let hasName = store.state.user.userInfo.name//用戶登錄if (hasToken) {//用戶登錄了,不能去loginif (to.path == '/login') {next('/home')} else {//用戶登陸了,而且還有用戶信息【去的并非是login】//登陸,去的不是login 去的是【home |search|detail|shopcart】//如果用戶名已有if (hasName) {next()} else {//用戶登陸了,但是沒有用戶信息 派發action讓倉庫存儲用戶信息在跳轉try {//發請求獲取用戶信息以后在放行await store.dispatch('getUserInfo')next()} catch (error) {//用戶沒有信息,還攜帶token發請求獲取用戶信息【失敗】//token【*****失效了】//token失效:本地清空數據、服務器的token通知服務器清除await store.dispatch('userLogout')//回到登錄頁,重新獲取一個新的學生證next('/login')}}}} else {//用戶未登錄||目前的判斷都是放行.將來這里會'回手掏'增加一些判斷//用戶未登錄:不能進入/trade、/pay、/paysuccess、/center、/center/myorder /center/grouporderlet toPath = to.path//要去的路由存在if (toPath.indexOf('/trade') != -1 || toPath.indexOf('/pay') != -1 || toPath.indexOf('/center') != -1) {//把未登錄的時候想去而沒有去成的路由地址,存儲于地址欄中【路由】next('/login?redirect=' + toPath)} else {next()}} }) export default router

swiper插件使用

***main.js npm i swiper //引入swiper樣式 import 'swiper/css/swiper.css' ***組件中: <template><!-- 輪播圖 --><div class="swiper-container" ref="cur"><div class="swiper-wrapper"><div class="swiper-slide" v-for="carousel in list" :key="carousel.id"><img :src="carousel.imgUrl" /></div></div><!-- 如果需要分頁器 --><div class="swiper-pagination"></div><!-- 如果需要導航按鈕 --><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></div> </template><script> //引入Swiper import Swiper from 'swiper' export default {name: 'Carousel',props: ['list'],watch: {list: {//立即監聽:不管你數據有沒有變化,我上來立即監聽一次//為什么watch監聽不到list:因為這個數據從來沒有發生變化《數據是父親給的,父親給的時候就是一個對象,對象里面該有的數據都是有的)immediate: true,handler() {//只能監聽到數據已經有了,但是v-for動態渲染結構我們還是沒有辦法確定的,因此還是需要用nextTickthis.$nextTick(() => {var mySwiper = new Swiper(this.$refs.cur, {loop: true,autoplay: true,//如果需要分頁器pagination: {el: '.swiper-pagination',//點擊小球的時候也切換圖片clickable: true},//如果需要前進后退按鈕navigation: {nextEl: '.swiper-button-next',prevEl: '.swiper-button-prev'}})})}}} } </script><style scoped></style>

支付代碼、QRCode插件使用、ElementUI注冊組件

***main.js import { Button, MessageBox,Message } from 'element-ui' //注冊全局組件 Vue.component(Button.name, Button) //ElementUI注冊組件的時候,還有一種寫法,掛在原型上 Vue.prototype.$msgbox = MessageBox Vue.prototype.$alert = MessageBox.alert Vue.prototype.$confirm = MessageBox.confirm Vue.prototype.$message = Message***$alert、$msgbox使用: npm install --save qrcode import QRCode from 'qrcode' //支付彈出框函數async open() {//生成二維碼地址let url = await QRCode.toDataURL(this.payInfo.codeUrl)this.$alert(`<img src=${url} />`, '請您微信支付', {//是否將 message屬性作為HTML片段處理dangerouslyUseHTMLString: true,//居中center: true,//顯示取消按鈕showCancelButton: true,//取消按鈕的文本內容cancelButtonText: '支付遇見問題',//確定按鈕的文本confirmButtonText: '已支付成功',//右上角的叉子showClose: true,//關閉彈出框的配置值beforeClose: (type, instance, done) => {//type:區分取消|確定按鈕//instance:當前組件實例//done:關閉彈出框的方法if (type == 'cancel') {// alert('請聯系管理員')//清除定時器clearInterval(this.timer)this.timer = null//關閉彈出框done()} else {//判斷是否真的支付了//開發人員后門if (this.code == 200) {clearInterval(this.timer)this.timer = nulldone()//跳轉到下一路由this.$router.push('/paysuccess')}}}}).catch(() => {}) //沒有進行錯誤捕獲,就會提示Uncaught (in promise) cancel錯誤。//需要知道支付成功與否 每隔1s就判斷支付成功沒//支付成功,路由的跳轉,如果支付失敗,提示信息//定時器沒有,開啟一個新的定時器if (!this.timer) {// console.log(this.timer) null// console.log(Boolean(this.timer)) false// console.log(Boolean(!this.timer)) truethis.timer = setInterval(async () => {//發請求獲取用戶支付狀態let result = await this.$API.reqPayStatus(this.orderId)if (result.code == 200) {//第一步:停止定時器clearInterval(this.timer)this.timer = null//保存支付成功返回的codethis.code = result.code//關閉彈出框this.$msgbox.close()//跳轉到下一路由this.$router.push('/paysuccess')}}, 1000)}} ***$confirm使用: // 點擊退出登錄的回調logout() {this.$confirm('要退出登錄嗎?', '提示', {confirmButtonText: '確定',cancelButtonText: '取消',type: 'warning'}).then(async() => {let res = await this.$request("/logout",{ timestamp: getTimeStamp() });// console.log(res);if (res.data.code != 200) {this.$message("退出登錄失敗, 請稍后重試!");return;}// 清空data和localstorage中的數據,以及cookie// window.localStorage.setItem("userInfo", "");// this.clearAllCookie();// 刪除localstoarge的userIdwindow.localStorage.removeItem("userId");// 在vuex中更新登錄狀態this.$store.commit("updataLoginState", false);this.$message.success("退出成功!");this.isCurrentUser = false;}).catch(() => {this.$message({type: 'info',message: '已取消登錄'}); });}, ***$message使用: this.$message.error("請先進行登錄操作");

b u s 、 bus、 busAPI全局綁定

***main.js //統一接口api文件夾里面全部請求函數 //統一引入 import * as API from '@/api'new Vue({render: h => h(App),//全局時間總線$bus配置// beforeCreate 函數就是 Vue 實例被創建出來之前,會執行它。在 beforeCreate 生命周期函數執行的時候,Vue實例中的 data 和 methods 中的數據都還沒有被初始化。beforeCreate() {//向外共享Vue的實例對象 固定寫法Vue.prototype.$bus = this// console.log(Vue.prototype.$bus)Vue.prototype.$API = API// console.log(Vue.prototype.$API)里面是reqAddOrUpdateShopCart,reqAddressInfo等接口},//注冊路由:底下的寫法KV一致省略V[router小寫]//注冊路由信息:當這里書寫router的時候,組件身上都擁有$route,$router屬性router,//注冊倉庫:組件實例的身上會多一個屬性$store屬性store }).$mount('#app') ***$bus使用場景: //通知兄弟組件:當前的索引值為幾this.$bus.$emit('getIndex', this.currentIndex) //全局事件總線:獲取兄弟組件傳遞過來的索引值this.$bus.$on('getIndex', index => {//修改當前響應式數據this.currentIndex = index}) ***$API使用場景: //獲取我的訂單方法async getData() {//結構出參數const { page, limit } = this//好處:不用引入reqMyOrderList接口let result = await this.$API.reqMyOrderList(page, limit)if (result.code == 200) {this.myOrder = result.data} },

vee-validate使用

npm install vee-validate --save ***main.js //引入表單檢驗插件 import '@/plugins/validate' ***validate.js //vee-validate插件:表單驗證區域 import Vue from 'vue' import VeeValidate from 'vee-validate' //中文提示信息 import zh_CN from 'vee-validate/dist/locale/zh_CN' Vue.use(VeeValidate)//表單驗證 VeeValidate.Validator.localize('zh_CN', {messages: {...zh_CN.messages,is: field => `${field}必須與密碼相同` //修改內置規則的message,讓確認密碼和密碼相同},attributes: {//給校驗的-field-屬性名映射中文名稱//給每個字段轉為中文phone: '手機號',code: '驗證碼',password: '密碼',password1: '確認密碼',agree: '協議'} }) //自定義校驗規則 VeeValidate.Validator.extend('agree', {validate: value => {return value},getMessage: field => field + '必須同意' }) ***組件中: <div class="content"><label>手機號:</label><!-- has('field') – 當前filed是否有錯誤(true/false) --><!-- errors.first('field') – 獲取關于當前field的第一個錯誤信息 --><input placeholder="請輸入你的手機號" v-model="phone" name="phone" v-validate="{ required: true, regex: /^1\d{10}$/ }" :class="{ invalid: errors.has('phone') }" /><span class="error-msg">{{ errors.first('phone') }}</span></div><div class="content"><label>驗證碼:</label><input placeholder="請輸入你的驗證碼" v-model="code" name="code" v-validate="{ required: true, regex: /^\d{6}$/ }" :class="{ invalid: errors.has('code') }" /><button style="width: 100px; height: 38px" @click="getCode">獲取驗證碼</button><span class="error-msg">{{ errors.first('code') }}</span></div><div class="content"><label>登錄密碼:</label><input type="password" placeholder="請輸入你的密碼" v-model="password" name="password" v-validate="{ required: true, regex: /^[0-9A-Za-z]{8,20}$/ }" :class="{ invalid: errors.has('password') }" />格式:8-20位密碼,禁止輸入符號<span class="error-msg">{{ errors.first('password') }}</span></div><div class="content"><label>確認密碼:</label><input type="password" placeholder="請輸入確認密碼" v-model="password1" name="password1" v-validate="{ required: true, is: password }" :class="{ invalid: errors.has('password1') }" /><span class="error-msg">{{ errors.first('password1') }}</span></div><div class="controls"><input type="checkbox" v-model="agree" name="agree" v-validate="{ required: true, agree: true }" :class="{ invalid: errors.has('agree') }" /><span>同意協議并注冊《尚品匯用戶協議》</span><span class="error-msg">{{ errors.first('agree') }}</span></div><script> data() {return {//收集表單數據--手機號phone: '',//驗證碼code: '',//密碼password: '',//確認密碼password1: '',//確認協議agree: true}},//注冊信息全部合格后才能注冊const success = await this.$validator.validateAll()// console.log(success);//布爾值//全部表單驗證成功,在向服務器發請求,進行注冊//只要有一個表單沒有成功,不會發請求 </script>

懶加載插件使用

npm i vue-lazyload -S //引入圖片懶加載插件 import VueLazyload from 'vue-lazyload' import atm from '@/assets/logo.png' Vue.use(VueLazyload, {// 放入懶加載的圖片,就是atmloading: atm }) ***組件中: <img v-lazy="good.defaultImg" />

i18n包使用

***main.js import ELEMENT from 'element-ui' // 引入語言對象 import i18n from '@/lang' Vue.use(ELEMENT, {// 配置 ELEMENT 語言轉換關系// 每個組件都會調用一次i18n: (key, value) => {// 組件內容處, 使用的相關參數和值↓// key: el.pagination.total (好比是要查找語言包的屬性路徑)// value: 對應要傳入的值 {total: 10}// i18n.t 好比 Vue組件$t// key就是去語言包環境找到對應的中文/英文值// value就是要傳入的值 會替換掉{} 位置, 換成對應值在原地顯示return i18n.t(key, value)} }) // 只是為了注冊elementUI組件(語言切換, 一會兒和Vuei18n集成)***組件中: <template><!--trigger 是下拉菜單的觸發時機@command 自定義事件 (檢測菜單項的點擊行為)--><el-dropdown trigger="click" @command="changeLanguage"><!-- 第一個子標簽是上來就顯示的標簽 --><div><svg-icon style="color:#fff;font-size:20px" icon-class="language" /></div><!-- 就會出現真正的下拉菜單項 --><el-dropdown-menu slot="dropdown"><!-- command 點擊時, 傳入給@command事件里參數$i18n 是Vue.use(Vuei18n)添加給Vue原型的全局屬性, 通過它可以拿到i18n里locale環境的英文標識('zh'/'en')--><el-dropdown-item command="zh" :disabled="'zh'=== $i18n.locale ">中文</el-dropdown-item><el-dropdown-item command="en" :disabled="'en'=== $i18n.locale ">English</el-dropdown-item></el-dropdown-menu></el-dropdown> </template><script> export default {methods: {// 下拉菜單項的點擊事件// lang的值就是 "zh" "en"changeLanguage(lang) {this.$i18n.locale = lang // 設置給本地的i18n插件this.$message.success('切換多語言成功')}} } </script>***lang.js import Vue from 'vue' import VueI18n from 'vue-i18n' // ElementUI的中英文語言包引入 // 語言包:對象 // 相同的key(鍵)名, 對應的對象(值, 不同的語言包, 對應值不同) import enLocale from 'element-ui/lib/locale/lang/en' import zhLocale from 'element-ui/lib/locale/lang/zh-CN'Vue.use(VueI18n)// 通過選項創建 VueI18n 實例 const i18n = new VueI18n({// 隱藏警告silentTranslationWarn: true,locale: 'zh-CN', // 設置地區messages: {en: {navbar: {companyName: 'Jiangsu Chuanzhi podcast Education Technology Co., Ltd',name: '{name}'},sidebar: {dashboard: 'Dashboard',approvals: 'Approvals',departments: 'Departements',employees: 'Employees',permission: 'Permission',attendances: 'Attendances',salarys: 'Salarys',setting: 'Company-Settings',social: 'Social'},...enLocale,message: {hello: 'hello world'}},zh: {navbar: {companyName: '江蘇傳智播客教育科技股份有限公司',name: '{name}'},sidebar: {dashboard: '首頁',approvals: '審批',departments: '組織架構',employees: '員工',permission: '權限',attendances: '考勤',salarys: '工資',setting: '公司設置',social: '社保'},...zhLocale,message: {hello: '你好, 世界'}}} // 設置地區信息 })// vuei18n內部的工作原理 // 1. 會給Vue原型上添加$t方法 // 2. 我們自己業務vue文件中, 文字部分都要換成$t方法, 然后在方法中傳入要獲取的對象的屬性值路徑字符串 // 3. $t方法內, 會根據locale的值, 去messages里面取出對應環境的語言對象, 然后再拼接本次尋找值對象屬性的路徑, 找到 對應的值返回到$t函數位置export default i18n

map使用

1.案例一:取給定數組的某一字段組成新數組 的后臺傳來的數據 data(json): [ //data的數據{"txt":"09:00-12:00","codId":"1","flgDel":"0","id":1},{"txt":"13:00-16:00","codId":"1","flgDel":"0","id":2},{"txt":"18:00-20:00","codId":"1","flgDel":"0","id":3} ] 前臺使用要為: ['09:00-12:00', '13:00-16:00', '18:00-20:00'] 用到map()只需一行。快捷方法出來了學去吧。 let time = data.map(item =>(item.txt)) console.log(time) //控制臺輸出如下 //['09:00-12:00', '13:00-16:00', '18:00-20:00'] 2.案例二:取給定數組的某些字段重命名并組成新數組 新的接口傳來data(json): [ //新data數據 {"txt":"拜訪","flgDel":"0","id":1}, {"txt":"面試","flgDel":"0","id":2}, {"txt":"其他","flgDel":"0","id":3} ] 前臺使用數組結構: [{ name: '拜訪' }, { name: '面試' }, { name: '其他' }] //這里看到相比于案例一有字段了,還新命名了 //只需一行map() let resion = data.map(item =>({name: item.txt})) console.log(resion) //控制臺輸出 //[{ name: '拜訪' }, { name: '面試' }, { name: '其他' }] 當然,或許你要的這樣? : [{ name: '拜訪',id:'1' }, { name: '面試',id:'2' }, { name: '其他',id:'3'}] //要兩個字段的數據 let resion2 = data.map(item =>({name: item.txt, id: item.id})) console.log(resion2) //控制臺輸出 //[{ name: '拜訪',id:'1' }, { name: '面試',id:'2' }, { name: '其他',id:'3'}] 又或許你想要這樣? : [{ name: '拜訪1' }, { name: '面試2' }, { name: '其他3'}] //要拼接的數據 let resion3 = data.map(item =>({name: item.txt + item.id})) console.log(resion3) //控制臺輸出 //[{ name: '拜訪1' }, { name: '面試2' }, { name: '其他3'}]

配置動態路由

*********************************************************************法一: import { createRouter, createWebHashHistory } from 'vue-router' import Index from '@/pages/index.vue' import Login from '@/pages/login.vue' import NotFound from '@/pages/404.vue' import Admin from '@/layouts/admin.vue' import GoodList from '@/pages/goods/list.vue' import CategoryList from '@/pages/category/list.vue' import UserList from '@/pages/user/list.vue' import OrderList from '@/pages/order/list.vue' import CommentList from '@/pages/comment/list.vue' import ImageList from '@/pages/image/list.vue' import NoticeList from '@/pages/notice/list.vue' import SettingBase from '@/pages/setting/base.vue' import CouponList from '@/pages/coupon/list.vue' import ManagerList from '@/pages/manager/list.vue' import AccessList from '@/pages/access/list.vue' import RoleList from '@/pages/role/list.vue' import SkusList from '@/pages/skus/list.vue' import LevelList from '@/pages/level/list.vue' import SettingBuy from '@/pages/setting/buy.vue' import SettingShip from '@/pages/setting/ship.vue' import DistributionIndex from '@/pages/distribution/index.vue' import DistributionSetting from '@/pages/distribution/setting.vue' //默認路由,所有用戶共享 const routes = [{path: '/',name: 'admin',component: Admin},{path: '/login',component: Login,meta: {title: '登錄頁'}},{path: '/:pathMatch(.*)*',name: 'NotFound',component: NotFound} ] //動態路由 const asyncRoutes = [{path: '/',name: '/',component: Index,meta: {title: '后臺首頁'}},{path: '/goods/list',name: '/goods/list',component: GoodList,meta: {title: '商品管理'}},{path: '/category/list',name: '/category/list',component: CategoryList,meta: {title: '分類列表'}},{path: '/user/list',name: '/user/list',component: UserList,meta: {title: '用戶列表'}},{path: '/order/list',name: '/order/list',component: OrderList,meta: {title: '訂單列表'}},{path: '/comment/list',name: '/comment/list',component: CommentList,meta: {title: '評價列表'}},{path: '/image/list',name: '/image/list',component: ImageList,meta: {title: '圖庫列表'}},{path: '/notice/list',name: '/notice/list',component: NoticeList,meta: {title: '公告列表'}},{path: '/setting/base',name: '/setting/base',component: SettingBase,meta: {title: '配置'}},{path: '/coupon/list',name: '/coupon/list',component: CouponList,meta: {title: '優惠券列表'}},{path: '/manager/list',name: '/manager/list',component: ManagerList,meta: {title: '管理員管理'}},{path: '/access/list',name: '/access/list',component: AccessList,meta: {title: '菜單權限管理'}},{path: '/role/list',name: '/role/list',component: RoleList,meta: {title: '角色管理'}},{path: '/skus/list',name: '/skus/list',component: SkusList,meta: {title: '規格管理'}},{path: '/level/list',name: '/level/list',component: LevelList,meta: {title: '會員等級'}},{path: '/setting/buy',name: '/setting/buy',component: SettingBuy,meta: {title: '支付設置'}},{path: '/setting/ship',name: '/setting/ship',component: SettingShip,meta: {title: '物流設置'}},{path: '/distribution/index',name: '/distribution/index',component: DistributionIndex,meta: {title: '分銷員管理'}},{path: '/distribution/setting',name: '/distribution/setting',component: DistributionSetting,meta: {title: '分銷設置'}} ]export const router = createRouter({history: createWebHashHistory(),routes })// 動態添加路由的方法 export function addRoutes(menus) {// 是否有新的路由let hasNewRoutes = falseconst findAndAddRoutesByMenus = arr => {arr.forEach(e => {let item = asyncRoutes.find(o => o.path == e.frontpath)if (item && !router.hasRoute(item.path)) {//添加到名字叫admin路由的子路由router.addRoute('admin', item)hasNewRoutes = true}if (e.child && e.child.length > 0) {findAndAddRoutesByMenus(e.child)}})}findAndAddRoutesByMenus(menus)return hasNewRoutes }***全局前置守衛.js:// 如果用戶登錄了,自動獲取用戶信息,并存儲在vuex當中let hasNewRoutes = falseif (token && !hasGetInfo) {let { menus } = await store.dispatch('getInfo')hasGetInfo = true//動態添加路由hasNewRoutes = addRoutes(menus)}*********************************************************************法二: import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) import Layout from '@/layout' import approvalsRouter from './modules/approvals' import departmentsRouter from './modules/departments' import employeesRouter from './modules/employees' import permissionRouter from './modules/permission' import attendancesRouter from './modules/attendances' import salarysRouter from './modules/salarys' import settingRouter from './modules/setting' import socialRouter from './modules/social'// 動態路由表,項目中不同的用戶可以訪問不同的功能 // 暫時讓所有人都看到這8個頁面(最后2天再去做篩選) // 動態路由規則 異步路由 //只做了前4個。后4個自己擴展 export const asyncRoutes = [departmentsRouter,settingRouter,employeesRouter,permissionRouter,approvalsRouter,attendancesRouter,salarysRouter,socialRouter ]export const constantRoutes = [{path: '/login',component: () => import('@/views/login/index'),hidden: true},{path: '/404',component: () => import('@/views/404'),hidden: true},{path: '/',component: Layout,redirect: '/dashboard',children: [{path: 'dashboard',name: 'Dashboard',component: () => import('@/views/dashboard/index'),meta: { title: '首頁', icon: 'dashboard' }}]},{path: '/excel',component: Layout,children: [{path: '',component: () => import('@/views/excel')}]}// { path: '*', redirect: '/404', hidden: true } ]const createRouter = () => new Router({// mode: 'history', // require service supportscrollBehavior: () => ({ y: 0 }),// routes: [...constantRoutes, ...asyncRoutes]routes: [...constantRoutes] })const router = createRouter()// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 export function resetRouter() {const newRouter = createRouter()router.matcher = newRouter.matcher // reset router// 重置的是路由對象內部match方法(匹配routes選項中的路由規則的)// match里會使用newRouter里routes一起代替掉了 }export default router***路由守衛.js import router, { asyncRoutes } from './router' import NProgress from 'nprogress' // progress bar import 'nprogress/nprogress.css' // progress bar style import store from '@/store' import getPageTitle from '@/utils/get-page-title'NProgress.configure({ showSpinner: false }) // NProgress Configurationconst whiteList = ['/login', '/404'] // 白名單: 無需登錄, 可以跳轉查看的路由地址(在路由表里)// 問題: 為何動態路由添加后, 在動態路由地址上刷新會404? // 前提1: 刷新時, 所有代碼重新執行, 回歸初始化 // 前提2: 刷新時, 路由會從/ 跳轉到瀏覽器地址欄所在的路由地址 (走一次路由守衛代碼) // 動態的還未添加, 所以404了// 問題: 右上角退出登錄+重新登錄, 進入到首頁時, 網頁刷新不? (不刷新) // 網頁本身是不刷新的, 完全依賴路由業務場景的切換 (單頁面應用好處: 用戶的體驗更好, 切換業務場景更快) // 內存里路由表, 之前添加的篩選后路由規則對象還在不? (在) // 問題2: 為何重新登錄, 路由定義重復了? // 退出登錄的時候, 把token和用戶信息清除了 // 登錄的時候, 先獲取到token保存到vuex和本地, 然后才是跳轉路由, 才執行路由守衛(所以判斷token有值) // 但是用戶信息沒有, 重新請求, 再添加一遍篩選后的路由對象, 所以導致了路由重復// 解決: 退出登錄的時候, 讓路由也回歸初始化// 問題: 什么是路由(導航)守衛? // 答案: 當路由發生跳轉的時候, 會觸發一個鉤子"函數", 在函數中可以通過跳轉或取消或強制切換跳轉地址來守衛導航 // 路由守衛里必須要有一個next()調用, 出口, 讓路由頁面跳轉 router.beforeEach(async(to, from, next) => {NProgress.start()const token = store.getters.token// 登錄了->不能去登錄頁// 非登錄->只能去登錄頁if (token) { // 登陸了if (to.path === '/login') { // 去登錄頁// 中斷要跳轉/login這次導航, 重新跳轉到/(首頁)next('/')NProgress.done()} else { // 去別的頁面next() // 如果手動讓cookie里token改錯誤, 刷新以后, vuex才會從本地取出錯誤token// 刷新時, 路由守衛會從 / 跳轉到地址欄里路由地址, 所以先讓頁面跳轉進去// 執行下面請求會401, 被動退出時, 才能拿到跳轉后的路由地址(未遂地址給登錄頁面, 否則next在下面, 未遂地址一直是/)if (!store.getters.name) {await store.dispatch('user/getUserInfoActions')// const menus = await store.dispatch('user/getUserInfoActions')// 用menus權限點英文字符串, 和路由規則對象name匹配// 把所有準備好的8個路由規則對象, 取出, 看看名字和menus里是否匹配, 匹配就證明// 此登錄的用戶有這個頁面的訪問權限, 讓filter收集此路由規則對象到新數組里// const filterList = asyncRoutes.filter(routeObj => {// const routeName = routeObj.children[0].name.toLowerCase()// return menus.includes(routeName)// })// filterList.push({ path: '*', redirect: '/404', hidden: true })// 始終都動態添加先8個路由規則對象// 知識點: 路由切換匹配的路由規則對象數組存在于內存中的// new Router時, 有一些初始的路由規則對象// addRoutes, 會給路由表, 再額外的增加一個規則對象// 現象: 路由規則對象添加成功, 但是左側的導航不見了const filterList = asyncRoutesrouter.addRoutes(filterList)// 給vuex也同步一份store.commit('permission/setRoutes', filterList)// 路由再跳轉一次, 因為上面next() 會導致白屏(因為放行時, 動態路由還沒有加入到內存中路由表里)// 添加完, 立刻再跳轉一次next({path: to.path,replace: true // 不讓回退 類似于this.$router.replace() 防止進入剛才的白屏})}}} else { // 沒有登錄if (whiteList.includes(to.path)) { // 要去的路由地址字符串, 是否在白名單數組里出現過, 出現過就放行next()} else { // 去別的頁面(內部項目, 不登錄別的頁面不能去)next('/login')NProgress.done()}} }) // 驗證: 把本地cookie里token手動刪除掉, 刷新, 看看是否走最后一個else內 router.afterEach((to, from) => {// 正常next()放行了跳轉了, 才會走后置守衛, 關閉正常流程進度條//動態改變titledocument.title = getPageTitle(to.meta.title)NProgress.done() })

mixins使用

***lyricScroll.js export default {props: {lyric: {type: Array,default: [],},},data() {return {// 當前歌詞索引lyricsIndex: 0,};},methods: {// 實現歌詞滾動lyricScroll(currentLyric) {let placeholderHeight = 0;// 獲取歌詞itemlet lyricsArr = document.querySelectorAll(".lyricsItem");// 獲取歌詞框let lyrics = document.querySelector(".lyrics");// console.log(lyrics.offsetTop)//123// console.log(lyricsArr[0].offsetTop)//123// placeholder的高度if (placeholderHeight == 0) {placeholderHeight = lyricsArr[0].offsetTop - lyrics.offsetTop;//123-123// console.log(placeholderHeight)//0}// 歌詞item在歌詞框的高度 = 歌詞框的offsetTop - 歌詞item的offsetTop// console.log(currentLyric);//歌詞索引// console.log(lyricsArr[currentLyric - 1])//歌詞第一句打印的是全部歌詞,后面打印的是上一句歌詞的divif (lyricsArr[currentLyric - 1]) {let distance = lyricsArr[currentLyric - 1].offsetTop - lyrics.offsetTop;// console.log(lyricsArr[currentLyric - 1].offsetTop)// console.log(lyrics.offsetTop)//123// console.log(distance)// lyricsArr[currentLyric].scrollIntoView();lyrics.scrollTo({behavior: "smooth",top: distance - placeholderHeight,});}},//獲取當前歌詞索引getCurrentLyricsIndex(currentTime) {let lyricsIndex = 0;this.lyric.some((item) => {if (lyricsIndex < this.lyric.length - 1) {if (currentTime > item[0]) {lyricsIndex += 1;}return currentTime <= item[0];}});// console.log(lyricsIndex);this.lyricsIndex = lyricsIndex;},},watch: {// 監聽當前播放時間"$store.state.currentTime"(currentTime, lastTime) {// 如果兩個時間間隔有1秒,則可得知進度條被拖動 需要重新校準歌詞index// 當歌詞數量大于1并且索引為零時,可能歌詞位置差距較大,走這個if進行快速跳轉if ((lastTime && Math.abs(currentTime - lastTime) >= 1) ||(this.lyricsIndex == 0 && this.lyric.length > 1)) {// 處理播放時間跳轉時歌詞位置的校準if (this.lyric.length > 1) {this.getCurrentLyricsIndex(currentTime);// 滑動到當前歌詞this.lyricScroll(this.lyricsIndex);}}// 根據實時播放時間實現歌詞滾動if (this.lyricsIndex < this.lyric.length) {if (currentTime >= this.lyric[this.lyricsIndex][0]) {this.lyricsIndex += 1;this.lyricScroll(this.lyricsIndex);}}},// 監聽vuex中的musicId 重置歌詞索引"$store.state.musicId"(musicId) {this.lyricsIndex = 0;},lyric(current) {// console.log("獲取了歌詞");// 大于一秒,說明歌詞在1秒后才請求成功 歌詞可能不能馬上跳轉到當前時間 這里進行校準if (this.$store.state.currentTime > 1) {// 處理播放時間跳轉時歌詞位置的校準if (this.lyric.length > 1) {this.getCurrentLyricsIndex(this.$store.state.currentTime);this.$nextTick(() => {// 滑動到當前歌詞this.lyricScroll(this.lyricsIndex);});}}},}, }; ***組件1 <script> import LyricsScroll from './LyricsScroll.js' export default {mixins: [LyricsScroll] } </script> ***組件2 <script> import LyricsScroll from './LyricsScroll.js' export default {mixins: [LyricsScroll] } </script>

后臺工具函數

import { ref, reactive, computed } from "vue" import { toast } from "@/composables/util" // 列表,分頁,搜索,刪除,修改狀態 export function useInitTable(opt = {}) {let searchForm = nulllet resetSearchForm = nullif (opt.searchForm) {searchForm = reactive({ ...opt.searchForm })resetSearchForm = () => {for (const key in opt.searchForm) {searchForm[key] = opt.searchForm[key]}getData()}}const tableData = ref([])const loading = ref(false)// 分頁const currentPage = ref(1)const total = ref(0)const limit = ref(10)// 獲取數據function getData(p = null) {if (typeof p == "number") {currentPage.value = p}loading.value = trueopt.getList(currentPage.value, searchForm).then(res => {if (opt.onGetListSuccess && typeof opt.onGetListSuccess == "function") {opt.onGetListSuccess(res)} else {tableData.value = res.listtotal.value = res.totalCount}}).finally(() => {loading.value = false})}getData()// 刪除const handleDelete = (id) => {loading.value = trueopt.delete(id).then(res => {toast("刪除成功")getData()}).finally(() => {loading.value = false})}// 修改狀態const handleStatusChange = (status, row) => {row.statusLoading = trueopt.updateStatus(row.id, status).then(res => {toast("修改狀態成功")row.status = status}).finally(() => {row.statusLoading = false})}// 多選選中IDconst multiSelectionIds = ref([])const handleSelectionChange = (e) => {multiSelectionIds.value = e.map(o => o.id)}// 批量刪除const multipleTableRef = ref(null)const handleMultiDelete = () => {loading.value = trueopt.delete(multiSelectionIds.value).then(res => {toast("刪除成功")// 清空選中if (multipleTableRef.value) {multipleTableRef.value.clearSelection()}getData()}).finally(() => {loading.value = false})}// 批量修改狀態const handleMultiStatusChange = (status) => {loading.value = trueopt.updateStatus(multiSelectionIds.value,status).then(res => {toast("修改狀態成功")// 清空選中if (multipleTableRef.value) {multipleTableRef.value.clearSelection()}getData()}).finally(() => {loading.value = false})}return {searchForm,resetSearchForm,tableData,loading,currentPage,total,limit,getData,handleDelete,handleStatusChange,handleSelectionChange,multipleTableRef,handleMultiDelete,handleMultiStatusChange,multiSelectionIds} }// 新增,修改 export function useInitForm(opt = {}) {// 表單部分const formDrawerRef = ref(null)const formRef = ref(null)const defaultForm = opt.formconst form = reactive({})const rules = opt.rules || {}const editId = ref(0)const drawerTitle = computed(() => editId.value ? "修改" : "新增")const handleSubmit = () => {formRef.value.validate((valid) => {if (!valid) returnformDrawerRef.value.showLoading()let body = {}if(opt.beforeSubmit && typeof opt.beforeSubmit == "function"){body = opt.beforeSubmit({ ...form })} else {body = form}const fun = editId.value ? opt.update(editId.value, body) : opt.create(body)fun.then(res => {toast(drawerTitle.value + "成功")// 修改刷新當前頁,新增刷新第一頁opt.getData(editId.value ? false : 1)formDrawerRef.value.close()}).finally(() => {formDrawerRef.value.hideLoading()})})}// 重置表單function resetForm(row = false) {if (formRef.value) formRef.value.clearValidate()for (const key in defaultForm) {form[key] = row[key]}}// 新增const handleCreate = () => {editId.value = 0resetForm(defaultForm)formDrawerRef.value.open()}// 編輯const handleEdit = (row) => {editId.value = row.idresetForm(row)formDrawerRef.value.open()}return {formDrawerRef,formRef,form,rules,editId,drawerTitle,handleSubmit,resetForm,handleCreate,handleEdit} } import { ElNotification,ElMessageBox } from 'element-plus' import nprogress from 'nprogress'//提示 export function toast(message,type="success",dangerouslyUseHTMLString=true){ElNotification({message,type,duration:3000,dangerouslyUseHTMLString}) } export function showModal(content = "提示內容",type = "warning",title = ""){return ElMessageBox.confirm(content,title,{confirmButtonText: '確認',cancelButtonText: '取消',type,}) } // 顯示全屏loading export function showFullLoading(){nprogress.start() }// 隱藏全屏loading export function hideFullLoading(){nprogress.done() } // 彈出輸入框 export function showPrompt(tip,value = ""){return ElMessageBox.prompt(tip, '', {confirmButtonText: '確認',cancelButtonText: '取消',inputValue:value}) } // 將query對象轉成url參數 export function queryParams(query){let q = []for (const key in query) {if(query[key]){q.push(`${key}=${encodeURIComponent(query[key])}`)}}// console.log(q)//['limit=10', 'keyword=ceshi']let r = q.join("&")// limit=10&keyword=ceshir = r ? ("?"+r) : ""return r } // 上移 export function useArrayMoveUp(arr,index){swapArray(arr,index,index - 1) }// 下移 export function useArrayMoveDown(arr,index){swapArray(arr,index,index + 1) }function swapArray(arr,index1,index2){arr[index1] = arr.splice(index2,1,arr[index1])[0]return arr }// sku排列算法 export function cartesianProductOf() {return Array.prototype.reduce.call(arguments, function (a, b) {var ret = [];a.forEach(function (a) {b.forEach(function (b) {ret.push(a.concat([b]));});});return ret;}, [[]]); } import {useCookies} from '@vueuse/integrations/useCookies' const TokenKey = "admin-token" const cookie = useCookies() export function getToken(){return cookie.get(TokenKey) } export function setToken(token){return cookie.set(TokenKey,token) } export function removeToken(){return cookie.remove(TokenKey) }

接口工具函數

// 將query對象轉成url參數 export function queryParams(query){let q = []for (const key in query) {if(query[key]){q.push(`${key}=${encodeURIComponent(query[key])}`)}}// console.log(q)//['limit=10', 'keyword=ceshi']let r = q.join("&")// limit=10&keyword=ceshir = r ? ("?"+r) : ""return r }

導出文件

const onSubmit = () => {if (!form.tab) return toast('訂單類型不能為空', 'error')loading.value = truelet starttime = nulllet endtime = nullif (form.time && Array.isArray(form.time)) {starttime = form.time[0]endtime = form.time[1]}exportOrder({tab: form.tab,starttime,endtime}).then(data => {let url = window.URL.createObjectURL(new Blob([data]))//定義一個a標簽let link = document.createElement('a')//隱藏掉a標簽link.style.display = 'none'link.href = url//文件命名let filename = new Date().getTime() + '.xlsx'link.setAttribute('download', filename)document.body.appendChild(link)link.click()close()}).finally(() => {loading.value = false}) }

可選鏈、??

先來看兩個場景: 場景1我需要判斷數組對象中的某個值是否存在進而去做其他事情:let title; if(data&&data.children&&data.children[0]&&data.children[0].title) {title = data.children[0].title } 場景2我需要判斷某個值是否有效進而去做其他事情let isMan,text,person = {name: 'zhangsan',hasCount: 0,isMan: false }; if(person.hasCount || person.hasCount === 0) {text = person.hasCount } else {text = '暫無數據' } 上面兩個場景我在開發中經常用到,后來在公眾號得知js的新語法可選鏈"?."以及雙問號"??"能使這兩個場景操作變得簡單。優化如下//場景1 let title = data?.children?.[0]?.title //場景2 let {hasCount} = person; text = hasCount ?? '暫無數據'//除此之外,"??"還有其他應用場景 let a; a ??= 6; console.log(a); // 6可選鏈的語法允許開發者訪問嵌套得更深的對象屬性,而不用擔心屬性是否真的存在。也就是說,如果可選鏈在挖掘過程遇到了null或undefined的值,就會通過短路(short-circuit)計算,返回undefined,而不會報錯。邏輯空分配運算符僅在空值或未定義(null or undefined)時才將值分配給a

富文本編輯器

第一步:

cnpm i tinymce cnpm i @tinymce/tinymce-vue

第二步:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Qj2DO9yJ-1681570610418)(F:\Roaming\Typora\typora-user-images\image-20230405183528985.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JLusu6CB-1681570610419)(F:\Roaming\Typora\typora-user-images\image-20230405183819682.png)]

第三步:創建editor組件

<template><editor v-model="content" tag-name="div" :init="init" /><ChooseImage :preview="false" ref="ChooseImageRef" :limit="9"/> </template> <script setup> import tinymce from "tinymce/tinymce"; import Editor from "@tinymce/tinymce-vue"; import ChooseImage from "@/components/ChooseImage.vue" import { ref, watch } from "vue" import "tinymce/themes/silver/theme"; // 引用主題文件 import "tinymce/icons/default"; // 引用圖標文件 import 'tinymce/models/dom' // tinymce插件可按自己的需要進行導入 // 更多插件參考:https://www.tiny.cloud/docs/plugins/ import "tinymce/plugins/advlist" import "tinymce/plugins/anchor" import "tinymce/plugins/autolink" import "tinymce/plugins/autoresize" import "tinymce/plugins/autosave" import "tinymce/plugins/charmap" // 特殊字符 import "tinymce/plugins/code" // 查看源碼 import "tinymce/plugins/codesample" // 插入代碼 import "tinymce/plugins/directionality" import "tinymce/plugins/emoticons" import "tinymce/plugins/fullscreen" //全屏 import "tinymce/plugins/help" import "tinymce/plugins/image" // 插入上傳圖片插件 import "tinymce/plugins/importcss" //圖片工具 import "tinymce/plugins/insertdatetime" //時間插入 import "tinymce/plugins/link" import "tinymce/plugins/lists" // 列表插件 import "tinymce/plugins/media" // 插入視頻插件 import "tinymce/plugins/nonbreaking" import "tinymce/plugins/pagebreak" //分頁 import "tinymce/plugins/preview" // 預覽 import "tinymce/plugins/quickbars" import "tinymce/plugins/save" // 保存 import "tinymce/plugins/searchreplace" //查詢替換 import "tinymce/plugins/table" // 插入表格插件 import "tinymce/plugins/template" //插入模板 import "tinymce/plugins/visualblocks" import "tinymce/plugins/visualchars" import "tinymce/plugins/wordcount" // 字數統計插件 // v-model const props = defineProps({modelValue: String, }) const emit = defineEmits(["update:modelValue"]) const ChooseImageRef = ref(null) // 配置 const init = {language_url: '/tinymce/langs/zh-Hans.js', // 中文語言包路徑language: "zh-Hans",skin_url: '/tinymce/skins/ui/oxide', // 編輯器皮膚樣式content_css: "/tinymce/skins/content/default/content.min.css",menubar: false, // 隱藏菜單欄autoresize_bottom_margin: 50,max_height: 500,min_height: 400,// height: 320,toolbar_mode: "none",plugins:'wordcount visualchars visualblocks template searchreplace save quickbars preview pagebreak nonbreaking media insertdatetime importcss image help fullscreen directionality codesample code charmap link code table lists advlist anchor autolink autoresize autosave',toolbar:"formats undo redo fontsizeselect fontselect ltr rtl searchreplace media imageUpload | outdent indent aligncenter alignleft alignright alignjustify lineheight underline quicklink h2 h3 blockquote numlist bullist table removeformat forecolor backcolor bold italic strikethrough hr link preview fullscreen help ",content_style: "p {margin: 5px 0; font-size: 14px}",fontsize_formats: "12px 14px 16px 18px 24px 36px 48px 56px 72px",font_formats:"微軟雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;蘋果蘋方 = PingFang SC, Microsoft YaHei, sans- serif; 宋體 = simsun, serif; 仿宋體 = FangSong, serif; 黑體 = SimHei, sans - serif; Arial = arial, helvetica, sans - serif;Arial Black = arial black, avant garde;Book Antiqua = book antiqua, palatino; ",branding: false,elementpath: false,resize: false, // 禁止改變大小statusbar: false, // 隱藏底部狀態欄setup:(editor)=>{editor.ui.registry.addButton("imageUpload",{tooltip:"插入圖片",icon:"image",onAction(){ChooseImageRef.value.open((data)=>{data.forEach(url=>{editor.insertContent(`<img src="${url}" style="width:100%;"/>`)})})}})} }; tinymce.init; // 初始化 const content = ref(props.modelValue) watch(props, (newVal) => content.value = newVal.modelValue) watch(content, (newVal) => emit("update:modelValue", newVal)) </script> <style> .tox-tinymce-aux {z-index: 9999 !important; } </style>

上傳圖片

***UploadFile.vue <template><!-- uploadImageAction接口地址 --><el-uploaddrag:action="uploadImageAction"multiple:headers="{token}"name="img":data="data":on-success="uploadSuccess":on-error="uploadError"><el-icon class="el-icon--upload"><upload-filled /></el-icon><div class="el-upload__text">在此放置文件或者 <em>點擊上傳</em></div><template #tip><div class="el-upload__tip">大小小于500kb的jpg/png文件</div></template></el-upload> </template> <script setup> import { uploadImageAction } from "@/api/image" import { getToken } from "@/composables/auth" import { toast } from "@/composables/util" const token = getToken()defineProps({data:Object, })const emit = defineEmits(["success"])const uploadSuccess = (response, uploadFile, uploadFiles)=>{emit("success",{response, uploadFile, uploadFiles})toast("上傳成功!") }const uploadError = (error, uploadFile, uploadFiles)=>{let msg = JSON.parse(error.message).msg || "上傳失敗"toast(msg,"error") } </script>***組件中: <el-drawer v-model="drawer" title="上傳圖片"><UploadFile :data="{ image_class_id }" @success="handleUploadSuccess" /> </el-drawer>

自定義指令(用于設置有權限的用戶才能看見的模塊)

***main.js import permission from "@/directives/permission.js" app.use(permission) ***permission.js import store from "@/store" function hasPermission(value,el = false){if(!Array.isArray(value)){throw new Error(`需要配置權限,例如 v-permission="['getStatistics3,GET']"`)}const hasAuth = value.findIndex(v=>store.state.ruleNames.includes(v)) != -1if(el && !hasAuth){el.parentNode && el.parentNode.removeChild(el)}return hasAuth }export default {install(app){app.directive("permission",{mounted(el,binding){hasPermission(binding.value,el)}})} } ***組件中:<el-row :gutter="20" class="mt-5"><el-col :span="12" :offset="0"><IndexChart v-permission="['getStatistics3,GET']" /></el-col><el-col :span="12" :offset="0" v-permission="['getStatistics2,GET']"><IndexCard title="店鋪及商品提示" tip="店鋪及商品提示" :btns="goods" class="mb-3" /><IndexCard title="交易提示" tip="需要立即處理的交易訂單" :btns="order" /></el-col></el-row>

Echarts使用

cnpm i echarts***IndexChart.vue <template><el-card shadow="never"><template #header><div class="flex justify-between"><span class="text-sm">訂單統計</span><div><el-check-tag v-for="(item, index) in options" :key="index" :checked="current == item.value" style="margin-right: 8px" @click="handleChoose(item.value)">{{ item.text }}</el-check-tag></div></div></template><div ref="el" id="chart" style="width: 100%; height: 300px"></div></el-card> </template> <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue' import * as echarts from 'echarts' import { useResizeObserver } from '@vueuse/core'import { getStatistics3 } from '@/api/index.js'const current = ref('week') const options = [{text: '近1個月',value: 'month'},{text: '近1周',value: 'week'},{text: '近24小時',value: 'hour'} ]const handleChoose = type => {current.value = typegetData() }var myChart = null onMounted(() => {var chartDom = document.getElementById('chart')if (chartDom) {myChart = echarts.init(chartDom)getData()} })onBeforeUnmount(() => {if (myChart) echarts.dispose(myChart) })function getData() {let option = {xAxis: {type: 'category',data: []},yAxis: {type: 'value'},series: [{data: [],type: 'bar',showBackground: true,backgroundStyle: {color: 'rgba(180, 180, 180, 0.2)'}}]}myChart.showLoading()getStatistics3(current.value).then(res => {option.xAxis.data = res.xoption.series[0].data = res.ymyChart.setOption(option)}).finally(() => {myChart.hideLoading()}) } //圖標等比例縮小和放大 const el = ref(null) if(myChart){useResizeObserver(el, entries => myChart.resize()) } </script>***index.vue <el-row :gutter="20" class="mt-5"><el-col :span="12" :offset="0"><IndexChart/></el-col> </el-row>

通過gsap庫實現數字滾動變化

cnpm i gsap<template>{{ d.num.toFixed(0) }} </template> <script setup> import { reactive,watch } from "vue" import gsap from "gsap"const props = defineProps({value:{type:Number,default:0} })const d = reactive({num:0 })function AnimateToValue(){gsap.to(d,{duration:0.5,num:props.value}) }AnimateToValue()watch(()=>props.value,()=>AnimateToValue())</script>

vue3暴露給父組件props、方法、事件

import { ref } from "vue"const showDrawer = ref(false)const props = defineProps({title:String,size:{type:String,default:"45%"},destroyOnClose:{type:Boolean,default:false},confirmText:{type:String,default:"提交"}})const loading = ref(false)const showLoading = ()=>loading.value = trueconst hideLoading = ()=>loading.value = false// 打開const open = ()=> showDrawer.value = true// 取消const close = ()=>showDrawer.value = false// 提交,傳事件給父組件const emit = defineEmits(["submit"])const submit = ()=> emit("submit")// 向父組件暴露以下方法defineExpose({open,close,showLoading,hideLoading})

vue3實現全屏顯示

cnpm i @vueuse/core import { useFullscreen } from '@vueuse/core' const {// 是否全屏狀態isFullscreen,// 切換全屏toggle } = useFullscreen() ***組件中 <el-icon class="icon-btn" @click="toggle"><full-screen v-if="!isFullscreen" /><aim v-else /> </el-icon>

進度條nprogress實現

cnpm i nprogress ***main.js import "nprogress/nprogress.css"

actions中的寫法

法一: async deleteCartListBySkuId({ commit }, skuId) {let result = await reqDeleteCartById(skuId)if (result.code == 200) {return 'ok '} else {return Promise.reject(new Error('faile'))} }, 法二: getInfo({commit}){return new Promise((resolve,reject)=>{getInfo().then(res=>{commit("SET_USERINFO",res)resolve(res)}).catch(err=>reject(err))}) }

通過VueUse使用cookie,封裝token

法一: cnpm i @vueuse/integrations cnpm i universal-cookieimport {useCookies} from '@vueuse/integrations/useCookies' const TokenKey = "admin-token" const cookie = useCookies() export function getToken(){return cookie.get(TokenKey) } export function setToken(token){return cookie.set(TokenKey,token) } export function removeToken(){return cookie.remove(TokenKey) } 法二: //對外暴露一個函數 //存儲token export const setToken = token => {localStorage.setItem('TOKEN', token) } //獲取token export const getToken = () => {return localStorage.getItem('TOKEN') } //清除本地token export const removeToken = () => {localStorage.removeItem('TOKEN') }

vite配置@地址、windi css、跨域

import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import WindiCSS from 'vite-plugin-windicss' import path from "path" // https://vitejs.dev/config/ export default defineConfig({resolve:{alias:{"@":path.resolve(__dirname,"src")}},server:{proxy:{'/api': {target: 'http://ceshi13.dishait.cn',changeOrigin: true,rewrite: (path) => path.replace(/^\/api/, '')},}},plugins: [vue(),WindiCSS()], })

JS高級

this指向(new綁定)

/*1.創建新的空對象2.將this指向這個空對象3.執行函數體中的代碼4.沒有顯示返回非空對象時, 默認返回這個對象*/function foo() {this.name = "why"console.log("foo函數:", this)}new foo()

this指向(顯示綁定)

// 顯式綁定var obj = {name: "why"}function foo() {console.log("foo函數:", this)}// 執行函數, 并且函數中的this指向obj對象// obj.foo = foo// obj.foo()// 執行函數, 并且強制this就是obj對象foo.call(obj)foo.call(123)foo.call("abc")

split

例子 1

在本例中,我們將按照不同的方式來分割字符串:

<script type="text/javascript">var str="How are you doing today?"document.write(str.split(" ") + "<br />") document.write(str.split("") + "<br />") document.write(str.split(" ",3))</script>

輸出:

How,are,you,doing,today? H,o,w, ,a,r,e, ,y,o,u, ,d,o,i,n,g, ,t,o,d,a,y,? How,are,you

例子 2

在本例中,我們將分割結構更為復雜的字符串:

"2:3:4:5".split(":") //將返回["2", "3", "4", "5"] "|a|b|c".split("|") //將返回["", "a", "b", "c"]

slice 從索引start開始(包括start)至end(不包括end)

例子 1

在本例中,我們將提取從位置 6 開始的所有字符:

<script type="text/javascript">var str="Hello happy world!" document.write(str.slice(6))</script>

輸出:

happy world!

例子 2

在本例中,我們將提取從位置 6 到位置 11 的所有字符:

<script type="text/javascript">var str="Hello happy world!" document.write(str.slice(6,11))</script>

輸出:

happy

substr

例子 1

在本例中,我們將使用 substr() 從字符串中提取一些字符:

<script type="text/javascript">var str="Hello world!" document.write(str.substr(3))</script>

輸出:

lo world!

例子 2

在本例中,我們將使用 substr() 從字符串中提取一些字符:

<script type="text/javascript">var str="Hello world!" document.write(str.substr(3,7))</script>

輸出:

lo worl

小程序筆記

后臺播放

const audioContext = wx.getBackgroundAudioManager() //需要設置后臺播放的歌名 audioContext.title = res.songs[0].name

順序播放、隨機播放、單曲循環

changeNewMusicAction(ctx, isNext = true) {// 1.獲取當前索引let index = ctx.playListIndex// 2.根據不同的播放模式, 獲取下一首歌的索引switch(ctx.playModeIndex) {case 0: // 順序播放index = isNext ? index + 1: index -1if (index === -1) index = ctx.playListSongs.length - 1if (index === ctx.playListSongs.length) index = 0breakcase 1: // 單曲循環breakcase 2: // 隨機播放index = Math.floor(Math.random() * ctx.playListSongs.length)break}console.log(index)// 3.獲取歌曲let currentSong = ctx.playListSongs[index]if (!currentSong) {currentSong = ctx.currentSong} else {// 記錄最新的索引ctx.playListIndex = index}// 4.播放新的歌曲this.dispatch("playMusicWithSongIdAction", { id: currentSong.id, isRefresh: true })}

var,let,const三者的特點和區別

https://blog.csdn.net/xiewenhui111/article/details/113133330

歌詞滾動

<swiper-item class="lyric"><scroll-view class="lyric-list" scroll-y scroll-top="{{lyricScrollTop}}" scroll-with-animation><block wx:for="{{lyricInfos}}" wx:key="index"><view class="item {{currentLyricIndex === index ? 'active': ''}}" style="padding-top: {{index === 0 ? (contentHeight/2-80): 0}}px; padding-bottom: {{index === lyricInfos.length - 1 ? (contentHeight/2+80): 0}}px;">{{item.text}}</view></block></scroll-view> </swiper-item>

目標歌詞展示

//獲取當前時間const currentTime = audioContext.currentTime * 1000//根據當前時間修改currentTime/sliderValueif(!this.data.isSliderChanging){ const sliderValue = currentTime / this.data.durationTime * 100this.setData({sliderValue,currentTime})}//根據當前時間去查找播放的歌詞let i = 0for (; i < this.data.lyricInfos.length; i++) {const lyricInfo = this.data.lyricInfos[i]if (currentTime < lyricInfo.time) {// 設置當前歌詞的索引和內容//此處i為上面循環結束后拿到的后一句的i,要i-1才是當前的const currentIndex = i - 1if (this.data.currentLyricIndex !== currentIndex) {const currentLyricInfo = this.data.lyricInfos[currentIndex]console.log(currentLyricInfo.text);this.setData({ currentLyricText: currentLyricInfo.text, currentLyricIndex: currentIndex })}break}}

歌詞轉換

// 正則(regular)表達式(expression): 字符串匹配利器// [00:58.65] \是對[]和.轉義 const timeRegExp = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/export function parseLyric(lyricString) {const lyricStrings = lyricString.split("\n")const lyricInfos = []for (const lineString of lyricStrings) {// [00:58.65]他們說 要縫好你的傷 沒有人愛小丑const timeResult = timeRegExp.exec(lineString)if (!timeResult) continue// 1.獲取時間const minute = timeResult[1] * 60 * 1000const second = timeResult[2] * 1000const millsecondTime = timeResult[3]const millsecond = millsecondTime.length === 2 ? millsecondTime * 10: millsecondTime * 1const time = minute + second + millsecond// 2.獲取歌詞文const text = lineString.replace(timeRegExp, "")lyricInfos.push({ time, text })}return lyricInfos }

音樂播放器

// pages/music-player/index.js import {getSongDetail,getSongLyric} from '../../services/api_player' import {audioContext} from '../../store/player-store' import {parseLyric} from '../../utils/parse-lyric' Page({data: {id:0,currentSong:{},currentPage:0,contentHeight:0,//顯示歌詞isMusicLyric:true,//總時長durationTime:0,//當前時間currentTime:0,//滑動到的時間(百分比)sliderValue:0,//是否在滑動isSliderChanging:false,//歌詞lyricInfos:[],//當前播放歌詞currentLyricText:"",//當前播放歌詞索引currentLyricIndex:0,//要滾動的距離lyricScrollTop:0},onLoad(options) {const id = options.idthis.setData({id})this.getPageData(id)//動態計算高度,寬度const screenHeight = getApp().globalData.screenHeightconst statusBarHeight = getApp().globalData.statusBarHeightconst navBarHeight = getApp().globalData.navBarHeightconst contentHeight = screenHeight - statusBarHeight - navBarHeightconst deviceRadio = getApp().globalData.deviceRadiothis.setData({contentHeight,isMusicLyric:deviceRadio >= 2})//創建播放器audioContext.stop()audioContext.src = `https://music.163.com/song/media/outer/url?id=${id}.mp3`audioContext.autoplay = true//audioContext事件監聽this.setupAudioContextListener()}, // ======================== 網絡請求 ======================== getPageData(id){getSongDetail(id).then(res => {this.setData({currentSong:res.songs[0],durationTime: res.songs[0].dt})})getSongLyric(id).then(res => {const lyricString = res.lrc.lyricconst lyric = parseLyric(lyricString)this.setData({lyricInfos:lyric})})}, // ======================== 事件處理 ======================== handleSwiperChange(event){const current = event.detail.currentthis.setData({currentPage:current})},handleSliderChange(event){// 1.獲取slider變化值(百分比)const value = event.detail.value// 2.計算需要播放的currentTimeconst currentTime = this.data.durationTime * value / 100// 3.設置context播放currentTime位置的音樂audioContext.pause()audioContext.seek(currentTime / 1000)// 4.記錄最新的sliderValue, 并且需要講isSliderChaning設置回falsethis.setData({ sliderValue: value, isSliderChanging: false })},handleSliderChanging(event){const value = event.detail.valueconst currentTime = this.data.durationTime * value / 100this.setData({ isSliderChanging: true, currentTime})},//事件監聽setupAudioContextListener(){audioContext.onCanplay(() => {audioContext.play()})audioContext.onTimeUpdate(() => {//獲取當前時間const currentTime = audioContext.currentTime * 1000//根據當前時間修改currentTime/sliderValueif(!this.data.isSliderChanging){ const sliderValue = currentTime / this.data.durationTime * 100this.setData({sliderValue,currentTime})}//根據當前時間去查找播放的歌詞let i = 0for (; i < this.data.lyricInfos.length; i++) {const lyricInfo = this.data.lyricInfos[i]if (currentTime < lyricInfo.time) {// 設置當前歌詞的索引和內容//此處i為上面循環結束后拿到的后一句的i,要i-1才是當前的const currentIndex = i - 1if (this.data.currentLyricIndex !== currentIndex) {const currentLyricInfo = this.data.lyricInfos[currentIndex]this.setData({ currentLyricText: currentLyricInfo.text, currentLyricIndex: currentIndex ,lyricScrollTop:currentIndex * 35})}break}}})} })

image mode的屬性

mode 有效值:mode 有 13 種模式,其中 4 種是縮放模式,9 種是裁剪模式。模式 值 說明 縮放 scaleToFill 不保持縱橫比縮放圖片,使圖片的寬高完全拉伸至填滿 image 元素 縮放 aspectFit 保持縱橫比縮放圖片,使圖片的長邊能完全顯示出來。也就是說,可以完整地將圖片顯示出來。 縮放 aspectFill 保持縱橫比縮放圖片,只保證圖片的短邊能完全顯示出來。也就是說,圖片通常只在水平或垂直方向是完整的,另一個方向將會發生截取。 縮放 widthFix 寬度不變,高度自動變化,保持原圖寬高比不變 裁剪 top 不縮放圖片,只顯示圖片的頂部區域 裁剪 bottom 不縮放圖片,只顯示圖片的底部區域 裁剪 center 不縮放圖片,只顯示圖片的中間區域 裁剪 left 不縮放圖片,只顯示圖片的左邊區域 裁剪 right 不縮放圖片,只顯示圖片的右邊區域 裁剪 top left 不縮放圖片,只顯示圖片的左上邊區域 裁剪 top right 不縮放圖片,只顯示圖片的右上邊區域 裁剪 bottom left 不縮放圖片,只顯示圖片的左下邊區域 裁剪 bottom right 不縮放圖片,只顯示圖片的右下邊區域

使用多個插槽,要設置

//想使用多個插槽,要設置 Component({options: {multipleSlots: true}, })

搜索關鍵字的高亮以及rich-text的使用

****string2nodes.jsexport default function stringToNodes(keyword, value) {const nodes = []if (keyword.toUpperCase().startsWith(value.toUpperCase())) {const key1 = keyword.slice(0, value.length)const node1 = {name: "span",attrs: { style: "color: #26ce8a; font-size: 14px;" },children: [ { type: "text", text: key1 } ]}nodes.push(node1)const key2 = keyword.slice(value.length)const node2 = {name: "span",attrs: { style: "color: #000000; font-size: 14px;" },children: [ { type: "text", text: key2 } ]}nodes.push(node2)} else {const node = {name: "span",attrs: { style: "color: #000000; font-size: 14px;" },children: [ { type: "text", text: keyword } ]} nodes.push(node)}return nodes}****組件內import stringToNodes from '../../utils/string2nodes'handleSearch(event){//獲取輸入的關鍵字const searchValue = event.detailthis.setData({searchValue})if (!searchValue.length){this.setData({suggestSongs:[]})return} debounceGetSearchSuggest(searchValue).then(res => {const suggestSongs = res.result.allMatch || []this.setData({suggestSongs})// 轉成nodes節點const suggestKeywords = suggestSongs.map(item => item.keyword)const suggestSongsNodes = []for( const item of suggestKeywords){const nodes = stringToNodes(item,searchValue)suggestSongsNodes.push(nodes)}this.setData({suggestSongsNodes})})}

防抖使用(搜索框)

**組件內 import debounce from '../../utils/debounce' const debounceGetSearchSuggest = debounce(getSearchSuggest,600)debounceGetSearchSuggest(searchValue).then(res => {this.setData({suggestSongs:res.result.allMatch}) })**debounce.js export default function debounce(fn, delay = 500, immediate = false, resultCallback) {// 1.定義一個定時器, 保存上一次的定時器let timer = nulllet isInvoke = false// 2.真正執行的函數//...args是searchValue,輸入的文字const _debounce = function(...args) {return new Promise((resolve, reject) => {// 取消上一次的定時器if (timer) clearTimeout(timer)// 判斷是否需要立即執行if (immediate && !isInvoke) {const result = fn.apply(this, args)if (resultCallback) resultCallback(result)resolve(result)isInvoke = true} else {// 延遲執行timer = setTimeout(() => {// 外部傳入的真正要執行的函數const result = fn.apply(this, args)if (resultCallback) resultCallback(result)resolve(result)isInvoke = falsetimer = null}, delay)}})}// 封裝取消功能_debounce.cancel = function() {console.log(timer)if (timer) clearTimeout(timer)timer = nullisInvoke = false}return _debounce}

export和export default的區別

export和export default的區別 - 知乎 (zhihu.com)

data-使用以及動態key數組

<block><ranking-area-item item="{{originalRankingsongs}}" bindtap="handleMoreClickBtn" data-id="1"></ranking-area-item><ranking-area-item item="{{newRankingsongs}}" bindtap="handleMoreClickBtn" data-id="2"></ranking-area-item><ranking-area-item item="{{soarRankingsongs}}" bindtap="handleMoreClickBtn" data-id="3"></ranking-area-item></block>**data-id id為自定義名稱,為下面事件event參數中添加一個id屬性,rankingMap[id]動態id獲取rankingMap數組中的value//排行榜點擊事件handleMoreClickBtn(event){const rankingMap = {1:"originalRanking",2:"newRanking",3:"soarRanking"}const id = event.currentTarget.dataset.idconst rankingName = rankingMap[id]this.navigateToDeatail(rankingName)}

引入hy-event-store,達到vuex效果

cnpm i hy-event-store**index.js import {rankingStore} from './ranking-store' export {rankingStore}**ranking-store.js import {HYEventStore } from 'hy-event-store' import {getRankings } from '../services/api_music' const rankingStore = new HYEventStore({state: {hotRanking: {}},actions: {getRankingDataAction(ctx) {getRankings(3778678).then((res) => {console.log(res);ctx.hotRanking = res.playlist})}} }) export {rankingStore }**組件 import {rankingStore} from '../../store/index'onLoad(options) {this.getPageData()// 獲取推薦音樂數據rankingStore.dispatch('getRankingDataAction')// 從store中獲取數據rankingStore.onState("hotRanking",(res) =>{//剛開始的hotRanking為空對象if(!res.tracks) returnconst recommendSongs = res.tracks.slice(0,7)this.setData({recommendSongs})})},

小程序解決插槽動態顯示方案

**header.wxss.header .slot:empty + .default {display: flex;}.header .default {display: none;align-items: center;font-size: 28rpx;color: #777;}**header.wxml<!--components/area-header/index.wxml--> <view class="header"><view class="title">{{title}}</view><view class="right" wx:if="{{showRight}}" bindtap="handleRightClick"><view class="slot"><slot></slot></view><view class="default"><text>{{rightText}}</text><image class="icon" src="/assets/images/icons/arrow-right.png"></image></view></view> </view> **home.wxml<!-- 推薦歌曲 --> <view class="recommend-song"><header title="推薦歌曲"></header> </view>

節流(規定時間內只能調用一次,普攻)

**throttle.jsexport default function throttle(fn, interval = 1000, options = { leading: true, trailing: false }) {// 1.記錄上一次的開始時間const { leading, trailing, resultCallback } = optionslet lastTime = 0let timer = null// 2.事件觸發時, 真正執行的函數const _throttle = function(...args) {return new Promise((resolve, reject) => {? // 2.1.獲取當前事件觸發時的時間? const nowTime = new Date().getTime()? if (!lastTime && !leading) lastTime = nowTime? // 2.2.使用當前觸發的時間和之前的時間間隔以及上一次開始的時間, 計算出還剩余多長事件需要去觸發函數? const remainTime = interval - (nowTime - lastTime)? if (remainTime <= 0) {? if (timer) {? clearTimeout(timer)? timer = null? }? // 2.3.真正觸發函數? const result = fn.apply(this, args)? if (resultCallback) resultCallback(result)? resolve(result)? // 2.4.保留上次觸發的時間? lastTime = nowTime? return? }? if (trailing && !timer) {? timer = setTimeout(() => {? timer = null? lastTime = !leading ? 0: new Date().getTime()? const result = fn.apply(this, args)? if (resultCallback) resultCallback(result)? resolve(result)? }, remainTime)? }})}_throttle.cancel = function() {if(timer) clearTimeout(timer)timer = nulllastTime = 0}return _throttle}組件引用:import throttle from '../../utils/throttle' const throttleQueryRect = throttle(queryRect)//動態計算swiper高度,防止手機不同樣式不同handleSwiperHeight() {throttleQueryRect(".swiper-image").then(res =>{const rect = res[0]this.setData({swiperHeight:rect.height})}) }

導入vant weapp

npm init -y cnpm i @vant/weapp@1.3.3 -S--production 將 app.json 中的 "style": "v2" 去除,小程序的新版基礎組件強行加上了許多樣式,難以覆蓋,不關閉將造成部分組件樣式混亂。 {"usingComponents": {"van-search": "@vant/weapp/search/index"} }打開微信開發者工具,點擊 工具 -> 構建 npm,并勾選 使用 npm 模塊 選項,構建完成后,即可引入組件。

動態計算swiper高度,防止手機不同樣式不同

<swiper-item class="swiper-item"><image src="{{item.pic}}" mode="widthFix" class="swiper-image" bindload="handleSwiperHeight"/> </swiper-item>swiperHeight:0handleSwiperHeight() {? //獲取圖片的高度? const query = wx.createSelectorQuery()? query.select('.swiper-image').boundingClientRect()? query.exec((res) =>{? const rect = res[0]? this.setData({swiperHeight:rect.height})? })}

(.wxs)日期和數量格式化

function formatCount(count) {var counter = parseInt(count)if (counter>100000000) {return (counter/100000000).toFixed(1) + '億'}else if (counter>10000) {return (counter/10000).toFixed(1) + '萬'}else{return counter + ''} } function addZero(time) {time = time + ''return ('00' + time).slice(time.length) } function formatDuration(time) {time = time/1000var minut = Math.floor(time/60)var second = Math.floor(time) % 60return addZero(minut) + ':' + addZero(second) } // commonjs module .exports={formatCount:formatCount,formatDuration:formatDuration } <wxs src="../../utils/format.wxs" module="format"/><view class="count"> ? {{format.formatCount(item.playCount)}} </view>

data-xxx的使用

data-xx 的作用是在事件中可以獲取這些自定義的節點數據,用于事件的邏輯處理比如 寫一個list列表 想知道點擊的list列表的那一個item ,比如獲取點擊的圖片,等等使用data-xx 需要注意的 xx 是自己取的名字, 后面跟著的渲染一定要是使用的值,否則無效比如點擊的是list 后面跟的是list的值,如果是圖片后面就要是圖片url的地址, ———————————————— 版權聲明:本文為CSDN博主「胡小牧」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。 原文鏈接:https://blog.csdn.net/qq_33210042/article/details/91983464<view wx:for="{{topMvs}}" wx:key="id" class="item"><video-item-v1 item='{{item}}' bindtap="VideoItemBtn" data-item="{{item}}"></video-item-v1> </view>VideoItemBtn(event){const id = event.currentTarget.dataset.item.id//頁面跳轉wx.navigateTo({url: `/pages/detail-video/index?id=${id}`,})}

總結

以上是生活随笔為你收集整理的前端自学Vue笔记干货(第一版,持续更新中~~~)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。