vite+vant+vue3新闻客户端app(一)

接口文档地址:

http://toutiao.itheima.net/api.html#u5173u6ce8u7528u62370a3ca20id3du5173u6ce8u7528u62373e203ca3e

项目源码地址:

https://gitee.com/git640640/news

一、项目初始化--使用vite创建项目

1.1搭建第一个 Vite 项目

你还可以通过附加的命令行选项直接指定项目名称和你想要使用的模板。例如,要构建一个 Vite + Vue 项目,运行:

# npm 7+, extra double-dash is needed:
npm create vite@latest news -- --template vue

安装结束,命令提示你项目创建成功,按照命令行的提示在终端中分别输入:

# 进入你的项目目录
cd news

# 启动开发服务
npm run dev

Ctrl+鼠标左键单击http://127.0.0.1:5173/进入http访问地址,如果浏览器能打开页面,说明项目创建成功了。

二、加入 Git 版本管理

几个好处:

  • 代码备份

  • 多人协作

  • 历史记录

(1)创建远程仓库

GitHub:https://gitee.com/git640640

(2)将本地仓库推到线上

如果没有本地仓库

#创建本地仓库
git init

#将文件添加到暂存区
git add .

#提交历史记录
git commit '提交日志'

#添加远程仓库地址
git remote add origin 你的远程仓库地址

#推送提交
git push -u origin master

如果已有本地仓库

#添加远程仓库地址
git remote add origin 你的远程仓库地址

#推送提交
git push -u origin master

如果之后项目代码有了变动需要提交

#将文件添加到暂存区
git add .

#提交历史记录
git commit '提交日志'

#推送提交
git push

三、调整初始目录结构

默认生成的目录结构不满足我们的开发需求,所以这里需要做一些自定义改动。

这里主要就是下面的两个工作:

  • 删除初始化的默认文件

  • 新增调整我们需要的目录结构

  1. 将 App.vue 修改为

<!-- 视图层: html -->
<template>
  <router-view></router-view>
</template>
<!-- 逻辑层:js -->
<script setup>
</script>

<style  scoped>
</style>
  1. 安装vue-router和less

npm install vue-router

npm install less

3.在src文件夹下新建router/index.js,将 router/index.js 修改为

import { createRouter, createWebHashHistory } from 'vue-router'
// 1、创建路由规则
const routes = [
]
// 2、创建路由实例
const router = createRouter({
    history: createWebHashHistory(),
    routes
});
export default router;

4.删除

  • src/components/HelloWorld.vue

  • src/components/Aside.vue

  • src/components/Main.vue

5.创建以下几个目录

  • src/api 目录

存储接口封装

  • src/utils 目录

存储一些工具模块

  • src/styles 目录

style.less文件,存储全局样式

在 main.js 中加载全局样式 import '~/styles/style.less'

四、引入 Vant 组件库

Vant 是有赞商城前端开发团队开发的一个基于 Vue.js 的移动端组件库,它提供了非常丰富的移动端功能组件,简单易用。

官方文档:https://vant-contrib.gitee.io/vant/#/zh-CN

安装

# Vue 3 项目,安装最新版 Vant
npm i vant

引入组件

方法一:常规用法

下面是使用 Vant 组件的用法示例:

main.js

import { createApp } from 'vue';
// 1. 引入你需要的组件
import { Button } from 'vant';
// 2. 引入组件样式
import 'vant/lib/index.css';

const app = createApp();

// 3. 注册你需要的组件
app.use(Button);

方法二:按需引入组件样式

在基于 vitewebpackvue-cli 的项目中使用 Vant 时,可以使用 unplugin-vue-components 插件,它可以自动引入组件,并按需引入组件的样式。

相比于常规用法,这种方式可以按需引入组件的 CSS 样式,从而减少一部分代码体积,但使用起来会变得繁琐一些。如果业务对 CSS 的体积要求不是特别极致,我们推荐使用更简便的常规用法。

  1. 安装插件

# 通过 npm 安装
npm i unplugin-vue-components -D
  1. 配置插件

如果是基于 vite 的项目,在 vite.config.js 文件中配置插件:

import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';

export default {
  plugins: [
    vue(),
    Components({
      resolvers: [VantResolver()],
    }),
  ],
};

3.使用组件

完成以上两步,就可以直接在模板中使用 Vant 组件了,unplugin-vue-components 会解析模板并自动注册对应的组件。

<template>
  <van-button type="primary" />
</template>

4.引入函数组件的样式

ant 中有个别组件是以函数的形式提供的,包括 ToastDialogNotifyImagePreview 组件。在使用函数组件时,unplugin-vue-components 无法自动引入对应的样式,因此需要手动引入样式。

// Toast
import { showToast } from 'vant';
import 'vant/es/toast/style';

// Dialog
import { showDialog } from 'vant';
import 'vant/es/dialog/style';

// Notify
import { showNotify } from 'vant';
import 'vant/es/notify/style';

// ImagePreview
import { showImagePreview } from 'vant';
import 'vant/es/image-preview/style';

你可以在项目的入口文件或公共模块中引入以上组件的样式,这样在业务代码中使用组件时,便不再需要重复引入样式了。

五、移动端 REM 适配

如果需要使用 rem 单位进行适配,推荐使用以下两个工具:

  • postcss-pxtorem 是一款 PostCSS 插件,用于将 px 单位转化为 rem 单位

下面我们分别将这两个工具配置到项目中完成 REM 适配。

一、使用lib-flexible 动态设置REM基准值(html标签的字体大小)

  1. 安装

npm i -S amfe-flexible
  1. 然后在main.js中加载执行该模块

import 'amfe-flexible'

最后测试:在浏览器中切换不同的手机设备尺寸,观察 html 标签 font-size 的变化。

二、使用postcss-pxtorem将px转为rem

  1. 安装

npm install postcss postcss-pxtorem --save-dev
  1. 在vite.config.js配置

如果设计稿的尺寸不是 375,而是 750 或其他大小,可以将 rootValue 配置调整为:

import postCssPxToRem from 'postcss-pxtorem';
export default defineConfig({
  plugins: [vue(),
    Components({
      resolvers: [VantResolver()],
    }),],
    css: {
      postcss: {
        plugins: [
          postCssPxToRem({
            rootValue({ file }) {
              return file.indexOf('vant') !== -1 ? 37.5 : 75;
            }, // 1rem的大小
            propList: ['*'], // 需要转换的属性,这里选择全部都进行转换
          })
        ]
      }
    },
})

3.配置完毕,重新启动服务

最后测试:刷新浏览器页面,审查元素的样式查看是否将px转化为rem。

转换之后的,可以看到 px 都被转换为了 rem。

需要注意的是:该插件不能转换行内样式中的px,例如 <div style="width: 200px;"></div>

六、将src目录配置成~

vite.config.js

import path from "path"
export default defineConfig({
  resolve:{
    alias:{
      "~":path.resolve(__dirname,"src")
    }
  },
})

七、封装请求模块

和之前项目一样,这里我们还是使用 axios 作为我们项目中的请求库,为了方便使用,我们把它封装为一个请求模块,在需要的时候直接加载即可。

  1. 安装

npm i axios
  1. 创建 src/utils/request.js

// 请求模块
import axios from 'axios'
const request = axios.create({
    baseURL:'http://toutiao.itheima.net/' //基础路径
})

// 请求拦截器

export default request;

我们把每一个请求都封装成每一个独立的功能函数,在需要的时候加载调用,这种做法更便于接口的管理和维护。

八、导入字体图标

九、配置路由页面

Tabbar处理

通过分析页面,我们可以看到,首页、视频、消息、我的都使用的是同一个底部标签栏,我们没必要在每个页面中都写一个,所有为了通用方便,我们可以使用vue router的嵌套路由来处理。

  • 父路由:一个空页面,包含一个tabbar,中间留子路由出口

  • 子路由:首页、视频、消息、我的

一、创建tabbar组件并配置路由

这里主要使用到vant组件:

  1. 创建 src/views/layout/index.vue

<!-- 视图层: html -->
<template>
  <div class="layout-container">
    <!-- 子路由的出口 -->
    <router-view></router-view>
    <!-- 标签导航栏 -->
    <van-tabbar route="true" v-model="active" active-color="rgb(197, 66, 34)">
      <van-tabbar-item replace to="/home" icon="wap-home-o"
        >首页</van-tabbar-item
      >
      <van-tabbar-item replace to="/video" icon="video-o">视频</van-tabbar-item>
      <van-tabbar-item replace to="/news" icon="chat-o">消息</van-tabbar-item>
      <van-tabbar-item replace to="/me" icon="user-o"></van-tabbar-item>
    </van-tabbar>
  </div>
</template>
<!-- 逻辑层:js -->
<script setup>
import { ref } from "vue";
const active = ref(0);
</script>
<style  scoped>
</style>

二、分别创建首页、视频、消息、我的页面组件

三、将layout组件配置到一级路由,将首页、视频、消息、我的页面组件配置为layout的子路由

// 1、创建路由规则
const routes = [
    {
        path:"/",      
        component:Layout,
        children:[
            {
                path:"/home",
                name:"home",
                component:Home,

            },
            {
                path: "/video",
                name: "video",
                component: Video,
            },
            {
                path: "/news",
                name: "news",
                component: News,
            },
            {
                path: "/me",
                name: "me",
                component: Me,
            }
        ]
    },
    {
        path: '/login',
        name: 'login',
        component: Login
    },
]

十、登录页面

这里用到的vant组件:

布局结构

  1. src/views/login/index.vue

<!-- 视图层: html -->
<template>
  <div class="login-container">
    <van-nav-bar
      class="app-nav-bar"
      title="登录"
      left-arrow
      @click-left="onClickLeft"
    />
    <!-- 可以使用 CellGroup 作为容器 -->

    <van-form ref="loginForm" @submit="onSubmit">
      <van-field
        v-model="user.mobile"
        name="mobile"
        icon-prefix="news"
        left-icon="shouji"
        placeholder="请输入手机号"
        :rules="userFormRules.mobile"
        type="number"
        maxlength="11"
      />
      <van-field
        v-model="user.code"
        name="code"
        icon-prefix="news"
        left-icon="yanzhengma"
        placeholder="请输入验证码"
        :rules="userFormRules.code"
        type="number"
        maxlength="6"
      >
        <template #button>
          <van-count-down
            v-if="isCountDownShow"
            @finish="isCountDownShow = false"
            :time="time"
            format="ss s"
          />

          <van-button
            v-else
            @click="onSendSms"
            native-type="button"
            class="send-btn"
            size="small"
            round
            >发送验证码</van-button
          >
        </template>
      </van-field>
      <div class="login-btn-wrap">
        <van-button type="info" native-type="submit" block class="login-btn"
          >登录</van-button
        >
      </div>
    </van-form>
  </div>
</template>
<!-- 逻辑层:js -->
<script setup>
</script>

<style lang="less" scoped>
.login-container {
  .login-btn-wrap {
    padding: 37px 27px;

    .login-btn {
      background-color: rgb(197, 66, 34);
      color: #fff;
      border: none !important;
    }
  }

  .send-btn {
    background-color: #ededed;
    color: #666;
  }
}
</style>

实现基本登录功能

思路:

  • 注册点击登录事件

  • 获取表单数据

  • 表单验证

  • 发请求提交

  • 根据请求结果做下一步处理

一、根据接口要求绑定获取表单数据

1、在src/views/login/index.vue添加user响应式数据字段
<script setup>
import { reactive } from "vue";
const user = reactive({
  mobile: "",
  code: "",
});
</script>
2、在表单中使用v-model绑定对应数据
 <van-field
        v-model="user.mobile"
        name="mobile"
        placeholder="请输入手机号"/>
      <van-field
        v-model="user.code"
        name="code"
        placeholder="请输入验证码"
        type="number"
      >

二、请求登录

  1. 创建 src/api/user.js 封装请求方法
import request from '~/utils/request'
// import store from '~/store'
// 登录注册验证
export const login = data => {
    return request({
        method: 'POST',
        url: '/v1_0/authorizations',
        data
    })
}
2.表单验证
登录状态提示

Vant 中内置了Toast 轻提示组件,可以实现移动端常见的提示效果。

forbidClick:是否禁止背景点击是否禁止背景点击

<script setup>
import {
  showLoadingToast,
  showSuccessToast,
  showFailToast,
  showToast,
} from "vant";
import "vant/es/toast/style";
showLoadingToast({
    message: "登录中...",
    forbidClick: true,
    duration: 0,
  });

    showSuccessToast("登陆成功");


    showFailToast("登陆失败");

</script>
  • 给van-field组件配置rules验证规则

  • 当表单提交的时候会自动触发表单验证

  • 如果验证通过,会触发submit事件

  • 如果验证不通过,不会触发submit事件

<van-form  @submit="onSubmit">
......
 <div class="login-btn-wrap">
     <van-button type="info" native-type="submit" block class="login-btn">登录</van-button>
 </div>
 </van-form>

<script setup>
import { reactive} from "vue";
import { login } from "~/api/user";
const user = reactive({
  mobile: "",
  code: "",
});
const userFormRules = {
  mobile: [
    { required: true, message: "手机号不能为空" },
    { pattern: /1[3|5|7|8|9]\d{9}/, message: "手机号格式错误" },
  ],
  code: [
    { required: true, message: "验证码不能为空" },
    { pattern: /^\d{6}$/, message: "验证码格式错误" },
  ],
};
async function onSubmit() {
  showLoadingToast({
    message: "登录中...",
    forbidClick: true, // 是否禁止背景点击
    duration: 0, // 持续展示 toast
  });
  try {
    const res = await login(user)
    console.log('登录成功', res)
    showSuccessToast("登陆成功");
  } catch (err) {
    showFailToast("登陆失败");
  }
}
</script>
3.验证码处理
验证手机号

  • 创建 src/api/user.js 封装请求方法

// 发送短信验证码
// 每手机号每分钟1次
export const sendSms = mobile => {
    return request({
        method: 'GET',
        url:`/v1_0/sms/codes/${mobile}`,
    })
}
  • 在src/views/login/index.vue中校验手机号

icon-prefix:图标类名前缀,等同于 Icon 组件的 class-prefix 属性

left-icon:左侧图标名称或图片链接,等同于 Icon 组件的 name 属性

validate:验证表单,支持传入一个或多个 name 来验证单个或部分表单项,不传入 name 时,会验证所有表单项

 <template>
......
    <van-form ref="loginForm" @submit="onSubmit">
      <van-field
        v-model="user.mobile"
        name="mobile"
        icon-prefix="news"
        left-icon="shouji"
        placeholder="请输入手机号"
        :rules="userFormRules.mobile"
        type="number"
        maxlength="11"
      />
      <van-field
        v-model="user.code"
        name="code"
        icon-prefix="news"
        left-icon="yanzhengma"
        placeholder="请输入验证码"
        :rules="userFormRules.code"
        type="number"
        maxlength="6"
      >
        <template #button>
          <van-count-down
            v-if="isCountDownShow"
            @finish="isCountDownShow = false"
            :time="time"
            format="ss s"
          />
          <van-button
            v-else
            @click="onSendSms"
            native-type="button"
            class="send-btn"
            size="small"
            round
            >发送验证码</van-button
          >
        </template>
      </van-field>
 ......
    </van-form>
  </div>
</template>
 // 1. 校验手机号
const loginForm = ref(null);
async function onSendSms() {
  try {
    await loginForm.value.validate("mobile");
  } catch (err) {
    return console.log("验证失败", err);
  }
}
  1. 使用倒计时组件

https://vant-contrib.gitee.io/vant/#/zh-CN/count-down

time:倒计时时长,单位毫秒·

format:时间格式,通过 format 属性设置倒计时文本的内容

finish:倒计时结束时触发

 <van-field
        v-model="user.code"
        name="code"
        icon-prefix="news"
        left-icon="yanzhengma"
        placeholder="请输入验证码"
        :rules="userFormRules.code"
        type="number"
        maxlength="6"
      >
        <template #button>
          <van-count-down
            v-if="isCountDownShow"
            @finish="isCountDownShow = false"
            :time="time"
            format="ss s"
          />

          <van-button
            v-else
            @click="onSendSms"
            native-type="button"
            class="send-btn"
            size="small"
            round
            >发送验证码</van-button
          >
        </template>
      </van-field>
<script setup>
const isCountDownShow = ref(false);
</script>
  1. 发送验证码

1、在 api/user.js 中添加封装数据接口
// 发送短信验证码
// 每手机号每分钟1次
export const sendSms = mobile => {
    return request({
        method: 'GET',
        url:`/v1_0/sms/codes/${mobile}`,
    })
}
2、给发送验证码按钮注册点击事件
 <van-button
            v-else
            @click="onSendSms"
            native-type="button"
            class="send-btn"
            size="small"
            round
            >发送验证码</van-button
          >
3、发送处理
const time = ref(1000 * 10);
async function onSendSms() {
   // 1. 校验手机号
  try {
    await loginForm.value.validate("mobile");
  } catch (err) {
    return console.log("验证失败", err);
  }
  // 2. 验证通过,显示倒计时
  isCountDownShow.value = true;
  // 3. 请求发送验证码
  try {
    await sendSms(user.mobile);
    showToast("发送成功");
  } catch (err) {
    // 发送失败,关闭倒计时
    isCountDownShow.value = false;
    if (err.response.status === 429) {
      showToast("发送太频繁了,请稍后重试");
    } else {
      showToast("发送失败,请稍后重试");
    }
  }
}

十一、处理用户Token

Token是用户登录成功之后服务端返回的一个身份令牌,在项目中的多个业务中需要使用到:

  • 访问需要授权的Api接口

  • 校验页面的访问权限

  • ...

但是我们只有在第一次用户登陆成功之后才能拿到Token。

所以为了能在其他模块中获取到Token数据,我们需要把它存储到一个公共的位置,方便随时取用。

往哪儿存?

  • 本地存储

  • 获取麻烦

  • 数据不是响应式的

  • Vuex容器(推荐)

  • 获取方便

  • 响应式的

引入vuex状态管理用户信息

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储

管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变

1、安装

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

npm install vuex@next --save

2、在 src/store/index.js 中配置vuex文件

import { createStore } from 'vuex'
export default createStore({
  state: {
    user:JSON.parse(window.localStorage.getItem(TOKEN_KEY))
  },

  mutations: {
    setUser(state, data) {
      state.user = data
      // 为了防止刷新丢失,我们需要把数据备份到本地存储
   window.localStorage.setItem(TOKEN_KEY,JSON.stringify(state.user))
    },
  },
  actions: {
  },
  modules: {
  }
})

3、全局引用vuex文件

//导入文件
import store from './store'
//挂载使用
createApp(App).use(store)

4、登录成功以后将后端返回的 token 相关数据存储到容器中

async function onSubmit() {
  showLoadingToast({
    message: "登录中...",
    forbidClick: true, // 是否禁止背景点击
    duration: 0, // 持续展示 toast
  });
  try {
    const res = await login(user);
    store.commit("setUser", res.data.data);
    console.log(data.data);
    showSuccessToast("登陆成功");
  } catch (err) {
    showFailToast("登陆失败");
  }
}

十二、优化封装本地存储操作模块

1、创建 src/utils/storage.js 模块。

// 封装本地存储操作模块
// 1.存储数据
export const setItem=(key,value)=>{
    // 将数组、对象类型的数据转化为Json格式字符串进行存储
    if(typeof value==='object'){
        value=JSON.stringify(value)
    }
    window.localStorage.setItem(key,value)
}
// 2.获取数据
export const getItem=key=>{
    const data=window.localStorage.getItem(key)
    try {
      return JSON.parse(data)  
    } catch (error) {
        return data
    }
}
// 3.删除数据
export const removeItem=key=>{
    window.localStorage.removeItem(key)
}

2、在 src/store/index.js 中

import { createStore } from 'vuex'
import { getItem, setItem } from '~/utils/storage.js'

const TOKEN_KEY = 'NEWS_USER'
export default createStore({
  state: {
    user: getItem(TOKEN_KEY),
    // user:JSON.parse(window.localStorage.getItem(TOKEN_KEY))
  },

  mutations: {
    setUser(state, data) {
      state.user = data
      // 为了防止刷新丢失,我们需要把数据备份到本地存储
      // window.localStorage.setItem(TOKEN_KEY,JSON.stringify(state.user))
      setItem(TOKEN_KEY, state.user)
    },
  },
  actions: {
  
  },
  modules: {
  }
})

3、在src/views/login/index.vue中

<script setup>
import { useStore } from "vuex";
import { useRouter } from "vue-router";
const store = useStore();
const router = useRouter();

async function onSubmit() {
  showLoadingToast({
    message: "登录中...",
    forbidClick: true, // 是否禁止背景点击
    duration: 0, // 持续展示 toast
  });
  try {
    // commit 和dispatch的区别在于commit是提交mutatious的同步操作,dispatch是分发actions的异步操作
    const { data } = await login(user);
    store.commit("setUser", data.data);
    console.log(data.data);
    showSuccessToast("登陆成功");
    router.push("/home");
  } catch (err) {
    showFailToast("登陆失败");
  }
}

</script>

十三、关于Token过期问题

登录成功之后后端会返回两个Token:

  • token:访问令牌,有效期2小时

  • refresh_token:刷新令牌,有效期14天,用于访问令牌过期之后重新获取新的令牌

我们的项目接口中设定Token有效期2小时,超过有效期服务端会返回401表示Token无效或过期了。

为什么过期时间这么短?

  • 为了安全,例如Token被别人盗用

过期了怎么办?

  • 让用户重新登录,用户体验太差了

  • 使用refresh_token解决token过期

十四、Vuex五个属性和使用方法

  1. state:是放置所有公共状态的属性,如果你有一个公共状态数据,你只需要定义在state对象中。state是vuex的基本数据,用来存储变量;

// 引入库
import Vuex from 'vuex'
// 安装 vuex
Vue.use(Vuex)
// 初始化vuex对象
const store = new Vuex.Store({
  state: {
    // 管理数据
    count: 0
  }
})

组件中可以使用this.$store获取到vuex中的store对象实例,可通过state属性获取count。

<template>
      <div>
        <div> state的数据:{{ $store.state.count }}</div>
    </div>
<template>
<script>
  // 安装完 vuex 会在组件中注入一个 $store 的变量
created() {
      console.log(this.$store.state.count);
}
</script>
  1. getters:从基本数据(state)派生的数据,相当于state的计算属性;

除了state之外,有时候还需要从state中派生出一些状态,这些状态是依赖state的,此时会用到getters,例如对列表进行过滤并计数:

const store = new Vuex.Store({
 state: {
    list: [1,2,3,4,5,6,7,8,9,10]
},
  getters: {
    // getters函数的第一个参数是 state
    // 必须要有返回值
     filterList:  state =>  state.list.filter(item => item > 5)
  }
})
  1. mutations:提交更新数据的方法,必须是同步的(如果需要异步使用action)。每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler)。回调函数就是我们实际进行状态更改的地方,并且它会接受state作为第一个参数,提交载荷作为第二个参数。

// ...
mutations: {
  addCount(state, data) {
    state.count += data
}

你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)

// ...
<button @click="$store.commit('addCount',10)> 
    count 增加
</button>
  1. action:和mutation的功能大致相同,不同之处在于:

  • action提交的是mutation,而不是直接变更状态

  • action可以包含任意异步操作

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    addCount(state) {
      state.count++
    }
  },
  actions: {
    addCount(store) {
      store.commit('addCount')
    }
  }
})

action函数接受一个与store实例具有相同方法和属性的context对象,因此你可以调用context.commit提交一个mutation,或者通过context.state和context.getter。

实践中,我们会经常用到ES2015的参数解构来简化代码:

actions: {
  addCount({ commit }) {
    commit('addCount')
  }
}

分发action

store.dispatch('addCount')

5.modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使结构非常清晰,方便管理。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值