提交 3e269647 编写于 作者: Q qin_jun_yan

Model traceability code review problem modification, method split optimization...

Model traceability code review problem modification, method split optimization and string hard coding rectification
上级 aeed834f
...@@ -34,6 +34,10 @@ export default new Vuex.Store({ ...@@ -34,6 +34,10 @@ export default new Vuex.Store({
multiSelectedGroupCount: 0, multiSelectedGroupCount: 0,
tableId: 0, tableId: 0,
componentsCount: 0, componentsCount: 0,
summaryDirList: undefined,
selectedBarList: [],
hidenDirChecked: [],
customizedColumnOptions: [],
}, },
mutations: { mutations: {
// set cancelTokenArr // set cancelTokenArr
......
...@@ -435,6 +435,14 @@ export default { ...@@ -435,6 +435,14 @@ export default {
'learning_rate', 'learning_rate',
'device_num', 'device_num',
], ],
valueType: {
float: 'float',
int: 'int',
string: 'string',
model_size: 'model_size',
learning_rate: 'learning_rate',
dataset_mark: 'dataset_mark',
},
table: { table: {
columnOptions: { columnOptions: {
summary_dir: { summary_dir: {
...@@ -964,10 +972,10 @@ export default { ...@@ -964,10 +972,10 @@ export default {
id: item, id: item,
checked: true, checked: true,
}; };
if (value && value.type === 'float') { if (value && value.type === this.valueType.float) {
obj.type = 'float'; obj.type = this.valueType.float;
} else if (value && value.type === 'int') { } else if (value && value.type === this.valueType.int) {
obj.type = 'int'; obj.type = this.valueType.int;
} }
arrayTemp.push(obj); arrayTemp.push(obj);
}); });
...@@ -1006,14 +1014,14 @@ export default { ...@@ -1006,14 +1014,14 @@ export default {
content.name === this.repeatTitle || content.name === this.repeatTitle ||
content.name === this.shuffleTitle || content.name === this.shuffleTitle ||
content.id === this.deviceNum || content.id === this.deviceNum ||
(content.type && content.type === 'int') (content.type && content.type === this.valueType.int)
) { ) {
obj.scale = true; obj.scale = true;
obj.minInterval = 1; obj.minInterval = 1;
this.setColorOfSelectedBar(selectedBarList, obj); this.setColorOfSelectedBar(selectedBarList, obj);
} else if ( } else if (
this.numberTypeIdList.includes(content.id) || this.numberTypeIdList.includes(content.id) ||
(content.type && content.type === 'float') (content.type && content.type === this.valueType.float)
) { ) {
obj.scale = true; obj.scale = true;
this.setColorOfSelectedBar(selectedBarList, obj); this.setColorOfSelectedBar(selectedBarList, obj);
...@@ -1024,7 +1032,7 @@ export default { ...@@ -1024,7 +1032,7 @@ export default {
show: false, show: false,
}; };
this.setColorOfSelectedBar(selectedBarList, obj); this.setColorOfSelectedBar(selectedBarList, obj);
if (content.id === 'dataset_mark') { if (content.id === this.valueType.dataset_mark) {
obj.axisLabel = { obj.axisLabel = {
show: false, show: false,
}; };
...@@ -1073,13 +1081,16 @@ export default { ...@@ -1073,13 +1081,16 @@ export default {
if (this.parallelEchart) { if (this.parallelEchart) {
this.parallelEchart.off('axisareaselected', null); this.parallelEchart.off('axisareaselected', null);
window.removeEventListener('resize', this.resizeChart, false); window.removeEventListener('resize', this.resizeChart, false);
} else {
this.parallelEchart = Echarts.init(
document.querySelector('#data-echart'),
);
} }
this.parallelEchart = Echarts.init(
document.querySelector('#data-echart'),
);
this.parallelEchart.setOption(option, true); this.parallelEchart.setOption(option, true);
window.addEventListener('resize', this.resizeChart, false); window.addEventListener('resize', this.resizeChart, false);
this.chartEventsListen(parallelAxis);
},
chartEventsListen(parallelAxis) {
this.parallelEchart.on('axisareaselected', (params) => { this.parallelEchart.on('axisareaselected', (params) => {
this.recordsNumber = 0; this.recordsNumber = 0;
this.showNumber = 0; this.showNumber = 0;
...@@ -1149,17 +1160,19 @@ export default { ...@@ -1149,17 +1160,19 @@ export default {
color: '#00a5a7', color: '#00a5a7',
}, },
formatter: function(val) { formatter: function(val) {
if (typeof val !== 'string') { if (typeof val !== this.valueType.string) {
return val; return val;
} }
const strs = val.split(''); const strs = val.split('');
let str = ''; let str = '';
if (val.length > 100) { const maxStringLength = 100;
return val.substring(0, 12) + '...'; const showStringLength = 12;
if (val.length > maxStringLength) {
return val.substring(0, showStringLength) + '...';
} else { } else {
for (let i = 0, s = ''; (s = strs[i++]); ) { for (let i = 0, s = ''; (s = strs[i++]); ) {
str += s; str += s;
if (!(i % 12)) { if (!(i % showStringLength)) {
str += '\n'; str += '\n';
} }
} }
...@@ -1204,20 +1217,25 @@ export default { ...@@ -1204,20 +1217,25 @@ export default {
if (isNaN(value) || !value) { if (isNaN(value) || !value) {
return value; return value;
} else { } else {
if (key === 'learning_rate') { const numDigits = 4;
let temp = value.toPrecision(4); if (key === this.valueType.learning_rate) {
let temp = value.toPrecision(numDigits);
let row = 0; let row = 0;
while (temp < 1) { while (temp < 1) {
temp = temp * 10; temp = temp * 10;
row += 1; row += 1;
} }
temp = this.toFixedFun(temp, 4); temp = this.toFixedFun(temp, numDigits);
return `${temp}${row ? `e-${row}` : ''}`; return `${temp}${row ? `e-${row}` : ''}`;
} else if (key === 'model_size') { } else if (key === this.valueType.model_size) {
return value + 'MB'; return value + 'MB';
} else { } else {
if (value < 1000) { const num = 1000;
return Math.round(value * Math.pow(10, 4)) / Math.pow(10, 4); if (value < num) {
return (
Math.round(value * Math.pow(10, numDigits)) /
Math.pow(10, numDigits)
);
} else { } else {
const reg = /(?=(\B)(\d{3})+$)/g; const reg = /(?=(\B)(\d{3})+$)/g;
return (value + '').replace(reg, ','); return (value + '').replace(reg, ',');
...@@ -1245,7 +1263,8 @@ export default { ...@@ -1245,7 +1263,8 @@ export default {
* @param {Object} scope * @param {Object} scope
*/ */
showDialogData(val, scope) { showDialogData(val, scope) {
if (typeof val !== 'string' || val === '{}') { const emptyObjectStr = '{}';
if (typeof val !== this.valueType.string || val === emptyObjectStr) {
return; return;
} else { } else {
const isJson = this.isJSON(val); const isJson = this.isJSON(val);
...@@ -1541,7 +1560,7 @@ export default { ...@@ -1541,7 +1560,7 @@ export default {
hideDataMarkTableData() { hideDataMarkTableData() {
const result = []; const result = [];
this.selectedBarList.forEach((item) => { this.selectedBarList.forEach((item) => {
if (item !== 'dataset_mark') { if (item !== this.valueType.dataset_mark) {
result.push(item); result.push(item);
} }
}); });
...@@ -1906,10 +1925,6 @@ export default { ...@@ -1906,10 +1925,6 @@ export default {
.el-color-alpha-slider { .el-color-alpha-slider {
display: none; display: none;
} }
.el-select > .el-input {
width: 280px !important;
max-width: 500px !important;
}
.select-inner-input { .select-inner-input {
width: calc(100% - 140px); width: calc(100% - 140px);
margin: 2px 4px; margin: 2px 4px;
...@@ -1958,6 +1973,10 @@ export default { ...@@ -1958,6 +1973,10 @@ export default {
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
position: relative; position: relative;
.el-select > .el-input {
width: 280px !important;
max-width: 500px !important;
}
.el-table th.is-leaf { .el-table th.is-leaf {
background: #f5f7fa; background: #f5f7fa;
} }
......
...@@ -128,7 +128,7 @@ limitations under the License. ...@@ -128,7 +128,7 @@ limitations under the License.
:prop="key" :prop="key"
:label="table.columnOptions[key].label.substring(3)" :label="table.columnOptions[key].label.substring(3)"
show-overflow-tooltip show-overflow-tooltip
min-width="150" min-width="120"
sortable="custom"> sortable="custom">
<template slot="header" <template slot="header"
slot-scope="scope"> slot-scope="scope">
...@@ -152,7 +152,7 @@ limitations under the License. ...@@ -152,7 +152,7 @@ limitations under the License.
:prop="key" :prop="key"
:label="table.columnOptions[key].label.substring(3)" :label="table.columnOptions[key].label.substring(3)"
show-overflow-tooltip show-overflow-tooltip
min-width="150" min-width="120"
sortable="custom"> sortable="custom">
<template slot="header" <template slot="header"
slot-scope="scope"> slot-scope="scope">
...@@ -426,6 +426,26 @@ export default { ...@@ -426,6 +426,26 @@ export default {
metric: 'metric/', metric: 'metric/',
userDefined: 'user_defined/', userDefined: 'user_defined/',
}, },
valueType: {
int: 'int',
str: 'str',
mixed: 'mixed',
category: 'category',
model_size: 'model_size',
dataset_mark: 'dataset_mark',
},
valueName: {
userDefined: 'userDefined',
metric: 'metric',
UserDefined: 'UserDefined',
Metric: 'Metric',
},
labelValue: {
loss: 'loss',
batch_size: 'batch_size',
epoch: 'epoch',
learning_rate: 'learning_rate',
},
}; };
}, },
computed: {}, computed: {},
...@@ -479,8 +499,9 @@ export default { ...@@ -479,8 +499,9 @@ export default {
} }
this.addIconBorder(row); this.addIconBorder(row);
this.tagDialogShow = true; this.tagDialogShow = true;
const dialogHeight = 130;
document.getElementById('tag-dialog').style.top = document.getElementById('tag-dialog').style.top =
window.event.clientY - 130 + 'px'; window.event.clientY - dialogHeight + 'px';
}, },
/** /**
...@@ -790,7 +811,7 @@ export default { ...@@ -790,7 +811,7 @@ export default {
required: true, required: true,
}, },
loss: { loss: {
label: 'loss', label: this.labelValue.loss,
required: true, required: true,
}, },
network: { network: {
...@@ -814,11 +835,11 @@ export default { ...@@ -814,11 +835,11 @@ export default {
required: false, required: false,
}, },
epoch: { epoch: {
label: 'epoch', label: this.labelValue.epoch,
required: false, required: false,
}, },
batch_size: { batch_size: {
label: 'batch_size', label: this.labelValue.batch_size,
required: false, required: false,
}, },
device_num: { device_num: {
...@@ -909,9 +930,9 @@ export default { ...@@ -909,9 +930,9 @@ export default {
} else if (item.indexOf('user_defined/') === 0) { } else if (item.indexOf('user_defined/') === 0) {
userDefinedArray.push(item); userDefinedArray.push(item);
} else if ( } else if (
item === 'epoch' || item === this.labelValue.epoch ||
item === 'batch_size' || item === this.labelValue.batch_size ||
item === 'learning_rate' item === this.labelValue.learning_rate
) { ) {
hyperArray.push(item); hyperArray.push(item);
} else { } else {
...@@ -965,7 +986,9 @@ export default { ...@@ -965,7 +986,9 @@ export default {
.then( .then(
(res) => { (res) => {
if (res && res.data && res.data.object) { if (res && res.data && res.data.object) {
const list = this.setDataOfModel(res.data.object); const listTemp = this.setDataOfModel(res.data.object);
const list = JSON.parse(JSON.stringify(listTemp));
const tempEchartData = JSON.parse(JSON.stringify(listTemp));
if (allData) { if (allData) {
let customized = {}; let customized = {};
if (res.data.customized) { if (res.data.customized) {
...@@ -973,17 +996,17 @@ export default { ...@@ -973,17 +996,17 @@ export default {
const customizedKeys = Object.keys(customized); const customizedKeys = Object.keys(customized);
if (customizedKeys.length) { if (customizedKeys.length) {
customizedKeys.forEach((i) => { customizedKeys.forEach((i) => {
if (customized[i].type === 'int') { if (customized[i].type === this.valueType.int) {
this.keysOfIntValue.push(i); this.keysOfIntValue.push(i);
} else if (customized[i].type === 'str') { } else if (customized[i].type === this.valueType.str) {
this.keysOfStringValue.push(i); this.keysOfStringValue.push(i);
} else if (customized[i].type === 'mixed') { } else if (customized[i].type === this.valueType.mixed) {
// list of type mixed // list of type mixed
this.keysOfMixed.push(i); this.keysOfMixed.push(i);
this.keysOfStringValue.push(i); this.keysOfStringValue.push(i);
} }
if (i.startsWith(this.replaceStr.userDefined)) { if (i.startsWith(this.replaceStr.userDefined)) {
this.labelObj.userDefined = 'userDefined'; this.labelObj.userDefined = this.valueName.userDefined;
customized[i].label = customized[i].label.replace( customized[i].label = customized[i].label.replace(
this.replaceStr.userDefined, this.replaceStr.userDefined,
'[U]', '[U]',
...@@ -997,7 +1020,7 @@ export default { ...@@ -997,7 +1020,7 @@ export default {
this.replaceStr.metric, this.replaceStr.metric,
'[M]', '[M]',
); );
this.labelObj.metric = 'metric'; this.labelObj.metric = this.valueName.metric;
const metricObject = {value: '', label: ''}; const metricObject = {value: '', label: ''};
metricObject.value = customized[i].label; metricObject.value = customized[i].label;
metricObject.label = customized[i].label; metricObject.label = customized[i].label;
...@@ -1033,7 +1056,7 @@ export default { ...@@ -1033,7 +1056,7 @@ export default {
]; ];
if (this.labelObj.metric) { if (this.labelObj.metric) {
const metricTemp = { const metricTemp = {
label: 'Metric', label: this.valueName.Metric,
options: this.metricOptions, options: this.metricOptions,
}; };
this.checkOptions.push(metricTemp); this.checkOptions.push(metricTemp);
...@@ -1041,7 +1064,7 @@ export default { ...@@ -1041,7 +1064,7 @@ export default {
} }
if (this.labelObj.userDefined) { if (this.labelObj.userDefined) {
const userTemp = { const userTemp = {
label: 'UserDefined', label: this.valueName.UserDefined,
options: this.userOptions, options: this.userOptions,
}; };
this.checkOptions.push(userTemp); this.checkOptions.push(userTemp);
...@@ -1049,19 +1072,19 @@ export default { ...@@ -1049,19 +1072,19 @@ export default {
} }
Object.keys(this.table.columnOptions).forEach((item) => { Object.keys(this.table.columnOptions).forEach((item) => {
if ( if (
item !== 'epoch' && item !== this.labelValue.epoch &&
item !== 'learning_rate' && item !== this.labelValue.learning_rate &&
item !== 'batch_size' item !== this.labelValue.batch_size
) { ) {
const index = this.table.optionsNotInCheckbox.indexOf( const haveItem = this.table.optionsNotInCheckbox.includes(
item, item,
); );
if (index < 0) { if (!haveItem) {
const otherType = {value: '', label: ''}; const otherType = {value: '', label: ''};
otherType.value = this.table.columnOptions[item].label; otherType.value = this.table.columnOptions[item].label;
otherType.label = this.table.columnOptions[item].label; otherType.label = this.table.columnOptions[item].label;
if ( if (
otherType.value === 'loss' || otherType.value === this.labelValue.loss ||
otherType.value === otherType.value ===
this.$t('modelTraceback.network') || this.$t('modelTraceback.network') ||
otherType.value === otherType.value ===
...@@ -1119,7 +1142,6 @@ export default { ...@@ -1119,7 +1142,6 @@ export default {
this.noData = !res.data.object.length; this.noData = !res.data.object.length;
this.showEchartPic = !!res.data.object.length; this.showEchartPic = !!res.data.object.length;
if (this.hidenDirChecked.length) { if (this.hidenDirChecked.length) {
const tempEchartData = this.setDataOfModel(res.data.object);
this.hidenDirChecked.forEach((dir) => { this.hidenDirChecked.forEach((dir) => {
tempEchartData.forEach((item, index) => { tempEchartData.forEach((item, index) => {
if (item.summary_dir === dir) { if (item.summary_dir === dir) {
...@@ -1145,16 +1167,16 @@ export default { ...@@ -1145,16 +1167,16 @@ export default {
return val[i] || val[i] === 0; return val[i] || val[i] === 0;
}); });
if (!flag) { if (!flag) {
let index = this.table.optionsNotInCheckbox.indexOf(i); let haveItem = this.table.optionsNotInCheckbox.includes(i);
if (index >= 0) { if (haveItem) {
this.table.optionsNotInCheckbox.splice(index, 1); this.table.optionsNotInCheckbox.splice(index, 1);
} }
index = this.table.optionsNotInEchart.indexOf(i); haveItem = this.table.optionsNotInEchart.includes(i);
if (index >= 0) { if (haveItem) {
this.table.optionsNotInEchart.splice(index, 1); this.table.optionsNotInEchart.splice(index, 1);
} }
index = this.table.optionsNotInTable.indexOf(i); haveItem = this.table.optionsNotInTable.includes(i);
if (index >= 0) { if (haveItem) {
this.table.optionsNotInTable.splice(index, 1); this.table.optionsNotInTable.splice(index, 1);
} }
...@@ -1230,8 +1252,9 @@ export default { ...@@ -1230,8 +1252,9 @@ export default {
? item.added_info.tag ? item.added_info.tag
: 0; : 0;
const modelData = JSON.parse(JSON.stringify(item.model_lineage)); const modelData = JSON.parse(JSON.stringify(item.model_lineage));
const byteNum = 1024;
modelData.model_size = parseFloat( modelData.model_size = parseFloat(
((modelData.model_size || 0) / 1024 / 1024).toFixed(2), ((modelData.model_size || 0) / byteNum / byteNum).toFixed(2),
); );
const keys = Object.keys(modelData.metric || {}); const keys = Object.keys(modelData.metric || {});
if (keys.length) { if (keys.length) {
...@@ -1512,9 +1535,9 @@ export default { ...@@ -1512,9 +1535,9 @@ export default {
values[i[key].toString()] = i[key].toString(); values[i[key].toString()] = i[key].toString();
} }
}); });
obj.type = 'category'; obj.type = this.valueType.category;
obj.data = Object.keys(values); obj.data = Object.keys(values);
if (key === 'dataset_mark') { if (key === this.valueType.dataset_mark) {
obj.axisLabel = { obj.axisLabel = {
show: false, show: false,
}; };
...@@ -1612,15 +1635,15 @@ export default { ...@@ -1612,15 +1635,15 @@ export default {
if (this.echart.chart) { if (this.echart.chart) {
this.echart.chart.off('axisareaselected', null); this.echart.chart.off('axisareaselected', null);
window.removeEventListener('resize', this.resizeChart, false); window.removeEventListener('resize', this.resizeChart, false);
} else {
this.echart.chart = Echarts.init(document.querySelector('#echart'));
} }
this.echart.chart = Echarts.init(document.querySelector('#echart'));
this.echart.chart.setOption(echartOption, true); this.echart.chart.setOption(echartOption, true);
window.addEventListener('resize', this.resizeChart, false); window.addEventListener('resize', this.resizeChart, false);
this.chartEventsListen(parallelAxis);
// select use api },
chartEventsListen(parallelAxis) {
this.echart.chart.on('axisareaselected', (params) => { this.echart.chart.on('axisareaselected', (params) => {
// key of mixed item
this.recordsNumber = 0; this.recordsNumber = 0;
this.showNumber = 0; this.showNumber = 0;
const key = params.parallelAxisId; const key = params.parallelAxisId;
...@@ -1649,15 +1672,16 @@ export default { ...@@ -1649,15 +1672,16 @@ export default {
const [axisData] = parallelAxis.filter((i) => { const [axisData] = parallelAxis.filter((i) => {
return i.id === key; return i.id === key;
}); });
const lineLength = 2;
if (axisData && range.length === 2) { if (axisData && range.length === lineLength) {
if (axisData && axisData.id === 'model_size') { if (axisData && axisData.id === this.valueType.model_size) {
const byteNum = 1024;
range = [ range = [
parseInt(range[0] * 1024 * 1024, 0), parseInt(range[0] * byteNum * byteNum, 0),
parseInt(range[1] * 1024 * 1024, 0), parseInt(range[1] * byteNum * byteNum, 0),
]; ];
} }
if (axisData.type === 'category') { if (axisData.type === this.valueType.category) {
const rangeData = {}; const rangeData = {};
for (let i = range[0]; i <= range[1]; i++) { for (let i = range[0]; i <= range[1]; i++) {
rangeData[axisData.data[i]] = axisData.data[i]; rangeData[axisData.data[i]] = axisData.data[i];
...@@ -1720,11 +1744,11 @@ export default { ...@@ -1720,11 +1744,11 @@ export default {
]; ];
this.keysOfMixed = []; this.keysOfMixed = [];
customizedKeys.forEach((i) => { customizedKeys.forEach((i) => {
if (customized[i].type === 'int') { if (customized[i].type === this.valueType.int) {
this.keysOfIntValue.push(i); this.keysOfIntValue.push(i);
} else if (customized[i].type === 'str') { } else if (customized[i].type === this.valueType.str) {
this.keysOfStringValue.push(i); this.keysOfStringValue.push(i);
} else if (customized[i].type === 'mixed') { } else if (customized[i].type === this.valueType.mixed) {
// list of type mixed // list of type mixed
this.keysOfMixed.push(i); this.keysOfMixed.push(i);
this.keysOfStringValue.push(i); this.keysOfStringValue.push(i);
...@@ -1858,20 +1882,25 @@ export default { ...@@ -1858,20 +1882,25 @@ export default {
if (isNaN(value) || !value) { if (isNaN(value) || !value) {
return value; return value;
} else { } else {
if (key === 'learning_rate') { const numDigits = 4;
let temp = value.toPrecision(4); if (key === this.labelValue.learning_rate) {
let temp = value.toPrecision(numDigits);
let row = 0; let row = 0;
while (temp < 1) { while (temp < 1) {
temp = temp * 10; temp = temp * 10;
row += 1; row += 1;
} }
temp = this.toFixedFun(temp, 4); temp = this.toFixedFun(temp, numDigits);
return `${temp}${row ? `e-${row}` : ''}`; return `${temp}${row ? `e-${row}` : ''}`;
} else if (key === 'model_size') { } else if (key === this.valueType.model_size) {
return value + 'MB'; return value + 'MB';
} else { } else {
if (value < 1000) { const num = 1000;
return Math.round(value * Math.pow(10, 4)) / Math.pow(10, 4); if (value < num) {
return (
Math.round(value * Math.pow(10, numDigits)) /
Math.pow(10, numDigits)
);
} else { } else {
const reg = /(?=(\B)(\d{3})+$)/g; const reg = /(?=(\B)(\d{3})+$)/g;
return (value + '').replace(reg, ','); return (value + '').replace(reg, ',');
...@@ -1883,7 +1912,9 @@ export default { ...@@ -1883,7 +1912,9 @@ export default {
* Resizing Chart * Resizing Chart
*/ */
resizeChart() { resizeChart() {
this.echart.chart.resize(); if (this.echart && this.echart.chart) {
this.echart.chart.resize();
}
}, },
}, },
/** /**
...@@ -1927,11 +1958,6 @@ export default { ...@@ -1927,11 +1958,6 @@ export default {
.el-tag.el-tag--info .el-tag__close { .el-tag.el-tag--info .el-tag__close {
color: #fff; color: #fff;
} }
// select
.el-select > .el-input {
min-width: 280px !important;
max-width: 500px !important;
}
.select-inner-input { .select-inner-input {
width: calc(100% - 140px); width: calc(100% - 140px);
margin: 2px 4px; margin: 2px 4px;
...@@ -2091,7 +2117,11 @@ export default { ...@@ -2091,7 +2117,11 @@ export default {
-webkit-box-shadow: 0 1px 0 0 rgba(200, 200, 200, 0.5); -webkit-box-shadow: 0 1px 0 0 rgba(200, 200, 200, 0.5);
box-shadow: 0 1px 0 0 rgba(200, 200, 200, 0.5); box-shadow: 0 1px 0 0 rgba(200, 200, 200, 0.5);
overflow: hidden; overflow: hidden;
// select
.el-select > .el-input {
min-width: 180px !important;
max-width: 500px !important;
}
.top-area { .top-area {
margin: 24px 32px 12px; margin: 24px 32px 12px;
display: flex; display: flex;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册