vue模板引擎mustache03__实现(笔记)

Vue2.x-mustache模板引擎(笔记) Vue2.x-mustache模板引擎Vue2.x-mustache模板引擎历史上曾经出现的数据变为视图的方法纯 DOM 法数组 join 法ES6 的反引号法mustache 的基本使用mustache 库简介mustache 的基本使用mustache 的底层核心机理`replace()` 方法实现简单地模板数据填充mustache 库的机理什么是 `tokens`?循环情况下的 `tokens`双重循环下的 `tokens``mustache` 库底层重点要做2个事情手写实现 mustache 库使用 阅读详情

目录

两个几个问题

HTML模板如何变为tokens形式的嵌套格式的数组?

数据data又怎们和tokens结合变为变为理想的dom然后上树的?

流程

parseTemplateToTokens(将HTML变为tokens)

Scanner扫描器

substring()方法

nextTokens(处理tokens为嵌套结构)

renderTemplate`(将tokens和data数据结合,返回处理好的dom然后上树)

lookup(寻找用连续点符号的keyName属性)

parseArray(循环数组渲染dom)

梳理

总结


两个几个问题

HTML模板如何变为tokens形式的嵌套格式的数组?

数据data又怎们和tokens结合变为变为理想的dom然后上树的?

流程

  • www文件夹下的index为入口文件,全局封装对象Fel_TemplateEngine(自定义)

  • 内部暴露一个render函数作为入口,供页面调用接收两个参数,模板(templateStr)和数据(data)

  • Scanner类,作为扫描器将模板编译为tokens(无嵌套格式,一个数组走到末尾)

  • nextTokens类,编译好的tokens做处理,当做

  • 建立parseTemplateToTokens类,

parseTemplateToTokens(将HTML变为tokens)

  • 理解:parseTemplateToTokens将Scanner扫描器和nextTokens(零星的tokens嵌套起来)

  • scanner在的作用是扫描模板字符串,遇到"{{" 、"}}"、"#"、"/"、"^"等符号进行拆分,并整合到一个数组中,但是结果是一个常规的数组,并未分层,和嵌套数组所应该有的嵌套结构不符,所以就有了nextTokens()方法,将这个无嵌套的数组转为原本模板对应的嵌套结构

  • import Scanner from './Scanner.js'
    import nextTokens from './nextTokens.js'
    /**
     * 将模板字符串变为数组
     */
    export default function parseTemplateToTokens(templateStr){
      var tokens = [];
      // 创建扫描器
      var scanner = new Scanner(templateStr);
      var words;
      //让扫描器工作
      while(!scanner.eos()){
        // 收集开始标记
        words = scanner.scanUtil("{{");
        // 保存
        if(words!=''){
          tokens.push(['text',words]);
        }
        // 过双大括号
        scanner.scan("{{")
          // 收集开始标记
          words = scanner.scanUtil("}}");
          // 保存
          if(words!=''){
            // 这时的words就是{{}}中间的东西,判断首字符
            if(words[0] == "#"){
              // 存起来,从下标为1的项开始存,因为下标为0的像是#
              tokens.push(["#",words.substring(1)]);
            }else if(words[0] == '/'){
              // 存起来,从下标为1的项开始存,因为下标为0的项是/
              tokens.push(["/",words.substring(1)]);
            }else{
              // 存起来
              tokens.push(['name',words]);
            }
          }
           // 过双大括号
        scanner.scan("}}")
      }
      // 返回折叠收集的tokens
      console.log(tokens);
      return nextTokens(tokens);
    }

Scanner扫描器

  • 单独建立Scanner.js文件,作为扫描器类

  • 内部三个函数

    • constructor构造函数接收一个模板(templateStr)

    • scan扫描方法

    • scanUtil方法

  • 大致流程

    以此模板为例

    我买了一个{{thing}},好{{mod}}阿

    左侧起始位置一个指针,指针往右移动从"我"到"个"的这个过程为scanUtil ,然后以scan扫描"{{",指针做过"thing"的过程为scanUtil....直至末尾

  • 相关知识点

    • substring()方法

      1、定义和用法
      substring() 方法用于提取字符串中介于两个指定下标之间的字符。
      substring() 方法返回的子串包括 开始 处的字符,但不包括 结束 处的字符。
      2、语法
      string.substring(from, to)
      3、参数
      from    必需。一个非负的整数,规定要提取的子串的第一个字符在 string Object 中的位置。
      to  可选。一个非负的整数,比要提取的子串的最后一个字符在 string Object 中的位置多 1。
      如果省略该参数,那么返回的子串会一直到字符串的结尾。
    • indexOf()方法

      1、定义和用法
      indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
      如果没有找到匹配的字符串则返回 -1。
      注意: indexOf() 方法区分大小写。
      提示: 同样你可以查看类似方法 lastIndexOf() 。
      2、语法
      string.indexOf(searchvalue,start)
      3、参数
      searchvalue 必需。规定需检索的字符串值。
      start   可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 string Object.length - 1。如省略该参数,则将从字符串的首字符开始检索。
      4、返回值
      Number类型 查找指定字符串第一次出现的位置,如果没找到匹配的字符串则返回 -1。

  • Scanner.js代码

    /**
     * 扫描类
     */
    export default class Scanner{
      constructor(templateStr){
        console.log(templateStr);
        // 将模板字符串写到实例身上
        this.templateStr = templateStr;
        // 指针
        this.pos =0;
        // 尾巴
        this.tail = templateStr;
      }
      // 功能弱,就是走过指定内容,没有返回值  
      scan(tag){
        if(this.tail.indexOf(tag)==0){
          //  tag有多长,比如{{长度是2,就让指针后移多少位
          this.pos +=  tag.length;
          //  尾巴也改变.改变尾巴位当前指针这个字符开始,到最后的全部字符
          this.tail = this.templateStr.substring(this.pos)
        }
      }
      // 让指针进行扫描,直到指定内容结束,并且能够返回结束之前路过的文字
      scanUtil(stopTag){
        // 记录一下执行本方法的时候的pos的值
        const pos_backup  = this.pos;
        // 当尾巴的开头不是stopTag的时候,说明还没有扫描到stopTag
        // 写&&很必要,因为防止找不到,那么寻找到最后也要停下来
        while( !this.eos() && this.tail.indexOf(stopTag) !=0 ){
          this.pos++
          // 改变尾巴为从当前指针这个字符开始,到最后的全部字符
          this.tail =  this.templateStr.substring(this.pos);
        }
        return this.templateStr.substring(pos_backup,this.pos);
      }
      // 指针是否到头,返回布尔值
      eos(){
        return this.pos >= this.templateStr.length;
      }
    }

nextTokens(处理tokens为嵌套结构)

  • 利用栈的先入后出的思想,结合对应的储存从而实现结构的更改,比较精妙的还有收集器的创建,一直指向最终要返回的数组,类似于一种引用,看似一直操作的是这个收集器,其实也相当于对要返回的数组进行添加,通过收集器指向的改变控制返回数组的结构。

  • /**
     * 函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,最为他的下标为3的项
     */
    export default function nextTokens(tokens){
      // 结果数组
      var nextedTokens = [];
      // 栈结构,存放小的tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前的操作的这个tokens小数组
      var sections = [];
      // 收集器,天生指向nextedTokens结果数组,引用类型值,所以指向的是同一个数组
      // 收集器的指向会变化,当遇到#的时候,收集器会指向这个下标为2的新数组
      var collector = nextedTokens;
      for(let i=0; i<tokens.length; i++){
        let token = tokens[i];
        switch(token[0]){
          case '#':
            // 收集器中放入token
           collector.push(token);
          //  入栈
          sections.push(token);
          // 收集器换人,给token添加下表为2的项,并且让收集器指向他
          token[2]=[];
          collector=token[2];
            break;
          case '/':
            // 出栈,pop()会返回刚刚弹出的项
            sections.pop();
            // 改变收集器为栈结构队尾(队尾为栈顶) 那项的下标为2的数组
            collector = sections.length>0?sections[sections.length-1][2]:nextedTokens;
            break;
          default:
            collector.push(token);
       }
      }
      return nextedTokens;
    }

renderTemplate`(将tokens和data数据结合,返回处理好的dom然后上树)

  • 内部通过对tokens的循环,判断当前token的第一个值如果text则直接加在需要返回的数据后面,如果是name则说明要添加data数据,不过在添加之前需要用lookup函数处理,防止识别错误造成渲染失败,遇到#号则说明需要循环数组遍历添加数据,结合parseArray函数来回嵌套形成递归调用从而将处理数组及其嵌套的数据渲染

  • import lookup from "./lookup.js";
    import parseArray from "./parseArray.js";
    /**
     * 函数的功能是让tokens数组变为dom字符串
     */
    export default function renderTemplate(tokens,data){
      console.log(tokens,data);
      // 结果字符串
      var resultStr = '';
      // 遍历tokens
      for(let i=0;i<tokens.length;i++){
        let token = tokens[i];
    ​
        // 看类型
        if(token[0] == 'text'){
          resultStr +=token[1];
        }else if(token[0] == 'name'){
          // 如果是name类型,直接使用他的值,使用lookup
          // 因为防止出现'a.b.c'的形式造成的无法识别问题
          resultStr += lookup(data, token[1]);
        }else if(token[0] == '#'){
          console.log(token);
          resultStr += parseArray(token,data)
        }
      }
      return resultStr;
    }

lookup(寻找用连续点符号的keyName属性)

  • /**
     * 功能是可以在dataObj对象中,寻找用连续点符号的keyName属性
     * 比如dataObj是
     * {
     *  a:{
     *    b:{
     *      c:100
     *    } 
     *  }
     * }
     * 那么lookup(dataObj,'a.b.c')结果是abc
     */
    export default function lookup(dataObj, keyName) {
      // 首先判断是否含有点符号,但不能是点本身
      if(keyName.indexOf('.') !=-1 && keyName!=".") {
        // 如果有点符号,那么拆开
        var keys = keyName.split('.');
        // 临时变量,用于周转,一层一层往下找
        var temp = dataObj;
        for(let i=0; i<keys.length; i++){
          temp =temp[keys[i]];
        }
        return temp;
      }
      // 如果没有点符号
      return dataObj[keyName]
    }

parseArray(循环数组渲染dom)

  • import lookup from "./lookup.js"
    import renderTemplate from "./renderTemplate.js"
    /**
     * 处理数组,结合renderTemplate实现递归
     * 注意函数接收的参数是token不是tokens
     * 
     * 这个函数要递归调用renderTemplate函数,调用次数由data界定
     */
    export default function parseArray(token,data){
      // 得到整体数组中这个数据要使用的部分
      console.log(token,data);
      var v=  lookup(data,token[1]);
      console.log(v);
      // 结果字符串
      var resultStr = '';
      // 遍历v数组,v可能为数组也可能为布尔值,此处布尔值忽略
      // 这个循环可能是这个包最那思考的一个循环
      // 他是遍历数据而不是遍历tokens
      for(let i=0; i< v.length; i++) {
        resultStr += renderTemplate(token[2],{
          // 现在这个数据小对象是v[i]的展开,就是v[i]本身
          ...v[i],
          '.':v[i]
        })
      }
      console.log(resultStr);
      return resultStr
    }

梳理

  • mustache中的模板引擎的功能主要是将HTML模板代码和需要渲染的数据相结合,然后是返回模板和数据结合后的DOM,模板引擎在工作中可以分为两大块,一个是将事先准备好的HTML解析为tokens数组的形式,方便后面和数据之间的结合(parseTemplateToTokens),另一个是就是将data和tokens结合返回真实的dom了(renderTemplate)。

  • 在parseTemplateToTokens中它又可以分为两小块,一个是扫描器(scanner.js,Scanner类)一个是nextTokens方法,将初步得到的tokens处理为嵌套的,和原DOM结构嵌套而是一样的形式

  • 在renderTemplate 中也有又两小块,一个是lookup判断处理防止渲染出错,另一个是parseArray方法循环数组渲染dom,利用栈的思想将tokens读取完毕即结束,返回最终dom

总结

  • 尽管是跟着视频把代码敲了出来但是还需要进一步的研究,其中的一些奥妙还是需要继续理解一番,对一些函数的处理理解的并不是很透彻,总结不是很到位,包括这个记录还是又不少问题,比如自身的理解太少、比如内容相对有点拖泥带水感觉,再接再励了。

  • 学习地址

前端插件art-template模板引擎art-template-test示例代码 这是我的博客《前端常用插件之art-template模板引擎》文章的示例代码 具体使用可参考文章,或者网上搜索也可以 立即下载

相关推荐

Synopsys SCL许可证服务器搭建详解:从安装到生成.dat文件

本文详细解析了Synopsys SCL许可证服务器的完整搭建流程,从系统环境准备、SCL安装到生成.dat文件。重点阐述了如何正确获取HOST ID、配置License文件以及调试常见故障,为数字芯片设计工程师提供了一套从零到一的实战避坑指南,确保EDA工具链稳定运行。

weixin_29306011的博客 612

【vue】vue的模版引擎

Vue是一款流行的前端框架,它的模版引擎是Vue的一个非常重要的特性。Vue的模版引擎允许我们在 HTML 中使用 Vue 的语法来编写模版,而不需要使用繁重的 JavaScript 代码来操作 DOM,大大提高了开发效率。最后,我们需要注意 Vue 的模版引擎仅仅是 Vue 的一部分功能,与 Vue 的实例和组件密切相关。在使用 Vue 的模版引擎时,我们需要牢记 Vue 实例的生命周期、数据绑定规则和组件通信等重要概念,以便能够快速构建出高质量的应用程序。在使用这些模版语法时,我们需要注意一些细节。

小墨鱼的前端问题录 763

2026最新 OpenClaw 手机端部署与实战:旧手机秒变 AI 智能终端(Android/iOS 双平台全覆盖)

OpenClaw 是2026年 GitHub 爆火的开源自托管 AI 执行网关,前身是 Clawdbot / Moltbot,主打“一句话让 AI 帮你完成真实操作”,核心能力包括发消息、整理文件、控制设备、自动化任务等,区别于普通对话式 AI,它能将 AI 指令转化为实际行动。无需高配置设备,无需专业技术,旧手机就能变身随身 AI 助手,既能实现自动化办公、生活便捷,又能保障数据隐私,还能体验极客玩法,这正是开源技术的魅力。# 初始化配置(按提示操作,配置 AI 模型 API Key)

wohaiweifaju的博客 5647

vue-mustache模板引擎

什么是模板引擎?模板引擎是将数据变为视图的最优雅方案mustache官方git: https://github.com/janl/mustache.jsmustache是“胡子“的意思,因为它的嵌入标记{{ }}非常像胡子{{ }}的语法也被Vue沿用。v-text,v-html 2:数组循环渲染数据 v-for 3:嵌套数组循环渲染数据 v-if 5:模板引擎实例 二:mustache的底层核心机理 1:正则表达式思路 .replace( RegExp 对象 , fun

taosy122的博客 1127

Crossbar RRAM技术解析:从1TnR架构到存储革命

非易失性存储器(NVM)是数据存储的核心技术,其原理基于物理状态的稳定改变以实现数据持久化。阻变存储器(RRAM)作为一种新兴NVM,利用导电细丝在介质层中的形成与断裂实现数据存储,具有高速、低功耗和结构简单的优势。在工程实践中,RRAM面临潜行路径电流等挑战,而Crossbar提出的1TnR架构通过引入共享晶体管有效解决了这一问题,在保证性能的同时提升了集成密度。该技术与CMOS工艺兼容,支持后端集成与多层堆叠,为高密度存储和嵌入式应用提供了新的解决方案。结合当前行业对高能效、高密度存储的需求,RRAM在

weixin_42523104的博客 321

【Python 实战基础】如何绘制热力图来模拟绘制北京景区热度图

Python如何绘制热力图来模拟绘制北京景区热度图 文件读写 基础语法 字符串处理 文件生成 数据构建

菜鸟实战的博客 3401

vue3——(模板应用,组件)

在 Setup 函数中,可以访问 props、context、attrs 等属性,并且直接返回需要的数据和方法,这样可以使得组件的结构更加清晰和简洁。Vue3 引入了 Teleport 功能,可以让组件在 DOM 上的任何位置渲染,这样可以避免一些布局上的问题,比如弹窗组件在父组件中嵌套太多层导致样式混乱等。详细分析:Vue3支持使用 Fragments,允许模板中直接使用多个根元素,而无需包裹在父元素中,使得模板组织更加灵活,代码更加简洁。当数据属性的值发生变化时,Vue会自动加载对应的组件。

gu644335363的博客 1563

Electron多窗口开发避坑指南:Pinia状态同步的3种解决方案对比

本文深入探讨了在Electron多窗口应用中实现Pinia状态同步的三种核心方案。针对Electron多窗口架构下状态隔离的挑战,文章对比了基础事件通信、共享存储以及智能Pinia插件等方案的优劣与适用场景,并提供了生产级的实现指南与选型建议,帮助开发者构建健壮高效的桌面应用。

spice的博客 479

vue实现直播弹幕功能

前端校招精编面试解析大全点击这里即可获取完整版pdf查看实战项目、讲解视频,并且会持续更新!**如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)前端校招精编面试解析大全点击这里即可获取完整版pdf查看。

2301_82242670的博客 945

亲测解决This application failed to start because no Qt platform plugin could be initialized.

这个问题是由于环境变量改变或者Qt的路径改变导致,解决办法是在环境变量里面重新添加Qt路径。

狂小虎/Anthony Dave的博客 KuangXiaoHu's Blogs 2921

ThingsBoard PE专业版解决方案技术文档——温度&湿度

温湿度解决方案

weixin_67440240的博客 819

VUE源码:模板引擎mustache

文章目录模板引擎的定义mustache的基本使用 模板引擎的定义 模板引擎就是将数据变为视图最优雅的解决方案 例如:VUE的v-for、mustache 历史上数据变为视图方法: 纯DOM方法 数组join ES6的反引号法:``${a} ` 模板引擎 mustache的基本使用 官方git:https://github.com/janl/mustache.js 例子: let arr = [ { name: "小红", sex: "女", age: 18 }, { name: "小黑", s

qq_45861785的博客 1816

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Vue源码:mustache模板引擎学习

由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!点击【学习资料】即可领取!看keyName中有没有点符号== ‘.’) {// 如果有点符号,那么拆开// 设置一个临时变量,这个临时变量用于周转,一层一层找下去// 每找一层,就把它设置为新的临时变量i++)// 如果没有点符号/**折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标3。

sgegv的博客 1071

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Vue源码解析之mustache模板引擎

本篇文章我们学习Vue源码之mustache模板引擎。学习如何将数据变为视图、mustache的基本使用、mustache的核心理念、并手撕mustache源码。

HuoZhiyan 的博客 1271

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Vue之mustache模板引擎

Vue之mustache模板引擎笔记记录参考历史上曾经出现的数据变为视图的方法mustache 参考 尚硅谷邵山欢(考拉老师)Vue之mustache模板引擎 为什么要学Vue源码 Vue组件由哪三部分组成? Vue父子组件如何通信? Vuex的作用是什么? Vue是怎么实现双向绑定的? Vue的最小化更新过程是怎样的? Vue如何实现指令系统的? 中高级前端、leader职位必会底层知识 为企业“造轮子”,开发通用组件 解决编程中遇见的问题 解决效率问题 vue源码非常庞大,各种机理很多:模

antRain的博客 1203

Vue之mustache模板引擎(笔记整理)

01 什么是模板引擎? **模板引擎**是将数据转变为视图最优雅的方案。

yan_danfeng的博客 3899

Vue源码之mustache模板引擎(二) 手写实现mustache

Vue源码之mustache模板引擎(二)

赤蓝紫の博客 1813

Vue源码之mustache模板引擎(一)

Vue源码之mustache模板引擎(一) 个人练习结果仓库(持续更新):Vue源码解析 抽空把之前学的东西写成笔记。 学习视频链接:【尚硅谷】Vue源码解析之mustache模板引擎 模板引擎是什么 模板引擎是将数据变为视图最优雅的解决方案。 其中,Vue中的列表渲染指令v-for就是一种模板引擎。而**插值表达式{{}}**便是本次要研究的mustache模板引擎的语法 将数据变为视图的方法 纯DOM法 很笨拙。需要频繁创建节点,添加数据,添加节点。 const arr = [ { '

赤蓝紫の博客 1268

vue模板引擎mustache__初识(笔记)

1、学Vue模板引擎和mustache什么联系 2、什么是模板引擎? 3、历史上曾经出现的数据变为试图的方法 1、学Vue模板引擎和mustache什么联系 回答 mustache是最早的模板引擎库,比Vue诞生要早,他的底层实现机理在当时是非常有创造性的、轰动性的。为后续的模板引擎的发展提供了崭新的思路。 mustache简介 mustache官方地址 解读: mustache是“胡子”的意思,因为它的嵌入标记{{}}非常像胡子

Flying____fish的博客 978

mustache模板引擎

mustache模板引擎

qq_60587956的博客 1544

Vue源码学习之mustache模板引擎

Vue源码学习之mustache模板引擎 该博文是在学习尚硅谷的vue源码教程同时做的笔记。课程都可以在b站搜到的哦。 mustache模板引擎 什么是模板引擎 模板引擎是将数据要变为视图最优雅的解决方案。 历史上出现的数据变为视图的方法: 纯DOM法:非常笨拙,没有实战的价值 数组join法:曾几何时非常流行 ES6的反引号法:${a} 模板引擎:解决数据变为视图的最优雅的方法 纯DOM法: const arr = [ {"name":xxx,"age":12}, {"name":xx

PrototypeOne的博客 463

【Vue源码解析】mustache模板引擎

模板引擎是将数据变为视图最优雅的解决方案以前出现过的其它数据变视图的方法纯 DOM 法数组 join 法在 js 里单双引号内的内容是不能换行的,为了提高 dom 结构可读性,利用了数组的 join 方法(将数组变为字符串),注意 join 的参数 ‘’ 不可以省略,否则得到的 str 字符串会是以逗号间隔的es6 的模板字符串(``)刚开始用模板引擎时可以引用 如下:

m0_61016904的博客 1496

Vue 源码学习 —— Mustache 模板引擎原理

前言 Mustache 是模板引擎思想的奠基者,Vue 中的模板引擎也借用了它的思想 因此,在学习Vue的模板引擎之前,先学习Mustache能加更容易理解模板引擎的设计思想 Mustache 下载:到 BootCDN 搜索 mustache,复制链接到浏览器打开,并保存到本地即可 或使用该链接:mustache.js(v4.1.0) 引入: 基本使用: 渲染普通对象: <div id="root"></div> <script src="js/mustache

叹之 635
上一篇: 2022-4-16周总结
下一篇: 第九周总结
Flying____fish
博客等级 码龄6年 5粉丝 60原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值