ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

微信小程序蓝牙控制硬件实战:HC-05模块连接与继电器控制

微信小程序蓝牙控制硬件实战:HC-05模块连接与继电器控制 简介本资源是一套完整的微信小程序蓝牙控制继电器及串口调试实战项目源码面向嵌入式开发初学者、物联网爱好者与微信小程序进阶开发者解决硬件远程控制与双向通信调试的实际问题适用于智能家居、实验室自动化等场景。压缩包共56个文件含8个核心JS逻辑文件实现蓝牙连接、指令发送与串口数据解析、10个WXSS样式文件适配多端UI、5个WXML页面结构文件含设备扫描、继电器开关面板、串口调试终端等界面以及17张PNG界面截图和README.md说明文档整体仅133KB轻量易部署。已有3845人学习下载。读者可直接导入微信开发者工具运行完整复现小程序与STM32蓝牙模块的配对、继电器通断控制、实时串口指令收发与状态回显全流程并通过云开发预留接口拓展设备状态存储功能代码结构清晰、注释充分具备良好的教学参考与二次开发基础。1. 项目概述从零构建一个微信小程序蓝牙硬件控制系统最近在折腾一个智能家居的小项目需要远程控制家里的电器开关。市面上的成品要么太贵要么功能不匹配索性自己动手用最熟悉的微信小程序作为前端通过蓝牙连接一个继电器模块实现手机遥控开关。这个过程中最核心也最磨人的部分就是打通小程序与蓝牙硬件这里用的是HC-05这类经典蓝牙模块之间的通信并实现稳定可靠的串口指令控制。网上资料虽然多但要么过于零散要么版本老旧踩了不少坑。今天就把这个“微信小程序蓝牙控制继电器及串口调试”的完整实现思路、核心代码和避坑经验系统地梳理出来手把手带你复现一个可用的项目。无论你是物联网初学者还是想在小程序硬件交互上深入一步的开发者这篇内容都能给你提供一条清晰的路径。这个项目的核心价值在于它剥离了复杂的云平台和网络配置实现了设备与手机的直连控制响应快、延迟低且不依赖外部网络非常适合智能开关、玩具遥控、小型机器人等离线或局域网场景的应用开发。我会从硬件选型、小程序蓝牙API详解、通信协议设计、到完整的源代码解析和调试技巧一步步拆解。你会发现只要理清了蓝牙通信那点事控制一个继电器开关其实没有想象中那么难。2. 核心硬件选型与电路设计思路2.1 蓝牙模块的选择HC-05还是BLE硬件是项目的基石第一步选型就决定了后续开发的复杂度。控制继电器我们主要在两个方案间纠结经典蓝牙模块如HC-05、HC-06和低功耗蓝牙BLE如ESP32、HM-10。为什么最终选择了经典蓝牙模块如HC-05对于继电器控制这种场景低功耗并非首要考虑我们更看重的是通信的稳定性和开发的便捷性。经典蓝牙模块SPP协议在数据传输上更像一个透明的串口线你发送什么数据对方就收到什么非常简单直接。微信小程序的蓝牙API对经典蓝牙SPP协议的支持虽然不如BLE那么“原生”但通过特定的服务UUID如0x1101和特征值UUID如0xFFE1进行模拟完全可以稳定通信。它的优势在于通信模型简单连接后即可双向收发数据无需处理BLE那套复杂的服务Service、特征Characteristic、描述符Descriptor概念对于发送“开/关”指令这种简单操作心智负担小。数据传输率高适合连续、小批量的数据流传输。兼容性强很多现有的单片机固件如Arduino的SoftwareSerial库默认就支持与HC-05通信生态成熟。注意微信小程序官方文档更侧重于BLE但经过实测通过正确配置UUID连接和控制HC-05这类经典蓝牙模块是完全可行的。这需要一点技巧后文会详细说明。相比之下如果选用ESP32这类自带BLE的芯片虽然可以直接使用小程序更推荐的BLE API但你需要为单片机编写BLE服务端的代码定义了服务UUID和特征值后才能进行读写操作。对于新手来说入门门槛稍高。结论对于本“遥控开关”项目追求快速实现和稳定控制HC-05这类经典蓝牙模块是更务实的选择。我们接下来的代码也将围绕如何让小程序与HC-05“对话”来展开。2.2 继电器模块与微控制器的连接继电器模块是执行机构负责根据小程序的指令物理上接通或断开220V市电电路。市面上常见的继电器模块有1路、2路、4路、8路等。继电器模块驱动原理 常见的5V继电器模块其控制端通常只需要三根线VCC接5V、GND接地、IN信号输入。当IN引脚接收到高电平如3.3V或5V时继电器吸合电路导通接收到低电平0V时继电器断开。模块内部通常已有光耦和晶体管进行隔离和驱动因此我们可以直接用单片机的GPIO口来控制。微控制器选型 这里的选择很多Arduino Uno、NodeMCUESP8266、STM32都可以。考虑到与HC-05的串口通信以及简单逻辑控制Arduino Uno因其生态丰富、入门简单成为首选。它的核心任务是通过硬件串口Serial或软件串口与HC-05蓝牙模块通信接收小程序发来的指令。解析指令例如收到字符A表示打开继电器B表示关闭。根据指令控制连接继电器IN脚的GPIO引脚输出高或低电平。电路连接示意图关键部分Arduino Uno -- HC-05蓝牙模块 5V -- VCC GND -- GND RX(0) -- TXD TX(1) -- RXD Arduino Uno -- 1路继电器模块 Pin 7 -- IN (信号脚) 5V -- VCC GND -- GND实操心得连接时务必将HC-05的TXD接Arduino的RXPin 0RXD接Arduino的TXPin 1。初次烧录Arduino程序时建议暂时拔掉HC-05与Pin 0、Pin 1的连接因为这两个引脚也用于USB通信同时连接可能导致上传失败。上传完成后再接回去。3. 微信小程序蓝牙API深度解析与连接流程微信小程序的蓝牙API位于wx对象下主要分为发现、连接、通信、监听几个阶段。与经典蓝牙模块通信关键在于找到正确的服务UUID和特征值UUID。3.1 初始化蓝牙适配器与监听状态任何蓝牙操作的前提是初始化蓝牙适配器并监听其状态变化。这一步必须在onLoad或用户主动触发时进行。// pages/index/index.js Page({ data: { devices: [], // 发现的设备列表 connectedDeviceId: , // 已连接设备的deviceId services: [], // 设备服务列表 characteristics: [], // 特征值列表 isConnected: false, log: 初始化... }, onLoad: function() { this._openBluetoothAdapter(); }, // 1. 初始化蓝牙适配器 _openBluetoothAdapter: function() { const that this; wx.openBluetoothAdapter({ success: (res) { that._addLog(蓝牙适配器初始化成功); // 监听适配器状态 wx.onBluetoothAdapterStateChange(function(state) { that._addLog(适配器状态可用${state.available}, 搜索中${state.discovering}); }); // 开始搜索设备 that._startBluetoothDevicesDiscovery(); }, fail: (err) { that._addLog(蓝牙适配器初始化失败请检查手机蓝牙是否打开或是否支持BLE); console.error(err); } }); }, _addLog: function(msg) { const newLog [${new Date().toLocaleTimeString()}] ${msg}\n; this.setData({ log: newLog this.data.log }); } })注意事项wx.openBluetoothAdapter的fail回调常见原因是用户手机蓝牙未开启或手机不支持BLE/蓝牙4.0以上。虽然我们连接经典蓝牙但小程序底层API仍依赖BLE框架因此手机需要支持BLE。3.2 发现设备与过滤HC-05开始搜索后会扫描到周围大量的蓝牙设备。HC-05模块在未配对时默认名称通常是HC-05。我们需要监听设备发现事件并过滤出目标设备。// 2. 开始搜索设备 _startBluetoothDevicesDiscovery: function() { const that this; wx.startBluetoothDevicesDiscovery({ services: [], // 为空表示扫描所有设备 allowDuplicatesKey: false, // 不允许重复上报 success: (res) { that._addLog(开始搜索蓝牙设备...); }, fail: (err) { that._addLog(开始搜索失败); console.error(err); } }); // 监听寻找到新设备的事件 wx.onBluetoothDeviceFound((devices) { const foundDevices devices.devices; if (foundDevices.length) { let newDevices []; foundDevices.forEach(device { // 过滤出名称包含HC-05的设备并去重 if (device.name device.name.indexOf(HC-05) ! -1) { const existingIndex that.data.devices.findIndex(d d.deviceId device.deviceId); if (existingIndex -1) { newDevices.push({ name: device.name, deviceId: device.deviceId, RSSI: device.RSSI || 0 }); } } }); if (newDevices.length) { that.setData({ devices: that.data.devices.concat(newDevices) }); that._addLog(发现设备${newDevices.map(d d.name).join(, )}); } } }); }关键点解析services参数如果知道目标设备广播的服务UUID可以填入以加快过滤。对于HC-05我们通常不知道其广播UUID所以留空。allowDuplicatesKey设为false同一设备只会上报一次避免列表重复。device.name这是识别HC-05的关键。有时模块可能被改名所以实际项目中可能需要提供一个手动输入设备名的功能。3.3 连接设备与探索服务关键步骤找到设备后点击列表中的设备进行连接。这是与经典蓝牙模块通信最需要技巧的一步因为我们需要使用一个“通用”的UUID来模拟串口服务。// 3. 连接设备 connectDevice: function(e) { const deviceId e.currentTarget.dataset.deviceid; const that this; that._addLog(正在连接 ${deviceId}...); wx.createBLEConnection({ deviceId: deviceId, success: (res) { that.setData({ connectedDeviceId: deviceId, isConnected: true }); that._addLog(设备连接成功正在获取服务...); // 连接成功后延迟一段时间再获取服务确保稳定 setTimeout(() { that._getBLEDeviceServices(deviceId); }, 500); }, fail: (err) { that._addLog(连接失败: ${err.errMsg}); } }); }, // 4. 获取蓝牙设备所有服务 _getBLEDeviceServices: function(deviceId) { const that this; wx.getBLEDeviceServices({ deviceId: deviceId, success: (res) { const services res.services; that._addLog(发现 ${services.length} 个服务); // 寻找串口服务经典蓝牙SPP通常使用 0x1101 或 0x1102 // 微信小程序中UUID需要转换成完整格式如 00001101-0000-1000-8000-00805F9B34FB const targetServiceId 00001101-0000-1000-8000-00805F9B34FB; // SPP服务UUID const foundService services.find(s s.uuid.toLowerCase() targetServiceId.toLowerCase()); if (foundService) { that._addLog(找到目标串口服务: ${foundService.uuid}); that._getBLEDeviceCharacteristics(deviceId, foundService.uuid); } else { // 如果没有找到标准SPP服务可以尝试列出所有服务供用户选择或者尝试其他常见UUID that._addLog(未找到标准SPP服务列出所有服务供检查:); services.forEach(s { that._addLog(服务UUID: ${s.uuid}); }); // 这里可以尝试连接第一个服务或者让用户手动选择 // 对于许多HC-05模块第一个服务可能就是可用的 if (services.length 0) { that._addLog(尝试连接第一个服务: ${services[0].uuid}); that._getBLEDeviceCharacteristics(deviceId, services[0].uuid); } } }, fail: (err) { that._addLog(获取服务失败: ${err.errMsg}); } }); },核心难点与解决方案 经典蓝牙模块如HC-05在BLE扫描下其服务UUID可能不是标准的0x1101。有些模块可能使用0xFFE0或者被映射成其他UUID。上述代码中我们首先尝试匹配标准SPP UUID。如果失败一个非常实用的调试技巧是将发现的所有服务UUID打印到日志中。然后你可以尝试连接一个看起来像主服务的UUID通常是列表中第一个。更稳妥的方法是在开发阶段先用手机上的通用蓝牙调试APP如LightBlue连接HC-05查看其提供的服务UUID和特征值UUID然后在小程序代码中写死这两个值。这是打通连接最关键的一步。3.4 获取特征值并启用通知找到正确的服务后需要获取该服务下的特征值Characteristic。用于通信的特征值通常有两个一个用于写入发送指令一个用于接收读取或通知。// 5. 获取指定服务的所有特征值 _getBLEDeviceCharacteristics: function(deviceId, serviceId) { const that this; wx.getBLEDeviceCharacteristics({ deviceId: deviceId, serviceId: serviceId, success: (res) { const characteristics res.characteristics; that._addLog(发现 ${characteristics.length} 个特征值); // 寻找具有写和通知属性的特征值 let writeCharId null; let notifyCharId null; characteristics.forEach(char { that._addLog(特征值UUID: ${char.uuid}, 属性: ${char.properties}); // 判断属性write表示可写notify表示可订阅通知 if (char.properties.write) { writeCharId char.uuid; } if (char.properties.notify || char.properties.indicate) { notifyCharId char.uuid; } }); if (writeCharId notifyCharId) { that.setData({ writeCharId: writeCharId, notifyCharId: notifyCharId, serviceId: serviceId }); that._addLog(找到写特征值: ${writeCharId}, 通知特征值: ${notifyCharId}); // 启用特征值变化通知用于接收数据 that._enableBLECharacteristicValueChange(deviceId, serviceId, notifyCharId); } else { that._addLog(未找到同时具备写和通知属性的特征值); // 尝试使用同一个特征值进行写和通知有些模块如此 if (characteristics.length 0) { const firstChar characteristics[0]; that.setData({ writeCharId: firstChar.uuid, notifyCharId: firstChar.uuid, serviceId: serviceId }); that._addLog(尝试使用唯一特征值: ${firstChar.uuid}); that._enableBLECharacteristicValueChange(deviceId, serviceId, firstChar.uuid); } } }, fail: (err) { that._addLog(获取特征值失败: ${err.errMsg}); } }); }, // 6. 启用特征值变化通知监听设备返回的数据 _enableBLECharacteristicValueChange: function(deviceId, serviceId, characteristicId) { const that this; wx.notifyBLECharacteristicValueChange({ deviceId: deviceId, serviceId: serviceId, characteristicId: characteristicId, state: true, // 启用通知 success: (res) { that._addLog(已启用数据通知监听); // 监听特征值变化事件 wx.onBLECharacteristicValueChange((res) { // 接收到的数据是 ArrayBuffer 类型需要转换 const receivedBytes new Uint8Array(res.value); const receivedMsg that._ab2str(receivedBytes); that._addLog(收到数据: ${receivedMsg}); // 这里可以解析继电器返回的状态等信息 }); }, fail: (err) { that._addLog(启用通知失败: ${err.errMsg}); } }); }, // ArrayBuffer 转字符串工具函数 _ab2str: function(buffer) { return String.fromCharCode.apply(null, new Uint8Array(buffer)); }属性解析properties.write该特征值支持写入我们通过它向蓝牙模块发送“开/关”指令。properties.notify该特征值支持通知当蓝牙模块Arduino有数据发送时小程序会通过wx.onBLECharacteristicValueChange事件收到。有时同一个特征值可能同时具备write和notify属性代码中也做了兼容处理。4. 通信协议设计与指令收发实现连接建立后小程序和Arduino之间需要一套简单的“语言”来沟通这就是通信协议。协议设计的原则是简单、明确、容错。4.1 指令集设计对于控制一个继电器开关最简单的协议就是单字符指令。发送A(或0x41) 继电器打开发送B(或0x42) 继电器关闭发送? 查询继电器当前状态为了增加可靠性可以设计一个带简单校验的指令格式例如[指令头][指令码][校验和][指令尾]。但对于入门项目单字符指令足够稳定。小程序端发送指令函数// 7. 向蓝牙设备写入数据发送指令 sendCommand: function(cmd) { const that this; const { deviceId, serviceId, writeCharId, isConnected } this.data; if (!isConnected || !writeCharId) { that._addLog(未连接或未找到可写特征值); return; } // 将字符串指令转换成 ArrayBuffer const buffer new ArrayBuffer(cmd.length); const dataView new DataView(buffer); for (let i 0; i cmd.length; i) { dataView.setUint8(i, cmd.charCodeAt(i)); } wx.writeBLECharacteristicValue({ deviceId: deviceId, serviceId: serviceId, characteristicId: writeCharId, value: buffer, success: (res) { that._addLog(发送指令成功: ${cmd}); }, fail: (err) { that._addLog(发送指令失败: ${err.errMsg}); // 常见的失败原因连接已断开、特征值不可写等 } }); }, // 页面上的按钮触发 turnOnRelay: function() { this.sendCommand(A); // 发送打开指令 }, turnOffRelay: function() { this.sendCommand(B); // 发送关闭指令 }4.2 Arduino端程序解析与响应Arduino端的代码核心是监听串口数据并根据收到的字符控制GPIO引脚。// Arduino Sketch (Relay_Control.ino) #define RELAY_PIN 7 // 继电器连接在数字引脚7 void setup() { Serial.begin(9600); // 初始化硬件串口波特率需与HC-05模块匹配通常是9600或115200 pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, LOW); // 初始状态为关闭 Serial.println(Relay Controller Ready. Send A to ON, B to OFF.); } void loop() { // 检查串口是否有数据可读 if (Serial.available() 0) { char receivedChar Serial.read(); // 读取一个字符 switch (receivedChar) { case A: case a: digitalWrite(RELAY_PIN, HIGH); // 输出高电平继电器吸合 Serial.println(STATUS: ON); // 向小程序返回状态 break; case B: case b: digitalWrite(RELAY_PIN, LOW); // 输出低电平继电器断开 Serial.println(STATUS: OFF); break; case ?: // 查询状态 if (digitalRead(RELAY_PIN) HIGH) { Serial.println(STATUS: ON); } else { Serial.println(STATUS: OFF); } break; default: // 忽略未知指令或返回错误信息 Serial.println(ERROR: Unknown command); break; } } // 可以在这里添加其他任务如传感器读取 }关键配置Serial.begin(9600)这里的波特率必须与你的HC-05模块设置的波特率一致。默认通常是9600如果你用AT命令修改过这里也要相应修改。Serial.println用于向串口也就是蓝牙模块发送数据这些数据会被小程序端的onBLECharacteristicValueChange事件捕获实现双向通信。这样小程序不仅能发送指令还能接收继电器状态反馈实现界面状态的同步更新。5. 小程序界面设计与状态管理一个直观的UI能极大提升用户体验。界面主要包括设备列表、连接状态显示、控制按钮和日志面板。5.1 WXML布局结构!-- pages/index/index.wxml -- view classcontainer view classheader text classtitle蓝牙继电器控制器/text view classstatus text状态: /text text class{{isConnected ? connected : disconnected}} {{isConnected ? 已连接 : 未连接}} /text /view /view view classsection view classsection-title设备列表/view button typeprimary bindtapstartScan disabled{{isScanning}} {{isScanning ? 搜索中... : 搜索设备}} /button scroll-view classdevice-list scroll-y view wx:for{{devices}} wx:keydeviceId classdevice-item bindtapconnectDevice>// 在Page的data中定义状态 data: { isConnected: false, connectedDeviceName: , devices: [], // ... 其他蓝牙参数 log: }, // 连接成功后的处理 wx.createBLEConnection({ success: (res) { this.setData({ isConnected: true, connectedDeviceName: deviceName }); this._addLog(已连接到 ${deviceName}); // 获取服务... } }); // 断开连接处理监听事件 wx.onBLEConnectionStateChange((res) { if (!res.connected) { this.setData({ isConnected: false, connectedDeviceName: , devices: [] // 清空列表需要重新搜索 }); this._addLog(设备连接已断开); // 停止监听特征值变化 wx.offBLECharacteristicValueChange(); } });实操心得一定要监听wx.onBLEConnectionStateChange事件。蓝牙连接可能因为距离、干扰或模块问题而意外断开。在这个回调中更新UI状态并清理资源如关闭通知监听可以避免程序状态与实际不符导致用户点击按钮无反应。6. 串口调试技巧与深度排错指南开发过程中串口调试是定位问题的生命线。不能仅依赖小程序的日志必须多维度验证。6.1 使用串口调试助手验证硬件链路在编写小程序代码之前强烈建议先用PC上的串口调试助手如SSCOM、XCOM、Arduino IDE的串口监视器验证整个硬件链路和通信协议是否正常。操作流程将HC-05模块通过USB转TTL工具连接到电脑。打开串口调试助手选择正确的COM口设置波特率与Arduino程序中的Serial.begin()一致如9600。在发送区输入字符A并发送观察继电器是否吸合通常会听到“咔哒”声同时接收区应能看到Arduino返回的STATUS: ON。发送B继电器应断开返回STATUS: OFF。如果这一步不成功说明问题在硬件或Arduino固件层面与小程序的代码无关。常见问题有HC-05未正确进入AT模式或波特率不匹配用USB转TTL连接HC-05的KEY引脚拉高再上电进入AT模式用AT指令ATUART?查看当前波特率并用ATUART9600,0,0设置为9600。接线错误反复检查VCC、GND、TXD、RXD的交叉连接。Arduino程序未上传或引脚定义错误确认程序已成功上传且RELAY_PIN的定义与实际接线一致。6.2 小程序端的深度调试方法当硬件链路确认无误后小程序端的问题主要集中在UUID匹配和数据收发上。1. UUID调试大法 这是连接经典蓝牙模块最常见的坑。如前所述使用以下代码打印所有服务和特征值wx.getBLEDeviceServices({ deviceId: deviceId, success: (res) { console.log(所有服务:, res.services); res.services.forEach(s { console.log(服务UUID:, s.uuid); // 获取每个服务的特征值 wx.getBLEDeviceCharacteristics({ deviceId: deviceId, serviceId: s.uuid, success: (charRes) { console.log(服务 ${s.uuid} 的特征值:, charRes.characteristics); } }); }); } });将控制台打印的UUID与用LightBlue这类专业APP扫描到的UUID进行比对找到正确的配对。2. 数据收发调试发送失败检查writeBLECharacteristicValue的fail回调。常见错误码10004表示特征值不支持写操作说明你选错了特征值UUID。收不到数据首先确认Arduino端确实通过Serial.println发送了数据。然后检查小程序端是否成功调用了notifyBLECharacteristicValueChange并进入了onBLECharacteristicValueChange回调。可以在回调里打印res.value这个ArrayBuffer看其长度和内容是否非空。数据乱码检查小程序端_ab2str转换函数是否正确以及Arduino和小程序两端对多字节编码如中文是否一致。简单指令建议始终使用ASCII字符。6.3 连接稳定性优化蓝牙连接在复杂环境中可能不稳定。可以增加以下策略心跳包机制小程序定时如每10秒向设备发送一个查询指令如?如果连续多次无响应则认为连接已断开主动触发重连流程。自动重连在onBLEConnectionStateChange监听到断开后不是简单提示用户而是尝试自动重新连接原设备。发送队列与确认机制对于关键指令可以设计一个简单的应答协议。例如小程序发送A后必须收到OK_A才认为执行成功否则进行重发最多3次。这能有效应对偶发的数据包丢失。7. 项目扩展与进阶思路一个基础的遥控开关实现后你可以在此基础上进行丰富和扩展打造更实用的项目。7.1 多路继电器控制与分组将单路继电器扩展为4路或8路模块指令协议可以升级为A1打开第1路B1关闭第1路A_ALL全部打开 Arduino端代码相应增加对多位指令的解析并控制多个GPIO引脚。7.2 添加状态反馈与同步当前项目是“盲操作”发送指令后不知道继电器是否真的动作了虽然Arduino会返回状态但网络可能丢包。可以在继电器输出端并联一个光耦或使用继电器的辅助触点将实际状态反馈给Arduino的另一个输入引脚。Arduino定时读取或在小程序查询时返回真实的物理状态实现UI的精准同步。7.3 结合传感器实现自动化将Arduino连接温湿度传感器如DHT11、光照传感器等。小程序不仅可以发送控制指令还可以请求传感器数据。例如小程序发送GET_TEMP。Arduino读取传感器数据并返回TEMP:25.6。小程序界面显示当前环境温度。你甚至可以设计简单的逻辑让Arduino在温度超过阈值时自动打开继电器连接风扇实现本地自动化小程序仅作为监控和参数设置端。7.4 界面美化与用户体验使用更美观的UI组件库如Vant Weapp、ColorUI来美化界面。增加连接动画、继电器状态的图标化显示如用红色/绿色表示开关、历史操作记录等功能。还可以加入“一键配网”功能让用户通过小程序直接配置HC-05模块的名称和密码。这个项目就像一把钥匙打开了微信小程序与物理世界交互的大门。从最初的蓝牙连接调试到最终稳定的控制每一步问题的解决都加深了对无线通信和嵌入式系统的理解。代码本身并不复杂但其中涉及的调试思路、问题排查方法是从事物联网开发更宝贵的财富。当你成功用手机点亮第一盏灯时那种连接数字与现实的成就感会激励你去做更多有趣的尝试。本文还有配套的精品资源点击获取
返回列表