当前位置: 首页 > article >正文

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, '&nbsp;');
      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样式问题


http://www.kler.cn/a/590972.html

相关文章:

  • 设备物联网无线交互控制,ESP32无线联动方案,产品智能化响应
  • 基于SpringBoot的Mybatis和纯MyBatis项目搭建的区别
  • docker 安装 nginx 部署Vue前端项目
  • AOP+Nacos实现动态数据源切换
  • 超详细kubernetes部署k8s----一台master和两台node
  • AK 接口
  • Oracle静默安装方法
  • MySQL事务:确保数据一致性的关键机制
  • 复旦:LLM不同层位置编码缩放
  • 【arXiv 2025】卷积加法自注意力CASAtt,轻量且高效,即插即用!
  • 【紫光同创FPGA开发常用工具】FPGACPLD的下载与固化
  • 数据库设计实验(4)—— 数据更新实验
  • 神思智飞无人机智能调度系统介绍
  • pwn刷题记录
  • 笔记:代码随想录算法训练营day56:图论理论基础、深搜理论基础、98. 所有可达路径、广搜理论基础
  • XXXX数字化交流BI系统分析与设计(40页PPT)(文末有下载方式)
  • Web 小项目: 网页版图书管理系统
  • ESMFold 安装教程
  • (6)用于无GPS导航的Nooploop
  • 音视频处理的“瑞士军刀”与“积木”:FFmpeg 与 GStreamer 的深度揭秘