CN-1479 fix: 搜索组件添加showHint自动完成提示

This commit is contained in:
刘洪洪
2023-12-07 10:03:31 +08:00
parent 5106f23c2b
commit 8cd3f87c3e
33 changed files with 8954 additions and 89 deletions

View File

@@ -1,37 +1,52 @@
<template>
<textarea
style="text-indent: 65px;"
cols="40"
ref="textSearch"
></textarea>
<div class="search__suffixes search__suffixes--text-mode" :class="showList ? '' : 'entity-explorer-home'" style="padding-left: 1px">
<span class="search__suffix">
<el-popover
popper-class="my-popper-class"
placement="top"
trigger="hover"
:content="$t('entity.switchToAdvancedSearch')"
>
<template #reference>
<i class="cn-icon cn-icon-filter" @click="changeMode"></i>
</template>
</el-popover>
</span>
<span v-show="isCloseIcon" class="search__suffix search__suffix-close" @click="cleanParams">
<i class="el-icon-error"></i>
</span>
<span class="search__suffix" @click="search">
<i class="el-icon-search"></i>
</span>
<div @click="handleClick" v-ele-click-outside="handleBlur">
<textarea
style="text-indent: 65px;"
cols="40"
ref="textSearch"
></textarea>
<div class="search__suffixes search__suffixes--text-mode" :class="showList ? '' : 'entity-explorer-home'" style="padding-left: 1px">
<span class="search__suffix">
<el-popover
popper-class="my-popper-class"
placement="top"
trigger="hover"
:content="$t('entity.switchToAdvancedSearch')"
>
<template #reference>
<i class="cn-icon cn-icon-filter" @click="changeMode"></i>
</template>
</el-popover>
</span>
<span v-show="isCloseIcon" class="search__suffix search__suffix-close" @click="cleanParams">
<i class="el-icon-error"></i>
</span>
<span class="search__suffix" @click.stop="search">
<i class="el-icon-search"></i>
</span>
</div>
<el-popover
placement="bottom"
width="100%"
ref="popoverRef"
:visible="hintVisible"
popper-class="search-show-hint-popover"
trigger="click">
<template #reference>
<div>
<hint v-if="hintVisible" :hintList="hintList"
@load="handleHintLoad"
@select="handleSelect"
:hintParams="hintParams"
:hintSearch="searchStr"></hint>
</div>
</template>
</el-popover>
</div>
</template>
<script>
import 'codemirror/theme/ambiance.css'
import 'codemirror/addon/hint/show-hint'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/display/placeholder'
import 'codemirror/mode/sql/sql'
import Parser, { stringInQuot, handleOperatorSpace } from '@/components/advancedSearch/meta/parser'
import CodeMirror from 'codemirror'
import { toRaw } from 'vue'
@@ -39,23 +54,62 @@ import _ from 'lodash'
import { columnType } from '@/components/advancedSearch/meta/meta'
import { handleErrorTip } from '@/components/advancedSearch/meta/error'
import { overwriteUrl, urlParamsHandler } from '@/utils/tools'
import Hint from '@/components/advancedSearch/showhint/Hint/Hint'
import { getDataset } from '@/components/advancedSearch/showhint/packages/getDataset'
import codeMirrorMixins from '@/components/advancedSearch/showhint/myCodeMirror.js'
export default {
name: 'TextMode',
mixins: [codeMirrorMixins],
props: {
columnList: Array,
str: String,
showList: Boolean,
showCloseIcon: Boolean
showCloseIcon: Boolean,
isShowHint: {
type: Boolean,
default: false
}
},
data () {
return {
codeMirror: null,
isCloseIcon: this.showCloseIcon,
isEdit: false
isEdit: false,
hintVisible: false,
dataset: null,
CodeMirror
}
},
emits: ['changeMode', 'search'],
created () {
if (this.isShowHint) {
this._initComponent()
}
},
provide () {
return {
getDataset: () => {
// provide() 写成方法之后,保证this的指向
return this.dataset || null
}
}
},
components: {
Hint
},
computed: {
searchStr () {
const { wholeTokenStr } = this.getWholeToken() || ''
if (['not in', 'not like', 'order by', 'group by'].includes(wholeTokenStr?.toLowerCase())) {
return wholeTokenStr
}
if (['operator', 'keyword', 'builtin'].includes(this.hintParams?.token?.type)) {
return this.hintParams?.token?.string
}
return this.hintParams.leftpart || this.hintSearch || ''
}
},
methods: {
cleanParams () {
toRaw(this.codeMirror).setValue('')
@@ -66,35 +120,50 @@ export default {
this.reloadUrl(routeQuery, 'cleanOldParams')
},
initCodeMirror () {
this.codeMirror = CodeMirror.fromTextArea(this.$refs.textSearch, {
mode: {
name: 'sql'
},
let option = {
mode: 'sql',
placeholder: '',
lineNumbers: false
})
}
if (this.isShowHint) {
option = {
keyMap: 'sublime',
tabSize: 2, // 缩进格式
theme: 'eclipse', // 主题,对应主题库 JS 需要提前引入
line: true,
lineNumbers: false, // 显示行数
indentUnit: 4, // 缩进单位为4
styleActiveLine: true, // 当前行背景高亮
mode: 'text/x-filter', // HMTL混合模式
foldGutter: true,
lint: true,
auto: 'auto', // 自动换行
autoCloseBrackets: true, // 自动闭合符号
matchBrackets: true, // 是否添加匹配括号高亮
spellcheck: true, // 启用拼写检查
autocorrect: true, // 启用自动更正
lineWrapping: true, // 滚动或换行以显示长行
// 提示配置
hintOptions: {
completeSingle: false, // 自动匹配唯一值
// 匹配 t_test_login.col_a 用. 来连接的
tables: {
filter_table: ['recv_time']
},
alignWithWord: false
}
}
}
this.codeMirror = CodeMirror.fromTextArea(this.$refs.textSearch, option)
this.codeMirror.setOption('extraKeys', {
Enter: (cm) => {}
})
this.codeMirror.on('focus', () => {
if (this.codeMirror.getValue().trim() !== '') {
this.isEdit = true
this.isCloseIcon = true
}
})
this.codeMirror.on('blur', () => {
const timer = setTimeout(() => {
this.isEdit = false
this.isCloseIcon = false
clearTimeout(timer)
}, 200)
})
this.codeMirror.on('update', () => {
this.isEdit = true
this.isCloseIcon = true
})
this.setCodemirrorValue()
this.initEvent()
this.initHint()
},
search () {
this.handleBlur()
const str = this.codeMirror.getValue().trim()
if (str) {
const parser = new Parser(this.columnList)
@@ -204,6 +273,174 @@ export default {
newUrl = urlParamsHandler(window.location.href, query, newParam, clean)
}
overwriteUrl(newUrl)
},
initEvent () {
this.codeMirror.on('focus', (coder) => {
if (this.codeMirror.getValue().trim() !== '') {
this.isEdit = true
this.isCloseIcon = true
}
if (this.isShowHint && this.$emit) {
this.$emit('focus', coder.getValue())
}
})
this.codeMirror.on('blur', (coder) => {
const timer = setTimeout(() => {
this.isEdit = false
this.isCloseIcon = false
if (this.isShowHint && this.$emit) {
this.$emit('blur', coder.getValue())
}
clearTimeout(timer)
}, 200)
})
this.codeMirror.on('update', () => {
this.isEdit = true
this.isCloseIcon = true
})
if (this.isShowHint) {
// 支持双向绑定
this.codeMirror.on('change', (coder) => {
if (this.$emit) {
this.$emit('input', coder.getValue())
}
})
this.codeMirror.on('startCompletion', () => {
// 展开自动提示的 事件回调
this.hintVisible = true
this.hintVm?.hintDeactive()
})
this.codeMirror.on('endCompletion', () => {
// 自动提示关闭
this.hintVisible = false
this.hintParams = {}
this.hintList = []
})
this.$emit('load', this.codeMirror)
}
},
_initComponent () {
getDataset(this, this.queryParams || {}).then((dataset, dataDisposeFun) => {
this.dataset = Object.freeze(dataset)
}).catch(err => {
console.error(err)
})
},
initHint () {
this.codeMirror.on('inputRead', () => {
setTimeout(() => {
this.codeMirror.showHint()
})
})
},
handleBlur () {
if (this.isShowHint) {
this.hintVisible = false
this.hintParams = {}
this.hintList = []
}
},
handleClick () {
if (this.isShowHint) {
this.hintVisible = true
this.codeMirror.showHint()
}
},
getWholeToken () {
// 获取 前一个token
const editor = this.hintParams.editor
const Pos = this.CodeMirror.Pos
const cur = this.hintParams.cur
const token = this.hintParams.token
if (!editor) {
return
}
const spaceToken = editor.getTokenAt(Pos(cur.line, token.start))
let preToken = ''
if (spaceToken && spaceToken?.string === ' ') {
preToken = editor.getTokenAt(Pos(cur.line, spaceToken.start))
}
const searchKey = `${preToken?.string} ${token?.string}`
return {
wholeTokenStr: searchKey,
spaceToken,
preToken,
token
}
},
handleHintLoad ({ vm }) {
this.hintVm = vm
},
handleSelect (item, index, hintList) {
if (index === 0) {
// 不可能选中0 第0项是标题, 选中0 说明没选中
this.hintParams?.editor?.closeHint()
this.$emit('query', CodeMirror)
return
}
const data = {
from: this.hintParams.from,
to: this.hintParams.to,
list: hintList
}
const { wholeTokenStr, preToken, token } = this.getWholeToken() || ''
let cur = null
cur = this.hintParams?.cur
// 上一个字段 是存在空格的关键字,整体删除上一个关键字
if (['not in', 'not like', 'order by', 'group by'].includes(wholeTokenStr?.toLowerCase())) {
this.hintParams?.editor?.replaceRange('', { line: cur.line, ch: preToken.start }, {
line: cur.line,
ch: token.end
})
}
this.completion && this.completion.pick(data, index)
},
setCodemirrorValue () {
// 如果地址栏包含参数q则将参数q回显到搜索栏内
let { q } = this.$route.query
if (this.str) {
toRaw(this.codeMirror).setValue(this.str)
}
if (q) {
if (q.indexOf('+') > -1) {
q = q.replace('+', ' ')
}
if (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1) {
q = decodeURI(q)
} else {
const str1 = q.substring(q.indexOf('%'), q.indexOf('%') + 3)
if (q.indexOf('%') > 0 && (str1 === '%20' || str1 === '%25')) {
q = decodeURI(q)
}
}
// 为避免地址栏任意输入导致全查询的q带QUERY解析时不识别导致的语法错误
// 如地址栏输入116.178.222.171此时的q很长刷新界面时需要把q里的116.178.222.171拿出来进行搜索
if (q.indexOf('QUERY') > -1) {
const strList = q.split(' ')
if (strList.length > 0) {
// 此时strList[1]为ip_addr:116.178.222.171获取116.178.222.171
q = strList[1].slice(8)
}
}
if (this.codeMirror) {
toRaw(this.codeMirror).setValue(q)
}
} else {
this.isCloseIcon = false
}
const vm = this
this.emitter.on('advanced-search', function () {
vm.search()
})
}
},
watch: {
@@ -227,42 +464,23 @@ export default {
}
},
mounted () {
// 如果地址栏包含参数q则将参数q回显到搜索栏内
let { q } = this.$route.query
this.initCodeMirror()
if (this.str) {
toRaw(this.codeMirror).setValue(this.str)
}
if (q) {
if (q.indexOf('+') > -1) {
q = q.replace('+', '')
}
if (q.indexOf('%') === 0 || q.indexOf('%20') > -1 || q.indexOf('%25') > -1) {
q = decodeURI(q)
} else {
const str1 = q.substring(q.indexOf('%'), q.indexOf('%') + 3)
if (q.indexOf('%') > 0 && (str1 !== '%20' || str1 === '%25')) {
q = decodeURI(q)
if (this.isShowHint) {
this.$nextTick(() => {
// dataset是避免数据未初始化完成注册失败ref是因为组件加载2次避免第二次时dom丢失导致数据挂载失败
if (this.dataset && this.$refs.textSearch) {
this.initShowHint()
this.initCodeMirror()
}
}
// 为避免地址栏任意输入导致全查询的q带QUERY解析时不识别导致的语法错误
// 如地址栏输入116.178.222.171此时的q很长刷新界面时需要把q里的116.178.222.171拿出来进行搜索
if (q.indexOf('QUERY') > -1) {
const strList = q.split(' ')
if (strList.length > 0) {
// 此时strList[1]为ip_addr:116.178.222.171获取116.178.222.171
q = strList[1].slice(8)
}
}
toRaw(this.codeMirror).setValue(q)
})
} else {
this.isCloseIcon = false
this.initCodeMirror()
}
const vm = this
this.emitter.on('advanced-search', function () {
vm.search()
})
}
}
</script>
<style>
.el-popper.search-show-hint-popover {
visibility: hidden !important;
}
</style>