安卓端微信内置浏览器采坑记录:document.documentElement.scrollHeight问题

本文探讨了在安卓手机微信内置浏览器中,document.documentElement.scrollHeight获取值与预期不符的问题,指出问题出在两者不一致上,并提供了将document.documentElement.scrollHeight更改为document.body.scrollHeight来解决问题的方法。

安卓手机微信内置浏览器里document.documentElement.scrollHeight有问题, 它获取到的值等于document.documentElement.clientHeight  , 解决办法是把document.documentElement.scrollHeight改成document.body.scrollHeight

<template> <view class="webview-container"> <cover-view class="webview-wrapper"> <web-view :src="webviewPath" id="targetWebview" allow-same-origin sandbox="allow-same-origin allow-scripts" ref="webviewRef"></web-view> </cover-view> <cover-view class="bottom-action-area"> <cover-view class="capture-btn" @click="captureWebview"> <cover-view class="btn-text">立即取证</cover-view> </cover-view> </cover-view> <!-- 预览组件 --> <cover-view v-if="showPreview" class="custom-preview"> <cover-view class="preview-header"> <cover-view class="title-container"> <cover-view class="preview-title">预览界面</cover-view> </cover-view> </cover-view> <cover-image :src="screenshotPath" class="preview-image" mode="aspectFit" @error="handleImageError" ></cover-image > <cover-view class="action-buttons"> <cover-view class="action-btn cancel-btn" @click="handleAction('放弃')">放弃</cover-view> <cover-view class="action-btn confirm-btn" @click="handleAction('固化')">固化</cover-view> </cover-view> <cover-view> </cover-view> </cover-view> </view> </template> <script setup lang="ts" > import { onLoad, onReady } from '@dcloudio/uni-app'; import html2canvas from 'html2canvas'; import { nextTick, ref } from 'vue'; declare const plus: any; const webviewPath = ref(''); const ws = ref<any>(null); const screenshotPath = ref(''); const showPreview = ref(false); const platform = ref(''); const originalWebviewHeight = ref('auto'); const screenshotFilePath = ref(''); const screenshot = ref(''); const iframeLoaded = ref(false); onLoad((options: any) => { if (options.url) { webviewPath.value = decodeURIComponent(options.url); } // 获取当前平台 const systemInfo = uni.getSystemInfoSync(); platform.value = systemInfo.platform; }); onReady(() => { // #ifdef APP-PLUS const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; const currentWebview = currentPage.$getAppWebview(); setTimeout(() => { if (currentWebview.children().length > 0) { const wv = currentWebview.children()[0]; ws.value = wv; // originalWebviewHeight.value = wv.getStyle().height || 'auto'; wv.setStyle({ height: 'auto' }); } }, 1000); // #endif // #ifdef H5 const iframe = document.getElementById('targetWebview'); console.log(iframe) if (!iframe) { console.warn('未找到 iframe 元素'); return; } // 设置加载监听 // #endif }); const captureWebview = async () => { uni.showLoading({ title: '正在取证中' }) // #ifdef APP-PLUS if (!ws.value) { uni.showToast({ title: '网页未加载完成', icon: 'none' }); return; } const bitmap = new plus.nativeObj.Bitmap('screenshot') const rand = Math.floor(Math.random() * 10000) const saveUrl = '_doc/' + rand + 'order.jpg' // 绘制页面截图 ws.value.draw(bitmap, () => { // 保存图片 bitmap.save( saveUrl, { format: 'jpg', quality: 90 }, (res:any) => { // 将本地路径转换为可以展示的路径 const previewPath = plus.io.convertLocalFileSystemURL(res.target) screenshotPath.value = previewPath screenshotFilePath.value = res.target; // 保存文件路径 console.log('截图保存成功,预览路径:', previewPath) // 延迟展示,确保加载完成 setTimeout(() => { showPreview.value = true uni.hideLoading() }, 300) bitmap.clear() // 清理 bitmap 占用资源 }, (err:any) => { console.error('保存失败:', err) uni.hideLoading() uni.showToast({ title: '保存失败: ' + err.message, icon: 'none', }) bitmap.clear() } ) }, (err:any) => { console.error('截图失败:', err) uni.showToast({ title: '截图失败: ' + err.message, icon: 'none', }) uni.hideLoading() }) // #endif // #ifdef H5 const iframe = document.querySelector<HTMLIFrameElement>('#targetWebview'); console.log(iframe) if (!iframe) { throw new Error("未找到 iframe 元素"); } if (iframe.contentDocument.readyState !== 'complete') { uni.showToast({ title: '网页未加载完成', icon: 'none' }); uni.hideLoading(); return; } const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; console.log(iframeDoc) const height = Math.max( iframeDoc.body.scrollHeight, iframeDoc.documentElement.scrollHeight, iframeDoc.body.offsetHeight, iframeDoc.documentElement.offsetHeight, iframeDoc.body.clientHeight, iframeDoc.documentElement.clientHeight ); html2canvas(iframeDoc.body, { width: iframe.scrollWidth, // 使用滚动宽度 height: iframe.scrollHeight, // 使用滚动高度 scrollX: -iframe.scrollLeft, // 修正滚动偏移 scrollY: -iframe.scrollTop, // 修正滚动偏移 useCORS: true, // 启用跨域支持 allowTaint: true, // 允许污染画布 logging: true, // 开启调试日志 backgroundColor: null }).then((canvas)=>{ let base64 = canvas.toDataURL('image/png'); screenshotPath.value = base64; // console.log(screenshotPath.value) // 这里就按照chrome等新版浏览器来处理 // const a = document.createElement('a'); // a.href = base64; // a.setAttribute('download', 'pigeons-download'); // a.click(); setTimeout(() => { showPreview.value = true uni.hideLoading() }, 300) }).catch((err)=>{ console.error('截屏失败:', err); uni.showToast({ title: '截屏失败,请重试', icon: 'none' }); uni.hideLoading(); }) // #endif } const closePreview = () => { showPreview.value = false; } //上传 const uploadEvidence = () => { // #ifdef APP-PLUS // APP环境上传 plus.io.resolveLocalFileSystemURL(screenshotFilePath.value, (entry:any) => { console.log('文件存在,开始上传:', entry.name); // 继续执行上传逻辑 uni.showLoading({ title: '上传证据中...', mask: true }); // 获取文件系统路径 const filePath = entry.toLocalURL(); uni.uploadFile({ url: 'http://192.168.1.80:1592/api/upload', filePath: filePath, name: 'file', type: 'image', formData: { 'timestamp': Date.now() }, success: (uploadRes) => { uni.hideLoading(); try { const data = JSON.parse(uploadRes.data); if (data.success) { uni.showToast({ title: '证据上传成功', icon: 'none' }) } else { uni.showToast({ title: '上传失败', icon: 'none' }) console.log('上传失败') } } catch (e) { uni.showToast({ title: '解析响应失败', icon: 'none' }) } // 3秒后关闭状态提示 setTimeout(() => { closePreview(); }, 3000); }, fail: (err) => { uni.hideLoading(); console.error('上传错误详情:', err); uni.showToast({ title: '网络错误'+ err.errMsg, icon: 'none' }) setTimeout(() => { }, 3000); } }); }, (err:any) => { console.error('文件不存在或路径错误:', err); uni.showToast({ title: '文件路径无效', icon: 'none' }); } ); uni.showLoading({ title: '上传证据中...', mask: true }); // #endif // #ifdef APP-H5 // H5环境上传 // if (!screenshotFilePath.value) { // uni.hideLoading(); // uni.showToast({ title: '文件路径无效', icon: 'none' }); // return; // } // 获取文件对象 // fetch(screenshotFilePath.value) // .then(res => res.blob()) // .then(blob => { // const formData = new FormData(); // formData.append('evidence', blob, `evidence-${Date.now()}.jpg`); // formData.append('timestamp', Date.now().toString()); // return fetch('http://192.168.1.80:1592/api/upload', { // method: 'POST', // body: formData // }); // }) // .then(response => response.json()) // .then(data => { // uni.hideLoading(); // if (data.success) { // uni.showToast({ title: '证据上传成功', icon: 'none' }); // } else { // uni.showToast({ title: '上传失败'+ data.message, icon: 'none' }); // } // setTimeout(() => { // closePreview(); // }, 3000); // }) // .catch(err => { // uni.hideLoading(); // uni.showToast({ title: '上传失败'+ err.message, icon: 'none' }) // setTimeout(() => { // }, 3000); // }); // #endif } const handleAction = (action: string) => { switch(action) { case '放弃': closePreview(); break; case '固化': // #ifdef APP-PLUS uni.saveImageToPhotosAlbum({ filePath: screenshotPath.value, success: () => { uni.showToast({ title: '证据已固化保存' }); // 调用上传函数 uploadEvidence(); }, fail: (err) => { uni.showToast({ title: '保存失败: ' + err.errMsg, icon: 'none' }); } }); // #endif // #ifdef APP-H5 // #endif break; } } const handleImageError = (e: any) => { console.error('图片加载错误:', e); uni.showToast({ title: '图片加载错误', icon: 'none' }); } </script> <style> .web-view { height: 100% !important; width: 100% !important; display: block !important; } .webview-container { position: relative; width: 100%; height: 100vh; overflow: hidden; } .webview-wrapper { height: calc(100vh - 120rpx); width: auto !important; overflow: visible !important; } .bottom-action-area { position: fixed; bottom: 0; left: 0; right: 0; z-index: 100; background-color: #007aff; padding: 30rpx; padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); display: flex; justify-content: center; align-items: center; } .capture-btn { width: 100%; height: 90rpx; background-color: #007aff; border-radius: 45rpx; box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.2); display: flex; align-items: center; justify-content: center; } .btn-text { color: #ffffff; font-size: 32rpx; font-weight: 500; } /* 优化预览组件 */ .custom-preview { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; background-color: rgba(0, 0, 0, 0.9); display: flex; flex-direction: column; /* 垂直排列子元素 */ align-items: center; /* 关键修改:去掉顶部内边距,只保留底部内边距 */ padding: 0 0 40rpx 0; box-sizing: border-box; } .preview-header { width: 100%; height: 250rpx; /* 增加高度 */ background-color: #007aff; color: #ffffff; font-size: 34rpx; /* 增大字体 */ font-weight: bold; display: flex; align-items: center; justify-content: center; /* 添加圆角效果 */ border-top-left-radius: 16rpx; border-top-right-radius: 16rpx; } .preview-title { color: #ffffff; /* 白色字体 */ font-size: 28rpx; /* 字体大小适配40rpx高度 */ font-weight: bold; line-height: 40rpx; /* 与容器高度一致,实现垂直居中 */ width: 100%; text-align: center; } .preview-image { width: 100%; height: 100%; object-fit: contain; border: 1rpx solid rgba(255, 255, 255, 0.1); border-radius: 8rpx; background-color: #f5f5f5; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3); } .action-buttons { display: flex; width: 100%; /* 充满整个宽度 */ justify-content: space-between; padding: 30rpx 5%; /* 左右留5%间隙,上下30rpx内边距扩展背景区域 */ margin-top: auto; /* 借助flex布局推到最底部 */ background-color: #353336; /* 底部模块背景色 */ box-sizing: border-box; /* 确保padding不影响宽度计算 */ } .action-btn { flex: 1; height: 90rpx; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; padding: 0; margin: 0 15rpx; border: none; color: #ffffff; /* 文字色适配深色背景 */ font-size: 34rpx; font-weight: 500; line-height: 1; } .cancel-btn { color: #ffffff; /* 文字颜色 */ font-size: 34rpx; font-weight: 500; /* 确保文字本身无偏移 */ line-height: 1; /* 清除行高影响 */ text-align: center; /* 辅助居中(冗余保障) */ background-color: #353336; } .confirm-btn { color: #ffffff; /* 文字颜色 */ font-size: 34rpx; font-weight: 500; /* 确保文字本身无偏移 */ line-height: 1; /* 清除行高影响 */ text-align: center; /* 辅助居中(冗余保障) */ background-color: #353336; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .bottom-action-img{ position: fixed; top: 100; left: 0; right: 0; z-index: 100; background-color: #007aff; padding: 30rpx; display: flex; justify-content: center; align-items: center; } .action-img{ width: 100%; height: 90rpx; background-color: #007aff; display: flex; } </style> 怎样将app内截图截取可视改为全部内容
最新发布
08-07
<think>我们面对的问题是如何在uni-app中使用html2canvas(或类似功能的库)来截取整个页面的内容,而不仅仅是可视区域。 由于uni-app是一个跨平台框架,我们需要考虑多兼容性(H5、小程序、App等)。然而,html2canvas主要用于浏览器环境(H5),所以我们的解决方案主要针对H5。 对于非H5(如小程序和App),可能需要用不同的方法,因为html2canvas无法直接在这些环境中运行。 根据引用[2]和[4],我们知道uni-app中可以使用canvas相关操作,但html2canvas并不直接支持小程序或App。因此,我们需要分平台处理: 1. H5:我们可以使用html2canvas库来截取整个页面。 2. 非H5(小程序、App):我们需要使用原生或跨平台的截图方案,例如使用uni-app的API(如uni.canvasToTempFilePath)或者使用wxml-to-canvas(针对小程序)等。 由于用户的问题中提到了“html2canvas”,我们首先考虑H5的解决方案。 步骤: 1. 在H5,我们可以使用html2canvas库。首先需要安装html2canvas。 2. 在需要截图的时候,调用html2canvas并传入整个页面的根节点(例如document.body),并设置一些选项以确保截取整个页面(包括滚动区域)。 但是,需要注意的是,html2canvas在H5截取整个页面时,需要处理页面滚动和元素位置。我们可以设置`scrollX`和`scrollY`为0,并设置`windowHeight`为整个页面的高度。 然而,html2canvas本身并不直接支持截取整个页面(包括超出可视区域的部分),但我们可以通过设置`scrollX`和`scrollY`为0,并且将目标元素设置为整个文档的根元素(即document.documentElement)来获取整个页面的截图。 具体实现思路(H5): - 安装html2canvas:`npm install html2canvas` - 在页面中引入:`import html2canvas from 'html2canvas'` - 调用html2canvas时,传入document.documentElement(整个页面的根元素)并设置选项: html2canvas(document.documentElement, { scrollX: 0, scrollY: 0, width: document.documentElement.scrollWidth, height: document.documentElement.scrollHeight }).then(canvas => { // 将canvas转换为图片 }); 但是,这种方法可能会遇到问题,因为html2canvas在渲染过程中会处理元素的样式和位置,包括滚动条。因此,我们需要确保页面没有滚动条(或者隐藏滚动条)以避免截取时出现滚动条。 另外,引用[4]提到了使用uni.createCanvasContext来操作canvas,这是在uni-app中操作canvas的通用方法。但是,html2canvas在H5使用,不需要使用uni.createCanvasContext,因为它是浏览器环境。 对于非H5,我们无法使用html2canvas,所以需要用其他方法: 小程序微信、支付宝等): 我们可以使用小程序的API:`wx.createSelectorQuery()`选择节点,然后使用`wx.createCanvasContext`绘制到canvas上,但绘制整个页面非常复杂,因为需要递归绘制所有节点,并且样式支持有限。 另一种方案是使用`wxml-to-canvas`(引用[2]提到),它可以将wxml结构转换为canvas绘制,但同样需要自己定义样式和布局,并且只能绘制部分节点(不能是整个页面)。 还有一种思路是使用小程序的`page.onShareAppMessage`中的截图API,但这是分享时截取当前页面,并且只能截取可视区域。 因此,对于小程序,如果必须截取整个页面,可能需要将页面拆分成多个部分分别绘制到canvas上,然后拼接。这非常复杂,且性能可能不佳。 App: App可以使用原生截图,如使用`plus.nativeObj.Bitmap`,但同样只能截取可视区域。或者使用web-view的整个网页截图(如果页面是web-view加载的),但uni-app的页面不是web-view。 考虑到跨平台实现的复杂性,我们可能需要分平台处理: - 在H5,使用html2canvas截取整个页面。 - 在非H5(小程序、App),如果只需要截取可视区域,可以使用uni-app自带的截图API(如uni.canvasToTempFilePath,但注意它只能截取canvas组件的内容,或者使用uni.saveScreenShot,但只能截取屏幕可视区域)。如果非H5也需要截取整个页面,可能需要用其他方式,比如将页面内容分块绘制到多个canvas上然后拼接,或者使用原生插件。 因此,我们给出一个以H5为重点的解决方案,非H5则提示用户当前只能截取可视区域(或者使用其他折中方案)。 具体代码实现: 1. 安装html2canvas(在项目根目录执行): ```bash npm install html2canvas ``` 2. 在需要使用的页面中引入: ```javascript import html2canvas from 'html2canvas'; ``` 3. 编写截图方法(注意区分平台): ```javascript methods: { captureFullPage() { // 判断平台 // #ifdef H5 this.captureH5FullPage(); // #endif // #ifndef H5 // 非H5平台,我们只能截取可视区域,或者提示用户不支持 uni.showToast({ title: '非H5暂不支持截取整个页面', icon: 'none' }); // 或者调用截取可视区域的方法 // this.captureVisibleArea(); // #endif }, captureH5FullPage() { // 在H5使用html2canvas const element = document.documentElement; // 整个页面的根元素 // 获取整个页面的宽高 const width = element.scrollWidth; const height = element.scrollHeight; html2canvas(element, { scrollX: 0, scrollY: 0, width: width, height: height, useCORS: true, // 如果图片有跨域问题,可以设置这个选项 allowTaint: true // 如果不需要跨域图片,可以设置为false,需要跨域则true }).then(canvas => { // 将canvas转换成图片 const imgData = canvas.toDataURL('image/png'); // 这里可以保存图片或者显示预览 // 例如,显示预览 this.previewImage = imgData; // 或者保存到本地 // 注意:在H5,保存图片可以使用a标签下载 const link = document.createElement('a'); link.href = imgData; link.download = 'fullpage-screenshot.png'; link.click(); }).catch(err => { console.error('截图失败', err); }); } } ``` 4. 在模板中,我们可以添加一个按钮来触发截图: ```html <button @click="captureFullPage">截图整个页面</button> ``` 注意事项: - 由于html2canvas在渲染过程中会遍历整个DOM树并绘制到canvas上,因此对于复杂的页面,可能会非常耗时,并且可能会占用大量内存。 - 跨域图片问题:如果页面中有跨域图片,需要确保图片服务器允许跨域,否则html2canvas可能无法绘制这些图片。可以通过设置`useCORS: true`和`allowTaint: true`来尝试解决,但注意`allowTaint`设置为true可能会导致canvas被污染,从而无法使用toDataURL方法(除非图片服务器设置了CORS)。 - 非H5,我们暂时没有提供完整的解决方案。如果非H5也需要截取整个页面,可能需要寻找其他插件或者自己实现。例如,在小程序,可以使用`wx.createSelectorQuery`获取所有节点信息,然后使用canvas绘制,但这非常复杂。 另外,引用[3]提到了使用canvas绘制验证码,这展示了uni-app中canvas的基本用法,但和整个页面截图无关。 总结:我们的方案主要解决H5的整个页面截图,非H5需要另外考虑。 相关问题: 1. 在uni-app中,如何实现跨平台的整个页面截图? 2. 使用html2canvas时,如何处理跨域图片? 3.微信小程序中,如何实现截取整个页面的功能? 4. 如何优化html2canvas的截图性能?</think>在 Uni-app 中实现全页面截图(而非仅可视区域)需要根据平台特性用不同方案。以下是完整实现方案: ### 一、H5 解决方案(使用 html2canvas) ```javascript // 安装依赖 npm install html2canvas // 页面中使用 import html2canvas from 'html2canvas'; async function captureFullPage() { // 获取完整页面高度 const pageHeight = document.documentElement.scrollHeight; // 创建临时容器 const container = document.createElement('div'); container.style.overflow = 'hidden'; container.style.width = document.documentElement.scrollWidth + 'px'; container.style.height = pageHeight + 'px'; // 克隆页面内容 const clone = document.documentElement.cloneNode(true); container.appendChild(clone); document.body.appendChild(container); try { // 生成截图 const canvas = await html2canvas(container, { useCORS: true, // 允许跨域图片 scale: window.devicePixelRatio, logging: false, height: pageHeight }); // 获取图片数据 const imgData = canvas.toDataURL('image/png'); // 保存图片 const link = document.createElement('a'); link.href = imgData; link.download = 'fullpage-screenshot.png'; link.click(); } finally { // 清理DOM document.body.removeChild(container); } } ``` ### 二、小程序解决方案(使用 Canvas 拼接) ```javascript // 使用 uni.createSelectorQuery 获取所有节点 const query = uni.createSelectorQuery().in(this); query.selectAll('.page-section').fields({ rect: true, node: true }, (res) => { const ctx = uni.createCanvasContext('fullpageCanvas'); let currentY = 0; res.forEach((item, index) => { // 绘制每个区块 ctx.drawImage(item.node, 0, currentY, item.width, item.height); currentY += item.height; // 最后一块绘制完成后保存 if (index === res.length - 1) { ctx.draw(false, () => { uni.canvasToTempFilePath({ canvasId: 'fullpageCanvas', success: (res) => { uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath }); } }); }); } }); }).exec(); ``` ### 三、跨平台兼容方案 ```javascript export default { methods: { async captureScreenshot() { // #ifdef H5 await this.captureH5FullPage(); // #endif // #ifdef MP-WEIXIN this.captureMiniProgramFullPage(); // #endif // #ifdef APP-PLUS this.captureAppFullPage(); // #endif }, // H5实现(见方案一) captureH5FullPage() { /* ... */ }, // 小程序实现(见方案二) captureMiniProgramFullPage() { /* ... */ }, // App实现 captureAppFullPage() { const webview = this.$scope.$getAppWebview(); webview.captureSnapshot({ format: 'png', height: webview.getContentHeight(), success: (bitmap) => { const path = plus.io.convertBitmap(bitmap); plus.gallery.save(path); } }); } } } ``` ### 关键注意事项 1. **H5跨域问题**: ```javascript html2canvas(element, { useCORS: true, // 允许加载跨域图片 allowTaint: false // 禁止污染画布 }); ``` 2. **滚动条处理**: ```css /* 截图时隐藏滚动条 */ .capturing ::-webkit-scrollbar { display: none; } ``` 3. **性能优化**: ```javascript // 分块渲染避免内存溢出 const CHUNK_HEIGHT = 2000; for (let y = 0; y < pageHeight; y += CHUNK_HEIGHT) { const height = Math.min(CHUNK_HEIGHT, pageHeight - y); await html2canvas(element, { y: y, height: height, // ... }); } ``` 4. **元素可见性处理**: ```javascript // 截图前显示隐藏元素 document.querySelectorAll('.hidden-element').forEach(el => { el.style.visibility = 'visible'; }); ``` > **重要提示**:全页面截图可能引发性能问题,建议: > 1. 限制最大截图高度(如 10000px) > 2. 添加加载提示 > 3. 复杂页面优先考虑服务渲染方案 --- ### 相关问题 1. 如何处理截图时动态加载的图片资源? 2.微信小程序中如何实现超过屏幕高度的 Canvas 绘制? 3. 截图过程中如何保持 SVG 图标的清晰度? 4. Uni-app 中如何实现跨平台的图片保存到相册功能? 5. 如何解决 html2canvas 截图模糊的问题?[^1] 6. Canvas 绘制时如何处理 fixed 定位元素?[^2] 7. 在 App 如何获取 Webview 的完整内容高度?[^3] 8. 截图过程中如何避免页面闪烁?[^4] [^1]: 参考 html2canvas 官方文档推荐的 DPR 适配方案 [^2]: 小程序 fixed 元素需要单独定位绘制 [^3]: 使用 `plus.webview.currentWebview().getContentHeight()` 获取 [^4]: 通过创建离屏容器避免页面布局抖动
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值