You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

debugger-grid-table-simple.vue 21 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. <!--
  2. Copyright 2020 Huawei Technologies Co., Ltd.All Rights Reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. -->
  13. <template>
  14. <div class="cl-slickgrid-container">
  15. <div class="data-show-container">
  16. <div v-show="incorrectData"
  17. class="error-msg-container">
  18. {{$t('components.gridIncorrectDataError')}}
  19. </div>
  20. <div v-show="!incorrectData && requestError"
  21. class="error-msg-container">
  22. {{errorMsg}}
  23. </div>
  24. <div v-show="!fullData.length && updated && !incorrectData && !requestError"
  25. class="error-msg-container">
  26. {{$t('components.gridTableNoData')}}
  27. </div>
  28. <div :id="itemId"
  29. v-show="!!fullData.length && !incorrectData"
  30. class="grid-item"></div>
  31. </div>
  32. <div class="operate-container"
  33. v-if="showOperate && (fullData.length || requestError)">
  34. <div class="filter-container"
  35. v-if="showFilterInput"
  36. @keyup.enter="filterChange">
  37. <div class="filter-input-title">{{$t('components.dimsFilterInputTitle')}}
  38. <span :title="$t('components.dimsFilterInputTip')"
  39. class="el-icon-warning"></span>
  40. </div>
  41. <div v-for="(item, itemIndex) in filterArr"
  42. :key="itemIndex">
  43. <el-input class="filter-input long-input"
  44. :class="item.showError ? 'error-border' : ''"
  45. v-model="item.model"></el-input>
  46. <span class="input-behind"
  47. v-if="itemIndex === filterArr.length - 1">{{$t('symbols.slashes')}}</span>
  48. <span class="input-behind"
  49. v-else>{{$t('symbols.point')}}</span>
  50. </div>
  51. <el-button class="filter-check"
  52. size="mini"
  53. v-if="!!filterArr.length"
  54. @click="filterChange">
  55. <i class="el-icon-check"></i>
  56. </el-button>
  57. <span class="filter-incorrect-text"
  58. v-if="!filterCorrect">{{$t('components.inCorrectInput')}}</span>
  59. </div>
  60. <div class="shape-wrap">
  61. <span>{{ $t('tensors.dimension') }} {{ shape }}</span>
  62. </div>
  63. <div class="accuracy-container">
  64. {{$t('components.gridAccuracy')}}<el-select v-model="accuracy"
  65. class="select-item"
  66. @change="accuracyChange">
  67. <el-option v-for="item in accuracyArr"
  68. :key="item.label"
  69. :label="item.label"
  70. :value="item.value"></el-option>
  71. </el-select>
  72. </div>
  73. </div>
  74. </div>
  75. </template>
  76. <script>
  77. import 'slickgrid/css/smoothness/jquery-ui-1.11.3.custom.css';
  78. import 'slickgrid/slick.grid.css';
  79. import 'slickgrid/lib/jquery-3.1.0';
  80. import 'slickgrid/lib/jquery-ui-1.9.2';
  81. import 'slickgrid/lib/jquery.event.drag-2.3.0.js';
  82. import 'slickgrid/slick.core.js';
  83. import 'slickgrid/slick.dataview.js';
  84. import 'slickgrid/slick.grid.js';
  85. export default {
  86. props: {
  87. // Table data
  88. fullData: {
  89. type: Array,
  90. default() {
  91. return [];
  92. },
  93. },
  94. // Display operation Bar
  95. showOperate: {
  96. type: Boolean,
  97. default: true,
  98. },
  99. showFilterInput: {
  100. type: Boolean,
  101. default: true,
  102. },
  103. // Display full screen
  104. fullScreen: {
  105. type: Boolean,
  106. default: false,
  107. },
  108. gridType: {
  109. type: String,
  110. default: 'value',
  111. },
  112. },
  113. data() {
  114. return {
  115. itemId: '', // Dom id
  116. gridObj: null, // Slickgrid object
  117. columnsData: [], // Column information
  118. columnsLength: 0, // Column length
  119. filterArr: [], // Dimension selection array
  120. formateData: [], // Formatted data
  121. formateArr: [], // Formatted array
  122. statistics: {}, // Object contain maximun and minimun
  123. accuracy: 10, // Accuracy value
  124. incorrectData: false, // Wheather the dimension is correctly selected
  125. updated: false, // Updated
  126. scrollTop: false, // Wheather scroll to the top
  127. filterCorrect: true, // Wheather the dimension input is correct
  128. requestError: false, // Exceeded the specification
  129. errorMsg: '', // Error message
  130. viewResizeFlag: false, // Size reset flag
  131. // Accuray options
  132. accuracyArr: [
  133. {label: 0, value: 0},
  134. {label: 1, value: 1},
  135. {label: 2, value: 2},
  136. {label: 3, value: 3},
  137. {label: 4, value: 4},
  138. {label: 5, value: 5},
  139. {label: 6, value: 6},
  140. {label: 7, value: 7},
  141. {label: 8, value: 8},
  142. {label: 9, value: 9},
  143. {label: 10, value: 10},
  144. ],
  145. // Table configuration items
  146. optionObj: {
  147. enableColumnReorder: false,
  148. enableCellNavigation: true,
  149. frozenColumn: 0,
  150. frozenRow: 0,
  151. },
  152. gridTypeKeys: {
  153. value: 'value',
  154. compare: 'compare',
  155. },
  156. tableStartIndex: {
  157. rowStartIndex: 0,
  158. colStartIndex: 0,
  159. },
  160. shape: '',
  161. };
  162. },
  163. computed: {},
  164. watch: {
  165. showFilterInput(newValue, oldValue) {
  166. this.resizeView();
  167. },
  168. },
  169. mounted() {
  170. this.init();
  171. },
  172. methods: {
  173. /**
  174. * Initialize
  175. */
  176. init() {
  177. this.itemId = `${new Date().getTime()}` + `${this.$store.state.componentsCount}`;
  178. this.$store.commit('componentsNum');
  179. },
  180. /**
  181. * Initialize dimension selection
  182. * @param {Array} dimension Dimension array
  183. * @param {String} filterStr Dimension String
  184. */
  185. initializeFilterArr(dimension, filterStr) {
  186. this.filterCorrect = true;
  187. if (!filterStr) {
  188. this.filterArr = [];
  189. return;
  190. }
  191. const tempFilterArr = filterStr.slice(1, filterStr.length - 1).split(',');
  192. const tempArr = [];
  193. const multiDimsArr = [];
  194. for (let i = 0; i < tempFilterArr.length; i++) {
  195. tempArr.push({
  196. model: tempFilterArr[i],
  197. max: dimension[i] - 1,
  198. showError: false,
  199. });
  200. if (tempFilterArr[i].indexOf(':') !== -1) {
  201. const curFilterArr = tempFilterArr[i].split(':');
  202. if (curFilterArr[0]) {
  203. let startIndex = Number(curFilterArr[0]);
  204. startIndex = startIndex < 0 ? dimension[i] + startIndex : startIndex;
  205. multiDimsArr.push(startIndex);
  206. } else {
  207. multiDimsArr.push(0);
  208. }
  209. }
  210. }
  211. this.filterArr = tempArr;
  212. if (!multiDimsArr.length) {
  213. this.tableStartIndex = {
  214. rowStartIndex: 0,
  215. colStartIndex: 0,
  216. };
  217. } else if (multiDimsArr.length >= 2) {
  218. this.tableStartIndex = {
  219. rowStartIndex: multiDimsArr[0],
  220. colStartIndex: multiDimsArr[1],
  221. };
  222. } else {
  223. this.tableStartIndex = {
  224. rowStartIndex: 0,
  225. colStartIndex: multiDimsArr[0],
  226. };
  227. }
  228. },
  229. /**
  230. * Initialize column information
  231. */
  232. formateColumnsData() {
  233. this.columnsData = [
  234. {
  235. id: -1,
  236. name: ' ',
  237. field: -1,
  238. width: 100,
  239. headerCssClass: 'headerStyle',
  240. },
  241. ];
  242. const columnSample = this.formateData[0];
  243. if (columnSample) {
  244. columnSample.forEach((num, numIndex) => {
  245. const order = numIndex + this.tableStartIndex.colStartIndex;
  246. this.columnsData.push({
  247. id: order,
  248. name: order,
  249. field: order,
  250. width: 100,
  251. headerCssClass: 'headerStyle',
  252. formatter:
  253. this.gridType === this.gridTypeKeys.compare ? this.formateCompareColor : this.formateValueColor,
  254. });
  255. });
  256. } else {
  257. this.columnsData = [];
  258. }
  259. },
  260. /**
  261. * Setting the Background color of data
  262. * @param {Number} row
  263. * @param {Number} cell
  264. * @param {String} value,
  265. * @param {Object} columnDef
  266. * @param {Object} dataContext
  267. * @return {String}
  268. */
  269. formateValueColor(row, cell, value, columnDef, dataContext) {
  270. if (!cell || !value || isNaN(value) || value === Infinity || value === -Infinity) {
  271. return value;
  272. } else if (value < 0) {
  273. return `<span class="table-item-span" style="background:rgba(227, 125, 41, ${
  274. value / this.statistics.overall_min
  275. })">${value}</span>`;
  276. } else {
  277. return `<span class="table-item-span" style="background:rgba(0, 165, 167, ${
  278. value / this.statistics.overall_max
  279. })">${value}</span>`;
  280. }
  281. },
  282. /**
  283. * Setting the background color of data
  284. * @param {Number} row
  285. * @param {Number} cell
  286. * @param {String} value,
  287. * @param {Object} columnDef
  288. * @param {Object} dataContext
  289. * @return {String}
  290. */
  291. formateCompareColor(row, cell, value, columnDef, dataContext) {
  292. if (value instanceof Array && value.length >= 3) {
  293. const valueNum = value[2];
  294. if (!cell || !valueNum || isNaN(valueNum) || valueNum === Infinity || valueNum === -Infinity) {
  295. return `<span class="table-item-span" title="${value[0]}→${value[1]}">${valueNum}</span>`;
  296. } else if (valueNum < 0) {
  297. return `<span class="table-item-span" title="${value[0]}→${
  298. value[1]
  299. }" style="background:rgba(227, 125, 41, ${valueNum / this.statistics.overall_min})">${valueNum}</span>`;
  300. } else {
  301. return `<span class="table-item-span" title="${value[0]}→${value[1]}" style="background:rgba(0, 165, 167, ${
  302. valueNum / this.statistics.overall_max
  303. })">${valueNum}</span>`;
  304. }
  305. } else {
  306. return value;
  307. }
  308. },
  309. calcForArrDims(array) {
  310. let dims = 0;
  311. let curValue = array;
  312. while (curValue instanceof Array && dims <= 3) {
  313. dims++;
  314. curValue = curValue[0];
  315. }
  316. return dims;
  317. },
  318. /**
  319. * Convetring raw data into table data
  320. */
  321. formateGridArray() {
  322. const dims = this.calcForArrDims(this.fullData);
  323. if (dims > 3) {
  324. this.formateData = [[[]]];
  325. this.columnsData = [];
  326. } else if (dims === 0) {
  327. this.formateData = [[this.fullData]];
  328. } else if (dims === 1) {
  329. this.formateData = [this.fullData];
  330. } else {
  331. this.formateData = this.fullData;
  332. }
  333. if (this.gridType === this.gridTypeKeys.compare && dims === 2) {
  334. this.formateData = [this.formateData];
  335. }
  336. const tempArr = [];
  337. if (this.gridType === this.gridTypeKeys.compare) {
  338. this.formateData.forEach((outerData, outerIndex) => {
  339. const tempData = {
  340. '-1': outerIndex + this.tableStartIndex.rowStartIndex,
  341. };
  342. outerData.forEach((innerData, innerIndex) => {
  343. const innerOrder = innerIndex + this.tableStartIndex.colStartIndex;
  344. const tempArr = [];
  345. innerData.forEach((innerValue) => {
  346. if (isNaN(innerValue) || innerValue === 'Infinity' || innerValue === '-Infinity') {
  347. tempArr.push(innerValue);
  348. } else {
  349. tempArr.push(innerValue.toFixed(this.accuracy));
  350. }
  351. });
  352. tempData[innerOrder] = tempArr;
  353. });
  354. tempArr.push(tempData);
  355. });
  356. } else {
  357. this.formateData.forEach((outerData, outerIndex) => {
  358. const tempData = {
  359. '-1': outerIndex + this.tableStartIndex.rowStartIndex,
  360. };
  361. outerData.forEach((innerData, innerIndex) => {
  362. const innerOrder = innerIndex + this.tableStartIndex.colStartIndex;
  363. if (isNaN(innerData) || innerData === 'Infinity' || innerData === '-Infinity') {
  364. tempData[innerOrder] = innerData;
  365. } else {
  366. tempData[innerOrder] = innerData.toFixed(this.accuracy);
  367. }
  368. });
  369. tempArr.push(tempData);
  370. });
  371. }
  372. this.formateArr = tempArr;
  373. },
  374. /**
  375. * Update the table
  376. */
  377. updateGrid() {
  378. this.$nextTick(() => {
  379. if (!this.gridObj) {
  380. this.gridObj = new Slick.Grid(`#${this.itemId}`, this.formateArr, this.columnsData, this.optionObj);
  381. this.columnsLength = this.columnsData.length;
  382. }
  383. this.gridObj.setData(this.formateArr, this.scrollTop);
  384. this.scrollTop = false;
  385. const columnsLength = this.columnsData.length;
  386. if (this.columnsLength !== columnsLength || this.viewResizeFlag) {
  387. this.gridObj.setColumns(this.columnsData);
  388. this.columnsLength = columnsLength;
  389. this.viewResizeFlag = false;
  390. }
  391. this.gridObj.render();
  392. });
  393. },
  394. /**
  395. * Accuracy changed
  396. * @param {Number} value The value after changed
  397. */
  398. accuracyChange(value) {
  399. this.formateGridArray();
  400. if (!this.requestError && !this.incorrectData) {
  401. this.updateGrid();
  402. }
  403. },
  404. /**
  405. * Dimension selection changed
  406. */
  407. filterChange() {
  408. // filter condition
  409. let filterCorrect = true;
  410. let incorrectData = false;
  411. let limitCount = 2;
  412. const tempArr = [];
  413. this.filterArr.forEach((filter) => {
  414. let value = filter.model.trim();
  415. if (!isNaN(value)) {
  416. if (value < -(filter.max + 1) || value > filter.max || value === '' || value % 1) {
  417. filter.showError = true;
  418. filterCorrect = false;
  419. } else {
  420. filter.showError = false;
  421. value = Number(value);
  422. }
  423. } else if (value.indexOf(':') !== -1) {
  424. const tempResult = this.checkCombinatorialInput(filter);
  425. if (tempResult) {
  426. filter.showError = false;
  427. if (!limitCount) {
  428. incorrectData = true;
  429. } else {
  430. limitCount--;
  431. }
  432. } else {
  433. filter.showError = true;
  434. filterCorrect = false;
  435. }
  436. } else {
  437. filter.showError = true;
  438. filterCorrect = false;
  439. }
  440. tempArr.push(value);
  441. });
  442. this.filterCorrect = filterCorrect;
  443. if (incorrectData && filterCorrect) {
  444. this.incorrectData = true;
  445. return;
  446. } else {
  447. this.incorrectData = false;
  448. }
  449. if (filterCorrect) {
  450. this.viewResizeFlag = true;
  451. this.$emit('martixFilterChange', tempArr);
  452. }
  453. },
  454. /**
  455. * Check combinatorial input
  456. * @param {Object} filter Filter item
  457. * @return {Boolean} Verification result
  458. */
  459. checkCombinatorialInput(filter) {
  460. const value = filter.model.trim();
  461. const tempArr = value.split(':');
  462. const startValue = tempArr[0];
  463. const endValue = tempArr[1];
  464. const limitCount = 2;
  465. if (!!startValue && (isNaN(startValue) || startValue < -(filter.max + 1) || startValue > filter.max)) {
  466. return false;
  467. }
  468. if (
  469. !!endValue &&
  470. (isNaN(endValue) || endValue <= -(filter.max + 1) || endValue > filter.max + 1 || !Number(endValue))
  471. ) {
  472. return false;
  473. }
  474. if (tempArr.length > limitCount) {
  475. return false;
  476. } else if (!startValue && !endValue) {
  477. return true;
  478. } else if (!!startValue && !!endValue) {
  479. const sv = startValue < 0 ? filter.max + Number(startValue) + 1 : Number(startValue);
  480. const ev = endValue < 0 ? filter.max + Number(endValue) + 1 : Number(endValue);
  481. if (ev <= sv) {
  482. return false;
  483. } else {
  484. return true;
  485. }
  486. } else {
  487. return true;
  488. }
  489. },
  490. /**
  491. * Updating Table Data
  492. * @param {Boolean} newDataFlag Wheather data is updated
  493. * @param {Array} dimension Array of dimension
  494. * @param {Object} statistics Object contains maximun and minimun
  495. * @param {String} filterStr String of dimension selection
  496. */
  497. updateGridData(newDataFlag, dimension, statistics, filterStr) {
  498. this.shape = dimension;
  499. this.updated = true;
  500. this.requestError = false;
  501. this.$nextTick(() => {
  502. if (!this.fullData || !this.fullData.length) {
  503. return;
  504. }
  505. if (newDataFlag) {
  506. this.viewResizeFlag = true;
  507. this.initializeFilterArr(dimension, filterStr);
  508. this.scrollTop = true;
  509. } else if (!this.filterArr.length && dimension && filterStr) {
  510. this.initializeFilterArr(dimension, filterStr);
  511. }
  512. if (newDataFlag || this.statistics.overall_max === undefined) {
  513. this.statistics = statistics;
  514. }
  515. this.formateGridArray();
  516. this.formateColumnsData();
  517. if (!this.incorrectData) {
  518. this.updateGrid();
  519. }
  520. });
  521. },
  522. /**
  523. * Update the view Size
  524. */
  525. resizeView() {
  526. if (this.gridObj) {
  527. if (this.incorrectData || this.requestError) {
  528. this.viewResizeFlag = true;
  529. } else {
  530. this.$nextTick(() => {
  531. this.gridObj.resizeCanvas();
  532. this.gridObj.render();
  533. });
  534. }
  535. }
  536. },
  537. /**
  538. * Expand/Collapse in full screen
  539. */
  540. toggleFullScreen() {
  541. this.$emit('toggleFullScreen');
  542. },
  543. /**
  544. * Show error message
  545. * @param {String} errorMsg Error message
  546. * @param {Array} dimension Array of dimension
  547. * @param {String} filterStr String of dimension selection
  548. * @param {Boolean} isUpdate Whether to reset
  549. */
  550. showRequestErrorMessage(errorMsg, dimension, filterStr, isUpdate) {
  551. this.shape = dimension;
  552. this.errorMsg = errorMsg;
  553. if ((!this.filterArr.length && dimension && filterStr) || isUpdate) {
  554. this.initializeFilterArr(dimension, filterStr);
  555. }
  556. this.requestError = true;
  557. },
  558. },
  559. destroyed() {},
  560. };
  561. </script>
  562. <style lang="scss">
  563. .cl-slickgrid-container {
  564. width: 100%;
  565. height: 100%;
  566. display: flex;
  567. flex-direction: column;
  568. .data-show-container {
  569. width: 100%;
  570. flex: 1;
  571. .grid-item {
  572. width: 100%;
  573. height: 100%;
  574. ::-webkit-scrollbar-button {
  575. z-index: 200;
  576. width: 10px;
  577. height: 10px;
  578. background: #fff;
  579. cursor: pointer;
  580. }
  581. ::-webkit-scrollbar-button:horizontal:single-button:start {
  582. background-image: url('../assets/images/scroll-btn-left.png');
  583. background-position: center;
  584. }
  585. ::-webkit-scrollbar-button:horizontal:single-button:end {
  586. background-image: url('../assets/images/scroll-btn-right.png');
  587. background-position: center;
  588. }
  589. ::-webkit-scrollbar-button:vertical:single-button:start {
  590. background-image: url('../assets/images/scroll-btn-up.png');
  591. background-position: center;
  592. }
  593. ::-webkit-scrollbar-button:vertical:single-button:end {
  594. background-image: url('../assets/images/scroll-btn-down.png');
  595. background-position: center;
  596. }
  597. ::-webkit-scrollbar-thumb {
  598. background-color: #bac5cc;
  599. }
  600. ::-webkit-scrollbar {
  601. width: 10px;
  602. height: 10px;
  603. }
  604. }
  605. .error-msg-container {
  606. width: 100%;
  607. height: 100%;
  608. display: flex;
  609. align-items: center;
  610. justify-content: center;
  611. }
  612. }
  613. .info-show-container {
  614. width: 100%;
  615. }
  616. .operate-container {
  617. width: 100%;
  618. text-overflow: ellipsis;
  619. white-space: nowrap;
  620. overflow: hidden;
  621. z-index: 9;
  622. flex-wrap: wrap;
  623. .full-screen-icon {
  624. float: right;
  625. margin-left: 15px;
  626. height: 100%;
  627. line-height: 34px;
  628. cursor: pointer;
  629. :hover {
  630. color: #00a5a7;
  631. }
  632. }
  633. .active-color {
  634. color: #00a5a7;
  635. }
  636. .filter-container {
  637. float: left;
  638. flex-wrap: wrap;
  639. display: flex;
  640. .filter-input-title {
  641. line-height: 34px;
  642. margin-right: 10px;
  643. }
  644. .error-border {
  645. input {
  646. border-color: red;
  647. }
  648. }
  649. .filter-input {
  650. text-align: center;
  651. }
  652. .long-input {
  653. width: 120px;
  654. }
  655. .input-behind {
  656. padding: 0 5px;
  657. }
  658. .filter-incorrect-text {
  659. margin-left: 10px;
  660. line-height: 32px;
  661. color: red;
  662. }
  663. }
  664. .shape-wrap {
  665. float: left;
  666. line-height: 34px;
  667. margin-left: 10px;
  668. }
  669. .accuracy-container {
  670. float: right;
  671. .select-item {
  672. width: 65px;
  673. margin-left: 5px;
  674. }
  675. }
  676. }
  677. }
  678. .slick-cell,
  679. .slick-headerrow-column,
  680. .slick-footerrow-column {
  681. padding: 0;
  682. border-top: none;
  683. border-left: none;
  684. text-align: center;
  685. }
  686. .ui-widget-content {
  687. background: none;
  688. }
  689. .headerStyle {
  690. vertical-align: middle;
  691. text-align: center;
  692. }
  693. .filter-check {
  694. font-size: 18px;
  695. color: #00a5a7;
  696. cursor: pointer;
  697. }
  698. .table-item-span {
  699. display: block;
  700. width: 100%;
  701. height: 100%;
  702. text-align: center;
  703. }
  704. </style>