关于vue3.x的一些体验

computed&watch

2.x和3.x的用法比较

kotlin 复制代码
### // 选项式api 用法
export default {
		name:"argeement-dialog",
		props:{
			top:{
				value:String,
				default:() => '50%' // 距离顶部   %/px
			},
		},
		data() {
			return {
				showDialog: false, //隐私弹窗
				Interval: null,
				countDown: 5, //弹窗倒计时
				envConfig: this.$config,
				agree: false, //勾选协议
				isChoice: true //更新时是否去掉勾选
			};
		},
		computed:{
			currentPricacy () {
				const currentPricacy = this.$store.state.privacy.currentPricacy
				console.log("currentPricacy:>>>>>",currentPricacy)
				return currentPricacy
			},
		},
    // 监听从缓存获取的数据变化
		watch: {
			'currentPricacy.isMatch': {
				handler: function(newVal,oldVal) {
					console.log(newVal,oldVal)
					if (newVal == 0) {
						 uni.hideTabBar()
						 this.showDialog = true
						 this.startCountDown()
						 // this.isChoice= false
						 console.log('数据变化了啊',oldVal)
						 if(this.updatePricacy && !this.updateDataPricacyVersion) { //隐私协议更新时且数据协议未更新时,去掉勾选
							 this.isChoice= false
						 } else{
							 this.isChoice= true
						 }
					} else if (newVal == 1){
						uni.showTabBar()
						this.showDialog = false
						this.isChoice= true 
					}
				},
				immediate: true
			},
   }
  }

升级过渡用法

xml 复制代码
<script type="text/javascript">
export default{
	setup(){

		let msg = ref('这是一个数据'); //响应式基本数据类型
		let str = ref('这是str');
		let obj = reactive({
			a:1,
			arr:['a','b','c']
		})

		watchEffect(()=>{
			console.log(  msg.value  )
		})

		watch( ()=>obj.arr , (newVal,oldVal)=>{
			console.log( newVal,oldVal )
		})

		watch( msg , (newVal,oldVal)=>{
			console.log(  newVal,oldVal )
		},{
			immediate:true
		})

		watch( str , (newVal,oldVal)=>{
			console.log(  newVal,oldVal )
		},{
			immediate:true
		})

		watch([msg,str],(newVal,oldVal)=>{
			console.log(  newVal,oldVal )
		},{
			immediate:true
		})

		return {
			msg,
			str,
			obj
		}
	}
}

</script>

组合式api 用法

xml 复制代码
<script setup>
  let msg = ref('这是数据');
  let str = ref('数据2');
  let obj = reactive({
  	a:1,
  	arr:['a','b','c']
  })
  const  currentPricacy = computed(() => {
    let currentPricacy = JSON.parse(window.localStorage.getItem('currentPricacy'))
    return currentPricacy
  })
  watch( currentPricacy, (newVal,oldVal)=>{
  	console.log(  33333,newVal)
  	if (newVal.isMatch == 0) {
  		alert('数据改变了',newVal.isMatch)
  	} else if (newVal.isMatch == 1){
  		console.log('数据未改变',newVal.isMatch)
  	}
  		
  },{
  	immediate:true
  })
</script>

小结:

vue2.x :

javascript 复制代码
watch:{
      obj:{
        handler function(newVal , oldVal){
          console.log( newVal , oldVal  )
        },
        immediate:true,
        // deep:true
      }
    }

vue3.x :

javascript 复制代码
1> 监听数据数据「初始化监听」
      	//
				watch( msg , (newVal,oldVal)=>{
					console.log(  newVal,oldVal )
				},{
					immediate:true
				})

2> 监听多个数据「一起监听」

				watch([msg,str],(newVal,oldVal)=>{
					console.log(  newVal,oldVal )
				},{
					immediate:true
				})

3> 监听"对象"中某个对象

				watch( ()=>obj.arr , (newVal,oldVal)=>{
					console.log( newVal,oldVal )
				})

4> 立即执行监听函数

				watchEffect(()=>{
					console.log(  msg.value  )
				})

参考链接:v3.cn.vuejs.org/api/compute...

provid&inject

2.0文档:cn.vuejs.org/api/reactiv...

kotlin 复制代码
// provid&inject&Mixins一起使用
import mixins from './mixins/parentMixins'

export default {
  data () {
    return {
      defaultData: {
        searchSeo: {
          keyword: '',
          inKeyword: '',
          outTitle: '',
          outKeyword: '',
          outDesc: '',
          outSeo: '',
          pictureLabel: ''
        },
        productDesc: {
          productName: ''
        }
      },
      isEdit: false,
      prevTabIdx: '', // 從哪一個tab跳轉過來的 選填項
      activeName: '1', // 當前所處選項卡位置
      isGroup: false, // 是否是組合產品
      isSaving: false, // 是否處於保存狀態
      activeTab: [{ active: true }, { active: true }, { active: true }, { active: false }, { active: false }, { active: false }, { active: false }, { active: false }, { active: false }], // tab嬾加載處理
      steps: [{ required: false }, { required: false }, { required: true }, { required: true }, { required: true }, { required: true }, { required: true }] // 決定是否必填
    }
  },
  mixins: [mixins],
  //provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。
  provide () {
    return {
      addProduct: this
    }
  },
  mounted () {
    // 修改頁面標題
    if (this.$route.params.type === '2') {
      this.isGroup = true
      this.$store.dispatch('editVisitedViews', { name: '新增組合產品', path: this.$route.fullPath })
    }
    this.$store.commit('ADDRELEASEPRODUCTBASEINFO')
  },
  methods: {
    productDecCallback (data) {
      this.bindDescAndSeoData()
    },
    changeTable () {
      this.bindDescAndSeoData()
    }
  }
}
</script>

批量子组件

javascript 复制代码
export default {
  data() {
    const validateCode = (rule, val, callback) => {
      if (val === '' || val == null) {
        callback(new Error('產品編號不能為空!'))
      }
      if (val.length < 6) {
        callback(new Error('產品編號最少輸入6位!'))
      }
      if (val.length > 10) {
        callback(new Error('產品編號最多隻能輸入10位!'))
      }
      callback()
    }
    // 中文名稱校驗
    const validateChinese = (rule, value, callback) => {
      if (value === '' || value == null) {
        callback(new Error('中文名稱不能為空!'))
      } else {
        /* eslint no-control-regex: off */
        if (value.replace(/[^\u0000-\u00ff]/g, 'aa').length > 200) {
          // 正則表達式最多支持80英文字母或40漢字
          callback(new Error('中文名稱不能超過200個字符!'))
        } else {
          callback()
        }
      }
    }
    // 外包裝材料
    const validatePackingMaterial = (rule, value, callback) => {
      if (value === '' || value == null) {
        callback()
      } else if (value.replace(/[^\u0000-\u00ff]/g, 'aa').length > 100) {
        /* eslint no-control-regex: off */
        // 正則表達式最多支持80英文字母或40漢字
        callback(new Error('外包裝材料不能超過100個字符!'))
      } else {
        callback()
      }
    }
    return {
      disabledNum: false,
      productCodeLabel: '產品編號',
      baseInfo: {
        id: '',
        type: '2',
        isHasWarehouse: 1, // 是否有实物库存
        code: '', // 产品编号 必填
        itemType: 1, // 产品分类 必填
        name: '', // 产品中文名称 必填
        alias: '', // 产品英文名称 必填
        stockUnit: '', // 计量单位
        selfPick: 1, // 是否可以自提
        packingMaterial: '', // 外包装材料
        length: undefined, // 包装尺寸 - 长
        width: undefined, // 包装尺寸 - 宽
        height: undefined, // 包装尺寸 - 高
        volume: undefined, // 体积
        weight: undefined, // 产品质量
        isPresell: 0, // 是否支持預售
        alertStock: undefined, // 警戒库存
        isDirectSale: 1, // 是否直销
        // isNeedFreight: 1, // 是否需要運費
        validDate: '',
        minBox: '', // Min Box 必填
        freeFreightChannel: [],
        excludeFreightChannel: [],
        taxRate: '', // 稅率 必填
        kit: '', // kit 必填
        isInvoice: 0, // 是否開局發票
        whetherSupermarket: 1, // 是否支持超商取貨
        isPeriod: 0, // 是否支持分期
        // isFreightFreeCalc: 0, // 是否免运费
        isDisplayCatalog: 1, // 是否类目显示
        allowMix: 1, // 是否允许混单
        isShipment: 1, // 是否出货
        shipmentScan: 1, // 出货扫码
        returnScan: 0, // 退货扫码
        tagLabel: '', // tag小标签
        plpTag: '', // PLP小標籤
        tagColor: 1, // tag底框颜色1:紫色2:黑色
        suitCompos: [{ label: '套裝組成:', value: '' }], // 因为后端数据结构设计,套装组成和使用介绍不存入baseInfo对象中
        useIntrs: [{ label: '使用介紹:', value: '' }]
      },
      isCheckedCode: false,
      /* 表單校驗規則 */
      rules: {
        length: [{ required: true, message: '請輸入數字!', trigger: 'blur' }],
        width: [{ required: true, message: '請輸入數字!', trigger: 'blur' }],
        height: [{ required: true, message: '請輸入數字!', trigger: 'blur' }],
        weight: [{ required: true, message: '請輸入數字!', trigger: 'blur' }],
        alertStock: [{ required: true, message: '請輸入數字!', trigger: 'blur' }],
        code: [{ required: true, validator: validateCode, trigger: 'blur' }],
        itemType: [{ required: true, message: '請選擇產品分類', trigger: 'change' }],
        type: [{ required: true, message: '請選擇組合產品類型', trigger: 'change' }],
        name: [{ required: true, validator: validateChinese, trigger: 'change' }],
        packingMaterial: [{ validator: validatePackingMaterial, trigger: 'change' }],
        minBox: [{ required: true, message: '請輸入minBox', trigger: 'change' }],
        taxRate: [{ required: true, message: '請選擇稅率', trigger: 'change' }],
        kit: [{ required: true, message: '請選擇 kit 類型', trigger: 'change' }],
        isFreightFreeCalc: [{ required: true, message: '請選擇是否參與免運費計算', trigger: 'change' }],
        isInvoice: [{ required: true, message: '請選擇是否開具發票', trigger: 'change' }],
        isPeriod: [{ required: true, message: '請選擇是否支持分期', trigger: 'change' }],
        tagLabel: [{ max: 10, message: '長度限制為10個字符', trigger: 'blur' }],
        plpTag: [{ max: 10, message: '長度限制為10個字符', trigger: 'blur' }]
      },
      /* 類型列表 */
      typeList: [
        { value: 1, label: 'Nuskin' },
        { value: 2, label: 'Scion' },
        { value: 3, label: 'Her Shades' },
        { value: 4, label: 'Pharmanex' },
        { value: 5, label: 'Miscellanea' }
      ],
      /* 單位列表 */
      unitList: [
        { value: 1, label: '只' },
        { value: 2, label: '袋' },
        { value: 3, label: '件' },
        { value: 4, label: '套' },
        { value: 5, label: '個' },
        { value: 6, label: '包' },
        { value: 7, label: '盒' },
        { value: 8, label: '箱' }
      ],
      /* 稅率列表 */
      taxRateList: [
        { value: 0.05, label: '應稅(5%)' },
        { value: 0, label: '零稅' }
      ],
      /* kit 列表 */
      kitList: [
        { value: 1, label: '單品' },
        { value: 2, label: '一般Kit' },
        { value: 3, label: 'Crop Kit' }
      ],
      freeChannelList: [...freeChannelMap.keys()].map(item => ({
        label: freeChannelMap.get(item) + freeChannelExtra,
        value: item
      })),
      moneyChannelList: [...moneyChannelMap.keys()].map(item => ({
        label: moneyChannelMap.get(item) + moneyChannelExtra,
        value: item
      }))
    }
  },
  inject: ['addProduct'], //注入
  mixins: [mixins],
  watch: {
    defaultData(val) {
      val.itemType = val.itemType - 0
      try {
        val.freeFreightChannel = val.freeFreightChannel ? val.freeFreightChannel.split(',') : []
        val.excludeFreightChannel = val.excludeFreightChannel ? val.excludeFreightChannel.split(',') : []
        val.freeFreightChannel = val.freeFreightChannel.map(item => Number(item))
        val.excludeFreightChannel = val.excludeFreightChannel.map(item => Number(item))
      } catch (error) {
        val.freeFreightChannel = []
        val.excludeFreightChannel = []
      }
      this.baseInfo = Object.assign({}, this.baseInfo, val)
      this.commit('RELEASEPRODUCTBASEINFO', val)
    }
  },
  created() {
    if (this.addProduct.isEdit) {
      this.disabledNum = true
    }
    this.type = this.$route.params.type
  },
  methods: {
    addUserIntr() {
      this.baseInfo.useIntrs.push({ value: '' })
    },
    addCompos() {
      this.baseInfo.suitCompos.push({ value: '' })
    },
    // 刪除產品介紹
    deleteUserIntr(i) {
      this.$confirm(`確認要刪除嗎?`, '提示', {
        confirmButtonText: '確認',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(() => {
          this.baseInfo.useIntrs.splice(i, 1)
        })
        .catch(() => {})
    },
    // 刪除套裝組成
    deleteCompos(i) {
      this.$confirm(`確認要刪除嗎?`, '提示', {
        confirmButtonText: '確認',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(() => {
          this.baseInfo.suitCompos.splice(i, 1)
        })
        .catch(() => {})
    },
    // 是否有實物庫
    checkedInvoiceHandle(boo) {
      if (boo) {
        this.productCodeLabel = '產品編號'
      } else {
        this.productCodeLabel = '產品虛擬編號'
      }
    },
    // 校驗編號
    validateProductCode(e) {
      let val = e.replace(/\D/g, '')
      this.baseInfo.code = val
      this.$refs.code.currentValue = val
      this.$refs.productInfoRule.validateField('code')
    },
    // 後台校驗編號的重復性
    checkedCode() {
      if (!this.baseInfo.code) {
        return
      }
      releaseProduct.getUniqueCode({ code: this.baseInfo.code }).then(res => {
        if (!res.data.data) {
          this.$message({
            message: '編號重複',
            type: 'warning'
          })
          this.baseInfo.code = ''
        }
      })
    },
    // 提交數據進入store
    submit() {
      let obj = Object.assign({}, this.baseInfo)
      for (let k in obj) {
        if (obj[k] == null) {
          obj[k] = ''
        }
      }
      obj.freeFreightChannel = obj.freeFreightChannel.join(',')
      obj.excludeFreightChannel = obj.excludeFreightChannel.join(',')
      // 產品類型(1 普通產品 2 組合產品 3 智芯產品)
      // 這里先寫死
      if (!this.addProduct.isGroup) {
        obj.type = '1'
        obj.typeName = '普通產品'
      }
      // 我們不能把baseItem中的其他tab添加的字段刪除掉
      let baseItem = this.$store.state.releaseProduct.productInfo.baseItem
      if (this.addProduct.isEdit) {
        let storeBaseItem = this.$store.state.editReleaseProduct.productInfo.baseItem
        if (storeBaseItem.id) {
          obj.id = storeBaseItem.id
        }
        // 因為產品價格tab雖然將價格置入store,但是並沒有更改基本信息中的retailPrice,所以需要在切換
        // 為baseItem的時候重置此價格
        // 未實現(解決此bug更好的的方式,價格在保存的時候從產品價格中去取,就不會出現值被覆蓋的情況)
        if (storeBaseItem.retailPrice) {
          obj.retailPrice = storeBaseItem.retailPrice
        }
      }
      baseItem = Object.assign({}, baseItem, obj)
      this.commit('RELEASEPRODUCTBASEINFO', baseItem)
    }
  }
}
</script>

效果展示:

3.0用法

xml 复制代码
// 父组件
<template>
	<base-info>
<template>
<script setup>
import baseInfo from '../components/baseInfo'
let num = ref(100)

provide('addProduct',num)

</script>

// 子组件
<template>
	<div>
 </div>
<template>
<script setup>

// 注入
const addProduct = inject('addProduct')
console.log(addProduct)

</script>

v-if 与 v-for 的优先级对比

2.x 版本中 v-for > v-if

3.x 版本中 v-if > v-for

v-for 中的 Ref 数组

xml 复制代码
	vue2.x 会自动把ref填充内容
	vue3.x 需要手动添加
			<ul>
		      <li v-for='item in 5' :key='item' :ref="setItemRef">
		        {{ item }}
		      </li>
		    </ul>
<script setup>
let  itemRefs:[];
methods:{
  setItemRef(el){
    this.arr.push( el );
  }
}
</script>

$children

perl 复制代码
vue2.x :  访问当前实例的子组件

vue3.x :  在 3.x 中,$children 已被移除,且不再支持。

设置:<HelloWorld msg="Welcome" ref='hw'/>

访问:this.$refs.hw 

关于生命周期的区别

setup 组合式API

注意:没有beforeCreate和created

其他生命周期要使用前面加"on" 例如:onMounted

参考链接:v3.cn.vuejs.org/guide/compo...

Teleport

基本用法:

xml 复制代码
<teleport to="某元素选择器">
  <!-- 子节点内容 -->
</teleport>
  1. 将组件html渲染到父组件外的指定元素
xml 复制代码
html
<!-- 弹出框组件 -->
<teleport to="#modal">
  <div>I'm a modal!</div> 
</teleport>

<!-- 指定渲染到的节点 -->
<div id="modal"></div>
  1. 避免组件过深层级嵌套问题, 简单理解为可以将dom放在任意想要的位置
xml 复制代码
html  
<!-- Navbar.vue -->
<teleport to="#navbar">
  <nav>...</nav>
</teleport>

<!-- App.vue -->
<div id="navbar"></div>
<router-view />

ref & reactive

官网解释:cn.vuejs.org/api/reactiv...

ref的使用场景

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。

接受基本类型和引用类型,简单易懂,操作便捷

reactive的使用场景:

  • 使用 Proxy 代理对象,拦截读取操作进行依赖收集。- 对对象属性递归调用 reactive() 方法,实现深层代理。- 设置属性时自动触发依赖,使其更新。

创建响应式数据对象:reactive可以用于将普通JavaScript对象转换为响应式数据对象,以便在模板中实现数据的双向绑定。

复杂数据结构:reactive适用于包含嵌套对象和数组的复杂数据结构,可以自动追踪其内部属性的变化。

typescript 复制代码
<script setup>
	import { userManager } from '@/store/userManager';
	import { storeToRefs } from 'pinia';
	const store = userManager()
	console.log(store)
	const { nickName, age, changeAge } = storeToRefs(store)
	
	let msg = ref('这是ref响应数据');
 
  let state = reactive({
    user: {
      name: 'lzx',
       getName() {
        	return this.name 
      },
      address: {
        city: 'hangzhou',
         getCity() {
          return this.city
        }
        street: {
          number: 123
        }
      }
    }
})

// 直接改变深层属性
state.user.address.street.number = 456 

// 使用computed观察深层属性变化
const number = computed(() => {
    let number = state.user.address.street.number)
  	return number

})

	onMounted(()=>{
	  console.log('onMounted');
})
</script>

toRefs

将响应式对象转换为普通对象,同时保持响应性

解构响应式对象时保持响应性

将响应式对象传递给函数式组件时保持响应性

xml 复制代码
<script setup>
	import { userManager } from '@/store/userManager';
	import { storeToRefs } from 'pinia';
	const store = userManager()
	console.log(store)
	const { nickName, age, changeAge } = storeToRefs(store)
	
	let msg = ref('这是ref响应数据');
 
  let state = reactive({
    user: {
      name: 'lzx',
      age:19,
       getName() {
        	return this.name 
      },
      address: {
        city: 'hangzhou',
         getCity() {
          return this.city
        }
        street: {
          number: 123
        }
      }
    }
})

const stateAsObject = toRefs(state)
// stateAsObject 是普通对象,但具有响应性
stateAsObject.age.value++ 

const { age } = toRefs(state)
age.value++ // 响应式

<MyComponent v-bind="toRefs(state)" /> //传递给组件时

</script>

响应式区别

markdown 复制代码
	vue2.x : Object.defineProperty()

	vue3.x : Proxy 

	1. Object.defineProperty()存在的问题

		1. 不能监听数组的变化
		2. 必须遍历对象的每一个属性

	2. Proxy 通过代理对象实现属性拦截。

状态管理 vuex & pinia

vuex官网:vuex.vuejs.org/zh/

vuex中定义store的目录划分

mutation-types.js

arduino 复制代码
// 用戶管理
export const USERLIST = 'USERLIST' // 用戶管理列表

modules>userMages>index.js

ini 复制代码
import userApi from '../../../api/userManageApi'
import * as types from '../../mutation-types'

const state = {
  userList: {}, // 用户列表

}
const getters = {
  userList: state => state.userList,
}

const actions = {
  getUserList ({ commit }, params) {
    userApi.userList(params).then(res => {
      let rec = res.data.data
      commit(types.USERLIST, {
        rec
      })
    })
  },

}
const mutations = {
  [types.USERLIST] (state, { rec }) {
    state.userList = rec
  },

}
export default {
  state,
  getters,
  actions,
  mutations
}

store> index.js, 将所有模块集合

javascript 复制代码
import Vue from 'vue'
import Vuex from 'vuex'

import userManager from './modules/userManager/index'


Vue.use(Vuex)

// const debug =process.env.NOOE_ENV !== 'production'
export default new Vuex.Store({
  modules: {

    userManager,

  }
})

mian.js 引入

javascript 复制代码
import 'es6-promise/auto'
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'



new Vue({
  el: '#app',
  router,
  // store挂载到vue 中
  store,
  components: {
    App
  },
  template: '<App/>'
})

view文件中使用

xml 复制代码
< template>
	   <div class="forms">
          <template>
            <el-tabs v-model="activeName" type="card" @tab-click="tabSearch" class="tabel-tags">
              <el-tab-pane label="所有用戶" name="all"></el-tab-pane>
              <el-tab-pane label="待審核" name="inProgress"></el-tab-pane>
              <el-tab-pane label="已審核" name="forbidden"></el-tab-pane>
            </el-tabs>
          </template>
          <el-table :data="userList.list" ref="userTabel" @selection-change="selectionChange" highlight-current-row border style="width: 100%">
            <!-- <el-table-column type="selection"></el-table-column> -->
            <el-table-column type="index" :index="indexMethod" width=50 align="center" label="序號">
            </el-table-column>
            <el-table-column prop="userName" align="center" label="會員編號">
            </el-table-column>
            <el-table-column align="center" prop="realName" label="稱謂">
            </el-table-column>
            <el-table-column prop="typeName" align="center" label="使用者類型">
            </el-table-column>
            <el-table-column align="center" label="聯繫手機">
              <template slot-scope="scope">
                {{scope.row.mobile || '-'}}
              </template>
            </el-table-column>
            <el-table-column align="center" label="綁定手機">
              <template slot-scope="scope">
                {{scope.row.mobile || '-'}}
              </template>
            </el-table-column>
            <el-table-column align="center" label="綁定電子信箱">
              <template slot-scope="scope">
                {{scope.row.email || '-'}}
              </template>
            </el-table-column>
            <!-- <el-table-column align="center" label="ageloc me 導入帳號">
              <template slot-scope="scope">
                {{ '-'}}
              </template>
            </el-table-column> -->
            <el-table-column align="center" label="註冊時間">
              <template slot-scope="scope">
                <div>
                  {{scope.row.registerTime}}
                </div>
              </template>
            </el-table-column>
            <el-table-column align="center" label="保薦人訊息">
              <template slot-scope="scope">
                <div>{{scope.row.refereeName || '-'}}</div>
                <div>{{scope.row.refereeCode || '-'}}</div>
              </template>
            </el-table-column>
            <el-table-column align="center" label="活動狀態">
              <template slot-scope="scope">
                {{statusArr[scope.row.status]}}
              </template>
            </el-table-column>
            <el-table-column align="center" label="代購授權">
              <template slot-scope="scope">
                <span v-if="scope.row.remark === '0'">已授權</span>
                <span v-else>未授權</span>
              </template>
            </el-table-column>
            <el-table-column align="center" label="鎖定" width="100">
              <template slot-scope="scope">
                <span v-if="scope.row.lockStatus===1">未鎖定</span>
                <span v-else>已鎖定</span>
                <!-- <el-button @click="unLock(scope.row)" type="text" size="small"  v-if="scope.row.lockStatus===1"><i class="iconfont icon-unlock"></i>未鎖定</el-button> -->
                <!-- <el-button @click="unLock(scope.row)" type="text" size="small"  v-else><i class="iconfont icon-lock"></i>已鎖定</el-button> -->
              </template>
            </el-table-column>
            <el-table-column align="center" label="獎金賬號當前狀態">
              <template slot-scope="scope">
                <span v-if="scope.row.fundAccountAuditStatus == 0">待審核</span>
                <span v-else-if="scope.row.fundAccountAuditStatus == 1">審核通過</span>
                <span v-else-if="scope.row.fundAccountAuditStatus == 2">審核不通過</span>
                <span v-else>-</span>
              </template>
            </el-table-column>
            <el-table-column label="操作" align="center" width="100">
              <template slot-scope="scope">
                <el-button v-permission:check v-if="scope.row.fundAccountAuditStatus == 0" @click="getAuditInfo(scope.row)" type="text" size="small" icon="el-icon-view">審核</el-button>
                <el-button v-permission:check @click="searchDetail(scope.row)" type="text" size="small" icon="el-icon-view">查看</el-button>
                <el-button v-permission:check v-if="scope.row.fundAccountAuditStatus == 0 || scope.row.fundAccountAuditStatus == 1 || scope.row.fundAccountAuditStatus == 2" @click="goRecords(scope.row)" type="text" size="small" icon="el-icon-view">歷史記錄</el-button>
                <el-button v-if="scope.row.shipment === true" @click="onRecover(scope.row)" type="text">連續出貨中斷恢復</el-button>
              </template>
            </el-table-column>
          </el-table>
          <el-pagination v-if="userList.list && userList.list.length && userList.total > 10" @size-change="listSizeChange" @current-change="pager" :current-page="pageNum" :page-sizes="[10, 20, 30, 40,50]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="userList.total">
          </el-pagination>
        </div>

      {{name }}
<template>
<script>
import { mapGetters, mapState,mapActions} from 'vuex'
export default {
 data () {
    return {
      activeName: 'all',
      total: 0,
      pageNum: 1,
      pageSize: 10,
      searchParams: {}
    }
  },
  computed: {
    ...mapGetters(['userList'])
  },

 /* computed: mapGetters({
    orders: 'orderManagerList'
  }),
  */


  computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  }),
  ...mapState(['name'])
   // name2 和 age2 都是别名
  ...mapState({ name2: 'name', age2: 'age'}])
  ...mapGetters('some/nested/module', [
    'someGetter', // -> this.someGetter
    'someOtherGetter', // -> this.someOtherGetter
  ])
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

vuex持久化存储

php 复制代码
npm install --save vuex-persistedstate

store>index.js 文件配置

import createPersistedState from 'vuex-persistedstate'

export default new Vuex.Store({
  modules: {
    app,
    order,
    userManager,


  },
  plugins: [
    // veux持久化配置
    createPersistedState({
      key: 'rabbitstore-client',
      paths: ['userManager', 'order']
    })
  ]
})

// 注意:

// ===> 默认是存储在localStorage中

// ===> key是存储数据的键名

// ===> paths是存储state中的数据,如果是模块下具体的数据需要加上模块名称,userManager.xxx

// ===> 修改state后触发才可以看到本地存储数据的的变化。

pinia状态管理

和vuex 相比较。

  1. Pinia 不需要像 Vuex 那样显式地提交 mutation,更新状态可以直接通过 actions 进行修改。
  2. Pinia 的 state 是一个函数
  3. Pinia 中的 state 是 reactive 的,不需要深层嵌套数据也可以自动响应。
  4. Pinia 提供的 actions、getters 更简洁,只需要添加在 store 实例上。
  5. Pinia 可以轻松复用 store,只需要导出一个 install 方法。

pinia模块划分及配置

和view 文件名呼应,采用命名空间的方式

store> index.js

引入pinia 及持久化缓存插件,统一抛出store供main.js 全局引用

javascript 复制代码
import { createPinia } from "pinia";

import  piniaPluginPersist from 'pinia-plugin-persist';

const store = createPinia(); 

store.use(piniaPluginPersist)

export default store

main.js

注入口配置

javascript 复制代码
import { createApp } from "vue"
import App from "./App.vue"
import router from "./router"
import store from "./store"


createApp(App).use(store).use(router).mount('#app');

持久化缓存配置

默认为sessionStotage存储, 可配置单个属性的储存

javascript 复制代码
import { defineStore } from 'pinia'

export const userManager = defineStore({
    id: 'userId',
    state: () => {
       return {
         nickName: 'lzx',
		 age: 18,
		 userData:{}
       }
     },
	 getters:{ // 计算属性,可缓存
		 changeAge() {
			 return this.age + 10;
		 }
	 },
     
	 actions: {
		// 异步
		//  async fetchUser() {
		// 	const response = await axios.get('/api/user')
		// 	this.userData = response.data
		// }
		 updateAge(val) { // pinia 中actions 可直接修改数据
			 this.age += val
		 }

    },
	// 默认储存为sessionStotage
	persist: {
	 enabled:true,
	 strategies :[{
		 key: 'userId', // 修改存储的键名,默认为当前 Store 的 id
		 storage: window.localStorage, // 存储位置修改为 sessionStorage
		 paths:['age'] // 配置需要缓存的数据
	 }]
	 
	},
})

小结:

看看 Vuex 和 Pinia 的整体设计以及它们之间的区别是什么。

Vuex

下面是Vuex工作原理的官方图示。

在 Vuex store(仓库)中,有4个主要组件。

1、State

这只是一个包含实际状态的对象。可以在开发工具中看到这个状态,如果想保留这个状态用于缓存或其他目的,也可以保存这个对象。

2、Actions

Actions 是执行异步任务的函数。它们是由关键字dispatch发起的。

Actions 通常会请求一个外部 API 或做一些其他的异步工作。它还负责调用适当的 mutation 来实际改变状态。这说明 actions 本身并没有改变状态,而是 commit 变化,让 mutation 来改变状态。

3、Mutations

Mutation 是唯一会真正同步改变状态的函数。Mutations 使用关键字commit。

4、Getters

Getters可以被认为是计算过的属性,应该被用来从状态中获得一个修改过的响应。

eg:

php 复制代码
const store = createStore({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

使用 store

在处理上述问题时,一个组件通常会调用dispatch来启动异步任务(比如从外部API中获取)。如果需要改变状态,比如一个简单的计数器,可以调用 commit

这意味着一个组件可以通过调用dispatchcommit来与 store 进行交互。这增加了一些复杂度。

在使用Vuex之前,对 "commit" 和 "dispatch" 这两个术语并不熟悉。由于这个原因,用它们来改变状态并不直观。对于一些人来说,这可能是不同的,但这使用 action 或 mutation 都有点不舒服。

另外值得注意的是,使用Vuex,一个组件可以访问整个 store,尽管在逻辑上将 Vuex store 分成不同的文件。

Pinia

与Vuex相比,Pinia的工作原理图如下:

整体架构比 Vuex 更简单,更容易理解。一个Pinia store 有3个主要组成部分:

1、State

与Vuex的定义一样。

2、Actions

这里的 Actions 与Vuex中的 Actions 和 mutations 的工作相同。这些函数是改变状态的唯一方式。如果想从外部API获取数据并更新状态,也可以使用 actions 。

与Vuex设置的另一个区别是,Pinia actions 是普通函数,心智负担比 vuex 小很多。

3、Getters

getter 完全等同于 Store 状态的计算属性

一个简单的Pinia store 的例子如下所示:

javascript 复制代码
export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  actions: {
    increment() {
      this.counter++
    }
  },
})

使用

如果有多个模板, Vuex 一般采用 modules 方式,这就需要在 store/index.ts中将所有的 modules通过 creaeStore 注册到 store 中,

那么Pinia 就省去了这些麻烦,createPinia() 即可,不需要注册 modules,

没有任何参数,所以连 store/index.ts都可以不用了,直接在main.ts 中添加即可,

采用命名空间的方式,一般对应view模块名 ,这一点会比Vuex简洁很多

javascript 复制代码
import { createPinia } from 'pinia'
app.use(createPinia());
# main.ts

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

const app = createApp(App)
app.use(createPinia())

app.mount('#app')

就目前而言,Pinia更容易理解和使用,Pinia还支持Vue 2和3的开箱即用,这使得迁移变得更加容易。

总结

Pinia 优势:

Vue2 和 Vue3 都支持

更小,只有1KB

不需要嵌套模块,符合Vue3的Composition api,让代码更加扁平化

抛弃了Mutations的操作,只有state、getters和actions.极大简化了状态管理库的使用完整的TypeScript支持

代码更加简洁,可以实现很好的代码自动分割

Pinia还有很多的用户和细节,请前往官网官方文档Home | Pinia (vuejs.org)

可以自行搭建或参考下面推荐的的开源项目,有助你更好的理解

timeline title 推荐几个项目 1 : Soybean Admin 2: vue-pure-admin 3 : ... 4 : ...

Soybean Admin 是一个基于 Vue3 / Vite3 / TypeScript / NaiveUI / Pinia 和 UnoCSS 的中后台模版,它使用了最新流行的前端技术栈,内置丰富的主题配置,有着极高的代码规范 ,基于文件的路由系统以及基于 Mock 的动态权限路由,开箱即用的中后台前端解决方案,也可用于学习参考。

Soybean Admin 的技术特性

  • 使用最新流行的前端技术栈 :使用 Vue3 / Vite 等前端前沿技术开发, 使用高效率的 npm 包管理器 pnpm
  • 支持 TypeScript
  • 丰富可配置的主题、暗黑模式,基于原子 css 框架 -- UnoCss 的动态主题颜色
  • 优良的代码规范:丰富的规范插件及极高要求的代码规范,学习价值也很大
  • 文件路由系统:基于文件的路由系统,根据页面文件自动生成路由声明、路由导入和路由模块
  • 权限路由 :提供前端静态和后端动态两种路由模式,基于 mock 的动态路由能快速实现后端动态路由
  • 网络请求函数 :基于 axios 的完善的请求函数封装,提供 Promise 和 hooks 两种请求函数,加入请求结果数据转换的适配器

Github: github.com/honghuangdc...

体验地址:admin.soybeanjs.cn/#/dashboard...

vue-pure-admin

vue-pure-admin 是一款开源免费且开箱即用的中后台管理系统模版(兼容移动端)。使用 Vue3 + Vite + Element Plus、TypeScript + Pinia + Tailwindcss 等主流技术开发。

体验地址:yiming_chang.gitee.io/vue-pure-ad...

Githubgithub.com/pure-admin/...

更多案例请前往:www.yuque.com/donsoft/qkn...

相关推荐
嚣张农民2 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
落魄小二2 小时前
el-table 表格索引不展示问题
javascript·vue.js·elementui
neter.asia3 小时前
vue中如何关闭eslint检测?
前端·javascript·vue.js
十一吖i3 小时前
前端将后端返回的文件下载到本地
vue.js·elementplus
光影少年3 小时前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
熊的猫4 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
mosen8685 小时前
Uniapp去除顶部导航栏-小程序、H5、APP适用
vue.js·微信小程序·小程序·uni-app·uniapp
别拿曾经看以后~6 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
Gavin_9157 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
Devil枫12 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试