zhangwencui
4 天以前 2643443d1609c7da11fb5785af6ef71fcc5d5020
src/components/Editor/index.vue
@@ -1,261 +1,328 @@
<template>
  <view class="editor-container">
    <div class="editor">
      <!-- <QuillEditor v-model:content="content"
                   contentType="html"
                   @textChange="(e) => emit('update:modelValue', content)"
                   :options="options"
                   :style="styles" /> -->
    </div>
  <view class="container">
    <view class="editor-wrapper"
          :style="{ height: height + 'px' }">
      <view class='toolbar'
            @tap="format"
            style="height: 120px;overflow-y: auto;">
        <view :class="formats.bold ? 'ql-active' : ''"
              class="iconfont icon-zitijiacu"
              data-name="bold">
        </view>
        <view :class="formats.italic ? 'ql-active' : ''"
              class="iconfont icon-zitixieti"
              data-name="italic">
        </view>
        <view :class="formats.underline ? 'ql-active' : ''"
              class="iconfont icon-zitixiahuaxian"
              data-name="underline"></view>
        <view :class="formats.strike ? 'ql-active' : ''"
              class="iconfont icon-zitishanchuxian"
              data-name="strike"></view>
        <!-- #ifndef MP-BAIDU -->
        <view :class="formats.align === 'left' ? 'ql-active' : ''"
              class="iconfont icon-zuoduiqi"
              data-name="align"
              data-value="left"></view>
        <!-- #endif -->
        <view :class="formats.align === 'center' ? 'ql-active' : ''"
              class="iconfont icon-juzhongduiqi"
              data-name="align"
              data-value="center"></view>
        <view :class="formats.align === 'right' ? 'ql-active' : ''"
              class="iconfont icon-youduiqi"
              data-name="align"
              data-value="right"></view>
        <view :class="formats.align === 'justify' ? 'ql-active' : ''"
              class="iconfont icon-zuoyouduiqi"
              data-name="align"
              data-value="justify"></view>
        <!-- #ifndef MP-BAIDU -->
        <view :class="formats.lineHeight ? 'ql-active' : ''"
              class="iconfont icon-line-height"
              data-name="lineHeight"
              data-value="2"></view>
        <view :class="formats.letterSpacing ? 'ql-active' : ''"
              class="iconfont icon-Character-Spacing"
              data-name="letterSpacing"
              data-value="2em"></view>
        <view :class="formats.marginTop ? 'ql-active' : ''"
              class="iconfont icon-722bianjiqi_duanqianju"
              data-name="marginTop"
              data-value="20px"></view>
        <view :class="formats.marginBottom ? 'ql-active' : ''"
              class="iconfont icon-723bianjiqi_duanhouju"
              data-name="marginBottom"
              data-value="20px"></view>
        <!-- #endif -->
        <view class="iconfont icon-clearedformat"
              @tap="removeFormat"></view>
        <!-- #ifndef MP-BAIDU -->
        <view :class="formats.fontFamily ? 'ql-active' : ''"
              class="iconfont icon-font"
              data-name="fontFamily"
              data-value="Pacifico"></view>
        <view :class="formats.fontSize === '24px' ? 'ql-active' : ''"
              class="iconfont icon-fontsize"
              data-name="fontSize"
              data-value="24px"></view>
        <!-- #endif -->
        <view :class="formats.color === '#0000ff' ? 'ql-active' : ''"
              class="iconfont icon-text_color"
              data-name="color"
              data-value="#0000ff"></view>
        <view :class="formats.backgroundColor === '#00ff00' ? 'ql-active' : ''"
              class="iconfont icon-fontbgcolor"
              data-name="backgroundColor"
              data-value="#00ff00"></view>
        <view class="iconfont icon-date"
              @tap="insertDate"></view>
        <view class="iconfont icon--checklist"
              data-name="list"
              data-value="check"></view>
        <view :class="formats.list === 'ordered' ? 'ql-active' : ''"
              class="iconfont icon-youxupailie"
              data-name="list"
              data-value="ordered"></view>
        <view :class="formats.list === 'bullet' ? 'ql-active' : ''"
              class="iconfont icon-wuxupailie"
              data-name="list"
              data-value="bullet"></view>
        <view class="iconfont icon-undo"
              @tap="undo"></view>
        <view class="iconfont icon-redo"
              @tap="redo"></view>
        <view class="iconfont icon-outdent"
              data-name="indent"
              data-value="-1"></view>
        <view class="iconfont icon-indent"
              data-name="indent"
              data-value="+1"></view>
        <view class="iconfont icon-fengexian"
              @tap="insertDivider"></view>
        <view class="iconfont icon-charutupian"
              @tap="insertImage"></view>
        <view :class="formats.header === 1 ? 'ql-active' : ''"
              class="iconfont icon-format-header-1"
              data-name="header"
              :data-value="1"></view>
        <view :class="formats.script === 'sub' ? 'ql-active' : ''"
              class="iconfont icon-zitixiabiao"
              data-name="script"
              data-value="sub"></view>
        <view :class="formats.script === 'super' ? 'ql-active' : ''"
              class="iconfont icon-zitishangbiao"
              data-name="script"
              data-value="super"></view>
        <view class="iconfont icon-shanchu"
              @tap="clear"></view>
        <view :class="formats.direction === 'rtl' ? 'ql-active' : ''"
              class="iconfont icon-direction-rtl"
              data-name="direction"
              data-value="rtl"></view>
      </view>
      <view class="content-wrapper"
            :style="{ height: (height - 140) + 'px' }">
        <editor id="editor"
                class="ql-container"
                placeholder="开始输入..."
                show-img-size
                show-img-toolbar
                show-img-resize
                @statuschange="onStatusChange"
                @input="onInput"
                :read-only="readOnly"
                @ready="onEditorReady">
        </editor>
      </view>
    </view>
  </view>
</template>
<script setup>
  import { ref, computed, watch } from "vue";
  // import { QuillEditor } from "@vueup/vue-quill";
  import "@vueup/vue-quill/dist/vue-quill.snow.css";
  import { getToken } from "@/utils/auth";
<script>
  export default {
    props: {
      modelValue: {
        type: String,
        default: "",
      },
      height: {
        type: Number,
        default: 300,
      },
      readOnly: {
        type: Boolean,
        default: false,
      },
    },
    data() {
      return {
        formats: {},
        editorCtx: null,
      };
    },
    watch: {
      modelValue: {
        handler(newValue) {
          if (this.editorCtx && newValue) {
            this.editorCtx.setContents({ html: newValue });
          }
        },
        immediate: true,
      },
    },
    onLoad() {
      // #ifndef MP-BAIDU
      uni.loadFontFace({
        family: "Pacifico",
        source: 'url("https://sungd.github.io/Pacifico.ttf")',
      });
      // #endif
    },
    methods: {
      onEditorReady() {
        // #ifdef MP-BAIDU
        this.editorCtx =
          requireDynamicLib("editorLib").createEditorContext("editor");
        // #endif
  const props = defineProps({
    /* 编辑器的内容 */
    modelValue: {
      type: String,
        // #ifdef APP-PLUS || MP-WEIXIN || H5
        uni
          .createSelectorQuery()
          .select("#editor")
          .context(res => {
            this.editorCtx = res.context;
            // 初始化内容
            if (this.modelValue) {
              this.editorCtx.setContents({ html: this.modelValue });
            }
          })
          .exec();
        // #endif
      },
      onInput() {
        if (this.editorCtx) {
          this.editorCtx.getContents({
            success: res => {
              this.$emit("update:modelValue", res.html);
            },
          });
        }
      },
      undo() {
        if (this.editorCtx) {
          this.editorCtx.undo();
        }
      },
      redo() {
        if (this.editorCtx) {
          this.editorCtx.redo();
        }
      },
      format(e) {
        if (!this.editorCtx) return;
        let { name, value } = e.target.dataset;
        if (!name) return;
        this.editorCtx.format(name, value);
      },
      onStatusChange(e) {
        const formats = e.detail;
        this.formats = formats;
      },
      insertDivider() {
        if (this.editorCtx) {
          this.editorCtx.insertDivider({
            success: function () {
              console.log("insert divider success");
            },
          });
        }
      },
      clear() {
        uni.showModal({
          title: "清空编辑器",
          content: "确定清空编辑器全部内容?",
          success: res => {
            if (res.confirm && this.editorCtx) {
              this.editorCtx.clear({
                success: function (res) {
                  console.log("clear success");
                },
              });
            }
          },
        });
      },
      removeFormat() {
        if (this.editorCtx) {
          this.editorCtx.removeFormat();
        }
      },
      insertDate() {
        if (!this.editorCtx) return;
        const date = new Date();
        const formatDate = `${date.getFullYear()}/${
          date.getMonth() + 1
        }/${date.getDate()}`;
        this.editorCtx.insertText({
          text: formatDate,
        });
      },
      insertImage() {
        if (!this.editorCtx) return;
        uni.chooseImage({
          count: 1,
          success: res => {
            this.editorCtx.insertImage({
              src: res.tempFilePaths[0],
              alt: "图像",
              success: function () {
                console.log("insert image success");
              },
            });
          },
        });
      },
    },
    /* 高度 */
    height: {
      type: Number,
      default: null,
    },
    /* 最小高度 */
    minHeight: {
      type: Number,
      default: null,
    },
    /* 只读 */
    readOnly: {
      type: Boolean,
      default: false,
    },
    /* 上传文件大小限制(MB) */
    fileSize: {
      type: Number,
      default: 5,
    },
    /* 类型(base64格式、url格式) */
    type: {
      type: String,
      default: "url",
    },
  });
  const emit = defineEmits(["update:modelValue"]);
  const styles = computed(() => {
    let style = {};
    if (props.minHeight) {
      style.minHeight = `${props.minHeight}px`;
    }
    if (props.height) {
      style.height = `${props.height}px`;
    }
    return style;
  });
  const content = ref("");
  watch(
    () => props.modelValue,
    v => {
      if (v !== content.value) {
        content.value = v == undefined ? "<p></p>" : v;
      }
    },
    { immediate: true }
  );
  const options = {
    theme: "snow",
    bounds: document.body,
    debug: "warn",
    modules: {
      // 工具栏配置
      toolbar: [
        [{ align: [] }], // 对齐方式
        ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
        ["blockquote", "code-block"], // 引用  代码块
        [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
        [{ indent: "-1" }, { indent: "+1" }], // 缩进
        [{ size: ["small", false, "large", "huge"] }], // 字体大小
        [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
        [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
        ["clean"], // 清除文本格式
        ["link", "image", "video"], // 链接、图片、视频
      ],
    },
    placeholder: "请输入内容",
    readOnly: props.readOnly,
  };
</script>
<style>
  .editor-container {
    width: 100%;
  @import "./editor-icon.css";
  .editor-wrapper {
    background: #fff;
  }
  .editor-img-uploader {
    display: none;
  }
  .editor {
    width: 100%;
  }
  .quill-editor {
    border: 1px solid #e8e8e8;
    border-radius: 8px;
  .content-wrapper {
    background: #fff;
    overflow: hidden;
  }
  /* Quill编辑器样式 */
  :deep(.ql-toolbar.ql-snow) {
    border-bottom: 1px solid #e8e8e8;
    border-radius: 8px 8px 0 0;
    padding: 8px 12px;
  .iconfont {
    display: inline-block;
    padding: 8px 8px;
    width: 24px;
    height: 24px;
    cursor: pointer;
    font-size: 20px;
  }
  :deep(.ql-container.ql-snow) {
    min-height: 300px;
    border-radius: 0 0 8px 8px;
  .toolbar {
    box-sizing: border-box;
    border-bottom: 0;
    font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  }
  :deep(.ql-editor) {
    min-height: 300px;
    font-size: 14px;
  .ql-container {
    box-sizing: border-box;
    padding: 12px 15px;
    width: 100%;
    height: 100%;
    font-size: 16px;
    line-height: 1.5;
    padding: 12px;
  }
  /* 移动端适配 */
  @media (max-width: 768px) {
    :deep(.ql-toolbar.ql-snow) {
      padding: 6px 8px;
    }
    :deep(.ql-editor) {
      font-size: 13px;
      padding: 10px;
    }
  .ql-active {
    color: #06c;
  }
  /* 图片样式 */
  :deep(.ql-editor img) {
    max-width: 100%;
    height: auto;
    border-radius: 4px;
    margin: 8px 0;
  }
  /* 工具栏按钮样式 */
  :deep(.ql-toolbar.ql-snow .ql-button) {
    height: 28px;
    width: 28px;
    padding: 4px;
  }
  :deep(.ql-toolbar.ql-snow .ql-picker-label) {
    height: 28px;
    padding: 4px 8px;
  }
  /* 提示框样式 */
  :deep(.ql-snow .ql-tooltip[data-mode="link"])::before {
    content: "请输入链接地址:";
  }
  :deep(.ql-snow .ql-tooltip.ql-editing a.ql-action)::after {
    border-right: 0px;
    content: "保存";
    padding-right: 0px;
  }
  :deep(.ql-snow .ql-tooltip[data-mode="video"])::before {
    content: "请输入视频地址:";
  }
  /* 字体大小选项 */
  :deep(.ql-snow .ql-picker.ql-size .ql-picker-label)::before,
  :deep(.ql-snow .ql-picker.ql-size .ql-picker-item)::before {
    content: "14px";
  }
  :deep(.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"])::before,
  :deep(.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"])::before {
    content: "10px";
  }
  :deep(.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"])::before,
  :deep(.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"])::before {
    content: "18px";
  }
  :deep(.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"])::before,
  :deep(.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"])::before {
    content: "32px";
  }
  /* 标题选项 */
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-label)::before,
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-item)::before {
    content: "文本";
  }
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"])::before,
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"])::before {
    content: "标题1";
  }
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"])::before,
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"])::before {
    content: "标题2";
  }
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"])::before,
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"])::before {
    content: "标题3";
  }
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"])::before,
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"])::before {
    content: "标题4";
  }
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"])::before,
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"])::before {
    content: "标题5";
  }
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"])::before,
  :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"])::before {
    content: "标题6";
  }
  /* 字体选项 */
  :deep(.ql-snow .ql-picker.ql-font .ql-picker-label)::before,
  :deep(.ql-snow .ql-picker.ql-font .ql-picker-item)::before {
    content: "标准字体";
  }
  :deep(.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"])::before,
  :deep(.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"])::before {
    content: "衬线字体";
  }
  :deep(
      .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]
    )::before,
  :deep(
      .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]
    )::before {
    content: "等宽字体";
  }
</style>
</style>