uniapp 开发安卓项目实现mqtt链接

做一个工厂扫码主动推送到pad上的一个需求需要用到mqtt链接首先需要引入mqttws31.js 注意不是官方那个版本是后面对其内容做出了一些修改内容过长放在最后面 let $mqtt = require("@/common/js/mqttws31.js"); export default { mixins: [mixin], components: { myNav, empty }, data() { return { TabCur: 0, sc
摘要由CSDN通过智能技术生成

做一个工厂扫码主动推送到pad上的一个需求需要用到mqtt链接

首先需要引入mqttws31.js 注意不是官方那个版本是后面对其内容做出了一些修改内容过长放在最后面

	let $mqtt = require("@/common/js/mqttws31.js");

	export default {
		mixins: [mixin],
		components: {
			myNav,
			empty
		},
		data() {
			return {
				TabCur: 0,
				scrollLeft: 0,
				params: {
					rcard: '', //sn条码
					stationCode: '', //当前pad登录工位代码
					productionLineCode: '', //当前pad登录产线代码
					processCode: '', //当前pad登录工位对应工序
				},
				stationInfo: {},
				loginInfo: {},
				mqtt_url:{},
				
				actionType:'',//动作类型
				inputCard:'',//进站读卡1
				insideCard:'',//站内读卡2
				outCard:'',//出站读卡3
				
				moInfo:{},//任务信息
				processInfo:{},//工序信息
				upperLimit:{},//堆积上线
			};
		},
		onLoad() {
			this.stationInfo = uni.getStorageSync('station_info');
			this.loginInfo = uni.getStorageSync('login_data');
			this.mqtt_url =  uni.getStorageSync('mqtt_url');
			this.params.stationCode = this.stationInfo.stationCode;
			this.params.productionLineCode = this.loginInfo.productionLineCode;
			this.params.processCode = this.stationInfo.processCode;
			console.log("loginInfo", this.loginInfo);
			console.log("stationInfo", this.stationInfo);
			console.log("mqtt_url", this.mqtt_url);
			// 获取登录接口返回数据
			let client;
			let host = this.mqtt_url.host;
			let port = parseInt(this.mqtt_url.port);
			// let host = "broker.emqx.io";
			// let port = 8083;
			let timestamp3 = new Date().getTime();
			// let clientId = "myclientid_" + parseInt(Math.random() * 100, 10);
			let clientId = this.params.stationCode
			let sentMsgs = 0;
			let connectOptions = connectOptions || {};
			let mqtt;
			let topic = this.params.stationCode;
			let serviceHost = "";
			let SN = "";
			let that = this;
		
			
			mqtt = new $mqtt.Client(
				host, //MQTT 域名
				port, //WebSocket 端口
				'/mqtt', //需要注意格式 是mqtt不是 ws 根据后端服务器设置
				clientId //客户端 ClientId
			);
			var options = {
				userName: "",
				password: "",
				keepAliveInterval: 30,
				timeout: 3,
				reconnect: false, // Enable automatic reconnect
				useSSL: false, //如果使用 HTTPS 加密则配置为 true
				onSuccess: function() { //连接成功
					console.log(new Date() + ": " + "连接成功");
					// Connection succeeded; subscribe to our topic
					//订阅消息
					mqtt.subscribe(topic, {
						qos: 0
					});
					//测试消息推送
					// var message = new $mqtt.Message("APP测试消息推送!!"); //set body
					message.destinationName = topic; // set topic
					// //发送 P2P 消息,topic 设置方式参考https://help.aliyun.com/document_detail/96176.html?spm=a2c4g.11186623.6.586.694f7cb4oookL7
					// message = new $mqtt.Message("Hello mqtt P2P Msg!!"); //set body
					// message.destinationName = topic + "/p2p/" + clientId; // set topic
					mqtt.send(message);

					// this.send()
				},
				onFailure: function(message) { //连接失败
					console.log(new Date() + ": " + "连接失败" + JSON.stringify(message));
					//连接失败,重新连接
				}
			};
			// mqtt.onConnectionLost = function onConnectionLost(response) {
			// 	console.log(new Date() + ": " + "连接中断" + JSON.stringify(response));
			// 	//连接失败,重新连接
			// };
			mqtt.onMessageArrived = function onMessageArrived(message) {
				var topic = message.destinationName;
				var payload = message.payloadString;
				console.log("获取RCard", JSON.parse(payload).RCard);
				
				that.params.rcard = JSON.parse(payload).RCard;
									
				switch(JSON.parse(payload).ActionType)
				{
					case 'InputRCard':
					that.inputCard = JSON.parse(payload).RCard;
					break;
					case 'InsideRCard':
					that.insideCard = JSON.parse(payload).RCard;
					that.params.rcard = that.insideCard;
					if(that.params.rcard !== ""){
						that.getMoTask();
					}
					
					break;
					case 'OutputRCard':
					that.outCard = JSON.parse(payload).RCard;
					break;
					case 'CollectionAction':
					that.inputCard = JSON.parse(payload).RCard;
					break;
				}
				
				that.title = payload
				// that.getTestPreparation()
				console.log(new Date() + ": " + "收到新消息" + topic + "   " + payload);
			};
			mqtt.connect(options);
			// 获取工位代码和产线代码
			
		},

修改后的mqtt代码

/*******************************************************************************
 * Copyright (c) 2013 IBM Corp.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 *
 * The Eclipse Public License is available at
 *    http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 *   http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *    Andrew Banks - initial API and implementation and initial documentation
 *******************************************************************************/


// Only expose a single object name in the global namespace.
// Everything must go through this module. Global Paho.MQTT module
// only has a single public function, client, which returns
// a Paho.MQTT client object given connection details.

/**
 * Send and receive messages using web browsers.
 * <p>
 * This programming interface lets a JavaScript client application use the MQTT V3.1 or
 * V3.1.1 protocol to connect to an MQTT-supporting messaging server.
 *
 * The function supported includes:
 * <ol>
 * <li>Connecting to and disconnecting from a server. The server is identified by its host name and port number.
 * <li>Specifying options that relate to the communications link with the server,
 * for example the frequency of keep-alive heartbeats, and whether SSL/TLS is required.
 * <li>Subscribing to and receiving messages from MQTT Topics.
 * <li>Publishing messages to MQTT Topics.
 * </ol>
 * <p>
 * The API consists of two main objects:
 * <dl>
 * <dt><b>{@link Paho.MQTT.Client}</b></dt>
 * <dd>This contains methods that provide the functionality of the API,
 * including provision of callbacks that notify the application when a message
 * arrives from or is delivered to the messaging server,
 * or when the status of its connection to the messaging server changes.</dd>
 * <dt><b>{@link Paho.MQTT.Message}</b></dt>
 * <dd>This encapsulates the payload of the message along with various attributes
 * associated with its delivery, in particular the destination to which it has
 * been (or is about to be) sent.</dd>
 * </dl>
 * <p>
 * The programming interface validates parameters passed to it, and will throw
 * an Error containing an error message intended for developer use, if it detects
 * an error with any parameter.
 * <p>
 * Example:
 *
 * <code><pre>
 client = new Paho.MQTT.Client(location.hostname, Number(location.port), "clientId");
 client.onConnectionLost = onConnectionLost;
 client.onMessageArrived = onMessageArrived;
 client.connect({onSuccess:onConnect});
 function onConnect() {
  // Once a connection has been made, make a subscription and send a message.
  console.log("onConnect");
  client.subscribe("/World");
  message = new Paho.MQTT.Message("Hello");
  message.destinationName = "/World";
  client.send(message);
};
 function onConnectionLost(responseObject) {
  if (responseObject.errorCode !== 0)
	console.log("onConnectionLost:"+responseObject.errorMessage);
};
 function onMessageArrived(message) {
  console.log("onMessageArrived:"+message.payloadString);
  client.disconnect();
};
 * </pre></code>
 * @namespace Paho.MQTT
 */
import plussocket from '@/common/js/plus-websocket.js'

(function ExportLibrary(root, factory) {
	if (typeof exports === 'object' && typeof module === 'object') {
		module.exports = factory();
	} else if (typeof define === 'function' && define.amd) {
		define(factory);
	} else if (typeof exports === 'object') {
		exports = factory();
	} else {
		if (typeof root.Paho === 'undefined') {
			root.Paho = {};
		}
		root.Paho.MQTT = factory();
	}
})(global, function LibraryFactory() {
	var PahoMQTT = (function(global) {

		// Private variables below, these are only visible inside the function closure
		// which is used to define the module.

		var version = "@VERSION@";
		var buildLevel = "@BUILDLEVEL@";

		/**
		 * Unique message type identifiers, with associated
		 * associated integer values.
		 * @private
		 */
		var MESSAGE_TYPE = {
			CONNECT: 1,
			CONNACK: 2,
			PUBLISH: 3,
			PUBACK: 4,
			PUBREC: 5,
			PUBREL: 6,
			PUBCOMP: 7,
			SUBSCRIBE: 8,
			SUBACK: 9,
			UNSUBSCRIBE: 10,
			UNSUBACK: 11,
			PINGREQ: 12,
			PINGRESP: 13,
			DISCONNECT: 14
		};

		// Collection of utility methods used to simplify module code
		// and promote the DRY pattern.

		/**
		 * Validate an object's parameter names to ensure they
		 * match a list of expected variables name for this option
		 * type. Used to ensure option object passed into the API don't
		 * contain erroneous parameters.
		 * @param {Object} obj - User options object
		 * @param {Object} keys - valid keys and types that may exist in obj.
		 * @throws {Error} Invalid option parameter found.
		 * @private
		 */
		var validate = function(obj, keys) {
			for (var key in obj) {
				if (obj.hasOwnProperty(key)) {
					if (keys.hasOwnProperty(key)) {
						if (typeof obj[key] !== keys[key])
							throw new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key]));
					} else {
						var errorStr = "Unknown property, " + key + ". Valid properties are:";
						for (var key in keys)
							if (keys.hasOwnProperty(key))
								errorStr = errorStr + " " + key;
						throw new Error(errorStr);
					}
				}
			}
		};

		/**
		 * Return a new function which runs the user function bound
		 * to a fixed scope.
		 * @param {function} User function
		 * @param {object} Function scope
		 * @return {function} User function bound to another scope
		 * @private
		 */
		var scope = function(f, scope) {
			return function() {
				return f.apply(scope, arguments);
			};
		};

		/**
		 * Unique message type identifiers, with associated
		 * associated integer values.
		 * @private
		 */
		var ERROR = {
			OK: {
				code: 0,
				text: "AMQJSC0000I OK."
			},
			CONNECT_TIMEOUT: {
				code: 1,
				text: "AMQJSC0001E Connect timed out."
			},
			SUBSCRIBE_TIMEOUT: {
				code: 2,
				text: "AMQJS0002E Subscribe timed out."
			},
			UNSUBSCRIBE_TIMEOUT: {
				code: 3,
				text: "AMQJS0003E Unsubscribe timed out."
			},
			PING_TIMEOUT: {
				code: 4,
				text: "AMQJS0004E Ping timed out."
			},
			INTERNAL_ERROR: {
				code: 5,
				text: "AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}"
			},
			CONNACK_RETURNCODE: {
				code: 6,
				text: "AMQJS0006E Bad Connack return code:{0} {1}."
			},
			SOCKET_ERROR: {
				code: 7,
				text: "AMQJS0007E Socket error:{0}."
			},
			SOCKET_CLOSE: {
				code: 8,
				text: "AMQJS0008I Socket closed."
			},
			MALFORMED_UTF: {
				code: 9,
				text: "AMQJS0009E Malformed UTF data:{0} {1} {2}."
			},
			UNSUPPORTED: {
				code: 10,
				text: "AMQJS0010E {0} is not supported by this browser."
			},
			INVALID_STATE: {
				code: 11,
				text: "AMQJS0011E Invalid state {0}."
			},
			INVALID_TYPE: {
				code: 12,
				text: "AMQJS0012E Invalid type {0} for {1}."
			},
			INVALID_ARGUMENT: {
				code: 13,
				text: "AMQJS0013E Invalid argument {0} for {1}."
			},
			UNSUPPORTED_OPERATION: {
				code: 14,
				text: "AMQJS0014E Unsupported operation."
			},
			INVALID_STORED_DATA: {
				code: 15,
				text: "AMQJS0015E Invalid data in local storage key={0} value={1}."
			},
			INVALID_MQTT_MESSAGE_TYPE: {
				code: 16,
				text: "AMQJS0016E Invalid MQTT message type {0}."
			},
			MALFORMED_UNICODE: {
				code: 17,
				text: "AMQJS0017E Malformed Unicode string:{0} {1}."
			},
			BUFFER_FULL: {
				code: 18,
				text: "AMQJS0018E Message buffer is full, maximum buffer size: {0}."
			},
		};

		/** CONNACK RC Meaning. */
		var CONNACK_RC = {
			0: "Connection Accepted",
			1: "Connection Refused: unacceptable protocol version",
			2: "Connection Refused: identifier rejected",
			3: "Connection Refused: server unavailable",
			4: "Connection Refused: bad user name or password",
			5: "Connection Refused: not authorized"
		};

		/**
		 * Format an error message text.
		 * @private
		 * @param {error} ERROR.KEY value above.
		 * @param {substitutions} [array] substituted into the text.
		 * @return the text with the substitutions made.
		 */
		var format = function(error, substitutions) {
			var text = error.text;
			if (substitutions) {
				var field, start;
				for (var i = 0; i < substitutions.length; i++) {
					field = "{" + i + "}";
					start = text.indexOf(field);
					if (start > 0) {
						var part1 = text.substring(0, start);
						var part2 = text.substring(start + field.length);
						text = part1 + substitutions[i] + part2;
					}
				}
			}
			return text;
		};

		//MQTT protocol and version          6    M    Q    I    s    d    p    3
		var MqttProtoIdentifierv3 = [0x00, 0x06, 0x4d, 0x51, 0x49, 0x73, 0x64, 0x70, 0x03];
		//MQTT proto/version for 311         4    M    Q    T    T    4
		var MqttProtoIdentifierv4 = [0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, 0x04];

		/**
		 * Construct an MQTT wire protocol message.
		 * @param type MQTT packet type.
		 * @param options optional wire message attributes.
		 *
		 * Optional properties
		 *
		 * messageIdentifier: message ID in the range [0..65535]
		 * payloadMessage:	Application Message - PUBLISH only
		 * connectStrings:	array of 0 or more Strings to be put into the CONNECT payload
		 * topics:			array of strings (SUBSCRIBE, UNSUBSCRIBE)
		 * requestQoS:		array of QoS values [0..2]
		 *
		 * "Flag" properties
		 * cleanSession:	true if present / false if absent (CONNECT)
		 * willMessage:  	true if present / false if absent (CONNECT)
		 * isRetained:		true if present / false if absent (CONNECT)
		 * userName:		true if present / false if absent (CONNECT)
		 * password:		true if present / false if absent (CONNECT)
		 * keepAliveInterval:	integer [0..65535]  (CONNECT)
		 *
		 * @private
		 * @ignore
		 */
		var WireMessage = function(type, options) {
			this.type = type;
			for (var name in options) {
				if (options.hasOwnProperty(name)) {
					this[name] = options[name];
				}
			}
		};

		WireMessage.prototype.encode = function() {
			// Compute the first byte of the fixed header
			var first = ((this.type & 0x0f) << 4);

			/*
			 * Now calculate the length of the variable header + payload by adding up the lengths
			 * of all the component parts
			 */

			var remLength = 0;
			var topicStrLength = new Array();
			var destinationNameLength = 0;

			// if the message contains a messageIdentifier then we need two bytes for that
			if (this.messageIdentifier != undefined)
				remLength += 2;

			switch (this.type) {
				// If this a Connect then we need to include 12 bytes for its header
				case MESSAGE_TYPE.CONNECT:
					switch (this.mqttVersion) {
						case 3:
							remLength += MqttProtoIdentifierv3.length + 3;
							break;
						case 4:
							remLength += MqttProtoIdentifierv4.length + 3;
							break;
					}

					remLength += UTF8Length(this.clientId) + 2;
					if (this.willMessage != undefined) {
						remLength += UTF8Length(this.willMessage.destinationName) + 2;
						// Will message is always a string, sent as UTF-8 characters with a preceding length.
						var willMessagePayloadBytes = this.willMessage.payloadBytes;
						if (!(willMessagePayloadBytes instanceof Uint8Array))
							willMessagePayloadBytes = new Uint8Array(payloadBytes);
						remLength += willMessagePayloadBytes.byteLength + 2;
					}
					if (this.userName != undefined)
						remLength += UTF8Length(this.userName) + 2;
					if (this.password != undefined)
						remLength += UTF8Length(this.password) + 2;
					break;

					// Subscribe, Unsubscribe can both contain topic strings
				case MESSAGE_TYPE.SUBSCRIBE:
					first |= 0x02; // Qos = 1;
					for (var i = 0; i < this.topics.length; i++) {
						topicStrLength[i] = UTF8Length(this.topics[i]);
						remLength += topicStrLength[i] + 2;
					}
					remLength += this.requestedQos.length; // 1 byte for each topic's Qos
					// QoS on Subscribe only
					break;

				case MESSAGE_TYPE.UNSUBSCRIBE:
					first |= 0x02; // Qos = 1;
					for (var i = 0; i < this.topics.length; i++) {
						topicStrLength[i] = UTF8Length(this.topics[i]);
						remLength += topicStrLength[i] + 2;
					}
					break;

				case MESSAGE_TYPE.PUBREL:
					first |= 0x02; // Qos = 1;
					break;

				case MESSAGE_TYPE.PUBLISH:
					if (this.payloadMessage.duplicate) first |= 0x08;
					first = first |= (this.payloadMessage.qos << 1);
					if (this.payloadMessage.retained) first |= 0x01;
					destinationNameLength = UTF8Length(this.payloadMessage.destinationName);
					remLength += destinationNameLength + 2;
					var payloadBytes = this.payloadMessage.payloadBytes;
					remLength += payloadBytes.byteLength;
					if (payloadBytes instanceof ArrayBuffer)
						payloadBytes = new Uint8Array(payloadBytes);
					else if (!(payloadBytes instanceof Uint8Array))
						payloadBytes = new Uint8Array(payloadBytes.buffer);
					break;

				case MESSAGE_TYPE.DISCONNECT:
					break;

				default:
					;
			}

			// Now we can allocate a buffer for the message

			var mbi = encodeMBI(remLength); // Convert the length to MQTT MBI format
			var pos = mbi.length + 1; // Offset of start of variable header
			var buffer = new ArrayBuffer(remLength + pos);
			var byteStream = new Uint8Array(buffer); // view it as a sequence of bytes

			//Write the fixed header into the buffer
			byteStream[0] = first;
			byteStream.set(mbi, 1);

			// If this is a PUBLISH then the variable header starts with a topic
			if (this.type == MESSAGE_TYPE.PUBLISH)
				pos = writeString(this.payloadMessage.destinationName, destinationNameLength, byteStream, pos);
			// If this is a CONNECT then the variable header contains the protocol name/version, flags and keepalive time

			else if (this.type == MESSAGE_TYPE.CONNECT) {
				switch (this.mqttVersion) {
					case 3:
						byteStream.set(MqttProtoIdentifierv3, pos);
						pos += MqttProtoIdentifierv3.length;
						break;
					case 4:
						byteStream.set(MqttProtoIdentifierv4, pos);
						pos += MqttProtoIdentifierv4.length;
						break;
				}
				var connectFlags = 0;
				if (this.cleanSession)
					connectFlags = 0x02;
				if (this.willMessage != undefined) {
					connectFlags |= 0x04;
					connectFlags |= (this.willMessage.qos << 3);
					if (this.willMessage.retained) {
						connectFlags |= 0x20;
					}
				}
				if (this.userName != undefined)
					connectFlags |= 0x80;
				if (this.password != undefined)
					connectFlags |= 0x40;
				byteStream[pos++] = connectFlags;
				pos = writeUint16(this.keepAliveInterval, byteStream, pos);
			}

			// Output the messageIdentifier - if there is one
			if (this.messageIdentifier != undefined)
				pos = writeUint16(this.messageIdentifier, byteStream, pos);

			switch (this.type) {
				case MESSAGE_TYPE.CONNECT:
					pos = writeString(this.clientId, UTF8Length(this.clientId), byteStream, pos);
					if (this.willMessage != undefined) {
						pos = writeString(this.willMessage.destinationName, UTF8Length(this.willMessage.destinationName), byteStream,
							pos);
						pos = writeUint16(willMessagePayloadBytes.byteLength, byteStream, pos);
						byteStream.set(willMessagePayloadBytes, pos);
						pos += willMessagePayloadBytes.byteLength;

					}
					if (this.userName != undefined)
						pos = writeString(this.userName, UTF8Length(this.userName), byteStream, pos);
					if (this.password != undefined)
						pos = writeString(this.password, UTF8Length(this.password), byteStream, pos);
					break;

				case MESSAGE_TYPE.PUBLISH:
					// PUBLISH has a text or binary payload, if text do not add a 2 byte length field, just the UTF characters.
					byteStream.set(payloadBytes, pos);

					break;

					//    	    case MESSAGE_TYPE.PUBREC:
					//    	    case MESSAGE_TYPE.PUBREL:
					//    	    case MESSAGE_TYPE.PUBCOMP:
					//    	    	break;

				case MESSAGE_TYPE.SUBSCRIBE:
					// SUBSCRIBE has a list of topic strings and request QoS
					for (var i = 0; i < this.topics.length; i++) {
						pos = writeString(this.topics[i], topicStrLength[i], byteStream, pos);
						byteStream[pos++] = this.requestedQos[i];
					}
					break;

				case MESSAGE_TYPE.UNSUBSCRIBE:
					// UNSUBSCRIBE has a list of topic strings
					for (var i = 0; i < this.topics.length; i++)
						pos = writeString(this.topics[i], topicStrLength[i], byteStream, pos);
					break;

				default:
					// Do nothing.
			}

			return buffer;
		}

		function decodeMessage(input, pos) {
			var startingPos = pos;
			var first = input[pos];
			var type = first >> 4;
			var messageInfo = first &= 0x0f;
			pos += 1;


			// Decode the remaining length (MBI format)

			var digit;
			var remLength = 0;
			var multiplier = 1;
			do {
				if (pos == input.length) {
					return [null, startingPos];
				}
				digit = input[pos++];
				remLength += ((digit & 0x7F) * multiplier);
				multiplier *= 128;
			} while ((digit & 0x80) != 0);

			var endPos = pos + remLength;
			if (endPos > input.length) {
				return [null, startingPos];
			}

			var wireMessage = new WireMessage(type);
			switch (type) {
				case MESSAGE_TYPE.CONNACK:
					var connectAcknowledgeFlags = input[pos++];
					if (connectAcknowledgeFlags & 0x01)
						wireMessage.sessionPresent = true;
					wireMessage.returnCode = input[pos++];
					break;

				case MESSAGE_TYPE.PUBLISH:
					var qos = (messageInfo >> 1) & 0x03;

					var len = readUint16(input, pos);
					pos += 2;
					var topicName = parseUTF8(input, pos, len);
					pos += len;
					// If QoS 1 or 2 there will be a messageIdentifier
					if (qos > 0) {
						wireMessage.messageIdentifier = readUint16(input, pos);
						pos += 2;
					}

					var message = new Message(input.subarray(pos, endPos));
					if ((messageInfo & 0x01) == 0x01)
						message.retained = true;
					if ((messageInfo & 0x08) == 0x08)
						message.duplicate = true;
					message.qos = qos;
					message.destinationName = topicName;
					wireMessage.payloadMessage = message;
					break;

				case MESSAGE_TYPE.PUBACK:
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无公害的小bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值