提交 06b88ee5 编写于 作者: M mindspore-ci-bot 提交者: Gitee

!418 Model traceability code review problem modification, method split...

!418 Model traceability code review problem modification, method split optimization and string hard coding rectification
Merge pull request !418 from 秦君艳/master
...@@ -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.
先完成此消息的编辑!
想要评论请 注册