vue 编辑器+使用场景+问题解决
vue 编辑器组件
-
添加依赖
"dependencies": { "@codemirror/autocomplete": "^6.4.2", "@codemirror/commands": "^6.2.1", "@codemirror/lang-javascript": "^6.0.2", "@codemirror/lang-sql": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.2.0", "@codemirror/search": "^6.2.3", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.11.2", "codemirror": "^5.65.15" },
-
导入组件:
const scCodeEditor = defineAsyncComponent(() => import("../codeEdit.vue")) export default { components: { scCodeEditor }, // ......
-
使用标签:
<sc-code-editor></sc-code-editor>
使用场景
<el-dialog v-model="showDatasetDialog" title="数据编辑">
<sc-code-editor v-model="editingData" :height="400"></sc-code-editor>
<div style="display: flex; justify-content: center; align-items: center;">
<span style="margin: auto">
<el-button type="primary" @click="saveDataset" size="default">确认</el-button>
<el-button @click="cancelDatasetEdit" size="default">取消</el-button>
</span>
</div>
</el-dialog>
-
是在下拉的标签
<el-collapse-item>
里v-for
循环显示多条数据中,内含一个触发弹窗的按钮:<el-form-item label="y轴数据:"> <el-button @click="editDataset(seriesItem)" size="default">编辑</el-button> </el-form-item>
-
编辑按钮
// 编辑数据 editDataset(seriesItem) { this.showDatasetDialog = true; // 打开弹窗 nextTick(() => { this.editingData = JSON.stringify(seriesItem.data); this.newSeriesItem = seriesItem }) },
-
保存按钮
// 保存的确定按钮 saveDataset() { this.newSeriesItem.data = JSON.parse(this.editingData); this.showDatasetDialog = false; this.editingData = ''; },
-
取消按钮
// 取消按钮 cancelDatasetEdit() { this.editingData = ''; this.showDatasetDialog = false; },
出现问题并解决
-
传到编辑器中的文本不对,不是想要的样式
- 看编辑器组件内 props 接收的是什么类型,JSON.stringify 返回的是 JSON 字符串,而 JSON.parse 返回的是一个对象,要在合适的地方恰当转换
this.editingData
在定义时置空不是 null,而应该是 “ ”
-
编辑器弹窗点击右上角的叉号关闭不是立刻关,更像是点一下叉号连着关了多个弹窗
- 这个问题可能与
v-model
绑定和v-for
循环结合使用时的共享状态有关,一个方法是尝试为每个弹窗使用不同的状态,而不是在循环中共享相同的showDatasetDialog
- 另一个简单的方式也有效,就是尽量把弹窗拿出来放到外层,不要放循环里
- 这个问题可能与
-
没加
nextTick()
的时候,打开编辑器后没有数据,点击一下弹窗内部获取焦点后才会显示数据:editDataset(seriesItem) { this.showDatasetDialog = true; this.editingData = JSON.stringify(seriesItem.data); this.newSeriesItem = seriesItem },
- 这个问题可能是由于在
editDataset()
方法中,弹窗被打开的时候,editingData
可能还没有被正确地设置,导致初始时是空白的(在 Vue 中,数据更新是异步的,因此在editDataset
中设置this.editingData
后,如果直接打开弹窗,可能会在弹窗中渲染时发现数据还没有更新),解决方案就是最开始展示的加上nextTick()
: - 使用
nextTick
的目的是等待当前数据更新完成后再执行代码块(可以理解为执行完nextTick
里的语句后才会执行此方法里nextTick
之外的语句),确保了editingData
已经包含了正确的数据,然后再打开弹窗,就不再出现空白的问题了
- 这个问题可能是由于在
第三方编辑器组件
-
codeEdit.vue:
<template> <div class="sc-code-editor" :style="{ 'height': _height }" style="z-index: 10000"> <textarea ref="textarea" v-model="contentValue"></textarea> </div> </template> <script> import { markRaw } from "vue"; //框架 import CodeMirror from "codemirror"; import "codemirror/lib/codemirror.css"; //主题 import "codemirror/theme/idea.css"; import "codemirror/theme/darcula.css"; //功能 import "codemirror/addon/selection/active-line"; //语言 import "codemirror/mode/javascript/javascript"; import "codemirror/mode/sql/sql"; export default { props: { modelValue: { type: String, default: "" }, mode: { type: String, default: "javascript" }, height: { type: [String, Number], default: 300 }, options: { type: Object, default: () => {} }, theme: { type: String, default: "idea" }, readOnly: { type: Boolean, default: false } }, data() { return { contentValue: this.modelValue, coder: null, opt: { theme: this.theme, //主题 styleActiveLine: true, //高亮当前行 lineNumbers: true, //行号 lineWrapping: false, //自动换行 tabSize: 4, //Tab缩进 indentUnit: 4, //缩进单位 indentWithTabs: true, //自动缩进 mode: this.mode, //语言 readOnly: this.readOnly, //只读 autoFormatOnLoad: true, ...this.options } }; }, computed: { _height() { return Number(this.height) ? Number(this.height) + "px" : this.height; } }, watch: { modelValue(val) { this.contentValue = val; if (val !== this.coder.getValue()) { this.coder.setValue(val); } } }, mounted() { this.$nextTick(() => { this.init(); }); //获取挂载的所有modes //console.log(CodeMirror.modes) }, methods: { init() { this.coder = markRaw(CodeMirror.fromTextArea(this.$refs.textarea, this.opt)); this.coder.on("change", coder => { this.contentValue = coder.getValue(); this.$emit("update:modelValue", this.contentValue); }); }, formatStrInJson(strValue) { return JSON.stringify(JSON.parse(strValue), null, 4); } } }; </script> <style scoped> .sc-code-editor { font-size: 14px; border: 1px solid #ddd; line-height: 150%; } .sc-code-editor:deep(.CodeMirror) { height: 100%; } </style>
-
小提一嘴:在 Vue 中,数据更新是异步的,即当你修改了 Vue 实例的数据时,Vue 并不会立即应用这个变化,而是将变化放入一个队列中,然后在一个事件循环周期内异步地执行这些变化,最终更新视图。这种机制可以提高性能,避免不必要的重复渲染。
- 在这种情况下,如果你在更新数据后立即访问这个数据,可能会得到之前的旧值,因为实际的数据更新是异步执行的。所以,上述如果在数据更新后立即打开弹窗,可能会在弹窗中渲染时发现数据还没有被正确地更新。
- 所以通过使用
nextTick
,你确保在其内部的回调(在其中更新this.editingData
和this.newSeriesItem
)将在下一个 DOM 更新周期之后执行。这确保了当你访问和设置this.editingData
时,DOM 已经以最新的数据更新,防止在对话框中看到空白数据的问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义