<template> <div class="calcu-field"> <el-form ref="form" :model="fieldForm" class="de-form-item" > <el-form-item :label="$t('dataset.field_edit_name')"> <el-input v-model="fieldForm.name" size="small" :placeholder="$t('dataset.input_edit_name')" /> </el-form-item> </el-form> <div class="calcu-cont" :style="mode === 'normal' ? {height: '544px'} : {height: '100px'}" > <div style="flex: 1"> <el-row> <el-row v-show="mode === 'normal'" style="max-width: 480px;" > <span> {{ $t('dataset.field_exp') }} <el-tooltip class="item" effect="dark" placement="bottom" > <div slot="content"> {{ $t('dataset.calc_tips.tip1') }} <br> {{ $t('dataset.calc_tips.tip2') }} </div> <i class="el-icon-info" style="cursor: pointer;" /> </el-tooltip> </span> <codemirror ref="myCm" v-model="fieldForm.originName" class="codemirror" :options="cmOption" @ready="onCmReady" @focus="onCmFocus" @input="onCmCodeChange" /> </el-row> <el-row style="margin-top: 28px;"> <el-form ref="form" :model="fieldForm" class="de-form-item" > <el-form-item> <template slot="label"> {{ $t('dataset.data_type') }} <el-tooltip class="item" effect="dark" placement="bottom" > <div slot="content"> 若字段表达式中使用聚合函数,则字段不能设置为维度使用。 </div> <i class="el-icon-info" style="cursor: pointer;z-index: 5;position: relative" /> </el-tooltip> </template> <el-radio v-model="fieldForm.groupType" label="d" >{{ $t('chart.dimension') }}</el-radio> <el-radio v-model="fieldForm.groupType" label="q" >{{ $t('chart.quota') }}</el-radio> </el-form-item> <el-form-item :label="$t('dataset.field_type')"> <el-radio v-for="item in fields" :key="item.value" v-model="fieldForm.deType" :label="item.value" >{{ item.label }}</el-radio> </el-form-item> </el-form> </el-row> </el-row> </div> <div v-show="mode === 'normal'" class="padding-lr" > <span class="mb8"> {{ $t('dataset.click_ref_field') }} <el-tooltip class="item" effect="dark" placement="bottom" > <div slot="content"> {{ $t('dataset.calc_tips.tip3') }} <br> {{ $t('dataset.calc_tips.tip4') }} <br> {{ $t('dataset.calc_tips.tip5') }} </div> <i class="el-icon-info" style="cursor: pointer;" /> </el-tooltip> </span> <el-input v-model="searchField" size="small" :placeholder="$t('dataset.edit_search')" prefix-icon="el-icon-search" style="margin-bottom: 12px" clearable /> <div class="field-height"> <span>{{ $t('chart.dimension') }}</span> <draggable v-if="dimensionData && dimensionData.length > 0" v-model="dimensionData" :options="{group:{name: 'drag',pull:'clone'},sort: true}" animation="300" class="drag-list" :disabled="true" > <transition-group> <span v-for="item in dimensionData" :key="item.id" class="item-dimension" :title="item.name" @click="insertFieldToCodeMirror('['+item.name+']')" > <svg-icon v-if="item.deExtractType === 0" icon-class="field_text" class="field-icon-text" /> <svg-icon v-if="item.deExtractType === 1" icon-class="field_time" class="field-icon-time" /> <svg-icon v-if="item.deExtractType === 2 || item.deExtractType === 3" icon-class="field_value" class="field-icon-value" /> <svg-icon v-if="item.deExtractType === 5" icon-class="field_location" class="field-icon-location" /> {{ item.name }} </span> </transition-group> </draggable> <div v-else class="class-na" >{{ $t('dataset.na') }}</div> </div> <div class="field-height"> <span>{{ $t('chart.quota') }}</span> <draggable v-if="quotaData && quotaData.length > 0" v-model="quotaData" :options="{group:{name: 'drag',pull:'clone'},sort: true}" animation="300" class="drag-list" :disabled="true" > <transition-group> <span v-for="item in quotaData" :key="item.id" class="item-quota" :title="item.name" @click="insertFieldToCodeMirror('['+item.name+']')" > <svg-icon v-if="item.deExtractType === 0" icon-class="field_text" class="field-icon-text" /> <svg-icon v-if="item.deExtractType === 1" icon-class="field_time" class="field-icon-time" /> <svg-icon v-if="item.deExtractType === 2 || item.deExtractType === 3" icon-class="field_value" class="field-icon-value" /> <svg-icon v-if="item.deExtractType === 5" icon-class="field_location" class="field-icon-location" /> <span>{{ item.name }}</span> </span> </transition-group> </draggable> <div v-else class="class-na" >{{ $t('dataset.na') }}</div> </div> </div> <div class="padding-lr" > <span class="mb8"> {{ $t('dataset.click_ref_function') }} <el-tooltip class="item" effect="dark" placement="bottom" > <div slot="content"> {{ $t('dataset.calc_tips.tip6') }} <br> {{ $t('dataset.calc_tips.tip7') }} <br> {{ $t('dataset.calc_tips.tip8') }} https://doris.apache.org/zh-CN/ </div> <i class="el-icon-info" style="cursor: pointer;" /> </el-tooltip> </span> <el-input v-model="searchFunction" size="small" style="margin-bottom: 12px" :placeholder="$t('dataset.edit_search')" prefix-icon="el-icon-search" clearable /> <el-row class="function-height"> <div v-if="functionData && functionData.length > 0"> <el-popover v-for="(item,index) in functionData" :key="index" class="function-pop" placement="right" width="200" trigger="hover" :open-delay="500" > <p class="pop-title">{{ item.name }}</p> <p class="pop-info">{{ item.func }}</p> <p class="pop-info">{{ item.desc }}</p> <span slot="reference" class="function-style" @click="insertParamToCodeMirror(item.func)" >{{ item.func }}</span> </el-popover> </div> <div v-else class="class-na" >{{ $t('chart.no_function') }}</div> </el-row> </div> </div> <div class="de-foot"> <deBtn secondary @click="closeCalcField" >{{ $t('dataset.cancel') }}</deBtn> <deBtn :disabled="!fieldForm.name || !fieldForm.originName" type="primary" :loading="loading" @click="saveCalcField" >{{ $t('dataset.confirm') }} </deBtn> </div> </div> </template> <script> import draggable from 'vuedraggable' import { codemirror } from 'vue-codemirror' // 核心样式 import 'codemirror/lib/codemirror.css' // 引入主题后还需要在 options 中指定主题才会生效 import 'codemirror/theme/solarized.css' import 'codemirror/mode/sql/sql.js' // require active-line.js import 'codemirror/addon/selection/active-line.js' // closebrackets import 'codemirror/addon/edit/closebrackets.js' // keyMap import 'codemirror/mode/clike/clike.js' import 'codemirror/addon/edit/matchbrackets.js' import 'codemirror/addon/comment/comment.js' import 'codemirror/addon/dialog/dialog.js' import 'codemirror/addon/dialog/dialog.css' import 'codemirror/addon/search/searchcursor.js' import 'codemirror/addon/search/search.js' import 'codemirror/keymap/emacs.js' // 引入代码自动提示插件 import 'codemirror/addon/hint/show-hint.css' import 'codemirror/addon/hint/sql-hint' import 'codemirror/addon/hint/show-hint' import { post } from '../../../api/dataset/dataset' export default { name: 'CalcChartFieldEdit', components: { codemirror, draggable }, props: { param: { // chart type: Object, required: true }, field: { type: Object, required: true }, mode: { type: String, required: false, default: 'normal' } }, data() { return { fieldForm: { id: null, name: '', groupType: 'd', deType: 0, originName: '', tableId: this.param.tableId, checked: 1, columnIndex: 0, size: 0, extField: 2, chartId: this.param.id }, cmOption: { tabSize: 2, styleActiveLine: true, lineNumbers: true, line: true, mode: 'text/x-sql', theme: 'solarized', hintOptions: { // 自定义提示选项 completeSingle: false // 当匹配只有一项的时候是否自动补全 } }, fields: [ { label: this.$t('dataset.text'), value: 0 }, { label: this.$t('dataset.time'), value: 1 }, { label: this.$t('dataset.value'), value: 2 }, { label: this.$t('dataset.value') + '(' + this.$t('dataset.float') + ')', value: 3 }, { label: this.$t('dataset.location'), value: 5 } ], functions: [], searchField: '', searchFunction: '', dimensionData: [], quotaData: [], functionData: [], tableFields: {}, name2Auto: [], loading: false } }, computed: { codemirror() { return this.$refs.myCm.codemirror }, panelInfo() { return this.$store.state.panel.panelInfo } }, watch: { 'param': function() { this.initFunctions() this.initDsFields() }, 'field': { handler: function() { this.initField() }, deep: true }, 'tableFields': function() { this.dimensionData = JSON.parse(JSON.stringify(this.tableFields.dimensionList)).filter(ele => ele.extField === 0) this.quotaData = JSON.parse(JSON.stringify(this.tableFields.quotaList)).filter(ele => ele.extField === 0) }, 'searchField': function(val) { if (val && val !== '') { this.dimensionData = JSON.parse(JSON.stringify(this.tableFields.dimensionList.filter(ele => ele.name.toLocaleLowerCase().includes(val.toLocaleLowerCase()) && ele.extField === 0))) this.quotaData = JSON.parse(JSON.stringify(this.tableFields.quotaList.filter(ele => ele.name.toLocaleLowerCase().includes(val.toLocaleLowerCase()) && ele.extField === 0))) } else { this.dimensionData = JSON.parse(JSON.stringify(this.tableFields.dimensionList)).filter(ele => ele.extField === 0) this.quotaData = JSON.parse(JSON.stringify(this.tableFields.quotaList)).filter(ele => ele.extField === 0) } }, 'searchFunction': function(val) { if (val && val !== '') { this.functionData = JSON.parse(JSON.stringify(this.functions.filter(ele => { return ele.func.toLocaleLowerCase().includes(val.toLocaleLowerCase()) }))) } else { this.functionData = JSON.parse(JSON.stringify(this.functions)) } } }, mounted() { this.loading = false this.$refs.myCm.codemirror.on('keypress', () => { this.$refs.myCm.codemirror.showHint() }) this.initFunctions() this.initDsFields() }, methods: { onCmReady(cm) { this.codemirror.setSize('-webkit-fill-available', 'auto') }, onCmFocus(cm) { }, onCmCodeChange(newCode) { this.fieldForm.originName = newCode }, insertParamToCodeMirror(param) { const pos1 = this.$refs.myCm.codemirror.getCursor() const pos2 = {} pos2.line = pos1.line pos2.ch = pos1.ch this.$refs.myCm.codemirror.replaceRange(param, pos2) }, insertFieldToCodeMirror(param) { const pos1 = this.$refs.myCm.codemirror.getCursor() const pos2 = {} pos2.line = pos1.line pos2.ch = pos1.ch this.$refs.myCm.codemirror.replaceRange(param, pos2) this.$refs.myCm.codemirror.markText(pos2, { line: pos2.line, ch: param.length + pos2.ch }, { atomic: true, selectRight: true }) }, initFunctions() { post('/dataset/function/listByTableId/' + this.param.tableId, null).then(response => { this.functions = response.data this.functionData = JSON.parse(JSON.stringify(this.functions)) }) }, initField() { if (this.field.id || this.mode === 'copy') { this.fieldForm = JSON.parse(JSON.stringify(this.field)) this.name2Auto = [] this.fieldForm.originName = this.setNameIdTrans('id', 'name', this.fieldForm.originName, this.name2Auto) setTimeout(() => { this.matchToAuto() }, 500) } else { this.fieldForm = JSON.parse(JSON.stringify(this.fieldForm)) } }, matchToAuto() { if (!this.name2Auto.length) return this.name2Auto.forEach(ele => { const search = this.$refs.myCm.codemirror.getSearchCursor(ele, { line: 0, ch: 0 }) if (search.find()) { const { from, to } = search.pos this.$refs.myCm.codemirror.markText({ line: from.line, ch: from.ch - 1 }, { line: to.line, ch: to.ch + 1 }, { atomic: true, selectRight: true }) } }) }, closeCalcField() { this.resetField() this.$emit('onEditClose', {}) }, setNameIdTrans(from, to, originName, name2Auto) { let name2Id = originName const nameIdMap = [...this.dimensionData, ...this.quotaData].reduce((pre, next) => { pre[next[from]] = next[to] return pre }, {}) const on = originName.match(/\[(.+?)\]/g) if (on) { on.forEach(itm => { const ele = itm.slice(1, -1) if (name2Auto) { name2Auto.push(nameIdMap[ele]) } name2Id = name2Id.replace(`[${ele}]`, `[${nameIdMap[ele]}]`) }) } return name2Id }, saveCalcField() { const { id, name = [], deType, originName } = this.fieldForm if (name.length > 50) { this.$message.error(this.$t('dataset.field_name_less_50')) return } if (!id) { this.fieldForm.type = deType this.fieldForm.deExtractType = deType this.fieldForm.tableId = this.param.tableId this.fieldForm.columnIndex = 0 this.fieldForm.chartId = this.param.id } this.loading = true post('/chart/field/save/' + this.panelInfo.id, { ...this.fieldForm, originName: this.setNameIdTrans('name', 'id', originName) }).then(response => { this.closeCalcField() this.loading = false }).catch(res => { this.loading = false }) }, resetField() { this.fieldForm = { id: null, name: '', groupType: 'd', deType: 0, originName: '', tableId: this.param.tableId, checked: 1, columnIndex: 0, size: 0, extField: 2, chartId: this.param.id } this.dimensionData = [] this.quotaData = [] this.searchField = '' this.searchFunction = '' }, initDsFields() { post('/dataset/field/listByDQ/' + this.param.tableId, null).then(response => { this.tableFields = response.data this.tableFields.dimensionListData = JSON.parse(JSON.stringify(this.tableFields.dimensionList)) this.tableFields.quotaListData = JSON.parse(JSON.stringify(this.tableFields.quotaList)) this.dimensionData = JSON.parse(JSON.stringify(this.tableFields.dimensionList)).filter(ele => ele.extField === 0) this.quotaData = JSON.parse(JSON.stringify(this.tableFields.quotaList)).filter(ele => ele.extField === 0) this.initField() }) } } } </script> <style scoped> .padding-lr { height: calc(100% - 80px); border: 1px solid var(--deCardStrokeColor, #dee0e3); border-radius: 4px; padding: 12px; box-sizing: border-box; margin-left: 12px; width: 214px; overflow-y: hidden; } .field-height { height: calc(50% - 41px); margin-top: 4px; } .drag-list { height: calc(100% - 26px); overflow: auto; } .item-dimension { padding: 3px 8px; margin: 2px 2px 0 2px; border: solid 1px #eee; text-align: left; color: #606266; /*background-color: rgba(35,46,64,.05);*/ background-color: white; display: block; word-break: break-all; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .blackTheme .item-dimension { border: solid 1px; border-color: #495865; color: #F2F6FC; background-color: var(--MainBG); } .item-dimension + .item-dimension { margin-top: 2px; } .item-dimension:hover { background: #e8f4ff; border-color: var(--primary, #3370ff); cursor: pointer; } .blackTheme .item-dimension:hover { /* color: var(--Main); */ background: var(--ContentBG); /* cursor: pointer; */ } .item-quota { padding: 3px 8px; margin: 2px 2px 0 2px; border: solid 1px #eee; text-align: left; color: #606266; /*background-color: rgba(35,46,64,.05);*/ background-color: white; display: block; word-break: break-all; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .blackTheme .item-quota { border: solid 1px; border-color: #495865; color: #F2F6FC; background-color: var(--MainBG); } .item-quota + .item-quota { margin-top: 2px; } .item-quota:hover { background: #f0f9eb; border-color: #b2d3a3; cursor: pointer; } .blackTheme .item-quota:hover { background: var(--ContentBG); } .function-style { color: var(--deTextPrimary, #1f2329); display: block; padding: 3px 8px; cursor: pointer; margin: 4px 0; word-break: break-word; border-radius: 2px; border: solid 1px #eee; } .blackTheme .function-style { border: solid 1px; border-color: #495865; color: #F2F6FC; background-color: var(--MainBG); } .function-style:hover { border-color: var(--primary, #3370ff); cursor: pointer; } .blackTheme .function-style:hover { color: #1890ff; background: var(--ContentBG); } .function-height { height: calc(100% - 81px); overflow: auto; margin-top: 4px; } .function-pop ::v-deep .el-popover { padding: 6px !important; } .pop-title { margin: 6px 0 0 0; font-size: 14px; font-weight: 500; } .pop-info { margin: 6px 0 0 0; font-size: 10px; } .class-na { margin-top: 8px; text-align: center; font-size: 14px; color: var(--deTextDisable); } </style>