vue中根据html动态渲染内容2.0

发布于:2025-04-11 ⋅ 阅读:(36) ⋅ 点赞:(0)

上次使用的是p标签用的contenteditable代替的可编辑的input,最后实现还是选择了用el-input的textarea方式。
一开始考虑的是需要根据用户输入自动撑开输入框,所以选择了p标签可编辑。
最后发现还是el-input会更好一点,只不过需要处理输入框撑开的样式问题。

好的,话不多说!
代码如下:

<script setup lang="ts">
// 存储每个 input 框的值
const inputValues = ref<string[]>([]);
// 存储 input 元素的 ref
const inputRefs: any = ref([]);
const isInputFocused = ref<boolean[]>([]);
const isClearIconClicked = ref<boolean[]>([]); // 用于延迟隐藏清除图标的定时器
const clearIconHideTimer: any = ref([]);
// 提取重复的判断逻辑到一个函数中
const shouldDisableOrHide = (itemConf, index) => {
  return (
    itemConf.value.baseConf.isReadOnly ||
    !(itemConf.value.customConf?.inputGroup[index]?.isSupportEdit ?? true)
  );
};
// 计算属性,用于生成渲染内容
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 ||
                shouldDisableOrHide(itemConf, index) ||
                !isInputFocused.value[index],
            },
          ],
          onClick: () => {
            if (!shouldDisableOrHide(itemConf, index)) {
              isClearIconClicked.value[index] = true;
              inputValues.value[index] = '';
              adjustInputWidth(index);
              handleChange(itemConf.value.customConf.inputGroup[index], '');
              // 点击后清除隐藏定时器
              clearTimeout(clearIconHideTimer.value[index]);
            }
          },
        },
        { default: () => h(CircleClose) },
      );
      const inputNode = h(ElInput, {
        type: 'textarea',
        modelValue: inputValues.value[index],
        'onUpdate:modelValue': (val: string) => {
          inputValues.value[index] = val;
          adjustInputWidth(index);
        },
        rows: 1,
        clearable: true,
        autosize: {
          minRows: 1, // 最小行数
          maxRows: 3, // 最大行数
        },

        class: [
          'underline_input',
          {
            'is-disabled': shouldDisableOrHide(itemConf, index),
          },
        ],
        disabled: shouldDisableOrHide(itemConf, index),
        placeholder: unref(itemConf).customConf?.inputGroup[index]?.placeholder || '请输入',
        onFocus: () => {
          isInputFocused.value[index] = true;
          clearTimeout(clearIconHideTimer.value[index]);
        },
        onBlur: () => {
          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;
          }, 100);
        },
        // ref: (el) => (inputRefs.value[index] = el),
      });

      nodes.push(h('p', { class: 'underline_input_wrap' }, [inputNode, clearIcon]));
    }
  });
  return h('div', nodes);
});

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 这个就是调整宽度的方法
const adjustInputWidth = (index: number) => {
  const textarea = inputRefs.value?.[index] || undefined;
  if (ctx && textarea) {
    ctx.font = '14px system-ui'; // 必须与 CSS 字体设置一致
    const text = inputValues.value[index] || textarea.placeholder;
    const textWidth = ctx.measureText(text).width;
    textarea.style.width = `${Math.max(textWidth + 30, 101)}px`; // 101px 为最小宽度
  } else {
    textarea.style.width = '101px';
  }
};

// 在组件挂载时初始化输入框宽度
onMounted(() => {
  nextTick(() => {
    inputRefs.value = unref(wrap_component)?.querySelectorAll('.underline_input') || [];
    inputRefs.value.forEach((textarea) => {
      textarea.style.width = 'auto !important';
      if (parseInt(textarea.style.width) < 101) {
        textarea.style.width = '101px';
      }
    });
});
</script>


<style lang="less" scoped>
.underline_input_wrap {
  display: inline-block;
  position: relative;
  margin-top: 20px;
  margin-bottom: 0;
  margin-right: 10px;
  max-width: calc(100% - 50px);
}
:deep(.underline_input) {
  width: 100%;
  border-radius: 6px 6px 6px 6px;
  border: none;
  margin-top: 0;
  margin-bottom: 0;
  display: inline-block;
  box-sizing: border-box;
  vertical-align: middle;
  color: #606266;
  .el-textarea__inner {
    background: #f5f7fb;
    // 宽度高度自适应
    min-height: 40px !important;
    min-width: 101px !important;
    max-width: 100%;
    max-height: 100px;
    line-height: 31px;
    resize: none !important; // 禁止用户手动调整文本框大小
    box-shadow: none;
    white-space: pre-wrap;
    word-break: break-all;
    /* 隐藏滚动条 */
    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
    &::-webkit-scrollbar-thumb {
      background: transparent;
    }
    &::-webkit-scrollbar-track {
      background: transparent;
    }
    &: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: 2px;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
  color: #999;
  z-index: 10; /* 增加 z-index 确保在最上层 */
  &:hover {
    color: #666;
  }
  &.is-hidden {
    display: none;
  }
}
</style>

至于样式大家可根据自己需要进行调整。


网站公告

今日签到

点亮在社区的每一天
去签到