【开发记录】Vue3写一个气泡对话框,可随意配置文字换行、图标、箭头位置和方向

对话框.gif

一、功能点

  1. 文字可以换行
  2. 可以随意这是重点加粗的文字
  3. 对话图标可以显示、隐藏
  4. 箭头可以调整左右方向、在对话框的上面、下面,以及相对中间偏移

二、具体功能能实现

功能都不复杂,这里简单说一下,哈哈,但有一个重要的点,还是先提前说一下:
对话框里的文字,是用v-html插入进去的,因为要实现内部文字的加粗,需要提前处理,将加粗的文字,用标签包起来。

1、文字换行

输入框回车后,所输入的文字内,会有\n,所以,把\n批量替换成
标签就可以了

htmlText = htmlText.replaceAll("\n", `<br />`);

2、内部部分文字加粗

遍历要加粗的文字数组,将需要加粗的文字,用标签包起来,大概的逻辑如下:

boldTexts.forEach(boldText => {
if (boldText) {
  htmlText = htmlText.replaceAll(boldText, `<>${boldText}</>`);
}
});
htmlText = htmlText.replaceAll("<>", `<span style="font-weight: 900;">`);
htmlText = htmlText.replaceAll("</>", `</span>`);

不过,有聪明的大佬,看出了<>这个标识符的漏洞,这里为了简化,就先这样,意思表达到了就好,详细实现,再下面的完整代码里。

3、箭头的调整

这个就简单了,就用用css样式去控制下,这里就不细说了,下面有完整的代码,一看就知道了。

三、完整代码

哈哈,写技术分享,不贴出完整代码的,都是在耍流氓。

<template>
  <div
    class="word-pop-container need-bubble"
    :class="{'has-horn': showHorn}"
    v-if="textHtml"
  >
    <div class="show-horn" v-if="showHorn" >
      <img src="./image/chat.png" />
    </div>
    <div>
      <span v-html="textHtml"></span>
    </div>
    <div
      class="arrow-box"
      :class="{
        flip: wordPopConfigData.arrowPosition,
        'no-filp ': !wordPopConfigData.arrowPosition,
      }"
    >
      <i
        class="arrow"
        :class="{ flip: wordPopConfigData.arrowFlip }"
        :style="{ left: wordPopConfigData.arrowOffsetX + 'px' }"
      ></i>
    </div>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, ref } from "vue";
export default defineComponent({
  name: "WordPop",
  props: {
    configData: {
      type: Object,
      require: true,
      default: (() => {}),
    }
  },
  setup(props) {
    const defaultConfigData = ref({
      text: "", // 对话文本
      boldTexts: [], // 加粗的文字
      showHorn: true, // 显示对话图标
      arrowPosition: false, // 箭头位置:fanse:下,true:上
      arrowFlip: false, // 箭头指向:false右,true:左
      arrowOffsetX: -40, // 箭头偏移,单位:像素,相对位置:对话框中间文职,正数向右偏移、负数向左偏移
    });

    const wordPopConfigData = computed(() => {
      return {
        ...defaultConfigData.value,
        ...props.configData,
      }
    });

    // 将字符串编码为ArrayBuffer格式
    const getMessageEncoding = (text: string) => {
      let enc = new TextEncoder();
      return enc.encode(text);
    }

    const textHtml = computed(() => {
      let htmlText = wordPopConfigData.value.text;
      const codeText = getMessageEncoding(htmlText).toString();
      const spanStart = `<${codeText}>`;
      const spanEnd = `<${codeText}/>`;
      wordPopConfigData.value.boldTexts.forEach(boldText => {
        if (boldText) {
          htmlText = htmlText.replaceAll(boldText, `${spanStart}${boldText}${spanEnd}`);
        }
      });
      htmlText = htmlText.replaceAll(spanStart, `<span style="font-weight: 900;">`);
      htmlText = htmlText.replaceAll(spanEnd, `</span>`);
      htmlText = htmlText.replaceAll("\n", `<br />`);
      return htmlText;
    });

    // 对话标识
    const showHorn = computed(() => {
      return Boolean(wordPopConfigData.value.showHorn || false);
    });


    return {
      wordPopConfigData,
      showHorn,
      textHtml,
    };
  },
});
</script>

<style lang="less" scoped>
.word-pop-container {
  width: auto;
  height: auto;
  position: relative;
  color: #000;
  letter-spacing: normal;
  text-align: left;
  white-space: nowrap;
  padding: 10px 18px;
  border-radius: 40px;
  display: inline-block;
  border: 3px solid #f2f2f2;
  &.has-horn {
    padding: 10px 18px 10px 43px;
  }
  .show-horn {
    position: absolute;
    width: 20px;
    height: 20px;
    top: 0px;
    bottom: 0px;
    left: 13px;
    margin: auto;
    img {
      width: 100%;
      height: 100%;
      border-radius: 15px;
    }
  }
  span {
    font-size: 16px;
    line-height: 1.4;
  }
  &.need-bubble {
    background-color: #fff;
  }
  .arrow-box {
    position: absolute;
    width: 30px;
    height: 20px;
    left: 50%;
    bottom: -20px;
    &.no-filp {
      transform: translateX(-50%);
    }
    &.flip {
      bottom: auto;
      top: 0;
      transform-origin: top center;
      transform: translateX(-50%) rotateX(180deg);
    }
  }
  .arrow {
    display: block;
    width: 100%;
    height: 100%;
    background-image: url(./image/sweepWordArrow.png);
    background-size: 100% 100%;
    background-repeat: no-repeat;
    position: absolute;
    bottom: 2.3px;
    &.flip {
      transform-origin: center;
      transform: rotateY(180deg);
    }
  }
}
</style>

四、最后

欢迎各位来用下我用了近2年,搞的一款网页编辑器,一定会让你感到惊讶的,哈哈!

  • 网站地址:https://www.h5nocode.com

之后,我会继续总结,把这个工具里的一些功能模块,抽离出来,放在https://github.com/lcl6659/vue3FunctionBlock 这里,感兴趣的朋友可以看看,顺手给个star,也算是支持一下我了,在此感谢!!

好的,我可以为您提供一个简单的 Vue Dialog 组件示例代码: ```vue <template> <div v-show="visible" class="dialog-wrapper"> <div class="dialog"> <div class="dialog-header"> <h3>{{ title }}</h3> <button class="close-btn" @click="closeDialog">X</button> </div> <div class="dialog-body"> <slot></slot> </div> <div class="dialog-footer"> <button class="btn-primary" @click="confirm">确认</button> <button class="btn-secondary" @click="closeDialog">取消</button> </div> </div> </div> </template> <script> export default { props: { visible: { type: Boolean, default: false, }, title: { type: String, default: '提示', }, }, methods: { confirm() { // 触发确认事件 this.$emit('confirm') }, closeDialog() { // 触发关闭事件 this.$emit('close') }, }, } </script> <style> .dialog-wrapper { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; } .dialog { width: 400px; background: #fff; border-radius: 5px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); } .dialog-header { padding: 10px; background: #f5f5f5; border-top-left-radius: 5px; border-top-right-radius: 5px; display: flex; justify-content: space-between; align-items: center; } .dialog-body { padding: 20px; } .dialog-footer { padding: 10px; background: #f5f5f5; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; display: flex; justify-content: flex-end; } .btn-primary { margin-right: 10px; padding: 5px 10px; background: #409eff; border: none; color: #fff; border-radius: 5px; cursor: pointer; } .btn-secondary { padding: 5px 10px; background: #fff; border: 1px solid #bbb; color: #333; border-radius: 5px; cursor: pointer; } </style> ``` 在上面的代码中,我们定义了一个名为 `Dialog` 的 Vue 组件,它有一个 `visible` 属性来控制对话框的显示与隐藏,还有一个 `title` 属性来设置对话框的标题。 对话框内部包含三个部分:头部、内容和底部按钮。头部包含一个标题和一个关闭按钮,内容部分使用了 Vue 的插槽来允许我们自定义对话框的内容,底部包含了两个按钮:确认和取消。 在组件内部,我们定义了 `confirm` 和 `closeDialog` 两个方法来分别触发确认和关闭事件(使用了 `$emit` 方法)。在外部使用该组件时,我们可以监听这两个事件来获取用户的操作结果。 最后,我们还为组件添加了一些简单的样式。这只是一个简单的示例代码,你可以根据自己的需求进行修改和定制。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值