`
jayghost
  • 浏览: 429476 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Google Map V3版 根据KeyDragZoom制作DragZoomControl

 
阅读更多

首先声明感谢http://google-maps-utility-library-v3.googlecode.com/svn/tags/keydragzoom/1.0/docs/examples.html的作者,我参考他的keydragzoom,修改了他的源码完成了我的dragzoomcontrol。

 

先介绍一下DragZoomControl,简单的很就是在第三版的Google Map中,添加一个按钮,点击“DragZoom”按钮后进行框选操作,框选完成后可以跟后台进行Ajax交互,这里的例子是将框选的southwest和northeast传给后台再传到前台。(有点无聊)。

 

然后就是贴上代码:

 

dragzoomcontrol.js:

 

(function () {
/**
* Converts 'thin', 'medium', and 'thick' to pixel widths
* in an MSIE environment. Not called for other browsers
* because getComputedStyle() returns pixel widths automatically.
* @param {String} widthValue
*/
var toPixels = function (widthValue) {
	var px;
	switch (widthValue) {
		case 'thin':
			px = "2px";
			break;
		case 'medium':
			px = "4px";
			break;
		case 'thick':
			px = "6px";
			break;
		default:
			px = widthValue;
	}
	return px;
};
/**
* Get the widths of the borders of an HTML element.
*
* @param {Object} h HTML element
* @return {Object} widths object (top, bottom left, right)
*/
var getBorderWidths = function (h) {
	var computedStyle;
	var bw = {};
	if (document.defaultView && document.defaultView.getComputedStyle) {
		computedStyle = h.ownerDocument.defaultView.getComputedStyle(h, "");
		if (computedStyle) {
			// The computed styles are always in pixel units (good!)
			bw.top = parseInt(computedStyle.borderTopWidth, 10) || 0;
			bw.bottom = parseInt(computedStyle.borderBottomWidth, 10) || 0;
			bw.left = parseInt(computedStyle.borderLeftWidth, 10) || 0;
			bw.right = parseInt(computedStyle.borderRightWidth, 10) || 0;
			return bw;
		}
	} else if (document.documentElement.currentStyle) { // MSIE
		if (h.currentStyle) {
			// The current styles may not be in pixel units so try to convert (bad!)
			bw.top = parseInt(toPixels(h.currentStyle.borderTopWidth), 10) || 0;
			bw.bottom = parseInt(toPixels(h.currentStyle.borderBottomWidth), 10) || 0;
			bw.left = parseInt(toPixels(h.currentStyle.borderLeftWidth), 10) || 0;
			bw.right = parseInt(toPixels(h.currentStyle.borderRightWidth), 10) || 0;
			return bw;
		}
	}
	// Shouldn't get this far for any modern browser
	bw.top = parseInt(h.style["border-top-width"], 10) || 0;
	bw.bottom = parseInt(h.style["border-bottom-width"], 10) || 0;
	bw.left = parseInt(h.style["border-left-width"], 10) || 0;
	bw.right = parseInt(h.style["border-right-width"], 10) || 0;
	return bw;
};
/**
* Get the position of the mouse relative to the document.
* @param {Object} e Mouse event
* @return {Object} left & top position
*/
var getMousePosition = function (e) {
	var posX = 0, posY = 0;
	e = e || window.event;
	if (typeof e.pageX !== "undefined") {
		posX = e.pageX;
		posY = e.pageY;
	} else if (typeof e.clientX !== "undefined") {
		posX = e.clientX +
		(typeof document.documentElement.scrollLeft !== "undefined" ? document.documentElement.scrollLeft : document.body.scrollLeft);
		posY = e.clientY +
		(typeof document.documentElement.scrollTop !== "undefined" ? document.documentElement.scrollTop : document.body.scrollTop);
	}
	return {
		left: posX,
		top: posY
	};
};
/**
* Get the position of an HTML element relative to the document.
* @param {Object} h HTML element
* @return {Object} left & top position
*/
var getElementPosition = function (h) {
	var posX = h.offsetLeft;
	var posY = h.offsetTop;
	var parent = h.offsetParent;
	// Add offsets for all ancestors in the hierarchy
	while (parent !== null) {
		// Adjust for scrolling elements which may affect the map position.
		if (parent !== document.body && parent !== document.documentElement) {
		posX -= parent.scrollLeft;
		posY -= parent.scrollTop;
		}
		posX += parent.offsetLeft;
		posY += parent.offsetTop;
		parent = parent.offsetParent;
	}
	return {
		left: posX,
		top: posY
	};
};
/**
* Set the properties of an object to those from another object.
* @param {Object} obj target object
* @param {Object} vals source object
*/
var setVals = function (obj, vals) {
	if (obj && vals) {
		for (var x in vals) {
			if (vals.hasOwnProperty(x)) {
				obj[x] = vals[x];
			}
		}
	}
	return obj;
};
/**
* Set the opacity. If op is not passed in, this function just performs an MSIE fix.
* @param {Node} div
* @param {Number} op (0-1)
*/
var setOpacity = function (div, op) {
	if (typeof op !== 'undefined') {
		div.style.opacity = op;
	}
	if (typeof div.style.opacity !== 'undefined') {
		div.style.filter = "alpha(opacity=" + (div.style.opacity * 100) + ")";
	}
};

function DragZoom(map, opt_zoomOpts) {
	var ov = new google.maps.OverlayView();
	var me = this;
	ov.onAdd = function () {
		me.init_(map, opt_zoomOpts);
	};
	ov.draw = function () {
	};
	ov.onRemove = function () {
	};
	ov.setMap(map);
	this.prjov_ = ov;
}
/**
* Init the tool.
*/
DragZoom.prototype.init_ = function (map, opt_zoomOpts) {
  // Set CSS styles for the DIV containing the control
  this.controlDiv_=opt_zoomOpts.controlDiv;
  this.controlDiv_.style.paddingTop = '5px';

  // Set CSS for the control border
  var controlUI = document.createElement('DIV');
  controlUI.style.backgroundColor = '#FFFFFF';
  controlUI.style.borderColor = '#717B87';
  controlUI.style.borderStyle = 'solid';
  controlUI.style.borderWidth = '1px';
  controlUI.style.boxShadow = '1px 2px 4px #888888';
  controlUI.style.cursor = 'pointer';
  controlUI.style.textAlign = 'center';
  controlUI.title = 'Click to Active the map DragZoom';
  this.controlDiv_.appendChild(controlUI);

  // Set CSS for the control interior
  var controlText = document.createElement('DIV');
  controlText.style.fontFamily = 'Arial,sans-serif';
  controlText.style.fontSize = '12px';
  controlText.style.paddingBottom = '1px';
  controlText.style.paddingLeft = '4px';
  controlText.style.paddingRight = '4px';
  controlText.style.paddingTop = '1px';
  controlText.innerHTML = 'DragZoom';
  controlUI.appendChild(controlText);

    this.map_ = map;
	opt_zoomOpts = opt_zoomOpts || {};
	this.borderWidths_ = getBorderWidths(this.map_.getDiv());//Container());
	this.paneDiv_ = document.createElement("div");
	this.paneDiv_.onselectstart = function () {
		return false;
	};
	// default style
	setVals(this.paneDiv_.style, {
		backgroundColor: 'white',
		opacity: 0.0,
		cursor: 'crosshair'
	});
	// allow overwrite
	setVals(this.paneDiv_.style, opt_zoomOpts.paneStyle);
	// stuff that cannot be overwritten
	setVals(this.paneDiv_.style, {
		position: 'absolute',
		overflow: 'hidden',
		zIndex: 10001,
		display: 'none'
	});
	setOpacity(this.paneDiv_);
	// An IE fix: if the background is transparent, it cannot capture mousedown events
	if (this.paneDiv_.style.backgroundColor === 'transparent') {
		this.paneDiv_.style.backgroundColor = 'white';
		setOpacity(this.paneDiv_, 0);
	}
	this.map_.getDiv().appendChild(this.paneDiv_);//Container()
	this.boxDiv_ = document.createElement('div');
	setVals(this.boxDiv_.style, {
		border: 'thin solid #FF0000'
	});
	setVals(this.boxDiv_.style, opt_zoomOpts.boxStyle);
	setVals(this.boxDiv_.style, {
		position: 'absolute',
		display: 'none'
	});
	setOpacity(this.boxDiv_);
	this.map_.getDiv().appendChild(this.boxDiv_);
	this.boxBorderWidths_ = getBorderWidths(this.boxDiv_);
	var me = this;
	this.controlUIDownListener_ = google.maps.event.addDomListener(controlUI, 'click', function() {
		me.onControlUIClick_();
	});
	this.mouseDownListener_ = google.maps.event.addDomListener(this.paneDiv_, 'mousedown', function (e) {
		me.onMouseDown_(e);
	});
	this.mouseDownListenerDocument_ = google.maps.event.addDomListener(document, 'mousedown', function (e) {
		me.onMouseDownDocument_(e);
	});
	this.mouseMoveListener_ = google.maps.event.addDomListener(document, 'mousemove', function (e) {
		me.onMouseMove_(e);
	});
	this.mouseUpListener_ = google.maps.event.addDomListener(document, 'mouseup', function (e) {
		me.onMouseUp_(e);
	});
	this.controlUIDown_ = false;
	this.dragging_ = false;
	this.startPt_ = null;
	this.endPt_ = null;
	this.boxMaxX_ = null;
	this.boxMaxY_ = null;
	this.mousePosn_ = null;
	this.mapPosn_ = getElementPosition(this.map_.getDiv());
	this.mouseDown_ = false;
};
/**
* Checks if the mouse is on top of the map. The position is captured
* in onMouseMove_.
* @return true if mouse is on top of the map div.
*/
DragZoom.prototype.isMouseOnMap_ = function () {
	var mousePos = this.mousePosn_;
	if (mousePos) {
		var mapPos = this.mapPosn_;
		var mapDiv = this.map_.getDiv();
		return mousePos.left > mapPos.left && mousePos.left < mapPos.left + mapDiv.offsetWidth &&
		mousePos.top > mapPos.top && mousePos.top < mapPos.top + mapDiv.offsetHeight;
	} else {
		// if user never moved mouse
		return false;
	}
};
/**
* Show or hide the overlay pane, depending on whether the mouse is over the map.
*/
DragZoom.prototype.setPaneVisibility_ = function () {
	if (this.map_ && this.controlUIDown_ && this.isMouseOnMap_()) {
		//alert("setPaneVisibility_");
		var mapDiv = this.map_.getDiv();
		this.paneDiv_.style.left = 0 + 'px';
		this.paneDiv_.style.top = 0 + 'px';
		this.paneDiv_.style.width = mapDiv.offsetWidth - (this.borderWidths_.left + this.borderWidths_.right) + 'px';
		this.paneDiv_.style.height = mapDiv.offsetHeight - (this.borderWidths_.top + this.borderWidths_.bottom) + 'px';
		this.paneDiv_.style.display = 'block';
		this.boxMaxX_ = parseInt(this.paneDiv_.style.width, 10) - (this.boxBorderWidths_.left + this.boxBorderWidths_.right);
		this.boxMaxY_ = parseInt(this.paneDiv_.style.height, 10) - (this.boxBorderWidths_.top + this.boxBorderWidths_.bottom);
	} else {
		this.paneDiv_.style.display = 'none';
	}
};
DragZoom.prototype.onControlUIClick_ = function () {
	if (this.map_ && !this.controlUIDown_) {
		//alert("onControlUIDown_");
		this.controlUIDown_ = true;
		this.controlDiv_.childNodes.item(0).style.backgroundColor = '#E8E8E8';
		this.controlDiv_.childNodes.item(0).childNodes.item(0).style.fontWeight = 'bold';
		this.setPaneVisibility_();
		/**
		* This event is fired when the ControlUI button is pressed.
		* @name DragZoom#activate
		* @event
		*/
		google.maps.event.trigger(me, 'activate');
	}else {
		alert("onControlUIUp_");
		this.controlUIDown_ = false;
		this.dragging_ = false;
		this.controlDiv_.childNodes.item(0).style.backgroundColor = '#FFFFFF';
		this.controlDiv_.childNodes.item(0).childNodes.item(0).style.fontWeight = 'normal';
		this.boxDiv_.style.display = 'none';
		this.paneDiv_.style.display = "none";
		/**
		* This event is fired while the user release the controlUI
		* @name DragZoom#deactivate
		* @event
		*/
		google.maps.event.trigger(this, 'deactivate');
	}
};
/**
* Get the <code>google.maps.Point</code> of the mouse position.
* @param {Object} e
* @return {google.maps.Point} point
* @private
*/
DragZoom.prototype.getMousePoint_ = function (e) {
	var mousePosn = getMousePosition(e);
	var p = new google.maps.Point();
	p.x = mousePosn.left - this.mapPosn_.left - this.borderWidths_.left;
	p.y = mousePosn.top - this.mapPosn_.top - this.borderWidths_.top;
	p.x = Math.min(p.x, this.boxMaxX_);
	p.y = Math.min(p.y, this.boxMaxY_);
	p.x = Math.max(p.x, 0);
	p.y = Math.max(p.y, 0);
	return p;
};
/**
* Handle mouse down.
* @param {Event} e
*/
DragZoom.prototype.onMouseDown_ = function (e) {
	if (this.map_ && this.controlUIDown_) {
		this.mapPosn_ = getElementPosition(this.map_.getDiv());
		this.dragging_ = true;
		this.startPt_ = this.endPt_ = this.getMousePoint_(e);
		var prj = this.prjov_.getProjection();
		var latlng = prj.fromDivPixelToLatLng(this.startPt_);
		/**
		* This event is fired when the drag operation begins.
		* @name DragZoom#dragstart
		* @param {GLatLng} startLatLng
		* @event
		*/
		google.maps.event.trigger(this, 'dragstart', latlng);
	}
};
/**
* Handle mouse down at the document level.
* @param {Event} e
*/
DragZoom.prototype.onMouseDownDocument_ = function (e) {
	this.mouseDown_ = true;
};
/**
* Handle mouse move.
* @param {Event} e
*/
DragZoom.prototype.onMouseMove_ = function (e) {
	this.mousePosn_ = getMousePosition(e);
	if (this.dragging_) {
		this.endPt_ = this.getMousePoint_(e);
		var left = Math.min(this.startPt_.x, this.endPt_.x);
		var top = Math.min(this.startPt_.y, this.endPt_.y);
		var width = Math.abs(this.startPt_.x - this.endPt_.x);
		var height = Math.abs(this.startPt_.y - this.endPt_.y);
		this.boxDiv_.style.left = left + 'px';
		this.boxDiv_.style.top = top + 'px';
		this.boxDiv_.style.width = width + 'px';
		this.boxDiv_.style.height = height + 'px';
		this.boxDiv_.style.display = 'block';
		/**
		* This event is repeatedly fired while the user drags the box. The southwest and northeast
		* point are passed as parameters of type <code>google.maps.Point</code> (for performance reasons),
		* relative to the map container. Note: the event listener is responsible
		* for converting Pixel to LatLng, if necessary.
		* @name DragZoom#drag
		* @param {google.maps.Point} southwestPixel
		* @param {google.maps.Point} northeastPixel
		* @event
		*/
		google.maps.event.trigger(this, 'drag', new google.maps.Point(left, top + height), new google.maps.Point(left + width, top));
	} else if (!this.mouseDown_) {
		this.setPaneVisibility_();
	}
};
/**
* Handle mouse up.
* @param {Event} e
*/
DragZoom.prototype.onMouseUp_ = function (e) {
	this.mouseDown_ = false;
	if (this.dragging_) {
		var left = Math.min(this.startPt_.x, this.endPt_.x);
		var top = Math.min(this.startPt_.y, this.endPt_.y);
		var width = Math.abs(this.startPt_.x - this.endPt_.x);
		var height = Math.abs(this.startPt_.y - this.endPt_.y);
		var prj = this.prjov_.getProjection();
		// 2009-05-29: since V3 does not have fromContainerPixel,
		var containerPos = getElementPosition(this.map_.getDiv());
		var mapPanePos = getElementPosition(this.prjov_.getPanes().mapPane);
		left = left + (containerPos.left - mapPanePos.left);
		top = top + (containerPos.top - mapPanePos.top);
		var sw = prj.fromDivPixelToLatLng(new google.maps.Point(left, top + height));
		var ne = prj.fromDivPixelToLatLng(new google.maps.Point(left + width, top));
		var bnds = new google.maps.LatLngBounds(sw, ne);
		this.map_.fitBounds(bnds);
		this.controlUIDown_ = false;
		this.dragging_ = false;
		this.controlDiv_.childNodes.item(0).style.backgroundColor = '#FFFFFF';
		this.controlDiv_.childNodes.item(0).childNodes.item(0).style.fontWeight = 'normal';
		this.boxDiv_.style.display = 'none';
		/**
		* This event is fired when the drag operation ends.
		* Note that the event is not fired if the hot key is released before the drag operation ends.
		* @name DragZoom#dragend
		* @param {GLatLngBounds} newBounds
		* @event
		*/
		google.maps.event.trigger(this, 'dragend', bnds);
	}
};
/**
* @name google.maps.Map
* @class These are new methods added to the Google Maps API's
* <a href = 'http://code.google.com/apis/maps/documentation/v3/reference.html#Map'>Map</a>
* class.
*/
/**
* Enable drag zoom. The user can zoom to an area of interest by holding down the hot key
* <code>(shift | ctrl | alt )</code> while dragging a box around the area.
* @param {KeyDragZoomOptions} opt_zoomOpts
*/
google.maps.Map.prototype.enableKeyDragZoom = function (opt_zoomOpts) {
	this.dragZoom_ = new DragZoom(this, opt_zoomOpts);
};
/**
* Disable drag zoom.
*/
google.maps.Map.prototype.disableKeyDragZoom = function () {
	var d = this.dragZoom_;
	if (d) {
		alert("disableKeyDragZoom");
		google.maps.event.removeListener(d.mouseDownListener_);
		google.maps.event.removeListener(d.mouseDownListenerDocument_);
		google.maps.event.removeListener(d.mouseMoveListener_);
		google.maps.event.removeListener(d.mouseUpListener_);
		google.maps.event.removeListener(d.controlUIDownListener_);
		google.maps.event.removeListener(d.controlUIUpListener_);
		this.getDiv().removeChild(d.boxDiv_);
		this.getDiv().removeChild(d.paneDiv_);
		this.dragZoom_ = null;
	}
};
/**
* Returns true if the drag zoom feature has been enabled.
* @return {Boolean}
*/
google.maps.Map.prototype.keyDragZoomEnabled = function () {
	return this.dragZoom_ !== null;
};
/**
* Returns the DragZoom object which is created when <code>google.maps.Map.enableKeyDragZoom</code> is called.
* With this object you can use <code>google.maps.event.addListener</code> to attach event listeners
* for the 'activate', 'deactivate', 'dragstart', 'drag', and 'dragend' events.
* @return {DragZoom}
*/
google.maps.Map.prototype.getDragZoomObject = function () {
	return this.dragZoom_;
};
})();

 index.jsp:

 

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
<style>
#map {
    width: 700px;
    height: 400px;
}
</style>
<script type="text/javascript" src="js/jquery-1.7.min.js"></script>
  <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
  <script type="text/javascript" src="js/dragzoomcontrol.js"></script>
  <script type="text/javascript">
var map;
var markersArray = [];
var center = new google.maps.LatLng(30.67, 104.06);
function init() {
  var myOptions = {
    zoom: 13,
    center: center,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  map = new google.maps.Map(document.getElementById("map"), myOptions);
  
  // Create the DIV to hold the control and
  // call the DragZoomControl() constructor passing in this DIV.
  var dragZoomControlDiv = document.createElement('DIV');
  dragZoomControlDiv.index = 1;
  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(dragZoomControlDiv);
  
  map.enableKeyDragZoom({
    controlDiv: dragZoomControlDiv,
    boxStyle: {
      opacity: 0.7
    },
    paneStyle: {
      backgroundColor: "gray",
      opacity: 0.1
    }
  });
  var dz = map.getDragZoomObject();
  google.maps.event.addListener(dz, 'activate', function() {
    log('DragZoom Activated');
   });
  google.maps.event.addListener(dz, 'dragstart', function(latlng) {
    log('DragZoom Started: ' + latlng);
  });
  google.maps.event.addListener(dz, 'drag', function(start, end) {
 //log('DragZoom Dragging...' + start + end);
  });
  google.maps.event.addListener(dz, 'dragend', function(bnds) {
    log('DragZoom DragEnd :' + bnds);
    $.ajax({
	  	 url: 'AjaxMap',
	  	 type: 'post',
	  	 dataType: 'json',
	  	 data: {
	  	 	swlat: bnds.getSouthWest().lat(),
	  	 	swlng: bnds.getSouthWest().lng(),
	  	 	nelat: bnds.getNorthEast().lat(),
	  	 	nelng: bnds.getNorthEast().lng()
	 	 },
	  	 success: function(jsonMsg){
			//var json = [eval('(' +jsonMsg +')')];
			var json=jsonMsg;
			var sw = new google.maps.LatLng(json.swlat, json.swlng);
			var ne = new google.maps.LatLng(json.nelat, json.nelng);
			deleteOverlays();
		 	addMarker(sw);
	 	 	addMarker(ne);
	 	}
  	});
  });
  google.maps.event.addListener(dz, 'deactivate', function() {
    log('DragZoom Deactivated');
  });
}
function addMarker(location) {
  marker = new google.maps.Marker({
    position: location,
    map: map,
	title: "Hello World"
  });
  markersArray.push(marker);
}
function deleteOverlays() {
  if (markersArray) {
    for (i in markersArray) {
      markersArray[i].setMap(null);
    }
    markersArray.length = 0;
  }
}
function log(msg){
	$("#msg").html($("#msg").html()+"<br/>"+msg);
  //document.getElementById('msg').innerHTML +="<br/>"+msg;
}
  </script>
  </head>
  <body onload="init()">
    <div id="map"></div>
    <br/>
    <div id="msg"></div>
  </body>
</html>

 后台的servlet:

 

package com.wujay;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.simple.JSONObject;

public class AjaxMap extends HttpServlet {

	public AjaxMap() {
		super();
	}

	public void destroy() {
		super.destroy(); // Just puts "destroy" string in log
		// Put your code here
	}

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=gbk");  
        PrintWriter out=response.getWriter();  
        String swlat=request.getParameter("swlat");
        String swlng=request.getParameter("swlng");
        String nelat=request.getParameter("nelat");
        String nelng=request.getParameter("nelng");
        JSONObject obj=new JSONObject();
        obj.put("swlat",swlat);
        obj.put("swlng",swlng);
        obj.put("nelat",nelat);
        obj.put("nelng",nelng);
        out.print(obj);
        out.flush();
        out.close();
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

	public void init() throws ServletException {
		// Put your code here
	}
}

 web.xml:

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <servlet>
    <servlet-name>AjaxMap</servlet-name>
    <servlet-class>com.wujay.AjaxMap</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>AjaxMap</servlet-name>
    <url-pattern>/AjaxMap</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

 我把MyEclipse工程作为附件有需要的可以看看。

 

The End

 

 

 

0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics