gemercheung 6 mesiacov pred
rodič
commit
bc696941b1

+ 48 - 37
packages/frontend/src/views/article/add.vue

@@ -7,55 +7,44 @@
     </template>
 
     <div class="editor-wrap">
-      <n-form
-        ref="modalFormRef" class="form wh-full" label-placement="left" label-align="left" :label-width="80"
-        :model="modalForm"
-      >
-        <n-form-item
-          label="文章名称" path="title" :rule="{
-            required: true,
-            message: '请输入文章名称',
-            trigger: ['input', 'blur'],
-          }"
-        >
+      <n-form ref="modalFormRef" class="form wh-full" label-placement="left" label-align="left" :label-width="80"
+        :model="modalForm">
+        <n-form-item label="文章名称" path="title" :rule="{
+          required: true,
+          message: '请输入文章名称',
+          trigger: ['input', 'blur'],
+        }">
           <n-input v-model:value="modalForm.title" />
         </n-form-item>
 
-        <n-form-item
-          label="文章分类" path="categoryId" :rule="{
-            required: true,
-            type: 'number',
-            trigger: ['change', 'blur'],
-            message: '请输入文章分类',
-          }"
-        >
-          <n-tree-select
-            v-model:value="modalForm.categoryId"
-            :options="allCategory"
-            label-field="title"
-            key-field="id"
-            placeholder="根分类"
-            clearable
-            style="max-width: 300px;"
-          />
+        <n-form-item label="文章分类" path="categoryId" :rule="{
+          required: true,
+          type: 'number',
+          trigger: ['change', 'blur'],
+          message: '请输入文章分类',
+        }">
+          <n-tree-select v-model:value="modalForm.categoryId" :options="allCategory" label-field="title" key-field="id"
+            placeholder="根分类" clearable style="max-width: 300px;" />
         </n-form-item>
 
         <n-tabs type="line" animated>
           <template v-for="(lang, index) in langs" :key="lang">
             <n-tab-pane :name="lang" :tab="langLabel[lang]" :index="index">
-              <n-form-item
-                label="文章名称" path="title" :rule="{
-                  required: true,
-                  message: '请输入文章名称',
-                  trigger: ['input', 'blur'],
-                }"
-              >
+              <n-form-item label="文章名称" path="title" :rule="{
+                required: true,
+                message: '请输入文章名称',
+                trigger: ['input', 'blur'],
+              }">
                 <n-input v-model:value="modalForm.translations[index].title" />
               </n-form-item>
               <div class="h-450">
-                <VividEditor v-model="modalForm.translations[index].content" :dark="isDark">
+                <VividEditor v-model="modalForm.translations[index].content" :dark="isDark"
+                  :handle-image-upload="handleUpload" :handle-video-upload="handleVideoUpload" >
                   <SlashCommand />
                   <DragHandle />
+                  <!-- <template #menu>
+                    <ImageExt />
+                  </template> -->
                 </VividEditor>
               </div>
             </n-tab-pane>
@@ -67,7 +56,7 @@
 </template>
 
 <script setup>
-import { DragHandle, SlashCommand, VividEditor } from '@4dkankan/vivid'
+import { DragHandle, ImageExt, SlashCommand, VividEditor } from '@4dkankan/vivid'
 import { useUserStore } from '@/store/index.js'
 import { initTranslations, langLabel, langs } from '@/utils/translations'
 import { useDark } from '@vueuse/core'
@@ -120,6 +109,28 @@ function handleAdd() {
     }
   })
 }
+function handleUpload(file) {
+  // eslint-disable-next-line no-async-promise-executor
+  return new Promise(async (resolve) => {
+    // console.log('handleUpload', file)
+    const data = new FormData()
+    // data.append('file', file)
+    const res = await articleApi.uploadImage(data)
+    // console.log('res', res)
+    resolve(res.data)
+  })
+}
+function handleVideoUpload(file) {
+  // eslint-disable-next-line no-async-promise-executor
+  return new Promise(async (resolve) => {
+    // console.log('handleUpload', file)
+    const data = new FormData()
+    data.append('file', file)
+    const res = await articleApi.uploadImage(data)
+    // console.log('res', res)
+    resolve(res.data)
+  })
+}
 </script>
 
 <style>

+ 5 - 1
packages/frontend/src/views/article/api.js

@@ -7,5 +7,9 @@ export default {
   delete: id => request.delete(`/article/${id}`),
   getOne: id => request.get(`/article/detail/${id}`),
   getAll: data => request.get('/article?enable=1', data),
-
+  uploadImage: data => request.post('/menu/cover/upload', data, {
+    headers: {
+      'Content-Type': 'multipart/form-data',
+    },
+  }),
 }

+ 34 - 11
packages/frontend/src/views/article/edit.vue

@@ -7,48 +7,48 @@
     </template>
 
     <div class="editor-wrap">
-      <n-form
+      <n-form
         ref="modalFormRef" class="form wh-full" label-placement="left" label-align="left" :label-width="80"
-        :model="modalForm"
+        :model="modalForm"
       >
-        <n-form-item
+        <n-form-item
           label="文章名称" path="title" :rule="{
             required: true,
             message: '请输入文章名称',
             trigger: ['input', 'blur'],
-          }"
+          }"
         >
           <n-input v-model:value="modalForm.title" />
         </n-form-item>
 
-        <n-form-item
+        <n-form-item
           label="文章分类" path="categoryId" :rule="{
             required: true,
             type: 'number',
             trigger: ['change', 'blur'],
             message: '请输入文章分类',
-          }"
+          }"
         >
-          <n-tree-select
+          <n-tree-select
             v-model:value="modalForm.categoryId" :options="allCategory" label-field="title" key-field="id"
-            placeholder="根分类" clearable
+            placeholder="根分类" clearable
           />
         </n-form-item>
 
         <n-tabs type="line" animated>
           <template v-for="(lang, index) in langs" :key="lang">
             <n-tab-pane :name="lang" :tab="langLabel[lang]" :index="index">
-              <n-form-item
+              <n-form-item
                 label="文章名称" path="title" :rule="{
                   required: true,
                   message: '请输入文章名称',
                   trigger: ['input', 'blur'],
-                }"
+                }"
               >
                 <n-input v-model:value="modalForm.translations[index].title" />
               </n-form-item>
               <div class="h-450">
-                <VividEditor v-model="modalForm.translations[index].content" :dark="isDark">
+                <VividEditor v-model="modalForm.translations[index].content" :dark="isDark" :handle-image-upload="handleUpload" :handle-video-upload="handleVideoUpload">
                   <SlashCommand />
                   <DragHandle />
                 </VividEditor>
@@ -123,6 +123,29 @@ function handleEdit() {
     }
   })
 }
+
+function handleUpload(file) {
+  // eslint-disable-next-line no-async-promise-executor
+  return new Promise(async (resolve) => {
+    console.log('handleUpload', file)
+    const data = new FormData()
+    data.append('file', file)
+    const res = await articleApi.uploadImage(data)
+    console.log('res', res)
+    resolve(res.data)
+  })
+}
+function handleVideoUpload(file) {
+  // eslint-disable-next-line no-async-promise-executor
+  return new Promise(async (resolve) => {
+    // console.log('handleUpload', file)
+    const data = new FormData()
+    data.append('file', file)
+    const res = await articleApi.uploadImage(data)
+    // console.log('res', res)
+    resolve(res.data)
+  })
+}
 </script>
 
 <style>

+ 93 - 78
packages/vivid/src/core/components/VividSimpleUpload.vue

@@ -1,56 +1,71 @@
 <script setup>
-  import { computed, nextTick, ref } from "vue";
-  import { useThemeVars, NUploadDragger, NUpload } from "naive-ui";
-  // 启用中文
-  const vars = useThemeVars();
-  const props = defineProps({
-    type: {
-      type: String,
-      default: "image", // image 或  video  或  audio
-    },
-  });
+import { computed, nextTick, ref } from "vue";
+import { useThemeVars, NUploadDragger, NUpload } from "naive-ui";
+// 启用中文
+const vars = useThemeVars();
+const props = defineProps({
+  type: {
+    type: String,
+    default: "image", // image 或  video  或  audio
+  },
+});
 
-  const accept = computed(() => {
-    switch (props.type) {
-      case "image":
-        return "image/*";
-      case "video":
-        return "video/*";
-      case "audio":
-        return "audio/*";
-    }
-  });
+const accept = computed(() => {
+  switch (props.type) {
+    case "image":
+      return "image/*";
+    case "video":
+      return "video/*";
+    case "audio":
+      return "audio/*";
+  }
+});
 
-  const fileValue = ref(null);
-  const fileBlob = ref(null);
-  const emit = defineEmits(["change"]);
+const fileValue = ref(null);
+const fileBlob = ref(null);
+const emit = defineEmits(["change"]);
 
-  function handleSelect(event) {
-    fileValue.value = URL.createObjectURL(event.file.file);
-    fileBlob.value = event.file.file;
-    nextTick(() => {
-      emit("change", fileBlob.value);
-    });
-  }
+function handleSelect(event) {
+  fileValue.value = URL.createObjectURL(event.file.file);
+  fileBlob.value = event.file.file;
+  nextTick(() => {
+    emit("change", fileBlob.value);
+  });
+}
 
-  function handleDel() {
-    fileValue.value = null;
-    fileBlob.value = null;
-    nextTick(() => {
-      emit("change", fileBlob.value);
-    });
-  }
+function handleDel() {
+  fileValue.value = null;
+  fileBlob.value = null;
+  nextTick(() => {
+    emit("change", fileBlob.value);
+  });
+}
 
-  function getFile() {
-    return fileBlob.value;
+function getFile() {
+  return fileBlob.value;
+}
+function beforeUpload({ file }) {
+  const MAX_FILE_SIZE_IMAGE = 5 * 1024 * 1024
+  const MAX_FILE_SIZE_VIDEO = 20 * 1024 * 1024
+  console.log('file', file.type)
+  if (file.file.size > MAX_FILE_SIZE_IMAGE && String(file.type).includes('image')) {
+    $message.error('图像文件大小不能超过 5MB')
+    return false
+  } else if (file.file.size > MAX_FILE_SIZE_VIDEO && String(file.type).includes('video')) {
+    $message.error('视频文件大小不能超过 20MB')
+    return false
   }
-
-  defineExpose({ getFile });
+  else {
+    return true
+  }
+}
+defineExpose({ getFile });
 </script>
 
 <template>
   <div class="hb-su-wrap">
-    <n-upload v-if="fileValue === null" :show-file-list="false" :accept="accept" @change="handleSelect">
+    <n-upload @before-upload="beforeUpload" v-if="fileValue === null" :show-file-list="false" :accept="accept"
+      @change="handleSelect">
       <n-upload-dragger>
         <div class="upload-drag-placeholder" v-if="props.type === 'image'">
           <i class="ri-image-add-line ri-2x"></i>
@@ -73,45 +88,45 @@
 </template>
 
 <style scoped>
-  .hb-su-wrap {
-    height: 100%;
-    width: 100%;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-  }
+.hb-su-wrap {
+  height: 100%;
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
 
-  .hb-su-preview {
-    width: 400px;
-    height: 100%;
-    position: relative;
-    box-sizing: border-box;
-  }
+.hb-su-preview {
+  width: 400px;
+  height: 100%;
+  position: relative;
+  box-sizing: border-box;
+}
 
-  .hb-su-close {
-    font-size: 12px;
-    color: #fff;
-    padding: 0 4px;
-    border-radius: 50%;
-    background-color: rgba(0, 0, 0, 0.7);
-    position: absolute;
-    top: 10px;
-    right: 10px;
-    z-index: 2;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    cursor: pointer;
-  }
+.hb-su-close {
+  font-size: 12px;
+  color: #fff;
+  padding: 0 4px;
+  border-radius: 50%;
+  background-color: rgba(0, 0, 0, 0.7);
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  z-index: 2;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+}
 
-  .hb-su-close:hover {
-    background-color: rgba(0, 0, 0, 0.8);
-  }
+.hb-su-close:hover {
+  background-color: rgba(0, 0, 0, 0.8);
+}
 
-  .upload-drag-placeholder{
-    display: flex;
-    flex-direction: column;
-    gap:10px;
+.upload-drag-placeholder {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
 
-  }
+}
 </style>

+ 9 - 1
packages/vivid/src/editor/Editor.vue

@@ -45,6 +45,14 @@
       type: Boolean,
       default: false,
     },
+    handleImageUpload: {
+			type: Function,
+			required: false,
+		},
+		handleVideoUpload: {
+			type: Function,
+			required: false,
+		},
   });
 
   let internalExt: (Extension | Node | Mark)[] = [];
@@ -243,7 +251,7 @@
         >
           <div :class="{ 'editor-readonly': readonly }" style="width: 100%">
             <slot name="menu" :readonly="readonly">
-              <vivid-menu class="editor-header" :editor="editor" />
+              <vivid-menu class="editor-header" :editor="editor" :handleImageUpload="handleImageUpload" :handleVideoUpload="handleVideoUpload"/>
             </slot>
           </div>
           <div class="editor-page" v-if="page">

+ 12 - 3
packages/vivid/src/editor/components/VividMenu.vue

@@ -44,7 +44,16 @@
 		TrailingNodeExt,
 	} from "../../core/extension";
 	import { useThemeVars } from "naive-ui";
-
+	const props = defineProps({
+		handleImageUpload: {
+			type: Function,
+			required: false,
+		},
+		handleVideoUpload: {
+			type: Function,
+			required: false,
+		},
+	})
 	const vars = useThemeVars();
 </script>
 <template>
@@ -83,8 +92,8 @@
 		<superscript-ext />
 		<divider-ext />
 		<math-ext />
-		<image-ext />
-		<video-ext />
+		<image-ext :handleUpload="handleImageUpload" />
+		<video-ext :handleUpload="handleVideoUpload"/>
 		<link-ext />
 		<divider-ext />
 		<table-ext />