vue中根据html动态渲染内容
需求:根据数据中的html,因为我是在做填空,所以是需要将html中的_____替换成input,由于具体需求我使用的是元素contenteditable代替的可编辑的input
html部分
<div class="wrap">
<component :is="renderedContent" ref="wrap_component" />
</div>
js部分
// 这个是为了保证输入的时候光标保持在最后
const moveCursorToEnd = (element: HTMLElement) => {
const range = document.createRange();
const selection = window.getSelection();
// 找到最后一个文本节点
let lastTextNode: Text | any = null;
const traverseNodes = (node: Node) => {
if (node.nodeType === Node.TEXT_NODE) {
lastTextNode = node as Text;
}
for (let i = 0; i < node.childNodes.length; i++) {
traverseNodes(node.childNodes[i]);
}
};
traverseNodes(element);
if (lastTextNode) {
range.setStart(lastTextNode, lastTextNode.textContent?.length || 0);
range.collapse(true);
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
} else {
range.setStart(element, 0);
range.collapse(true);
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
}
// 兼容性处理:确保元素获取焦点
element.focus();
if (document.activeElement !== element) {
element.focus();
}
};
// 计算属性,用于生成渲染内容
const renderedContent = computed(() => {
if (!itemConf.value.customConf?.inputHtml) return null;
const parts = itemConf.value.customConf.inputHtml.split(/_{1,}/);
let nodes: any = [];
parts.forEach((part, index) => {
if (part) {
const replacedSpaces = part.replace(/ /g, ' ');
const replacedPart = replacedSpaces.replace(/<div>/g, '<br>').replace(/<\/div>/g, '');
nodes.push(h('span', { class: 'custom-span', innerHTML: replacedPart }));
}
if (index < parts.length - 1) {
if (!inputValues.value[index]) {
inputValues.value[index] = '';
}
if (!isInputFocused.value[index]) {
isInputFocused.value[index] = false;
}
if (!isClearIconClicked.value[index]) {
isClearIconClicked.value[index] = false;
}
if (!clearIconHideTimer.value[index]) {
clearIconHideTimer.value[index] = 0;
}
const clearIcon = h(
ElIcon,
{
class: [
'clear_icon',
{
'is-hidden':
inputValues.value[index].length === 0 ||
itemConf.value.baseConf.isReadOnly ||
!isInputFocused.value[index],
},
],
onClick: () => {
if (!itemConf.value.baseConf.isReadOnly) {
isClearIconClicked.value[index] = true;
inputValues.value[index] = '';
if (inputRefs.value[index]) {
inputRefs.value[index].innerText = '';
}
adjustInputWidth(index);
handleChange(itemConf.value.customConf.inputGroup[index], '');
// 点击后清除隐藏定时器
clearTimeout(clearIconHideTimer.value[index]);
}
},
},
{ default: () => h(CircleClose) },
);
const inputNode = h(
'p',
{
contenteditable: !itemConf.value.baseConf.isReadOnly,
class: [
'underline_input',
{
'is-disabled': itemConf.value.baseConf.isReadOnly,
},
],
disabled: itemConf.value.baseConf.isReadOnly,
innerHTML: inputValues.value[index],
placeholder: unref(itemConf).customConf?.inputGroup[index]?.placeholder || '请输入',
onInput: async (event: InputEvent) => {
const target = event.target as HTMLParagraphElement;
adjustInputWidth(index);
inputValues.value[index] = target.innerHTML;
await nextTick(() => {
moveCursorToEnd(target);
});
},
onFocus: () => {
if (!itemConf.value.baseConf.isReadOnly) {
isInputFocused.value[index] = true;
clearTimeout(clearIconHideTimer.value[index]);
}
},
onBlur: () => {
if (!itemConf.value.baseConf.isReadOnly) {
handleChange(itemConf.value.customConf.inputGroup[index], inputValues.value[index]);
clearIconHideTimer.value[index] = setTimeout(() => {
if (!isClearIconClicked.value[index]) {
isInputFocused.value[index] = false;
}
isClearIconClicked.value[index] = false;
}, 200);
}
},
onMousedown: (event: MouseEvent) => {
if (itemConf.value.baseConf.isReadOnly) {
event.preventDefault();
event.stopPropagation();
}
},
onKeydown: (event: KeyboardEvent) => {
if (itemConf.value.baseConf.isReadOnly) {
event.preventDefault();
event.stopPropagation();
}
},
ref: (el) => (inputRefs.value[index] = el),
},
// [clearIcon],
);
nodes.push(h('p', { class: 'underline_input_wrap' }, [inputNode, clearIcon]));
}
});
return h('div', nodes);
});
css部分
.underline_input_wrap {
display: inline-block;
// max-width: calc(100% - 70px);
position: relative;
margin-top: 20px;
margin-bottom: 0;
max-width: calc(100% - 50px);
}
.underline_input {
position: relative;
height: 40px;
min-width: 101px;
// max-width: calc(100% - 70px);
max-width: 100%;
background: #f5f7fb;
border-radius: 6px 6px 6px 6px;
border: none;
margin-left: 10px;
margin-top: 0;
margin-bottom: 0;
display: inline-block;
box-sizing: border-box;
padding: 0 26px 0 12px;
background: #f5f7fb;
vertical-align: middle;
color: #606266;
background: #f5f7fb;
vertical-align: middle;
&:focus {
outline: none;
border: 1px solid #1a77ff;
color: #606266;
}
&:disabled {
color: #bbbfc4;
cursor: not-allowed;
}
&::placeholder {
color: #a8abb2;
font-size: 14px;
}
}
.underline_input.is-disabled {
color: #bbbfc4;
cursor: not-allowed;
}
.underline_input[contenteditable='true']:empty::before,
.underline_input.is-disabled:empty::before {
content: attr(placeholder);
color: #bbbfc4;
}
:deep(.clear_icon) {
position: absolute;
width: 14px;
height: 14px;
right: 5px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #999;
z-index: 10; /* 增加 z-index 确保在最上层 */
&:hover {
color: #666;
}
&.is-hidden {
display: none;
}
}
我们要模拟input可清除,所以需要我们去调整样式,以及placeholder样式问题