提交 ac9c5f5a 编写于 作者: 1 100pah

enhance graphic

上级 ec07b33f
...@@ -36,18 +36,31 @@ define(function(require) { ...@@ -36,18 +36,31 @@ define(function(require) {
type: 'graphic', type: 'graphic',
defaultOption: { defaultOption: {
// Besides settings of graphic elements, each element // Extra properties for each elements:
// can be set with: left, right, top, bottom, width, height.
// If left/rigth is set, final shape.x/cx is not used.
// If top/bottom is set, final shape.y/cy is not used.
// Otherwise location is decided by shape setting.
// This mechanism is useful when you want position a group against the
// right side of this container, where you do not need to consider the
// settings of elements in this group.
// //
// Note: width/height setting only specify contianer(group) size, if // left/right/top/bottom: (like 12, '22%', 'center', default undefined)
// needed. And percentage value (like '33%') is not supported in // If left/rigth is set, shape.x/shape.cx/position is not used.
// width/height. See the reason in the layout algorithm below. // If top/bottom is set, shape.y/shape.cy/position is not used.
// This mechanism is useful when you want position a group/element
// against the right side or center of this container.
//
// width/height: (can only pixel value, default 0)
// Only be used to specify contianer(group) size, if needed. And
// can not be percentage value (like '33%'). See the reason in the
// layout algorithm below.
//
// bounding: (enum: 'all' (default) | 'raw')
// Specify how to calculate boundingRect when locating.
// 'all': Get uioned and transformed boundingRect
// from both itself and its descendants.
// This mode simplies confine a group of elements in the bounding
// of their ancester container (e.g., using 'right: 0').
// 'raw': Only use not transformed boundingRect of itself.
// This mode likes css behavior, useful when you want a
// element can overflow its container. (Consider a rotated
// circle needs to be located in a corner.)
// Note: elements is always behind its ancestors in elements array.
elements: [], elements: [],
parentId: null parentId: null
}, },
...@@ -118,10 +131,6 @@ define(function(require) { ...@@ -118,10 +131,6 @@ define(function(require) {
: existElParentId // parent not specified : existElParentId // parent not specified
? existElParentId ? existElParentId
: null; : null;
newElOption.hv = [
isSetLoc(newElOption, ['left', 'right']), // Rigid body, dont care `width`.
isSetLoc(newElOption, ['top', 'bottom']) // Rigid body, Dont care `height`.
];
newElOption.parentOption = null; // Clear newElOption.parentOption = null; // Clear
elOptionsToUpdate.push(newElOption); elOptionsToUpdate.push(newElOption);
...@@ -130,6 +139,15 @@ define(function(require) { ...@@ -130,6 +139,15 @@ define(function(require) {
var $action = newElOption.$action; var $action = newElOption.$action;
if (!$action || $action === 'merge') { if (!$action || $action === 'merge') {
if (existElOption) { if (existElOption) {
if (__DEV__) {
var newType = newElOption.type;
zrUtil.assert(
!newType || existElOption.type === newType,
'Please set $action: "replace" to change `type`'
);
}
// We can ensure that newElOptCopy and existElOption are not // We can ensure that newElOptCopy and existElOption are not
// the same object, so merge will not change newElOptCopy. // the same object, so merge will not change newElOptCopy.
zrUtil.merge(existElOption, newElOptCopy, true); zrUtil.merge(existElOption, newElOptCopy, true);
...@@ -150,6 +168,18 @@ define(function(require) { ...@@ -150,6 +168,18 @@ define(function(require) {
existElOption && (existList[index] = null); existElOption && (existList[index] = null);
} }
if (existList[index]) {
existList[index].hv = newElOption.hv = [
isSetLoc(newElOption, ['left', 'right']), // Rigid body, dont care `width`.
isSetLoc(newElOption, ['top', 'bottom']) // Rigid body, Dont care `height`.
];
// Given defualt group size, otherwise may layout error.
if (existList[index].type === 'group') {
existList[index].width == null && (existList[index].width = newElOption.width = 0);
existList[index].height == null && (existList[index].height = newElOption.height = 0);
}
}
}, this); }, this);
// Clean // Clean
...@@ -229,7 +259,20 @@ define(function(require) { ...@@ -229,7 +259,20 @@ define(function(require) {
* @override * @override
*/ */
render: function (graphicModel, ecModel, api) { render: function (graphicModel, ecModel, api) {
this._updateElements(graphicModel, api);
this._relocate(graphicModel, api);
},
/**
* @private
*/
_updateElements: function (graphicModel, api) {
var elOptionsToUpdate = graphicModel.useElOptionsToUpdate(); var elOptionsToUpdate = graphicModel.useElOptionsToUpdate();
if (!elOptionsToUpdate) {
return;
}
var elMap = this._elMap; var elMap = this._elMap;
var rootGroup = this.group; var rootGroup = this.group;
...@@ -243,7 +286,7 @@ define(function(require) { ...@@ -243,7 +286,7 @@ define(function(require) {
// In top/bottom mode, textVertical should not be used. But // In top/bottom mode, textVertical should not be used. But
// textBaseline should not be 'alphabetic', which is not precise. // textBaseline should not be 'alphabetic', which is not precise.
if (elOption.hv[1] && elOption.type === 'text') { if (elOption.hv && elOption.hv[1] && elOption.type === 'text') {
elOption.style = zrUtil.defaults({textBaseline: 'middle'}, elOption.style); elOption.style = zrUtil.defaults({textBaseline: 'middle'}, elOption.style);
elOption.style.textVerticalAlign = null; elOption.style.textVerticalAlign = null;
} }
...@@ -251,24 +294,25 @@ define(function(require) { ...@@ -251,24 +294,25 @@ define(function(require) {
// Remove unnecessary props to avoid potential problem. // Remove unnecessary props to avoid potential problem.
var elOptionCleaned = getCleanedElOption(elOption); var elOptionCleaned = getCleanedElOption(elOption);
// For simple, do not support parent change, otherwise reorder is needed.
if (__DEV__) {
existEl && zrUtil.assert(
targetElParent === existEl.parent,
'Changing parent is not supported.'
);
}
if (!$action || $action === 'merge') { if (!$action || $action === 'merge') {
if (existEl) { existEl
existEl.attr(elOptionCleaned); ? existEl.attr(elOptionCleaned)
if (targetElParent !== existEl.parent) { : createEl(id, targetElParent, elOptionCleaned, elMap);
removeEl(id, existEl, elMap);
targetElParent.add(existEl);
}
}
else {
createEl(id, targetElParent, elOptionCleaned, elMap);
}
} }
else if ($action === 'replace') { else if ($action === 'replace') {
removeEl(id, existEl, elMap); removeEl(existEl, elMap);
createEl(id, targetElParent, elOptionCleaned, elMap); createEl(id, targetElParent, elOptionCleaned, elMap);
} }
else if ($action === 'remove') { else if ($action === 'remove') {
removeEl(id, existEl, elMap); removeEl(existEl, elMap);
} }
if (elMap[id]) { if (elMap[id]) {
...@@ -276,16 +320,24 @@ define(function(require) { ...@@ -276,16 +320,24 @@ define(function(require) {
elMap[id].__ecGraphicHeight = elOption.height; elMap[id].__ecGraphicHeight = elOption.height;
} }
}); });
},
/**
* @private
*/
_relocate: function (graphicModel, api) {
// A very simple layout mechanism is used, where the size(width/height) can // A very simple layout mechanism is used, where the size(width/height) can
// not be determined by its parent(group) or its children, but the location // not be determined by its parent(group) or its children, but the location
// can be determined by its parent(group) and its chilren. // can be determined by its parent(group) and its chilren.
// If enable size dependency, both top-down and bottom-up tranverse is needed // If enable size dependency, both top-down and bottom-up tranverse is needed
// and recursive dependency needs to be handle, which make it too complecated. // and recursive dependency needs to be handle, which make it too complecated.
var elOptions = graphicModel.option.elements;
var rootGroup = this.group;
var elMap = this._elMap;
// Bottom-up tranvese to locate elements. // Bottom-up tranvese all elements (consider when ec resize) to locate elements.
for (var i = elOptionsToUpdate.length - 1; i >= 0; i--) { for (var i = elOptions.length - 1; i >= 0; i--) {
var elOption = elOptionsToUpdate[i]; var elOption = elOptions[i];
var el = elMap[elOption.id]; var el = elMap[elOption.id];
if (!el) { if (!el) {
...@@ -303,7 +355,10 @@ define(function(require) { ...@@ -303,7 +355,10 @@ define(function(require) {
height: parentEl.__ecGraphicHeight || 0 height: parentEl.__ecGraphicHeight || 0
}; };
layoutUtil.positionElement(el, elOption, containerInfo, null, elOption.hv); layoutUtil.positionElement(
el, elOption, containerInfo, null,
{hv: elOption.hv, boundingMode: elOption.bounding}
);
} }
}, },
...@@ -331,13 +386,17 @@ define(function(require) { ...@@ -331,13 +386,17 @@ define(function(require) {
var el = new Clz(elOption); var el = new Clz(elOption);
targetElParent.add(el); targetElParent.add(el);
elMap[id] = el; elMap[id] = el;
el.__ecGraphicId = id;
} }
function removeEl(id, existEl, elMap) { function removeEl(existEl, elMap) {
var existElParent = existEl && existEl.parent; var existElParent = existEl && existEl.parent;
if (existElParent) { if (existElParent) {
existEl.type === 'group' && existEl.tranvese(function (el) {
removeEl(el, elMap);
});
delete elMap[existEl.__ecGraphicId];
existElParent.remove(existEl); existElParent.remove(existEl);
delete elMap[id];
} }
} }
...@@ -345,7 +404,7 @@ define(function(require) { ...@@ -345,7 +404,7 @@ define(function(require) {
function getCleanedElOption(elOption) { function getCleanedElOption(elOption) {
elOption = zrUtil.extend({}, elOption); elOption = zrUtil.extend({}, elOption);
zrUtil.each( zrUtil.each(
['id', 'parentId', '$action', 'hv'].concat(layoutUtil.LOCATION_PARAMS), ['id', 'parentId', '$action', 'hv', 'bounding'].concat(layoutUtil.LOCATION_PARAMS),
function (name) { function (name) {
delete elOption[name]; delete elOption[name];
} }
......
...@@ -248,12 +248,16 @@ define(function(require) { ...@@ -248,12 +248,16 @@ define(function(require) {
return rect; return rect;
}; };
/** /**
* Position a zr element in viewport * Position a zr element in viewport
* Group position is specified by either * Group position is specified by either
* {left, top}, {right, bottom} * {left, top}, {right, bottom}
* If all properties exists, right and bottom will be igonred. * If all properties exists, right and bottom will be igonred.
* *
* This method modified and only modified el.position.
* If be called repeatly with the same input el, the same result will be gotten.
*
* @param {module:zrender/Element} el Should have `getBoundingRect` method. * @param {module:zrender/Element} el Should have `getBoundingRect` method.
* @param {Object} positionInfo * @param {Object} positionInfo
* @param {number|string} [positionInfo.left] * @param {number|string} [positionInfo.left]
...@@ -262,28 +266,58 @@ define(function(require) { ...@@ -262,28 +266,58 @@ define(function(require) {
* @param {number|string} [positionInfo.bottom] * @param {number|string} [positionInfo.bottom]
* @param {Object} containerRect * @param {Object} containerRect
* @param {string|number} margin * @param {string|number} margin
* @param {Array.<number>} [hv=[1,1]] Only horizontal or only vertical. * @param {Object} [opt]
* @param {Array.<number>} [opt.hv=[1,1]] Only horizontal or only vertical.
* @param {Array.<number>} [opt.boundingMode='all']
* Specify how to calculate boundingRect when locating.
* 'all': Get uioned and transformed boundingRect
* from both itself and its descendants.
* This mode simplies confine the elements in the bounding
* of their container (e.g., using 'right: 0').
* 'raw': Only use not transformed boundingRect of itself.
* This mode likes css behavior, useful when you want a
* element can overflow its container. (Consider a rotated
* circle needs to be located in a corner.)
* In this mode positionInfo.width/height can only be number.
*/ */
layout.positionElement = function ( layout.positionElement = function (el, positionInfo, containerRect, margin, opt) {
el, positionInfo, containerRect, margin, hv var h = !opt || !opt.hv || opt.hv[0];
) { var v = !opt || !opt.hv || opt.hv[1];
var elRect = el.getBoundingRect(); var boundingMode = opt && opt.boundingMode || 'all';
if (!h && !v) {
return;
}
var rect = (boundingMode === 'raw' && el.type === 'group')
? (new BoundingRect(+positionInfo.width || 0, +positionInfo.height || 0))
: el.getBoundingRect();
var useTransform = boundingMode !== 'raw' && el.needLocalTransform();
if (useTransform) {
var transform = el.getLocalTransform();
// Notice: raw rect may be inner object of el,
// which should not be modified.
rect = rect.clone();
rect.applyTransform(transform);
}
positionInfo = layout.getLayoutRect( positionInfo = layout.getLayoutRect(
zrUtil.defaults( zrUtil.defaults(
{width: elRect.width, height: elRect.height}, {width: rect.width, height: rect.height},
positionInfo positionInfo
), ),
containerRect, containerRect,
margin margin
); );
var position = el.position; // Because 'tranlate' is the last step in transform
hv = hv || [1, 1]; // (see zrender/core/Transformable#getLocalTransfrom),
// we can just only modify el.position to get final result.
var elPos = el.position;
el.attr('position', [ el.attr('position', [
hv[0] ? positionInfo.x - elRect.x : position[0], !h ? elPos[0] : useTransform ? elPos[0] + positionInfo.x - rect.x : positionInfo.x,
hv[1] ? positionInfo.y - elRect.y : position[1] !v ? elPos[1] : useTransform ? elPos[1] + positionInfo.y - rect.y : positionInfo.y
]); ]);
}; };
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
<script src="config.js"></script> <script src="config.js"></script>
<script src="lib/jquery.min.js"></script> <script src="lib/jquery.min.js"></script>
<script src="lib/facePrint.js"></script> <script src="lib/facePrint.js"></script>
<script src="lib/draggable.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="reset.css"> <link rel="stylesheet" href="reset.css">
</head> </head>
...@@ -63,6 +64,329 @@ ...@@ -63,6 +64,329 @@
<h3>Test group setOption(getOption) mapping.</h3>
<h3>Test left/right/top/bottom in group.</h3>
<h3>Test resize.</h3>
<div class="block" id="main4" style="height: 300px">
<div class="chart"></div>
<textarea>
{
backgroundColor: '#bba',
graphic: [{
id: 'img',
type: 'image',
right: 20,
top: 'center',
style: {
image: 'http://echarts.baidu.com/images/favicon.png',
width: 50,
height: 50,
opacity: 0.5
}
}, {
type: 'text',
bottom: 0,
right: 0,
rotation: Math.PI / 4,
style: {
font: '24px Microsoft YaHei',
text: '全屏右下角'
},
z: 100
}, {
type: 'text',
bottom: 0,
left: 'center',
style: {
font: '18px sans-serif',
text: '全屏最下中间\n这是多行文字\n这是第三行'
},
z: 100
}, {
type: 'group',
id: 'gr',
width: 230,
height: 110,
position: [70, 90],
children: [{
type: 'rect',
shape: {
width: 230,
height: 80
},
style: {
stroke: 'red',
fill: 'transparent',
lineWidth: 2
},
z: 100
}, {
type: 'rect',
shape: {
width: 60,
height: 110
},
style: {
stroke: 'red',
fill: 'transparent',
lineWidth: 2
},
z: 100
}, {
id: 'grouptext',
type: 'text',
bottom: 0,
right: 0,
rotation: 0.5,
style: {
font: '14px Microsoft YaHei',
text: 'group最右下角'
},
z: 100
}]
}]
}
</textarea>
<textarea>
{
graphic: [{
id: 'img',
left: 'center',
top: 'middle'
}]
}
</textarea>
<script>
require([
'echarts',
'echarts/chart/line',
'echarts/component/graphic',
'echarts/component/grid',
'echarts/component/legend',
'echarts/component/tooltip'
], function (echarts) {
var main = getMain('main4');
if (!main.dom) {
return;
}
var chart = echarts.init(main.dom);
draggable.init(main.dom, chart, {throttle: 70});
chart.setOption(main.option);
setTimeout(function () {
chart.setOption(chart.getOption());
setTimeout(function () {
chart.setOption(main.options[1]);
}, 1200);
}, 1200);
var rotation = 0;
setInterval(function () {
chart.setOption({
graphic: {
id: 'img',
rotation: (rotation += Math.PI / 360)
}
});
}, 17);
});
</script>
</div>
<h3>Width/height is determined by previous sibling.</h3>
<div class="block" id="main5" style="height: 400px">
<div class="chart"></div>
<textarea>
{
backgroundColor: 'rgba(0,0,255,0.08)',
legend: {
data:['高度(km)与气温(°C)变化关系']
},
tooltip: {
trigger: 'axis'
},
xAxis: {
},
yAxis: {
type: 'category',
data: ['0', '10', '20', '30', '40', '50', '60', '70', '80']
},
graphic: [{
type: 'image',
id: 'img',
z: -10,
right: 0,
top: 0,
bounding: 'raw',
origin: [75, 75],
style: {
fill: '#000',
image: 'http://echarts.baidu.com/images/favicon.png',
width: 150,
height: 150,
opacity: 0.4
}
}, {
type: 'group',
id: 'rectgroup1',
bottom: 0,
right: 0,
bounding: 'raw',
children: [{
type: 'rect',
left: 'center',
top: 'center',
shape: {
width: 20,
height: 80
},
style: {
stroke: 'green',
fill: 'transparent'
}
}, {
type: 'rect',
left: 'center',
top: 'center',
shape: {
width: 80,
height: 20
},
style: {
stroke: 'green',
fill: 'transparent'
}
}]
}, {
type: 'rect',
id: 'rect2',
bottom: 0,
right: 'center',
shape: {
width: 50,
height: 80
},
style: {
stroke: 'green',
fill: 'transparent'
}
}, {
type: 'group',
id: 'textGroup1',
left: '10%',
top: 'center',
children: [
{
type: 'rect',
z: 100,
left: 'center',
top: 'center',
shape: {
width: 170,
height: 70
},
style: {
fill: '#fff',
stroke: '#999',
lineWidth: 2,
shadowBlur: 8,
shadowOffsetX: 3,
shadowOffsetY: 3,
shadowColor: 'rgba(0,0,0,0.3)'
}
},
{
type: 'text',
z: 100,
top: 'middle',
left: 'center',
style: {
text: [
'横轴表示温度,单位是°C',
'纵轴表示高度,单位是km',
'右上角有一个图片做的水印'
].join('\n'),
font: '12px Microsoft YaHei'
}
}
]
}],
series: [
{
name: '高度(km)与气温(°C)变化关系',
type: 'line',
data:[15, -50, -56.5, -46.5, -22.1, -2.5, -27.7, -55.7, -76.5]
}
]
}
</textarea>
<script>
require([
'echarts',
'echarts/chart/line',
'echarts/component/graphic',
'echarts/component/grid',
'echarts/component/legend',
'echarts/component/tooltip'
], function (echarts) {
var main = getMain('main5');
if (!main.dom) {
return;
}
var chart = echarts.init(main.dom);
draggable.init(main.dom, chart, {throttle: 70});
chart.setOption(main.option);
var rotation = 0;
setInterval(function () {
rotation += Math.PI / 60;
chart.setOption({
graphic: [{
id: 'img',
bounding: 'raw',
origin: [75, 75],
rotation: rotation
}, {
id: 'rectgroup1',
rotation: rotation
}, {
id: 'rect2',
rotation: rotation
}, {
id: 'textGroup1',
rotation: rotation
}]
});
}, 17);
});
</script>
</div>
...@@ -83,6 +407,25 @@ ...@@ -83,6 +407,25 @@
lineWidth: 3 lineWidth: 3
} }
} }
}
</textarea>
<textarea>
{
graphic: {
type: 'rect',
$action: 'replace',
shape: {
x: 50,
y: 50,
width: 20,
height: 60
},
style: {
fill: 'green',
stroke: 'pink',
lineWidth: 3
}
}
} }
</textarea> </textarea>
<script> <script>
...@@ -102,6 +445,11 @@ ...@@ -102,6 +445,11 @@
chart.setOption(main.option); chart.setOption(main.option);
setTimeout(function () { setTimeout(function () {
chart.setOption(chart.getOption()); chart.setOption(chart.getOption());
setTimeout(function () {
chart.setOption(main.options[1]);
}, 1200);
}, 800); }, 800);
}); });
</script> </script>
...@@ -413,132 +761,6 @@ ...@@ -413,132 +761,6 @@
<h3>Test group setOption(getOption) mapping.</h3>
<h3>Test left/right/top/bottom in group.</h3>
<div class="block" id="main4" style="height: 300px">
<div class="chart"></div>
<textarea>
{
backgroundColor: '#bba',
graphic: [{
id: 'img',
type: 'image',
right: 0,
top: 'center',
style: {
image: 'http://echarts.baidu.com/images/favicon.png',
x: 10,
y: 30,
width: 50,
height: 50,
opacity: 0.5
}
}, {
type: 'text',
bottom: 0,
right: 0,
style: {
font: '18px sans-serif',
text: '全屏右下角'
},
z: 100
}, {
type: 'text',
bottom: 0,
left: 'center',
style: {
font: '18px sans-serif',
text: '全屏最下中间\n这是多行文字\n这是第三行'
},
z: 100
}, {
type: 'group',
id: 'gr',
width: 230,
height: 110,
position: [70, 90],
children: [{
type: 'rect',
shape: {
width: 230,
height: 80
},
style: {
stroke: 'red',
fill: 'transparent',
lineWidth: 2
},
z: 100
}, {
type: 'rect',
shape: {
width: 60,
height: 110
},
style: {
stroke: 'red',
fill: 'transparent',
lineWidth: 2
},
z: 100
}, {
type: 'text',
bottom: 0,
right: 0,
style: {
font: '18px sans-serif',
text: '文字在group最右下角',
},
z: 100
}]
}]
}
</textarea>
<textarea>
{
graphic: [{
id: 'img',
left: 0
}]
}
</textarea>
<script>
require([
'echarts',
'echarts/chart/line',
'echarts/component/graphic',
'echarts/component/grid',
'echarts/component/legend',
'echarts/component/tooltip'
], function (echarts) {
var main = getMain('main4');
if (!main.dom) {
return;
}
var chart = echarts.init(main.dom);
chart.setOption(main.option);
setTimeout(function () {
chart.setOption(chart.getOption());
setTimeout(function () {
chart.setOption(main.options[1]);
}, 1200);
}, 1200);
});
</script>
</div>
...@@ -549,108 +771,6 @@ ...@@ -549,108 +771,6 @@
<h3>Width/height is determined by previous sibling.</h3>
<div class="block" id="main5" style="height: 400px">
<div class="chart"></div>
<textarea>
{
legend: {
data:['高度(km)与气温(°C)变化关系']
},
tooltip: {
trigger: 'axis'
},
xAxis: {
},
yAxis: {
type: 'category',
data: ['0', '10', '20', '30', '40', '50', '60', '70', '80']
},
graphic: [{
type: 'image',
right: 0,
top: 0,
z: -10,
style: {
image: 'http://echarts.baidu.com/images/favicon.png',
width: 250,
height: 250,
opacity: 0.4
}
}, {
type: 'group',
left: '10%',
top: 'center',
width: 190,
height: 90,
children: [
{
type: 'rect',
z: 100,
shape: {
width: 190,
height: 90
},
style: {
fill: '#fff',
stroke: '#999',
lineWidth: 2,
shadowBlur: 8,
shadowOffsetX: 3,
shadowOffsetY: 3,
shadowColor: 'rgba(0,0,0,0.3)'
}
},
{
type: 'text',
z: 100,
top: 'middle',
left: 'center',
style: {
text: [
'横轴表示温度,单位是°C',
'纵轴表示高度,单位是km',
'右上角有一个图片做的水印',
'这个文本块可以放在图中各',
'种位置'
].join('\n'),
font: '14px Microsoft YaHei'
}
}
]
}],
series: [
{
name: '高度(km)与气温(°C)变化关系',
type: 'line',
data:[15, -50, -56.5, -46.5, -22.1, -2.5, -27.7, -55.7, -76.5]
}
]
}
</textarea>
<script>
require([
'echarts',
'echarts/chart/line',
'echarts/component/graphic',
'echarts/component/grid',
'echarts/component/legend',
'echarts/component/tooltip'
], function (echarts) {
var main = getMain('main5');
if (!main.dom) {
return;
}
var chart = echarts.init(main.dom);
chart.setOption(main.option);
});
</script>
</div>
</body> </body>
</html> </html>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册