微信小程序> 微信小程序Ble设备连接与发送-微信小程序ble-微信小程序ibeacon

微信小程序Ble设备连接与发送-微信小程序ble-微信小程序ibeacon

浏览量:2021 时间: 来源:Vhaiyue
1.

闲来没事钻研小程序开发,写了一篇关于微信小程序ble设备控制的底层api,话不多说直接上源码:

2.

目录结构:

3.

baseBleApi.js文件:

varcurrentDevice{};//ble设备varnotifyService{};//唤醒特征值varwriteService{};//写入服务varwriteCharacteristic{};//写入特征值varnotifyCharacteristic{};//唤醒特征值varonSendSuccessCallBackundefined;//成功回调varonConnectCallbackundefined;//链接回调varbleUtilsrequire('utils/strUtils.js');varcrcrequire('crc.js');varisConnectedfalse;//是否已链接module.exports{writeCommend:writeCommend,closeBLEConnection:closeBLEConnection//disConnect,disConnect,//openBluetoothAdapter:openBluetoothAdapter}/***@paramsendCommend写入命令*@paramdeviceName锁具名称*@paramonSuccessCallBack成功回调*@paramonFailCallBack返回失败回调*@paramonCompleteCallBack成功失败都会回调**@paramservices主服务serviceUUID*@paramwriteServiceUUID写入服务UUID*@paramnotifyServiceUUID唤醒服务UUID**@paramnotifyCharacteristicUUID唤醒特征值UUID*@paramwriteCharacteristicUUID写入特征值UUID*/functionwriteCommend(options){varparams{};vardefalt{adviceId:"",sendCommend:"",onSuccessCallBack:functionsuccess(res){},onFailCallBack:functionsuccess(res){},onCompleteCallBack:functionsuccess(res){},services:[],writeServiceUUID:"",notifyServiceUUID:"",notifyCharacteristicUUID:"",writeCharacteristicUUID:""};paramsObject.assign(defalt,options)//setConnectionStateChange(params.onFailCallBack)if(!setConnectionStateChange()){openBluetoothAdapter(params);}else{//typeofstr'string'sendCmd(params.sendCommend,params.onSuccessCallBack,params.onFailCallBack);}}/***初始化蓝牙适配器*/functionopenBluetoothAdapter(params){wx.openBluetoothAdapter({success:function(res){console.log("初始化蓝牙适配器成功")console.log(res)startBluetoothDevicesDiscovery(params)},fail:function(res){console.log("初始化蓝牙适配器失败")params.onFailCallBack(res.errMsg)console.log(res);return},complete:function(res){console.log(res);}})}/***开始搜寻附近的蓝牙外围设备。注意,该操作比较耗费系统资源,请在搜索并连接到设备后调用stop方法停止搜索。*@paramsservices:['4asdga'],根据主ServiceUUID进行搜索特定蓝牙,提高搜索效率*本ble设备主ServiceUUid:"6E400001-B5A3-F393-E0A9-E50E24DCCA9E"*/vardelayTimer;//停止循环获取tagvarisFoundfalsefunctionstartBluetoothDevicesDiscovery(params,connectCallback){if(typeofconnectCallback'undefined'){connectCallbackfunction(errMsg){}}onConnectCallbackconnectCallback;setTimeout(function(){if(isFound){return;}else{console.log("搜索设备超时");params.onFailCallBack("搜索设备超时")stopBluetoothDevicesDiscovery();clearInterval(delayTimer)return}},10000);wx.startBluetoothDevicesDiscovery({services:params.services,success:function(res){console.log(res)console.log("开启搜索成功")getBluetoothDevices(params)},fail:function(res){console.log("开启搜索失败")console.log(res)params.onFailCallBack(res)return},complete:function(res){//completeconsole.log(res);}})//每隔一秒获取一次delayTimersetInterval(function(){getBluetoothDevices(params)},1000)}/***获取所有已发现的蓝牙设备,包括已经和本机处于连接状态的设备*/functiongetBluetoothDevices(params){wx.getBluetoothDevices({success:function(res){console.log("getBluetoothDevices");console.log(res.devices);for(vari0;ires.devices.length;i++){//忽略传入的deviceName大小写//isContainsbleUtilsvarlockNameFinalbleUtils.removeBytes(params.adviceId,":")if(bleUtils.isContains(res.devices[i].name,lockNameFinal)){console.log("搜索到要链接的设备....")stopBluetoothDevicesDiscovery();isFoundtrueclearInterval(delayTimer)currentDeviceres.devices[i]createBLEConnection(params)}}},fail:function(res){clearInterval(delayTimer)console.log("没有搜索到要链接的设备....")console.log(res)params.onFailCallBack(res)stopBluetoothDevicesDiscovery();return}})}/***停止搜寻附近的蓝牙外围设备。请在确保找到需要连接的设备后调用该方法停止搜索。*/functionstopBluetoothDevicesDiscovery(){wx.stopBluetoothDevicesDiscovery({success:function(res){console.log(res)}})}/***连接低功耗蓝牙设备*/functioncreateBLEConnection(params){//setConnectionStateChange(params.onFailCallBack);setTimeout(function(){if(isConnected)return;console.log("连接设备超时");params.onFailCallBack("连接设备超时")return},5000)wx.createBLEConnection({//这里的deviceId需要在上面的getBluetoothDevices或onBluetoothDeviceFound接口中获取deviceId:currentDevice.deviceId+"",success:function(res){console.log(res)console.log(`连接成功:${currentDevice.deviceId}`)isConnectedtruegetBLEDeviceServices(params);},fail:function(res){console.log(res)params.onFailCallBack(res)console.log(`连接失败:${currentDevice.deviceId}`)}})}/***closeBLEConnection*断开与低功耗蓝牙设备的连接*/functioncloseBLEConnection(deviceId){wx.closeBLEConnection({deviceId:currentDevice.deviceId+"",success:function(res){console.log(res)}})}/****返回蓝牙是否正处于链接状态*/functionsetConnectionStateChange(onFailCallback){wx.onBLEConnectionStateChange(function(res){//该方法回调中可以用于处理连接意外断开等异常情况console.log(`device${res.deviceId}statehaschanged,connected:${res.connected}`);returnres.connected;});}/***获取蓝牙设备所有service(服务)*@paramswriteServiceUUID:ble设备所具有的写入服务UUID*@paramsnotifyServiceUUID:ble设备具有的唤醒服务UUID*/functiongetBLEDeviceServices(params){wx.getBLEDeviceServices({//这里的deviceId需要在上面的getBluetoothDevices或onBluetoothDeviceFound接口中获取deviceId:currentDevice.deviceId+"",success:function(res){console.log(`获取服务成功:`)console.log('deviceservices:',res.services)for(vari0;ires.services.length;i++){if(res.services[i].uuidparams.writeServiceUUID){writeServiceres.services[i]}if(res.services[i].uuidparams.notifyServiceUUID){notifyServiceres.services[i]}}//获取getNotifyBLEDeviceCharacteristics(params);}})}/***获取蓝牙设备唤醒characteristic(特征值)*/functiongetNotifyBLEDeviceCharacteristics(params){wx.getBLEDeviceCharacteristics({//这里的deviceId需要在上面的getBluetoothDevices或onBluetoothDeviceFound接口中获取deviceId:currentDevice.deviceId+"",//这里的serviceId需要在上面的getBLEDeviceServices接口中获取serviceId:notifyService.uuid+"",success:function(res){console.log("唤醒特征值获取成功:")console.log('devicegetBLEDeviceCharacteristics:',res.characteristics)for(vari0;ires.characteristics.length;i++){if(res.characteristics[i].uuidparams.notifyCharacteristicUUID){notifyCharacteristicres.characteristics[i]}}//getWriteBLEDeviceCharacteristics();console.log("唤醒特征值:",notifyCharacteristic)getWriteBLEDeviceCharacteristics(params)}})}functiongetWriteBLEDeviceCharacteristics(params){wx.getBLEDeviceCharacteristics({//这里的deviceId需要在上面的getBluetoothDevices或onBluetoothDeviceFound接口中获取deviceId:currentDevice.deviceId+"",//这里的serviceId需要在上面的getBLEDeviceServices接口中获取serviceId:writeService.uuid+"",success:function(res){console.log("写入特征值获取成功:")console.log('devicegetBLEDeviceCharacteristics:',res.characteristics)for(vari0;ires.characteristics.length;i++){if(res.characteristics[i].uuidparams.writeCharacteristicUUID){writeCharacteristicres.characteristics[i]}}console.log("xieru:",writeCharacteristic)initNotifyListener(params);}})}/****连接成功后,初始化回调监听**启用低功耗蓝牙设备特征值变化时的notify功能。注意:*必须设备的特征值支持notify才可以成功调用,具体参照characteristic的properties属性*/functioninitNotifyListener(params){wx.notifyBLECharacteristicValueChanged({deviceId:currentDevice.deviceId+"",serviceId:notifyService.uuid+"",characteristicId:notifyCharacteristic.uuid+"",state:true,success:function(res){console.log(`开启监听成功${res.errMsg}`);setTimeout(function(){onConnectCallback('ok');//连接成功后,初始化回调监听回调sendCmd(params.sendCommend,params.onSuccessCallBack,params.onFailCallBack);},200);},fail:function(res){console.log("开启监听失败"+res.errMsg);params.onFailCallBack("开启监听失败");}});onBLECharacteristicValueChange();}/***启用低功耗蓝牙设备特征值变化时的notify功能。注意:*必须设备的特征值支持notify才可以成功调用,具体参照characteristic的properties属性*/functiononBLECharacteristicValueChange(){wx.onBLECharacteristicValueChange(function(res){console.log(`characteristic${res.characteristicId}haschanged,nowis${bleUtils.arrayBuffer2HexString(res.value)}`);onSendSuccessCallBack(bleUtils.arrayBuffer2HexString(res.value));})}/***发送指令,不关心指令具体长度*@paramcommond指令*@paramonSuccess指令执行成功回调*/functionsendCmd(commond,onSuccess,onFailCallback){varsendCommondscrc.getCRCCmd(commond);//对commond的CRC处理必须放在这里if(typeofonSuccess'undefined'){onSuccessfunction(result){}}onSendSuccessCallBackonSuccess;sendCmds(sendCommonds,0,onFailCallback);}/****逐条发送指令*/functionsendCmds(commond,index,onFailCallback){varitemCmd;varisLastfalse;//判断是否是最后一条if(commond.lengthindex+40){itemCmdcommond.substr(index,40);}else{isLasttrue;itemCmdcommond.substr(index);}writeCommendToBle(itemCmd,function(errMsg){if(errMsg'ok'!isLast){//发送成功并且不是最后一条时,执行下一条sendCmds(commond,index+40);}},onFailCallback)}//向蓝牙中写入数据(ble蓝牙)(增加指纹)functionwriteCommendToBle(commonds,onSendCallback,onFailCallback){varcommondcommonds;console.log("commond:"+commond)letbufferbleUtils.hexString2ArrayBuffer(commond);console.log(`执行指令:${bleUtils.arrayBuffer2HexString(buffer)}`);wx.writeBLECharacteristicValue({deviceId:currentDevice.deviceId+"",serviceId:writeService.uuid+'',characteristicId:writeCharacteristic.uuid+'',//这里的value是ArrayBuffer类型value:buffer,success:function(res){console.log('发送指令成功')console.log('writeBLECharacteristicValuesuccess',res.errMsg)onSendCallback('ok');},fail:function(res){console.log(`执行指令失败${res.errMsg}`);onFailCallback("执行指令失败");}})}4.

crc.js文件:

functionCRC16(data){varlendata.length;if(len0){varcrc0xFFFF;for(vari0;ilen;i++){crc(crc^(data[i]));for(varj0;j8;j++){crc(crc1)!0?((crc1)^0xA001):(crc1);}}varhi((crc0xFF00)8);//高位置varlo(crc0x00FF);//低位置//return[hi,lo];return[lo,hi];//大端模式}return[0,0];};functionisArray(arr){returnObject.prototype.toString.call(arr)'[objectArray]';};functionToCRC16(str,isReverse){returntoString(CRC16(isArray(str)?str:strToByte(str)),isReverse);};functionstrToByte(str){vartmpstr.split(''),arr[];for(vari0,ctmp.length;ic;i++){varjencodeURI(tmp[i]);if(j.length1){arr.push(j.charCodeAt());}else{varbj.split('%');for(varm1;mb.length;m++){arr.push(parseInt('0x'+b[m]));}}}returnarr;};functionconvertChinese(str){vartmpstr.split(''),arr[];for(vari0,ctmp.length;ic;i++){varstmp[i].charCodeAt();if(s0||s127){arr.push(s.toString(16));}else{arr.push(tmp[i]);}}returnarr;};functionfilterChinese(str){vartmpstr.split(''),arr[];for(vari0,ctmp.length;ic;i++){varstmp[i].charCodeAt();if(s0s127){arr.push(tmp[i]);}}returnarr;};functionstrToHex(hex,isFilterChinese){hexisFilterChinese?filterChinese(hex).join(''):convertChinese(hex).join('');//清除所有空格hexhex.replace(/s/g,"");//若字符个数为奇数,补一个空格hex+hex.length%2!0?"":"";varchex.length/2,arr[];for(vari0;ic;i++){arr.push(parseInt(hex.substr(i*2,2),16));}returnarr;};functionpadLeft(s,w,pc){if(pcundefined){pc'0';}for(vari0,cw-s.length;ic;i++){spc+s;}returns;};functiontoString(arr,isReverse){if(typeofisReverse'undefined'){isReversetrue;}varhiarr[0],loarr[1];returnpadLeft((isReverse?hi+lo*0x100:hi*0x100+lo).toString(16).toUpperCase(),4,'0');};functionCRC16(data){varlendata.length;if(len0){varcrc0xFFFF;for(vari0;ilen;i++){crc(crc^(data[i]));for(varj0;j8;j++){crc(crc1)!0?((crc1)^0xA001):(crc1);}}varhi((crc0xFF00)8);//高位置varlo(crc0x00FF);//低位置//return[hi,lo];return[lo,hi];//大端模式}return[0,0];};functionToCRC16(str,isReverse){returntoString(CRC16(isArray(str)?str:strToByte(str)),isReverse);};functionToModbusCRC16(str,isReverse){returntoString(CRC16(isArray(str)?str:strToHex(str)),isReverse);};/***给命令增加CRC16进制**@paramhex*@return16进制*/functiongetCRCCmd(hex){varhexTemphex;if(hex.toUpperCase().startsWith("0X")){hexTemphex.substr(4);}elseif(hex.toUpperCase().startsWith("AA")){hexTemphex.substr(2);}returnhex+getCRCStr(hexTemp);}/***获取CRC16进制**@paramdata*@return*/functiongetCRCStr(data){returnToModbusCRC16(data);}module.exports{ToCRC16:ToCRC16,ToModbusCRC16:ToModbusCRC16,getCRCCmd:getCRCCmd}5.

strUtils文件:

/***ArrayBuffer转16进制字符串*/functionarrayBuffer2HexString(buf){varout"";varu8anewUint8Array(buf);varsingle;for(vari0;iu8a.length;i++){singleu8a[i].toString(16)while(single.length2)single"0".concat(single);out+single;}returnout;}/***1、字符串转换为十六进制*主要使用charCodeAt()方法,此方法返回一个字符的Unicode值,该字符位于指定索引位置。*/functionstringToHex(str){varval"";for(vari0;istr.length;i++){val+str.charCodeAt(i).toString(16);}returnval;}functionfilterChinese(str){vartmpstr.split(''),arr[];for(vari0,ctmp.length;ic;i++){varstmp[i].charCodeAt();if(s0s127){arr.push(tmp[i]);}}returnarr;};functionstrToHex(hex,isFilterChinese){hexisFilterChinese?filterChinese(hex).join(''):convertChinese(hex).join('');//清除所有空格hexhex.replace(/s/g,"");//若字符个数为奇数,补一个空格hex+hex.length%2!0?"":"";varchex.length/2,arr[];for(vari0;ic;i++){arr.push(parseInt(hex.substr(i*2,2),16));}returnarr;};/***16进制字符串转ArrayBuffer*/functionhexString2ArrayBuffer(hexStr){varcounthexStr.length/2;letbuffernewArrayBuffer(count);letdataViewnewDataView(buffer);for(vari0;icount;i++){varcurCharCodeparseInt(hexStr.substr(i*2,2),16);dataView.setUint8(i,curCharCode);}returnbuffer;}/***字符串转为ArrayBuffer对象,参数为字符串*/functionstring2ArrayBuffer(str){varbufnewArrayBuffer(str.length*2);//每个字符占用2个字节varbufViewnewUint8Array(buf);for(vari0,strLenstr.length;istrLen;i++){bufView[i]str.charCodeAt(i);}returnbuf;}functionisArray(arr){returnObject.prototype.toString.call(arr)'[objectArray]';};functionstrToByte(str){vartmpstr.split(''),arr[];for(vari0,ctmp.length;ic;i++){varjencodeURI(tmp[i]);if(j.length1){arr.push(j.charCodeAt());}else{varbj.split('%');for(varm1;mb.length;m++){arr.push(parseInt('0x'+b[m]));}}}returnarr;};functionconvertChinese(str){vartmpstr.split(''),arr[];for(vari0,ctmp.length;ic;i++){varstmp[i].charCodeAt();if(s0||s127){arr.push(s.toString(16));}else{arr.push(tmp[i]);}}returnarr;};functionfilterChinese(str){vartmpstr.split(''),arr[];for(vari0,ctmp.length;ic;i++){varstmp[i].charCodeAt();if(s0s127){arr.push(tmp[i]);}}returnarr;};functionstrToHex(hex,isFilterChinese){hexisFilterChinese?filterChinese(hex).join(''):convertChinese(hex).join('');//清除所有空格hexhex.replace(/s/g,"");//若字符个数为奇数,补一个空格hex+hex.length%2!0?"":"";varchex.length/2,arr[];for(vari0;ic;i++){arr.push(parseInt(hex.substr(i*2,2),16));}returnarr;};functionpadLeft(s,w,pc){if(pcundefined){pc'0';}for(vari0,cw-s.length;ic;i++){spc+s;}returns;};functiontoString(arr,isReverse){if(typeofisReverse'undefined'){isReversetrue;}varhiarr[0],loarr[1];returnpadLeft((isReverse?hi+lo*0x100:hi*0x100+lo).toString(16).toUpperCase(),4,'0');};/***16进制字符串异或处理**@paramstr1*@paramstr2*@return*/functionstringXor(str1,str2){if(!str1!str2){return"";}if(!str1str2){returnstr2;}if(str1!str2){returnstr1;}varlongStr;varshortStr;if(str1.lengthstr2.length){longStrstr1;shortStrstr2;}else{longStrstr2;shortStrstr1;}varcountparseInt(shortStr.length/2);varleftCountlongStr.length-shortStr.length;varresultStr"";if(leftCount0){resultStr+longStr.substr(0,leftCount);}for(vari0;icount;i++){varshortCharCodeparseInt(shortStr.substr(i*2,2),16);varlongCharCodeparseInt(longStr.substr(leftCount+i*2,2),16);varresultCodeshortCharCode^longCharCode;varsingleresultCode.toString(16);while(single.length2)single"0".concat(single);resultStr+single;}returnresultStr.toUpperCase();}/***指令两个16进制字符串异或处理**@paramcommand*@paramsecretKey*@return*/functiongetSecretEncoding(command,secretKey){if(!command||!secretKey){return"";}varsecretLengthsecretKey.length;varlengthparseInt(command.length/secretLength);console.log(`command(${command.length})/secretLength(${secretLength})${length}`);varresultCmd"";console.log(`secretKey(${secretKey.length}):${secretKey}`);for(vari0;ilength;i++){varpartcommand.substr(i*secretLength,secretLength);resultCmd+stringXor(part,secretKey);console.log(`part${i}:${stringXor(part,secretKey)}`);}varlastLencommand.length%secretLength;if(lastLen0){console.log(`lastCMD:${command.substr(command.length-lastLen,lastLen)}`);console.log(`lastSecretKey:${secretKey.substr(0,lastLen)}`);varlastPartcommand.substr(command.length-lastLen,lastLen);varlastCmdstringXor(lastPart,secretKey.substr(0,lastLen));resultCmd+lastCmd;console.log(`lastPart:${lastCmd}`);}returnresultCmd;}/***2、十六进制转换为字符串*主要使用fromCharCode()方法,此方法将Unicode码转换为与之对应的字符。*/functionhexToString(str){varval"";vararrstr.split(",");for(vari0;iarr.length;i++){val+arr[i].fromCharCode(i);}returnval;}/***获取随机长度16进制字符串*/functiongetRamNumber(length){varresult'';for(vari0;ilength;i++){result+Math.floor(Math.random()*16).toString(16);//获取0-15并通过toString转16进制}//默认字母小写,手动转大写returnresult.toUpperCase();//另toLowerCase()转小写}/***得到BCD码时间字符串**@paramdatetime*@return*/functiongetBCDTime(datetime,needWeek){if(typeofdatetime'undefined'){datetimenewDate();}if(typeofneedWeek'undefined'){needWeektrue;}varyeardatetime.getFullYear()-2000;//获取年份,从2000年开始计算if(year0)year0;//不允许小于2000年的年份出现varmonthdatetime.getMonth()+1;//获取月份0-11所以需要加1vardaydatetime.getDate();//获取日varhourdatetime.getHours();//小时varminutedatetime.getMinutes();//分varseconddatetime.getSeconds();//秒if(needWeek){vardayOfWeekdatetime.getDay();//一周的第几天0-6returnformatNumber(year)+formatNumber(month)+formatNumber(day)+formatNumber(dayOfWeek)+formatNumber(hour)+formatNumber(minute)+formatNumber(second);//得到BCD码的时间字符串}else{returnformatNumber(year)+formatNumber(month)+formatNumber(day)+formatNumber(hour)+formatNumber(minute)+formatNumber(second);//得到BCD码的时间字符串}}functionformatNumber(n){nn.toString()return(n[1]?n:'0'+n)+"";}/***判断一个字符串是否包含子串*/functionisContains(str,substr){varstrUpstr.toUpperCase();varsubstrUpsubstr.toUpperCase()returnnewRegExp(substrUp).test(strUp);}/***去除字符串中特定的字符*/functionremoveBytes(str,substr){varitemsstr.split(substr)//会得到一个数组,数组中包括利用o分割后的多个字符串(不包括o)varnewStritems.join("");returnnewStr//}}module.exports{hexString2ArrayBuffer:hexString2ArrayBuffer,arrayBuffer2HexString:arrayBuffer2HexString,//getCRCCmd:getCRCCmd,getBCDTime:getBCDTime,stringToHex:stringToHex,stringXor:stringXor,getSecretEncoding:getSecretEncoding,hexToString:hexToString,getRamNumber:getRamNumber,isContains:isContains,removeBytes:removeBytes}听过baseBleApi接口调用输入参数直接可以对ble设备进行搜索发送链接6.

傻瓜式调用,

7.

为了方便用户,这里进行了二级封装

8.

antsBleApi.js文件时为了方便用户调用的二级接口:

varbleApirequire('baseBleApi.js');module.exports{sendCommand:sendCommand}functionsendCommand(params){vardefaults{adviceId:"",sendCommend:"",onSuccessCallBack:functionsuccessCallBack(res){},onFailCallBack:functionfailCallBack(res){},onCompleteCallBack:functioncompleteCallBack(res){},services:["xxxxx-xxxxxxxxx-xxxxx"],writeServiceUUID:"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",notifyServiceUUID:"xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",notifyCharacteristicUUID:"xxxxxxxxxx-xxxxx-xxxx-xxxx-xxxxxxxx",writeCharacteristicUUID:"xxxx-xxxx-xxxx-xxxx-xxxx"}varsetParamsObject.assign(defaults,params)bleApi.writeCommend(setParams);}此接口中service可以不写,但是adviceId,链接时必须写,,链接标志,writeServiceUUID,notifyServiceUUID,notifyCharacteristicUUID,writeCharacteristicUUID这几个参数是为了进行ble设备通信所用到的9.

调用demo:

openDoor:function(){varthatthisprogressUtils.showLoding();bleApi.sendCommand({deviceId:"xx:xx:xx:xx:xx:xx",sendCommend:"xxxxxxxxx",onSuccessCallBack:functiononSuccessCallBack(result){wx.showToast({title:result,icon:'',image:'',duration:1500,})console.log('result:'+result);}});

版权声明

即速应用倡导尊重与保护知识产权。如发现本站文章存在版权问题,烦请提供版权疑问、身份证明、版权证明、联系方式等发邮件至197452366@qq.com ,我们将及时处理。本站文章仅作分享交流用途,作者观点不等同于即速应用观点。用户与作者的任何交易与本站无关,请知悉。

产品经理

手机 : 13312967497

擅长 : 小程序流量变现

扫码领取礼包

最新资讯

热门模板

  • 头条
  • 搜狐
  • 微博
  • 百家
  • 一点资讯
  • 知乎