linkis-web/src/components/consoleComponent/result.vue (734 lines of code) (raw):

<!-- ~ Licensed to the Apache Software Foundation (ASF) under one or more ~ contributor license agreements. See the NOTICE file distributed with ~ this work for additional information regarding copyright ownership. ~ The ASF licenses this file to You under the Apache License, Version 2.0 ~ (the "License"); you may not use this file except in compliance with ~ the License. You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. --> <template> <div ref="view" class="we-result-view"> <we-toolbar ref="toolbar" v-show="result.path" :current-path="result.path" :all-path="result.allPath" :show-filter="tableData.type !== 'normal'" :script="script" :row="hightLightRow" :visualShow="visualShow" :dispatch="dispatch" :getResultUrl="getResultUrl" :comData="comData" :result-type="resultType" @on-analysis="$emit('on-analysis', arguments[0])" @on-filter="handleFilterView" /> <we-filter-view v-if="isFilterViewShow" :head-rows="heads" :height="resultHeight" @on-check="filterList"></we-filter-view> <Spin v-show="isLoading" size="large" fix /> <div v-if="resultType === '1'" :style="{'height': resultHeight+'px'}" class="text-result-div"> <div v-if="result.bodyRows"> <!-- The data format is not uniform, first loop the external data, and then loop the internal(数据格式不统一,先循环外部数据,再循环内部 )--> <div v-for="(item,index) in result.bodyRows" :key="index"> <div v-for="(row, subindex) in item[0].split('\n')" :key="subindex">{{ row }}</div> </div> </div> <span v-else class="empty-text"></span> </div> <div v-else-if="resultType === '2' && tableData.type && visualShow=== 'table'" class="result-table-content" :class="{'table-box': tableData.type === 'normal'}"> <template v-if="tableData.type === 'normal' && !result.hugeData"> <wb-table border highlight-row outer-sort :tableHeight="resultHeight" :columns="data.headRows" :tableList="data.bodyRows" @on-current-change="onRowClick" @on-head-dblclick="copyLabel" @on-sort-change="sortChange" class="result-normal-table"> </wb-table> <we-water-mask v-if="isWaterMask" :text="watermaskText" ref="watermask"></we-water-mask> </template> <template v-else-if="!result.hugeData"> <we-table ref="resultTable" :data="data" :width="tableData.width" :height="resultHeight" :size="tableData.size" @dbl-click="copyLabel" @on-click="onWeTableRowClick" @on-sort-change="sortChange"/> <we-water-mask v-if="isWaterMask" :text="watermaskText" ref="watermask"></we-water-mask> </template> <div v-if="result.hugeData" :style="{height: resultHeight+'px', padding: '15px'}"> {{ $t('message.linkis.resultSet.prefixText') }}<a :href="`/#/results?resultPath=${resultPath}&fileName=${$route.query.fileName}`" target="_blank">{{ $t('message.linkis.resultSet.linkText') }}</a> </div> </div> <div v-else-if="['visual', 'dataWrangler'].includes(visualShow) && resultType === '2'"> <visualAnalysis v-if="visualShow === 'visual' && visualParams" :visualParams="visualParams" :style="{'height': (resultHeight + 40) +'px'}"> </visualAnalysis> <dataWrangler v-if="visualShow === 'dataWrangler' && dataWranglerParams" :dataWranglerParams="dataWranglerParams" :style="{'height': resultHeight +'px'}" > </dataWrangler> </div> <div v-else-if="resultType === '4'"> <iframe id="iframeName" :style="{'height': resultHeight+'px'}" :srcdoc="htmlData" frameborder="0" width="100%" @change="iframeOnChange()" /> </div> <div v-else-if="resultType === '5'" :style="{'height': resultHeight+'px'}" class="html-result-div" v-html="result.bodyRows[0][0]"/> <span v-else class="empty-text"></span> <div class="we-page-container" v-if="visualShow!== 'visual' && visualShow!== 'dataWrangler'"> <result-set-list v-if="script.resultList && script.resultList.length>1" :current="script.resultSet - 0" :list="script.resultList" @change="changeSet"> </result-set-list> <div class="page-hint"> <Page :transfer="true" v-if="resultType === '2'" ref="page" :total="tableData.total" :page-size-opts="page.sizeOpts" :page-size="page.size" :current="page.current" class-name="page" size="small" show-total show-sizer :prev-text="$t('message.linkis.previousPage')" :next-text="$t('message.linkis.nextPage')" @on-change="change" @on-page-size-change="changeSize" /> <span class="hint-text">{{result.total === 5000 ? $t('message.linkis.resultSet.largeResultTips') : ''}}</span> </div> </div> <we-menu ref="contextMenu" id="file"> <we-menu-item> <span>行转列</span> </we-menu-item> </we-menu> </div> </template> <script> import visualAnalysis from './visualAnalysis.vue'; import util from '@/common/util'; import Table from '@/components/virtualTable'; import WbTable from '@/components/table'; import WeWaterMask from '@/components/watermark'; import WeToolbar from './toolbar.vue'; import elementResizeEvent from '@/common/helper/elementResizeEvent'; import resultSetList from './resultSetList.vue'; import filter from './filter.vue'; import dataWrangler from './dataWrangler.vue'; import mixin from '@/common/service/mixin'; /** * Script execution result set tab panel(脚本执行结果集tab面板) * ! 1. Shared with the management console console.vue after the workflow node is executed(与工作流节点执行后的管理台console.vue 共用) */ export default { name: 'scriptResult', components: { WbTable, WeTable: Table.WeTable, WeToolbar, WeWaterMask, resultSetList, WeFilterView: filter, visualAnalysis, dataWrangler }, props: { script: [Object], visualShow: { type: String, default: 'table' }, visualParams: Object, scriptViewState: Object, dataWranglerParams: Object, dispatch: { type: Function, required: true }, getResultUrl: { type: String, defalut: `filesystem` }, comData: { type: Object }, }, mixins: [mixin], data() { const username = this.getUserName(); return { isWaterMask: false, watermaskText: `${this.$t('message.common.watermaskText')}-${username}`, data: { headRows: [], bodyRows: [], }, heads: [], tableData: { type: 'normal', width: 1000, size: 100, total: 100, }, page: { current: 1, size: 50, sizeOpts: [20, 50, 80, 100], }, isLoading: false, // currently highlighted line(当前高亮的行) hightLightRow: null, isFilterViewShow: false }; }, computed: { result() { let res = { headRows: [], bodyRows: [], type: 'EMPTY', total: 0, path: '', cache: {}, allPath: [] }; if (this.script.resultList && this.script.resultSet !== undefined) { res = this.script.resultList[this.script.resultSet].result if(res) { res.allPath = this.script.resultList.map(result => result.path); } } if(!res && this.script.result){ res = this.script.result } return res; }, htmlData() { return this.result.bodyRows[0] && this.result.bodyRows[0][0] }, resultType() { return this.result.type; }, resultHeight() { return this.scriptViewState.bottomContentHeight }, resultPath() { let path = '' if (this.script.resultList && this.script.resultSet !== undefined) { path = this.script.resultList[this.script.resultSet].path } return path } }, watch: { '$route'(val) { if (val.path === '/home') { this.data.headRows = this.data.headRows.slice(); } }, script: { deep: true, handler: function() { this.updateData(); } } }, mounted() { elementResizeEvent.bind(this.$el, this.resize); this.updateData(); this.initPage(); }, beforeDestroy: function() { elementResizeEvent.unbind(this.$el); }, methods: { initPage() { if (this.result.current && this.result.size) { this.page = Object.assign({...this.page}, { current: this.result.current, size: this.result.size, }); } this.change(this.page.current); }, sortByCol(col) { let sortIndex this.data.headRows.map((head, index) => { if (head.content === col.content) { sortIndex = index } }) // 大于50列排序现将要排序的列和原始index保持(大于50列排序现将要排序的列和原始index保持) let sortColumnAll = this.originRows.map((row, index) => { return { originIndex: index, value: row[sortIndex] } }) // Sort the found columns(将找出的列排序) sortColumnAll = this.arraySortByName(sortColumnAll, col.columnType, 'value');// From small to large(从小到大) let newRow = []; sortColumnAll.map((item, index) => { newRow[index] = this.originRows[item.originIndex]; }) return newRow; }, sortChange({column, key, order, cb}) { let list = [] if (order === 'normal') {// restore original data(恢复原来数据) if (this.tableData.type === 'normal') { list = this.result.bodyRows.map((row) => { let newItem = {}; row.forEach((item, index) => { Object.assign(newItem, { [this.result.headRows[index].columnName]: item, }); }); return newItem; }); } else { list = this.result.bodyRows || []; } } else { if (this.tableData.type === 'normal') { list = this.arraySortByName(this.originRows, column.columnType, key);// 从小到大 } else { list = this.sortByCol(column) } if (order === 'asc') {// 升序 } else if (order === 'desc') {// 降序 list.reverse(); } } this.originRows = list; this.sortType = order; // The sorted data is stored(排序后的数据进行存储) const tmpResult = this.getResult(); // restore original fields(还原初始字段) tmpResult.sortBodyRows = this.originRows.map(items => { return Object.values(items); }); tmpResult.sortType = this.sortType; tmpResult.current = this.page.current; tmpResult.size = this.pagesize; const resultSet = this.script.resultSet || 0; this.$set(this.script.resultList[resultSet], 'result', tmpResult); this.dispatch('IndexedDB:updateResult', { tabId: this.script.id, resultSet: resultSet, showPanel: this.scriptViewState.showPanel, ...this.script.resultList, }); this.pageingData(); if (cb) { cb() } }, arraySortByName(list, valueType, key) { if (list === undefined || list === null) return []; list.sort((a, b) => { let strA = a[key]; let strB = b[key]; // Who is the illegal value and who is in front(谁为非法值谁在前面) if (strA === undefined || strA === null || strA === '' || strA === ' ' || strA === ' ' || strA === 'NULL') { return -1; } if (strB === undefined || strB === null || strB === '' || strB === ' ' || strB === ' ' || strB === 'NULL') { return 1; } // if integer size(如果为整数型大小) if (['int', 'float', 'double', 'long', 'short', 'bigint', 'decimal'].includes(valueType.toLowerCase())) { return strA - strB; } const charAry = strA.split(''); for (const i in charAry) { if ((this.charCompare(strA[i], strB[i]) !== 0)) { return this.charCompare(strA[i], strB[i]); } } // If you can't compare it through the above loop comparison, there is no solution, and return -1 directly(如果通过上面的循环对比还比不出来,就无解了,直接返回-1) return -1; }); return list; }, charCompare(charA, charB) { // Who is the illegal value and who is in front(谁为非法值谁在前面) if (charA === undefined || charA === null || charA === '' || charA === ' ' || charA === ' ') { return -1; } if (charB === undefined || charB === null || charB === '' || charB === ' ' || charB === ' ') { return 1; } return charA.localeCompare(charB); }, notChinese(char) { const charCode = char.charCodeAt(0); return charCode >= 0 && charCode <= 128; }, updateData() { /** * result.type * TEXT_TYPE: '1' * TABLE_TYPE: '2' * IO_TYPE: '3' * PICTURE_TYPE: '4' * HTML_TYPE: '5' */ if (this.result.type === '2') { this.tableData.type = this.result.headRows.length > 50 ? 'virtual' : 'normal'; let headRows = this.result.headRows || []; let heads = []; let bodyRows = []; // Determine whether to sort(判断是否进行过排序) switch(this.result.sortType) { case 'asc': case 'desc': this.originRows = this.result.sortBodyRows || []; this.sortType = this.result.sortType; break; default: this.originRows = this.result.bodyRows || []; break; } this.tableData.total = this.result.total; if (this.tableData.type === 'normal') { for (let item of headRows) { heads.push({ title: item.columnName, key: item.columnName, minWidth: 120, width: 120, maxWidth: 240, className: 'columnClass', sortable: 'custom', columnType: item.dataType, colHeadHoverTitle: item.columnName +'\n'+item.dataType, checked: true, // render: (h, params) => { // return h('span', { // attrs: { // title: params.row[params.column.title] // } // },params.row[params.column.title]) // }, // renderHeader: (h, params) => { // return h('span', { // attrs: { // title: item.dataType, // }, // on: { // dblclick: (e) => { // this.copyLabel({ // content: params.column.title, // }); // return false; // }, // }, // }, // params.column.title // ); // }, }); } this.originRows = this.originRows.map((row, i) => { let newItem = {}; row.forEach((item, index) => { try { Object.assign(newItem, { // After the result set data is transformed, it cannot be used directly as a key, and the table field must be used as the only key.(结果集数据改造后不能直接当key使用,得用表字段作为唯一的key) [headRows[index].columnName]: item, }); } catch (error) { window.console.error(error, row.length, i); } }); return newItem; }); } else { for (let i = 0; i < headRows.length; i++) { heads.push({ content: headRows[i].columnName, title: headRows[i].columnName, sortable: true, checked: true, columnType: headRows[i].dataType, colHeadHoverTitle: headRows[i].columnName +'\n'+headRows[i].dataType, }); } } this.heads = heads; this.data = { headRows: heads, bodyRows }; this.pageingData(); } }, pageingData() { if (this.originRows && this.originRows.length) { let { current, size, } = this.page; let maxLen = Math.min(this.tableData.total, current * size); let minLen = Math.max(0, (current - 1) * size); let newArr = this.originRows.slice(minLen, maxLen); this.data.bodyRows = newArr; } }, change(page = 1) { this.hightLightRow = null; this.page.current = page; // paging data for storage(分页后的数据进行存储) const tmpResult = this.getResult(); tmpResult.current = this.page.current; tmpResult.size = this.pagesize; const resultSet = this.script.resultSet || 0; if(this.script.resultList && this.script.resultList[resultSet]) this.$set(this.script.resultList[resultSet], 'result', tmpResult); this.dispatch('IndexedDB:updateResult', { tabId: this.script.id, resultSet: resultSet, showPanel: this.scriptViewState.showPanel, ...this.script.resultList, }); this.pageingData(); }, changeSize(size) { this.hightLightRow = null; this.page.size = size; this.page.current = 1; // paging data for storage(分页后的数据进行存储) const tmpResult = this.getResult(); tmpResult.current = this.page.current; tmpResult.size = this.pagesize; const resultSet = this.script.resultSet || 0; this.$set(this.script.resultList[resultSet], 'result', tmpResult); this.dispatch('IndexedDB:updateResult', { tabId: this.script.id, resultSet: resultSet, showPanel: this.scriptViewState.showPanel, ...this.script.resultList, }); this.pageingData(); }, resizeWaterMask() { if (this.$refs.watermask) { this.$refs.watermask.updateCanvas(this.watermaskText); } }, resize() { this.resizeWaterMask(); return false; }, changeSet(set) { this.isLoading = true; this.page.current = 1; this.hightLightRow = null; this.isFilterViewShow = false; this.$emit('on-set-change', { currentSet: set, }, () => { this.isLoading = false; }); }, copyLabel(head) { util.executeCopy(head.content || head.title); }, getResult() { const result = Object.assign(this.result, { cache: { }, current: this.page.current, size: this.page.size, }); return result; }, iframeOnChange() { this.$nextTick(() => { document.all.iframeName.contentWindow.location.reload(); }); }, onRowClick(currentRow) { // Find the current type by column name(通过列命名去查找当前的类型) const row = Object.values(currentRow); this.rowToColumnData(row); }, onWeTableRowClick(index) { const row = this.data.bodyRows[index]; this.rowToColumnData(row); }, rowToColumnData(row) { this.hightLightRow = [] this.data.headRows.forEach((subItem, index) => { const temObject = { columnName: subItem.title, dataType: subItem.columnType, value: row[index] }; this.hightLightRow.push(temObject); }) }, handleFilterView() { this.isFilterViewShow = !this.isFilterViewShow; }, filterList(field) { field.checked = !field.checked; let rows = []; let cols = []; let bodyRows = [] this.heads.forEach((it,index) => { if(it.checked) { rows.push({...it}); cols.push(index); } }); bodyRows = this.result.bodyRows.map(row=>{ return row.filter((it,index) => cols.indexOf(index) > -1) }) this.data.headRows = rows; this.originRows = bodyRows; this.pageingData(); } }, }; </script> <style lang="scss" scoped> @import '@/common/style/variables.scss'; .we-result-view { width: 100%; height: calc(100% - 55px); overflow: hidden; padding-left: $toolbarWidth; background: $body-background; .html-result-div { overflow-y: auto; } .result-table-content { height: calc(100% - 52px); .ivu-table-header { width: calc(100% - 8px); tr > th:nth-last-of-type(2) { border-right: none; } } .result-normal-table { border: none; border-right: $border-width-base $border-style-base $border-color-base; min-width: 100%; .ivu-table { min-width: 100%; .is-null { color: $error-color; font-style: italic; } } } } .text-result-div { overflow: auto; padding: 10px; } .empty-text { position: $relative; top: 20px; left: 20px; } .table-box { overflow: hidden; } .we-page-container { display: flex; width: 100%; height: 42px; text-align: center; margin-top: 10px; padding-left: 10px; .page-hint { display: inline-flex; margin: 0 auto; align-items: center; .page { display: inline-block; } .hint-text { display: inline-block; margin-left: 5px; } } .set { width: 90px; // padding-right: 20px; } } } .result-normal-table table { min-width: 100%; } .result-table-content .list-view-item div[title='NULL'], .result-table-content td[title='NULL'], .result-table-content span[title='NULL'] { color: #ed4014; } .result-normal-table .columnClass { height: 30px; line-height: 1.25; /deep/.ivu-table-cell { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; position: relative; overflow: hidden; .ivu-table-sort { position: absolute; top: 50%; right: 0; transform: translate(-50%,-50%); z-index: 90; i.on { color: #FFFFFF; } } } } </style>