使用 Ant-Design-Vue 制作一個帶圖片上傳功能的表單對話框
功能需求
使用 Antdv 的 Modal 組件內(nèi)嵌一個 a-form 表單,具有添加數(shù)據(jù)和圖片的功能。




頁面結(jié)構(gòu)設(shè)計
<template>
<!--Modal-->
<a-modal>
<div>
<a-form>
<a-form-item label="水果名稱" name="fruitName">
<a-input type="text" placeholder="水果名稱">
</a-form-item>
<a-form-item label="價 格" name="price">
<a-input type="number" placeholder="價格(元/公斤)">
</a-form-item>
<a-form-item label="庫 存" name="stock">
<a-input type="number" placeholder="庫存(公斤)">
</a-form-item>
<a-form-item label="圖 片">
<a-upload>
<div>
<img v-if="!base64img" src="@/assets/plus.png">
<!-- ↑ 如果沒有添加圖片則顯示一個加號占位符圖片 -->
<img v-else :src="base64img">
<!-- ↑ 如果添加了圖片則顯示圖片 -->
</div>
</a-upload>
</a-form-item>
<a-form-item>
<a-button>提交</a-button>
</a-form-item>
</a-form>
</div>
</a-modal>
</template>
只展示了基礎(chǔ)的頁面結(jié)構(gòu),省略了綁定的事件、邏輯和樣式代碼。
思路整理
Modal 中有一個表單,表單中有 4 個子項目,前三個為 input 輸入框,最后一個是圖片上傳的組件,均為必填項。所以要為前三個輸入框加上前端的表單驗證,只需要在 a-form 的 :rules 中加入 [{required: true, message: ...}]。上傳圖片則采用單獨的邏輯判斷,因為表單輸入的前三項和圖片上傳采用的是不同的接口,上傳表單數(shù)據(jù)(水果名稱、價格、庫存)和圖片的路徑使用的是一個接口,上傳圖片至服務(wù)器使用的是另一個接口。所以我們要在點擊提交按鈕時添加一個判斷圖片是否為空的操作,如果為空則拒絕向服務(wù)器發(fā)送請求。
關(guān)于圖片上傳的部分可以看 這篇文章
由于 Antdv 的 Modal 組件是有默認(rèn)的 確認(rèn) 和 取消 按鈕,這點對于提交表來來說不太方便,所以要禁用默認(rèn)的按鈕,再向表單中添加一個 form-item 提交按鈕。
在上傳文件時,為了避免上傳相同名稱的文件,我們要用當(dāng)前時間戳來為文件重命名,手段是 const renameFile = new File(...)
添加數(shù)據(jù)成功后,需要重新從數(shù)據(jù)庫中獲取下數(shù)據(jù),這個方法直接使用 onMounted 中的就可以了。由于數(shù)據(jù)量較少,故沒有在服務(wù)端使用分頁,每次請求都會返回數(shù)據(jù)庫中的所有數(shù)據(jù)。
邏輯部分
// ↓ 控制 Modal 組件是否顯示
const addModalVisible = ref<boolean>(false);
// 點擊顯示 Modal 的回調(diào)
function showAddModal() {
console.log("Show Add Modal");
addModalVisible.value = true;
}
// 表單數(shù)據(jù)
const formData = reactive({
fruitName: "",
price: "",
stock: "",
avatar: "",
})
/**
* 上傳圖片需要用到的變量
*/
const fileList = ref();
// ↑ 放置圖片的
const base64img = ref();
// ↑ 用來放置 base64 轉(zhuǎn)碼后的圖片
const btnLoading = ref<boolean>(false);
// 控制 提交 按鈕的 loading 狀態(tài)
/**
* 上傳文件的回調(diào)
*/
function beforeUpload(file: any) {
const isImg = (file.type === 'image/jpeg' || file.type === 'image/png');
// 如果文件不是圖片格式則禁止上傳
if (!isImg) {
Notice.error("只能上傳 jpeg/jpg/png 格式的文件!");
// ↑ Notice.error 是我自己寫的 Notice 類的一個靜態(tài)函數(shù)
// 用的是 antdv 的 Notifiacation
return Upload.LIST_IGNORE;
// ↑ 這個不解釋了,上一篇博客中提到了
}
fileList.value = file;
// ↓ 獲取所需要上傳圖片的 Base64 編碼
getBase64(fileList.value, (cb_img: string) => {
// 將獲取到的所要上傳圖片的 Base64 編碼渲染到圖片上
base64img.value = cb_img;
})
return false;
}
/**
* 待上傳圖片被刪除的回調(diào)
*/
function handleRemove() {
fileList.value = null;
// ↑ 置空待上傳文件列表
base64img.value = null;
// ↑ 顯示默認(rèn)占位圖
}
/**
* 點擊 Modal 中的添加按鈕的回調(diào)
*/
function addItem() {
// 添加圖片文件到服務(wù)器指定位置
const imgForm = new FormData();
if (fileList.value != null) {
// ↓ 以當(dāng)前時間戳重命名文件
const newFileName = Date.now() + "." + fileList.value.name.substring(fileList.value.name.lastIndexOf(".") + 1);
// ↓ avatar 是數(shù)據(jù)庫中儲存圖片地址的字段,后面會加上地址重新賦值一次
formData.avatar = newFileName;
const renameFile = new File([fileList.value], newFileName);
// ↑ 創(chuàng)建新文件:以新文件名重命名文件
imgForm.append("file", renameFile);
} else {
Notice.error("圖片不能為空!");
return Upload.LIST_IGNORE;
}
btnLoading.value = true;
// 將圖片上傳至服務(wù)器
const addData = new Promise((resolve, reject) => {
// fruitApi 是自己封裝的 Axios
fruitApi({
method: 'put',
url: 'api/upload',
data: imgForm,
})
.then((resp) => {
resolve(resp);
})
.catch((error) => {
reject(error);
})
})
// 添加數(shù)據(jù)至數(shù)據(jù)庫
// 并添加文件的名稱和路徑至數(shù)據(jù)庫
const uploadPic = new Promise((resolve, reject) => {
fruitApi({
method: 'post',
url: 'api/fruits',
data: {
fruitName: formData.fruitName,
avatar: imagesAddr + "/" + formData.avatar,
// imagesAddr 是服務(wù)器儲存圖片的地址
price: formData.price,
stock: formData.stock,
}
})
.then((resp: any) => {
resolve(resp);
})
.catch((error) => {
reject(error);
})
})
Promise.all([addData, uploadPic])
.then((resp) => {
console.log("請求成功: ");
console.log(resp);
changeUploadItemColor("success");
// ↑ 上傳成功改變顏色,也在上個博客提到過了
Notice.success("添加成功!");
setTimeout(() => {
getFruits();
// ↑ 上傳成功后重新獲取數(shù)據(jù)
}, 500)
})
.catch((error) => {
console.log("請求失敗: ");
console.log(error);
changeUploadItemColor("error");
Notice.error("添加失敗~");
})
.finally(() => {
btnLoading.value = false;
// 取消按鈕的 loading 狀態(tài)
})
}
完善頁面結(jié)構(gòu)
<!--Modal-->
<a-modal v-model:visible="addModalVisible"
title="模態(tài)對話框"
:confirm-loading="btnLoading"
:footer="null"
>
<div>
<a-form
:model="formData"
@finish="addItem"
>
<a-form-item label="水果名稱" name="fruitName" :rules="[{required: true, message: '請輸入水果名稱'}]">
<a-input type="text" placeholder="水果名稱" v-model:value="formData.fruitName"/>
</a-form-item>
<a-form-item label="價 格" name="price" :rules="[{required: true, message: '請輸入價格'}]">
<a-input type="number" placeholder="價格(元/公斤)" suffix="RMB" v-model:value="formData.price"/>
</a-form-item>
<a-form-item label="庫 存" name="stock" :rules="[{required: true, message: '請輸入庫存'}]">
<a-input type="number" placeholder="庫存(公斤)" v-model:value="formData.stock"/>
</a-form-item>
<a-form-item label="圖 片">
<a-upload
:before-upload="beforeUpload"
:max-count="1"
@remove="handleRemove"
>
<div>
<img v-if="!base64img" src="@/assets/plus.png" />
<img v-else :src="base64img" />
</div>
</a-upload>
</a-form-item>
<a-form-item>
<a-button html-type="submit" :loading="btnLoading">提交</a-button>
</a-form-item>
</a-form>
</div>
</a-modal>
完整代碼:https://blog-static.cnblogs.com/files/blogs/754613/modalForm.vue.js?t=1665405534

浙公網(wǎng)安備 33010602011771號