问题:图片加载失败

前端报错


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

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

配置类
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>