提交 096c0f16 编写于 作者: L lang

Area chart

上级 1940aff8
......@@ -6,7 +6,7 @@ define(function (require) {
var graphic = require('../../util/graphic');
function createSymbol(data, idx, enableAnimation) {
var point = data.getItemLayout(idx).point;
var point = data.getItemLayout(idx);
var color = data.getItemVisual(idx, 'color');
var symbolSize = data.getItemVisual(idx, 'symbolSize');
......@@ -85,7 +85,7 @@ define(function (require) {
}
var symbolSize = data.getItemVisual(newIdx, 'symbolSize');
var point = data.getItemLayout(newIdx).point;
var point = data.getItemLayout(newIdx);
var el = oldData.getItemGraphicEl(oldIdx);
// Symbol changed
......
......@@ -10,6 +10,6 @@ define(function (require) {
require('../visual/symbol'), 'line', 'circle', 'line'
));
echarts.registerLayout(zrUtil.curry(
require('../layout/line'), 'line'
require('../layout/points'), 'line'
));
});
\ No newline at end of file
define(function(require) {
'use strict';
return require('zrender/graphic/Path').extend({
type: 'ec-area',
shape: {
points: [],
// Offset between stacked base points and points
stackedOnPoints: []
},
buildPath: function (ctx, shape) {
var points = shape.points;
var stackedOnPoints = shape.stackedOnPoints;
var i = 0;
var len = points.length;
while (i < points.length) {
for (var k = i; k < len; k++) {
var p = points[k];
if (p == null || isNaN(p[0]) || isNaN(p[1])) {
break;
}
ctx[k === i ? 'moveTo' : 'lineTo'](p[0], p[1]);
}
var tmp = k;
for (k--; k >= i; k--) {
var p = stackedOnPoints[k];
ctx.lineTo(p[0], p[1]);
}
i = tmp + 1;
}
}
});
});
\ No newline at end of file
......@@ -9,6 +9,7 @@ define(function(require) {
var DataSymbol = require('../helper/DataSymbol');
var lineAnimationDiff = require('./lineAnimationDiff');
var graphic = require('../../util/graphic');
var AreaPath = require('./Area');
function isPointsSame(points1, points2) {
if (points1.length !== points2.length) {
......@@ -36,6 +37,45 @@ define(function(require) {
return extent;
}
function getDataArray(coordSys, data, points) {
var dimensions = coordSys.type === 'cartesian2d' ? ['x', 'y'] : ['radius', 'angle'];
return data.map(dimensions, function (x, y, idx) {
return {
x: x,
y: y,
point: points[idx],
name: data.getName(idx),
idx: idx,
rawIdx: data.getRawIndex(idx)
};
});
}
/**
* @param {module:echarts/coord/cartesian/Cartesian2D|module:echarts/coord/polar/Polar} coordSys
* @param {module:echarts/data/List} data
* @param {Array.<Array.<number>>} points
* @private
*/
function getStackedOnPoints(coordSys, data, points) {
var stackedOnData = data.stackedOn;
if (stackedOnData) {
return stackedOnData.map(stackedOnData.getItemLayout, true);
}
else {
var valueAxis = coordSys.getOtherAxis(coordSys.getBaseAxis());
var valueStart = valueAxis.getExtent()[0];
var dim = valueAxis.dim;
var baseCoordOffset = dim === 'x' || dim === 'radius' ? 1 : 0;
return zrUtil.map(points, function (point, idx) {
var pt = [];
pt[baseCoordOffset] = point[baseCoordOffset];
pt[1 - baseCoordOffset] = valueStart;
return pt;
});
}
}
return require('../../echarts').extendChartView({
type: 'line',
......@@ -48,98 +88,93 @@ define(function(require) {
var coordSys = seriesModel.coordinateSystem;
var group = this.group;
var data = seriesModel.getData();
var lineStyleNormalModel = seriesModel.getModel('itemStyle.normal.lineStyle');
var points = data.map(function (idx) {
var layout = data.getItemLayout(idx);
return layout && layout.point;
}, true);
var dimensions = coordSys.type === 'cartesian2d' ? ['x', 'y'] : ['radius', 'angle'];
var plainDataList = data.map(dimensions, function (x, y, idx) {
return {
x: x,
y: y,
point: points[idx],
name: data.getName(idx),
idx: idx,
rawIdx: data.getRawIndex(idx)
};
});
var lineStyleModel = seriesModel.getModel('itemStyle.normal.lineStyle');
var areaStyleModel = seriesModel.getModel('itemStyle.normal.areaStyle');
var points = data.map(data.getItemLayout, true);
var dataArray = getDataArray(coordSys, data, points);
var prevCoordSys = this._coordSys;
var isCoordSysPolar = coordSys.type === 'polar';
var prevCoordSys = this._coordSys;
// FIXME Update after animation
// if (
// isCoordSysPolar
// && points.length > 2
// && coordinateSystem.getAngleAxis().type === 'category'
// ) {
// // Close polyline
// points.push(Array.prototype.slice.call(points[0]));
// }
// Draw symbols, enable animation on the first draw
var dataSymbol = this._dataSymbol;
var polyline = this._polyline;
var enableAnimation = ecModel.get('animation');
var polygon = this._polygon;
var hasAnimation = ecModel.get('animation');
var isAreaChart = !areaStyleModel.isEmpty();
var stackedOnPoints = getStackedOnPoints(
coordSys, data, points
);
var stackedOnDataArray = data.stackedOn ? getDataArray(
coordSys, data.stackedOn, stackedOnPoints
) : zrUtil.map(stackedOnPoints, function (pt) {
return {
point: pt
};
});
// Initialization animation or coordinate system changed
if (
!(polyline
&& prevCoordSys.type === coordSys.type
&& enableAnimation)
&& hasAnimation)
) {
// Remove previous created polyline
if (polyline) {
group.remove(polyline);
dataSymbol.updateData(data, hasAnimation);
polyline = this._newPolyline(group, points, coordSys, hasAnimation);
if (isAreaChart) {
polygon = this._newPolygon(
group, points,
stackedOnPoints,
coordSys, hasAnimation
);
}
}
else {
dataSymbol.updateData(data, false);
polyline = new graphic.Polyline({
shape: {
points: points
}
});
// var removeClipPath = zrUtil.bind(polyline.removeClipPath, polyline);
var clipPath = isCoordSysPolar
? this._createPolarClipShape(coordSys, enableAnimation)
: this._createGridClipShape(coordSys, enableAnimation);
polyline.setClipPath(clipPath);
group.add(polyline);
this._polyline = polyline;
}
else {
// Update clipPath
var clipPath = isCoordSysPolar
? this._createPolarClipShape(coordSys)
: this._createGridClipShape(coordSys);
polyline.setClipPath(clipPath);
// FIXME Clip path used by more than one elements
polyline.setClipPath(
this._createClipShape(coordSys)
);
polygon && polygon.setClipPath(
this._createClipShape(coordSys)
);
dataSymbol.updateData(data, false);
// In the case data zoom triggerred refreshing frequently
// Data may not change if line has a category axis. So it should animate nothing
if (!isPointsSame(this._plainDataList, plainDataList)) {
if (!isPointsSame(this._dataArray, dataArray)) {
this._updateAnimation(
data, plainDataList, coordSys
data, dataArray, stackedOnDataArray, coordSys
);
}
// Add back
group.add(polyline);
group.add(polygon);
}
polyline.setStyle(zrUtil.extend(
lineStyleNormalModel.getLineStyle(),
lineStyleModel.getLineStyle(),
{
stroke: data.getVisual('color'),
lineJoin: 'bevel'
}
));
if (polygon) {
polygon.style.opacity = 0.7;
polygon.setStyle(zrUtil.extend(
areaStyleModel.getAreaStyle(),
{
fill: data.getVisual('color'),
lineJoin: 'bevel'
}
));
}
// Make sure symbols is on top of line
group.remove(dataSymbol.group);
......@@ -148,13 +183,76 @@ define(function(require) {
this._data = data;
// Save the coordinate system and data for transition animation when data changed
this._plainDataList = plainDataList;
this._dataArray = dataArray;
this._stackedOnDataArray = stackedOnDataArray;
this._coordSys = coordSys;
!isCoordSysPolar && !seriesModel.get('showAllSymbol')
&& this._updateSymbolDisplay(data, coordSys);
},
/**
* @param {module:zrender/container/Group} group
* @param {Array.<Array.<number>>} points
* @param {module:echarts/coord/cartesian/Cartesian2D|module:echarts/coord/polar/Polar} coordSys
* @param {boolean} hasAnimation
* @private
*/
_newPolyline: function (group, points, coordSys, hasAnimation) {
var polyline = this._polyline;
// Remove previous created polyline
if (polyline) {
group.remove(polyline);
}
polyline = new graphic.Polyline({
shape: {
points: points
},
silent: true
});
var clipPath = this._createClipShape(coordSys, hasAnimation);
polyline.setClipPath(clipPath);
group.add(polyline);
this._polyline = polyline;
return polyline;
},
/**
* @param {module:zrender/container/Group} group
* @param {Array.<Array.<number>>} stackedOnPoints
* @param {Array.<Array.<number>>} points
* @param {module:echarts/coord/cartesian/Cartesian2D|module:echarts/coord/polar/Polar} coordSys
* @param {boolean} hasAnimation
* @private
*/
_newPolygon: function (group, points, stackedOnPoints, coordSys, hasAnimation) {
var polygon = this._polygon;
// Remove previous created polygon
if (polygon) {
group.remove(polygon);
}
polygon = new AreaPath({
shape: {
points: points,
stackedOnPoints: stackedOnPoints
},
silent: true
});
var clipPath = this._createClipShape(coordSys, hasAnimation);
polygon.setClipPath(clipPath);
group.add(polygon);
this._polygon = polygon;
return polygon;
},
/**
* @private
*/
......@@ -174,10 +272,14 @@ define(function(require) {
/**
* @private
*/
_updateAnimation: function (data, plainDataList, coordSys) {
_updateAnimation: function (data, dataArray, stackedOnDataArray, coordSys) {
var polyline = this._polyline;
var polygon = this._polygon;
var diff = lineAnimationDiff(
this._plainDataList, plainDataList, this._coordSys, coordSys
this._dataArray, dataArray,
this._stackedOnDataArray, stackedOnDataArray,
this._coordSys, coordSys
);
polyline.shape.points = diff.current;
polyline.animateTo({
......@@ -186,6 +288,19 @@ define(function(require) {
}
}, 300, 'cubicOut');
if (polygon) {
var polygonShape = polygon.shape;
polygonShape.points = diff.current;
polygonShape.stackedOnPoints = diff.stackedOnCurrent;
polygon.animateTo({
shape: {
points: diff.next,
stackedOnPoints: diff.stackedOnNext
}
}, 300, 'cubicOut');
}
var updatedDataInfo = [];
var addedDataIndices = [];
var diffStatus = diff.status;
......@@ -226,7 +341,13 @@ define(function(require) {
}
},
_createGridClipShape: function (cartesian, animation, categoryAxis) {
_createClipShape: function (coordSys, hasAnimation) {
return coordSys.type === 'polar'
? this._createPolarClipShape(coordSys, hasAnimation)
: this._createGridClipShape(coordSys, hasAnimation);
},
_createGridClipShape: function (cartesian, animation) {
var xExtent = getAxisExtentWithGap(cartesian.getAxis('x'));
var yExtent = getAxisExtentWithGap(cartesian.getAxis('y'));
......@@ -284,7 +405,9 @@ define(function(require) {
},
remove: function () {
this.group.remove(this._polyline);
var group = this.group;
group.remove(this._polyline);
group.remove(this._polygon);
this._dataSymbol.remove(true);
}
});
......
......@@ -6,10 +6,32 @@ define(function (require) {
return a.name === b.name;
}
return function (oldData, newData, oldCoordSys, newCoordSys) {
function getStackedOnPoint(coordSys, dataItem) {
if ('x' in dataItem) {
return coordSys.dataToPoint([dataItem.x, dataItem.y]);
}
else {
var valueAxis = coordSys.getOtherAxis(coordSys.getBaseAxis());
var valueStart = valueAxis.getExtent()[0];
var dim = valueAxis.dim;
dim === 'radius' && (dim = 'x');
dim === 'angle' && (dim = 'y');
var baseCoordOffset = dim === 'x' ? 1 : 0;
var pt = [];
pt[baseCoordOffset] = valueAxis.dataToCoord(dataItem[dim]);
pt[1 - baseCoordOffset] = valueStart;
return pt;
}
}
return function (oldData, newData, oldStackedData, newStackedData, oldCoordSys, newCoordSys) {
var oldPoints = [];
var newPoints = [];
// Points for stacking base line
var oldStackedPoints = [];
var newStackedPoints = [];
var status = [];
var sortedIndices = [];
var rawIndices = [];
......@@ -27,21 +49,43 @@ define(function (require) {
case '=':
oldPoints.push(oldData[diffItem.idx].point);
newPoints.push(newData[diffItem.idx1].point);
oldStackedPoints.push(oldStackedData[diffItem.idx].point);
newStackedPoints.push(newStackedData[diffItem.idx1].point);
rawIndices.push(newData[diffItem.idx1].rawIdx);
break;
case '+':
var newDataItem = newData[diffItem.idx];
oldPoints.push(oldCoordSys.dataToPoint([newDataItem.x, newDataItem.y]));
var newStackedDataItem = newStackedData[diffItem.idx];
oldPoints.push(
oldCoordSys.dataToPoint([newDataItem.x, newDataItem.y])
);
newPoints.push(newDataItem.point);
oldStackedPoints.push(
getStackedOnPoint(oldCoordSys, newStackedDataItem)
);
newStackedPoints.push(newStackedPoints)
rawIndices.push(newDataItem.rawIdx);
break;
case '-':
var oldDataItem = oldData[diffItem.idx];
var oldStackedDataItem = oldStackedData[diffItem.idx];
// Data is replaced. In the case of dynamic data queue
// FIXME FIXME FIXME
if (oldDataItem.rawIdx !== diffItem.idx) {
oldPoints.push(oldDataItem.point);
newPoints.push(newCoordSys.dataToPoint([oldDataItem.x, oldDataItem.y]));
oldStackedPoints.push(oldStackedDataItem.point);
newStackedPoints.push(
getStackedOnPoint(newCoordSys, oldStackedDataItem)
);
rawIndices.push(oldDataItem.rawIdx);
}
else {
......@@ -64,17 +108,29 @@ define(function (require) {
var sortedOldPoints = [];
var sortedNewPoints = [];
var sortedOldStackedPoints = [];
var sortedNewStackedPoints = [];
var sortedStatus = [];
for (var i = 0; i < sortedIndices.length; i++) {
var oldIndex = sortedIndices[i];
sortedOldPoints[i] = oldPoints[oldIndex];
sortedNewPoints[i] = newPoints[oldIndex];
sortedStatus[i] = status[oldIndex];
var idx = sortedIndices[i];
sortedOldPoints[i] = oldPoints[idx];
sortedNewPoints[i] = newPoints[idx];
sortedOldStackedPoints[i] = oldStackedPoints[idx];
sortedNewStackedPoints[i] = newStackedPoints[idx];
sortedStatus[i] = status[idx];
}
return {
current: sortedOldPoints,
next: sortedNewPoints,
stackedOnCurrent: sortedOldStackedPoints,
stackedOnNext: sortedNewStackedPoints,
status: sortedStatus
};
}
......
define(function (require) {
'use strict';
function getSeriesStackId(seriesModel) {
return seriesModel.get('stack') || '__ec_stack_' + seriesModel.seriesIndex;
}
return function (seriesType, ecModel, api) {
var lastStackCoords = {};
ecModel.eachSeriesByType(seriesType, function (lineSeries) {
var data = lineSeries.getData();
var coordSys = lineSeries.coordinateSystem;
var dims = coordSys.type === 'cartesian2d' ? ['x', 'y'] : ['radius', 'angle'];
var stackId = getSeriesStackId(lineSeries);
var baseAxis = coordSys.getBaseAxis();
var valueAxis = coordSys.getOtherAxis(baseAxis);
var valueAxisStart = valueAxis.getExtent()[0];
var baseCoordOffset = baseAxis.dim === 'x' ? 0 : 1;
lastStackCoords[stackId] = lastStackCoords[stackId] || [];
data.each(dims, function (x, y, idx) {
if (!isNaN(y) && !isNaN(x)) {
var lastCoord = lastStackCoords[stackId][idx] || valueAxisStart;
var point = coordSys.dataToPoint([x, y]);
var stackPoint = [];
stackPoint[baseCoordOffset] = point[baseCoordOffset];
stackPoint[1 - baseCoordOffset] = lastCoord;
lastStackCoords[stackId][idx] = lastCoord;
data.setItemLayout(idx, {
point: point,
// Stack points for area charts
stackOn: stackPoint
});
}
}, true);
});
}
});
\ No newline at end of file
......@@ -9,9 +9,7 @@ define(function (require) {
data.each(dims, function (x, y, idx) {
if (!isNaN(y) && !isNaN(x)) {
var point = coordSys.dataToPoint([x, y]);
data.setItemLayout(idx, {
point: point
});
data.setItemLayout(idx, point);
}
}, true);
});
......
......@@ -6,6 +6,7 @@ define(function (require) {
['shadowBlur'],
['shadowOffsetX'],
['shadowOffsetY'],
['opacity'],
['shadowColor']
]
)
......
......@@ -164,7 +164,7 @@ define(function(require) {
this.setStyle(this.__normalStyle);
}
var MOUSEOVER = 'mouseover';
var MOUSEOUT = 'mouseover';
var MOUSEOUT = 'mouseout';
/**
* Set hover style of element
* @param {module:zrender/graphic/Displayable} el
......
<html>
<head>
<meta charset="utf-8">
<script src="esl.js"></script>
<script src="config.js"></script>
</head>
<body>
<style>
html, body, #main {
width: 100%;
height: 100%;
}
</style>
<div id="main"></div>
<script>
require([
'echarts',
'echarts/chart/line',
'echarts/component/legend',
'echarts/component/grid',
'echarts/component/tooltip'
], function (echarts) {
var chart = echarts.init(document.getElementById('main'), null, {
renderer: 'canvas'
});
var xAxisData = [];
var data1 = [];
var data2 = [];
var data3 = [];
for (var i = 0; i < 10; i++) {
xAxisData.push('类目' + i);
data1.push(+Math.random().toFixed(3));
data2.push(+Math.random().toFixed(3));
data3.push(+Math.random().toFixed(3));
}
var itemStyle = {
normal: {
borderColor: 'white',
borderWidth: 3,
// shadowBlur: 10,
// shadowOffsetX: 0,
// shadowOffsetY: 5,
// shadowColor: 'rgba(0, 0, 0, 0.4)',
lineStyle: {
width: 3,
// shadowBlur: 10,
// shadowOffsetX: 0,
// shadowOffsetY: 5,
// shadowColor: 'rgba(0, 0, 0, 0.4)'
},
areaStyle: {
}
}
};
chart.setOption({
legend: {
data: ['line', 'line2', 'line3']
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line'
}
},
xAxis: {
// data: ['类目1', '类目2', '类目3', '类目4', '类目5',]
data: xAxisData,
boundaryGap: false,
// inverse: true,
splitArea: {
show: true
}
},
yAxis: {
splitLine: {
// show: false
}
},
series: [{
name: 'line',
type: 'line',
stack: 'all',
symbol: 'circle',
symbolSize: 10,
data: data1,
itemStyle: itemStyle
}, {
name: 'line2',
type: 'line',
stack: 'all',
symbol: 'circle',
symbolSize: 10,
data: data2,
itemStyle: itemStyle
}, {
name: 'line3',
type: 'line',
stack: 'all',
symbol: 'circle',
symbolSize: 10,
data: data3,
itemStyle: itemStyle
}]
});
})
</script>
</body>
</html>
\ No newline at end of file
......@@ -28,7 +28,7 @@
var xAxisData = [];
var data1 = [];
var count = 0;
for (; count < 100; count++) {
for (; count < 200; count++) {
xAxisData.push('类目' + count);
data1.push(+Math.random().toFixed(3));
}
......@@ -42,11 +42,14 @@
// shadowOffsetY: 5,
// shadowColor: 'rgba(0, 0, 0, 0.4)',
lineStyle: {
width: 3,
width: 2,
// shadowBlur: 10,
// shadowOffsetX: 0,
// shadowOffsetY: 5,
// shadowColor: 'rgba(0, 0, 0, 0.4)'
},
areaStyle: {
}
}
};
......@@ -63,9 +66,11 @@
},
// animation: false,
xAxis: {
// data: ['类目1', '类目2', '类目3', '类目4', '类目5',]
axisLabel: {
interval: 20
},
data: xAxisData,
// boundaryGap: false
boundaryGap: false
},
yAxis: {
splitLine: {
......@@ -84,12 +89,13 @@
});
setInterval(function () {
data1.shift();
data1.push(+Math.random().toFixed(3));
xAxisData.shift();
xAxisData.push('类目' + count++);
for (var i = 0; i < 1; i++) {
xAxisData.shift();
xAxisData.push('类目' + count);
data1.shift();
data1.push(+Math.random().toFixed(3));
count++;
}
chart.setOption({
xAxis: {
data: xAxisData
......@@ -99,7 +105,7 @@
data: data1
}]
});
}, 100);
}, 1000);
})
</script>
......
......@@ -31,7 +31,7 @@
var data2 = [];
var data3 = [];
for (var i = 0; i < 10; i++) {
for (var i = 0; i < 100; i++) {
xAxisData.push('类目' + i);
data1.push(+Math.random().toFixed(3));
data2.push(+Math.random().toFixed(3));
......@@ -42,16 +42,16 @@
normal: {
borderColor: 'white',
borderWidth: 3,
shadowBlur: 10,
shadowOffsetX: 0,
shadowOffsetY: 5,
shadowColor: 'rgba(0, 0, 0, 0.4)',
// shadowBlur: 10,
// shadowOffsetX: 0,
// shadowOffsetY: 5,
// shadowColor: 'rgba(0, 0, 0, 0.4)',
lineStyle: {
width: 3,
shadowBlur: 10,
shadowOffsetX: 0,
shadowOffsetY: 5,
shadowColor: 'rgba(0, 0, 0, 0.4)'
// shadowBlur: 10,
// shadowOffsetX: 0,
// shadowOffsetY: 5,
// shadowColor: 'rgba(0, 0, 0, 0.4)'
}
}
};
......@@ -61,12 +61,19 @@
data: ['line', 'line2', 'line3']
},
tooltip: {
trigger: 'axis'
trigger: 'axis',
axisPointer: {
type: 'line'
}
},
xAxis: {
// data: ['类目1', '类目2', '类目3', '类目4', '类目5',]
data: xAxisData,
boundaryGap: false
boundaryGap: false,
// inverse: true,
splitArea: {
show: true
}
},
yAxis: {
splitLine: {
......
......@@ -58,18 +58,21 @@
series: [{
coordinateSystem: 'polar',
name: 'line',
stack: 'all',
type: 'line',
symbolSize: 10,
data: data1
}, {
coordinateSystem: 'polar',
name: 'line2',
stack: 'all',
type: 'line',
symbolSize: 10,
data: data2
}, {
coordinateSystem: 'polar',
name: 'line3',
stack: 'all',
type: 'line',
symbolSize: 10,
data: data3
......
......@@ -27,8 +27,10 @@
var data = [];
for (var i = 0; i < 300; i++) {
data.push([i, (i * 5) % 360]);
for (var i = 0; i < 100; i++) {
var theta = i / 100 * 360;
var r = 5 * (1 + Math.sin(theta / 180 * Math.PI));
data.push([r, theta]);
}
chart.setOption({
......
......@@ -64,15 +64,7 @@
series: [{
name: 'scatter',
type: 'scatter',
itemStyle: {
normal: {
opacity: 0.8,
shadowBlur: 10,
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
symbol: 'diamond',
symbolSize: function (val) {
return val[2] * 40;
},
......@@ -80,15 +72,6 @@
}, {
name: 'scatter2',
type: 'scatter',
itemStyle: {
normal: {
opacity: 0.8,
shadowBlur: 10,
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
symbolSize: function (val) {
return val[2] * 40;
},
......@@ -96,15 +79,6 @@
}, {
name: 'scatter3',
type: 'scatter',
itemStyle: {
normal: {
opacity: 0.8,
shadowBlur: 10,
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
symbolSize: function (val) {
return val[2] * 40;
},
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册