光标错乱的问题借鉴:Vue3中使用CodeMirror出现setValue后点击报错 - 简书
版本及其安装
"sql-formatter": "^4.0.2"
"codemirror": "^5.65.9"
npm install --save sql-formatter@4.0.2
npm install codemirror@5.65.2
在setup
函数中,我们通过ref
和reactive
函数创建响应式数据,其特点是,每次修改数据都会更新UI
界面,这样的问题是非常消耗性能
所以,如果我们有些时候,有些操作可以不需要每次修改数据都去更新UI
界面,那么我们可以通过vue3
提供的toRaw
方法来获取该数据被Proxy
包装前的原始数据,然后通过对原始数据进行修改,进而可以修改对应的代理对象内部数据。这是通过原始数据修改改变的,并不会触发 UI
界面更新
有的情况下,我们希望某个数据在以后的操作中永远都不会被追踪,vue3
提供了一个方法:markRaw
markRaw标记后,当后面将sqlEditor变成proxy对象时,发现调用change方法改变数据时,界面并不会再跟新了
s
<script>
// 核心样式
import 'codemirror/lib/codemirror.css'
// 引入主题后还需要在 options 中指定主题才会生效
import 'codemirror/addon/display/fullscreen.css' // 全屏显示编辑器
import 'codemirror/addon/display/fullscreen.js' // 全屏显示编辑器
import 'codemirror/theme/cobalt.css'
import 'codemirror/theme/eclipse.css'
import 'codemirror/theme/ayu-dark.css'
import 'codemirror/theme/idea.css'
import 'codemirror/theme/solarized.css'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/sql-hint.js'
import 'codemirror/addon/selection/active-line.js'
import 'codemirror/addon/edit/matchbrackets.js'
import 'codemirror/mode/sql/sql'
import CodeMirror from 'codemirror'
import { format } from 'sql-formatter'
sqlEditor.value = markRaw(CodeMirror.fromTextArea(proxy.$refs.codeMirror, {
mode: "text/x-sparksql", // spark sql模式
lineNumbers: options.lineNumbers, // 显示行号
styleActiveLine: options.styleActiveLine, // 激活当前行
theme: options.theme, // 主题
lineWrapping: options.lineWrapping, // 自动换行
matchBrackets: true, // 括号匹配
autoCloseBrackets: true,
line: true,
extraKeys: { // 触发提示按键
[options.extraKeys]: "autocomplete"
},
hintOptions: { // 自定义提示选项
completeSingle: false, // 当匹配只有一项的时候是否自动补全
tables: {} // 代码提示
},
}))
// setValue('SELECT')
editorEvents()
</script>
sqledit.vue
<template>
<div class="sqleditor">
<div v-show="runLoading" class="run-loadings">
<span class="el-icon-loading"></span>
正在执行
</div>
<div class="header-tools">
<ul class="tools-list">
<li class="tools-item" @click="onRunCode">
<i class="el-icon-video-play"></i>
运行
</li>
<li class="tools-item" @click="onStopRun">
<i class="el-icon-video-pause"></i>
停止
</li>
<li class="tools-item" @click="onCommitOrder">
<i class="el-icon-s-promotion"></i>
提交
</li>
<li class="tools-item" @click="onFormatSQL">
<i class="el-icon-document"></i>
格式化
</li>
</ul>
</div>
<textarea ref="codeMirror" class="sqleditor-top"></textarea>
<!-- <div class="sqleditor-bottom">
<div class="sqleditor-bottom-tablesresult" :style="{height: `${tablesHeight}px`}">
<el-tabs type="border-card" style="height: 99%">
<el-tab-pane label="信息">
<span v-if="runType === 0" class="el-icon-info" style="color: #909399;"></span>
<span v-if="runType === 1" class="el-icon-success" style="color: #67C23A;"></span>
<span v-if="runType === 2" class="el-icon-warning" style="color: #E6A23C;"></span>
<span v-if="runType === 3" class="el-icon-error" style="color: #F56C6C;"></span>
{{ runResult }}
<template v-if="runType === 3">
<div class="error-message"><b>message:</b>{{ errMsg.sqlMessage }}</div>
<div class="error-message"><b>errno:</b>{{ errMsg.errno }}</div>
<div class="error-message"><b>sql:</b>"{{ errMsg.sql }}"</div>
<div class="error-message"><b>code:</b>{{ errMsg.code }}</div>
<div class="error-message"><b>sqlState:</b>{{ errMsg.sqlState }}</div>
</template>
</el-tab-pane>
<el-tab-pane label="结果">
<el-table
:data="tableData"
border
:style="{width: '100%', height: `${tablesHeight - 80}px`}">
<el-table-column
v-for="item in columns"
:key="item.value"
:prop="item.value"
:label="item.label"
/>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</div> -->
</div>
</template>
<script setup>
// 核心样式
import 'codemirror/lib/codemirror.css'
// 引入主题后还需要在 options 中指定主题才会生效
import 'codemirror/addon/display/fullscreen.css' // 全屏显示编辑器
import 'codemirror/addon/display/fullscreen.js' // 全屏显示编辑器
import 'codemirror/theme/cobalt.css'
import 'codemirror/theme/eclipse.css'
import 'codemirror/theme/ayu-dark.css'
import 'codemirror/theme/idea.css'
import 'codemirror/theme/solarized.css'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/sql-hint.js'
import 'codemirror/addon/selection/active-line.js'
import 'codemirror/addon/edit/matchbrackets.js'
import 'codemirror/mode/sql/sql'
import CodeMirror from 'codemirror'
import { format } from 'sql-formatter'
const props = defineProps({
// value: {
// type: String,
// default: ''
// },
runLoading: {
type: Boolean,
default: false
},
czshow: {
type: Object,
default: ''
}
})
watch(() => props.czshow, val => {
if(val.value && val.type == '双击'){
console.log(val,'setValue')
setValue(val.value)
}
},{ deep: true, immediate: true })
const emit = defineEmits()
const { proxy } = getCurrentInstance();
const sqlEditor = ref(null)
const codes = ref('')
const options = reactive({
styleActiveLine: true,
lineNumbers: true,
lineWrapping: false,
extraKeys: 'Ctrl',
theme: 'ayu-dark',
})
onMounted(() => {
init()
setSize()
})
function init() {
sqlEditor.value = markRaw(CodeMirror.fromTextArea(proxy.$refs.codeMirror, {
mode: "text/x-sparksql", // spark sql模式
lineNumbers: options.lineNumbers, // 显示行号
styleActiveLine: options.styleActiveLine, // 激活当前行
theme: options.theme, // 主题
lineWrapping: options.lineWrapping, // 自动换行
matchBrackets: true, // 括号匹配
autoCloseBrackets: true,
line: true,
extraKeys: { // 触发提示按键
[options.extraKeys]: "autocomplete"
},
hintOptions: { // 自定义提示选项
completeSingle: false, // 当匹配只有一项的时候是否自动补全
tables: {} // 代码提示
},
}))
// setValue('SELECT')
editorEvents()
}
function setValue(sql) {
sqlEditor.value.setValue(sql)
}
function setSize(width = 'auto', height = '300px') {
sqlEditor.value.setSize(width,height)
}
function setHintOptions(tables) {
sqlEditor.value.options.hintOptions.tables = tables
}
function editorEvents() {
// 设置代码提示
sqlEditor.value.on('keyup', (cm, event) => {
if (event.keyCode >= 65 && event.keyCode <= 90){
cm.showHint();
}
//所有的字母和'$','{','.'在键按下之后都将触发自动完成
if (!cm.state.completionActive && ((event.keyCode >= 65 && event.keyCode <= 90 )
|| event.keyCode == 52 || event.keyCode == 219 || event.keyCode == 190)) {
CodeMirror.commands.autocomplete(cm, null, {completeSingle: false});
}
})
// 代码输入的双向绑定
sqlEditor.value.on('change', (editor) => {
// 这里要用多一个载体去获取值,不然会重复赋值卡顿
console.log(editor,'editor')
codes.value = editor.getValue()
if (emit) {
emit('input1', codes.value)
}
})
}
function onRunCode() {
// 运行代码
emit('run', sqlEditor.value.getValue())
}
function onStopRun() {
// 停止执行代码
emit('stop')
}
function onCommitOrder() {
// 提交命令
emit('commit', sqlEditor.value.getValue())
}
function onFormatSQL() {
// 格式化代码
if (sqlEditor.value.getValue().trim() === '') {
// this.$message.warning('请先编辑 SQL 命令!')
return
}
const sqlCode = sqlEditor.value.getValue()
console.log(sqlCode,'sqlCode')
sqlEditor.value.setValue(format(sqlCode))
}
</script>
<style lang="scss">
.sqleditor {
font-size: 18px;
width: 100%;
height: 100%;
position: relative;
&-top{
width: 100%;
height: 89%;
}
&-bottom{
width: 100%;
height: 40%;
&-tablesresult {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 5px 0 0 1px;
.el-tabs {
height: 100%;
.el-tab-pane {
font-size: 12px;
height: 100%;
.error-message {
margin: 8px 0px;
box-sizing: border-box;
padding-left: 14px;
}
}
.el-table {
// border: 1px solid red;
// height: 100px;
height: 84%;
overflow-y: scroll;
font-size: 12px;
}
.el-table::-webkit-scrollbar {
width: 5px;
}
.el-table::-webkit-scrollbar-thumb {
border-radius: 10px;
background: rgba(0, 0, 0, 0.2);
}
.el-tabs__item{
font-size: 12px;
}
}
.el-tabs__content{
border: none;
border-radius: 0px;
// margin: 1rem;
// height: 82%;
}
}
}
.header-tools {
height: 11%;
user-select: none;
.tools-list {
list-style: none;
padding: 0 0 0 20px;
margin: 0;
display: flex;
align-items: center;
height: 100%;
font-size: 12px;
color: #666;
.tools-item {
margin: 0px 10px;
cursor: pointer;
&:hover {
color: #999;
}
.el-icon-video-play {
color: #4BC451;
}
.el-icon-video-pause {
color: #F35353;
}
.el-icon-document-checked,.el-icon-s-promotion,.el-icon-document {
color: #4381E6;
}
}
}
}
// 加载状态
.run-loadings {
position: absolute;
bottom: 0;
width: 100%;
height: calc(100% - 40px);
z-index: 999;
background: rgba($color: #000000, $alpha: .3);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #462CF4;
font-size: 12px;
.el-icon-loading {
font-size: 22px;
margin-bottom: 10px;
color: #462CF4;
}
}
}
</style>
主页面
<template>
<div class="editprocess">
<div class="editprocess-contain">
<el-card style="height: 98%;">
<div style="display: flex;height: 100%;">
<div class="mapContainer">
<SqlEditor class="mapContainer-righttop"
ref="codemirrorEditor"
:run-loading="runLoading"
:czshow="czshow"
@input1="onEditorInput"
@run="onSQLRun"
@stop="onSQLStop"
@commit="onSQLCommit">
</SqlEditor>
<div class="mapContainer-rightbottom">
<div class="tablesresult">
<el-tabs type="border-card" style="height: 99%">
<el-tab-pane label="信息">
<span v-if="runType === 0" class="el-icon-info" style="color: #909399;"></span>
<span v-if="runType === 1" class="el-icon-success" style="color: #67C23A;"></span>
<span v-if="runType === 2" class="el-icon-warning" style="color: #E6A23C;"></span>
<span v-if="runType === 3" class="el-icon-error" style="color: #F56C6C;"></span>
{{ runResult }}
<template v-if="runType === 3">
<div class="error-message"><b>message:</b>{{ errMsg.sqlMessage }}</div>
<div class="error-message"><b>errno:</b>{{ errMsg.errno }}</div>
<div class="error-message"><b>sql:</b>"{{ errMsg.sql }}"</div>
<div class="error-message"><b>code:</b>{{ errMsg.code }}</div>
<div class="error-message"><b>sqlState:</b>{{ errMsg.sqlState }}</div>
</template>
</el-tab-pane>
<el-tab-pane label="结果">
<el-table
:data="tableData.arr"
border>
<el-table-column
v-for="item in columns"
:key="item.value"
:prop="item.value"
:label="item.label"
/>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</div>
</el-card>
</div>
<pointDialog :pointDialogVisual="pointDialogVisual"></pointDialog>
</div>
</template>
<script setup>
import Product from './SQLproduct'
import SqlEditor from './SqlEditor'
import pointDialog from './pointDialog.vue'
import { useRouter } from "vue-router"
const content = ref('')
const { proxy } = getCurrentInstance();
// sql
const tablesHeight = ref('150')
const code = ref('')
const czshow = reactive({
type: '',
value: '',
})
const runLoading = ref(false)
const runResult = ref('暂无数据')
const runType = ref(0)
const tableData = reactive({
arr:[]
})
const columns = reactive({
arr:[]
})
const errMsg = reactive({
sqlMessage: '',
errno: '',
sql: '',
code: '',
sqlState: ''
})
function onEditorInput(codes) {
// 编辑器输入事件
console.log(codes,'1111111')
code.value = codes
czshow.type = ''
}
function onSQLStop() {
// SQL编辑器停止运行事件
}
function onSQLCommit() {
// SQL编辑器提交事件
}
function onSQLRun() {
// SQL编辑器运行事件
executeSql()
}
function executeSql() {
// 执行SQL语句
console.log(code.value)
if (code.value.trim() === '') {
// this.$message.warning('请先编辑 SQL 命令!')
return
}
runLoading.value = true
tableData.arr = []
columns.arr = []
runResult.value = '执行中...'
runType.value = 0
// errMsg = {}
// const res = await this.sendSQL({ sql: code.value })
// const { type, result } = res
// if (code.value.startsWith('SELECT')) {
// if (type === '1') {
// runType.value = 1
// runResult.value = `成功查询 ${result.length} 条数据`
// if (result.length > 0) {
// tableData.arr = [...result]
// const obj = { ...tableData.arr[0] }
// for(let key in obj) {
// columns.arr.push({
// label: key,
// value: key
// })
// }
// }
// } else if (type === '2') {
// runType.value = 3
// runResult.value = '执行失败!'
// errMsg = {...result}
// }
// } else {
// if (type === '1') {
// runType.value = 1
// runResult.value = `执行成功!${result.affectedRows}行数据受影响`
// queryTables()
// } else if (type === '2') {
// runType.value = 3
// runResult.value = '执行失败!'
// errMsg = {...result}
// }
// }
runLoading.value = false
}
// product传值
function getdblclickData(val){
debugger
if(code.value){
code.value = code.value +' '+ val
}else{
code.value = val
}
czshow.value = code.value
czshow.type = '双击'
console.log(code.value,'code传值')
}
</script>
<style lang="scss">
.editprocess{
width: 100%;
height: 95%;
&-top{
display: flex;
justify-content: space-between;
.title{
font-size: 1.8rem;
margin-bottom: 2rem;
font-family: PingFang SC;
color: rgba(63, 63, 63, 0.6);
}
}
&-contain{
width: 100%;
height: 100%;
&-left{
width: 20%;
height: 100%;
&-toptitle{
text-align: center;
font-size: 20px;
font-family: PingFang SC;
font-weight: bold;
color: #3F3F3F;
}
&-product{
overflow: auto;
width: 100%;
height: 96%;
}
}
.mapContainer{
width: 80%;
height: 100%;
padding: 10px;
&-righttop{
width: 100%;
height: 50%;
}
&-rightbottom{
width: 100%;
height: 50%;
.tablesresult {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 5px 0 0 1px;
.el-tabs {
height: 100%;
.el-tab-pane {
font-size: 12px;
height: 100%;
.error-message {
margin: 8px 0px;
box-sizing: border-box;
padding-left: 14px;
}
}
.el-table {
// border: 1px solid red;
// height: 100px;
height: 84%;
overflow-y: scroll;
font-size: 12px;
}
.el-table::-webkit-scrollbar {
width: 5px;
}
.el-table::-webkit-scrollbar-thumb {
border-radius: 10px;
background: rgba(0, 0, 0, 0.2);
}
.el-tabs__item{
font-size: 12px;
}
}
.el-tabs__content{
border: none;
border-radius: 0px;
// margin: 1rem;
// height: 82%;
}
}
}
}
}
}
</style>
结果