ソースを参照

feat: 富文本添加

IlhamTahir 1 年間 前
コミット
4959e402b7

+ 1 - 0
components.d.ts

@@ -8,6 +8,7 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     Copyright: typeof import('./src/components/Copyright.vue')['default']
+    Editor: typeof import('./src/components/Editor.vue')['default']
     ImagePreviewer: typeof import('./src/components/ImagePreviewer.vue')['default']
     ImageUpload: typeof import('./src/components/ImageUpload.vue')['default']
     RegularPage: typeof import('./src/components/RegularPage.vue')['default']

+ 14 - 0
env.d.ts

@@ -1 +1,15 @@
 /// <reference types="vite/client" />
+import { SlateDescendant, SlateElement, SlateText, Toolbar } from '@wangeditor/editor'
+
+declare module '@wangeditor/editor' {
+  // 扩展 Text
+  interface SlateText {
+    text: string
+  }
+
+  // 扩展 Element
+  interface SlateElement {
+    type: string
+    children: SlateDescendant[]
+  }
+}

+ 6 - 1
src/api/file.ts

@@ -1,7 +1,7 @@
 import type { RequestMethodResponse, UploadFile } from 'tdesign-vue-next'
 import httpClient from '@/api/httpClient'
 
-export const uploadFile = async (files: UploadFile): Promise<RequestMethodResponse> => {
+export const uploadFileOld = async (files: UploadFile): Promise<RequestMethodResponse> => {
   return new Promise((resolve, reject) => {
       httpClient.uploadFile<{
         url: string
@@ -22,3 +22,8 @@ export const uploadFile = async (files: UploadFile): Promise<RequestMethodRespon
   })
 
 }
+
+
+export const uploadFile = async (file: File)  => {
+  return httpClient.uploadFile<{ url: string }>('/files/upload', file)
+}

+ 83 - 0
src/components/Editor.vue

@@ -0,0 +1,83 @@
+<script setup lang="ts">
+import '@wangeditor/editor/dist/css/style.css'
+
+// @ts-ignore
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
+import { useAppStore } from '@/stores/app'
+import type { IEditorConfig } from '@wangeditor/editor'
+import { uploadFile, uploadFileOld } from '@/api/file'
+
+
+const props = withDefaults(defineProps<{
+  modelValue: string
+  mode?: 'default' | 'simple'
+}>(), {
+  mode: 'default'
+})
+
+
+const emit = defineEmits<{
+  'update:modelValue': [string]
+}>()
+
+const editorRef = shallowRef()
+const valueHtml = computed({
+  get: () => props.modelValue,
+  set: (value) => emit('update:modelValue', value)
+})
+
+const appStore = useAppStore()
+
+const editorConfig: Partial<IEditorConfig> = {
+  placeholder: '请输入内容...',
+  MENU_CONF: {
+    uploadImage: {
+      async customUpload(file: File, insertFn: (url: string) => void) {
+        const result = await uploadFile(file)
+        insertFn(result.url)
+      }
+    }
+  }
+}
+const toolbarConfig = {}
+
+
+
+
+const handleCreated = (editor: typeof Editor) => {
+  editorRef.value = editor // 记录 editor 实例,重要!
+}
+
+// 组件销毁时,也及时销毁编辑器
+//
+
+onBeforeUnmount(() => {
+  const editor = editorRef.value
+  if (editor == null) return
+  editor.destroy()
+})
+
+</script>
+
+<template>
+  <div  class="w-full border-2">
+
+    <Toolbar
+      style="border-bottom: 1px solid #ccc"
+      :editor="editorRef"
+      :defaultConfig="toolbarConfig"
+      :mode="mode"
+    />
+    <Editor
+      style="height: 500px; overflow-y: hidden;"
+      v-model="valueHtml"
+      :defaultConfig="editorConfig"
+      :mode="mode"
+      @onCreated="handleCreated"
+    />
+  </div>
+</template>
+
+<style scoped>
+
+</style>

+ 5 - 1
src/pages/article/components/ArticleDialog.vue

@@ -10,6 +10,7 @@ import type {
 import { MessagePlugin } from 'tdesign-vue-next'
 import { updateArticle, createArticle } from '@/api/article'
 import { searchCategories } from '@/api/category'
+import Editor from '@/components/Editor.vue'
 const props = defineProps<{
   headerTitle?: String | null
   article: Article | null
@@ -134,6 +135,7 @@ fetchCategories('')
     :confirmBtn="confirmBtnText"
     :closeOnOverlayClick="false"
     @close="handleCloseDialog"
+    destroy-on-close
     @confirm="handleSave"
   >
     <t-space direction="vertical" style="width: 100%">
@@ -150,7 +152,9 @@ fetchCategories('')
             @search="fetchCategories"
           />
         </TFormItem>
-        <TFormItem label="内容:" name="content"><TTextarea v-model="articleData.content"></TTextarea> </TFormItem>
+        <TFormItem label="内容:" name="content">
+          <Editor v-model="articleData.content"></Editor>
+        </TFormItem>
       </TForm>
     </t-space>
   </t-dialog>

+ 2 - 2
src/pages/components/PicUploader.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { uploadFile } from '@/api/file'
+import { uploadFileOld } from '@/api/file'
 import { ref, watch } from 'vue'
 import type { UploadFile } from 'tdesign-vue-next'
 
@@ -37,7 +37,7 @@ watch(() => props.modelValue, (value) => {
 </script>
 
 <template>
-  <TUpload theme="image" accept="image/jpeg,image/png" :request-method="uploadFile" v-model="fileList">
+  <TUpload theme="image" accept="image/jpeg,image/png" :request-method="uploadFileOld" v-model="fileList">
   </TUpload>
 </template>