From 2643443d1609c7da11fb5785af6ef71fcc5d5020 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期五, 23 一月 2026 14:52:20 +0800
Subject: [PATCH] 富文本编辑器引入

---
 src/components/Editor/index.vue |  545 ++++++++++++++++++++++++++++++-----------------------
 1 files changed, 306 insertions(+), 239 deletions(-)

diff --git a/src/components/Editor/index.vue b/src/components/Editor/index.vue
index 952c1d8..499b11a 100644
--- a/src/components/Editor/index.vue
+++ b/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,
-    },
-    /* 绫诲瀷锛坆ase64鏍煎紡銆乽rl鏍煎紡锛� */
-    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"], // 寮曠敤  浠g爜鍧�
-        [{ 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>
\ No newline at end of file
+</style>

--
Gitblit v1.9.3