vue2的wangEditor实现图片上传(本地图片)含后端源码

最近在写毕业设计要用到富文本编辑器,实现图片上传官方文档有点难懂,有些注释之类的不太理解,(可能是我这两个星期才看完vue2的教学视频很多没有看过的原因,有用词不对的地方多多见谅)于是看了多个文章和视频终于解决了!

(已二编)

前面为解决过程和我自己的理解,不敢兴趣可之间跳到后面,是全部要用的源码,旁边有目录

当然可以去看让我茅塞顿开的b站一个视频,不过是vue3的但是大差不差

Vue3入门项目-104-富文本编辑-上传图片丨讲师·景水_哔哩哔哩_bilibili

理解和解释过程

我废话比较多,主打一个我当时找文章希望有人解答的,我都在我的文章里面解答了

ok,那么我就从头开始说

首先根据官方文档

看到这里的时候一脸懵逼因为我学的vue2好像没有怎么写的,一时摸不准是什么

于是去看了官方视频发现是另外的前端语言的语法(好像),如果是vue2就在

找到这个变量按照这个格式写,就是上面是.的形式我们是对象里面嵌套对象

然后在这里面写server对你的后端地址

然后到了这里我看官方视频就搞定了,我寻思着我应该也可以了,但是我又不确定后端写啥,于是就找通义给了些关键词生成了一下,然后改了改

后端存图片

后端全部源码

package cn.yangeit.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/upload") // 确保路径与前端请求的路径一致
public class SimpleFileUploadController {

    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, Object>> handleFileUpload(
            @RequestParam(value = "your-custom-name", required = true) MultipartFile file,
            @RequestParam(value = "token", required = false) String token,
            @RequestParam(value = "otherKey", required = false) String otherKey) throws IOException {
        System.out.println("Received file: " + file.getOriginalFilename());

        // 检查是否有文件被上传
        if (file.isEmpty()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(createErrorResponse("No file provided."));
        }

        // 获取文件原始名称
        String originalFilename = file.getOriginalFilename();
        if (originalFilename == null || originalFilename.trim().isEmpty()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(createErrorResponse("Invalid file name."));
        }

        // 检查文件类型是否为图片
        String contentType = file.getContentType();
        if (contentType == null || !contentType.startsWith("image/")) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(createErrorResponse("Invalid file type. Only images are allowed."));
        }

        // 模拟成功上传,实际应用中应该进行文件的保存等操作

        String filePath = "D:\\Code\\wenjian" +
                "\\graduation_project\\src\\assets\\" + originalFilename;
        File locaFile=new File(filePath);
        file.transferTo(locaFile);
        File file1=new File(filePath);
            System.out.println(file1.exists());



        Map<String, Object> response = new HashMap<>();
        response.put("errno", 0);
        Map<String, String> data = new HashMap<>();
        data.put("url",originalFilename);
        data.put("alt", "Sample Alt Text");
        response.put("data", data);

        return ResponseEntity.ok().body(response);
    }

    private Map<String, Object> createErrorResponse(String message) {
        Map<String, Object> response = new HashMap<>();
        response.put("errno", 1);
        response.put("message", message);
        return response;
    }
}

但是我这里出现了两个问题,还有一个上传图片后端存储前端出现的一个小问题

跨域问题

其实我也不太了解报了这个错之后我就百度了一下,大致是因为我前后端同时开,端口不一致之类的,解决如下

在文件里面写下面的代码就可以解决了

package cn.yangeit;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();

        // 允许来自 http://localhost:8080 的请求
        config.setAllowedOrigins(Arrays.asList("http://localhost:8081")); // 替换为你实际的前端域名

        // 允许携带凭证(如 cookies)
        config.setAllowCredentials(true);

        // 允许所有请求头
        config.addAllowedHeader("*");

        // 允许所有 HTTP 方法
        config.addAllowedMethod("*");

        // 预检请求的有效期(单位:秒)
        config.setMaxAge(3600L);

        source.registerCorsConfiguration("/**", config);

        return new CorsFilter(source);
    }
}

不过上面有一个地方可能要改

2.这个报错是我自己粗心的问题

后端和前台报不一致错误

vue报的

Upload error at Object.getResponseError (webpack-internal:///./node_modules/@wangeditor/editor/dist/index.esm.js:80:59011) at XMLHttpRequest.eval (webpack-internal:///./node_modules/@wangeditor/editor/dist/index.esm.js:80:65265)

后台前面报的

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Thu Sep 05 21:28:28 CST 2024

There was an unexpected error (type=Method Not Allowed, status=405).

找了半天才发现是我前端给的键和后台对不上

两种解决方法

后台的改了

或者前端将传递过去的建名改了

按理来说应该就欧克了,但是你上传图片会发现图片没有,然后我之前看视频好像有个成功的人上传的是那种有http,互联网输入地址可以访问的,于是我猜可能需要改一下wangEditor接收到后台返回信息处理到vue2显示的的代码(这个话可能有点乱,不用理解)

在之前放server的地方添加一个customInsert(res,insertFn)的方法,下面解释有点乱请见谅

这个地方有一个点,我前面说了我后台暂时没有实现图片存放到本地文件夹的操作,我是打算之后后端接收图片就存在assets文件下,所以为了测试功能我先存了一部分图片在assets下,然后桌面建的文件里面有一批一样的图片,所以我点上传桌面的实际上获取的是assets里的(不知道可不可以看懂)

二编后就是直接在后端存图片到前端这个位置

后端存储图片前端显示问题

当我实现了后端存储后,我在前端点击上传图片先是上传已经存在文件夹下的,成功了,但是当我上传一个没有提前存的报了下面的错

index.esm.js:23  wangEditor upload file - onSuccess error Error: Cannot find module './a1-ji2.jpeg'
    at webpackContextResolve (app.2c52cdfcb5e9abdf.hot-update.js:28:11)
    at webpackContext (app.2c52cdfcb5e9abdf.hot-update.js:23:11)
    at customInsert (MyEditor.vue:52:1)
    at onSuccess (index.esm.js:16:1)
    at Function.eval (index.esm.js:23:1)
    at eval (Translator.js:50:1)
    at e.emit (Translator.js:50:1)
    at am.emit (Translator.js:50:1)
    at eval (Translator.js:50:1)
    at Array.forEach (<anonymous>)

我就奇怪,想是不是没有存储成功,然后去看了下前端文件夹目录发现图片已经在哪里了,然后我又插入那张图片了一次,发现可以了

于是我就猜测可能是后端存好发到前端的时候,前端还没有加载好

于是去网上找了好久怎么处理

要将

改成

不是很建议用有一个前端的延迟的函数,那个找不到了给你们看不了,反正用那个的话连续上传两张图片的话会报一个错,就用我这个是OK的,回头要是找到了补上

源码

后台目录

注意后端代码并没有实现将图片存放到本地文件夹需要自己写,后面写完了我会二编修改这部分代码

package cn.yangeit.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/upload") // 确保路径与前端请求的路径一致
public class SimpleFileUploadController {

    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, Object>> handleFileUpload(
            @RequestParam(value = "your-custom-name", required = true) MultipartFile file,
            @RequestParam(value = "token", required = false) String token,
            @RequestParam(value = "otherKey", required = false) String otherKey) throws IOException {
        System.out.println("Received file: " + file.getOriginalFilename());

        // 检查是否有文件被上传
        if (file.isEmpty()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(createErrorResponse("No file provided."));
        }

        // 获取文件原始名称
        String originalFilename = file.getOriginalFilename();
        if (originalFilename == null || originalFilename.trim().isEmpty()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(createErrorResponse("Invalid file name."));
        }

        // 检查文件类型是否为图片
        String contentType = file.getContentType();
        if (contentType == null || !contentType.startsWith("image/")) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(createErrorResponse("Invalid file type. Only images are allowed."));
        }

        // 模拟成功上传,实际应用中应该进行文件的保存等操作

        String filePath = "D:\\Code\\wenjian" +
                "\\graduation_project\\src\\assets\\" + originalFilename;
        File locaFile=new File(filePath);
        file.transferTo(locaFile);
        File file1=new File(filePath);
            System.out.println(file1.exists());



        Map<String, Object> response = new HashMap<>();
        response.put("errno", 0);
        Map<String, String> data = new HashMap<>();
        data.put("url",originalFilename);
        data.put("alt", "Sample Alt Text");
        response.put("data", data);

        return ResponseEntity.ok().body(response);
    }

    private Map<String, Object> createErrorResponse(String message) {
        Map<String, Object> response = new HashMap<>();
        response.put("errno", 1);
        response.put("message", message);
        return response;
    }
}

跨域解决 

package cn.yangeit;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();

        // 允许来自 http://localhost:8080 的请求
        config.setAllowedOrigins(Arrays.asList("http://localhost:8081")); // 替换为你实际的前端域名

        // 允许携带凭证(如 cookies)
        config.setAllowCredentials(true);

        // 允许所有请求头
        config.addAllowedHeader("*");

        // 允许所有 HTTP 方法
        config.addAllowedMethod("*");

        // 预检请求的有效期(单位:秒)
        config.setMaxAge(3600L);

        source.registerCorsConfiguration("/**", config);

        return new CorsFilter(source);
    }
}

 不过上面有一个地方可能要改

前台目录

<template>
  <div  class="app">
    <Toolbar
      style="border-bottom: 1px solid #ccc"
      :editor="editor"
      :defaultConfig="toolbarConfig"
      :mode="mode"
    />
    <div class="editor" ref="editorContainer" >
      <MyTitle></MyTitle>
      <Editor
        style="height: auto; overflow-y: hidden;min-height: 700px;"
        v-model="html"
        :defaultConfig="editorConfig"
        :mode="mode"
        @onCreated="onCreated"
      />
    </div>
    <ArticleDetails></ArticleDetails>
  </div>
</template>

<script>
import Vue from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import MyTitle from './MyTitle.vue'
import ArticleDetails from './ArticleDetails.vue'

export default Vue.extend({
  components: { Editor, Toolbar, MyTitle, ArticleDetails },
  data () {
    return {
      editor: null, // 实例
      html: '', // 默认内容
      toolbarConfig: {
        excludeKeys: [
          'group-video',
          'todo'
        ]
      }, // 工具栏配置
      editorConfig: {
        placeholder: '请输入内容...',
        MENU_CONF: {
          uploadImage: {
            server: 'http://localhost:8080/upload',
            fieldName: 'your-custom-name',
            customInsert (res, insertFn) {
              import(`@/assets/${res.data.url}`).then((imageModule) => {
                // 使用 imageModule.default 或 imageModule.name 来获取图片
                insertFn(imageModule.default || imageModule)
              })
              console.log(res.data.url)
            }
          }
        },
        scroll: false
      }, // 编辑器配置
      mode: '' // or 'simple'
      // title: ''
    }
  },
  watch: {
    html: function (newVal) {
      this.adjustHeight()
    }
  },
  methods: {
    onCreated (editor) {
      this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
      this.adjustHeight()
    },
    getHtml () { // 获取html
      return this.html
    },
    adjustHeight () {
    }
  },
  // mounted () {
  //   // 模拟 ajax 请求,异步渲染编辑器
  //   setTimeout(() => {
  //     this.html = '<p>模拟 Ajax 异步设置内容 HTML</p>'
  //   }, 1500)
  // },
  beforeDestroy () {
    const editor = this.editor
    if (editor == null) return
    editor.destroy() // 组件销毁时,及时销毁编辑器
  }
})
</script>

<style src="@wangeditor/editor/dist/css/style.css"></style>
<style>
.w-e-toolbar {
  background-color: rgb(245, 246, 247)!important;
  display: flex; /* 使用Flexbox布局 */
  justify-content: center; /* 水平居中 */
  align-items: center; /* 垂直居中 */
}

.w-e-bar {
  display: flex; /* 使用Flexbox布局 */
  justify-content: center; /* 水平居中 */
}
</style>

<style scoped>
.app {
  border: 1px solid #ccc;
  background-color: rgb(245, 246, 247);
  width: 1450px;
   /* 固定宽度 */
  margin: 0 auto; /* 居中 */
  position: relative;
  overflow-x: auto; /* 水平滚动条 */
  overflow-y: hidden; /* 隐藏垂直滚动条 */
}

.editor {
  position: relative; /* 设置相对定位,使得内部绝对定位元素能够相对于此元素定位 */
  width: 800px; /* 固定宽度 */
  margin: 0 auto; /* 居中 */
  margin-top: 30px;
  margin-bottom: 30px;
  background-color: white;
  padding: 20px 50px;
  box-sizing: border-box; /* 包括padding和border在内 */
}

</style>

这个地方有一个点,我前面说了我后台实现图片存放到本地文件夹的操作,我是在后端接收图片就存在assets文件下

最后要是没有成功可以评论或者私信

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值