这里写自定义目录标题
这是一篇单纯记录项目踩坑及经验的文章
使用codeMirror
引入、定义、添加标签,获取标签、展示标签内容
具体引用和定义可以去 codeMirror 官网查看,这里主要想讲获取标签,因为项目有需求是展示预览,根据标签()括号内的内容展示,我一开始直接使用codeMirror的getAllMarks获取添加的标签来展示预览,但是过了很久才发现,预览顺序和编辑器内的标签的排序不对应。
具体是这样的,如果我正常挨个追加标签的话,getAllMarks获取的标签和编辑框内的标签顺序是一样的。但是,假如说我现已追加两个标签,再在他们中间追加一个,那么就不能对应啦,getAllMarks获取的最后一个标签将是追加在第二个位置的那个,而不是实际显示的顺序啦,我想了一个比较笨的方法,在追加标签的时候,添加了一个隐藏的span标签,再用getElementsByClass获取,这样获取到的是在页面中按顺序来的,与生成的先后顺序无关,如果你用更好的方法,请多指教哦
/**
* 插入标记
*/
addCodeMirrorTextMarker(startPos, endPos, markData, error, elementType) {
const self = this;
const msg = document.createElement('span');
const dataSpan = document.createElement('span');
// msg获取指定范围内的editorDoc的内容,指要标记的内容【elementType(elementPattern)】
msg.innerText = editorDoc.getRange(startPos, endPos);
// dataSpan的存在以确保预览元素顺序和编辑框内元素顺序一致
dataSpan.innerText = JSON.stringify(markData);
dataSpan.style.display = 'none';
dataSpan.className = 'inner-span-markData';
msg.appendChild(dataSpan);
if (error) {
msg.className = 'outer-span-mark-' + elementType + ' outer-span-mark-margin CodeMirror-lint-mark CodeMirror-lint-mark-error';
} else {
msg.className = 'outer-span-mark-' + elementType + ' outer-span-mark-margin CodeMirror-lint-mark';
}
// 判断追加得元素是否为第一个,不是第一个需要添加左边距
if (editorDoc.getAllMarks().length >= 1) {
msg.classList.add('outer-span-mark-margin-left');
}
msg.addEventListener('click', function (e) {
const elementStr = e.target.children[0].innerHTML;
const element = JSON.parse(elementStr);
self.checkedElement = element;
});
editorDoc.markText(startPos, endPos, {
className: msg.className,
inclusiveLeft: false,
inclusiveRight: false,
selectLeft: true,
selectRight: true,
atomic: false,
replacedWith: msg,
handleMouseEvents: true,
markData
});
},
获取标签:
/*
* 获取公式编辑器最后一个追加的元素
* 以getElementsByClassName获取的元素是在最后面的元素,
* 以editorDoc.getAllMarks获取的元素是最后追加的元素
* */
getLastElementByEditor() {
const self = this;
// 获取追加的 CodeMirror-mark
editorInstance.focus();
const markSpan = editorDoc.getAllMarks();
self.lastElementMatch(markSpan);
},
getLastElementByClass() {
const self = this;
editorInstance.focus();
const pos = editorDoc.getCursor();
const marks = editorDoc.findMarksAt(pos);
self.lastElementMatch(marks);
},
光标问题
在使用过程中可能出现光标不能好好定位这个事儿,一开始我做了一系列的操作来定位光标,都不能让光标在出现他应在的位置,真是不听话,放个五一假期回来后,居然用一个refresh就解决了,果然还是要放假,工作效率都提高了🤭
// 刷新编辑区,解决光标问题
editorInstance.refresh();
以下是我得一系列骚操作之后但没有解决问题的代码,仅为了警醒自己以后少走弯路,当然前提是好运。
elementChange(lastElement) {
const self = this;
// 如何将标记内容的改变再体现在markData上
// 获取光标位置
const pos = editorDoc.getCursor();
// 重新定位光标,获得元素开始位置
oldMarkData = mark[0].markData;
const oldLength = oldMarkData.replacedStr.length;
const startPos = { line: pos.line, ch: pos.ch - oldLength };
// 判断光标所在位置,当前元素的在左右位置
const oldPos = mark[0].lines[0].markedSpans[0].to;
let dev;
const newLength = addString.length;
if (oldLength < newLength) {
dev = newLength - oldLength;
editorDoc.findMarksAt(pos)[0].lines[0].markedSpans[0].to = oldPos + dev;
} else {
dev = oldLength - newLength;
editorDoc.findMarksAt(pos)[0].lines[0].markedSpans[0].to = oldPos - dev;
}
// 光标定位
const endPos = { line: startPos.line, ch: startPos.ch + newLength + 1 };
editorDoc.setCursor(endPos);
const newPos = editorDoc.getCursor();
CodeMirror.commands.goLineRight(editorInstance);
const marks = editorDoc.getAllMarks();
// editorInstance.focus();
如何设置文本域不能输入,却能删除(不是只读哦)
我也很好奇为什么会有这么无理的要求,不能输入,但能删除!!因为我所做的项目要求可以在外追加标签到公式编辑框内,用户可以删除所追加的标签,但不可以任意输入,所以虽然物理但也可以接受。我想的太简单了,以为添加一个readonly就完事儿了,殊不知加了readonly之后,所有的键盘按键都禁用了,那么既然关于键盘,我想是不是可以拦截一下键盘的按下事件,判断是那个键按下了呢,有了思路问题就可以迎刃而解啦。
每个按键都有他自己的编码keycode,了解这个之后还需要用到键盘事件keydown和keyup,在键盘按下(keydown)的时候拦截判断键盘码keycode,在放开(keyup)的时候决定允许通过还是拦截,代码如下:
let k;
editorInstance.on('keydown', (evt) => {
// 获取键盘码,delete back,left,right,up,down予以通过
k = window.event.keyCode;
if (k !== 8 && k !== 46 && (k < 37 || k > 40)) {
// 不允许此次键入
markWhenKeyup = false;
// 阻止默认事件
window.event.preventDefault();
} else {
markWhenKeyup = true;
window.event.returnValue = true;
}
});
editorInstance.on('keyup', (evt) => {
if (markWhenKeyup && (k === 8 || k === 46 || (k > 36 && k < 41))) {
// 允许删除
markWhenKeyup = false;
}
});
注意:其中最重要的一行是window.event.preventDefalt(),我一开始没加这句导致任意按键都可以通过,但是当我打开F12加debugger走他又可以拦截,关闭F12又不可以拦截,可把我愁得,查询资料后发现只需要加上这一句,哈哈哈代码就是如此,另外还要注意是否需要加window,因为当时一个我学长和我一起解决得这个问题,可能有些情况下不用加。
最终效果如下:
$refs未定义问题
这个问题我在前一篇文章中有讲解,查看上一篇
将输入框的原生事件组合到一起
此次项目中对输入框有不同的要求,要求只能输入数字或者只能输入数字和英文,在输入的同时需要将输入的内容展示到公式编辑框中,这些需求都指向input事件,我一开始将过滤输入内容(1)和实时展示(2)分开两个方法写,但是在同一个输入框中又不能出现两个@input,这个确实为难到我了,后来在同事的帮助下,使用$listeners,想了解跟更多看 vue的listeners属性
这里是我的使用情况,你可以将其他原生事件也组合到这儿
inputListeners: function () {
const self = this;
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 从父级添加所有的监听器
this.$listeners,
// 添加自定义监听器,
{
input: function (e) {
if (self.checkedElement.elementType === 'variable') self.checkedElement.variablePattern = e.replace(/[\u4e00-\u9fa5/\s+/]|[^a-zA-Z0-9\u4E00-\u9FA5]/g, '');
else self.checkedElement.elementPattern = e.replace(/[\u4e00-\u9fa5/\s+/]|[^a-zA-Z0-9\u4E00-\u9FA5]/g, '');
self.elementChange();
}
}
{
blur: function (e) {
// blur事件多用于表单校验
}
}
);
},
html代码片段
<!-- 使用οnkeyup="value=value.replace(/[^\w\.\/]/ig,'')"有缺陷 -->
<el-input
class="full-width search-select"
type="text"
v-on="inputListeners"
placeholder="请输入"
v-model="checkedElement.elementPattern"
:maxlength="checkedElement.limitLength"
show-word-limit
/>
</el-form-item>
其实后来我觉得我其实也可以在一个input事件里面完成的,在过滤方法的最后加一个方法调用就行啊!我当时脑子短路了吧·······
这里有提到onkeyup会有缺点,具体可见这篇分享,好吧我找不到那篇分享了,那我自己说一下吧,可看下面两图
注意看,当我在中文输入下敲了“我是大哥”的拼音,我保证我只敲了一遍哈,而输入框内已经出现了三遍,并且回车后长度已经超过了限制。
综上,使用onkeyup的缺点显而易见了,不必赘述,建议大家还是使用input事件。
一点点心得
最后最后,这次使用codemirror的心得是,因为用到额是一个我并不熟悉的东西,所以写代码的时候总是胆怯,不敢写,生怕出错,但是但是我想告诉自己,你就大胆写,勇敢的写,出错就出错嘛,出错就回退,没错就是发掘新的知识,还有,对于不熟悉的领域,多看文档嘛,多看代码。还有就是,这次的项目要求真的是太严格了,对于有原型的项目,样式严格按照他的要求来,一点细节都不能放过,我看你以后还怎么挑我的样式问题。
还有,感谢我得同事,我的上级吧,给我提出各种问题,其中有我坦然接受的,还有我觉得是无理的,不管怎样,在他的折磨下,我这次真的成长了很多,也学到了很多。