不吃猫的鱼y 2023-11-09 19:08 采纳率: 81%
浏览 48
已结题

springboot+vue3前后端分离项目,前端上传图片,加载失败

问题:图片加载失败

img


前端报错

img

img


直接点图片地址打开显示如下

img


后台可以看到图片地址,也打不开

img


配置类

package com.itmk.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
    @Value("${web.load-path}")
    private String loadPath;


//    跨域配置
    @Override
    public void addCorsMappings(CorsRegistry registry){
    registry.addMapping( "/**")
        .allowedOriginPatterns ("*")
        .allowedMethods("*")
        .allowedHeaders("*")
        .maxAge (3600)
        .allowCredentials (true);}

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/images/**")
                .addResourceLocations("file:D:/images/");

    }
}


后端controller

package com.itmk.web.goods.controller;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itmk.utils.ResultUtils;
import com.itmk.utils.ResultVo;
import com.itmk.web.goods.entity.GoodsParm;
import com.itmk.web.goods.entity.ListParm;
import com.itmk.web.goods.entity.SysGoods;
import com.itmk.web.goods.service.SysGoodsService;
import com.itmk.web.goods_specs.entity.SysGoodsSpecs;
import com.itmk.web.goods_specs.sevice.SysGoodsSpecsService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/goods")
public class SysGoodsController {
    @Autowired
    private SysGoodsService sysGoodsService;
    @Autowired
    private SysGoodsSpecsService sysGoodsSpecsService;
    //新增
    @PostMapping
    public ResultVo add(@RequestBody GoodsParm parm){
        sysGoodsService.saveGoods((parm));
        return ResultUtils.success("新增成功!");
    }
    @GetMapping("/list")
    public ResultVo getList(ListParm parm) {
            //构造分页对象
        IPage<SysGoods> page = new Page<>(parm.getCurrentPage(), parm.getPageSize());
            //构造查询条件
        QueryWrapper<SysGoods> query = new QueryWrapper<>();
        query.lambda().like(StringUtils.isNotEmpty(parm.getGoodsName()), SysGoods::getGoodsName, parm.getGoodsName())
                .orderByDesc(SysGoods::getOrderNum);
        IPage<SysGoods> list = sysGoodsService.page(page, query);
        if (list.getRecords().size() >0){
            for (int i =0;i<list.getRecords().size();i++){
                QueryWrapper<SysGoodsSpecs> queryWrapper = new QueryWrapper<>();
                queryWrapper.lambda().eq(SysGoodsSpecs::getGoodsId,list.getRecords().get(i).getGoodsId())
                                .orderByAsc(SysGoodsSpecs::getOrderNum);
                List<SysGoodsSpecs> specs=sysGoodsSpecsService.list(queryWrapper);
                list.getRecords().get(i).setSpecs(specs);
            }
        }
        return ResultUtils.success("查询成功", list);

    }

    //编辑
    @PutMapping
    public ResultVo edit(@RequestBody GoodsParm parm){
        sysGoodsService.editGoods((parm));
        return ResultUtils.success("编辑成功!");
    }

    //删除
    @DeleteMapping("/{goodsId}")
    public ResultVo delete(@PathVariable("goodsId") Long goodsId){
        sysGoodsService.deleteGoods(goodsId);
        return ResultUtils.success("删除成功!");
    }
}


前端

import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
import { IEditorConfig } from '@wangeditor/editor'


export default function useEditor() {
    type InsertFnType = (url: string, alt?: string, href?: string) => void
    // 编辑器实例,必须用 shallowRef
    const editorRef = shallowRef()

    // 内容 HTML
    const valueHtml = ref('')
    const toolbarConfig = {}
    // const editorConfig = { placeholder: '请输入内容...' }
    const editorConfig: Partial<IEditorConfig> = { MENU_CONF: {}, placeholder: '请输入内容...' }
    const mode = ref('default')
    // 上传图片的配置
    editorConfig.MENU_CONF!['uploadImage'] = {
        // form-data fieldName , 默认值 'wangeditor-uploaded-image' 
        fieldName: 'file',
        //上传图片后端地址
        server: process.env.BASE_API + '/api/upload/uploadImage', // 自定义插入图片
        customInsert(res: any, insertFn: InsertFnType) {
            // res 即服务端的返回结果
            console.log(res)
            // 1 从 res 中找到 url alt href ,然后插图图片
            insertFn(process.env.BASE_API + res.data)
        },
    }
    // 组件销毁时,也及时销毁编辑器
    onBeforeUnmount(() => {
        const editor = editorRef.value
        if (editor == null) return
        editor.destroy()
    })

    const handleCreated = (editor: any) => {
        editorRef.value = editor // 记录 editor 实例,重要!
    }
    return {
        editorRef,
        valueHtml,
        toolbarConfig,
        editorConfig,
        handleCreated,
        mode
    }
}

<template>
  <el-upload action="#" list-type="picture-card" :auto-upload="false" ref="uploadRef" :on-change="uploadFile" :limit="3" 
  :on-exceed="moreLimit"
    :on-remove="handleRemove" :file-list="fileList">
    <el-icon>
      <Plus />
    </el-icon>
  </el-upload>
  <el-dialog v-model="dialogVisible">
    <img w-full :src="dialogImageUrl" alt="Preview Image" />
  </el-dialog>
</template>

<script setup lang="ts">
import { uploadImageApi } from '@/api/img/index'
import { ref } from 'vue'
import { Plus } from '@element-plus/icons-vue'
import { NewType } from '@/type/BaseType'
import { UploadFile, UploadUserFile, ElMessage,UploadFiles } from 'element-plus'
//注册时间
const emits = defineEmits(['getImg'])
//图片上传组件的ref属性
const uploadRef = ref()
const dialogImageUrl = ref("")
const dialogVisible = ref(false);
type PropType = {
  fileList: UploadUserFile[];
  oldUrl: Array<{ url: string }>
}
//接收父组件传递的参数
const props = withDefaults(defineProps<PropType>(), {
  fileList: () => [],
  oldUrl: () => []
})
//返回给父组件的值
const newImgRes = ref<NewType>({
  newImgUrl: [],
  deleteUrl: []
})
//删除图片
const handleRemove = (file: UploadFile) => {
  if (props.oldUrl.some(item => item.url === file.name)){
    newImgRes.value.deleteUrl.push({ url: file.name })
    emits("getImg", newImgRes.value);
}else {
  let images = newImgRes.value.newImgUrl.filter((item) => item.url != file.name); 
  newImgRes.value.newImgUrl = images;
  emits("getImg", newImgRes.value);
}
};
//文件个数超出
const moreLimit = ( files: File[], uploadFiles: UploadFiles)=>{
ElMessage.warning("最多只能上传" + uploadFiles.length + "张图片!"); };
//上传数据提交
const uploadFile = async (file: any) => {
  //判断选择的图片是不是图片类型
  const typeArr = ["image/png", "image/gif", "image/jpeg", "image/jpg"];
  const isImg = typeArr.indexOf(file.raw.type) !== -1;
  const isMore3M = file.size / 1024 / 1024 < 3;
  if (!isImg) {
    ElMessage.warning("只能上传图片类型!");
    uploadRef.value?.clearFiles();
    return;
  }
  if (!isMore3M) {
    ElMessage.warning("图片大小不能超过3M");
    uploadRef.value?.clearFiles();
    return;
  }
  //组装上传的数据
  const formData = new FormData()
  formData.append("file", file.raw)
  //提交数据
  const res = await uploadImageApi(formData)
  if (res && res.code == 200 && res.data) {
    ElMessage.success('图片上传成功')
    file.name = process.env.BASE_API + res.data;
    //设置到返回子组件的数据里面
    newImgRes.value.newImgUrl.push({
      url: process.env.BASE_API + res.data

    })
    console.log(newImgRes.value.newImgUrl)
    emits('getImg',newImgRes.value)
  }
};

</script>

<style scoped></style>


<template>
    <SysDialog :title="dialog.title" :width="dialog.width" :height="dialog.height" :visible="dialog.visible"
        @onClose="onClose" @onConfirm="commit">
        <template v-slot:content>
            <el-form :model="addModel" ref="addFormRef" :rules="rules" label-width="80px" :inline="false" size="default">
                <el-row :gutter="20">
                    <el-col :span="12" :offset="0">
                        <el-form-item prop="categoryId" label="菜品分类">
                            <el-select style="width: 100%;" v-model="addModel.categoryId" ref="mySelected"
                                placeholder="请选择分类" size="default">
                                <el-option v-for="item in selectData" :key="item['value']" :label="item['label']"
                                    :value="item['value']" />
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12" :offset="0">
                        <el-form-item prop="goodsName" label="菜品名称">
                            <el-input v-model="addModel.goodsName"></el-input>
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="20">
                    <el-col :span="12" :offset="0">
                        <el-form-item prop="orderNum" label="菜品序号">
                            <el-input v-model="addModel.orderNum"></el-input>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12" :offset="0">
                        <el-form-item porp="status" label="是否热推">
                            <el-radio-group v-model="addModel.status">
                                <el-radio :label="'0'"></el-radio>
                                <el-radio :label="'1'"></el-radio>

                            </el-radio-group>
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-form-item prop="goodsUnit" label="菜品单位">
                    <el-input v-model="addModel.goodsUnit"></el-input>
                </el-form-item>
                <el-form-item prop="specs " label="菜品规格">
                    <el-row :gutter="20">
                        <!-- <el-col :span="12" :offset="0">
                        <el-divider content-position="left">菜品规格</el-divider>
                    </el-col> -->
                        <el-col style="display: flex;  align-items: center;" :span="12" :offset="0">
                            <el-button type="primary" style="margin-left: 20px;" plain :icon="Plus" size="small"
                                @click="addSpecs">添加规格</el-button>

                        </el-col>
                    </el-row>
                </el-form-item>


                <el-form-item v-for="num in addModel.specs.length" label="" size="small" :key="num">
                    <el-row :gutter="20">
                        <el-col :span="7" :offset="0">
                            <el-form-item :label="'名称' + num" size="small">
                                <el-input v-model="addModel.specs[num - 1].specsName"></el-input>
                            </el-form-item>
                        </el-col>

                        <el-col :span="7" :offset="0">
                            <el-form-item :label="'价格' + num" size="small">
                                <el-input v-model="addModel.specs[num - 1].goodsPrice"></el-input>
                            </el-form-item>
                        </el-col>
                        <el-col :span="7" :offset="0">
                            <el-form-item :label="'序号' + num" size="small">
                                <el-input v-model="addModel.specs[num - 1].orderNum"></el-input>
                            </el-form-item>
                        </el-col>
                        <el-col :span="3" :offset="0">
                            <el-button @click="deleteSpecs(num)" style="margin-left: 15px;" :icon="Close" type="danger"
                                plain circle size="small"></el-button>

                        </el-col>
                    </el-row>

                </el-form-item>

                <el-form-item prop="goodsImage" label="菜品图片">
                    <UnloadImage @getImg="getImg" :oldUrl="oldUrl" :fileList="fileList"></UnloadImage>
                </el-form-item>
                <el-form-item prop="goodsDesc" label="菜品详情">
                    <div style="border: 1px solid #ccc">
                        <Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig"
                            :mode="mode" />
                        <Editor style="height: 200px; 
                    overflow-y: hidden;" v-model="valueHtml" :defaultConfig="editorConfig" :mode="mode"
                            @onCreated="handleCreated" />
                    </div>
                </el-form-item>
            </el-form>

        </template>
    </SysDialog>
</template>

<script setup lang="ts">
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import useSelectCategory from '@/composables/goods/useSelectCategory'
import UnloadImage from '@/components/UploadImage.vue';
import { GoodsType, SpecsType } from '@/api/goods/GoodsModel';
import SysDialog from '@/components/SysDialog.vue';
import useDialog from '@/hooks/UseDiglog'; 
import { ElMessage, FormInstance, UploadUserFile } from 'element-plus';
import { nextTick, reactive, ref, watch } from "vue";
import { EditType, NewType } from '@/type/BaseType';
import useEditor from "@/composables/goods/useEditor"
import { Plus, Close } from '@element-plus/icons-vue';
import { addApi, editApi } from '@/api/goods/index'
//编辑器
const { editorRef, valueHtml, toolbarConfig, editorConfig, handleCreated, mode } = useEditor()
//表单的ref属性
const addFormRef = ref<FormInstance>();
//图片初始值
const fileList = ref<Array<UploadUserFile>>([]);
//回显的图片
const oldUrl = ref<Array<{ url: string }>>([])
//图片地址
const imgUrl = ref<Array<{ url: string }>>([])
//弹框属性
const { dialog, onClose, onConfirm, onShow } = useDialog();
//下拉分类
const { selectData, getSelect } = useSelectCategory()


//表单绑定对象
const addModel = reactive<GoodsType>({
    type: "",
    goodsId: "",
    categoryId: "",
    goodsName: "",
    goodsImage: "",
    goodsDesc: "",
    status: "",
    goodsUnit: "",
    orderNum: "",
    specs: [],
});

//显示弹框
const show = (type: string, row?: GoodsType) => {
    getSelect();
    dialog.width = 960;
    dialog.height = 560;

    //清空图片
    imgUrl.value = []
    oldUrl.value = []
    fileList.value = []

    //清空文本编辑器
    if (editorRef.value) {
        editorRef.value.clear()
    }



    onShow();
    //编辑回显
    if (type == EditType.EDIT && row) {
        nextTick(() => {
            Object.assign(addModel, row)
            //图片回显
            if (addModel.goodsImage) {
                let imgs = addModel.goodsImage.split(",")
                for (let i = 0; i < imgs.length; i++) {
                    let img = {
                        name: '',
                        url: ''
                    }
                    img.name = imgs[i]
                    img.url = imgs[i]
                    fileList.value.push(img)
                    oldUrl.value.push({ url: imgs[i] })
                }

            }
            //编辑器回显
            valueHtml.value = addModel.goodsDesc
        })
    } else {
        nextTick(() => {
            //清空规格
            addModel.specs = []
            //设置默认规格 
            addSpecs()
        })

    }
    //清空表单
    addFormRef.value?.resetFields();
    //设置本次操作是新增还是编辑
    addModel.type = type;
};
defineExpose({
    show
})

//菜品详情验证规则
const checkEdit = (rule: any, value: any, callback: any) => {
    if (editorRef.value.getText().length == 0) {
        callback(new Error('请填写菜品详情'))
    } else {
        callback()
    }
}
//菜品规格验证
const checkSpecs = (rule: any, value: any, callback: any) => {
    if (addModel.specs.length == 0) {
        callback(new Error('请填写规格'))
    } else {
        let tag = false;
        addModel.specs.forEach((item: SpecsType) => {
            if (!item.specsName) {
                tag = false;
                callback(new Error('请填写规格名称'))
            } else if (!item.goodsPrice) {
                tag = false;
                callback(new Error('请填写规格价格'))
            } else if (!item.orderNum) {
                tag = false;
                callback(new Error('请填写规格序号'))
            } else {
                tag = true;
            }
        })
        if (tag) {
            callback();
        }
    }
}
//表单验证规则
const rules = reactive({
    categoryId: [{
        required: true,
        message: "请选择分类",
        trigger: "blur"
    },
    ],
    goodsName: [{
        required: true,
        message: "请填写菜品名称",
        tigger: "blur"
    },
    ],
    goodsImage: [{
        required: true,
        message: "请上传菜品图片",
        tigger: "blur"
    },
    ],
    goodsUnit: [{
        required: true,
        message: "请填写单位",
        tigger: "blur"
    },
    ],
    orderNum: [{
        required: true,
        message: "请选择序号",
        tigger: "blur"
    },
    ],
    status: [{
        required: true,
        message: "请选择是否热推",
        tigger: "blur"
    },
    ],
    goodsDesc: [{
        tigger: "blur",
        validator: checkEdit,
        required: true,
    },
    ],
    specs: [{
        required: true,
        validator: checkSpecs,
        tigger: "blur"
    },
    ],
})
//图片上传地址
const getImg = (img:NewType) => {
    console.log("999");
    // console.log(img.newImgUr1);
    imgUrl.value = oldUrl.value.concat(img.newImgUrl);
    if (img.deleteUrl.length > 0) {
        let newArr = imgUrl.value.filter((x) => !img.deleteUrl.some((item) => x.url === item.url)); 
        imgUrl.value = newArr;
    }
    // console.Tog(imgUr1.value)
    //把图片路径拼接为逗号分隔的字符串
    let url = "";
    for (let k = 0; k < imgUrl.value.length; k++) {
        url = url + imgUrl.value[k].url + ",";
    }
    addModel.goodsImage = url.substring(0, url.lastIndexOf(","))
    console.log(addModel.goodsImage)
};
//获取编辑器数据
watch(
    () => valueHtml.value,
    (value) => {
        console.log(value)
        addModel.goodsDesc = value
    }
)
//添加规格
const addSpecs = () => {
    addModel.specs.push({

        goodsPrice: '',
        specsName: '',
        orderNum: ''
    })
}
//删除规格
const deleteSpecs = (num: number) => {
    addModel.specs.splice(num - 1, 1);
}
//注册事件
const emits = defineEmits(['orRefsh'])
//提交表单
const commit = () => {
    console.log(addModel)
    addFormRef.value?.validate(async(valid) => {
        if (valid) {
            let res = null;
            if (addModel.type == EditType.ADD) {
                res = await addApi(addModel);
            } else {
                res = await editApi(addModel)
            }

            if (res && res.code == 200) {
                ElMessage.success(res.msg)
                emits('orRefsh')
                onClose()
            }

        }
    })
}
</script>

<style scoped></style>

  • 写回答

10条回答 默认 最新

  • CSDN专家-sinJack 2023-11-10 00:31
    关注

    192.168.31.70这个地址是不是不通?
    本地ping 192.168.31.70这个地址看看。

    评论

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 11月10日
  • 创建了问题 11月9日