element-ui 源码调用接口跨域问题

今天在看 upload 组件源码时,在组件源码当中调用的本地启动的 nodejs 服务写的上传接口,遇到跨域问题:

问题一、在 upload.md 中调用 nodejs 服务中的 上传接口,控制台报跨域报错。

解决方法1:在根目录增加 vue.config.js 文件,解决控制台报错。

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000', // 后端地址
        changeOrigin: true,
        pathRewrite: {
            '^/api' : '/api'
        },
      }
    }
  }
};

问题二、基于上面的问题,控制台不再报错后,接口一直 404。

解决方法1:nodejs 增加 cor 组件,前端代码中使用域名+接口的形式调用,可以正常调用接口:

const Koa = require('koa');
const serve = require('koa-static');
const path = require('path');
const fs = require('fs').promises; // 使用 promises 版本的 fs
const { koaBody } = require('koa-body');
const jsonError = require('koa-json-error');
const parameter = require('koa-parameter');
const koajwt = require('koa-jwt');
const cors = require('@koa/cors'); // 新增:CORS 支持

const { connectDB } = require('./db/index.js');

const user = require('./routes/user.js');
const department = require('./routes/department.js');
const departmentApplication = require('./routes/departmentApplication.js');
const certificate = require('./routes/certificate.js');
const post = require('./routes/post.js');
const userApplication = require('./routes/userApplication.js');
const menu = require('./routes/menu.js');

const app = new Koa();

// ===== 添加 CORS 中间件 =====
app.use(
  cors({
    origin: 'http://localhost:8085', // 修改为你的前端地址
    credentials: true, // 允许携带 cookies 或 token
    allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowHeaders: ['Content-Type', 'Authorization'],
  })
);

// 静态资源中间件
app.use(serve(path.join(__dirname, 'uploads'))); // 上传文件目录
app.use(serve(path.join(__dirname, 'public'), { prefix: '/static' })); // 公共静态资源目录

// 处理根路径和 favicon.ico 请求
app.use(async (ctx, next) => {
  if (ctx.request.url === '/') {
    ctx.body = '<h1>Welcome to the Koa server</h1>';
    return;
  }

  if (ctx.request.url === '/favicon.ico') {
    ctx.type = 'image/x-icon';
    try {
      ctx.body = await fs.readFile(
        path.join(__dirname, 'public', 'favicon.ico')
      );
    } catch (err) {
      ctx.status = 404;
      ctx.body = 'Favicon not found';
    }
    return;
  }

  await next();
});

// JWT 校验中间件
app.use(
  koajwt({
    secret: 'jqh-server-jwt',
  }).unless({
    path: [
      /^\/api\/user\/login/, // 登录页面不做权限控制
      /^\/static\//, // public下的 static 开头的资源不做权限控制
      /^\/favicon\.ico$/, // favicon.ico 图标不做权限控制
    ],
  })
);

// 错误处理、body 解析、参数校验中间件
app.use(jsonError());
app.use(
  koaBody({
    multipart: true, // 支持文件上传
    formidable: {
      maxFileSize: 2000 * 1024 * 1024, // 限制文件上传大小,例如2GB
    },
  })
);
app.use(parameter(app));

// 连接数据库
connectDB();

// 注册路由
app.use(user.routes()).use(user.allowedMethods());
app.use(department.routes()).use(department.allowedMethods());
app
  .use(departmentApplication.routes())
  .use(departmentApplication.allowedMethods());
app.use(certificate.routes()).use(certificate.allowedMethods());
app.use(post.routes()).use(post.allowedMethods());
app.use(userApplication.routes()).use(userApplication.allowedMethods());
app.use(menu.routes()).use(menu.allowedMethods());

// 监听端口
app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

 前端部分:

<template>
  <div>
    <el-upload
      ref="uploadRef"
      :auto-upload="false"
      :on-change="handleAvatarChange"
      :show-file-list="false"
      class="upload-avatar-wrapper"
      accept="image/*"
      :action="uploadUrl"
    >
      <img
        v-if="form.avatar"
        :src="form.avatar"
        class="avatar"
        style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
      />
      <div
        v-else
        class="avatar-uploader-icon"
        style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
      >
        <i class="el-icon-plus"></i>
      </div>
    </el-upload>

    <el-button @click="customUpload">上传</el-button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        token:
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3MTE5NjEzLCJleHAiOjE3NDc3MjQ0MTN9.YQBUW1nt8MtVaTUE-sJ8R78h9ngbnzjmLZRk4kkUMHU',
        form: {
          operName: 'super001',
          password: '123456',
          avatar: '',
        },
        selectedFile: null,
        uploadUrl: 'http://localhost:3000/api/user/uploadAvatar',
      };
    },
    methods: {
      // 文件选择变化时
      handleAvatarChange(file) {
        const reader = new FileReader();
        reader.onload = (e) => {
          this.form.avatar = e.target.result; // 正确赋值
        };
        if (file.raw) {
          reader.readAsDataURL(file.raw);
          this.selectedFile = file.raw;
        }
      },

      // 自定义上传方法(由按钮触发)
      customUpload() {
        if (!this.selectedFile) {
          this.$message.warning('请先选择一个文件');
          return;
        }

        const formData = new FormData();
        formData.append('operName', this.form.operName);
        formData.append('password', this.form.password);
        formData.append('avatar', this.selectedFile);

        fetch(this.uploadUrl, {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${this.token}`,
          },
          body: formData,
          credentials: 'include',
        })
          .then((response) => {
            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
          })
          .then((data) => {
            console.log('Success:', data);
            this.selectedFile = null;
            this.$message.success('上传成功');
          })
          .catch((error) => {
            console.error('Error:', error);
            this.$message.error('上传失败');
          });
      },
    },
  };
</script>

<style>
  /* 样式部分保持不变 */
  .upload-avatar-wrapper {
    width: 60px;
    height: 60px;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .upload-avatar-wrapper:hover {
    border-color: #409eff;
  }

  .avatar-uploader-icon {
    font-size: 28px;
  }

  .avatar {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
</style>

解决方法2:使用的是 npm run dev 启动的项目,通过 package.json 可以看到所使用的 webpack 配置文件。

找到 webpack.demo.js 配置文件:

后端 nodejs 代码不用使用 cor 组件,前端代码如下:

<template>
  <div>
    <el-upload
      ref="uploadRef"
      :auto-upload="false"
      :on-change="handleAvatarChange"
      :show-file-list="false"
      class="upload-avatar-wrapper"
      accept="image/*"
      :action="uploadUrl"
    >
      <img
        v-if="form.avatar"
        :src="form.avatar"
        class="avatar"
        style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
      />
      <div
        v-else
        class="avatar-uploader-icon"
        style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
      >
        <i class="el-icon-plus"></i>
      </div>
    </el-upload>

    <el-button @click="customUpload">上传</el-button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        token:
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3MTE5NjEzLCJleHAiOjE3NDc3MjQ0MTN9.YQBUW1nt8MtVaTUE-sJ8R78h9ngbnzjmLZRk4kkUMHU',
        form: {
          operName: 'super001',
          password: '123456',
          avatar: '',
        },
        selectedFile: null,
        uploadUrl: '/api/user/uploadAvatar',
      };
    },
    methods: {
      // 文件选择变化时
      handleAvatarChange(file) {
        const reader = new FileReader();
        reader.onload = (e) => {
          this.form.avatar = e.target.result; // 正确赋值
        };
        if (file.raw) {
          reader.readAsDataURL(file.raw);
          this.selectedFile = file.raw;
        }
      },

      // 自定义上传方法(由按钮触发)
      customUpload() {
        if (!this.selectedFile) {
          this.$message.warning('请先选择一个文件');
          return;
        }

        const formData = new FormData();
        formData.append('operName', this.form.operName);
        formData.append('password', this.form.password);
        formData.append('avatar', this.selectedFile);

        fetch(this.uploadUrl, {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${this.token}`,
          },
          body: formData,
          credentials: 'include',
        })
          .then((response) => {
            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
          })
          .then((data) => {
            console.log('Success:', data);
            this.selectedFile = null;
            this.$message.success('上传成功');
          })
          .catch((error) => {
            console.error('Error:', error);
            this.$message.error('上传失败');
          });
      },
    },
  };
</script>

<style>
  /* 样式部分保持不变 */
  .upload-avatar-wrapper {
    width: 60px;
    height: 60px;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .upload-avatar-wrapper:hover {
    border-color: #409eff;
  }

  .avatar-uploader-icon {
    font-size: 28px;
  }

  .avatar {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
</style>

 

总结:建议使用前端增加反向代理的方式。产生问题的原因,对源码当中的项目启动配置不熟悉,想当然的增加 vue.config.js 配置,以为可以解决问题,行不通时,考虑从后端代码解决问题。问题解决完成之后,已经有了退路,冷静下来寻找问题的原因,发现启动脚本 npm run dev 中使用的 build 文件夹下的 webpack.demo.js 配置,在这个配置中增加反向代理并重启后,跨域问题解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jqh_0484

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值