16_模板字符替换
实现效果
- 选择对应模板,文案会自动填充到多行文本框,且可替换占位字符会高亮
- 在替换编辑里,编辑替换占位字符
- 可添加、编辑、删除模板
具体操作流程及规则:
-
点击活动公告-添加或某个活动编辑处;找到添加模板
-
在模板界面可以随意添加或修改公告模板;也可以删除不再需要的模板
或者在【具体模板】输入要添加的模板标签,然后在模板框内填入模板内容点保存即可
-
点击保存后回到公告界面,选择某个模板后可自动录入模板内容,且可替换占位字符会高亮
-
替换逻辑是,根据字母表 aaaa 到 zzzz;都可以替换(看模板上怎么放这些字母)
然后在替换编辑直接输入日常那些道具名字;替换的字数不限制,比如活动日期
-
弄好替换的文案直接点【一键录入】;就会录入文案
-
注意事项:一键录入后再修改替换框内的内容是修改不了的;因为已经替换过字母了没这么智能;
若一键录入后需要修改替换内容,则自行在公告内容内修改
组件实现
1. 公告模板组件
edit-template-modal.vue
选择框+输入框的结合:可实现编辑已有模板 或 新增模板
<AutoComplete
v-model="templateInfo.name"
:data="templateOptions"
@on-change="handleChangeContent"
class="w-150px"
></AutoComplete>
props: {
templateList: Array
},
data() {
return {
templateInfo: {
name: '',
// ...
},
}
},
computed: {
templateOptions() {
return this.templateList.map(item => item.name);
}
},
methods: {
handleChangeContent(value) {
// 遍历 templateList 存在 name===value,则处理回显信息,且记录id -> 编辑
if (this.templateOptions.includes(value)) {
this.templateList.map(item => {
if (item.name === value) {
// ...
}
});
this.type = 'edit';
} else {
this.type = 'add';
}
},
}
删除已有模板
<div class="ml-5 select-wrap">
<Button
class="select-button"
type="primary"
icon="md-arrow-dropdown"
v-click-outside="handdleHideOption"
@click="showOption = !showOption"
>已有模板</Button
>
<transition name="vertical-toggle">
<ul v-show="showOption" class="select-option">
<li class="select-item" v-for="item in templateList" :key="item.id">
<span>{{ item.name }}</span>
<div class="select-remove" @click.stop.prevent="handleRemoveTemplate(item.id)">
<Icon type="md-close" />
</div>
</li>
</ul>
</transition>
</div>
props: {
templateList: Array
},
data() {
return {
showOption: false,
}
},
directives: {
'click-outside': {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
// 检查点击事件是否发生在元素外部
if (!(el === event.target || el.contains(event.target))) {
// 如果是,调用提供的方法
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent);
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent);
}
}
},
methods: {
handdleHideOption() {
this.showOption = false;
},
// 删除模板
handleRemoveTemplate(id) {
this.$Modal.confirm({
title: '提示',
content: '<p>删除模板标题后,对应的模板内容也会删除</p><p>是否继续当前操作?</p>',
onOk: async () => {
const params = { id };
const { data: result } = await this.$axios.post(this.apis.remove, params);
if (result.code === 1) {
this.$Message.success(result.msg);
this.showOption = false;
this.$emit('getTemplateList');
} else {
this.$Message.warning(result.msg);
}
}
});
}
}
2. 多行文本框占位符高亮
hightlight-box.vue
-
实现思路:
- div 跟 textarea 重叠样式和位置保持完全一致
- 同时 textarea 文字跟背景透明
- textarea 负责输入
- div 负责高亮显示
-
需要注意的点:
- textarea 需要把文字跟背景设置成透明的;
- 上层负责显示的 div,通过
pointer-events: none;
将鼠标事件设置为失效
<div class="highlight-box">
<div class="textarea-outer" ref="textareaOuter" :style="{ height: `${maxHeight}px` }">
<div ref="outerInner" class="outer-inner" style="white-space: pre-wrap" v-html="highlightHtml(value)"></div>
</div>
<Input
ref="textareaBox"
placeholder="请输入内容..."
v-model.trim="inputContent"
type="textarea"
:autosize="{ minRows: 8, maxRows: 8 }"
:style="{ height: `${maxHeight}px` }"
@keyup.enter="syncScrollTop"
></Input>
</div>
props: {
content: String,
highlightKey: Array,
maxHeight: {
type: Number,
default: 170
}
},
data() {
return {
value: '',
inputContent: ''
};
},
watch: {
content(newVal) {
this.inputContent = newVal;
},
inputContent(newVal) {
this.value = newVal ? newVal.replace(/</g, '<').replace(/>/g, '>') : newVal;
}
}
- 生成 div 展示的内容
methods: {
highlightHtml(str) {
if (str) {
let rebuild = str;
if (this.highlightKey.filter(item => ~str.indexOf(item)).length) {
let regExp = null;
this.highlightKey.forEach(item => {
regExp = new RegExp(item, 'g');
rebuild = rebuild.replace(regExp, `<span class="hight-light-text">${item}</span>`);
});
}
return rebuild;
}
return str;
},
}
- 监听滚动事件,令div跟textare的滚动高度保持一致
mounted() {
const wrap = this.$refs.textareaBox.$el.getElementsByTagName('textarea')[0];
wrap.addEventListener('scroll', this.syncScrollTop);
wrap.addEventListener('mousewheel', this.syncScrollTop);
},
beforeDestory() {
const wrap = this.$refs.textareaBox.$el.getElementsByTagName('textarea')[0];
wrap.removeEventListener('scroll', this.syncScrollTop);
wrap.removeEventListener('mousewheel', this.syncScrollTop);
},
methods: {
syncScrollTop() {
const wrap = this.$refs.textareaBox.$el.getElementsByTagName('textarea')[0];
const outerWrap = this.$refs.textareaOuter;
const outerInner = this.$refs.outerInner;
if (wrap.scrollHeight > this.maxHeight && outerInner.scrollHeight !== wrap.scrollHeight) {
outerInner.style.height = `${wrap.scrollHeight}px`;
}
if (wrap.scrollTop !== outerWrap.scrollTop) {
outerWrap.scrollTop = wrap.scrollTop;
}
},
}
- 多行文本框样式
@width: 600px;
::v-deep textarea.ivu-input {
width: @width;
line-height: 20px;
resize: none;
// 光标的颜色
color: #333333;
// 文本颜色
text-shadow: 0 0 0 rgba(0, 0, 0, 0);
-webkit-text-fill-color: transparent;
background: transparent;
border-radius: 5px;
border: 1px solid #e0e0e0;
padding: 4px 8px;
&::placeholder {
-webkit-text-fill-color: #999999;
}
&:hover {
border-color: #4c84ff;
}
&:focus {
border-color: #4c84ff;
box-shadow: 0 0 0 2px #dbe4ff;
outline: none;
}
}
::v-deep .hight-light-text {
color: #f27c49;
}
- 其它样式
@width: 600px;
.highlight-box {
position: relative;
font-size: 14px;
width: @width;
position: relative;
color: #515a61;
background: #ffffff;
border-radius: 5px;
overflow: hidden;
.textarea-outer {
width: @width;
position: absolute;
top: 0;
left: 0;
right: 0;
border: 1px solid transparent;
border-top: 0;
// 鼠标事件失效 ie6-10不支持
pointer-events: none;
cursor: text;
overflow-y: auto;
line-height: 20px;
word-break: break-word;
.outer-inner {
padding: 4px 8px;
width: 100%;
&:hover {
border-color: #4c84ff;
}
}
}
}
3. 替换编辑
replace-item-select.vue
<div class="row-y-center mb-5" v-for="(item, index) in data" :key="item.keyId">
<span class="t-nowrap t-right" style="width: 50px">{{ labelList[index] }}:</span>
<Input class="t-nowrap ml-5 w-100px" type="text" v-model="item.content" />
<Button
class="col-auto ml-5"
type="primary"
shape="circle"
size="small"
icon="md-add"
@click="addItem(index)"
></Button>
<Button
class="col-auto ml-5"
type="error"
shape="circle"
size="small"
icon="md-remove"
@click="deleteItem(index)"
></Button>
</div>
- 生成小写字母
function getGenerateSmall() {
var str = [];
for (var i = 97; i < 123; i++) {
str.push(String.fromCharCode(i).repeat(4));
}
return str;
}
const labelList = getGenerateSmall();
- 创建一个新选项
let autoAddId = 0; // 自增id
function createUnit() {
return {
keyId: autoAddId++,
content: ''
};
}
- 对外暴露初始化
function initList(min) {
let units = [];
while (units.length < min) {
units.push(createUnit());
}
return units;
}
- 其他属性
props: {
data: Array,
addItem: {
type: Function,
default(index) {
// input插入1, 但是keyId 是最后+1
this.data.splice(index + 1, 0, createUnit());
}
},
deleteItem: {
type: Function,
default(index) {
if (index == 0) {
this.$Message.warning('只剩一条数据的情况下不允许删除...');
return false;
}
this.data.splice(index, 1);
}
}
},
data() {
return {
labelList
};
}
应用
<FormItem>
<hightlight-box
:content.sync="editInfo.content"
:highlightKey="templateData.labelList"
@preview="handlePreview"
>
<!-- 有模板则显示 -->
<Select
v-model="templateData.content"
class="mb-5 w-150px"
@on-change="handleChangeContent"
>
<Option
v-for="item in templateData.list"
:value="item.content"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<Button class="mb-5 ml-5" type="primary" icon="ios-add" @click="handleEditTemplate">
点击加号添加模板
</Button>
</hightlight-box>
</FormItem>
<FormItem label="替换编辑:">
<div class="replace-item">
<Button
class="mt-5"
style="margin-left: 250px"
size="small"
type="primary"
@click="handleReplace"
>一键录入</Button>
<replace-items-select :data="templateData.replaceList"></replace-items-select>
</div>
</FormItem>
data() {
return {
editInfo: {
content: null,
// ...
},
templateData: {
list: [], // 模板列表
labelList: [],
content: '',
showReplace: false,
// 0-表示未使用模板 1-表示使用模板未做替换 2-使用模板且已替换[但不一定已全替换]
replaceStep: 0,
replaceList: [],
controller: {
handleOpen: null
}
},
}
},
methods: {
// 选择具体模板
handleChangeContent() {
// 公告内容
this.editInfo.content = value;
// 展示-替换编辑
this.templateData.showReplace = true;
this.templateData.replaceStep = 1;
this.templateData.replaceList = ReplaceItemsSelect.initList(7);
},
// 点击加号添加模板
handleEditTemplate() {
this.templateData.controller.handleOpen();
},
// 一键录入
handleReplace() {
const replaceList = this.templateData.replaceList;
let content = this.editInfo.content;
replaceList.map((item, index) => {
if (item.content) {
const str = new RegExp(this.templateData.labelList[index], 'g');
content = content.replace(str, item.content);
}
});
this.editInfo.content = content;
this.templateData.replaceStep = 2;
},
// 预览
handlePreview() {},
}