最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 使用 Tampermonkey 编写高级跨网站自动化任务脚本

    正文概述 掘金(TopFE)   2021-02-22   1033

    Tampermonkey 介绍

    官方介绍

    为了照顾读者中一部分对 Tampermonkey(国内成油猴,以下都简称 TM)不熟悉的读者,这里我借助官方对 TM 的介绍和教程帮助入门用户做以下介绍。

    TM 具有以下特点:

    • 方便的脚本管理:位于右上方的 TM 图标显示正在运行的脚本的数量,单击图标就可以看到正在运行的脚本和可能在这个网页上运行的脚本。
    • 脚本概览:Tampermonkey 概览清晰地显示所有安装的脚本。您可以看到它们最后的更新时间,如果它们有自己的主页,您还可以对它们进行分类和其他更多的功能。
    • 设置多样性:您可以为设置页面在三种不同的等级中进行选择。不常用的选项将被隐藏,通过这种方式来简化页面。
    • 脚本自动更新:您可以对脚本的检查更新频率进行设置。不再因为过时的脚本而产生漏洞。
    • 安全:可以使用正则自定义运行脚本的网站。
    • 兼容性:编辑的脚本不仅可以在 Chrome 上运行,也可以借助 Greasemonkey 在火狐上运行,同时脚本支持 ES6。
    • Chrome 同步:您正在使用多个 Chrome 浏览器,一个家用,一个工作用?您希望您可以同步自己的脚本?那么,您仅需设置 Tampermonkey 的同步功能。
    • CodeMirror 编辑器:TM 提供了一个嵌入式脚本编辑器,支持 JSHint 语法检查,减少错误,也可使用此编辑器直接引用本地的文件。

    作用

    TM 允许用户写脚本在特定的网站特定的时机运行,所以,我们可以对网站的功能、样式、交互,进行改造和扩展。

    最简单的就是修改样式的样式,修改背景色、文本颜色,高级一点对网站的数据进行控制,如一些广告拦截脚本、视频下载脚本、破解百度提取码、一键截图。

    快速安装

    如果浏览器是 Chrome 谷歌浏览器的话,并且具备访问外网的能力,直接访问这个链接下载:

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    如果不具备访问外网的能力,建议使用火狐浏览器来进行安装 TM 安装地址如下:

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    有关平台

    由于 TM 运行用户编写自己的脚本对网站功能进行扩展,于是就产生了很多优秀的脚本。这些脚本,存放在各个平台上面,可以供用户自由下载,使用。很多是源代码开放的,这里介绍以下几个用得比较多的平台。

    GreasyFork

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    它由 Jason Barnabe 创建,Jason Barnabe 同时也是 Stylish 网站的创办者,在其储存库中有大量的脚本资源。

    • 大量的脚本资源(在 2016 年 2 月份时,大约有 9400 个)
    • 拥有可以从 Github 中进行脚本同步的功能
    • 非常活跃的开放源代码发展模式

    OpenUserJS

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    你可以在这个网站搜索适用于某个网站的脚本,或根据功能进行搜索,非常方便。

    GitHub/Gist

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    GitHub 全球所有程序员的集中地,这里存在大量优秀的脚本,只要是善于搜索。

    TM 的 API 解读,及简单脚本的编写

    当我们点击 TM 的图标,在点击添加一个脚本的时候会出现这样的一个界面:

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    我们看到脚本是以几行格式化的注释开始的,而这些标准化的注释就是 TM 的配置参数,TM 前几行的注释都是标准化的,如:

    // @name         New Userscript
    
    • //:双斜杠是表示 JS 的单行注释开始
    • @name:表示要配置的 TM 的变量,@name 表示 TM 的脚本名称,后面空格加脚本的名称变量值
    • New Userscript:表示脚本的名称

    我们需要引入配置脚本运行的网站、版本、作者、描述,都是使用固定的标签来配置的。下面让我来对其一一讲解。

    基础 API 的解读

    由于基础 API 比较简单,一句话就能够说清楚,我这里就不花太多笔墨来解释了,有不懂的可以单独与我联系。

    • @name:脚本名称
    • @namespace:脚本的命名空间
    • @version:脚本的版本,用于检查更新。但需要用户设置更新频率
    • @author:脚本的作者
    • @description:简短重要的描述
    • @homepage、@homepageURL、@website and @source:用于从脚本名链接到给定页的作者主页
    • @icon、@iconURL、@defaulticon:低分率的脚本图标,会在脚本管理列表上显示
    • @icon64、@icon64URL:脚本 icon 64*64 如果给了这个标签,但给了图标,则图标图像将在选项页的某些位置缩放
    • @updateURL:更新脚本的地址,注意:只有存在 @version 标签才会去更新
    • @downloadURL:定义检测到更新时将从中下载脚本的 URL。如果值为 none,则不会执行更新检查。
    • @supportURL:使用者报告 issues 和个人支持的地址
    • @include:脚本应该运行的页面, 可以使用正则匹配。 允许多个标签, 请注意 @include 不支持 url hash 参数,可以访问这里获取更多的信息,这个标签很关键,一个脚本应该除了在预定的网站上完成它本来的功能外不应该在其他网站上运行,以防个人信息泄露,或造成不安全隐患。
    // @include  https://blog.csdn.net/github_35631540/article/details/102685580
    // @include  https://fizzz.blog.csdn.net/
    // @include  https://me.csdn.net/github_35631540
    
    • @match 和 @include 标签差不多的意思, 允许多个标记实例。
    • @exclude 排除 URL,即使它们包含在 @include 或 @match 中。允许多个标签。
    // @exclude   https://blog.csdn.net/github_35631540/article/details/102602675
    // @exclude   https://blog.csdn.net/github_35631540/article/details/102619223
    
    • @require 指向一个脚本文件,会在本脚本运行前加载并执行 我们可以使用这个配置 引入 jQuery 不过要注意:通过 @require 加载的脚本及其“use strict”语句可能会影响用户脚本的 strict 模式!
    // @require https://code.jquery.com/jquery-2.1.4.min.js
    // @require https://code.jquery.com/jquery-2.1.3.min.js#sha256=23456...
    // @require https://code.jquery.com/jquery-2.1.2.min.js#md5=34567...,sha256=6789...
    
    
    • @resource:预加载一些资源,HTML、JSON,可以通过脚本通过 gm_getresourceurl 和 gm_getresourcetext 访问的资源。
    // @resource icon1 http://www.tampermonkey.net/favicon.ico
    // @resource icon2 /images/icon.png
    // @resource html http://www.tampermonkey.net/index.html
    // @resource xml http://www.tampermonkey.net/crx/tampermonkey.xml
    // @resource SRIsecured1 http://www.tampermonkey.net/favicon.ico#md5=123434...
    // @resource SRIsecured2 // http://www.tampermonkey.net/favicon.ico#md5=123434.
    
    
    • @connect:此标记定义脚本链接的域(没有顶级域),包括允许由 GM_xmlhttpRequest 检索的子域。
    // @connect   value
    

    value 可以是以下几个值:

    • 域可以是:tampermokey.net(可以允许子域名),子域名如:safari.tampermokey.net
    • self:列出脚本当前运行的域
    • localhost 有权限访问 localhost 1.2.3.4 链接到 IP 地址

    如果无法声明用户脚本可能连接到的所有域,则最好执行以下操作:

    声明所有已知或至少所有可能由脚本连接的公共域。这样,大多数用户都可以避免确认对话框。

    另外在脚本中添加“@connect*”。通过这样做,Tampermonkey 仍然会询问用户是否允许下一个连接到未提及的域,但也会提供一个“总是允许所有域”按钮。如果用户单击此按钮,则将自动允许所有未来的请求。

    用户还可以通过在“脚本设置”选项卡的用户域白名单中添加“*”来白名单所有请求。

    允许多个标记实例。

    • @run-at:定义脚本被注入的时间,与其他脚本处理相反,@run-at 定义了脚本要运行的第一可能时间。

    这意味着,使用 @require 标记的脚本可能会在文档已加载后执行,因为获取所需脚本花费了很长时间。无论如何,在给定的注入时刻之后发生的所有 domnodeinserted 和 domcontentloaded 事件都将被缓存,并在注入时传递给脚本。

    // @run-at document-start // 脚本会被尽可能快地注入 
    // @run-at document-body // 当body元素存在是被注入 
    // @run-at document-end // 当DOMContentLoaded事件被触发时或者之后注入 
    // @run-at document-idle // 当DOMContentLoaded事件被触发后被注入 如果没有@run-at标签也是在此时注入 
    // @run-at context-menu // 当点击浏览器上下文菜单时被注入(仅仅是桌面Chrome-based浏览器) 
    // 注意:如果使用了context-menu @include和@exclude的变量都将被忽略,但是未来可能会改变` 
    
    • @grant

    @grant 被用于设置 GM_* 函数的白名单, GM_*function 是一些 unsafeWindow 对象和一些有影响的 window 函数,如果没有 @grant 标签,TM 会无法调用 GM_* 函数。

    // @grant GM_setValue  
    // @grant GM_getValue
    // @grant GM_setClipboard
    // @grant unsafeWindow
    // @grant window.close
    // @grant window.focus
    
    
    • @noframes:这个标签表明脚本在主页面上运行,而不是在 iframes 里。

    基础脚本编写,以修改 CSDN 样式、表单自动填写为例

    接下来给大家看一个我修改 CSDN 博客主页的脚本:

    // ==UserScript==
    // @name         New Userscript
    // @namespace    http://tampermonkey.net/
    // @version      0.1
    // @description  try to take over the world!
    // @author       You
    // @match        https://blog.csdn.net/github_35631540
    // @grant        none
    // ==/UserScript==
    
    (function() {
        'use strict';
        let injectStyle = document.createElement('style')
        injectStyle.innerHTML= 'html body{background-image: url(https://img-blog.csdnimg.cn/20191109172245482.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9maXp6ei5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70);}'
        document.querySelector('body').append(injectStyle)
    })();
    
    

    效果如下:

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    代码很简单的,就写了三行代码:

    • 首先创建一个 style 标签,然后填充 style 内容,将 html body 的背景图片修改一下。
    • 然后将 style 标签追加到 body 中,就这么简单。

    有的人会觉得,TM 追加网站样式一点要用这么 low 的方式吗?其实 TM 有提供高级函数,直接注入样式,上面的三行代码可以简化为一句 我们一步一步慢慢来。高级教程会讲到。

    再来看下一个例子,我们以 CSDN 申请博客专家的表单来演示自动填充表单脚本。

    // ==UserScript==
    // @name         New Userscript
    // @namespace    http://tampermonkey.net/
    // @version      0.1
    // @description  try to take over the world!
    // @author       You
    // @match        https://blog.csdn.net/experts/apply
    // @grant        none
    // ==/UserScript==
    
    (function() {
        'use strict';
        document.querySelector("#RealName").value = 'Fizz'
        document.querySelector("#Email").value = 'mengchen_0212@foxmail.com'
        document.querySelector("#Mobile").value = '1366666666'
        document.querySelector("#City").value = '深圳'
        document.querySelector("#Company").value = '大疆创新'
    })();
    
    

    我们使用 F12 可以很容易得到每个表单项的 id, 然后使用最简单的操作 DOM 的方式为表单赋值,我们也可以将一些参数放到 URL 里面,再使用脚本自动解析 URL 填充到表单里。

    这里要说一下,上传文件的 input 和富文本编辑器,虽然能够拿到表单选择器,但还需要真正赋值的方法,有时候需要稍微了解一下业务代码的逻辑,组装或模拟数据、事件。这些也是高级脚本的必修课。

    高级 API 应用程序接口

    在 TM 中,为了满足更多极客深度扩展网站,整合数据的需求,对外开发了更高层次的 API。这些 API 可以使你直接访问页面函数和变量、直接添加样式、存储数据(不跨域)、设置监听事件、使用 XHR和打开新的浏览器 Tab 页。下面让我们学习一下。

    unsafeWindow

    unsafeWindow 对象提供权限访问页面的 js 函数和变量 如下图,直接使用原页面的变量操作,此对象不用使用 @grant 获取权限。

    GM_addStyle(css)

    GM_addStyle 函数可以向 DOM 中直接添加 CSS 样式,参数是字符串样式。

    // @grant        GM_addStyle
    
    GM_addStyle(`html body{background-image: url(https://img-blog.csdnimg.cn/20191109172245482.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9maXp6ei5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)`)
    
    

    一行代码就可以为网站注入额外的样式。对于高级函数 GM_*function,由于这类函数,副作用比较大,所以在 TM 中使用时要事先获取使用函数的权限,相当于引入函数。

    如果没有 // @garnt GM_addStyle 这句注释的话,直接使用 GM_addStyle 会报错。

    在 TM 编写脚本时,有时会遇到临时存储数据的问题,TM 提供了一种方案:

    • GM_setValue(key,value) 向 storage 中存储一个 key,值为 value 的值

    • GM_getValue(key) 从 storage 中获取 key 的值

    • GM_listValues() 列出 storage 所有的值

    • GM_deleteValue(key) 从 storage 中删除 key 的值

      // @grant        GM_setValue
      // @grant        GM_getValue
      // @grant        GM_deleteValue
      // @grant        GM_listValues
      
      GM_setValue('time1', '2019年11月10日11:43:28')
      GM_setValue('time2', '2019年11月10日11:43:41')
      console.log('获取time',GM_getValue('time')
      console.log('storage中所有的值',GM_listValues())
      GM_deleteValue('time1')
      console.log('获取time1',GM_getValue('time1')
      console.log('storage中所有的值',GM_listValues())
      
      

    GM_openInTab(url,options)、GM_openInTab(url,loadInBackground)

    使用这个 API 可以打开一个新的浏览器标签页, url 是网页 URL,options 是一个对象,可以有以下几个值:

    • active 决定新的 tab 是否被聚焦,聚焦的意思是直接显示
    • insert 插入一个新的 tab 在当前的 tab 后面
    • setParent 在 tab 关闭后重新聚焦当前 tab
     // grant  GM_openInTab
     GM_openInTab('https://fizzz.blog.csdn.net/', {active: true, insert: true,setParent:true})
    
    

    此函数返回一个具有函数 close,侦听器 onclose 和一个名为 closed 的标志的对象,意思就是可以代码控制关闭新建的 Tab 页。

    GM_addValueChangeListener(name,function(name,old_value,new_value,remote) {})

    在 storage 里添加一个改变事件的监听,并返回监听 id,name 是被观察的变量。

    回调函数的 remote 变量是显示此值是从另一个选项卡的实例修改的(true)还是在此脚本实例中修改的(false)。

    因此,不同浏览器选项卡的脚本可以使用此功能相互通信。

    可以使用此 API 实现不同浏览器 Tab 的相互通讯,当 name 指向的是一个对象的时候,并且修改这个对象中的某个属性时 不会触发监听函数。只有 name 指向字符串、数组、布尔等基本数据类型是,可以触发监听函数,此函数返回一个 listenerId 用于移除监听事件。

    GM_removeValueChangeListener(listener_id)

    根据 GM_addValueChangeListener 函数返回的 listenerId 来移除一个监听改变的事件。具体用法示例如下。

    **GM_xmlhttpRequest **

    创建一个 XMLHttpRequest,具体的参数如下可以查看这里,这里就不一一讲解了,都是 HTTP 常规的一些属性。

    GM_download(details)、GM_download(url,name)

    下载资源到本地磁盘。

    details 的属性:

    • url:资源的 url
    • name:文件名,出于安全原因,文件的扩展名必须在 TM 参数页面的的白名单里
    • headers:如 GM_xmlhttpRequest 一样设置请求头部
    • saveAs:boolean 值,显示一个保存的弹窗
    • onerror:下载以失败结束执行的回调函数
    • onload 现在完成后执行的回调函数
    • onprogress 下载过程中变化的回调函数
    • ontimeout 下载超时执行的回调函数

    GM_notification(details,ondone)、GM_notification(text,title,image,onclick)

    显示一个 H5 的桌面通知,或者高亮当前 tab。

    details 的属性:

    • text:通知的问题 如果高亮就 就不需要
    • title:通知的标题
    • image:图片
    • highlight:一个 boolean 标志,是否高亮 tab
    • silent - 一个 boolean 是否播放音乐
    • timeout:通知显示的时间 0 表示 一直显示
    • ondone:通知被关闭时 无论是被点击还是超时 执行的函数
    • onclick:点击通知触发的函数

    所有参数的作用与其对应的详细信息属性挂件完全相同。

    GM_setClipboard(data,info)

    复制数据到粘贴板,参数 info 可以是对象如 {type: 'text', mimetype: 'text/plain'},或者是一个字符串 text、html。

    高级脚本的编写

    分析 技术方案 以 M2C 为例

    之前我写过一篇将 CSDN 文章迁移到慕课网的脚本编写博客, 写的很详细。点击链接可以查看详情 。 这次做 Chat 我就不拿以前的那篇文章来做高级脚本编写的案例了,这次使用新的脚本,将慕课网的手记迁移到 CSDN 项目简称为 M2C(慕课到 CSDN)。

    既然确定了效果,就想实现思路。

    首先拿到慕课的手记文章,这一步很容易,使用以下一行代码就可以实现:

    document.querySelector('.detail-content.js-lookimg').innerHTML
    

    效果如下:

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    拿到了文章内容就要打开 CSDN 的写博客的页面,将拿到的 HTML 内容填充进去,这一步也是最难的,为什么难?首先我们要知道对方网站使用的是什么富文本编辑器,其次我们要找到这个编辑器设置内容的 API,找到 API 了还不想,如果网站没有把富文本编辑器的对象没有暴露出来,那一切都还白搭了。拿不到对象,就不能调取设置内容的方法。这里我也给大家一个思考,如果拿不到编辑器对象,能不能把编辑器对象销毁掉,重新初始化一个,然后再调取设置内容的 API?

    废话不说,开始分析 CSDN 的富文本编辑器。

    这个网站有两个编辑器,一个是 CKEditor,一个是 Markdown 编辑器。

    使用的是 CKEditor 版本 1.5.8 DEV,最新的是 5+。大家想想这编辑器到底有多老了,大家 F12 可以很清楚地看到大片带有 CKEditor 为 class 标签。

    至于版本:

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    这么老的编辑器,我搜了一下相关的 API,官网都是英文的,最后花了好长时间找到这篇博客:

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    CKEDITOR.replace ("content") 来初始化编辑器,然后使用返回的对象设置或获取编辑器的内容,content 肯定是编辑器容器 id,无疑在这里就是 editor。我们在调试面板里使用 Ctrl+Shift+F 进行全局搜索 replace ('editor')

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    找到了两处,在控制台打印 bodyEditor 显示 bodyEditor is not defined,这是因为在这段代码的外层使用以下代码包裹:

     $(function () {
     })
    
    

    这样我们在 window 上就访问不到了 bodyEditor。拿不到 bodyEditor,就无法设置编辑器的值,这条路暂时是不通的。

    既然富文本编辑器不行,那我们就研究它的 Markdown 吧。 Markdown 就简单多了,直接一行代码搞定。

    document.querySelector('.editor .editor__inner .markdown-highlighting').innerHTML = '1324'
    
    

    使用 Tampermonkey 编写高级跨网站自动化任务脚本

    到这里分析和研究就结束了,那就开始写代码吧,完整代码如下:

    // ==UserScript==
    // @name         M2C
    // @namespace    https://blog.csdn.net/github_35631540
    // @version      0.1
    // @description  慕课文章迁移到csdn
    // @author       Fizz
    // @match        *://mp.csdn.net/*
    // @match        https://www.imooc.com/article/*
    // @grant        GM_addStyle
    // @grant        GM_setValue
    // @grant        GM_openInTab
    // @grant        GM_getValue
    // ==/UserScript==
    
    (function() {
        'use strict';
        const mukArticleUlr = `https://www.imooc.com/article` // 慕课文章链接前
        const pushArticleUrl = 'https://mp.csdn.net/mdeditor?not_checkout=1'
        let body = document.querySelector('body')
        let injectDiv = document.createElement('div')
        injectDiv.classList.add('myinject')
        injectDiv.innerHTML = `搬迁`
        injectDiv.onclick = function (e) {
            let articleContent = document.querySelector('.detail-content.js-lookimg').innerText
            let title = document.querySelector('.js-title').innerText
            // 使用GM_setValue存储文章数据
            GM_setValue('myTitle', title)
            GM_setValue('myArticleContent', articleContent)
            GM_openInTab(pushArticleUrl, { active: true, insert: true, setParent :true }) // 打开慕课发布文章tab, active参数表示在tab关闭后重新聚焦当前tab
        }
    
      // 注入的样式,搬迁按钮的样式
      let injectStyle = `
       .myinject{
        width: 50px;
        height: 50px;
        border-radius: 50%;
        background-color: #fec04e;
        border-color: #fec04e;
        position: fixed;
        bottom: 20px;
        right: 20px;
        opacity: .6;
        color: brown;
        line-height: 50px;
        vertical-align: middle;
        text-align: center;
        cursor: pointer;
      }
      .myinject:hover{
        opacity: 1;
        color: #fff;
      }`
    
      let href = location.href
      // 根据当前url判断 是慕课文章页面还是csdn发布博客文章页面 从而进行不同的操作
      if (href.startsWith(pushArticleUrl)) {
          console.log(GM_getValue('myTitle'))
          console.log(GM_getValue('myArticleContent'))
        let title = GM_getValue('myTitle')
        let articleContent = GM_getValue('myArticleContent')
        setTimeout(x => {
          document.querySelector(".article-bar__title").value = title
          // 将文章内容填充到markdown编辑器里
          document.querySelector('.editor .editor__inner.markdown-highlighting').innerHTML = articleContent
        },2000)
      }
    
      if(href.startsWith(mukArticleUlr)) {
        GM_addStyle(injectStyle)
        body.appendChild(injectDiv)
      }
    
    })();
    
    

    这段代码只能将文章的文本填充到编辑器中,代码、图片、视频等多媒体格式格式,转存支持不友好。还有很大优化空间。

    总结

    我们要学会利用技术提升自己的浏览信息、操作信息的效率,减去负担。我的微信 demon_0212,有任何疑问都可以和我来讨论,备注 Chat 谢谢大家的参与。



    起源地下载网 » 使用 Tampermonkey 编写高级跨网站自动化任务脚本

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元