contenteditable 相关介绍
实现效果展示
contenteditable 属性规定是否可编辑元素的内容
属性值
true
可以编辑元素内容false
无法编辑元素内容classname
继承父元素的contenteditable
属性
<div class="flex" contenteditable="true"></div>
Selection属性一览
https://developer.mozilla.org/zh-CN/docs/Web/API/Selection
widnow.getSelection()
返回一个 Selection
对象,表示用户选择的文本范围或光标的当前位置
Section.getRangeAt()
返回选区开始的节点(Node)
const sel = window.getSelection();
const range = sel.getRangeAt(0);
光标所在位置追加元素代码
var sel, range;
var textContent;
//失去焦点时获取光标的位置
sel = window.getSelection();
// 先获取一下焦点
$('.flex').focus()
$('.flex').blur(function() {
getblur()
})
function getblur() {
sel = window.getSelection();
range = sel.getRangeAt(0);
}
// 光标所在位置追加元素
function insertHtmlAtCaret(html) {
if (window.getSelection) {
// IE9 and non-IE
if (sel.getRangeAt && sel.rangeCount) {
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(),
node,
lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag); // Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (document.selection && document.selection.type != "Control") {
// IE < 9
document.selection.createRange().pasteHTML(html);
}
textContent = $(".flex").html();
}
文字生成图片
/**
* js使用canvas将文字转换成图像数据base64
* @param {string} text 文字内容 "abc"
* @param {string} fontsize 文字大小 20
* @param {function} fontcolor 文字颜色 "#000"
* @param {boolean} imgBase64Data 图像数据
*/
export const textBecomeImg = (text,fontsize,fontcolor) => {
var canvas = document.createElement('canvas');
var $buHeight = 0;
if(fontsize <= 32){ $buHeight = 1; }
else if(fontsize > 32 && fontsize <= 60 ){ $buHeight = 2;}
else if(fontsize > 60 && fontsize <= 80 ){ $buHeight = 4;}
else if(fontsize > 80 && fontsize <= 100 ){ $buHeight = 6;}
else if(fontsize > 100 ){ $buHeight = 10;}
canvas.height=fontsize + $buHeight ;
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = fontcolor;
context.font=fontsize+"px Arial";
context.textBaseline = 'middle';
context.fillText(text,5,fontsize/2)
canvas.width = context.measureText(text).width;
context.fillStyle = fontcolor;
context.font=14+"px Arial";
context.textAlign="left";
context.textBaseline = 'middle';
context.fillText(text,5,fontsize/2)
var dataUrl = canvas.toDataURL('image/png');
return dataUrl;
}
剩下的就是jq了 jquery大法好
this.$nextTick(() => {
$('.collapse-item').click(function() {
const { name, key } = JSON.parse($(this).attr('data'))
let htmlStr = "";
const tempKey = "this.paramsKey." + key;
const url = textBecomeImg(name, 20, "#fff");
htmlStr = `<img data-key="${ tempKey }" style="background: #3296fa;margin: 0 10px 10px 0;" src="${url}">`;
insertHtmlAtCaret(htmlStr)
})
})
注意: 项目本身是vue工程,但是该功能需要各种操作dom,所以就在组件里见面vue与jq结合使用了
整个组件代码
方案改过一版,代码还未进行优化,因为代码里面使用了一些项目本身的数据,所以整套逻辑只能做参考了
- html
<template>
<el-card>
<div class="hide-show">
<div class="hide-show-left">
<el-collapse v-model="activeNames" @change="handleChange">
<el-collapse-item
v-for="(item, index) in collapseData"
:key="index"
title="当前表单"
name="1"
>
<p v-for="val in item.list" :key="val.key" class="collapse-item" :data="JSON.stringify(val)">{{ val.name }}</p>
</el-collapse-item>
</el-collapse>
</div>
<div class="hide-show-right">
<div class="hide-show-right-box">
<div class="flex" contenteditable="true"></div>
</div>
<div class="hide-show-right-bottom">
<p class="title">满足以上条件字段隐藏</p>
<ul>
<li class="li-item">请从左侧面板选择字段或选项</li>
<li class="li-item">
支持
<span style="color: red;">英文</span>模式下运算符(+, -, *, /, >, <, ==, !=, <=, >=)
</li>
<li class="li-item">
<p>参考场景:</p>
<p>总金额控件输入的值小于10000时,需要隐藏当前控件,则可将隐藏条件设置为: 总金额 < 100</p>
</li>
</ul>
</div>
</div>
</div>
</el-card>
</template>
- js
<script>
import { textBecomeImg } from "./utils.js";
export default {
name: "hide-show",
props: {
// 当前组件dataInfo
dataInfo: {
type: Object,
default: () => {}
},
// 画布里面已经拖拽的并且key存在的组件
data: {
type: Array,
default: () => []
}
},
data() {
return {
activeNames: ["1"],
collapseData: [
{
title: "当前表单",
type: 1,
list: []
}
],
paramsKey: {}
};
},
methods: {
// 格式化数据方法
handleFormatData() {
const list = [];
const params = {};
this.data.forEach(item => {
// 区分 栅格 标签页 子表 这几类布局组件
if (item.type === "grid") {
// 栅格
item.columns.forEach(val => {
val.list.forEach(v => {
if (v.model && v.model != "") {
this.$set(this.paramsKey, v.model, v.value);
list.push({
name: v.name,
type: 1,
key: v.model
});
}
});
});
} else if (item.type === "tabs") {
// 标签页
item.tab.forEach(val => {
val.columns.forEach(v => {
if (v.type === "grid") {
v.columns.forEach(col => {
col.list.forEach(l => {
if (l.model && l.model != "") {
this.$set(this.paramsKey, l.model, l.value);
list.push({
name: l.name,
type: 1,
key: l.model
});
}
});
});
} else {
if (v.model && v.model != "") {
this.$set(this.paramsKey, v.model, v.value);
list.push({
name: v.name,
type: 1,
key: v.model
});
}
}
});
});
} else if (item.type === "childrenTable") {
// 子表
// 子表不能作为控制显示隐藏的条件
console.log("item: ", item);
} else {
// 普通组件
if (item.model && item.model != "") {
this.$set(this.paramsKey, item.model, item.value);
list.push({
name: item.name,
type: 1,
key: item.model
});
}
}
});
this.collapseData[0]["list"] = list.filter(
val => val.key != this.dataInfo.model
);
},
handleChange(val) {},
// 清空
handleClearExpression() {
$(".flex").empty();
},
// 获取最终表达式
handleGetExpression() {
let m = ''
let str = $('.flex').prop("outerHTML")
// 谷歌浏览器如果开启翻译功能会导致生成font标签包裹文本
str = str.replace(/<font style="vertical-align: inherit;">/g, '')
str = str.replace(/<\/font>/g, '')
str = str.replace(/<div contenteditable="true" type="parent" class="flex">/g, '')
str = str.replace(/<\/div>/g, '')
$('.flex').empty()
$('.flex').html(str)
Array.from($('.flex').contents()).forEach(val => {
if ($(val).attr('data-key')) {
m = m + $(val).attr('data-key')
} else {
m = m + $(val).text()
}
})
try {
eval(m);
if (
this.dataInfo.options &&
typeof this.dataInfo.options !== "string"
) {
this.dataInfo.options.js_content = m;
this.dataInfo.options.htmlStr = $(".flex").prop("outerHTML");
} else {
this.dataInfo.options = {};
this.dataInfo.options.js_content = m;
this.dataInfo.options.htmlStr = $(".flex").prop("outerHTML");
}
this.$emit("colse-dailog");
return str;
} catch (err) {
console.log(err);
this.$message.error("配置的表达式不合法,请检查!");
}
}
},
mounted() {
const _this = this;
$(".hide-show-right-box").html(_this.dataInfo.options.htmlStr);
$(".flex").keydown(function(e) {
if (e.keyCode == 13) {
e.preventDefault();
}
});
this.handleFormatData();
sel = window.getSelection();
var sel, range;
var textContent;
$('.flex').focus()
$('.flex').blur(function() {
getblur()
})
function getblur() {
range = sel.getRangeAt(0);
}
// 光标所在位置追加元素
function insertHtmlAtCaret(html) {
if (window.getSelection) {
// IE9 and non-IE
if (sel.getRangeAt && sel.rangeCount) {
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(),
node,
lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag); // Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (document.selection && document.selection.type != "Control") {
// IE < 9
document.selection.createRange().pasteHTML(html);
}
textContent = $(".flex").html();
}
this.$nextTick(() => {
$('.collapse-item').click(function() {
const { name, key } = JSON.parse($(this).attr('data'))
let htmlStr = "";
const tempKey = "this.paramsKey." + key;
const url = textBecomeImg(name, 20, "#fff");
// hspace=15
htmlStr = `<img data-key="${ tempKey }" style="background: #3296fa;" src="${url}" hspace=15>`;
insertHtmlAtCaret(htmlStr)
})
})
}
};
</script>
- css
<style lang="scss">
.hide-show {
margin: 0 auto;
width: 700px;
height: 450px;
border: 1px solid #e6e6e6;
border-radius: 8px;
box-sizing: border-box;
display: flex;
justify-content: flex-start;
.hide-show-left {
border-right: 1px solid #e6e6e6;
width: 220px;
height: 100%;
background: #e6e6e6;
padding: 10px;
border-radius: 8px 0 0 8px;
overflow: auto;
box-sizing: border-box;
.collapse-item {
text-align: center;
height: 32px;
line-height: 32px;
border-bottom: 1px solid #e6e6e6;
cursor: pointer;
margin: 0;
padding: 0;
}
.collapse-item:hover {
background: #eee;
height: 32px;
line-height: 32px;
}
.el-collapse-item__header {
background: #3296fa;
color: #fff;
padding-left: 15px;
}
.el-collapse-item__content {
padding-bottom: 0;
}
}
.hide-show-right {
flex: 1;
background: #fff;
border-radius: 8px;
box-sizing: border-box;
.hide-show-right-box {
height: 250px;
overflow: auto;
padding: 10px;
.flex {
color: #666;
padding: 20px;
outline: none;
line-height: 28px;
height: 28px;
font-size: 20px;
}
.flex,
.flex * {
-webkit-user-select: auto;
-webkit-user-modify: read-write;
}
width: 100%;
padding: 10px;
}
.hide-show-right-bottom {
* {
margin: 0;
padding: 0;
}
height: 200px;
.title {
background: #eed5d2;
height: 32px;
line-height: 32px;
padding-left: 20px;
margin-bottom: 15px;
color: #fff;
}
ul {
padding-left: 20px;
li {
line-height: 22px;
}
}
}
}
}
</style>