vue3 用el-dialog实现用户协议,内容是富文本, 滚动到最后才允许关闭

<template>
  <div>
    <el-dialog
      v-model="dialogVisible"
      title="用户协议"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :show-close="false"
      width="50%"
      destroy-on-close
    >
      <div class="ql-container ql-snow">
        <div ref="contentRef" class="notice-content ql-editor" v-html="protocolHtml" @scroll="handleScroll"></div>
      </div>

      <template #footer>
        <div class="dialog-footer">
          <el-button size="default" @click="handleClose">关闭</el-button>
          <el-button type="primary" @click="handleAgree" :disabled="!canClose"> 我已阅读并同意 </el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
  import { ref, nextTick, defineProps, defineEmits } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";

  const emits = defineEmits(["close"]);
  const props = defineProps({
    isShow: {
      type: Boolean,
      default: false
    }
  });

  const dialogVisible = ref(false);
  const protocolHtml = ref(`
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  <h1>这是用户协议内容,您需要滚动到底部才能关闭本弹窗</h1>
  `);

  watch(
    () => props.isShow,
    (newVal, oldVal) => {
      dialogVisible.value = newVal;
      if (newVal) {
        canClose.value = false;
        nextTick(() => {
          const el = contentRef.value;
          if (el) {
            el.scrollTop = 0;
            el.addEventListener("scroll", handleScroll);
          }
        });
      }
    },
    {
      immediate: true,
      deep: true
    }
  );

  const contentRef = ref(null);
  const canClose = ref(false);
  const handleScroll = () => {
    const el = contentRef.value;
    if (!el) return;
    const isBottom = Math.abs(el.scrollHeight - el.scrollTop - el.clientHeight) < 10;
    canClose.value = isBottom;
  };

  const handleClose = () => {
    if (canClose.value) {
      emits("close", false);
    } else {
      ElMessage.warning("请先阅读完用户协议");
    }
  };

  // 同意协议
  const handleAgree = () => {
    if (canClose.value) {
      ElMessage.success("您已同意用户协议");
      emits("agreeClose", false);
    }
  };

  // 清理滚动事件
  onBeforeUnmount(() => {
    const el = contentRef.value;
    if (el) {
      el.removeEventListener("scroll", handleScroll);
    }
  });
</script>

<style scoped>
  .notice-content {
    height: 400px;
    overflow-y: auto;
    border: 1px solid #eee;
    padding: 15px;
    border-radius: 4px;
    background-color: #f9f9f9;
    font-size: 14px;
    line-height: 1.6;
    white-space: pre-wrap;
    word-break: break-all;
  }
  .dialog-footer {
    text-align: right;
  }
  ::v-deep .ql-container.ql-snow {
    border: none;
  }
</style>

更多推荐