vue中根据html动态渲染内容

需求:根据数据中的html,因为我是在做填空,所以是需要将html中的_____替换成input,由于具体需求我使用的是元素contenteditable代替的可编辑的input

html部分

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(/
/g, '
').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样式问题

你可能感兴趣的:(vue.js,html,javascript)