js HTML5 中定义的 JavaScript 脚本

目录

前言

一、跨文档消息传递

1、XDM 的核心方法——postMessage() 方法

2、接收 XDM 消息会触发一个事件——message 事件

3、案例

二、原生拖放

1、拖动性——draggable 属性

2、自定义放置目标

3、拖放事件

(1)、在拖动元素上触发事件

(2)、在可放置目标上触发的事件

(3)、探索不同情况下,事件触发

4、dataTransfer 对象

(1)、dataTransfer 对象的 2 个方法

(2)、dataTransfer 对象的 dropEffect 属性

(3)、dataTransfer 对象的 effectAllowed 属性

(4)、dataTransfer 对象的其他成员

5、拖放兼容性

(1)、drop 事件兼容 Firefox

(2)、dataTransfer 对象兼容 Firefox 和 IE

三、媒体元素

1、audio 和 video 元素的属性

2、audio 和 video 元素的方法

3、自定义媒体播放器

4、检测编解码器的支持情况

(1)、新的书写格式

(2)、如何检测浏览器是否支持某种格式的编解码器呢?

四、历史状态管理

1、hashchange 事件

2、历史状态管理 API

(1)、history.pushState() 方法

(2)、history.replaceState() 方法

3、hash 和 history 路由对比


前言

本文会用到 EventUtil 事件处理程序,详情请戳:js 跨浏览器事件处理程序脚本_weixin79893765432...的博客-CSDN博客

一、跨文档消息传递

跨文档消息传递是 HTML5 中新增的功能,简称为 XDM,Web Messaging 指的也是 XDM。

XDM 方案可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递;
  • 多窗口之间消息传递;
  • 页面与嵌套的iframe消息传递;
  • 上面三个场景的跨域数据传递。

1、XDM 的核心方法——postMessage() 方法

postMessage(data, origin) 方法接受两个参数:

  • data:一条消息。有的浏览器允许传入任何数据,有的浏览器仅支持传入字符串,所以传参时最好先用 JSON.stringify() 方法,把数据转为字符串。
  • origin:一个表示消息接收方所在域的字符串。如果是“*”表示可以传递给任意窗口(不安全不推荐);如果要指定和当前窗口同源的话可以设置为"/"。

发送方通过 postMessage() 方法发送 XDM 消息:

var data = {
    name: 'Mali',
    age: '18'
};
// 发送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), "http://127.0.0.1:80/");

2、接收 XDM 消息会触发一个事件——message 事件

接收方接收到 XDM 消息时会触发 window 对象的 message 事件,该事件是以异步的形式触发的,因此会 延迟 一段时间。

触发 message 事件后,传递给 onmessage 处理程序的事件对象包含以下三个重要信息:

  • data:消息内容。
  • origin:发送消息的文档(发送者)所在域。
  • source:发送消息的文档(发送者)的 window 对象的代理。这个代理用于:在发送消息的窗口中调用 postMassage() 方法。如果发送消息的窗口来自同一个域,那这个对象就是 window。只通过这个代理调用 postMassage() 方法即可。

接收到消息后,验证发送窗口的来源是很重要的,在 onMessage 处理程序中检测消息的来源可以确保传入的消息来自已知页面,这样才更安全可靠。

检测模式如下:

<script src="./methods/EventUtil.js"></script>
<script>
    EventUtil.addHandler(window, "message", function(){
        var event = EventUtil.getEvent(event);
        // 确保发送消息的域是已知域
        if(event.origin == "http://test.com"){
            // 处理接受到的数据
            processMessage(event.data);
            // (可选)向来源窗口发送回执
            event.source.postMessage("Received", "http://p2p.test.com");
        }
    });
</script>

3、案例

tip:该测试案例,需要开启本地服务器(Wampserver)。

// test.html 中
<iframe name="richedit" id="myframe" style="width: 300px;height: 100px;" src="http://127.0.0.1:80/iframe/myIframe.html"></iframe>
<script>
    var iframe = document.getElementById("myframe");
    iframe.onload = function() {
        var data = {
            name: 'Mali',
            age: '18'
        };
        // 发送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data), "http://127.0.0.1:80/");
    };
</script>
// myIframe.html 中
<p>hello</p>
<div id="box"></div>
<script src="./methods/EventUtil.js"></script>
<script>
    EventUtil.addHandler(window, "message", function(){
        var event = EventUtil.getEvent(event);
        // 确保发送消息的域是已知域
        if(event.origin == "http://127.0.0.1:80/" || event.origin == "http://localhost"){
            // 处理接受到的数据
            processMessage(event.data);
            // (可选)向来源窗口发送回执
            event.source.postMessage("Received", "http://127.0.0.1:80/iframe/test.html");
        }
    });
    function processMessage(data){
        data = JSON.parse(data);
        if(data){
            var box = document.getElementById("box");
            box.innerHTML = `我是${data.name},今年${data.age}岁了`;
        }
    }
</script>

效果:

二、原生拖放

拖放就是抓取对象后拖到另一个位置。

1、拖动性——draggable 属性

在 HTML5 中,默认情况下,文本只有在被选中的情况下才能拖动,而图像和链接在任何时候都可以拖动。也就是说,不用编写代码,用户就可以拖动它们。这是为什么呢?

原来,HTML5 为所有 HTML 元素定义了一个 draggable 属性:接收一个布尔值,表示元素是否可以拖动。

  • 对于图像和链接:draggable 属性的默认值为 true。
  • 对于文本:当文本被选中时,draggable 属性的值就变为 true。

想让其他元素可以拖动,或者让图像、链接不能拖动,都可以通过设置 draggable 属性的值来实现。

<!-- 不允许该图像拖动 -->
<img src="./img/333.png" draggable="false" alt="atm给你小心心">

<!-- 允许该div拖动 -->
<div draggable="true">666666</div>

2、自定义放置目标

拖动元素时,经过某些无效放置目标时,会显示不能放置标志

不过任何元素都可以自定义为有效的放置目标,方法是——禁止 dragover 事件的默认行为

例如:假设有一个 id 为 “droptarget” 的 <div> 元素,可以用如下代码将它变成一个放置目标:

var droptarget = document.getElementById("droptarget");

EventUtil.addHandler(droptarget, "dragover", function(event){
	EventUtil.preventDefault(event);
});

3、拖放事件

拖放事件,可分为 “拖动元素上触发的事件” 和 “可放置目标上触发的事件”。

(1)、在拖动元素上触发事件

  • ondragstart - 当用户开始拖动元素时触发。
  • ondrag - 当元素正在拖动时触发。
  • ondragend - 当用户完成元素拖动后触发。

(2)、在可放置目标上触发的事件

  • dragenter - 当拖动元素进入可放置目标时触发。
  • dragover - 当拖动元素在可放置目标内移动时触发。
  • drop - 当拖动元素最终被放置在了可放置目标时触发。
  • dragleave - 当拖动元素离开可放置目标时触发。

(3)、探索不同情况下,事件触发

  • 鼠标在拖动元素上按下,不触发拖放系列事件。
  • 鼠标在拖动元素上按下,并开始移动该元素(未进入可放置目标),先后触发:dragstartdrag 事件。
  • 鼠标拖动元素一直移动(未进入可放置目标),一直触发:drag 事件。
  • 释放鼠标(未进入可放置目标),触发:dragend 事件。
  • 拖动元素进入可放置目标,先后触发:dragenterdragover 事件。
  • 在放置目标内,拖动元素一直移动,一直先后触发:dragoverdrag 事件。
  • 将元素放置在放置目标内,依次触发:dragoverdropdragend 事件。
  • 拖动元素,从放置目标移出,依次触发:dragleavedragdragend 事件。

4、dataTransfer 对象

dataTransfer 对象,是拖放事件对象的属性,所以,它只能在拖放事件处理程序中被访问。

dataTransfer 对象,有 2 个功能:

  • 用于从被拖动元素向放置目标传送字符串格式的数据——setData() + getData() 方法。
  • 用于确定被拖动的元素和作为放置目标的元素能够接收什么操作——dropEffect 和 effectAllowed 属性。

(1)、dataTransfer 对象的 2 个方法

  • setData() 方法:用来保存拖动元素的数据(浏览器自动调用,也可以在 dragstart 事件处理程序中手动保存这些数据)。
    接收 2 个参数:一个表示数据类型的字符串 和 一个数据。第一个参数的可选值包括:"URL" 和 "text"。
    当第一个参数为 "text" 类型时,第二个参数只能是文本数据,表示“设置和接收文本数据”;
    当第一个参数为 "URL" 类型时,第二个参数只能是 URL 地址,表示“设置和接收 URL”。
  • getData() 方法:获取由 setData() 方法保存的值(只能在 drop 事件处理程序中读取 dataTransfer 对象中的数据)。
    接收 1 个参数:一个表示数据类型的字符串,可选的值包括:"URL" 和 "text"。
    当参数为 "text" 类型时,表示“读取文本”;
    当参数为 "URL" 类型时,表示“读取 URL”。

(2)、dataTransfer 对象的 dropEffect 属性

dropEffect 属性:用于获知被拖动的元素能够执行哪种放置行为。该属性有以下可选值:

  • "none":不能把拖动的元素放在这里。这是除文本框之外的所有元素的默认值。
  • "move":应该把拖动的元素移动到放置目标。
  • "copy":应该把拖动元素复制到放置目标。
  • "link":表示放置目标会打开拖动的元素(但拖动的元素必须是一个链接,有 URL)。

必须在可放置目标的 dragenter 事件处理程序中针对放置目标来设置 dropEffect 属性。

另外,dropEffect 属性只有和 effectAllowed 属性一起使用时才有效

(3)、dataTransfer 对象的 effectAllowed 属性

effectAllowed 属性:用来表示允许拖动元素的哪种 dropEffect。该属性的可选值如下:

  • "uninitialized":没有给被拖动元素设置任何放置行为。
  • "none":被拖动的元素不能有任何行为。
  • "copy":只允许值为 "copy" 的 dropEffect。
  • "link":只允许值为 "link" 的 dropEffect。
  • "move":只允许值为 "move" 的 dropEffect。
  • "copyLink":允许值为 "copy" 和 "link" 的 dropEffect。
  • "copyMove":允许值为 "copy" 和 "move" 的 dropEffect。
  • "linkMove":允许值为 "link" 和 "move" 的 dropEffect。
  • "all":允许任意的 dropEffect。

必须在被拖动元素的 dragstart 事件处理程序中设置 effectAllowed 属性。

(4)、dataTransfer 对象的其他成员

根据 HTML5 的规定,dataTransfer 对象还包括以下方法和属性:

  • addElement(element):为拖动操作添加一个元素。该元素只影响数据,不改变页面外观。
  • clearData(format):清除以特定格式保存的数据。
  • setDragImage(element, x, y):指定一幅图像,当拖动发生时,显示在光标下方。接收 3 个参数:要显示的 HTML 元素、光标在该图像上的 x 坐标 和 光标在该图像上的 y 坐标。
  • types:当前保存的数据类型。这是一个类数组的集合,以 "text" 这样的字符串形式保存着数据类型。

5、拖放兼容性

(1)、drop 事件兼容 Firefox

在 Firefox 3.5+ 中,drop 事件的默认行为是打开被放置目标上的 URL。这就导致了两种结果:

如果把图片放置到目标上,页面就会转向图像文件;

如果把文本放置在目标上,则会导致无效 URL 错误。

为了让 Firefox 支持正常的拖放,需要取消 drop 事件的默认行为,阻止它打开 URL。代码实现如下:

EventUtil.addHandler(droptarget, "drop", function(){
	EventUtil.preventDefault(event);
})

(2)、dataTransfer 对象兼容 Firefox 和 IE

Firefox 5 之前的版本不能正确的将 "text" 和 "url" 映射为 "text/plain" 和 "text/uri-list"。但是却能够把 "Text" 映射为 "text/plain"。

为了更好地在跨浏览器的情况下从 dataTransfer 对象中取得数据,最好在取得 URL 数据时检测两个值,在取得文本数据时使用 "Text"。

EventUtil.addHandler(placeTarget, "drop", function(event){
	var dataTransfer = event.dataTransfer;
	
	// 读取 URL
	var url = dataTransfer.getData("url") || dataTransfer.getData("text/uri-list");
	
	// 读取文本
	var text = dataTransfer.getData("Text");
	
	console.log([url, text]);

	EventUtil.preventDefault(event);
})

注意:IE 只定义了 "URL" 和 "text" 两种有效的类型,HTML5 则允许指定各种 MIME 类型,也兼容 "text"  和 "URL"  类型,不过这两种类型分别被映射为 "text/plain" 和 "text/uri-list"。一定要把短数据类型放在前面,因为 IE10 及之前的版本仍然不支持扩展的 MIME 类型名,而它们在遇到无法识别的数据类型时,会抛出错误。

三、媒体元素

HTML5 新增的媒体元素指的是:<audio> 和 <video>,分别是“音频”和“视频”。

1、audio 和 video 元素的属性

属性数据类型说明
autoplay布尔值取得或设置 autoplay 标志
buffered时间范围表示已下载的缓冲的时间范围的对象
bufferedBytes字节范围表示已下载的缓冲的字节范围的对象
bufferingRate整数下载过程中每秒平均接收到的位数
bufferingThrottled布尔值表示浏览器是否对缓冲进行了节流
controls布尔值取得或设置 ontrols 属性,用于显示或隐藏浏览器内置的控件
currentLoop整数媒体文件已经循环的次数
currentSrc字符串当前播放的媒体文件的 URL
currentTime浮点数已经播放的秒数
defaultPlaybackRate浮点数取得或设置默认的播放速度。默认值为 1.0 秒
duration浮点数媒体的总播放时间(秒数)
ended布尔值表示媒体文件是否播放完成
loop布尔值取得或设置媒体文件在播放完成后是否再重头开始播放
muted布尔值取得或设置媒体文件是否静音
poster字符串规定视频下载时显示的图像,或者在用户点击播放按钮前显示的图像。
networkState整数表示当前媒体的网络连接状态:0表示空;1表示正在加载;2表示正在加载元数据;
3表示已经加载了第一帧;4表示加载完成。
paused布尔值表示播放器是否暂停
playbackRate浮点数取得或设置当前的播放速度。用户可以改变这个值,让媒体播放速度变快或者变慢,
这与只能由开发人员修改的 defaultPlaybackRate 不同。
played时间范围到目前为止已经播放的时间范围
readyState整数表示媒体是否已经就绪(可以播放了)。0表示不可用;1表示可以显示当前帧;
2表示可以开始播放了;3表示媒体可以从头到尾播放了。
seekable时间范围可以搜索的时间范围
seeking布尔值表示播放器是否正移动到媒体文件中的新位置
src字符串媒体文件的来源
start浮点数取得或设置媒体文件中开始播放的位置,以秒表示
totalBytes整数当前资源所需的总字节数
videoHeight整数返回视频(不一定是元素)的高度。只适用于 <video>
videoWidth整数返回视频(不一定是元素)的宽度。只适用于 <video>
volume浮点数取得或设置当前音量,值为 0.0 到 1.0

2、audio 和 video 元素的方法

事件触发时机
abort下载中断
canplay可以播放时;readyState 的值为 2 时
canplaythrough播放可继续,而且应该不会中断;readyState 的值为 3 时
canshowcurrentframe当前帧已经下载完成;readyState 的值为 1 时
dataunavailable因为没有数据而不能播放;readyState 的值为 0 时
durationchangeduration 属性的值改变
emptied网络连接关闭
empty发生错误,阻止了媒体下载
ended媒体已经播放到末尾了,播放停止
error下载期间发生网络错误
load所有媒体已经加载完成。这个事件可能会被废弃,建议使用 canplaythrough
loadeddata媒体的第一帧已经加载完成
loadedmetadata媒体的元数据已经加载完成
loadstart下载已开始
pause播放已暂停
play媒体已经接收到指令开始播放
playing媒体已经实际开始播放
progress正在下载
ratechange播放媒体的速度改变
seeked搜索结束
seeking正移动到新位置
stalled浏览器尝试下载,但未接收到数据
timeupdatecurrentTime 被不合理或意外的方式更新
volumechangevolume 属性值或 muted 属性值已改变
waiting播放暂停,等待下载更多数据

3、自定义媒体播放器

使用 <audio> 和 <video> 元素的属性、事件,以及 play() 和 pause() 这两个方法,可以手动控制媒体文件的播放。

举个栗子:自定义播放一个小视频

<body>
    <div class="mediaplayer">
        <div class="video">
            <video id="player" src="./movie/movie.mp4" poster="./img/66.jpeg" width="300" height="200">
                视频播放器不可用。
            </video>
        </div>
        <div class="controls">
            <input type="button" value="播放" id="video-btn">
            <span id="curtime">0</span> / <span id="duration">0</span>
        </div>
    </div>
</body>
<script type="text/javascript">
    var player = document.getElementById("player"),
        btn = document.getElementById("video-btn");
        curtime = document.getElementById("curtime");
        duration = document.getElementById("duration");

    // 更新播放时间
    window.onload = function () {
        duration.innerHTML = player.duration;
    };

    // 为按钮添加事件处理程序
    EventUtil.addHandler(btn, "click", function (e) {
        if (player.paused) {
            player.play();
            btn.value = "暂停";
        } else {
            player.pause();
            btn.value = "播放";
        }
    });

    // 定时更新当前时间
    var timer;
    clearInterval(timer);
    timer = setInterval(function () {
        curtime.innerHTML = player.currentTime;
    }, 200);
</script>

video踩坑记 

4、检测编解码器的支持情况

(1)、新的书写格式

并非所有的浏览器都支持 <audio> 和 <video> 元素的所有 “编解码器”。所以,开发者必须提供媒体来源。为此,不用在标签中指定 src 属性了,而是要像下面这样使用一个或多个 <source> 元素。

<!-- 嵌入视频 -->
<video id="myVideo">
	<source src="myvideo.mp4" type="video/mp4"></source>
	<source src="myvideo.ogv" type="video/ogg"></source>
	<source src="myvideo.webm" type="video/webm"></source>
	当前浏览器不支持 video。
</video>

<!-- 嵌入音频 -->
<audio id="myAudio">
	<source src="song.ogg" type="audio/ogg">
	<source src="song.mp3" type="audio/mpeg">
	当前浏览器不支持 audio。
</audio>

(2)、如何检测浏览器是否支持某种格式的编解码器呢?

JavaScript 提供了一个方法:canPlayType() 方法。

canPlayType() 方法接收 1 个参数:一个格式或者编码器字符串。可能的返回值如下:

  • "probably":当返回该值时,为 true。
  • "maybe":当返回该值时,为 true。
  • "":当返回该值时,为 false。

如果给 canPlayType() 方法传入一种 MIME 类型,返回的值可能是 "maybe" 或者 ""。

如果给 canPlayType() 方法同时传入 MIME 类型和编解码器,返回的值还可能是 "probably"。

canPlayType() 方法的应用:

var audio = document.getElementById("audio-player");

// 可能是 "maybe"
if(audio.canPlayType("audio/mpeg")){
	// 进一步处理
}

// 可能是 "probably"
if(audio.canPlayType("audio/ogg; codecs=\"vorbis\"")){
	// 进一步处理
}

下表列出了已知的已得到支持的 音频 格式和解码器:

音频字符串支持的浏览器
AACaudio/mp4; codecs="mp4a.40.2"IE9+、Safari4+、iOS版Safari
MP3audio/mpegIE9+、Chrome
Vorbisaudio/ogg; codecs="vorbis"Firefox3.5+、Chrome、Opera10.5+
MAVaudio/wav; codecs="1"Firefox3.5+、Chrome、Opera10.5+

下表列出了已知的已得到支持的 视频 格式和解码器:

视频字符串支持的浏览器
H.264video/mp4; codecs="avc1.42E01E, mp4a.40.2"IE9+、Safari4+、iOS版Safari、Andriod版WebKit
Theoravideo/ogg; codecs="theora"Firefox3.5+、Chrome、Opera10.5
WebMvideo/webm; codecs="vp8, vorbis"Firefox4+、Chrome、Opera10.6+

四、历史状态管理

在开发中,用户每次操作不一定会打开一个全新的页面,因此“后退”和“前进”按钮也就失去了作用,导致用户很难在不同状态间切换。要解决这个问题,有两种办法:

  • 首选使用 hashchange 事件:可以知道 URL 的参数什么时候发生了变化。
  • 其次可以使用“历史状态管理 API”:能够在不加载新页面的情况下改变浏览器的 URL。

1、hashchange 事件

hashchange 事件的作用:当 URL 的参数列表(及 URL 中“#”后面的所有字符串)发生变化时,通知开发人员。 

必须要把 hashchange 事件添加给 window 对象。

hashchange 事件的 event 对象新增了两个属性:oldURL 和 newURL。这两属性分别保存着参数列表变化前后的完整 URL。

使用下面的方法可以跨浏览器检测浏览器是否支持 hashchange 事件:

var isSupported = ("onhashchange" in window) && (document.documentMode === undefined || document.documentMode > 7);

hashchange 事件的应用:

var isSupported = ("onhashchange" in window) && (document.documentMode === undefined || document.documentMode > 7);

if(isSupported){
    EventUtil.addHandler(window, "hashchange", function(event){
        alert(`当前 hash 值${location.hash}`);
    });
}

支持 hashchange 事件的浏览器有 IE8+、Firefox3.6+、Safari5+、Chrome 和 Opera10.6+。

支持 oldURL 和 newURL 属性的只有 Firefox6+、Chrome 和 Opera10.6+。因此,最好使用 location 对象来确定当前参数列表。

2、历史状态管理 API

历史状态管理 API 包括两个方法:

  • history.pushState() 方法:能够在不加载新页面的情况下改变浏览器的 URL。
  • history.replaceState() 方法:更新当前 event.state 的状态。

(1)、history.pushState() 方法

pushState() 方法接收 3 个参数:状态对象、新状态的标题 和(可选的)相对 URL。

  • 状态对象:应该尽可能提供初始化页面状态所需的各种信息。
  • 新状态的标题:目前还没有完全实现,因此完全可以只传入一个空字符串("")或者一个短标题也可以。
  • 相对 URL:可选的参数。如果传入这个参数,浏览器地址栏的地址会更新。

例如:

history.pushState({name:"Nicholas"}, "Nicholas' page", "nicholas.html");

执行 pushState() 方法后,新的状态信息就会被加入历史状态栈,而浏览器地址栏也会变成新的相对 URL。但是,浏览器并不会真的向服务器发送请求,即使状态改变之后查询 location.href,也会返回与地址栏中相同的地址。

因为 pushState() 方法会创建新的历史状态,所以 “后退” 按钮就能够使用了。

返回的浏览器加载的第一个页面时,event.state 的值为 null。

在按下 “后退” 按钮时,就从状态栈中的一个状态跳到另一个状态了,就会触发 window对象的 popstate 事件,popstate 事件对象中有一个 state 属性,存储着当初传递给 pushState() 方法的 “状态对象”。

得到这个状态对象后,必须手动把页面重置为状态对象中状态,因为浏览器不会自动帮你重置。

(2)、history.replaceState() 方法

replaceState() 方法接收 2 个参数:状态对象 和 新状态的标题。(与 pushState() 方法的前两个参数相同)

调用这个方法不会在历史状态栈中创建新状态,只会重写当前状态——按当前地址栏中的地址给服务器发送请求,把新的页面实现出来。

在A页面,调用 pushState() 方法后 URL 地址指向B页面。再通过在 A 页面调用 replaceState() 方法,然后,刷新一下 A 页面,变成了B页面了。

3、hash 和 history 路由对比

  • hash 模式的实现原理是通过监听hashChange事件来实现的。
  • history 模式是通过调用 history.pushState方法(或者replaceState) 并且 监听popstate事件来实现的。history.pushState会追加历史记录,并更换地址栏地址信息,但是页面不会刷新,需要手动调用地址变化之后的处理函数,并在处理函数内部决定跳转逻辑;监听popstate事件是为了响应浏览器的前进后退功能。
  • hash 模式会在地址栏中有 # 号。
  • history 模式没有;同时由于 history 模式的实现原理用到 H5 的新特性,所以它对浏览器的兼容性有要求(IE >= 10)。
  • hash 模式 SEO 表现差。history 模式 SEO 表现良好。
  • 使用 hash 模式时,要求正确配置服务器。而使用 history 模式时无需配置服务器。

【推荐阅读】

web 历史状态管理 - zhanglw - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值