Uniapp 实现 ESP32-S3 蓝牙配网功能(完整代码 + 原理详解)

📱Uniapp与ESP32-S3蓝牙配网全解析:从原理到实战

一、🚀 项目背景与目标

在物联网应用开发中,为设备配置WiFi网络是最基础的需求。传统方式需要用户通过复杂的命令行或专业工具进行配置,对普通用户不友好。本文将介绍如何使用Uniapp开发一个可视化蓝牙配网界面,让用户通过手机App轻松完成ESP32-S3设备的WiFi配置。

二、💡 技术原理与核心概念

1. 蓝牙低功耗(BLE)通信基础

  • GATT协议:基于属性的分层协议,是BLE通信的基础
  • Service(服务):包含一组相关特征的集合,如"电池服务"
  • Characteristic(特征):最小数据单元,有唯一UUID,如"电池电量"
  • Descriptor(描述符):提供特征值的附加信息

2. Uniapp蓝牙API核心方法

方法名作用
openBluetoothAdapter初始化蓝牙模块
startBluetoothDevicesDiscovery开始扫描蓝牙设备
createBLEConnection连接到蓝牙设备
getBLEDeviceServices获取设备的服务
getBLEDeviceCharacteristics获取特征值
writeBLECharacteristicValue向特征值写入数据
notifyBLECharacteristicValueChange启用特征值变化通知

三、🛠️ 完整代码实现

1. 核心页面代码

<template>
	<view class="container">
		<view class="header">
			<text class="title">ESP32-S3 蓝牙配网</text>
		</view>

		<view class="form">
			<input v-model="ssid" placeholder="请输入 WiFi 名称" class="input" />
			<input v-model="password" placeholder="请输入 WiFi 密码" type="password" class="input" />
		</view>

		<view class="buttons">
			<button @click="startScan" :disabled="scanning">{{ scanning ? '扫描中...' : '扫描设备' }}</button>
			<button @click="connectToDevice" :disabled="!selectedDeviceId || scanning">连接设备</button>
			<button @click="sendCredentials" :disabled="!isConnected || scanning">发送配网信息</button>
		</view>

		<view class="status">状态:{{ statusText }}</view>

		<view class="devices" v-if="devices.length">
			<text>扫描到的设备:</text>
			<view v-for="item in devices" :key="item.deviceId" @click="selectDevice(item)" class="device-item">
				<text :style="{ color: selectedDeviceId === item.deviceId ? 'green' : '#333' }">
					{{ item.name || item.localName || '未知设备' }}
					({{ item.deviceId.slice(-6) }})
				</text>
			</view>
		</view>

		<view class="log">
			<text>日志输出:</text>
			<scroll-view scroll-y style="max-height: 200px;">
				<view v-for="(log, index) in logs" :key="index">{{ log }}</view>
			</scroll-view>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				ssid: 'Marvellous',
				password: 'Marvellous170303',
				devices: [],
				selectedDeviceId: '',
				// 蓝牙服务与特征值UUID(需与ESP32端一致)
				serviceId: '000000FF-0000-1000-8000-00805f9b34fb',
				charSSID: '0000FFE2-0000-1000-8000-00805f9b34fb',
				charPASS: '0000FFE3-0000-1000-8000-00805f9b34fb',
				charNotify: '0000FFE1-0000-1000-8000-00805f9b34fb',
				isConnected: false,
				scanning: false,
				statusText: '等待操作',
				logs: [],
				deviceFoundListener: null,
				connectRetryCount: 0,  // 连接重试计数
				maxConnectRetries: 3,  // 最大重试次数
				isAppPlatform: uni.getSystemInfoSync().platform === 'android' || uni.getSystemInfoSync().platform === 'ios'
			};
		},
		methods: {
			// 日志输出方法(带时间戳)
			log(msg) {
				const time = new Date().toLocaleTimeString();
				this.logs.push(`[${time}] ${msg}`);
				this.ensureScrollVisible();  // 确保日志可见性
				console.log(`[${time}] ${msg}`);
			},

			// 确保日志滚动到底部(修复DOM访问异常)
			ensureScrollVisible() {
				setTimeout(() => {
					try {
						const query = uni.createSelectorQuery();
						query.select('.log scroll-view').boundingClientRect(rect => {
							if (rect) {
								query.select('.log scroll-view').scrollOffset(offset => {
									if (offset && offset.scrollHeight > offset.height) {
										query.select('.log scroll-view')
											.scrollOffset({ scrollTop: offset.scrollHeight, duration: 0 })
											.exec();
									}
								}).exec();
							}
						}).exec();
					} catch (e) {
						console.error('滚动日志失败:', e);
					}
				}, 100);
			},

			// 开始扫描设备
			startScan() {
				// 重置状态
				this.devices = [];
				this.scanning = true;
				this.selectedDeviceId = '';
				this.statusText = '正在初始化蓝牙...';
				this.log('开始扫描流程:初始化蓝牙适配器');

				// 清除之前的监听
				if (this.deviceFoundListener) {
					uni.offBluetoothDeviceFound(this.deviceFoundListener);
				}

				// App平台特殊处理
				if (this.isAppPlatform) {
					this.checkAppBluetoothPermission();
				} else {
					this.initBluetoothAdapter();
				}
			},

			// 检查App平台蓝牙权限
			checkAppBluetoothPermission() {
				// #ifdef APP-PLUS
				plus.android.requestPermissions(
					['android.permission.BLUETOOTH', 'android.permission.ACCESS_FINE_LOCATION'],
					() => {
						this.log('蓝牙权限已获取');
						this.initBluetoothAdapter();
					},
					err => {
						this.log(`用户拒绝蓝牙权限:${JSON.stringify(err)}`);
						this.statusText = '请授予蓝牙权限';
						this.scanning = false;
						
						// 提示用户去设置中开启权限
						uni.showModal({
							title: '权限请求',
							content: '蓝牙功能需要位置权限才能正常工作,请在设置中授予权限',
							success: (res) => {
								if (res.confirm) {
									plus.runtime.openURL('app-settings:'); // 打开应用设置页面
								}
							}
						});
					}
				);
				// #endif

				// #ifndef APP-PLUS
				this.initBluetoothAdapter(); // 非App平台直接初始化
				// #endif
			},

			// 初始化蓝牙适配器
			initBluetoothAdapter() {
				// 1. 初始化蓝牙适配器
				uni.openBluetoothAdapter({
					success: () => {
						this.log('蓝牙适配器初始化成功');
						this.statusText = '正在扫描设备(8秒)...';

						// 2. 监听设备发现事件(实时获取新设备)
						this.deviceFoundListener = uni.onBluetoothDeviceFound(res => {
							this.handleDeviceFound(res);
						});

						// 3. 开始扫描设备(不限制服务UUID,扫描所有设备)
						uni.startBluetoothDevicesDiscovery({
							services: [], // 关键:不指定服务,避免过滤掉目标设备
							allowDuplicatesKey: false, // 不允许重复上报同一设备
							interval: 0, // 扫描间隔(0为默认最快速度)
							success: () => {
								this.log('扫描命令已发送,开始接收设备...');
								// 4. 扫描8秒后自动停止(延长扫描时间提高成功率)
								setTimeout(() => {
									this.stopScan();
								}, 8000);
							},
							fail: err => {
								this.log(`启动扫描失败:${JSON.stringify(err)}`);
								this.statusText = '扫描启动失败';
								this.scanning = false;
							}
						});
					},
					fail: err => {
						this.log(`蓝牙初始化失败:${JSON.stringify(err)}`);
						this.scanning = false;

						// 错误处理:提示用户开启蓝牙
						if (err.errCode === 10001) {
							this.statusText = '蓝牙未开启';
							uni.showModal({
								title: '蓝牙未开启',
								content: '请先开启手机蓝牙功能',
								showCancel: false,
								success: () => {
									this.statusText = '等待操作';
								}
							});
						} else {
							this.statusText = '蓝牙初始化失败';
						}
					}
				});
			},

			// 处理发现的设备
			handleDeviceFound(res) {
				const devices = res.devices || [];
				devices.forEach(device => {
					// 过滤无效设备(信号强度过低或无ID)
					if (!device.deviceId || device.RSSI < -85) {
						return;
					}

					// 检查设备是否可连接(部分设备会返回canConnect属性)
					if (device.canConnect !== undefined && !device.canConnect) {
						this.log(`忽略不可连接设备:${device.name || '未知'}`);
						return;
					}

					// 检查是否已添加(去重)
					const isExist = this.devices.some(d => d.deviceId === device.deviceId);
					if (!isExist) {
						// 设备名称优先级:name > localName > 未知
						const deviceName = device.name || device.localName || '未知设备';
						this.log(`发现新设备:${deviceName}(信号强度:${device.RSSI}dBm)`);
						this.devices.push(device);
					}
				});
			},

			// 停止扫描设备
			stopScan() {
				if (!this.scanning) return;

				uni.stopBluetoothDevicesDiscovery({
					success: () => {
						this.log(`扫描结束,共发现 ${this.devices.length} 个设备`);
						this.statusText = `扫描完成(${this.devices.length}个设备)`;
					},
					fail: err => {
						this.log(`停止扫描失败:${JSON.stringify(err)}`);
					},
					complete: () => {
						this.scanning = false;
					}
				});
			},

			// 选择设备
			selectDevice(device) {
				if (this.scanning) return; // 扫描中不允许选择

				this.selectedDeviceId = device.deviceId;
				const deviceName = device.name || device.localName || '未知设备';
				this.statusText = `已选择:${deviceName}`;
				this.log(`选中设备:${deviceName}(ID:${device.deviceId})`);
			},

			// 连接设备(主方法)
			connectToDevice() {
				if (!this.selectedDeviceId) return;
				this.connectRetryCount = 0; // 重置重试计数
				
				this.statusText = '正在连接设备...';
				const device = this.devices.find(d => d.deviceId === this.selectedDeviceId);
				const deviceName = device?.name || device?.localName || '未知设备';
				this.log(`开始连接设备:${deviceName}`);

				// 关键优化:先断开可能的已有连接
				if (this.isConnected) {
					this.log('关闭已有连接...');
					uni.closeBLEConnection({
						deviceId: this.selectedDeviceId,
						success: () => {
							this.log('已断开旧连接,准备重新连接');
							this.performBLEConnection();
						},
						fail: () => {
							this.log('断开旧连接失败,尝试直接连接');
							this.performBLEConnection();
						}
					});
				} else {
					this.performBLEConnection();
				}
			},

			// 执行蓝牙连接(带重试机制)
			performBLEConnection() {
				uni.createBLEConnection({
					deviceId: this.selectedDeviceId,
					timeout: 15000, // 延长超时时间至15秒
					success: () => {
						this.connectRetryCount = 0; // 重置重试计数
						this.log('设备连接成功,开始发现服务...');
						this.statusText = '连接成功,发现服务中...';
						setTimeout(() => {
							this.discoverServices();
						}, 800); // 增加延迟时间,确保连接稳定
					},
					fail: err => {
						this.log(`连接失败:${JSON.stringify(err)}`);
						
						// 连接超时错误,尝试重试
						if (err.errCode === 10012 && this.connectRetryCount < this.maxConnectRetries) {
							this.connectRetryCount++;
							this.log(`尝试第 ${this.connectRetryCount}/${this.maxConnectRetries} 次重试连接...`);
							
							// 增加随机延迟,避免固定频率重试导致系统阻塞
							const delay = 1000 + Math.random() * 1000;
							setTimeout(() => {
								this.performBLEConnection();
							}, delay);
							return;
						}
						
						// 最终连接失败处理
						this.statusText = '连接设备失败';
						
						// 提供更详细的错误提示
						if (err.errCode === 10012) {
							uni.showModal({
								title: '连接超时',
								content: '1. 请确保设备处于可连接状态\n2. 尝试重启设备和手机蓝牙\n3. 请将设备靠近手机',
								showCancel: false
							});
						}
					}
				});
			},

			// 发现设备服务
			discoverServices() {
				uni.getBLEDeviceServices({
					deviceId: this.selectedDeviceId,
					success: res => {
						this.log(`发现服务列表:${JSON.stringify(res.services.map(s => s.uuid))}`);

						// 查找目标服务(忽略大小写匹配)
						const targetService = res.services.find(s =>
							s.uuid.toLowerCase() === this.serviceId.toLowerCase()
						);

						if (targetService) {
							this.log(`找到目标服务:${this.serviceId}`);
							this.isConnected = true;
							this.statusText = '已连接,等待发送配网信息';
							this.enableNotify(); // 启用通知特征
						} else {
							this.log(`未找到目标服务(${this.serviceId})`);
							this.statusText = '连接失败:未找到目标服务';
							this.disconnect(); // 断开连接
							uni.showToast({ title: '未找到目标服务', icon: 'none' });
						}
					},
					fail: err => {
						this.log(`获取服务失败:${JSON.stringify(err)}`);
						this.statusText = '获取服务失败';
						this.disconnect();
					}
				});
			},

			// 启用通知(接收设备返回的结果)
			enableNotify() {
				uni.getBLEDeviceCharacteristics({
					deviceId: this.selectedDeviceId,
					serviceId: this.serviceId,
					success: res => {
						this.log(`服务特征列表:${JSON.stringify(res.characteristics.map(c => c.uuid))}`);

						// 查找通知特征
						const notifyChar = res.characteristics.find(c =>
							c.uuid.toLowerCase() === this.charNotify.toLowerCase()
						);

						if (!notifyChar) {
							this.log(`未找到通知特征(${this.charNotify})`);
							return;
						}

						// 检查是否支持通知
						if (!notifyChar.properties.notify) {
							this.log(`通知特征不支持notify属性`);
							return;
						}

						// 启用通知
						uni.notifyBLECharacteristicValueChange({
							deviceId: this.selectedDeviceId,
							serviceId: this.serviceId,
							characteristicId: this.charNotify,
							state: true,
							success: () => {
								this.log('通知功能已启用,等待设备响应...');
								this.statusText = '已连接,可发送配网信息';
							},
							fail: err => {
								this.log(`启用通知失败:${JSON.stringify(err)}`);
							}
						});
					},
					fail: err => {
						this.log(`获取特征失败:${JSON.stringify(err)}`);
					}
				});
			},

			// 发送配网信息(SSID和密码)
			sendCredentials() {
				if (!this.ssid.trim() || !this.password) {
					uni.showToast({
						title: '请输入WiFi名称和密码',
						icon: 'none'
					});
					return;
				}

				this.statusText = '正在发送SSID...';
				// 先发送SSID,成功后再发送密码
				this.writeToCharacteristic(this.charSSID, this.ssid, () => {
					this.statusText = '正在发送密码...';
					this.writeToCharacteristic(this.charPASS, this.password, () => {
						this.statusText = '配网信息发送完成,等待设备回应...';
						this.log('SSID和密码已全部发送,等待设备连接WiFi...');
					});
				});
			},

			// 向特征值写入数据
			writeToCharacteristic(uuid, value, callback) {
				try {
					// 将字符串转换为ArrayBuffer(UTF-8编码)
					const buffer = new TextEncoder().encode(value);

					uni.writeBLECharacteristicValue({
						deviceId: this.selectedDeviceId,
						serviceId: this.serviceId,
						characteristicId: uuid,
						value: buffer.buffer,
						success: () => {
							this.log(`成功写入${uuid}:${value}`);
							callback && callback();
						},
						fail: err => {
							this.log(`写入${uuid}失败:${JSON.stringify(err)}`);
							this.statusText = `发送失败:${err.errMsg}`;
						}
					});
				} catch (e) {
					this.log(`写入数据异常:${e.message}`);
				}
			},

			// 处理设备返回的通知
			onNotify(res) {
				try {
					const result = new TextDecoder().decode(res.value);
					this.statusText = `设备回应:${result}`;
					this.log(`收到设备通知:${result}`);

					// 常见结果处理(根据ESP32返回的实际内容调整)
					if (result.includes('成功')) {
						uni.showToast({
							title: '配网成功!',
							icon: 'success'
						});
					} else if (result.includes('失败')) {
						uni.showToast({
							title: '配网失败',
							icon: 'none'
						});
					}
				} catch (e) {
					this.log(`解析通知数据失败:${e.message}`);
				}
			},

			// 断开连接
			disconnect() {
				if (!this.selectedDeviceId) return;

				uni.closeBLEConnection({
					deviceId: this.selectedDeviceId,
					success: () => {
						this.log('已断开设备连接');
					},
					fail: err => {
						this.log(`断开连接失败:${JSON.stringify(err)}`);
						
						// 尝试强制重置蓝牙适配器
						this.resetBluetoothAdapter();
					},
					complete: () => {
						this.isConnected = false;
						this.selectedDeviceId = '';
					}
				});
			},

			// 重置蓝牙适配器
			resetBluetoothAdapter() {
				this.log('正在重置蓝牙适配器...');
				uni.closeBluetoothAdapter({
					success: () => {
						setTimeout(() => {
							uni.openBluetoothAdapter({
								success: () => {
									this.log('蓝牙适配器重置成功');
								},
								fail: err => {
									this.log(`蓝牙适配器重置失败:${JSON.stringify(err)}`);
								}
							});
						}, 1000);
					}
				});
			}
		},

		// 页面加载时初始化通知监听
		onLoad() {
			// 监听设备特征值变化(接收配网结果)
			uni.onBLECharacteristicValueChange(res => {
				this.onNotify(res);
			});
		},

		// 页面卸载时清理资源
		onUnload() {
			this.log('页面关闭,清理蓝牙资源');
			// 停止扫描
			if (this.scanning) {
				uni.stopBluetoothDevicesDiscovery();
			}
			// 断开连接
			this.disconnect();
			// 关闭蓝牙适配器
			uni.closeBluetoothAdapter();
			// 移除设备发现监听
			if (this.deviceFoundListener) {
				uni.offBluetoothDeviceFound(this.deviceFoundListener);
			}
		}
	};
</script>

<style scoped>
	.container {
		padding: 20rpx;
		font-size: 28rpx;
	}

	.header {
		text-align: center;
		padding: 30rpx 0;
	}

	.title {
		font-size: 36rpx;
		font-weight: bold;
		color: #333;
	}

	.form {
		margin: 20rpx 0;
	}

	.input {
		border: 1px solid #ccc;
		border-radius: 8rpx;
		margin-bottom: 20rpx;
		padding: 20rpx;
		font-size: 28rpx;
	}

	.buttons {
		display: flex;
		gap: 15rpx;
		margin: 30rpx 0;
	}

	.buttons button {
		flex: 1;
		padding: 20rpx 0;
		border-radius: 8rpx;
		background-color: #007aff;
		color: white;
	}

	.buttons button:disabled {
		background-color: #ccc;
	}

	.status {
		margin: 20rpx 0;
		padding: 15rpx;
		border-left: 4rpx solid #007aff;
		background-color: #f5f7fa;
		color: #333;
	}

	.devices {
		margin: 20rpx 0;
		padding: 15rpx;
		border: 1px solid #eee;
		border-radius: 8rpx;
	}

	.device-item {
		padding: 15rpx 0;
		border-bottom: 1px solid #eee;
		cursor: pointer;
	}

	.device-item:last-child {
		border-bottom: none;
	}

	.log {
		margin: 20rpx 0;
		padding: 15rpx;
		border: 1px solid #eee;
		border-radius: 8rpx;
		color: #666;
	}

	.log text {
		display: block;
		margin-bottom: 10rpx;
		font-weight: bold;
		color: #333;
	}

	.log scroll-view {
		border: 1px dashed #eee;
		padding: 10rpx;
	}
</style>

四、🔌 蓝牙通信流程详解

1. 蓝牙初始化与扫描

用户 页面 蓝牙模块 点击"扫描设备"按钮 初始化适配器(openBluetoothAdapter) 初始化成功 开始扫描(startBluetoothDevicesDiscovery) 返回发现的设备(onBluetoothDeviceFound) 更新设备列表并显示 8秒后停止扫描(stopBluetoothDevicesDiscovery) 用户 页面 蓝牙模块

2. 设备连接与服务发现

用户 页面 蓝牙模块 选择设备并点击"连接设备" 创建连接(createBLEConnection) 连接成功 获取服务列表(getBLEDeviceServices) 返回服务列表 获取特征值列表(getBLEDeviceCharacteristics) 返回特征值列表 启用通知(notifyBLECharacteristicValueChange) 用户 页面 蓝牙模块

3. 数据传输流程

用户 页面 蓝牙模块 ESP32设备 输入WiFi信息并点击"发送配网信息" 写入SSID到charSSID(writeBLECharacteristicValue) 传输SSID数据 接收确认 写入成功 写入密码到charPASS 传输密码数据 接收确认 写入成功 通过charNotify发送配网结果 触发特征值变化事件(onBLECharacteristicValueChange) 解析结果并显示给用户 用户 页面 蓝牙模块 ESP32设备

五、⚙️ 关键技术点解析

1. UUID匹配机制

代码中定义的UUID必须与ESP32端完全一致:

// 服务UUID - 标识ESP32的配网服务
serviceId: '000000FF-0000-1000-8000-00805f9b34fb',

// 特征值UUID - 分别用于接收SSID、密码和发送通知
charSSID: '0000FFE2-0000-1000-8000-00805f9b34fb',
charPASS: '0000FFE3-0000-1000-8000-00805f9b34fb',
charNotify: '0000FFE1-0000-1000-8000-00805f9b34fb',

2. ArrayBuffer数据处理

蓝牙传输要求数据为二进制格式,通过以下方式转换:

// 字符串转ArrayBuffer(发送时)
const buffer = new TextEncoder().encode("WiFi名称");

// ArrayBuffer转字符串(接收时)
const result = new TextDecoder().decode(arrayBuffer);

3. 连接重试机制

针对连接超时问题,实现了自动重试:

// 连接超时错误处理
if (err.errCode === 10012 && this.connectRetryCount < 3) {
  this.connectRetryCount++;
  this.log(`尝试第 ${this.connectRetryCount}/3 次重试连接...`);
  
  // 随机延迟避免系统阻塞
  const delay = 1000 + Math.random() * 1000;
  setTimeout(() => this.performBLEConnection(), delay);
}

4. 平台权限适配

// #ifdef APP-PLUS
// App平台需要额外申请位置权限
plus.android.requestPermissions([
  'android.permission.BLUETOOTH',
  'android.permission.ACCESS_FINE_LOCATION'
]);
// #endif

六、🛠️ 开发与调试指南

1. 环境准备

  • HBuilderX 3.2.0+
  • 安卓/iOS真机(蓝牙功能在模拟器中受限)
  • ESP32-S3开发板(已烧录蓝牙配网固件)

2. 调试技巧

  1. 日志输出:页面下方的日志区域实时显示操作记录,便于追踪问题
  2. 信号强度:扫描到的设备会显示RSSI值(信号强度),选择信号强的设备
  3. 错误码参考
错误码含义解决方案
10001蓝牙未开启提示用户开启蓝牙
10002没有找到指定设备检查设备是否在范围内
10003连接失败检查设备是否被占用,尝试重启
10004没有找到指定服务检查UUID是否与设备一致
10008连接超时增加超时时间或重试次数

七、💡 常见问题与解决方案

1. 扫描不到设备

  • 检查手机蓝牙是否开启
  • 确认ESP32设备处于可发现状态
  • 尝试重启手机和设备
  • 检查App的蓝牙权限是否已授予

2. 连接失败或超时

  • 确保设备UUID匹配
  • 检查设备是否已被其他设备连接
  • 增加连接超时时间(代码中默认15秒)
  • 实现自动重试机制(代码中已包含)

3. 数据发送失败

  • 确认特征值权限是否正确配置(需支持WRITE属性)
  • 检查数据长度是否超过MTU限制(通常为20字节,超长需分包)
  • 添加错误处理和重发机制

八、📈 性能优化建议

  1. 扫描优化

    • 限制扫描时间(代码中为8秒)
    • 过滤无效设备(信号弱、无名称)
  2. 连接稳定性

    • 实现自动重试机制
    • 连接前先断开已有连接
    • 发现服务前增加短暂延迟
  3. 内存管理

    • 页面卸载时清理所有蓝牙资源
    • 移除事件监听避免内存泄漏

九、🎉 总结与扩展

通过本文提供的代码和解析,你可以实现一个完整的ESP32-S3蓝牙配网功能。这个方案具有以下特点:

  • 跨平台支持:同时支持安卓和iOS设备
  • 用户友好:直观的界面和详细的日志输出
  • 高稳定性:完善的错误处理和重试机制
  • 可扩展性:可根据需求添加更多功能,如:
    • 设备固件升级
    • 设备状态监控
    • 多设备管理

十、📚 参考资源

  1. Uniapp蓝牙API文档
  2. ESP32 Arduino蓝牙开发指南
  3. 蓝牙GATT协议详解
  4. ArrayBuffer数据处理

希望这篇文章对你理解蓝牙配网和Uniapp开发有所帮助!如果你有任何问题或建议,欢迎在评论区留言讨论~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值