[Android] Web Console: Uncaught TypeError: Object [object Object] has no method 'xxx'

JS回调Native在Android4.2+的兼容性问题

我们开发的产品,有一部分功能,需要在WebView中打开web页面,然后在web页面中通过js方法回调部分native的功能。

对于web回调native的开发方式,如果不了解的话,可以参考我以前的一篇博文《Android中Webview使用自定义的javascript进行回调》http://blog.youkuaiyun.com/arui319/article/details/7044638 

最近测试发现,在某些最新机型上(4.2及以上),JS回调好像不起做用了。打开log,提示Uncaught TypeError: Object [object Object] has no method 'xxx' 。其中xxx就是web页面中写的js方法名。

仔细研究,发现是因为Android4.2及以上版本对于js的支持方式有改变导致(又是一起新版本导致的不兼容事件,最近这种事情越来越多了)。具体的,请看下面这篇文章,写的很详细了,没有必要再写一遍了,请直接参考吧。http://blog.youkuaiyun.com/zgjxwl/article/details/9627685

记录于此,方便网友查询。

转载于:https://www.cnblogs.com/yuzhongwusan/p/4213412.html

vue.runtime.esm.js:4664 [Vue warn]: Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option. warn @ vue.runtime.esm.js:4664 set @ vue.runtime.esm.js:1060 startReadAloud @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/h5/TourRouteMap.vue?vue&type=script&lang=js:579 navigateTo @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/h5/TourRouteMap.vue?vue&type=script&lang=js:535 click @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[3]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/h5/TourRouteMap.vue?vue&type=template&id=4f502143&scoped=true:53 invokeWithErrorHandling @ vue.runtime.esm.js:3074 invoker @ vue.runtime.esm.js:1876 original_1._wrapper @ vue.runtime.esm.js:7535 4.26/:150 [esri.views.3d.layers.graphics.Graphics3DCore] Graphic in layer 198c009dfe4-layer-6 has no geometry and will not render m._consoleWriter @ 4.26/:150 m._log @ 4.26/:149 m.warn @ 4.26/:148 Ca._getRenderingInfo @ SceneView.js:10864 Ca._addImmediate @ SceneView.js:10850 Ca.add @ SceneView.js:10849 Ca._graphicsCollectionChanged @ SceneView.js:10834 (anonymous) @ SceneView.js:10815 u @ 4.26/:1614 G._dispatchChange @ 4.26/:81 (anonymous) @ 4.26/:76 r @ 4.26/:187 (anonymous) @ 4.26/:190 index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/components/MapComponentTour.vue?vue&type=script&lang=js:1688 Uncaught TypeError: this.graphicsLayerCompanion.refresh is not a function at eval (index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/components/MapComponentTour.vue?vue&type=script&lang=js:1688:37)
08-20
https://github.com/vuejs/vue-devtools element-ui.common.js:29286 POST http://localhost:9876/uplodPython 404 (Not Found) upload @ element-ui.common.js:29286 post @ element-ui.common.js:29592 upload @ element-ui.common.js:29520 eval @ element-ui.common.js:29511 uploadFiles @ element-ui.common.js:29509 handleChange @ element-ui.common.js:29490 invokeWithErrorHandling @ vue.runtime.esm.js:3058 invoker @ vue.runtime.esm.js:1859 original_1._wrapper @ vue.runtime.esm.js:7508 uplodPython:1 Access to fetch at 'http://localhost:9090/api/api/uploadPython' from origin 'http://localhost:9876' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/uplodPython.vue?vue&type=script&lang=js:98 POST http://localhost:9090/api/api/uploadPython net::ERR_FAILED 200 (OK) handleFileUpload @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/uplodPython.vue?vue&type=script&lang=js:98 handleError @ element-ui.common.js:29887 onError @ element-ui.common.js:29588 onload @ element-ui.common.js:29267 XMLHttpRequest.send upload @ element-ui.common.js:29286 post @ element-ui.common.js:29592 upload @ element-ui.common.js:29520 eval @ element-ui.common.js:29511 uploadFiles @ element-ui.common.js:29509 handleChange @ element-ui.common.js:29490 invokeWithErrorHandling @ vue.runtime.esm.js:3058 invoker @ vue.runtime.esm.js:1859 original_1._wrapper @ vue.runtime.esm.js:7508 index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/uplodPython.vue?vue&type=script&lang=js:98 Uncaught (in promise) TypeError: Failed to fetch at VueComponent.handleFileUpload (index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/uplodPython.vue?vue&type=script&lang=js:98:7) at VueComponent.handleError (element-ui.common.js:29887:12) at Object.onError (element-ui.common.js:29588:18) at XMLHttpRequest.onload (element-ui.common.js:29267:21) handleFileUpload @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/uplodPython.vue?vue&type=script&lang=js:98 handleError @ element-ui.common.js:29887 onError @ element-ui.common.js:29588 onload @ element-ui.common.js:29267 XMLHttpRequest.send upload @ element-ui.common.js:29286 post @ element-ui.common.js:29592 upload @ element-ui.common.js:29520 eval @ element-ui.common.js:29511 uploadFiles @ element-ui.common.js:29509 handleChange @ element-ui.common.js:29490 invokeWithErrorHandling @ vue.runtime.esm.js:3058 invoker @ vue.runtime.esm.js:1859 original_1._wrapper @ vue.runtime.esm.js:7508 uplodPython:1 Access to fetch at 'http://localhost:9090/api/api/uploadPython' from origin 'http://localhost:9876' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/uplodPython.vue?vue&type=script&lang=js:98 POST http://localhost:9090/api/api/uploadPython net::ERR_FAILED 200 (OK) handleFileUpload @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/uplodPython.vue?vue&type=script&lang=js:98 handleStart @ element-ui.common.js:29859 eval @ element-ui.common.js:29510 uploadFiles @ element-ui.common.js:29509 handleChange @ element-ui.common.js:29490 invokeWithErrorHandling @ vue.runtime.esm.js:3058 invoker @ vue.runtime.esm.js:1859 original_1._wrapper @ vue.runtime.esm.js:7508 index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/uplodPython.vue?vue&type=script&lang=js:98 Uncaught (in promise) TypeError: Failed to fetch at VueComponent.handleFileUpload (index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/uplodPython.vue?vue&type=script&lang=js:98:7) at VueComponent.handleStart (element-ui.common.js:29859:12) at eval (element-ui.common.js:29510:15) at Array.forEach (<anonymous>) at VueComponent.uploadFiles (element-ui.common.js:29509:17) at VueComponent.handleChange (element-ui.common.js:29490:12) at invokeWithErrorHandling (vue.runtime.esm.js:3058:30) at HTMLInputElement.invoker (vue.runtime.esm.js:1859:20) at original_1._wrapper (vue.runtime.esm.js:7508:35)<template> <div class="classifier-container"> <el-card class="box-card"> <h2 style="text-align: center; color: #1890ff">🐾 动物图像分类器</h2> <p style="text-align: center; color: #666; margin-bottom: 30px"> 支持识别:猫(cat)、狗(dog)、鱼(fish)、大象(elephant) </p> <!-- 图片上传区 --> <el-upload class="upload-box" drag action="#" :auto-upload="true" :show-file-list="false" :on-change="handleFileUpload" accept="image/jpeg, image/jpg, image/png" > <i class="el-icon-upload"></i> <div class="el-upload__text"> 将图片拖到此处,或点击选择图片 </div> <div class="el-upload__tip" slot="tip"> 支持 JPG、PNG 格式,大小不超过 5MB </div> </el-upload> <!-- 预览与加载状态 --> <div v-if="previewImage" class="preview-section"> <h4>🖼 图像预览</h4> <img :src="previewImage" alt="预览" class="preview-img" /> <div v-if="loading" class="loading-box"> <el-progress type="circle" :percentage="progress" :width="80" ></el-progress> <p style="margin-top: 10px; color: #409eff">推理中...</p> </div> <!-- 预测结果展示 --> <div v-if="result && !loading" class="result-section"> <h4>✅ 识别结果</h4> <el-alert :title="`检测到: ${result.predicted_class} (置信度: ${result.confidence})`" :type="getClassType(result.predicted_class)" show-icon :closable="false" ></el-alert> <!-- 概率分布图表 --> <div class="chart-box"> <h5>📊 各类别概率分布:</h5> <el-bar-chart :data="chartData" /> </div> </div> </div> </el-card> </div> </template> <script> // 简易条形图组件(无需 echarts,使用纯 CSS + Element UI) const ElBarChart = { props: ["data"], render(h) { return h( "div", { style: "margin-top: 10px;" }, this.data.map((item) => h("div", { style: "margin: 12px 0;" }, [ h( "span", { style: "display:inline-block;width:80px;color:#666;" }, item.label ), h("el-progress", { props: { percentage: item.value * 100, strokeWidth: 14, "text-inside": true, status: item.isMax ? "success" : "", }, style: "width: calc(100% - 90px); display:inline-block; vertical-align: middle;", }), ]) ) ); }, }; export default { components: { ElBarChart }, data() { return { previewImage: null, // 预览图片 base64 loading: false, // 是否正在推理 progress: 0, // 进度条 result: null, // 推理返回结果 uploadUrl: "http://localhost:9090/api/api/uploadPython", // Spring Boot 后端地址 classColors: { cat: "success", dog: "primary", fish: "info", elephant: "warning", }, }; }, computed: { chartData() { if (!this.result || !this.result.all_probabilities) return []; const all = this.result.all_probabilities; const maxClass = this.result.predicted_class; return Object.keys(all).map((label) => ({ label, value: all[label], isMax: label === maxClass, })); }, }, methods: { getClassType(className) { return this.classColors[className] || "info"; }, handleFileUpload(file, fileList) { const allowedTypes = ["image/jpeg", "image/jpg", "image/png"]; const maxSize = 5 * 1024 * 1024; // 5MB if (!allowedTypes.includes(file.raw.type)) { this.$message.error("仅支持 JPG/PNG 格式!"); return; } if (file.size > maxSize) { this.$message.error("图片不能超过 5MB!"); return; } // 显示预览 const reader = new FileReader(); reader.onload = (e) => { this.previewImage = e.target.result; }; reader.readAsDataURL(file.raw); // 重置状态 this.loading = true; this.progress = 0; this.result = null; // 模拟进度增长 const interval = setInterval(() => { if (this.progress >= 90) clearInterval(interval); else this.progress += 5; }, 200); // 创建 FormData 发送图片 const formData = new FormData(); formData.append("file", file.raw); // 调用后端 API fetch(this.uploadUrl, { method: "POST", body: formData, }) .then((res) => res.json()) .then((data) => { clearInterval(interval); this.progress = 100; this.loading = false; console.log(res); if (data.success) { this.result = data; this.$message.success(`识别完成:${data.predicted_class}`); } else { this.handleError(data); } }); // .catch((err) => { // clearInterval(interval); // this.loading = false; // this.handleError({ error: "网络错误或服务未启动" }); // }); }, handleError(data) { console.error("推理失败:", data); this.$alert(data.error || "未知错误", "❌ 推理失败", { confirmButtonText: "确定", type: "error", }); }, }, }; </script> <style scoped> .classifier-container { max-width: 800px; margin: 40px auto; padding: 20px; } .box-card { border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .upload-box { width: 100%; margin-bottom: 30px; } .preview-section { margin-top: 20px; text-align: center; } .preview-img { max-width: 100%; max-height: 300px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); margin-bottom: 20px; } .loading-box { margin: 20px auto; width: fit-content; } .result-section { text-align: left; margin-top: 20px; } .chart-box { margin-top: 20px; background: #f9f9f9; padding: 15px; border-radius: 8px; border: 1px solid #eee; } </style> package com.shop.jieyou.controller; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.shop.jieyou.common.Result; import com.shop.jieyou.entity.UserItem; import com.shop.jieyou.service.ItemService; import com.shop.jieyou.service.PythonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; /** * 花卉相关接口控制器 * 提供三大功能: * 1. 获取中国十大名花数据(来自爬虫或缓存) * 2. 手动刷新花卉数据(强制重新爬取) * 3. 基于用户行为的花卉推荐(调用Python协同过滤脚本) */ @RestController @CrossOrigin(origins = "*") // 允许所有域访问,用于前端开发调试(生产环境建议限制域名) @RequestMapping("/api") public class FlowerController { @Autowired private PythonService pythonService; // 注入业务服务层,处理数据获取与推荐逻辑 @Autowired private ItemService itemService; /** * 接口:GET /api/flowers * 功能:获取“中国十大名花”数据列表 * 数据来源:可能来自数据库、Redis 缓存 或 调用 Python 爬虫脚本 * * @return Result<List<Map<String, Object>>> 返回包含花卉信息的成功响应 */ @GetMapping("/flowers") public Result<List<Map<String, Object>>> getTopTenFlowers() { try { // 调用服务层获取花卉数据(内部可能带缓存机制) List<Map<String, Object>> flowers = pythonService.getFlowers(); return Result.success(flowers); // 成功返回数据 } catch (Exception e) { // 捕获异常并统一返回错误码和消息,避免暴露堆栈给前端 return Result.error("500", "获取花卉数据失败:" + e.getMessage()); } } /** * 接口:POST /api/flowers/refresh * 功能:强制刷新花卉数据缓存,触发重新爬取 * 使用场景:管理员手动更新数据时调用 * * @return Result<Map<String, Object>> 返回刷新结果信息 */ @PostMapping("/flowers/refresh") public Result<Map<String, Object>> refreshData() { try { // TODO: 如果实现了 clearCache 方法,请取消注释并调用 // pythonService.clearCache(); // 清除旧缓存,下次 getFlowers 将重新爬取 // 重新获取最新数据(假设此时会触发爬虫) List<Map<String, Object>> flowers = pythonService.getFlowers(); // 构造返回信息 Map<String, Object> data = new HashMap<>(); data.put("message", "数据已刷新"); data.put("count", flowers.size()); return Result.success(data); } catch (Exception e) { return Result.error("500", "刷新失败:" + e.getMessage()); } } // ========== 推荐系统相关常量定义 ========== /** * 输入文件路径:Java 将用户-商品行为数据写入此 JSON 文件供 Python 脚本读取 * 注意:src/main/resources 是编译后打包进 jar 的资源目录,不适合运行时写入! * 建议改为外部路径如 "./data/input.json" */ private static final String INPUT_PATH = "src/main/resources/scripts/input.json"; /** * 输出文件路径:Python 脚本将推荐结果写入此文件,Java 再读取返回给前端 */ private static final String OUTPUT_PATH = "src/main/resources/scripts/output.json"; /** * Python 协同过滤脚本路径 * 注意:resources 目录下的 .py 文件在打包后无法直接作为可执行脚本运行 * 更佳做法是将脚本放在项目外部或使用 ProcessBuilder 启动独立服务 */ private static final String PYTHON_SCRIPT = "src/main/resources/scripts/collaborative.py"; /** * 接口:GET /api/recommend?userId=123 * 功能:为指定用户生成个性化花卉推荐列表 * 实现方式:Java 查询数据库 → 写入 JSON 文件 → 调用 Python 脚本计算 → 读取结果返回 * * @param userId 用户ID,必填参数 * @return Result<JsonNode> 推荐的商品ID数组(如 [101, 105, 108]) */ @GetMapping("/recommend") public Result recommendFlowers(@RequestParam("userId") Long userId) { try { // 1. 获取用户行为数据 List<UserItem> matrix = pythonService.getUserItemMatrix(); // 2. 调用 Python 脚本(通过 stdin/stdout 通信) ProcessBuilder pb = new ProcessBuilder("python", PYTHON_SCRIPT, String.valueOf(userId)); pb.redirectErrorStream(true); // 合并错误流 Process process = pb.start(); // 3. 将数据写入脚本的标准输入 ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(process.getOutputStream(), matrix); process.getOutputStream().close(); // 关闭输入,通知Python结束读取 // 4. 读取Python脚本的输出(推荐结果) JsonNode result = mapper.readTree(process.getInputStream()); // 5. 等待脚本执行完毕 int exitCode = process.waitFor(); if (exitCode != 0) { return Result.error("500", "Python script failed with exit code: " + exitCode); } System.out.println(result); return Result.success(result); } catch (Exception e) { e.printStackTrace(); return Result.error("500", "推荐生成失败:" + e.getMessage()); } } @PostMapping("/predict") public ResponseEntity<String> predict(@RequestParam("file") MultipartFile file) { try { // 保存上传的文件到临时路径 String tempDir = System.getProperty("java.io.tmpdir"); File tempFile = new File(tempDir, file.getOriginalFilename()); file.transferTo(tempFile); // 调用 Python 脚本执行预测 ProcessBuilder pb = new ProcessBuilder( "D:\\Python\\python.exe", "D:/DevCode/商城/Shop-master/shop-springboot/src/main/resources/scripts/image_classifier.py", "predict", tempFile.getAbsolutePath() ); pb.redirectErrorStream(true); // 合并 stdout 和 stderr Process process = pb.start(); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); StringBuilder output = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { output.append(line); } int exitCode = process.waitFor(); if (exitCode == 0) { return ResponseEntity.ok(output.toString().trim()); } else { return ResponseEntity.status(500).body("{\"error\": \"Prediction failed\"}"); } } catch (Exception e) { return ResponseEntity.status(500).body("{\"error\": \"" + e.getMessage() + "\"}"); } } private static final String PYTHON_EXECUTABLE = "D:\\Python\\python.exe"; // 或 "python3" private static final String INFER_SCRIPT_PATH = "D:/DevCode/商城/Shop-master/shop-springboot/src/main/resources/scripts/python-model/infer.py"; @CrossOrigin(origins = "*") @PostMapping("/uploadPython") public Result<?> classifyImage(@RequestParam("file") MultipartFile file) { ObjectMapper mapper = new ObjectMapper(); // 临时保存上传的图片 File tempImage; try { tempImage = File.createTempFile("img_", "_" + file.getOriginalFilename()); file.transferTo(tempImage); } catch (IOException e) { return Result.error("500", "文件保存失败" + e.getMessage()); } try { // 构建命令 ProcessBuilder pb = new ProcessBuilder( PYTHON_EXECUTABLE, INFER_SCRIPT_PATH, tempImage.getAbsolutePath() ); pb.redirectErrorStream(true); // 执行 Process process = pb.start(); // 读取输出 BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); StringBuilder output = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { output.append(line); } // 等待完成 boolean finished = process.waitFor(30, TimeUnit.SECONDS); // 超时保护 if (!finished) { process.destroyForcibly(); return Result.error("500", "推理超时"); } reader.close(); // 清理临时文件 tempImage.delete(); // 解析 JSON String jsonOutput = output.toString().trim(); if (jsonOutput.isEmpty()) { return Result.error("500", "Python 无返回结果"); } JsonNode result = mapper.readTree(jsonOutput); return Result.success(result); } catch (InterruptedException | JsonProcessingException e) { return Result.error("500", "解析错误"); } catch (Exception e) { return Result.error("500", "系统错误"+e.getMessage()); } } }
最新发布
10-22
### 解决方案 在 JavaScript 中遇到 `Cannot convert undefined or null to object` 的 TypeError 通常是因为尝试调用对象方法或访问属性时,该变量实际上是 `undefined` 或 `null`。这种错误可以通过更严格的类型检查来避免。 以下是几种常见的解决方案: #### 方法一:使用条件语句验证数据是否存在 通过简单的条件判断可以有效防止此类错误的发生。如果某个值可能为 `undefined` 或 `null`,可以在操作前对其进行验证[^1]。 ```javascript function safeAccess(obj, key) { if (obj && typeof obj === 'object') { return obj[key]; } return undefined; } const data = {}; console.log(safeAccess(data, 'nonExistentKey')); // 输出: undefined ``` #### 方法二:利用可选链(Optional Chaining) 现代 JavaScript 提供了一种简洁的方式来处理这种情况——即 **可选链** (`?.`) 运算符。它允许开发者安全地访问深层嵌套的对象属性而无需担心中间节点为空的情况。 ```javascript const user = null; // 使用传统方式可能会抛出异常 try { console.log(user.address.city); } catch(e){ console.error('Error:', e.message); } // 使用可选链则不会报错 console.log(user?.address?.city); // 输出: undefined ``` #### 方法三:设置默认值以防万一 当不确定传入的数据是否会缺失某些字段时,可以设定默认值作为备用选项。这不仅提高了代码的安全性也增强了其健壮性[^2]。 ```javascript function getUserInfo({ name = 'Guest', age = 0 } = {}) { return `${name}, ${age}`; } let userInfo = null; console.log(getUserInfo(userInfo)); // 输出: Guest, 0 ``` #### 方法四:捕获并处理运行期错误 尽管预防措施很重要,但在复杂的应用场景下仍可能出现未预料到的问题。此时合理运用 try-catch 结构可以帮助程序平稳度过意外状况。 ```javascript try { const result = someFunctionThatMightFail(); processResult(result); } catch(error) { handleError(error); } ``` 以上四种策略可以根据具体需求单独或者组合应用以彻底根除因转换 `undefined` 和 `null` 至对象所引发的 TypeError 错误。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值