【从前端到后端导入excel资料实现批量导入-笔记模仿芋道源码的《系统管理-用户管理-导入-批量导入》】

news/2025/10/8 17:10:51/文章来源:https://www.cnblogs.com/yxysuanfa/p/19129844

批量导入预约数据-笔记

前端

场馆列表

该列表进入出现的是这样的,这儿是列表操作

<el-table-column label="操作" align="center" width="220px"><template #default="scope"><el-buttonlinktype="primary"@click="openForm('update', scope.row.id, scope.row.name)"v-hasPermi="['sports:venue:update']">编辑</el-button><el-buttonlinktype="primary"@click="openImportForm('update', scope.row.id, scope.row.name, scope.row.capacity)"v-hasPermi="['sports:venue:update']">批量导入预约</el-button><el-buttonlinktype="danger"@click="handleDelete(scope.row.id)"v-hasPermi="['sports:venue:delete']">删除</el-button></template></el-table-column>

批量导入操作在这儿

<el-button
link
type="primary"
@
click="openImportForm('update', scope.row.id, scope.row.name, scope.row.capacity)"
v-hasPermi="['sports:venue:update']"
>
批量导入预约
<
/el-button>
<
!-- 批量导入 -->
<UserImportForm ref="importFormRef" @
success="getList" />
import UserImportForm from './UserImportForm.vue'
// 批量导入
const importFormRef = ref(
)
const openImportForm = (type: string
, id?: number
, name?: string
, capacity: number
) =>
{
if (!importFormRef.value) {
console.error('importFormRef 未正确绑定'
)
return
}
importFormRef.value.open(type, id, name)
}

这是导入的弹窗

<template><Dialog v-model="dialogVisible" title="批量导入场馆预约" width="480"><el-form-item class="form-item"><el-date-pickerv-model="bookingDates"type="date"value-format="YYYY-MM-DD"placeholder="请选择预约的日期":disabled-date="disabledDate":clearable="true"class="custom-date-picker"/></el-form-item><el-uploadref="uploadRef"v-model:file-list="fileList":action="importUrl":auto-upload="false":disabled="formLoading":headers="uploadHeaders":limit="1":on-error="submitFormError":on-exceed="handleExceed":on-success="submitFormSuccess"accept=".xlsx, .xls"dragclass="upload-area"><Icon icon="ep:upload" /><div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div><template #tip><div class="el-upload__tip text-center"><span>仅允许导入 xls、xlsx 格式文件。</span><el-link:underline="false"style="font-size: 12px; vertical-align: baseline"type="primary"@click="importTemplate">下载模板</el-link></div></template></el-upload><template #footer><el-button :disabled="formLoading" type="primary" @click="submitForm"class="submit-button">确 定</el-button><el-button @click="dialogVisible = false"class="cancel-button">取 消</el-button></template></Dialog></template><script lang="ts" setup>import {ref, nextTick, defineExpose}from 'vue'import axios from 'axios'import {getAccessToken, getTenantId}from '@/utils/auth'import *as VenueBookingApi from '@/api/sports/venuebooking'import download from '@/utils/download'import {getUserProfile}from '@/api/system/user/profile'defineOptions({name: 'VenueBookingImportForm'})const message = useMessage()const dialogVisible = ref(false)const formLoading = ref(false)const uploadRef = ref()const importUrl =import.meta.env.VITE_BASE_URL +import.meta.env.VITE_API_URL + '/sports/venue-booking/import'const uploadHeaders = ref()const fileList = ref([])const updateSupport = ref(0)const bookingDates = ref([]) // 日期范围const venueId = ref(null) // 用于存储场馆IDconst venueName = ref('') // 用于存储场馆名称// 用于打开导入对话框并传入参数const open = (type: string, id: number, name: string) =>{dialogVisible.value = truevenueId.value = id // 设置 venueIdvenueName.value = name // 设置 venueNameupdateSupport.value = 0fileList.value = []bookingDates.value = [] // 重置日期范围resetForm()}defineExpose({open})// 限制选择日期的函数(例如,不能选择过去的日期)const disabledDate = (date: Date) =>{return date.getTime() < Date.now() // 禁止选择过去的日期}// 提交表单const submitForm =async () =>{if (!validateForm())returnuploadHeaders.value = {Authorization: 'Bearer ' + getAccessToken(),'tenant-id': getTenantId()}formLoading.value = true// 创建 FormData 对象,包含文件和其他参数const formData =new FormData()formData.append('file', fileList.value[0].raw)formData.append('venueId', venueId.value) // 场馆 IDformData.append('venueName', venueName.value) // 场馆名称formData.append('bookingTime', bookingDates.value) // 预约日期formData.append('updateSupport', updateSupport.value ? 'true' : 'false')// 获取用户资料并添加到 formData 中// 发起请求try {const res =await getUserProfile();// 假设 getUserProfile 是异步函数formData.append('agent', res.nickname);// 将 nickname 作为 agent 添加到 FormData 中formData.append('agentId', res.id);const response =await axios.post(importUrl, formData, {headers: uploadHeaders.value})// 检查接口返回的 response 格式,如果是包含 code 的结构if (response.data && response.data.code === 0) {// 上传成功message.alertSuccess('批量导入成功!!')submitFormSuccess(response.data)}else {// 上传失败,显示错误信息submitFormError(response.data)}}catch (error) {submitFormError(error) // 请求失败}}// 失败回调const submitFormError = (error: any):void =>{if (error && error.msg) {message.error(error.msg || '上传失败,请重新上传!')}else {message.error('上传失败,请重新上传!')}formLoading.value = false}// 成功回调const submitFormSuccess = (response: any) =>{if (response.code !== 0) {message.error(response.msg || '上传失败')formLoading.value = falsereturn}// 完成后恢复状态formLoading.value = falsedialogVisible.value = false}// 重置表单const resetForm =async (): Promise<void>=>{formLoading.value = falseawait nextTick()uploadRef.value?.clearFiles()}// 处理文件超出限制const handleExceed = ():void =>{message.error('最多只能上传一个文件!')}// 下载模板const importTemplate =async () =>{const res =await VenueBookingApi.importUserTemplate()download.excel(res, '用户导入模版.xls')}// 验证表单输入const validateForm = () =>{if (!fileList.value.length) {message.error('请上传文件')return false}if (!bookingDates.value || bookingDates.value.length === 0) {message.error('请先选择预约日期')return false}return true}</script><style scoped>.form-item {margin-left: 110px;}</style>
// 下载用户导入模板
export
const importUserTemplate = (
) =>
{
return request.download({
url: '/sports/venue-booking/get-import-template'
}
)
}

下载用户模板插件

const download0 = (data: Blob, fileName: string
, mineType: string
) =>
{
// 创建 blob
const blob =
new Blob([data]
, {
type: mineType
}
)
// 创建 href 超链接,点击进行下载
window.URL = window.URL || window.webkitURL
const href = URL.createObjectURL(blob)
const downA = document.createElement('a'
)
downA.href = href
downA.download = fileName
downA.click(
)
// 销毁超连接
window.URL.revokeObjectURL(href)
}
const download = {
// 下载 Excel 方法
excel: (data: Blob, fileName: string
) =>
{
download0(data, fileName, 'application/vnd.ms-excel'
)
}
,
// 下载 Word 方法
word: (data: Blob, fileName: string
) =>
{
download0(data, fileName, 'application/msword'
)
}
,
// 下载 Zip 方法
zip: (data: Blob, fileName: string
) =>
{
download0(data, fileName, 'application/zip'
)
}
,
// 下载 Html 方法
html: (data: Blob, fileName: string
) =>
{
download0(data, fileName, 'text/html'
)
}
,
// 下载 Markdown 方法
markdown: (data: Blob, fileName: string
) =>
{
download0(data, fileName, 'text/markdown'
)
}
,
// 下载图片(允许跨域)
image: ({
url,
canvasWidth,
canvasHeight,
drawWithImageSize = true
}: {
url: string
canvasWidth?: number // 指定画布宽度
canvasHeight?: number // 指定画布高度
drawWithImageSize?: boolean // 将图片绘制在画布上时带上图片的宽高值, 默认是要带上的
}
) =>
{
const image =
new Image(
)
// image.setAttribute('crossOrigin', 'anonymous')
image.src = url
image.onload = (
) =>
{
const canvas = document.createElement('canvas'
)
canvas.width = canvasWidth || image.width
canvas.height = canvasHeight || image.height
const ctx = canvas.getContext('2d'
)
as CanvasRenderingContext2D
ctx?.clearRect(0
, 0
, canvas.width, canvas.height)
if (drawWithImageSize) {
ctx.drawImage(image, 0
, 0
, image.width, image.height)
}
else {
ctx.drawImage(image, 0
, 0
)
}
const url = canvas.toDataURL('image/png'
)
const a = document.createElement('a'
)
a.href = url
a.download = 'image.png'
a.click(
)
}
}
}
export
default download

后端

用户模板下载

@GetMapping("/get-import-template"
)
@Operation(summary = "获得导入用户模板"
)
public
void importTemplate(HttpServletResponse response) throws IOException {
// 手动创建导出 demo
List<UserImportDemoExcelVO> list = Arrays.asList(UserImportDemoExcelVO.builder().registrant("张三").phone("15601691300").idCard("522324198805060010").build());// 输出ExcelUtils.write(response, "预约用户导入模板.xls", "预约用户列表", UserImportDemoExcelVO.class, list);}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false
) // 设置 chain = false,避免用户导入有问题
public
class UserImportDemoExcelVO {
// @ExcelProperty("场馆id")
// private Long venueId;
//
// @ExcelProperty("场馆名称")
// private String venueName;
@ExcelProperty("预定人姓名"
)
private String registrant;
@ExcelProperty("预定人联系方式"
)
private String phone;
@ExcelProperty("预定人身份证号"
)
private String idCard;
// @ExcelProperty("预约时间")
// private LocalDateTime bookingTime;
}

导入模板

@PostMapping("/import"
)
@Operation(summary = "导入场馆预约数据"
)
@Parameters({
@Parameter(name = "file"
, description = "Excel 文件"
, required = true
)
,
@Parameter(name = "venueId"
, description = "场馆 ID"
, required = true
)
,
@Parameter(name = "venueName"
, description = "场馆名称"
, required = true
)
,
@Parameter(name = "bookingTime"
, description = "预约时间"
, required = true
)
,
@Parameter(name = "agent"
, description = "代理预约人"
, required = true
)
,
@Parameter(name = "agentId"
, description = "代理id"
, required = true
)
,
@Parameter(name = "updateSupport"
, description = "是否支持更新,默认为 false"
, example = "false"
)
}
)
@PreAuthorize("@ss.hasPermission('sports:venue:booking:import')"
)
public CommonResult<VenueBookingRespVO>importExcel(@RequestParam("file") MultipartFile file,@RequestParam("venueId") Long venueId,@RequestParam("agentId") Long agentId,@RequestParam("venueName") String venueName,@RequestParam("bookingTime") String bookingTime,@RequestParam("agent") String agent,@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {// 解析 Excel 文件为对象列表List<UserImportExcelVO> list = ExcelUtils.read(file, UserImportExcelVO.class);// 调用 service 层进行导入,获取导入结果CommonResult<VenueBookingRespVO> result = venueBookingService.importVenueBookingList(list, venueId, venueName,agent,agentId, bookingTime, updateSupport);// 返回服务层的响应return result;}
@Schema(description = "管理后台 - 观赛预约记录 Response VO"
)
@Data
@ExcelIgnoreUnannotated
public
class VenueBookingRespVO {
@ExcelProperty("代理预定人id"
)
private Long id;
@Schema(description = "预定人"
)
@ExcelProperty("预定人"
)
private String registrant;
@Schema(description = "预定人"
)
@ExcelProperty("代理预定人"
)
private String agent;
@Schema(description = "代理预定人id"
, example = "19070"
)
@ExcelProperty("代理预定人id"
)
private Long agentId;
@Schema(description = "预定人员电话号码"
)
@ExcelProperty("预定人员电话号码"
)
private String phone;
@Schema(description = "预定人员身份证"
)
@ExcelProperty("预定人员身份证"
)
private String idCard;
@Schema(description = "预约时间"
)
@ExcelProperty("预约时间"
)
private LocalDateTime bookingTime;
@Schema(description = "场馆id"
, example = "19070"
)
@ExcelProperty("场馆id"
)
private Long venueId;
@Schema(description = "场馆名称"
)
@ExcelProperty("场馆名称"
)
private String venueName;
@Schema(description = "创建时间"
, requiredMode = Schema.RequiredMode.REQUIRED
)
@ExcelProperty("创建时间"
)
private LocalDateTime createTime;
private String message;
// 带有多个字段的构造方法
public VenueBookingRespVO(String message, String registrant, String phone, String idCard,
LocalDateTime bookingTime, Long venueId, String venueName, LocalDateTime createTime
) {
this.message = message;
this.registrant = registrant;
this.phone = phone;
this.idCard = idCard;
this.bookingTime = bookingTime;
this.venueId = venueId;
this.venueName = venueName;
this.createTime = createTime;
}
}
/**
* 用户 Excel 导入 VO
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false
) // 设置 chain = false,避免用户导入有问题
public
class UserImportExcelVO {
@ExcelProperty("场馆id"
)
private Long venueId;
@ExcelProperty("场馆名称"
)
private String venueName;
@ExcelProperty("预定人姓名"
)
private String registrant;
@ExcelProperty("预定人联系方式"
)
private String phone;
@ExcelProperty("预定人身份证号"
)
private String idCard;
@ExcelProperty("预约时间"
)
private LocalDateTime bookingTime;
}
@Override
@Transactional(rollbackFor = Exception.class)
public CommonResult<VenueBookingRespVO>importVenueBookingList(List<UserImportExcelVO> list, Long venueId, String venueName, String agent, Long agentId, String bookingTime, Boolean updateSupport) {if (list ==null || list.isEmpty()) {return CommonResult.error(400, "导入的数据不能为空!");}List<String> errorMessages =new ArrayList<>();LocalDateTime bookingDateTime = LocalDateTime.parse(bookingTime + "T00:00:00");// 身份证号和手机号验证正则表达式String idCardRegex = "^[1-9]\\d{5}(?:18|19|20)\\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\\d|30|31)\\d{3}[\\dXx]$";Pattern idCardPattern = Pattern.compile(idCardRegex);String phoneRegex = "^1[3-9]\\d{9}$";Pattern phonePattern = Pattern.compile(phoneRegex);List<VenueBookingDO> venueBookingList =new ArrayList<>();Set<String> idCardSet =new HashSet<>();// 用于存储已存在的身份证号,查重for (int i = 0; i < list.size(); i++) {UserImportExcelVO excelVO = list.get(i);errorMessages.clear();// 清空错误信息列表// 验证身份证号格式Matcher idCardMatcher = idCardPattern.matcher(excelVO.getIdCard());if (!idCardMatcher.matches()) {errorMessages.add("第" + (i + 1) + "条记录:身份证号格式不正确");}// 验证手机号格式Matcher phoneMatcher = phonePattern.matcher(excelVO.getPhone());if (!phoneMatcher.matches()) {errorMessages.add("第" + (i + 1) + "条记录:手机号格式不正确");}// 检查身份证号是否重复if (idCardSet.contains(excelVO.getIdCard())) {errorMessages.add("第" + (i + 1) + "条记录:身份数据重复");}else {idCardSet.add(excelVO.getIdCard());// 加入已存在身份证号集合}// 如果有错误,返回错误信息并终止插入if (!errorMessages.isEmpty()) {return CommonResult.error(400, String.join(",", errorMessages));// 使用 error 方法返回错误信息}// 对身份证号进行加密处理String encryptedIdCard = Sm2Util.signMd5(excelVO.getIdCard());excelVO.setVenueId(venueId);excelVO.setVenueName(venueName);excelVO.setBookingTime(bookingDateTime);excelVO.setIdCard(encryptedIdCard);VenueBookingDO venueBookingDO =new VenueBookingDO();venueBookingDO.setVenueId(excelVO.getVenueId());venueBookingDO.setVenueName(excelVO.getVenueName());venueBookingDO.setBookingTime(excelVO.getBookingTime());venueBookingDO.setIdCard(excelVO.getIdCard());// 设置加密后的身份证号venueBookingDO.setPhone(excelVO.getPhone());venueBookingDO.setRegistrant(excelVO.getRegistrant());venueBookingDO.setAgent(agent);venueBookingDO.setAgentId(agentId);venueBookingDO.setBookingStatus(0);venueBookingList.add(venueBookingDO);}// 批量插入数据if (!venueBookingList.isEmpty()) {venueBookingMapper.insertBatch(venueBookingList);}// 返回成功的响应,填充所有字段return CommonResult.success(new VenueBookingRespVO("导入成功", "registrantData", "12345678901", "123456789012345678", bookingDateTime, venueId, venueName, LocalDateTime.now()));}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/931745.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开发网站的基本原则网络购物平台有哪几个

在使用 Kotlin 和 Jetpack Compose 进行 Android 开发时&#xff0c;选择正确的单位来设置视图尺寸、间距和字体大小是非常重要的。这些单位确保应用在各种设备和屏幕尺寸上都能保持良好的布局和可读性。 三种常用的单位 1. Density-independent Pixels (dp) 用途&#xff1…

实验任务1——8

实验1 #include <stdio.h>int main(){ printf(" O\n"); printf("<H>\n"); printf("I I"); return 0;}#include <stdio.h>int main(){ printf(" O\t O\n");…

laya 武器旋转砍怪判断

laya 武器旋转砍怪判断typescript代码:export interface IMonster extends Laya.Node {id: number;isDie:boolean;getPoint(): Laya.Point;getHitRect():Laya.Rectangle; }export class Monster extends Laya.Sprite i…

完整教程:将音频数据累积到缓冲区,达到阈值时触发处理

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

实用指南:Android studio初体验

实用指南:Android studio初体验2025-10-08 17:01 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !import…

一款专门为 WPF 打造的开源 Office 风格用户界面控件库

前言 今天大姚给大家分享一款专门为 WPF 打造的开源(MIT license)、免费的 Office 风格用户界面控件库:Fluent.Ribbon。 项目介绍 Fluent.Ribbon 一个为 Windows Presentation Foundation(WPF)实现类 Office 开源…

一款专门为 WPF 打造的开源 Office 风格用户界面控件库

前言 今天大姚给大家分享一款专门为 WPF 打造的开源(MIT license)、免费的 Office 风格用户界面控件库:Fluent.Ribbon。 项目介绍 Fluent.Ribbon 一个为 Windows Presentation Foundation(WPF)实现类 Office 开源…

部门网站建设内容方案莆田市的网站建设公司

微信小程序页面传值为对象[Object Object]详解 1、先将传递的对象转化为JSON字符串拼接到url上2、在接受对象页面进行转译3、打印结果 1、先将传递的对象转化为JSON字符串拼接到url上 // info为对象 let stationInfo JSON.stringify(info) uni.navigateTo({url: /pages/statio…

网站建设后运维合同湖北省市政工程建设官方网站

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过cpolar 内网穿透软件实现ssh 远程连接kali! 1. 启动kali ssh 服务 默认新安装的kali系统会关闭ssh 连接服务,我们通…

给Ubuntu用户的SSH免密登入公钥文件和文件夹设置权限

给Ubuntu用户的SSH免密登入公钥文件和文件夹设置权限摘要 以root身份登入了Ubuntu,把其他用户的公钥加入其$home/.ssh/authorized_keys文件后,需要设置文件和文件夹的权限。 在配置 SSH 免密登录时,.ssh 目录和 aut…

dockercontainerd代理设置脚本

具体脚本 root@ubuntu-21:~# cat RQproxy.sh #!/bin/bash #用于docker或者containerd开启代理#####################修改成自己的代理的ip+port########################### httpipp=Environment="HTTP_PROXY=htt…

建设部网站官网证书查询广东深圳宝安区

科技观潮techsina与浪同行打造一个自己能全局掌控的硬件一个自己完全掌控的系统找人来开发App创造生态繁荣。让它有用、好用&#xff0c;让更多人用——这一系列动作&#xff0c;我们曾在iPhone的历史上都亲眼见证过。出品 / 新浪科技 ID&#xff1a;techsina作者 / 晓光视频 /…

php开发网站优势怎么做自己的销售网站

BOM&#xff08;Bill of Material&#xff09;物料清单 BOM&#xff08;Bill of Material&#xff09;物料清单&#xff0c;是计算机可以识别的产品结构数据文件&#xff0c;也是ERP的主导文件。BOM使系统识别产品结构&#xff0c;也是联系与沟通企业各项业务的纽带。ERP系统中…

阿里云能做网站么如何查网站是哪个公司做的

在MATLAB中&#xff0c;我们经常需要绘制图形并进行一些自定义的操作。在本示例中&#xff0c;我们将演示如何在MATLAB中绘制一个图形&#xff0c;并通过放大某个特定的区域来突出显示。 ## 原始图形 首先&#xff0c;我们绘制了一个包含正弦和余弦函数的图形。 % MATLAB 代…

自己有服务器和域名怎么做网站网站设计流程是

Michael Arrington曾发表一篇博文说&#xff0c;创业者必须加倍的努力工作&#xff0c;甚至不惜趴在办公桌上睡觉&#xff0c;这样才能成功。对此&#xff0c;我并不赞同其观点&#xff0c;我看了很多评论都是关于这样工作会适得其反&#xff0c;不但没有获得成功&#xff0c;相…

商丘网站建设软件开发平台软件

【二级分销小程序功能介绍】 二级分销小程序是一款专门为企业提供分销管理的移动应用程序。它的主要功能包括商品管理、订单管理、分销设置、分销商等级、分销佣金、分销海报等方面&#xff0c;下面我们逐一进行介绍&#xff1a; 1. 商品管理&#xff1a; 二级分销小程序可以…

2025国庆集训总结

DAY 1 T1 我去这不是简单题?预估\(100\)pts。 T2 我去这不是简单题?立马写,过样例了。交一发。 毕竟是数数题,再捋捋,诶好像不太对,我好像会多算一些方案,把贡献再划分一下,重新写了一遍,感觉这下没问题了。又…

tampermonkey油猴脚本, 动画疯评分显示增强脚本

动画疯评分增强脚本, 能够自动获取和显示动画评分.🎬 按需加载:在页面左下角提供一个控制面板,只有当您点击【获取评分】按钮时,脚本才会开始工作,避免了不必要的资源消耗。 ⭐ 自定义高亮:在获取评分前,会弹窗…

网站备案会检查空间360网站建设官网

配置mongodb副本集实现数据流实时获取 前言操作步骤1. docker拉取mongodb镜像2. 连接mongo1镜像的mongosh3. 在mongosh中初始化副本集 注意点 前言 由于想用nodejs实现实时获取Mongodb数据流&#xff0c;但是报错显示需要有副本集的mongodb才能实现实时获取信息流&#xff0c;…

课后 10.8

import java.util.Random; import java.util.Scanner; import java.util.HashSet; import java.util.Timer; import java.util.TimerTask; public class Math { private static HashSet questionSet = new HashSet<…