From 0dafb47e7dd152a7cb871580ebb0e72762568f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E5=BA=86=E6=B3=B0?= <12369755+htcloud1@user.noreply.gitee.com> Date: Sat, 22 Aug 2026 16:35:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(attendee):=20Excel=20=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=20(=E5=90=8E=E7=AB=AF=20/importData=20+=20/i?= =?UTF-8?q?mportTemplate=20+=205=E9=82=80=E8=AF=B7=E5=B7=AE=E9=9B=86?= =?UTF-8?q?=E6=8E=A8=E9=80=81,=20=E5=89=8D=E7=AB=AF=20dialog-upload=20?= =?UTF-8?q?=E5=A5=97=E7=94=A8=20dialog-import=20SKILL)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BizMeetingAttendeeController.java | 58 +++++++++ .../domain/vo/BizMeetingAttendeeImportVo.java | 113 ++++++++++++++++++ .../service/IBizMeetingAttendeeService.java | 15 +++ .../impl/BizMeetingAttendeeServiceImpl.java | 71 ++++++++++- ry-vue3/src/views/manager/MeetingDetail.vue | 96 +++++++++++++++ 5 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/vo/BizMeetingAttendeeImportVo.java diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingAttendeeController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingAttendeeController.java index 1e0392c..33c84c3 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingAttendeeController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingAttendeeController.java @@ -1,19 +1,27 @@ package com.ruoyi.business.controller; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.business.domain.BizMeeting; import com.ruoyi.business.domain.BizMeetingAttendee; +import com.ruoyi.business.domain.dto.ImportResult; +import com.ruoyi.business.domain.vo.BizMeetingAttendeeImportVo; import com.ruoyi.business.notify.BizNotifyService; import com.ruoyi.business.service.IBizMeetingAttendeeService; import com.ruoyi.business.service.IBizMeetingService; +import javax.servlet.http.HttpServletResponse; + /** * 会议参会人 Controller (劳务协议 / 手写签名 + 管理端 CRUD) * @@ -119,6 +127,56 @@ public class BizMeetingAttendeeController extends BaseController { return toAjax(attendeeService.updateHandsign(entity)); } + /** + * 批量导入参会人 — Excel 模板下载 (Manager/Admin 在 MeetingDetail "批量导入" 按钮触发). + * + *
复用 {@link BizMeetingAttendeeImportVo} 的 @Excel 注解自动生成表头.
+ */
+ @GetMapping("/importTemplate")
+ public void importTemplate(HttpServletResponse response) {
+ ExcelUtil 三步:
+ * 返回 ImportResult { okNum, ngNum, ngList: [{rowNum, message}] }, 前端 ImportResultDialog 直接渲染.
+ *
+ * @param meetingId 会议 id (query 传, 决定导入到哪个会议)
+ */
+ @Log(title = "参会人批量导入", businessType = BusinessType.IMPORT)
+ @PostMapping("/importData")
+ public AjaxResult importData(@RequestParam("file") MultipartFile file,
+ @RequestParam("meetingId") Long meetingId) throws Exception {
+ // 1. 导入前快照
+ Set 仅用于 Excel 批量导入 + 模板下载. 字段顺序与 Excel 列一一对应, 调 sort 时同步改模板下载.
+ *
+ * 关键约束:
+ * 每行处理: 解析 → 校验手机号 → 复用 {@link #insertByPhoneWithProfile} (查/建 sys_user + 写 attendee 行) →
+ * 单行失败不入 ngList, 其它行继续. 返回的 ImportResult 含 okNum/ngNum/ngList 给前端 ImportResultDialog.
+ *
+ * 注意: 本方法**只插数据**, 不触发 #5 会议邀请通知. 调用方 (controller) 拿到成功行后统一调
+ * {@code bizNotifyService.meetingInvitation} 推通知, 避免在 service 层耦合 notify 依赖.
+ *
+ * @return ImportResult { okNum, ngNum, ngList: [{rowNum, message}] }
+ */
+ ImportResult importFromExcel(MultipartFile file, Long meetingId, String operName) throws Exception;
}
\ No newline at end of file
diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java
index 3af3b42..ee5763f 100644
--- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java
+++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java
@@ -6,12 +6,16 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.business.domain.BizMeetingAttendee;
+import com.ruoyi.business.domain.dto.ImportResult;
+import com.ruoyi.business.domain.vo.BizMeetingAttendeeImportVo;
import com.ruoyi.business.mapper.BizMeetingAttendeeMapper;
import com.ruoyi.business.service.IBizMeetingAttendeeService;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.service.ISysUserService;
@@ -160,4 +164,69 @@ public class BizMeetingAttendeeServiceImpl implements IBizMeetingAttendeeService
public List 复用 {@link #insertByPhoneWithProfile} 做单行处理, 让"按手机号查/建 sys_user + 写 attendee"
+ * 逻辑和单条新增一致. 单行失败只计入 ngList, 不中断其它行.
+ *
+ * 注意: 本方法只插数据, 不发 #5 通知. controller 在 import 前快照 userIds, import 后 diff,
+ * 只对"新加入"的 userId 调 notify, 避免给已存在的参会人重发邀请.
+ */
+ @Override
+ public ImportResult importFromExcel(MultipartFile file, Long meetingId, String operName) throws Exception {
+ if (meetingId == null) throw new ServiceException("meetingId 不能为空");
+
+ ExcelUtil
+ *
+ *
+ *
+ *
+ *
+ * @author guoju
+ */
+public class BizMeetingAttendeeImportVo {
+
+ /** 手机号 (必填, 按此查/建 sys_user) */
+ @Excel(name = "手机号", sort = 1)
+ private String phone;
+
+ /** 姓名 */
+ @Excel(name = "姓名", sort = 2)
+ private String name;
+
+ /** 工作单位 */
+ @Excel(name = "工作单位", sort = 3)
+ private String workUnit;
+
+ /** 科室 */
+ @Excel(name = "科室", sort = 4)
+ private String department;
+
+ /** 职称 */
+ @Excel(name = "职称", sort = 5)
+ private String title;
+
+ /** 身份证号 */
+ @Excel(name = "身份证号", sort = 6)
+ private String idCard;
+
+ /** 银行 */
+ @Excel(name = "银行", sort = 7)
+ private String bankName;
+
+ /** 银行卡号码 */
+ @Excel(name = "银行卡号码", sort = 8)
+ private String bankCard;
+
+ /** 开户行 */
+ @Excel(name = "开户行", sort = 9)
+ private String bankBranch;
+
+ /** 角色 (原"劳务形式": 授课/主持/评审...) */
+ @Excel(name = "角色", sort = 10)
+ private String laborForm;
+
+ /** 应发金额 (decimal, 元) */
+ @Excel(name = "应发金额", sort = 11)
+ private BigDecimal feePreTax;
+
+ /** 个税税金 (decimal, 元) */
+ @Excel(name = "个税税金", sort = 12)
+ private BigDecimal tax;
+
+ /** 增值税及附加成本 (decimal, 元) */
+ @Excel(name = "增值税及附加成本", sort = 13)
+ private BigDecimal vatAndSurcharge;
+
+ /** 实发金额 (decimal, 元) */
+ @Excel(name = "实发金额", sort = 14)
+ private BigDecimal fee;
+
+ /** 摘要 */
+ @Excel(name = "摘要", sort = 15)
+ private String summary;
+
+ public String getPhone() { return phone; }
+ public void setPhone(String phone) { this.phone = phone; }
+ public String getName() { return name; }
+ public void setName(String name) { this.name = name; }
+ public String getWorkUnit() { return workUnit; }
+ public void setWorkUnit(String workUnit) { this.workUnit = workUnit; }
+ public String getDepartment() { return department; }
+ public void setDepartment(String department) { this.department = department; }
+ public String getTitle() { return title; }
+ public void setTitle(String title) { this.title = title; }
+ public String getIdCard() { return idCard; }
+ public void setIdCard(String idCard) { this.idCard = idCard; }
+ public String getBankName() { return bankName; }
+ public void setBankName(String bankName) { this.bankName = bankName; }
+ public String getBankCard() { return bankCard; }
+ public void setBankCard(String bankCard) { this.bankCard = bankCard; }
+ public String getBankBranch() { return bankBranch; }
+ public void setBankBranch(String bankBranch) { this.bankBranch = bankBranch; }
+ public String getLaborForm() { return laborForm; }
+ public void setLaborForm(String laborForm) { this.laborForm = laborForm; }
+ public BigDecimal getFeePreTax() { return feePreTax; }
+ public void setFeePreTax(BigDecimal feePreTax) { this.feePreTax = feePreTax; }
+ public BigDecimal getTax() { return tax; }
+ public void setTax(BigDecimal tax) { this.tax = tax; }
+ public BigDecimal getVatAndSurcharge() { return vatAndSurcharge; }
+ public void setVatAndSurcharge(BigDecimal vatAndSurcharge) { this.vatAndSurcharge = vatAndSurcharge; }
+ public BigDecimal getFee() { return fee; }
+ public void setFee(BigDecimal fee) { this.fee = fee; }
+ public String getSummary() { return summary; }
+ public void setSummary(String summary) { this.summary = summary; }
+}
\ No newline at end of file
diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizMeetingAttendeeService.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizMeetingAttendeeService.java
index a902438..6af9ad8 100644
--- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizMeetingAttendeeService.java
+++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizMeetingAttendeeService.java
@@ -1,7 +1,9 @@
package com.ruoyi.business.service;
import java.util.List;
+import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.business.domain.BizMeetingAttendee;
+import com.ruoyi.business.domain.dto.ImportResult;
public interface IBizMeetingAttendeeService {
int insert(BizMeetingAttendee entity);
@@ -45,4 +47,17 @@ public interface IBizMeetingAttendeeService {
* 列表实现层直接返 mapper 结果; 业务方通常用 {@code new HashSet<>(service.selectUserIdsByMeetingId(mid))} 做 contains 判断.
*/
List