Vue+nodejs+mysql茶叶销售商场项目笔记(支付懒得写版

cnpm i express-generator -g

Express-generator是Express的应用生成器,通过使用生成器工具,可以快速创建一个Express的应用骨架,express --view=ejs server 创建server文件夹,默认端口号为3000

消除默认样式

96baca4c3a8e422ca1921305b6438f2e.png

* {
  margin: 0;
  padding: 0;
}

ul {
  list-style: none;
}

input {
  border: none;
  margin: 0;
  padding: 0;
}

input:focus {
  outline: none;
}

 然后在main.js中导入common.css

.prettierrc配置文件

{
  "trailingComma": "none",
  "semi": false,
  "singleQuote": true,
  "arrowParens": "avoid",
  "printWidth": 300,
  "endOfLine": "auto"
}

flexible.js

flexible.js是淘宝开发出的一个用来适配移动端的js框架。在main.js中引入flexible.js

点击同一个路由跳转出现的问题

dddb2a1b3ddb4e6c847c83d321fcba5e.png

 // 点击tabbar进行路由跳转
    switchTab(item) {
      if (this.$route.path === item.path) return
      this.$router.replace(item.path)
    }

 若点击同一个路由,只要进行return即可

vue.config.js配置了@代表src目录

let path = require('path')
module.exports = {
  configureWebpack: config => {
    config.resolve = {
      extensions: ['.js', '.json', '.vue'],
      alias: {
        '@': path.resolve(__dirname, './src')
      }
    }
  }
}

ly-tab插件

具体用法参考github,我使用的是第二版本的

5e7eaa663f2d472cb170d25deb046ea8.png

v-deep

::v-deep 是一个特殊的深度作用选择器,它只在scoped样式中起作用

<style scoped>
::v-deep .swiper-pagination-bullet-active {
  background-color: #b0352f;
}
</style>

 public文件夹

使用public文件夹下的图片时,进行v-for遍历时,img标签的src属性直接使用./开头就行

 data() {
    return {
      swiperList: [
        {
          id: 1,
          imgUrl: '/images/swiper1.jpeg'
        },
        {
          id: 2,
          imgUrl: '/images/swiper2.jpeg'
        },
        {
          id: 3,
          imgUrl: '/images/swiper3.jpeg'
        }
      ],
}

cnpm i vue-awesome-swiper@3.1.3

轮播图插件

<template>
  <div>
    <swiper :options="mySwiperOption">
      <swiper-slide v-for="(item, i) in swiperList" :key="i">
        <img :src="item.imgUrl" width="100%" />
      </swiper-slide>
      <div class="swiper-pagination" slot="pagination"></div>
    </swiper>
  </div>
</template>

<script>
import { swiper, swiperSlide } from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
export default {
  name: 'MySwiper',
  components: {
    // 注册 vue-awesome-swiper 组件
    swiper,
    swiperSlide
  },
  data() {
    return {
      swiperList: [
        {
          id: 1,
          imgUrl: './images/swiper1.jpeg'
        },
        {
          id: 2,
          imgUrl: './images/swiper2.jpeg'
        },
        {
          id: 3,
          imgUrl: './images/swiper3.jpeg'
        }
      ],
      mySwiperOption: {
        pagination: {
          el: '.swiper-pagination', //与slot="pagination"处 class 一致
          clickable: true //轮播按钮支持点击
        },
        //自动播放
        autoplay: {
          delay: 1000,
          disableOnInteraction: false
        },
        //循环
        loop: true
      }
    }
  }
}
</script>

<style scoped>
::v-deep .swiper-pagination-bullet-active {
  background-color: #b0352f;
}
</style>

 插槽的使用

47ec91853e394714b430f4ae85bd2533.png​​​​​​

 此标题火爆推荐由插槽实现

标题的组件如下,样式省略

<template>
  <div class="title">
    <span>
      <slot>火爆推荐</slot>
    </span>
  </div>
</template>

<script>
export default {
  name: 'MyCard'
}
</script>

引入组件后直接在<Card>填写内容</Card>即可更换默认标题

better-scroll

better-scroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件,在mounted生命周期执行,具体使用查看该文档,cnpm i better-scroll。在better-scorll的使用中如果用this.$nextTick无法解决滚动问题,可以使用setTimeout解决方案,因为页面中有很多调用接口异步操作获取到的数据,因为页面渲染较快,betterscroll无法快速获取到需要滚动的内容高度。默认取消click事件和scroll事件

//最外面的盒子
.detail {
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
//beterr-scroll的wraper盒子
section {
  flex: 1;
  overflow: hidden;
}

Object.freeze

对请求回来不用修改的数据使用Object.freeze会提高性能 

async getData() {
      const res = await axios({
        url: '/api/index_list/0/data/1'
      })
      console.log(res)
      this.items = Object.freeze(res.data.data.topBar)
    },

constructor

例如res.data.data.constructor !== Array判断该属性是否数组

this.$nextTick

当dom更新完在加载? this.$nextTick(()=>{ })

路由传值

/*显式 */
this.$router.push({ path:'/detail', query:{ id } }) 
/*隐式:*/ 
this.$router.push({ name:'Detail', params:{ id } }) 

keep-alive

keep-alive : 是一个vue的内置组件 作用:缓存组件 优势:提升性能 只要用到keep-alive会再多俩个生命周期 : activated、deactivated 。可以和路由的meta搭配,哪些组件需要缓存,哪些不需要

搜索的本地历史记录

 // 搜素
    goSearch() {
      // 如果输入为空
      if (!this.searchValue) return
      // 如果没有本地存储
      if (!localStorage.getItem('history')) {
        // 注意[]要加字符串
        localStorage.setItem('history', '[]')
      } else {
        this.historyList = JSON.parse(localStorage.getItem('history'))
      }
      // 将新输入的值存进数组
      this.historyList.unshift(this.searchValue)
      // 利用set去重
      this.historyList = Array.from(new Set(this.historyList))
      localStorage.setItem('history', JSON.stringify(this.historyList))

      // 如果重复跳转到同一个路由进行return
      if (this.$route.query.key === this.searchValue) return
      // 跳转到搜索列表
      this.$router.push({
        name: 'SearchList',
        query: {
          key: this.searchValue
        }
      })
    }

75f331f519254b37b05bac69066c96ac.png

 清空历史记录后显示

245224b08c2d49cb8c48713fe7194871.png

 axios的二次封装(个人感觉很垃圾不推荐

在src文件夹中创建common文件夹,在common文件夹中创建api文件夹,在api文件夹中创建request.js文件

import axios from 'axios'
import { Toast } from 'vant'

export default {
  // 默认参数
  common: {
    method: 'get',
    data: {},
    params: {}
  },
  $axios(options = {}) {
    options.method = options.method || this.common.method
    options.data = options.data || this.common.data
    options.params = options.params || this.common.params

    Toast.loading({
      message: '加载中...',
      forbidClick: true,
      duration: 0
    })
    return axios(options).then(r => {
      return new Promise((res, rej) => {
        Toast.clear()
        // 如果请求不到返回失败结果
        if (!res) rej()
        // 返回成功结果
        res(r.data.data)
      })
    })
  }
}

连接mysql

cnpm i mysql

node中的serve目录下创建db文件夹,在db文件夹中创建sql.js,

const mysql = require('mysql')
const connection = mysql.createConnection({
  host: 'localhost',
  port: '13306',
  user: 'root',
  password: 'xxx',
  database: 'vue_tea'
})

module.exports = connection

然后在routes的index.js文件夹下引入

var express = require('express')
var router = express.Router()
// 连接数据库
var connection = require('../db/sql')

// 数据库中的good_list
router.get('/api/goods/shopList', function (req, res) {
  const { search } = req.query
  // 查询所有茶叶
  connection.query(`select * from good_list where name like "%${search}%"`, function (err, results) {
    res.send({
      code: 0,
      data: results
    })
  })
})

升序降序

vue代码

     <div class="menu_sub" v-for="(v, i) in labelList.data" :key="i" @click="changeTab(i)">
          <!-- 判断当前高亮的是哪项 -->
          <span :class="labelList.currentIndex === i ? 'active' : ''">{{ v.title }}</span>
          <div class="menu_img" v-if="v.title !== '综合'">
            <img src="../../assets/images/search/上箭头.svg" alt="" :class="v.status === 1 ? 'img_active' : ''" />
            <img src="../../assets/images/search/下箭头.svg" alt="" :class="v.status === 2 ? 'img_active' : ''" />
          </div>
        </div>
      </div>
 data() {
    return {
      // status:0都不亮 1上箭头亮 2下箭头亮
      labelList: {
        currentIndex: 0,
        data: [
          {
            title: '综合',
            key: 'zh'
          },
          {
            title: '价格',
            status: 0,
            key: 'price'
          },
          {
            title: '销量',
            status: 0,
            key: 'num'
          }
        ]
      }
    }
  },
 methods: {
    // 获取goodList数据
    async getData() {
      const res = await http.$axios({
        url: '/api/goods/shopList',
        params: {
          search: this.$route.query.key,
          ...this.orderBy
        }
      })
      this.goodList = res
    },
    // 切换选项卡
    changeTab(index) {
      this.labelList.currentIndex = index
      this.labelList.data.forEach((v, i) => {
        if (index !== i) {
          v.status = 0
        }
      })
      // 如果不等于综合
      if (index != 0) {
        this.labelList.data[index].status = this.labelList.data[index].status === 1 ? 2 : 1
      }
      // 请求数据
      this.getData()
    }
  },
  computed: {
    // 升序降序
    orderBy() {
      const obj = this.labelList.data[this.labelList.currentIndex]
      const val = obj.status === 1 ? 'asc' : 'desc'
      return {
        [obj.key]: val
      }
    }
  }

node的代码

// 数据库中的good_list
router.get('/api/goods/shopList', function (req, res) {
  // console.log(req.query)
  const { search } = req.query
  // 是综合还是价格,销量
  const label = Object.keys(req.query)[1]
  // 是什么排序
  const order = Object.values(req.query)[1]
  // 升序降序
  if (label === 'zh') {
    // 模糊查询指定的茶叶
    connection.query(`select * from good_list where name like "%${search}%"`, function (err, results) {
      res.send({
        code: 0,
        data: results
      })
    })
  } else {
    connection.query(`select * from good_list where name like "%${search}%" order by ${label} ${order}`, function (err, results) {
      res.send({
        code: 0,
        data: results
      })
    })
  }
})

安装sass

 cnpm i node-sass sass-loader

better-scroll的左右联动

5183770803ca40228478b1be790e1d93.png

 核心代码如下:

  <div class="list_l" ref="left">
        <ul>
          <li v-for="(item, i) in leftList" :key="i" @click="goScroll(i)" :class="{ active: i === currentIndex }">{{ item.title }}</li>
        </ul>
      </div>
 data() {
    return {
    // 左边列表数据
      leftList: [],
      // 右侧列表数据
      rightList: [],
      lBScroll: '',
      rBScorll: '',
      allHeight: [],
      // 右侧滚动距离
      scrollY: ''
    }
  },
methods:{
 goScroll(index) {
      this.rBScorll.scrollTo(0, -this.allHeight[index], 300)
    }

}
 mounted() {
    setTimeout(() => {
      this.lBScroll = new BetterScroll(this.$refs.left, {
        click: true
      })

      this.rBScorll = new BetterScroll(this.$refs.right, {
        click: true,
        probeType: 3
      })
      //统计右侧所有板块高度值,并且放入数组中
      let height = 0
      this.allHeight.push(height)
      //获取右侧每一块高度
      let uls = this.$refs.right.getElementsByClassName('first_list')
      //把dom对象转换成功真正的数组
      Array.from(uls).forEach(v => {
        height += v.clientHeight
        this.allHeight.push(height)
      })
      //得到右侧滚动的值
      this.rBScorll.on('scroll', pos => {
        this.scrollY = Math.abs(pos.y)
      })
    }, 100)
  },
  computed: {
    currentIndex() {
      return this.allHeight.findIndex((item, index) => {
        // 大于等于当前的高度小于下一个的高度
        return this.scrollY >= item && this.scrollY < this.allHeight[index + 1]
      })
    }
  }

导航栏头部渐变显示

e0ca372cfb5641d89e0c5a0074094c59.png

 57a59003f9c14109af34a640ceb5cb9b.png

有个透明度渐变的过程

 // 获取dom元素
    setTimeout(() => {
      this.aBetterScroll = new BetterScroll(this.$refs.wraper, {
        probeType: 3,
        click: true,
        bounce: false
      })
      this.aBetterScroll.on('scroll', pos => {
        const posY = Math.abs(pos.y)
        // 若大于50则展示后来显示的导航栏
        if (posY > 50) {
          this.isShow = false
          // 渐变显示
          this.styleOpacity.opacity = posY / 60 > 1 ? 1 : posY / 60
        } else {
          this.isShow = true
        }
      })
    }, 10)

 input的pattern属性

模式是正则表达式

<input type="text" placeholder="请输入手机号" pattern="[0~9]*" />

手机号密码登录

vue代码如下

data() {
    return {
      userTel: '',
      userPwd: '',
      rules: {
        // 手机和密码验证
        userTel: {
          // 第一个1第二个是2-9后面9个是任意数字
          rule: /^1[23456789]\d{9}$/,
          msg: '手机号长度是11位数字'
        },
        userPwd: {
          rule: /^\w{6,12}$/,
          msg: '密码长度要求6,12位'
        }
      }
    }
  },
  methods: {
    // 登录按钮
    async login() {
      if (!this.vaildata('userTel')) return
      if (!this.vaildata('userPwd')) return

      const res = await http.$axios({
        method: 'post',
        url: '/api/login',
        data: {
          tel: this.userTel,
          pwd: this.userPwd
        }
      })
      console.log(res)
      if (res.code === 200) {
        Toast('登录成功')
      } else {
        Toast(res.msg)
        return false
      }
    },
    // 验证
    vaildata(key) {
      let bool = true
      // 验证手机和密码
      if (!this.rules[key].rule.test(this[key])) {
        Toast(this.rules[key].msg)
        bool = false
        return false
      }

      return bool
    }
  }

node代码如下

const user = {
  // 查询手机号
  queryUserTel(v) {
    return `select * from user where tel=${v}`
  },
  // 查询密码
  queryUserPwd(v) {
    // 密码传过来需要加字符串
    return `select * from user where pwd='${v}'`
  }
}

module.exports = user

其中queryUserPwd中传过来的v值在sql 语句中还要再加''引号,否则报错 

var user = require('../db/userLogin')

// 用户密码登录
router.post('/api/login', function (req, res) {
  // 查询手机号
  connection.query(user.queryUserTel(req.body.tel), function (err, results) {
    if (err) return err
    // 手机号存在
    if (results.length > 0) {
      // 查询密码
      connection.query(user.queryUserPwd(req.body.pwd), function (err2, results2) {
        if (err2) return err2
        // 密码存在
        if (results2.length > 0) {
          res.send({
            code: 200,
            data: {
              code: 200,
              success: true,
              msg: results2[0]
            }
          })
          // 密码错误
        } else {
          res.send({
            code: 302,
            data: {
              code: 302,
              success: false,
              msg: '密码错误'
            }
          })
        }
      })
      // 手机号不存在
    } else {
      res.send({
        code: 301,
        data: {
          code: 301,
          success: false,
          msg: '手机号不存在'
        }
      })
    }
  })
})

短信验证码登录

cnpm i qcloudsms_js

node的腾讯云如何操作请查看GitHub - qcloudsms/qcloudsms_js: qcloudsms Node.js SDK

使用vuex存储用户信息

 index.js中代码,用module进行模块化管理

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user.js'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    user
  }
})

user.js中代码如下

export default {
  state: {
    // 登录状态
    loginState: JSON.parse(localStorage.getItem('userInfo')) ? true : false,
    userInfo: JSON.parse(localStorage.getItem('userInfo')) || {},
    token: JSON.parse(localStorage.getItem('userInfo'))?.token
  },
  getters: {},
  mutations: {
    // 设置用户信息
    SETUSER(state, payload) {
      state.userInfo = payload
      state.token = payload.token
      state.loginState = true
      // 持久化存储
      localStorage.setItem('userInfo', JSON.stringify(payload))
    },
    // 退出登录
    LOGOUT(state) {
      localStorage.removeItem('userInfo')
      state.userInfo = {}
      state.token = ''
      state.loginState = false
    }
  },
  actions: {}
}

vue中主要代码如下,具体使用参照官网vuex3

import { mapMutations } from 'vuex'

...

 methods: {
    // 设置用户信息
    ...mapMutations(['SETUSER']),
}

vuex中的actions不能直接修改state中的数据,要通过mutations

jsonwebtoken

cnpm i jsonwebtoken

在db目录下的userLogin.js下对注册新用户的手机号进行加密

 // 插入注册用户
  insertRegister(tel, pwd) {
    const jwt = require('jsonwebtoken')
    const payload = tel
    const secret = 'xiaoyu'
    // 进行加密
    const token = jwt.sign(payload, secret)

    return `insert into user values(null,${tel},'${pwd}','/images/user.jpeg','${tel}','${token}')`
  },

对前端传入的token进行解析

// 加入购物车
router.post('/api/addCart', function (req, res) {
  const token = req.headers.token
  // 解析token
  const tel = jwt.decode(token)
  console.log(tel)
})

axios的再次封装

在vue中传入headers:{token:true}表示传入token值

 // 加入购物车
    async addCart() {
      const res = await http.$axios({
        url: '/api/addCart',
        method: 'post',
        data: {
          id: this.$route.query.id
        },
        headers: {
          token: true
        }
      })
      console.log(res)
    }

在axios中,若没有登录,而传入了token:true,就会跳转到登录页面

import axios from 'axios'
import { Toast } from 'vant'
import store from '@/store'
import router from '@/router'

export default {
  // 默认参数
  common: {
    method: 'get',
    data: {},
    params: {},
    headers: {}
  },
  $axios(options = {}) {
    options.method = options.method || this.common.method
    options.data = options.data || this.common.data
    options.params = options.params || this.common.params
    options.headers = options.headers || this.common.headers

    // 是否是登录状态
    if (options.headers.token) {
      options.headers.token = store.state.user.token
      // 如果token不存在则跳转到登录页面
      if (!options.headers.token) {
        router.push('/login')
        return false
      }
    }

    Toast.loading({
      message: '加载中...',
      forbidClick: true,
      duration: 0
    })
    return axios(options).then(r => {
      return new Promise((res, rej) => {
        Toast.clear()
        // 如果请求不到返回失败结果
        if (!res) rej()
        // 返回成功结果
        res(r.data.data)
      })
    })
  }
}

购物车之全选和单选按钮

将后端返回的数据赋值给list,再创建一个selectList,通过比较这两个数组的长度是否相等来判断全选按钮,selectList中放的是每个商品的id值。单选按钮判断:点击单选按钮后,通过传过来的索引值,在list中查找与之对应的id值,并通过id值在selectList中查找与之对应的下标值,并把它删除

vuex中的cart中的代码如下

export default {
  state: {
    list: [],
    selectList: []
  },
  getters: {
    // 判断list和selectList的长度是否相等
    isCheckedAll(state) {
      return state.list.length === state.selectList.length
    }
  },
  mutations: {
    // 获取购物车数据
    GETCARTLIST(state, res) {
      state.list = res
      // 初始化selectList,确保刚开始两数组长度是一样的
      state.selectList = state.list.map(v => {
        return v.id
      })
    },
    // 全选
    ALLCHECKED(state) {
      // 将list中每个checked赋值为true,再返回每个商品的id值,
      // 若selectList的长度等于list的长度则为全选
      state.selectList = state.list.map(i => {
        i.checked = true
        return i.id
      })
    },
    // 全不选
    UNCHECKEDALL(state) {
      state.list.forEach(v => {
        v.checked = false
      })
      state.selectList = []
    },
    // 单选
    SINGLECHECK(state, i) {
      const id = state.list[i].id
      const index = state.selectList.indexOf(id)
      //能在selectList找到对应的id,就删除
      if (index > -1) {
        return state.selectList.splice(index, 1)
      }
      // 如果是未选则进行添加
      state.selectList.push(id)
    }
  },
  actions: {
    checkedAll({ commit, getters }) {
      // 若为true则点击后全不选
      getters.isCheckedAll ? commit('UNCHECKEDALL') : commit('ALLCHECKED')
    }
  }
}

注意,vue中的全选按钮的v-model要换成:value,因为vuex并不是双向绑定的

购物车之总计

在getters中的代码如下,省略了其他代码

  getters: {
    // 总件数与总计
    total(state) {
      const total = {
        num: 0,
        price: 0
      }
      state.list.forEach(v => {
        // 如果是选中状态
        if (v.checked) {
          total.num += v.goods_num
          total.price += v.goods_price
        }
      })
      return total
    }
  },

挂载在computed上就可以了

  computed: {
    ...mapState({
      // 购物车数据
      cartList: state => state.cart.list
    }),
    ...mapGetters(['isCheckedAll', 'total'])
  }

$event参数

 <van-stepper @change="changeNum($event, item)" :default-value="item.goods_num" integer />

可以通过传入$event参数来获得该组件上的一些属性

通过slot判断路由跳转

通过this.$slots.default[0].text获取插槽上的文字来判断路由的跳转,default为默认插槽的名字

 goMy() {
      if (this.$slots.default[0].text === '我的地址') {
        this.$router.push('/my')
      } else {
        this.$router.push('/address')
      }
    }

将修改路由和添加路由放入同一个组件

通过首页的路由传值,在data中保存下该值,再通过v-if来判断显示添加地址还是修改地址

   // 跳转到添加地址页面
    goAdd() {
      this.$router.push({
        path: '/address/add',
        query: {
          addStaus: true
        }
      })
    },

下载qs

cnpm i qs

qs增加数据的安全性,在axios 中使用

全局路由前置守卫

// 路由全局前置守卫
router.beforeEach((to, from, next) => {
  // 需要登录权限的页面
  const authRouter = ['Cart', 'Address', 'Index', 'AddAddress', 'Order']
  // 解析本地存储的token
  const Auth = JSON.parse(localStorage.getItem('userInfo'))
  if (authRouter.indexOf(to.name) > 0 && !Auth) {
    next({
      name: 'Login'
    })
  }
  next()
})

 手机端查看移动端项目

npm run serve后,在手机上输入network即可

 真机测试

禁止双击放大缩小使用fastclick插件

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要下载Vue Node.js MySQL项目,您需要按照以下步骤进行操作: 1. 首先,确保您已经安装了Node.jsMySQL数据库。如果没有,请前往它们的官方网站并按照说明进行安装。 2. 打开您的命令行终端,创建一个新的项目文件夹。您可以使用以下命令创建一个名为"my-project"的文件夹: ``` mkdir my-project ``` 3. 进入新创建的项目文件夹: ``` cd my-project ``` 4. 使用以下命令初始化一个新的Node.js项目: ``` npm init -y ``` 这将会自动生成一个默认的`package.json`文件。 5. 安装Vue.js。使用以下命令将Vue.js添加到您的项目中: ``` npm install vue ``` 6. 安装Express框架,用于构建服务器端应用。使用以下命令将Express添加到您的项目中: ``` npm install express ``` 7. 安装mysql模块,用于连接和操作MySQL数据库。使用以下命令将mysql模块添加到您的项目中: ``` npm install mysql ``` 8. 创建一个新的JavaScript文件,例如`app.js`,并打开它。 9. 在`app.js`中编写您的服务器端代码,包括Vue.js前端代码和与MySQL数据库的交互。您可以根据您的项目需求编写自定义的代码。 10. 保存`app.js`文件并返回终端。 11. 在终端中运行以下命令以启动服务器: ``` node app.js ``` 12. 当服务器启动成功后,您将看到一个成功的消息。此时,您可以在浏览器中访问`http://localhost:3000`来查看您的Vue Node.js MySQL项目。 总结一下,下载Vue Node.js MySQL项目的步骤包括:创建项目文件夹,初始化Node.js项目,安装Vue.js、Express和mysql模块,编写服务器端代码,启动服务器并在浏览器中查看项目。这样,您就可以成功下载和运行Vue Node.js MySQL项目了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值