项目上要用到类似于boss上填简历的工作经历的描述的文本框,文本框上要有选择无序列表、有序列表的功能,直接引入一个富文本编辑器感觉不划算,最终还是决定自己动手写一个:
以下是组件内代码
上次经测试有bug,主要在有序文本,反复测试修改后的应该是够用了的
<!-- 无序、有序文本框 -->
<template>
<div class="serial-selecter">
<div class="serial-toolbar">
<el-tooltip effect="dark" content="无序列表" placement="top">
<i class="iconfont icon-list" @click="changeType('li')"></i>
</el-tooltip>
<el-tooltip effect="dark" content="有序列表" placement="top">
<i class="iconfont icon-ol" @click="changeType('ol')"></i>
</el-tooltip>
</div>
<div class="ipt-box">
<el-input type="textarea" ref="txtatea" v-model="serialtxt" :maxlength="maxlength" show-word-limit :placeholder="placeholder" :disabled="disabled" @keyup.native.enter="enterHandle" @input="inputHandle" :autosize="{ minRows: 3, maxRows: 6}"></el-input>
</div>
</div>
</template>
<script>
export default {
props: {
value: { default: ''}, // 必须要使用value
maxlength: { default: 100 },
placeholder: { default: '请输入' },
disabled: { default: false },
},
data () {
return {
liststr: '●',
type: '',
serialtxt: '',
}
},
watch: {
value: {
immediate: true,
handler(val) {
this.serialtxt = val;
}
}
},
methods: {
changeType(type) {
if(this.disabled) return
this.type = type;
this.enterHandle();
},
enterHandle() {
// console.log(this.$refs.txtatea.$el.firstChild.selectionStart);
let mousePos = this.$refs.txtatea.$el.firstChild.selectionStart; // 光标职位
let txtBeforeAll = this.serialtxt.slice(0, mousePos); // 光标之前的所有文本
let txtAfterAll = this.serialtxt.slice(mousePos); // 光标之后的所有文本
let lastIndex = txtBeforeAll.lastIndexOf('\n'); // 文本最后一个换行符位置
let curtxt = txtBeforeAll.slice(lastIndex+1); // 文本最后一个换行符位置到光标之间的文本(光标当前行的文本)
if(this.type == 'li') {
if(txtBeforeAll.slice(lastIndex+1,lastIndex+2) == this.liststr) { // 如果已有排序,则是取消排序
if(txtBeforeAll.slice(lastIndex+2,lastIndex+3) == ' ') {
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + txtBeforeAll.slice(lastIndex+3) + txtAfterAll;
}else {
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + txtBeforeAll.slice(lastIndex+2) + txtAfterAll;
}
this.type = '';
} else { // 否则,添加排序
// 如果已存在有序排序,则替换为无序
if((parseInt(curtxt) > 0) && (txtBeforeAll.slice(lastIndex+parseInt(curtxt).toString().length+1,lastIndex+parseInt(curtxt).toString().length+2) == '.')) { // 如果已有排序,则是取消排序
let numLength = parseInt(curtxt).toString().length;
if(txtBeforeAll.slice(lastIndex+numLength+2,lastIndex+numLength+3) == ' ') {
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + this.liststr+' ' + txtBeforeAll.slice(lastIndex+numLength+3) + txtAfterAll;
}else {
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + this.liststr+' ' + txtBeforeAll.slice(lastIndex+numLength+2) + txtAfterAll;
}
} else {
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + this.liststr+' ' + txtBeforeAll.slice(lastIndex+1) + txtAfterAll;
}
}
}else if (this.type == 'ol') { // 有序
if((parseInt(curtxt) > 0) && (txtBeforeAll.slice(lastIndex+parseInt(curtxt).toString().length+1,lastIndex+parseInt(curtxt).toString().length+2) == '.')) { // 如果已有排序,则是取消排序
// console.log(parseInt(curtxt).toString());
let numLength = parseInt(curtxt).toString().length;
if(txtBeforeAll.slice(lastIndex+numLength+2,lastIndex+numLength+3) == ' ') {
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + txtBeforeAll.slice(lastIndex+numLength+3) + txtAfterAll;
}else {
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + txtBeforeAll.slice(lastIndex+numLength+2) + txtAfterAll;
}
this.type = '';
} else { // 否则,添加排序
// 如果已存在无序排序,则替换为有序
if(txtBeforeAll.slice(lastIndex+1,lastIndex+2) == this.liststr) {
if(txtBeforeAll.slice(lastIndex+2,lastIndex+3) == ' ') {
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + '1.'+' ' + txtBeforeAll.slice(lastIndex+3) + txtAfterAll;
}else {
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + '1.'+' ' + txtBeforeAll.slice(lastIndex+2) + txtAfterAll;
}
}else {
// 根据上一行数字得出本行排序数字
let seIndex = txtBeforeAll.slice(0, lastIndex).lastIndexOf('\n') // 上一个换行符出现的位置
if(seIndex < 0 && !(parseInt(txtBeforeAll.slice(seIndex+1,seIndex+2)) > 0 && (txtBeforeAll.slice(seIndex+2,seIndex+3) == '.'))) { // 上一行未出现换行符且第一个未出现数字,则是第一个
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + '1.'+' ' + txtBeforeAll.slice(lastIndex+1) + txtAfterAll;
} else {
// 获取第一出现‘.’的位置
let dotIndex = txtBeforeAll.slice(seIndex).indexOf('.');
// console.log(dotIndex);
this.serialtxt = txtBeforeAll.slice(0, lastIndex+1) + (parseInt(txtBeforeAll.slice(seIndex+1,seIndex+dotIndex))+1)+'. ' + txtBeforeAll.slice(lastIndex+1) + txtAfterAll;
}
}
}
}
this.$emit('input', this.serialtxt);
},
inputHandle() {
this.$emit('input', this.serialtxt);
}
}
}
</script>
<style lang='less' scoped>
.serial-selecter {
.serial-toolbar {
height: 39px;
border: 1px solid @color-border;
border-bottom: none;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
background-color: #f8f9fb;
padding-left: 20px;
.iconfont {
margin: 0 5px;
font-size: 16px;
cursor: pointer;
}
}
.ipt-box {
/deep/ .el-textarea__inner{
border-top-right-radius: 0;
border-top-left-radius: 0;
}
}
}
</style>
引用
由于实现了组件的双向绑定,可以直接在父组件这样用
<serial-text v-model="postInfo.description" :placeholder="'请输入不少于15个字的职位描述;\n您可以选择输入岗位职责、任职要求、岗位发展规划、晋升通道等岗位相关内容;\n同时应避免发布违法违规、含有歧视性、虚假、夸张以及不文明的信息。'" :maxlength="5000"></serial-text>