完美解决百度地图MarkerClusterer 移动地图时,Marker 的Label 丢失的问题
这篇文章来自http://www.cnblogs.com/jicheng1014 不好意思,那些rss 站太生猛了。
先吐槽一下百度地图的开发者,其实这个问题我绝对不是第一个遇到的人
很多人把这个问题提交到了百度Map贴吧,没有人回复,
我记得我几个月前也通过微薄向@百度地图API 反映过这个问题 当时他回复了下去看看,之后就没下文了。
ok,描述下症状
当将自定义的Marker(含有Label)通过MarkerClusterer 管理的时候,当地图发生任何移动、缩放 的时候,Marker 的Label 就会自动消失
关于解决方案
网上我找到了这个解决方案Nautilus 同学的一个解决方案 http://blog.sina.com.cn/s/blog_484d87770101empl.html
屏蔽了Cluster.prototype.remove 中的
this._markers[i].getMap() && this._map.removeOverlay(this._markers[i]); 这句话
但是其实这句话的调用是 _redraw 当中的_clearLastClusters 里清除Cluster用的,其他操作的时候并没有走这个,所以就导致了他说的“不完美”解决,偶尔还是会出现这个问题。
而且实际上removeOverlay 这个语句本身是有意义的,屏蔽了这个代码会造成clusterer 出现了,但是marker 不消失的问题(偶尔出现,未仔细测试)。
其实诱发这个问题的原因是在于
map.removeOverlay(marker),在这句话执行的时候,会自动将marker.label =null, 由于Map 这个js 是百度自身摇匀了的,不是太好分析,但是我们可以在Clusterer 处理的所有地方都先读出来Label,等remove完毕之后再将这个Maker.setLabel 过去就ok了
tmplabel = this._markers[i].getLabel();
this._markers[i].getMap() && this._map.removeOverlay(this._markers[i]);
this._markers[i].setLabel(tmplabel);
知道了诱因,就ok了,我在所有的removeOverlay Marker 都加入了上面的语句,之后 就可以“完美解决”这个问题了。ps 想用map的RemoveOverlay 事件,但是在他响应这个事件的时候,label 已经空了,所以不行
最后,贴上我修改之后的MarkClusterer.js 代码
/**
* @fileoverview MarkerClusterer标记聚合器用来解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能。
* 主入口类是<a href="symbols/BMapLib.MarkerClusterer.html">MarkerClusterer</a>,
* 基于Baidu Map API 1.2。
*
* @author Baidu Map Api Group
* @version 1.2
*/ /**
* @namespace BMap的所有library类均放在BMapLib命名空间下
*/
var BMapLib = window.BMapLib = BMapLib || {};
(function(){ /**
* 获取一个扩展的视图范围,把上下左右都扩大一样的像素值。
* @param {Map} map BMap.Map的实例化对象
* @param {BMap.Bounds} bounds BMap.Bounds的实例化对象
* @param {Number} gridSize 要扩大的像素值
*
* @return {BMap.Bounds} 返回扩大后的视图范围。
*/
var getExtendedBounds = function(map, bounds, gridSize){
bounds = cutBoundsInRange(bounds);
var pixelNE = map.pointToPixel(bounds.getNorthEast());
var pixelSW = map.pointToPixel(bounds.getSouthWest());
pixelNE.x += gridSize;
pixelNE.y -= gridSize;
pixelSW.x -= gridSize;
pixelSW.y += gridSize;
var newNE = map.pixelToPoint(pixelNE);
var newSW = map.pixelToPoint(pixelSW);
return new BMap.Bounds(newSW, newNE);
}; /**
* 按照百度地图支持的世界范围对bounds进行边界处理
* @param {BMap.Bounds} bounds BMap.Bounds的实例化对象
*
* @return {BMap.Bounds} 返回不越界的视图范围
*/
var cutBoundsInRange = function (bounds) {
var maxX = getRange(bounds.getNorthEast().lng, -180, 180);
var minX = getRange(bounds.getSouthWest().lng, -180, 180);
var maxY = getRange(bounds.getNorthEast().lat, -74, 74);
var minY = getRange(bounds.getSouthWest().lat, -74, 74);
return new BMap.Bounds(new BMap.Point(minX, minY), new BMap.Point(maxX, maxY));
}; /**
* 对单个值进行边界处理。
* @param {Number} i 要处理的数值
* @param {Number} min 下边界值
* @param {Number} max 上边界值
*
* @return {Number} 返回不越界的数值
*/
var getRange = function (i, mix, max) {
mix && (i = Math.max(i, mix));
max && (i = Math.min(i, max));
return i;
}; /**
* 判断给定的对象是否为数组
* @param {Object} source 要测试的对象
*
* @return {Boolean} 如果是数组返回true,否则返回false
*/
var isArray = function (source) {
return '[object Array]' === Object.prototype.toString.call(source);
}; /**
* 返回item在source中的索引位置
* @param {Object} item 要测试的对象
* @param {Array} source 数组
*
* @return {Number} 如果在数组内,返回索引,否则返回-1
*/
var indexOf = function(item, source){
var index = -1;
if(isArray(source)){
if (source.indexOf) {
index = source.indexOf(item);
} else {
for (var i = 0, m; m = source[i]; i++) {
if (m === item) {
index = i;
break;
}
}
}
}
return index;
}; /**
*@exports MarkerClusterer as BMapLib.MarkerClusterer
*/
var MarkerClusterer =
/**
* MarkerClusterer
* @class 用来解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能
* @constructor
* @param {Map} map 地图的一个实例。
* @param {Json Object} options 可选参数,可选项包括:<br />
* markers {Array<Marker>} 要聚合的标记数组<br />
* girdSize {Number} 聚合计算时网格的像素大小,默认60<br />
* maxZoom {Number} 最大的聚合级别,大于该级别就不进行相应的聚合<br />
* minClusterSize {Number} 最小的聚合数量,小于该数量的不能成为一个聚合,默认为2<br />
* isAverangeCenter {Boolean} 聚合点的落脚位置是否是所有聚合在内点的平均值,默认为否,落脚在聚合内的第一个点<br />
* styles {Array<IconStyle>} 自定义聚合后的图标风格,请参考TextIconOverlay类<br />
*/
BMapLib.MarkerClusterer = function(map, options){
if (!map){
return;
}
this._map = map;
this._markers = [];
this._clusters = []; var opts = options || {};
this._gridSize = opts["gridSize"] || 60;
this._maxZoom = opts["maxZoom"] || 18;
this._minClusterSize = opts["minClusterSize"] || 2;
this._isAverageCenter = false;
if (opts['isAverageCenter'] != undefined) {
this._isAverageCenter = opts['isAverageCenter'];
}
this._styles = opts["styles"] || []; var that = this;
this._map.addEventListener("zoomend",function(){
that._redraw();
}); this._map.addEventListener("moveend",function(){
that._redraw();
}); var mkrs = opts["markers"];
isArray(mkrs) && this.addMarkers(mkrs);
}; /**
* 添加要聚合的标记数组。
* @param {Array<Marker>} markers 要聚合的标记数组
*
* @return 无返回值。
*/
MarkerClusterer.prototype.addMarkers = function(markers){
for(var i = 0, len = markers.length; i <len ; i++){
this._pushMarkerTo(markers[i]);
}
this._createClusters();
}; /**
* 把一个标记添加到要聚合的标记数组中
* @param {BMap.Marker} marker 要添加的标记
*
* @return 无返回值。
*/
MarkerClusterer.prototype._pushMarkerTo = function(marker){
var index = indexOf(marker, this._markers);
if(index === -1){
marker.isInCluster = false;
this._markers.push(marker);//Marker拖放后enableDragging不做变化,忽略
}
}; /**
* 添加一个聚合的标记。
* @param {BMap.Marker} marker 要聚合的单个标记。
* @return 无返回值。
*/
MarkerClusterer.prototype.addMarker = function(marker) {
this._pushMarkerTo(marker);
this._createClusters();
}; /**
* 根据所给定的标记,创建聚合点
* @return 无返回值
*/
MarkerClusterer.prototype._createClusters = function(){
var mapBounds = this._map.getBounds();
var extendedBounds = getExtendedBounds(this._map, mapBounds, this._gridSize);
for(var i = 0, marker; marker = this._markers[i]; i++){
if(!marker.isInCluster && extendedBounds.containsPoint(marker.getPosition()) ){
this._addToClosestCluster(marker);
}
}
}; /**
* 根据标记的位置,把它添加到最近的聚合中
* @param {BMap.Marker} marker 要进行聚合的单个标记
*
* @return 无返回值。
*/
MarkerClusterer.prototype._addToClosestCluster = function (marker){
var distance = 4000000;
var clusterToAddTo = null;
var position = marker.getPosition();
for(var i = 0, cluster; cluster = this._clusters[i]; i++){
var center = cluster.getCenter();
if(center){
var d = this._map.getDistance(center, marker.getPosition());
if(d < distance){
distance = d;
clusterToAddTo = cluster;
}
}
} if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)){
clusterToAddTo.addMarker(marker);
} else {
var cluster = new Cluster(this);
cluster.addMarker(marker);
this._clusters.push(cluster);
}
}; /**
* 清除上一次的聚合的结果
* @return 无返回值。
*/
MarkerClusterer.prototype._clearLastClusters = function(){
for(var i = 0, cluster; cluster = this._clusters[i]; i++){
cluster.remove();
}
this._clusters = [];//置空Cluster数组
this._removeMarkersFromCluster();//把Marker的cluster标记设为false
}; /**
* 清除某个聚合中的所有标记
* @return 无返回值
*/
MarkerClusterer.prototype._removeMarkersFromCluster = function(){
for(var i = 0, marker; marker = this._markers[i]; i++){
marker.isInCluster = false;
}
}; /**
* 把所有的标记从地图上清除
* @return 无返回值
*/
MarkerClusterer.prototype._removeMarkersFromMap = function(){
for(var i = 0, marker; marker = this._markers[i]; i++){
marker.isInCluster = false;
tmplabel = marker.getLabel();
this._map.removeOverlay(marker);
marker.setLabel(tmplabel);
}
}; /**
* 删除单个标记
* @param {BMap.Marker} marker 需要被删除的marker
*
* @return {Boolean} 删除成功返回true,否则返回false
*/
MarkerClusterer.prototype._removeMarker = function(marker) {
var index = indexOf(marker, this._markers);
if (index === -1) {
return false;
}
tmplabel = marker.getLabel();
this._map.removeOverlay(marker);
marker.setLabel(tmplabel);
this._markers.splice(index, 1);
return true;
}; /**
* 删除单个标记
* @param {BMap.Marker} marker 需要被删除的marker
*
* @return {Boolean} 删除成功返回true,否则返回false
*/
MarkerClusterer.prototype.removeMarker = function(marker) {
var success = this._removeMarker(marker);
if (success) {
this._clearLastClusters();
this._createClusters();
}
return success;
}; /**
* 删除一组标记
* @param {Array<BMap.Marker>} markers 需要被删除的marker数组
*
* @return {Boolean} 删除成功返回true,否则返回false
*/
MarkerClusterer.prototype.removeMarkers = function(markers) {
var success = false;
for (var i = 0; i < markers.length; i++) {
var r = this._removeMarker(markers[i]);
success = success || r;
} if (success) {
this._clearLastClusters();
this._createClusters();
}
return success;
}; /**
* 从地图上彻底清除所有的标记
* @return 无返回值
*/
MarkerClusterer.prototype.clearMarkers = function() {
this._clearLastClusters();
this._removeMarkersFromMap();
this._markers = [];
}; /**
* 重新生成,比如改变了属性等
* @return 无返回值
*/
MarkerClusterer.prototype._redraw = function () {
this._clearLastClusters();
this._createClusters();
}; /**
* 获取网格大小
* @return {Number} 网格大小
*/
MarkerClusterer.prototype.getGridSize = function() {
return this._gridSize;
}; /**
* 设置网格大小
* @param {Number} size 网格大小
* @return 无返回值
*/
MarkerClusterer.prototype.setGridSize = function(size) {
this._gridSize = size;
this._redraw();
}; /**
* 获取聚合的最大缩放级别。
* @return {Number} 聚合的最大缩放级别。
*/
MarkerClusterer.prototype.getMaxZoom = function() {
return this._maxZoom;
}; /**
* 设置聚合的最大缩放级别
* @param {Number} maxZoom 聚合的最大缩放级别
* @return 无返回值
*/
MarkerClusterer.prototype.setMaxZoom = function(maxZoom) {
this._maxZoom = maxZoom;
this._redraw();
}; /**
* 获取聚合的样式风格集合
* @return {Array<IconStyle>} 聚合的样式风格集合
*/
MarkerClusterer.prototype.getStyles = function() {
return this._styles;
}; /**
* 设置聚合的样式风格集合
* @param {Array<IconStyle>} styles 样式风格数组
* @return 无返回值
*/
MarkerClusterer.prototype.setStyles = function(styles) {
this._styles = styles;
this._redraw();
}; /**
* 获取单个聚合的最小数量。
* @return {Number} 单个聚合的最小数量。
*/
MarkerClusterer.prototype.getMinClusterSize = function() {
return this._minClusterSize;
}; /**
* 设置单个聚合的最小数量。
* @param {Number} size 单个聚合的最小数量。
* @return 无返回值。
*/
MarkerClusterer.prototype.setMinClusterSize = function(size) {
this._minClusterSize = size;
this._redraw();
}; /**
* 获取单个聚合的落脚点是否是聚合内所有标记的平均中心。
* @return {Boolean} true或false。
*/
MarkerClusterer.prototype.isAverageCenter = function() {
return this._isAverageCenter;
}; /**
* 获取聚合的Map实例。
* @return {Map} Map的示例。
*/
MarkerClusterer.prototype.getMap = function() {
return this._map;
}; /**
* 获取所有的标记数组。
* @return {Array<Marker>} 标记数组。
*/
MarkerClusterer.prototype.getMarkers = function() {
return this._markers;
}; /**
* 获取聚合的总数量。
* @return {Number} 聚合的总数量。
*/
MarkerClusterer.prototype.getClustersCount = function() {
var count = 0;
for(var i = 0, cluster; cluster = this._clusters[i]; i++){
cluster.isReal() && count++;
}
return count;
}; /**
* @ignore
* Cluster
* @class 表示一个聚合对象,该聚合,包含有N个标记,这N个标记组成的范围,并有予以显示在Map上的TextIconOverlay等。
* @constructor
* @param {MarkerClusterer} markerClusterer 一个标记聚合器示例。
*/
function Cluster(markerClusterer){
this._markerClusterer = markerClusterer;
this._map = markerClusterer.getMap();
this._minClusterSize = markerClusterer.getMinClusterSize();
this._isAverageCenter = markerClusterer.isAverageCenter();
this._center = null;//落脚位置
this._markers = [];//这个Cluster中所包含的markers
this._gridBounds = null;//以中心点为准,向四边扩大gridSize个像素的范围,也即网格范围
this._isReal = false; //真的是个聚合 this._clusterMarker = new BMapLib.TextIconOverlay(this._center, this._markers.length, {"styles":this._markerClusterer.getStyles()});
//this._map.addOverlay(this._clusterMarker);
} /**
* 向该聚合添加一个标记。
* @param {Marker} marker 要添加的标记。
* @return 无返回值。
*/
Cluster.prototype.addMarker = function(marker){
if(this.isMarkerInCluster(marker)){
return false;
}//也可用marker.isInCluster判断,外面判断OK,这里基本不会命中 if (!this._center){
this._center = marker.getPosition();
this.updateGridBounds();//
} else {
if(this._isAverageCenter){
var l = this._markers.length + 1;
var lat = (this._center.lat * (l - 1) + marker.getPosition().lat) / l;
var lng = (this._center.lng * (l - 1) + marker.getPosition().lng) / l;
this._center = new BMap.Point(lng, lat);
this.updateGridBounds();
}//计算新的Center
} marker.isInCluster = true;
this._markers.push(marker); var len = this._markers.length;
if(len < this._minClusterSize ){
this._map.addOverlay(marker);
//this.updateClusterMarker();
return true;
} else if (len === this._minClusterSize) {
for (var i = 0; i < len; i++) {
tmplabel = this._markers[i].getLabel();
this._markers[i].getMap() && this._map.removeOverlay(this._markers[i]);
this._markers[i].setLabel(tmplabel);
} }
this._map.addOverlay(this._clusterMarker);
this._isReal = true;
this.updateClusterMarker();
return true;
}; /**
* 判断一个标记是否在该聚合中。
* @param {Marker} marker 要判断的标记。
* @return {Boolean} true或false。
*/
Cluster.prototype.isMarkerInCluster= function(marker){
if (this._markers.indexOf) {
return this._markers.indexOf(marker) != -1;
} else {
for (var i = 0, m; m = this._markers[i]; i++) {
if (m === marker) {
return true;
}
}
}
return false;
}; /**
* 判断一个标记是否在该聚合网格范围中。
* @param {Marker} marker 要判断的标记。
* @return {Boolean} true或false。
*/
Cluster.prototype.isMarkerInClusterBounds = function(marker) {
return this._gridBounds.containsPoint(marker.getPosition());
}; Cluster.prototype.isReal = function(marker) {
return this._isReal;
}; /**
* 更新该聚合的网格范围。
* @return 无返回值。
*/
Cluster.prototype.updateGridBounds = function() {
var bounds = new BMap.Bounds(this._center, this._center);
this._gridBounds = getExtendedBounds(this._map, bounds, this._markerClusterer.getGridSize());
}; /**
* 更新该聚合的显示样式,也即TextIconOverlay。
* @return 无返回值。
*/
Cluster.prototype.updateClusterMarker = function () {
if (this._map.getZoom() > this._markerClusterer.getMaxZoom()) {
this._clusterMarker && this._map.removeOverlay(this._clusterMarker);
for (var i = 0, marker; marker = this._markers[i]; i++) {
this._map.addOverlay(marker);
}
return;
} if (this._markers.length < this._minClusterSize) {
this._clusterMarker.hide();
return;
} this._clusterMarker.setPosition(this._center); this._clusterMarker.setText(this._markers.length); var thatMap = this._map;
var thatBounds = this.getBounds();
this._clusterMarker.addEventListener("click", function(event){
thatMap.setViewport(thatBounds);
}); }; /**
* 删除该聚合。
* @return 无返回值。
*/
Cluster.prototype.remove = function(){
for (var i = 0, m; m = this._markers[i]; i++) {
var tmplabel = this._markers[i].getLabel();
this._markers[i].getMap() && this._map.removeOverlay(this._markers[i]);
this._markers[i].setLabel(tmplabel); }//清除散的标记点
this._map.removeOverlay(this._clusterMarker);
this._markers.length = 0;
delete this._markers;
} /**
* 获取该聚合所包含的所有标记的最小外接矩形的范围。
* @return {BMap.Bounds} 计算出的范围。
*/
Cluster.prototype.getBounds = function() {
var bounds = new BMap.Bounds(this._center,this._center);
for (var i = 0, marker; marker = this._markers[i]; i++) {
bounds.extend(marker.getPosition());
}
return bounds;
}; /**
* 获取该聚合的落脚点。
* @return {BMap.Point} 该聚合的落脚点。
*/
Cluster.prototype.getCenter = function() {
return this._center;
}; })();
最后吐槽下所谓的 haogongju 这个站点,从研究到解决这个问题还是花了差不多1天时间,写完日志没几分钟就被抄了,转载我理解,留个原文出处嘛
完美解决百度地图MarkerClusterer 移动地图时,Marker 的Label 丢失的问题的更多相关文章
- [转]百度地图点聚合MarkerClusterer移动地图时,Marker的Label丢失的问题
参考文献:http://www.cnblogs.com/jicheng1014/p/3143859.html 问题现象: 使用MarkerClusterer_min.js,可以实现点聚合,但是当将带有 ...
- 完美解决百度网盘、浏览器下载限速问题proxyee-down(附带win破解版云盘)
win版破解云盘 下载: <PanDownload> 使用文档: <PanDownload使用> Mac方法 限速.限速! 平时下载东西限速倒无所谓,遇到急一点的.盯着80km ...
- 解决百度编辑器在编辑视频时src丢失的问题
问题描述:使用的是最新的UEditor 1.4.3.3版本,在上传完视频后,编辑的时候出现视频的src丢失的问题 解决方式:修改ueditor.config.js文件,将 img: ['src', ' ...
- Swift教程_swift常见问题(0005)_完美解决Cannot override 'dealloc'异常
Swift教程_swift常见问题(0001)_CoreData: warning: Unable to load class named 'xxx' for entity 'xxx' Swift教程 ...
- leaflet中如何优雅的解决百度、高德地图的偏移问题
话不多说,先上效果图 以前在做项目时,经常会听到客户说,你们这个地图是哪来的,太丑了,能不能换成百度地图--高德也行-- 大家生活中,基本上都已经习惯了使用百度地图和高德地图,而在做项目时,用这两个地 ...
- 【百度地图API】如何制作商圈地图?行政地图?
原文:[百度地图API]如何制作商圈地图?行政地图? 摘要: 想要显示某一个区域,并且鼠标放上去,该区域就会变色.这时,你就需要巧用多边形覆盖物,和它的鼠标事件了! 快来看看去哪儿网的实例吧:http ...
- 微信公众号开发《三》微信JS-SDK之地理位置的获取,集成百度地图实现在线地图搜索
本次讲解微信开发第三篇:获取用户地址位置信息,是非常常用的功能,特别是服务行业公众号,尤为需要该功能,本次讲解的就是如何调用微信JS-SDK接口,获取用户位置信息,并结合百度地铁,实现在线地图搜索,与 ...
- ios 一步一步学会自定义地图吹出框(CalloutView)-->(百度地图,高德地图,google地图)
前言 在 ios上边使用地图库的同学肯定遇到过这样的问题:吹出框只能设置title和subtitle和左右的view,不管是百度地图还是高德地图还是自带的 google地图,只提供了这四个属性,如果想 ...
- 微信公众号开发《三》微信JS-SDK之地理位置的获取与在线导航,集成百度地图实现在线地图搜索
本次讲解微信开发第三篇:获取用户地址位置信息,是非常常用的功能,特别是服务行业公众号,尤为需要该功能,本次讲解的就是如何调用微信JS-SDK接口,获取用户位置信息,并结合百度地铁,实现在线地图搜索,与 ...
随机推荐
- [转]unity3d中创建双面材质
在其它三维软件中设置好的双面材质导入到unity3d中就失去了效果,不过我们可以通过自定义材质来在unity3d中实现双面材质的效果.步骤如下:1.在资源库中新建一新shader:代码如下: Shad ...
- TModJS:目录
ylbtech-TModJS:目录 1.返回顶部 1. https://github.com/aui/tmodjs 2. https://www.npmjs.com/package/tmodjs 3. ...
- mybatis---demo1--(1-n)----bai
实体类1: package com.etc.entity; import java.util.List; public class Teacher { private int tid; private ...
- NW.js将网站打包成桌面应用
需求:已有一个网站,因为浏览器兼容性等问题,想自己封装一个浏览器,打开时固定打开这个网站,通过这样的封装,将网站变成桌面应用程序. 1. 下载并安装NW.js的最新稳定版本(NW.js) 2. 创建一 ...
- js数组中的reverse()方法
reverse方法是将数组中的元素的顺序进行反转,在原数组上操作,然后返回原数组.由于本人是学习js的新人,对reverse函数进行了几个小实验,以下实验均在Chrome浏览器上运行 实验一:reve ...
- Matlab数据类型的转换
Matlab中有15种基本数据类型,主要是整型.浮点.逻辑.字符.日期和时间.结构数组.单元格数组以及函数句柄等. 1.整型:(int8:uint8:int16:uint16:int32:uint32 ...
- [hdu1176]免费馅饼(数塔dp)
题意:中文题,不解释了 = = 解题关键:逆推,转化为数塔dp就可以了 dp[i][j]表示在i秒j位置的最大值. 转移方程:$dp[i][j] = \max (dp[i + 1][j],dp[i + ...
- 6.1 安装Ubuntu
声明:sunny从来没有接触过linux.今天是第一次接触linux,日后每天都会接触linux了.坚持,每日练习,相信毕业后的我,一定会从小白成为linux大神. 安装Ubuntu之前,请先参考该博 ...
- 关于.net中的身份认证(AuthorizeAttribute)的问题
引言 新公司当初面试的时候问了我很多用户验证的问题,这里就把我的对于验证的想法写一下,希望可以有一个系统的学习记录. 一.如何验证 B/S结构的请求是http请求,个人理解的http请求有两个特点:1 ...
- EasyUI 在mvc中的引入与使用
使用步骤: 一.先引入: 1.easyui下载,可以去官方网站去下载最新版EasyUI官方下载地址:http://www.jeasyui.com/download/index.php 2.下载后解压, ...