實(shí)現(xiàn)導(dǎo)入功能主要還是使用 Ant Design Vue 中的 upload 樣式組件
upload 組件官網(wǎng):https://www.antdv.com/components/upload-cn/
前端實(shí)現(xiàn)
<a-upload
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:customRequest="customRequest"
@change="customChange"
:disabled="uploadDisabled">
<a-button type="primary" :icon="uploadIcon" :disabled="uploadDisabled">導(dǎo)入</a-button>
</a-upload>
代碼解讀:這段代碼中 a-upload 標(biāo)簽表示是在頁(yè)面中引入了導(dǎo)入組件
accept 中這段代碼表示導(dǎo)入的文件只能是 excel 導(dǎo)入[.xlsx 或 .xls 都支持]
customRequest 表示自定義方法代替默認(rèn)方法去實(shí)現(xiàn)文件導(dǎo)入/上傳操作
@change 事件是在文件上傳中、完成、失敗都會(huì)調(diào)用這個(gè)函數(shù)
disabled 是否禁用上傳組件
a-button 創(chuàng)建一個(gè)操作按鈕,type 為 primary 表示這是一個(gè)主要按鈕,icon 表示為按鈕設(shè)置一個(gè)圖標(biāo)
disabled是否禁用此按鈕
屬性定義
將上面自定義的屬性在data中定義好
to-top 的樣式為這個(gè):這個(gè)樣式也是用的 Ant Design Vue 中的 icon圖標(biāo)庫(kù)
export default {
data() {
return {
// 導(dǎo)入excel時(shí)的icon圖標(biāo)
uploadIcon: 'to-top',
// 導(dǎo)入excel時(shí)是否禁用上傳按鈕
uploadDisabled: false
}
}
}
方法實(shí)現(xiàn)
自定義實(shí)現(xiàn)方法,參數(shù) data 就是上傳文件的信息
data.file 是上傳至后臺(tái)的 excel 文件
上傳文件必須要通過(guò)創(chuàng)建 FormData 對(duì)象進(jìn)行傳參
batchImportGetRecord 是我的項(xiàng)目中自定義上傳文件的方法,是利用 axios 請(qǐng)求封裝好的方法,也可以使用 ajax ,實(shí)際代碼編寫(xiě)需要按照自己項(xiàng)目中封裝好的網(wǎng)絡(luò)請(qǐng)求來(lái)進(jìn)行編寫(xiě)
finally 塊表示請(qǐng)求成功或是失敗都會(huì)執(zhí)行的操作
/**
* 自定義導(dǎo)入文件方法
* @param data 上傳的 excel 文件
*/
customRequest(data) {
const file = data.file; // 需要上傳的 excel 文件
const formData = new FormData();
formData.append('file', file);
data.onProgress();
batchImportGetRecord(formData).then(res => {
if (res) {
this.$message.success('導(dǎo)入成功');
} else {
this.$message.error('導(dǎo)入失敗');
}
}).finally(() => {
this.switchIconAndStatus(false);
});
}
上傳中、完成、失敗都會(huì)調(diào)用這個(gè)函數(shù)
data.file.status 是文件上傳的一些狀態(tài)
文件上傳狀態(tài) | 描述 |
---|---|
uploading | 上傳中 |
done | 上傳成功 |
error | 上傳失敗 |
removed | 取消上傳 |
/**
* 導(dǎo)入功能的 change 事件
* @param data 上傳文件過(guò)程中的文件狀態(tài)信息
*/
customChange(data) {
if (data.file.status === 'uploading') {
this.switchIconAndStatus(true);
} else if (data.file.status === 'done') {
this.switchIconAndStatus(false);
} else if (data.file.status === 'error') {
this.switchIconAndStatus(false);
}
}
按照程序設(shè)計(jì),重復(fù)代碼過(guò)多就需要進(jìn)行封裝,方便使用
通過(guò)上傳成功/失敗的情況更改上傳按鈕的圖標(biāo)和按鈕禁用的樣式
uploadIcon = loading 表示切換按鈕的圖標(biāo)為如下圖:
這樣做的好處就是在導(dǎo)入大數(shù)據(jù)量的 excel 時(shí),后臺(tái)處理較慢,添加了此圖標(biāo)及對(duì)應(yīng)的按鈕禁用可以防止用戶不停的點(diǎn)擊,給使用者制造一個(gè)正在處理數(shù)據(jù)的過(guò)程中,讓使用者稍等片刻...
/**
* 導(dǎo)入按鈕的圖片和狀態(tài)切換
* @param flag 根據(jù)此標(biāo)識(shí)來(lái)區(qū)分正在導(dǎo)入還是導(dǎo)入成功或未導(dǎo)入的圖標(biāo)及禁用情況
*/
switchIconAndStatus(flag) {
if (flag) {
this.uploadIcon = 'loading';
this.uploadDisabled = true;
} else {
this.uploadIcon = 'to-top';
this.uploadDisabled = false;
}
}
后端實(shí)現(xiàn)
后臺(tái)接口,圖片上傳請(qǐng)求默認(rèn)為 post 請(qǐng)求,通過(guò) MultipartFile 類型接收上傳的文件,注意這里的 括號(hào)中的參數(shù)要和前端上傳的參數(shù)名稱一致,不一致后臺(tái)就接收不到這個(gè)參數(shù)
@PostMapping(value = "import")
public Boolean importExcelData(@RequestParam("file") MultipartFile file) {
return studentService.importExcelData(file);
}
這里舉例導(dǎo)入一個(gè)學(xué)生信息表 excel 文件
@Override
@Transactional(rollbackFor = Exception.class)
public boolean importExcelData(MultipartFile file) {
List<Student> list = new ArrayList<Student>();
Student student = null;
try {
// 通過(guò)文件輸入流讀取到對(duì)應(yīng)的 workbook 工作簿
XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream());
// 只解析第一張 sheet 工作表
XSSFSheet sheet = workbook.getSheetAt(0);
// 遍歷第一個(gè)工作表的所有行
for (int i = 0; i < sheet.getPhysicalNumberOfRows(); i++) {
if (i == 0) continue; // 跳過(guò)標(biāo)題行
XSSFRow row = sheet.getRow(i); // 獲取工作表中的某一行,通過(guò)下標(biāo)獲取
if (row == null) continue; // 跳過(guò)空行
// 構(gòu)造要批量插入的Student對(duì)象
student = new Student();
// 遍歷一個(gè)行中的所有列
for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {
XSSFCell cell = row.getCell(j); // 獲取一行中的某個(gè)單元格,通過(guò)下標(biāo)獲取
if (cell == null) continue; // 跳過(guò)空單元格
// 獲取單元格中的字符串內(nèi)容
String cellValue = cell.getStringCellValue();
// 獲取單元格中的數(shù)字內(nèi)容
double cellValue2 = cell.getNumericCellValue();
// 判斷單元格是第幾個(gè),從零開(kāi)始
switch (j) {
case 0: // 第一列就是姓名
student.setName(cellValue); // 給實(shí)體類的setter屬性賦值
break;
case 1: // 年齡
student.setAge(cellValue2);
break;
case 2: // 愛(ài)好
student.setHobby(cellValue);
break;
case 3: // 家庭地址
student.setAddress(cellValue);
break;
case 4: // 出生日期
// 如果沒(méi)有特意去定義 excel 中的日期,那么獲取到的日期就是字符串類型
// 這里將字符串日期轉(zhuǎn)換為日期格式 LocalDateTime 或 Date
// 1. 將日期轉(zhuǎn)換為 LocalDateTime
// LocalDateTime time = LocalDateTime.parse(cellValue, DateTimeFormatter.ofPattern("yyyy年M月d日HH:mm:ss"));
// 2. 將日期轉(zhuǎn)換為 Date
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年M月d日HH:mm:ss");
Date date = sdf.parse(cellValue);
student.setBirthday(date);
break;
}
}
list.add(student);
}
// 做一下批量添加學(xué)生信息的操作即可,這里使用 MyBatis-Plus 提供的方法進(jìn)行批量新增
studentService.saveBatch(list);
return true;
} catch (Exception e) {
e.printStackTrace();
System.err.println("導(dǎo)入失敗");
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 手動(dòng)回滾代碼
return false;
}
}
代碼解讀:
file.getInputStream()
構(gòu)建一個(gè) workbookwork.getSheetAt(index)
通過(guò)此方法獲取工作表,通過(guò)索引來(lái)獲取,索引從零開(kāi)始sheet.getPhysicalNumberOfRows()
方法可以獲取 sheet 工作表中的所有行的數(shù)量sheet.getRow(index)
通過(guò)下標(biāo)獲取對(duì)應(yīng)的行,下標(biāo)都是從零開(kāi)始row.getPhysicalNumberOfCells()
方法可以獲取到一行中所有單元格的數(shù)量row.getCell(index)
通過(guò)下標(biāo)獲取對(duì)應(yīng)的單元格,下標(biāo)都是從零開(kāi)始cell.getStringCellValue()
此方法用于獲取單元格中為字符串類型的內(nèi)容值,相關(guān)的方法有多種,可以獲取 Boolean,日期類型,數(shù)字類型等….最后經(jīng)過(guò)讀取所有內(nèi)容后將單元格內(nèi)容一行行的讀取設(shè)置到 實(shí)體類中,并將實(shí)體類經(jīng)過(guò)每次循環(huán)都添加到 list 集合中,最后通過(guò)批量插入表的操作給插入到數(shù)據(jù)庫(kù)中,比起來(lái)一條條的插入,批量插入明顯效率更高,因?yàn)楹笈_(tái)請(qǐng)求數(shù)據(jù)庫(kù)也是在消耗網(wǎng)絡(luò)資源,中間一去一回也是浪費(fèi)時(shí)間,而批量插入明顯網(wǎng)絡(luò)資源消耗的更少。
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
添加事務(wù)后,當(dāng)導(dǎo)入失敗時(shí),可以進(jìn)行代碼回滾操作`
聯(lián)系客服