提交 c67e5a3c 编写于 作者: P pah100

add hoverlink

上级 79ce1d7d
......@@ -113,6 +113,7 @@ define(function (require) {
symbolPath.draggable = draggable;
symbolPath.cursor = draggable ? 'move' : 'pointer';
};
/**
* Update symbol properties
* @param {module:echarts/data/List} data
......
/**
* @file Data zoom model
*/
......@@ -19,13 +18,14 @@ define(function(require) {
* @protected
*/
defaultOption: {
align: 'auto', // 'auto', 'left', 'right', 'top', 'bottom'
calculable: false, // 是否值域漫游,启用后无视splitNumber和pieces,线性渐变
range: [-Infinity, Infinity], // 当前选中范围
hoverLink: true,
realtime: true,
itemWidth: null, // 值域图形宽度
itemHeight: null // 值域图形高度
align: 'auto', // 'auto', 'left', 'right', 'top', 'bottom'
calculable: false, // This prop effect default component type determine,
// See echarts/component/visualMap/typeDefaulter.
range: [-Infinity, Infinity], // selected range
realtime: true, // Whether realtime update.
itemHeight: null, // The length of the range control edge.
itemWidth: null, // The length of the other side.
hoverLink: true // Enable hover highlight.
},
/**
......@@ -129,6 +129,28 @@ define(function(require) {
(range[0] <= dataExtent[0] || range[0] <= value)
&& (range[1] >= dataExtent[1] || value <= range[1])
) ? 'inRange' : 'outOfRange';
},
/**
* @public
* @params {Array.<number>} range target value: range[0] <= value && value <= range[1]
* @return {Array.<Object>} [{seriesId, dataIndices: <Array.<number>>}, ...]
*/
findTargetDataIndices: function (range) {
var result = [];
this.eachTargetSeries(function (seriesModel) {
var dataIndices = [];
var data = seriesModel.getData();
data.each(this.getDataDimension(data), function (value, dataIndex) {
range[0] <= value && value <= range[1] && dataIndices.push(dataIndex);
}, true, this);
result.push({seriesId: seriesModel.id, dataIndices: dataIndices});
}, this);
return result;
}
});
......
......@@ -5,11 +5,16 @@ define(function(require) {
var zrUtil = require('zrender/core/util');
var numberUtil = require('../../util/number');
var sliderMove = require('../helper/sliderMove');
var linearMap = numberUtil.linearMap;
var LinearGradient = require('zrender/graphic/LinearGradient');
var helper = require('./helper');
var linearMap = numberUtil.linearMap;
var convertDataIndicesToBatch = helper.convertDataIndicesToBatch;
var each = zrUtil.each;
// Arbitrary value
var HOVER_LINK_RANGE = 6;
// Notice:
// Any "interval" should be by the order of [low, high].
// "handle0" (handleIndex === 0) maps to
......@@ -53,6 +58,11 @@ define(function(require) {
* @private
*/
this._useHandle;
/**
* @private
*/
this._hoverLinkDataIndices = [];
},
/**
......@@ -98,6 +108,9 @@ define(function(require) {
// Real update view
this._updateView();
this._enableHoverLinkToSeries();
this._enableHoverLinkFromSeries();
this.positionGroup(thisGroup);
},
......@@ -170,7 +183,6 @@ define(function(require) {
// Handle
if (useHandle) {
shapes.handleGroups = [];
shapes.handleThumbs = [];
shapes.handleLabels = [];
shapes.handleLabelPoints = [];
......@@ -179,8 +191,7 @@ define(function(require) {
this._createHandle(barGroup, 1, itemSize, textSize, orient, itemAlign);
}
// Indicator
// FIXME
this._createIndicator(barGroup, itemSize, textSize, orient);
targetGroup.add(barGroup);
},
......@@ -189,45 +200,76 @@ define(function(require) {
* @private
*/
_createHandle: function (barGroup, handleIndex, itemSize, textSize, orient) {
var handleGroup = new graphic.Group({position: [itemSize[0], 0]});
var handleThumb = createPolygon(
createHandlePoints(handleIndex, textSize),
zrUtil.bind(this._modifyHandle, this, handleIndex),
'move'
);
handleGroup.add(handleThumb);
// For text locating. Text is always horizontal layout
// but should not be effected by transform.
var handleLabelPoint = {
x: orient === 'horizontal'
? textSize / 2
: textSize * 1.5,
y: orient === 'horizontal'
? (handleIndex === 0 ? -(textSize * 1.5) : (textSize * 1.5))
: (handleIndex === 0 ? -textSize / 2 : textSize / 2)
};
handleThumb.position[0] = itemSize[0];
barGroup.add(handleThumb);
// Text is always horizontal layout but should not be effected by
// transform (orient/inverse). So label is built separately but not
// use zrender/graphic/helper/RectText, and is located based on view
// group (according to handleLabelPoint) but not barGroup.
var textStyleModel = this.visualMapModel.textStyleModel;
var handleLabel = new graphic.Text({
silent: true,
style: {
x: 0, y: 0, text: '',
textVerticalAlign: 'middle',
textFont: textStyleModel.getFont(),
fill: textStyleModel.getTextColor()
}
});
this.group.add(handleLabel);
this.group.add(handleLabel); // Text do not transform
var handleLabelPoint = [
orient === 'horizontal'
? textSize / 2
: textSize * 1.5,
orient === 'horizontal'
? (handleIndex === 0 ? -(textSize * 1.5) : (textSize * 1.5))
: (handleIndex === 0 ? -textSize / 2 : textSize / 2)
];
var shapes = this._shapes;
shapes.handleThumbs[handleIndex] = handleThumb;
shapes.handleGroups[handleIndex] = handleGroup;
shapes.handleLabelPoints[handleIndex] = handleLabelPoint;
shapes.handleLabels[handleIndex] = handleLabel;
},
/**
* @private
*/
_createIndicator: function (barGroup, itemSize, textSize, orient) {
var indicator = createPolygon(
createIndicatorPoints(), null, 'move'
);
indicator.position[0] = itemSize[0];
indicator.attr({invisible: true, silent: true});
barGroup.add(indicator);
var textStyleModel = this.visualMapModel.textStyleModel;
var indicatorLabel = new graphic.Text({
silent: true,
invisible: true,
style: {
x: 0, y: 0, text: '',
textFont: textStyleModel.getFont(),
fill: textStyleModel.getTextColor()
}
});
this.group.add(indicatorLabel);
var indicatorLabelPoint = [
orient === 'horizontal' ? textSize / 2 : textSize * 1.5,
0
];
barGroup.add(handleGroup);
var shapes = this._shapes;
shapes.indicator = indicator;
shapes.indicatorLabel = indicatorLabel;
shapes.indicatorLabelPoint = indicatorLabelPoint;
},
/**
......@@ -261,8 +303,8 @@ define(function(require) {
var sizeExtent = [0, visualMapModel.itemSize[1]];
this._handleEnds = [
linearMap(dataInterval[0], dataExtent, sizeExtent,true),
linearMap(dataInterval[1], dataExtent, sizeExtent,true)
linearMap(dataInterval[0], dataExtent, sizeExtent, true),
linearMap(dataInterval[1], dataExtent, sizeExtent, true)
];
},
......@@ -300,13 +342,12 @@ define(function(require) {
var visualMapModel = this.visualMapModel;
var dataExtent = visualMapModel.getExtent();
var shapes = this._shapes;
var dataInterval = this._dataInterval;
var outOfRangeHandleEnds = [0, visualMapModel.itemSize[1]];
var inRangeHandleEnds = forSketch ? outOfRangeHandleEnds : this._handleEnds;
var visualInRange = this._createBarVisual(
dataInterval, dataExtent, inRangeHandleEnds, 'inRange'
this._dataInterval, dataExtent, inRangeHandleEnds, 'inRange'
);
var visualOutOfRange = this._createBarVisual(
dataExtent, dataExtent, outOfRangeHandleEnds, 'outOfRange'
......@@ -325,25 +366,7 @@ define(function(require) {
})
.setShape('points', visualOutOfRange.barPoints);
this._useHandle && each([0, 1], function (handleIndex) {
shapes.handleThumbs[handleIndex].setStyle(
'fill', visualInRange.handlesColor[handleIndex]
);
shapes.handleLabels[handleIndex].setStyle({
text: visualMapModel.formatValueText(dataInterval[handleIndex]),
textAlign: this._applyTransform(
this._orient === 'horizontal'
? (handleIndex === 0 ? 'bottom' : 'top')
: 'left',
shapes.barGroup
)
});
}, this);
this._updateHandlePosition(inRangeHandleEnds);
this._updateHandle(inRangeHandleEnds, visualInRange);
},
/**
......@@ -442,26 +465,37 @@ define(function(require) {
/**
* @private
*/
_updateHandlePosition: function (handleEnds) {
_updateHandle: function (handleEnds, visualInRange) {
if (!this._useHandle) {
return;
}
var shapes = this._shapes;
var visualMapModel = this.visualMapModel;
var handleThumbs = shapes.handleThumbs;
var handleLabels = shapes.handleLabels;
each([0, 1], function (handleIndex) {
var handleGroup = shapes.handleGroups[handleIndex];
handleGroup.position[1] = handleEnds[handleIndex];
var handleThumb = handleThumbs[handleIndex];
handleThumb.setStyle('fill', visualInRange.handlesColor[handleIndex]);
handleThumb.position[1] = handleEnds[handleIndex];
// Update handle label position.
var labelPoint = shapes.handleLabelPoints[handleIndex];
var textPoint = graphic.applyTransform(
[labelPoint.x, labelPoint.y],
graphic.getTransform(handleGroup, this.group)
shapes.handleLabelPoints[handleIndex],
graphic.getTransform(handleThumb, this.group)
);
shapes.handleLabels[handleIndex].setStyle({
x: textPoint[0], y: textPoint[1]
handleLabels[handleIndex].setStyle({
x: textPoint[0],
y: textPoint[1],
text: visualMapModel.formatValueText(this._dataInterval[handleIndex]),
textVerticalAlign: 'middle',
textAlign: this._applyTransform(
this._orient === 'horizontal'
? (handleIndex === 0 ? 'bottom' : 'top')
: 'left',
shapes.barGroup
)
});
}, this);
},
......@@ -469,13 +503,177 @@ define(function(require) {
/**
* @private
*/
_applyTransform: function (vertex, element, inverse) {
var transform = graphic.getTransform(element, this.group);
_updateIndicator: function (value, isRange) {
var visualMapModel = this.visualMapModel;
var dataExtent = visualMapModel.getExtent();
var sizeExtent = [0, visualMapModel.itemSize[1]];
var pos = linearMap(value, dataExtent, sizeExtent, true);
var shapes = this._shapes;
var indicator = shapes.indicator;
if (!indicator) {
return;
}
indicator.position[1] = pos;
indicator.attr('invisible', false);
var opts = {convertOpacityToAlpha: true};
var color = this.getControllerVisual(value, 'color', opts);
indicator.setStyle('fill', color);
// Update handle label position.
var textPoint = graphic.applyTransform(
shapes.indicatorLabelPoint,
graphic.getTransform(indicator, this.group)
);
var indicatorLabel = shapes.indicatorLabel;
indicatorLabel.attr('invisible', false);
var align = this._applyTransform('left', shapes.barGroup);
var orient = this._orient;
indicatorLabel.setStyle({
text: (isRange ? '' : '') + visualMapModel.formatValueText(value),
textVerticalAlign: orient === 'horizontal' ? align : 'middle',
textAlign: orient === 'horizontal' ? 'center' : align,
x: textPoint[0],
y: textPoint[1]
});
},
/**
* @private
*/
_enableHoverLinkToSeries: function () {
this._shapes.barGroup
.on('mousemove', zrUtil.bind(onMouseOver, this))
.on('mouseout', zrUtil.bind(this._clearHoverLinkToSeries, this));
function onMouseOver(e) {
var visualMapModel = this.visualMapModel;
if (!visualMapModel.option.hoverLink) {
return;
}
var pos = this._applyTransform(
[e.offsetX, e.offsetY], this._shapes.barGroup, true, true
)[1];
var hoverRange = [pos - HOVER_LINK_RANGE / 2, pos + HOVER_LINK_RANGE / 2];
var sizeExtent = [0, visualMapModel.itemSize[1]];
var dataExtent = visualMapModel.getExtent();
var valueRange = [
linearMap(hoverRange[0], sizeExtent, dataExtent, true),
linearMap(hoverRange[1], sizeExtent, dataExtent, true)
];
this._updateIndicator((valueRange[0] + valueRange[1]) / 2, true);
var oldBatch = convertDataIndicesToBatch(this._hoverLinkDataIndices);
this._hoverLinkDataIndices = visualMapModel.findTargetDataIndices(valueRange);
var newBatch = convertDataIndicesToBatch(this._hoverLinkDataIndices);
var resultBatches = helper.removeDuplicateBatch(oldBatch, newBatch);
this.api.dispatchAction({type: 'downplay', batch: resultBatches[0]});
this.api.dispatchAction({type: 'highlight', batch: resultBatches[1]});
}
},
/**
* @private
*/
_enableHoverLinkFromSeries: function () {
var zr = this.api.getZr();
if (this.visualMapModel.option.hoverLink) {
zr.on('mouseover', this._hoverLinkFromSeriesMouseOver, this);
zr.on('mouseout', this._hideIndicator, this);
}
else {
this._clearHoverLinkFromSeries();
}
},
/**
* @private
*/
_hoverLinkFromSeriesMouseOver: function (e) {
var el = e.target;
if (!el || el.dataIndex == null) {
return;
}
var dataModel = el.dataModel || this.ecModel.getSeriesByIndex(el.seriesIndex);
var data = dataModel.getData();
var dim = data.getDimension(this.visualMapModel.getDataDimension(data));
var value = data.get(dim, el.dataIndex, true);
this._updateIndicator(value);
},
/**
* @private
*/
_hideIndicator: function () {
var shapes = this._shapes;
shapes.indicator.attr('invisible', true);
shapes.indicatorLabel.attr('invisible', true);
},
/**
* @private
*/
_clearHoverLinkToSeries: function () {
this._hideIndicator();
var indices = this._hoverLinkDataIndices;
this.api.dispatchAction({
type: 'downplay',
batch: convertDataIndicesToBatch(indices)
});
indices.length = 0;
},
/**
* @private
*/
_clearHoverLinkFromSeries: function () {
this._hideIndicator();
var zr = this.api.getZr();
zr.off('mouseover', this._hoverLinkFromSeriesMouseOver);
zr.off('mouseout', this._hideIndicator);
},
/**
* @private
*/
_applyTransform: function (vertex, element, inverse, global) {
var transform = graphic.getTransform(element, global ? null : this.group);
return graphic[
zrUtil.isArray(vertex)
? 'applyTransform' : 'transformDirection'
zrUtil.isArray(vertex) ? 'applyTransform' : 'transformDirection'
](vertex, transform, inverse);
},
/**
* @override
*/
dispose: function () {
this._clearHoverLinkFromSeries();
this._clearHoverLinkToSeries();
},
/**
* @override
*/
remove: function () {
this._clearHoverLinkFromSeries();
this._clearHoverLinkToSeries();
}
});
......@@ -495,5 +693,9 @@ define(function(require) {
: [[0, 0], [textSize, 0], [textSize, textSize]];
}
function createIndicatorPoints() {
return [[0, 0], [5, -5], [5, 5]];
}
return ContinuousVisualMapView;
});
......@@ -37,16 +37,24 @@ define(function(require) {
// When categories: {'cate1': false, 'cate3': true}
// When selected === false, means all unselected.
align: 'auto', // 'auto', 'left', 'right'
itemWidth: 20, // 值域图形宽度
itemHeight: 14, // 值域图形高度
itemWidth: 20, // When put the controller vertically, it is the length of
// horizontal side of each item. Otherwise, vertical side.
itemHeight: 14, // When put the controller vertically, it is the length of
// vertical side of each item. Otherwise, horizontal side.
itemSymbol: 'roundRect',
pieceList: null, // 值顺序:由高到低, item can be:
// {min, max, value, color, colorSaturation, colorAlpha, symbol, symbolSize}
categories: null, // 描述 category 数据。如:['some1', 'some2', 'some3'],设置后,min max失效。
splitNumber: 5, // 分割段数,默认为5,为0时为线性渐变 (continous)
selectedMode: 'multiple',
itemGap: 10 // 各个item之间的间隔,单位px,默认为10,
// 横向布局时为水平间隔,纵向布局时为纵向间隔
pieceList: null, // Each item is Object, with some of those attrs:
// {min, max, value, color, colorSaturation, colorAlpha, opacity,
// symbol, symbolSize}, which customize the range or visual
// coding of the certain piece. Besides, see "Order Rule".
categories: null, // category names, like: ['some1', 'some2', 'some3'].
// Attr min/max are ignored when categories set. See "Order Rule"
splitNumber: 5, // If set to 5, auto split five pieces equally.
// If set to 0 and component type not set, component type will be
// determined as "continuous". (It is less reasonable but for ec2
// compatibility, see echarts/component/visualMap/typeDefaulter)
selectedMode: 'multiple', // Can be 'multiple' or 'single'.
itemGap: 10, // The gap between two items, in px.
hoverLink: true // Enable hover highlight.
},
/**
......@@ -169,14 +177,37 @@ define(function(require) {
* @override
*/
getValueState: function (value) {
var pieceList = this._pieceList;
var index = VisualMapping.findPieceIndex(value, pieceList);
var index = VisualMapping.findPieceIndex(value, this._pieceList);
return index != null
? (this.option.selected[this.getSelectedMapKey(pieceList[index])]
? (this.option.selected[this.getSelectedMapKey(this._pieceList[index])]
? 'inRange' : 'outOfRange'
)
: 'outOfRange';
},
/**
* @public
* @params {number} pieceIndex piece index in visualMapModel.getPieceList()
* @return {Array.<Object>} [{seriesId, dataIndices: <Array.<number>>}, ...]
*/
findTargetDataIndices: function (pieceIndex) {
var result = [];
this.eachTargetSeries(function (seriesModel) {
var dataIndices = [];
var data = seriesModel.getData();
data.each(this.getDataDimension(data), function (value, dataIndex) {
// Should always base on model pieceList, because it is order sensitive.
var pIdx = VisualMapping.findPieceIndex(value, this._pieceList);
pIdx === pieceIndex && dataIndices.push(dataIndex);
}, true, this);
result.push({seriesId: seriesModel.id, dataIndices: dataIndices});
}, this);
return result;
}
});
......
......@@ -34,7 +34,7 @@ define(function(require) {
showEndsText && this._renderEndsText(thisGroup, viewData.endsText[0], itemSize);
zrUtil.each(viewData.pieceList, renderItem, this);
zrUtil.each(viewData.viewPieceList, renderItem, this);
showEndsText && this._renderEndsText(thisGroup, viewData.endsText[1], itemSize);
......@@ -47,24 +47,27 @@ define(function(require) {
this.positionGroup(thisGroup);
function renderItem(item) {
var piece = item.piece;
var itemGroup = new graphic.Group();
itemGroup.onclick = zrUtil.bind(this._onItemClick, this, item.piece);
itemGroup.onclick = zrUtil.bind(this._onItemClick, this, piece);
this._enableHoverLink(itemGroup, item.indexInModelPieceList);
var representValue = this._getRepresentValue(item.piece);
var representValue = this._getRepresentValue(piece);
this._createItemSymbol(
itemGroup, representValue, [0, 0, itemSize[0], itemSize[1]]
);
if (showLabel) {
var visualState = this.visualMapModel.getValueState(representValue);
itemGroup.add(new graphic.Text({
style: {
x: itemAlign === 'right' ? -textGap : itemSize[0] + textGap,
y: itemSize[1] / 2,
text: item.piece.text,
text: piece.text,
textVerticalAlign: 'middle',
textAlign: itemAlign,
textFont: textFont,
......@@ -78,12 +81,33 @@ define(function(require) {
}
},
/**
* @private
*/
_enableHoverLink: function (itemGroup, pieceIndex) {
itemGroup
.on('mouseover', zrUtil.bind(onHoverLink, this, 'highlight'))
.on('mouseout', zrUtil.bind(onHoverLink, this, 'downplay'));
function onHoverLink(method) {
var visualMapModel = this.visualMapModel;
visualMapModel.option.hoverLink && this.api.dispatchAction({
type: method,
batch: helper.convertDataIndicesToBatch(
visualMapModel.findTargetDataIndices(pieceIndex)
)
});
}
},
/**
* @private
*/
_getItemAlign: function () {
var visualMapModel = this.visualMapModel;
var modelOption = visualMapModel.option;
if (modelOption.orient === 'vertical') {
return helper.getItemAlign(
visualMapModel, this.api, visualMapModel.itemSize
......@@ -105,8 +129,10 @@ define(function(require) {
if (!text) {
return;
}
var itemGroup = new graphic.Group();
var textStyleModel = this.visualMapModel.textStyleModel;
itemGroup.add(new graphic.Text({
style: {
x: itemSize[0] / 2,
......@@ -129,8 +155,8 @@ define(function(require) {
_getViewData: function () {
var visualMapModel = this.visualMapModel;
var pieceList = zrUtil.map(visualMapModel.getPieceList(), function (piece, index) {
return {piece: piece, index: index};
var viewPieceList = zrUtil.map(visualMapModel.getPieceList(), function (piece, index) {
return {piece: piece, indexInModelPieceList: index};
});
var endsText = visualMapModel.get('text');
......@@ -138,16 +164,16 @@ define(function(require) {
var orient = visualMapModel.get('orient');
var inverse = visualMapModel.get('inverse');
// Order of pieceList is always [low, ..., high]
// Order of model pieceList is always [low, ..., high]
if (orient === 'horizontal' ? inverse : !inverse) {
pieceList.reverse();
viewPieceList.reverse();
}
// Origin order of endsText is [high, low]
else if (endsText) {
endsText = endsText.slice().reverse();
}
return {pieceList: pieceList, endsText: endsText};
return {viewPieceList: viewPieceList, endsText: endsText};
},
/**
......
define(function(require) {
var layout = require('../../util/layout');
var zrUtil = require('zrender/core/util');
var DataDiffer = require('../../data/DataDiffer');
var helper = {
......@@ -42,8 +44,43 @@ define(function(require) {
(rect.margin[rParam[2]] || 0) + rect[rParam[0]] + rect[rParam[1]] * 0.5
< ecSize[rParam[1]] * 0.5 ? 0 : 1
];
},
convertDataIndicesToBatch: function (dataIndicesBySeries) {
var batch = [];
zrUtil.each(dataIndicesBySeries, function (item) {
zrUtil.each(item.dataIndices, function (dataIndex) {
batch.push({seriesId: item.seriesId, dataIndex: dataIndex});
});
});
return batch;
},
removeDuplicateBatch: function (batchA, batchB) {
var result = [[], []];
(new DataDiffer(batchA, batchB, getKey, getKey))
.add(add)
.update(zrUtil.noop)
.remove(remove)
.execute();
function getKey(item) {
return item.seriesId + '-' + item.dataIndex;
}
function add(index) {
result[1].push(batchB[index]);
}
function remove(index) {
result[0].push(batchA[index]);
}
return result;
}
};
return helper;
});
......@@ -183,6 +183,7 @@ define(function (require) {
* @param {string|number} dim
* Dimension can be concrete names like x, y, z, lng, lat, angle, radius
* Or a ordinal number. For example getDimensionInfo(0) will return 'x' or 'lng' or 'radius'
* @return {string} Concrete dim name.
*/
listProto.getDimension = function (dim) {
if (!isNaN(dim)) {
......@@ -480,7 +481,7 @@ define(function (require) {
/**
* Retreive the index of nearest value
* @param {string>} dim
* @param {string} dim
* @param {number} value
* @param {boolean} stack If given value is after stacked
* @return {number}
......
......@@ -398,7 +398,7 @@ define(function(require) {
* in coordinate of its ancestor (param ancestor)
*
* @param {module:zrender/mixin/Transformable} target
* @param {module:zrender/mixin/Transformable} ancestor
* @param {module:zrender/mixin/Transformable} [ancestor]
*/
graphic.getTransform = function (target, ancestor) {
var mat = matrix.identity([]);
......
......@@ -3,6 +3,7 @@ define(function (require) {
var Group = require('zrender/container/Group');
var componentUtil = require('../util/component');
var clazzUtil = require('../util/clazz');
var zrUtil = require('zrender/core/util');
function Chart() {
......
此差异已折叠。
......@@ -415,8 +415,8 @@
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
symbolSize: 5,
data: []
symbolSize: 20,
data: [[50, 50, 50, 50, 50]]
}
]
};
......
......@@ -21,6 +21,7 @@
'echarts/component/legend',
'echarts/component/grid',
'echarts/component/visualMap',
'echarts/component/tooltip',
'map/js/china'
], function (echarts) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册