Vuex的使用

vuex的使用

组件之间共享数据的方式

父向子传值:v-bind属性绑定
子向父传值:v-on 事件绑定
兄弟组件之间共享数据:EventBus
$on 接收数据的那个组件
$emit 发送数据的那个组件

vuex概述

vuex是实现组件全局状态管理的一种机制,可以方便实现组件之间的数据共享
使用在大的、频繁的数据之间的共享
定义一个store仓库,把数据共享到store,其他组件直接在store仓库里面取值
在这里插入图片描述
特点

  • 在vuex中集中共享数据,易于开发和后期的维护
  • 能够高效的实现组件之间的数据共享,提高开发效率
  • 存储在Vuex中的数据都是响应式的,能够实时的保持数据与页面的同步
    什么样的数据存储到Vuex中
    只有组件之间共享的数据,才有必要存储到Vuex中,私有数据依旧存储在组件自身的data中

Vuex基本使用

  1. 安装vuex依赖包
npm install vuex --save
  1. 导入vuex包
import Vuex from 'vuex'
Vue.use(Vuex)
  1. 创建store对象
const store =new Vuex.Store(
	{
		//state中存放的就是全局共享的数据
		state:{
		count:0
	}
	}) 
	//将store对象挂载到vue实例中
	new Vue({
		el:'#app',
		render:h=>h(app),
		router,
		//将创建的共享数据对象,挂载到Vuex实例中,所有组件就可以直接从store中获取全局的数据了
		store
})
计数器案例
  1. 创建vuex项目
    在命令行窗口里面输入vue ui(vue-cli为3.0)
    当项目仪表盘打开之后,我们点击页面左下角的项目管理下拉列表,再点击Vue项目管理器
    点击创建项目,如下图所示
    第一步,设置项目名称和包管理器
    在这里插入图片描述
    第二步,设置手动配置项目
    在这里插入图片描述
    第三步,设置功能项
    在这里插入图片描述
    第四步,创建项目
    使用vuex完成计数器案例:
    打开刚刚创建的vuex项目,找到src目录中的App.vue组件,将代码重新编写如下:
<template>
  <div>
    <my-addition></my-addition>

    <p>----------------------------------------</p>

    <my-subtraction></my-subtraction>
  </div>
</template>

<script>
import Addition from './components/Addition.vue'
import Subtraction from './components/Subtraction.vue'

export default {
  data() {
    return {}
  },
  components: {
    'my-subtraction': Subtraction,
    'my-addition': Addition
  }
}
</script>

<style>
</style>

在components文件夹中创建Addition.vue组件,代码如下:

<template>
    <div>
        <h3>当前最新的count值为:</h3>
        <button>+1</button>
    </div>
</template>

<script>
export default {
  data() {
    return {}
  }
}
</script>

<style>
</style>

在components文件夹中创建Subtraction.vue组件,代码如下:

<template>
    <div>
        <h3>当前最新的count值为:</h3>
        <button>-1</button>
    </div>
</template>

<script>
export default {
  data() {
    return {}
  }
}
</script>

<style>
</style>

最后在项目根目录(与src平级)中创建 .prettierrc 文件,编写代码如下:

{
    "semi":false,//取掉结尾的分号
    "singleQuote":true//将双引号改为单引号
}

Vuex核心概念

State

使用
state提供唯一的公共数据源,所有共享的数据都要统一放到store的state中进行存储
在store下的index.js中导入并在state中写上数据源

//创建store数据源,提供唯一公共数据
const store =new Vuex.store({
	state: { count: 0 })
})

1. 组件访问state中数据的第一种方式:

this.$store.state.全局数据名称

2. 组件访问state中数据的第二种方式:
a.导入

import { mapState } from 'vuex'

b. 通过刚才导入的mapState函数,将当前组件需要的全局数据,映射为当前组件的computed计算属性:

computed:{
...mapState({'count'})
}
mutations

用于变更state中的数据
(1)只能通过mutations变更store数据,不可以直接操作store中的数据
(3)通过这种方式虽然操作起来稍微繁琐一些,但是可以集中监控所有数据的变化

mutations导入的第一种方式

  1. 在加法组件中调用
<button @click="handle1">+1</button>

methods:{
handle1(){
	//触发mutations的第一种方式
	this.$store.commit('add')
}}
  1. 使用 在store下的index.js中
const store =new Vuex.store({
	state: { count: 0 },
	mutations: {
		add(state){
			//变更状态
			state.count++
	 	}
})

mutation传递参数
调用

methods:{
handle2(){
	//在调用commit函数,触发mutatios时携带参数
	this.$store.commit('addN',3)
	}
}

触发mutations时传递参数:

const store =new Vuex.store({
	state:{count:0},
	mutations:{
			addN(state,step){
			//变更状态
			state.count+=step
		}
	}
})

mutations导入的第二种方式

//1.从vuex中按需导入mapMutations函数
import { mapMutations } from 'vuex'

通过刚才导入的mapMutations函数,将需要的mutations函数,映射为当前组件的methods方法
在子组件写入函数

//2.将指定的mutatiuons函数,映射为当前组件的methods函数
computed: {
	...mapMutations({'add','addN'}),
}
methods:{
	//在对应的函数上调用
	btnHandler1(){
		this.sub()
	},
	btnHandler2(){
		this.subN(3)
	}
}

使用延时器进行操作(下面的方法不会再vue调试器里面出现变化)

//在store.js里面
mutatios:{
	add(state){
		//这样在vue调试器里面使用的时候count不会发生变化
		//不要在mutation函数中执行异步操作
		setTimeout(()=>{
			//延时1s
			state.count++
		},100)
}}
action

actio用于处理异步处理
如果通过异步操作变更数据,必须通过action,而不能使用Mutation,但是action中还是通过触发mutatios的方式间接的变更数据。

使用action的第一种方式

action异步处理不携带参数

//定义action
const store =new Vuex.store({
mutations:{
	add(state){
	state.count++
	}
},
actions:{
	addAsync(context){
		setTimeout(()=>{
			//在actions中不能直接修改state中的数据;
			//必须通过context.commit()触发mutation才行
			context.commit('add')
			//commit调用muntations
		},1000)
	}
})
//触发
methods:{
	handle(){
		//触发actios的第一种方式 dispatch调用actions
		this.$store.dispatch('addAsync')
	}
}

使用actions异步任务时携带参数:

//定义actio
const store =new Vuex.store({
muntations:{
	addN(state,step){
		state.count+=step
	}
},
actions:{
	addNAsync(context,step){
		setTimeout(()=>{
			context.commit('addN',step)
		},1000)
	}
})
//触发actios
methods:{
handle(){
	//在调用dispatch函数
	//触发actios时携带参数
	this.$store.dispatch('addNAsync',5)
	}
}
触发actios的第二种方式

1.从vuex中按需导入mapActios函数

import {mapActions} from 'vuex'

2.通过刚才导入的mapactios函数,将需要的actios函数,映射为当前组件的方法:

computed:{
	...mapActios(['addASync','addASync'])
}
//对应的函数进行调用
函数名:{
this.subAsync()
}

触发actions异步任务时携带参数

//定义actios
const store =new Vuex.store({
mutations: {
	addN(state,step){
	}
},
actions: {
	addNAsync(context,step){
		setTimeout(()=>{
			context.commit('addN',step)
		},1000)
	}
}
})
//触发actions
methods:{
	handle(){
		//在调用dispatch函数,触发actions时携带参数
		this.$store.dispatch('addNAsync',5)
	}
}
	//mapActios本质上把全局的函数映射为自己的函数
Getter

getter用于对Store中的数据进行加工处理形成新的数据
(1)getter可以对Store中已有的数据加工处理之后形成新的数据,类似vue的计算属性
(2)store中数据发生变化,getter的数据也会跟着发生变化,只会起包装的属性

//定义getter
const store =new Vuex.store({
state:{
	count :0
},
getters:{
	showNum:state=>{
		return '当前最新的数量是'+state.count+''
	}
}
})

使用getter的第一种方式:

this.$store.getters.名称
//可以直接写在插值括号中

使用getters的第二种方式
在store.js中

import { map Getters} from 'vuex'
computed:{
...mapGetters{['showNum']}
}
//直接将映射过来的showNum传到插值表达式里面

Vuex实现Todo业务功能
1.新建项目
Presets中打开Manual(手动选择依赖项)
Features中打开Babel、Vuex、Linter/Formatte、Use coonfig files
Configuration 选择ESLint+Standard config
最后是否将项目存为预设,选择Continue without saving
2.安装依赖axios、ant-design-vue
3.在main.js中写以下代码

import Vue from 'vue'
import App from './App.vue'

// 1. 导入 ant-design-vue 组件库
import Antd from 'ant-design-vue'
// 2. 导入组件库的样式表
import 'ant-design-vue/dist/antd.css'

Vue.config.productionTip = false
// 3. 安装组件库
Vue.use(Antd)

new Vue({
  
  render: h => h(App)
}).$mount('#app')

在app.vue中写上todo的模板

<template>
  <div id="app">
    <a-input placeholder="请输入任务" class="my_ipt" />
    <a-button type="primary">添加事项</a-button>

    <a-list bordered :dataSource="list" class="dt_list">
      <a-list-item slot="renderItem" slot-scope="item">
        <!-- 复选框 -->
        <a-checkbox>{{item.info}}</a-checkbox>
        <!-- 删除链接 -->
        <a slot="actions">删除</a>
      </a-list-item>

      <!-- footer区域 -->
      <div slot="footer" class="footer">
        <!-- 未完成的任务个数 -->
        <span>0条剩余</span>
        <!-- 操作按钮 -->
        <a-button-group>
          <a-button type="primary">全部</a-button>
          <a-button>未完成</a-button>
          <a-button>已完成</a-button>
        </a-button-group>
        <!-- 把已经完成的任务清空 -->
        <a>清除已完成</a>
      </div>
    </a-list>
  </div>
</template>

<script>
export default {
  name: 'app',
  data() {
    return {
      list: [
        {
          id: 0,
          info: 'Racing car sprays burning fuel into crowd.',
          done: false
        },
        { id: 1, info: 'Japanese princess to wed commoner.', done: false },
        {
          id: 2,
          info: 'Australian walks 100km after outback crash.',
          done: false
        },
        { id: 3, info: 'Man charged over missing wedding girl.', done: false },
        { id: 4, info: 'Los Angeles battles huge wildfires.', done: false }
      ]
    }
  }
}
</script>

<style scoped>
#app {
  padding: 10px;
}

.my_ipt {
  width: 500px;
  margin-right: 10px;
}

.dt_list {
  width: 500px;
  margin-top: 10px;
}

.footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

在任务栏中启动项目:
① 通过 vue ui 命令打开可视化面板,创建新项目 vuex-demo2
② 安装 vuex 依赖包 npm install vuex axios ant-design-vue –S
③ 实现 Todos 基本布局(基于已有样式模板)
解决出现的警告1.需要添加一个新的空行
新建一个配置文件.prettierrc

{
"semi":false,//去除分号
"singleQuote":true//单引号替换双引号
}

1.列表数据的动态加载
main.js导入样式
import store from ‘./store.js’
挂载

new Vue(
render:h=>h(App),
store}).$mount('#app')

将app.vue中list数组抽放在public下新建的list.json文件中
list.json

 [
        {
          id: 0,
          info: 'Racing car sprays burning fuel into crowd.',
          done: false
        },
        { id: 1, info: 'Japanese princess to wed commoner.', done: false },
        {
          id: 2,
          info: 'Australian walks 100km after outback crash.',
          done: false
        },
        { id: 3, info: 'Man charged over missing wedding girl.', done: false 				 	},
        { id: 4, info: 'Los Angeles battles huge wildfires.', done: false }
      ]

App.vue中list为一个list:[];
通过axios请求list文件,就会返回一个真实的数据,然后将数据挂载到vuex的store中进行存储,
怎样通过发起请求来获取数据:
1.在store.js中导入

import axios from 'axios'

由于axios是异步操作,所以将axios定义到actions节点

actions:{
	//定义了一个异步操作
	getList(context){
		axios.get('/list.json').then(({data})=>{
		console.log(data)
		})
	}
}

调用getList函数,
在页面app.vue中定义一个生命周期函数,created

created(){
	//发起数据请求
	this.$store.dispatch('getList')
}

将请求到的数据挂载到state的store中进行存储

state:{
	//所有的任务列表
	list:[]
}

在mutations中对list进行赋值

mutations:{
	//外界谁调用谁传一个list,拿到list就可以把下面的list赋值到state中的list
	initList(state,list){
	state.list=list;
}}

将获取到的数据传给mutations

actions:{
	//定义了一个异步操作
	getList(context){
		axios.get('/list.json').then(({data})=>{
			console.log(data)
			context.commit('initList',data)
		})
	}
}

上述操作就成功请求到数据并赋值到state的list中,
在app.vue中得到list数据
导入mapState

import { mapState } from 'vuex'

将mapState放到计算属性computed当中

computed(){
...mapState(['list'])//需要导进来的数据
}

将list渲染到页面上,并成功的挂载到state上:
实现文本框中的双向同步:
在state中定义新的数据节点:

state:{
//所有的任务列表
list:[]
inputValue:’aaa‘//文本框的内容
}

希望让state里面的inputValue跟页面的文本框做双向的数据绑定
实现方法
利用文本框的value属性,动态绑定到inputValue上
将全局的inputValue映射为当前的计算属性:

computed(){
...mapState(['list','inputValue'])//需要导进来的数据
//inputValue就被映射为app的计算属性了
}

将app.vue中文本的输入框代码改为

 <a-input placeholder="请输入任务" class="my_ipt" :value="inputValue" />

通过改变文本框的内容,动态的改变state中inputValue中的值
给input 添加 @change监听函数

	 <a-input placeholder="请输入任务" class="my_ipt" :value="inputValue" @change="handleInputChange" />

方法中写上:

methods:{
//监听文本框的变化
handleInputChange(e){
console.log(e.target.value)//拿到当前文本框最新的值
}
}

如何为state重新赋值的操作
调用mutations

mutations:{
//外界谁调用谁传一个list,拿到list就可以把下面的list赋值到state中的list
initList(state,list){
state.list=list;
}
//为store中的inputValue赋值
setInputValue(state,val){
state.inputValue=val
}
}

在app.vue中调用

methods:{
//监听文本框的变化
handleInputChange(e){
console.log(e.target.value)//拿到当前文本框最新的值
this.$store.commit('setInputValue',e.target.value)//将参数传递进去
}
}

这就实现了文本框中的值与state中inputValue中的值同步变化,
实现点击按钮将输入框中的内容添加到列表中:
先为按钮添加点击事件:

 <a-button type="primary" @click="addItemToList">添加事项</a-button>

在methods:

methods:{
//监听文本框的变化
handleInputChange(e){
console.log(e.target.value)//拿到当前文本框最新的值
this.$store.commit('setInputValue',e.target.value)//将参数传递进去
}
//向列表中新增item项
addItemToList(){
//判断输入框中的值是否是空
	if(this.inputValue.trim().length<=0){
		return this.$message.warning('文本框内容不能为空');//来源于Antd这个组件库
	}
	//触发mutations 在mutations里面添加,
	this.$store.commit('addItem')//
}
}
mutations:{
//添加列表项目
addItem(state){
const obj={
id:nextId,//由于id是有次序的,所以要在state中定义一个nextId:5,
info:state.inputValue.trim(),
done:false//代表是否完成
}
//然后将obj对象追加到list列表
state.list.push(obj)
state.nextId++;//保证nextId是一个未被占用的ID
//完成添加后需要将inputValue进行清空
state.inputValue=''
}
}

删除对应事项:
1.找到对应的删除 代码并绑定事件

   <a slot="actions" @click="removeItemById">删除</a>//根据id删除对应的事项

因为当前的项是通过对象的格式来获取的,得到id可以通过item.id来传

<a slot="actions" @click="removeItemById(item.id)">删除</a>//根据id删除对应的事项

在methods属性中新增一个事件处理函数:

methods:{
//监听文本框的变化
handleInputChange(e){
console.log(e.target.value)//拿到当前文本框最新的值
this.$store.commit('setInputValue',e.target.value)//将参数传递进去
}
//向列表中新增item项
addItemToList(){
//判断输入框中的值是否是空
	if(this.inputValue.trim().length<=0){
		return this.$message.warning('文本框内容不能为空');//来源于Antd这个组件库
	}
	//触发mutations 在mutations里面添加,
	this.$store.commit('addItem')//
}
removeItemById(id){
console.log(id);//可以正常的拿到对应的id
}
}

在mutations中进行移除state中list中的数据

mutations:{
//添加列表项目
addItem(state){
const obj={
id:nextId,//由于id是有次序的,所以要在state中定义一个nextId:5,
info:state.inputValue.trim(),
done:false//代表是否完成
}
//然后将obj对象追加到list列表
state.list.push(obj)
state.nextId++;//保证nextId是一个未被占用的ID
//完成添加后需要将inputValue进行清空
state.inputValue=''
},
//根据id删除对应的任务事项
removeItem(state,id){
//1.根据id查找对应项的索引
const i=state.list.findIndex(x=>x.id===id)//i就是找到的对应项的索引
//2.根据索引,删除对应的元素
if(i!=-1){
state.list.splice(i,1)}
}
}

在app.vue中调用commit函数

methods:{
//监听文本框的变化
handleInputChange(e){
console.log(e.target.value)//拿到当前文本框最新的值
this.$store.commit('setInputValue',e.target.value)//将参数传递进去
},
//向列表中新增item项
addItemToList(){
//判断输入框中的值是否是空
	if(this.inputValue.trim().length<=0){
		return this.$message.warning('文本框内容不能为空');//来源于Antd这个组件库
	}
	//触发mutations 在mutations里面添加,
	this.$store.commit('addItem')//
},
removeItemById(id){
console.log(id);//可以正常的拿到对应的id
this.$store.commit('removeItem',id)//把id传进去,并且调用removeItem方法
}
}

完成复选框状态的绑定:

  <a-checkbox :checked="item.done">{{item.info}}</a-checkbox>
  //状态存在于item.done

修改复选框的状态,然后将对应项的id赋值到对应的list上
1.点击复选框,拿到复选框的状态
监听复选框的状态,,发生变化,会触发change函数,就会调用函数体@change="(e)=>{cbStatusChanges(e)}"

<a-checkbox :checked="item.done" @change="(e,item.id)=>{cbStatusChanges(e)}">{{item.info}}</a-checkbox>
//传入当前对应的id

在methods:{}中定义cbStatusChanges函数,监听复选框选中状态的变化

methods:{
//监听文本框的变化
handleInputChange(e){
console.log(e.target.value)//拿到当前文本框最新的值
this.$store.commit('setInputValue',e.target.value)//将参数传递进去
},
//向列表中新增item项
addItemToList(){
//判断输入框中的值是否是空
	if(this.inputValue.trim().length<=0){
		return this.$message.warning('文本框内容不能为空');//来源于Antd这个组件库
	}
	//触发mutations 在mutations里面添加,
	this.$store.commit('addItem')//
},
removeItemById(id){
console.log(id);//可以正常的拿到对应的id
this.$store.commit('removeItem',id)//把id传进去,并且调用removeItem方法
},
cbStatusChanges(e,id){
console.log(e.target.checked)//点击后会打印出对应的事件对象,会打印出checked状态,
console.log(id)//拿到当前的id值
//组织出一个参数函数
const param={
	id:id,
	status:e.target.checked
}
//触发mutations函数
this.$store.commit('changesStatus',param);
},
}

在mutations中,添加changesStatus

mutations:{
//添加列表项目
addItem(state){
const obj={
id:nextId,//由于id是有次序的,所以要在state中定义一个nextId:5,
info:state.inputValue.trim(),
done:false//代表是否完成
}
//然后将obj对象追加到list列表
state.list.push(obj)
state.nextId++;//保证nextId是一个未被占用的ID
//完成添加后需要将inputValue进行清空
state.inputValue=''
},
//根据id删除对应的任务事项
removeItem(state,id){
//1.根据id查找对应项的索引
const i=state.list.findIndex(x=>x.id===id)//i就是找到的对应项的索引
//2.根据索引,删除对应的元素
if(i!=-1){
state.list.splice(i,1)}
},
//修改列表项选中状态
changesStatus(state,param){
//1.查找索引
state.list.findIndex(x=>x.id==param.id);
//判断修改
if(!=-1){
state.list[i].done=param.status;
}
}}

统计未完成的任务条数
使用vuex中的getter,针对源数据进行包装
在actios中写下列代码

getters:{
unDoneLength(state){
return  state.list.filter(x=>x.done==false).length//使用filter进行过滤,x代表里面的每一项,返回一个新数组,通过链式编程.length,把对应的长度return 出去
}
}

在app.vue组件中使用geters
在app.vue组件中导入

import {mapState,mapGetters} from "vuex"

mapGetters结合computed属性进行使用

computed(){
...mapState(['list','inputValue'])//需要导进来的数据
//inputValue就被映射为app的计算属性了
...mapGetters(['unDoneLength'])
}

在模板中进行修改

  <span>{{unDoneLength}}条剩余</span>

清除已完成的功能
为按钮绑定事件处理函数

<a @click="clear">清除已完成</a>

在methods中:使用定义的方法:

methods:{
//监听文本框的变化
handleInputChange(e){
console.log(e.target.value)//拿到当前文本框最新的值
this.$store.commit('setInputValue',e.target.value)//将参数传递进去
},
//向列表中新增item项
addItemToList(){
//判断输入框中的值是否是空
	if(this.inputValue.trim().length<=0){
		return this.$message.warning('文本框内容不能为空');//来源于Antd这个组件库
	}
	//触发mutations 在mutations里面添加,
	this.$store.commit('addItem')//
},
removeItemById(id){
console.log(id);//可以正常的拿到对应的id
this.$store.commit('removeItem',id)//把id传进去,并且调用removeItem方法
},
cbStatusChanges(e,id){
console.log(e.target.checked)//点击后会打印出对应的事件对象,会打印出checked状态,
console.log(id)//拿到当前的id值
//组织出一个参数函数
const param={
	id:id,
	status:e.target.checked
}
//触发mutations函数
this.$store.commit('changesStatus',param);
},
clear(){
//触发mutations的一个函数
this.$store.commit('cleanDone');
}
}

在mutations节点的最后写:

cleanDone(state){
state.list=state.list.filter(x=>x.done===false)//过滤掉未完成的重新赋值给list
}

完成最后列表高亮激活的状态
点击不同的按钮,实现不同的高亮效果
为3个按钮添加点击事件

<a-button type="primary" @click="changeList('all')">全部</a-button>
          <a-button @click="changeList(undone)">未完成</a-button>
          <a-button @click="changeList('done')">已完成</a-button>

在methods方法中使用

changeListy(key){
console.log(key);
this.$store.commit('changeViewKey',key)
}

在state中定义一个默认的属性

viewKey:'all'

修改state中的viewKey,去触发mutations:

changeViewKey(state,key){
state.viewKey=key;
}

将state中的viewKey映射为app.vue中的计算属性

computed(){
...mapState(['list','inputValue','viewKey'])//需要导进来的数据
//inputValue就被映射为app的计算属性了
...mapGetters(['unDoneLength'])
}

获取到viewKey之后,按需设置按钮的type值,通过写一个三元表达式;

<a-button :type="viewKey==='all'?'primary':'defalut'" @click="changeList('all')">全部</a-button>//default为默认的状态
          <a-button :type="viewKey==='undone'?'primary':'defalut'" @click="changeList(undone)">未完成</a-button>
          <a-button :type="viewKey==='done'?'primary':'defalut'" @click="changeList('done')">已完成</a-button>

列表数据的按需切换
getters可以对数据进行包装,点击未完成显示未完成的数据,
核心得点是将list切换为getters
在getters节点中

infolist(state){
if(state.viewKey==='all'){
return state.list
}
if(state.view==='undone'){}
return state.list.filter(x=>!x.done)
}
if(state.viewKey==='done'){
return state.list.filter(x=>x.done)
}
return state.list

映射的时候就不用在映射list而是映射getters里面的infolis
修改最后的计算属性:

computed(){
...mapState(['inputValue','viewKey'])//需要导进来的数据
//inputValue就被映射为app的计算属性了
...mapGetters(['unDoneLength','infolist'])
}

最后在修改数据源的数据名

  <a-list bordered :dataSource="infolist" class="dt_list">

完整项目地址:https://github.com/haohuihai/vuex-TodoList.git

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值