浏览代码

提交代码

zlwu 1 年之前
父节点
当前提交
e60161f7b9

+ 73 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormViolationController.java

@@ -4,6 +4,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
 
 
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
 import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.annotation.SaCheckPermission;
@@ -23,9 +24,12 @@ import org.dromara.xg.domain.vo.XgDormViolationVo;
 import org.dromara.xg.domain.bo.XgDormViolationBo;
 import org.dromara.xg.domain.bo.XgDormViolationBo;
 import org.dromara.xg.domain.vo.StudentOptionVo;
 import org.dromara.xg.domain.vo.StudentOptionVo;
 import org.dromara.xg.domain.vo.XgDormViolationExportVo;
 import org.dromara.xg.domain.vo.XgDormViolationExportVo;
+import org.dromara.xg.domain.vo.XgDormViolationImportVo;
 import org.dromara.xg.domain.bo.StudentOptionBo;
 import org.dromara.xg.domain.bo.StudentOptionBo;
 import org.dromara.xg.service.IXgDormViolationService;
 import org.dromara.xg.service.IXgDormViolationService;
+import org.springframework.web.multipart.MultipartFile;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
 
 
 /**
 /**
  * 宿舍违纪记录
  * 宿舍违纪记录
@@ -37,6 +41,7 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
 @RequiredArgsConstructor
 @RequiredArgsConstructor
 @RestController
 @RestController
 @RequestMapping("/dormViolation/dormViolation")
 @RequestMapping("/dormViolation/dormViolation")
+@Slf4j
 public class XgDormViolationController extends BaseController {
 public class XgDormViolationController extends BaseController {
 
 
     private final IXgDormViolationService xgDormViolationService;
     private final IXgDormViolationService xgDormViolationService;
@@ -131,4 +136,72 @@ public class XgDormViolationController extends BaseController {
     public R<StudentOptionVo> getStudentById(@NotNull(message = "学生ID不能为空") @PathVariable Long studentId) {
     public R<StudentOptionVo> getStudentById(@NotNull(message = "学生ID不能为空") @PathVariable Long studentId) {
         return R.ok(xgDormViolationService.getStudentById(studentId));
         return R.ok(xgDormViolationService.getStudentById(studentId));
     }
     }
+
+    /**
+     * 下载宿舍违纪记录导入模板
+     */
+    @Log(title = "宿舍违纪记录", businessType = BusinessType.EXPORT)
+    @SaCheckPermission("dormViolation:dormViolation:import")
+    @PostMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) {
+        // ExcelUtil.exportExcel(
+        // List.of(new XgDormViolationImportVo()),
+        // "宿舍违纪记录",
+        // XgDormViolationImportVo.class,
+        // response);
+        try {
+            // 设置响应头
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = "宿舍违纪信息导入模板.xlsx";
+            fileName = java.net.URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+",
+                    "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" +
+                    fileName);
+
+            // 读取模板文件
+            org.springframework.core.io.Resource resource = new org.springframework.core.io.ClassPathResource(
+                    "dormViolation_template.xlsx");
+            java.io.InputStream inputStream = resource.getInputStream();
+            java.io.OutputStream outputStream = response.getOutputStream();
+
+            // 复制文件内容到响应流
+            byte[] buffer = new byte[1024];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytesRead);
+            }
+
+            // 关闭流
+            inputStream.close();
+            outputStream.flush();
+            outputStream.close();
+
+        } catch (Exception e) {
+            log.error("宿舍违纪信息导入模板失败", e);
+            try {
+                response.setContentType("text/html;charset=utf-8");
+                response.getWriter().write("下载模板失败:" + e.getMessage());
+            } catch (Exception ex) {
+                log.error("写入错误响应失败", ex);
+            }
+        }
+    }
+
+    /**
+     * 导入宿舍违纪记录数据
+     *
+     * @param file          Excel文件
+     * @param updateSupport 是否更新支持,如果已存在,则进行更新数据
+     */
+    @Log(title = "宿舍违纪记录", businessType = BusinessType.IMPORT)
+    @SaCheckPermission("dormViolation:dormViolation:import")
+    @PostMapping("/importData")
+    public R<Void> importData(MultipartFile file, boolean updateSupport) throws Exception {
+        List<XgDormViolationImportVo> violationList = ExcelUtil.importExcel(file.getInputStream(),
+                XgDormViolationImportVo.class);
+        String operName = LoginHelper.getUsername();
+        String message = xgDormViolationService.importDormViolation(violationList, updateSupport, operName);
+        return R.ok(message);
+    }
 }
 }

+ 96 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormViolationImportVo.java

@@ -0,0 +1,96 @@
+package org.dromara.xg.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 宿舍违纪记录导入视图对象 xg_dorm_violation_import
+ *
+ * @author zlwu
+ * @date 2025-01-31
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class XgDormViolationImportVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 学生姓名
+     */
+    @NotBlank(message = "学生姓名不能为空")
+    @ExcelProperty(value = "学生姓名")
+    private String studentName;
+
+    /**
+     * 学号
+     */
+    @NotBlank(message = "学号不能为空")
+    @ExcelProperty(value = "学号")
+    private String studentNo;
+
+    /**
+     * 学院名称
+     */
+    @ExcelProperty(value = "学院名称")
+    private String collegeName;
+
+    /**
+     * 专业名称
+     */
+    @ExcelProperty(value = "专业名称")
+    private String majorName;
+
+    /**
+     * 班级名称
+     */
+    @ExcelProperty(value = "班级名称")
+    private String className;
+
+    /**
+     * 违纪类型
+     */
+    @NotBlank(message = "违纪类型不能为空")
+    @ExcelProperty(value = "违纪类型", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "dorm_violation_type")
+    private String violationType;
+
+    /**
+     * 违纪等级
+     */
+    @NotBlank(message = "违纪等级不能为空")
+    @ExcelProperty(value = "违纪等级", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "dorm_violation_level")
+    private String violationLevel;
+
+    /**
+     * 违纪发生时间
+     */
+    @NotNull(message = "违纪发生时间不能为空")
+    @ExcelProperty(value = "违纪发生时间")
+    private Date violationTime;
+
+    /**
+     * 违纪行为描述
+     */
+    @NotBlank(message = "违纪行为描述不能为空")
+    @ExcelProperty(value = "违纪行为描述")
+    private String violationDescription;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+}

+ 11 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormViolationService.java

@@ -4,6 +4,7 @@ import org.dromara.xg.domain.vo.XgDormViolationVo;
 import org.dromara.xg.domain.bo.XgDormViolationBo;
 import org.dromara.xg.domain.bo.XgDormViolationBo;
 import org.dromara.xg.domain.vo.StudentOptionVo;
 import org.dromara.xg.domain.vo.StudentOptionVo;
 import org.dromara.xg.domain.bo.StudentOptionBo;
 import org.dromara.xg.domain.bo.StudentOptionBo;
+import org.dromara.xg.domain.vo.XgDormViolationImportVo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.PageQuery;
 
 
@@ -92,4 +93,14 @@ public interface IXgDormViolationService {
      * @return 学生详细信息
      * @return 学生详细信息
      */
      */
     StudentOptionVo getStudentByStudentNo(String studentNo);
     StudentOptionVo getStudentByStudentNo(String studentNo);
+
+    /**
+     * 导入宿舍违纪记录数据
+     *
+     * @param importList      导入的宿舍违纪记录列表
+     * @param isUpdateSupport 是否支持更新已存在的记录
+     * @param operName        操作人员名称
+     * @return 导入结果信息
+     */
+    String importDormViolation(List<XgDormViolationImportVo> importList, boolean isUpdateSupport, String operName);
 }
 }

+ 92 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormViolationServiceImpl.java

@@ -2,6 +2,7 @@ package org.dromara.xg.service.impl;
 
 
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.utils.StringUtils;
+import cn.hutool.core.util.ObjectUtil;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -14,6 +15,7 @@ import org.dromara.xg.domain.bo.XgDormViolationBo;
 import org.dromara.xg.domain.vo.XgDormViolationVo;
 import org.dromara.xg.domain.vo.XgDormViolationVo;
 import org.dromara.xg.domain.bo.StudentOptionBo;
 import org.dromara.xg.domain.bo.StudentOptionBo;
 import org.dromara.xg.domain.vo.StudentOptionVo;
 import org.dromara.xg.domain.vo.StudentOptionVo;
+import org.dromara.xg.domain.vo.XgDormViolationImportVo;
 import org.dromara.xg.domain.XgDormViolation;
 import org.dromara.xg.domain.XgDormViolation;
 import org.dromara.xg.mapper.XgDormViolationMapper;
 import org.dromara.xg.mapper.XgDormViolationMapper;
 import org.dromara.xg.service.IXgDormViolationService;
 import org.dromara.xg.service.IXgDormViolationService;
@@ -195,4 +197,94 @@ public class XgDormViolationServiceImpl implements IXgDormViolationService {
     public StudentOptionVo getStudentByStudentNo(String studentNo) {
     public StudentOptionVo getStudentByStudentNo(String studentNo) {
         return baseMapper.selectStudentByStudentNo(studentNo);
         return baseMapper.selectStudentByStudentNo(studentNo);
     }
     }
+
+    /**
+     * 导入宿舍违纪记录数据
+     *
+     * @param importList      导入的宿舍违纪记录列表
+     * @param isUpdateSupport 是否支持更新已存在的记录
+     * @param operName        操作人员名称
+     * @return 导入结果信息
+     */
+    @Override
+    public String importDormViolation(List<XgDormViolationImportVo> importList, boolean isUpdateSupport,
+            String operName) {
+        if (ObjectUtil.isNull(importList) || importList.isEmpty()) {
+            throw new RuntimeException("导入宿舍违纪记录数据不能为空!");
+        }
+
+        int successNum = 0;
+        int failureNum = 0;
+        StringBuilder successMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+
+        for (XgDormViolationImportVo importVo : importList) {
+            try {
+                // 通过学号查询学生信息
+                StudentOptionVo student = this.getStudentByStudentNo(importVo.getStudentNo());
+                if (student == null) {
+                    failureNum++;
+                    String msg = "<br/>" + failureNum + "、学号 " + importVo.getStudentNo() + " 的学生信息不存在";
+                    failureMsg.append(msg);
+                    continue;
+                }
+
+                // 验证学生姓名是否匹配
+                // if (!student.getStudentName().equals(importVo.getStudentName())) {
+                // failureNum++;
+                // String msg = "<br/>" + failureNum + "、学号 " + importVo.getStudentNo() + "
+                // 对应的学生姓名不匹配,数据库中为:"
+                // + student.getStudentName() + ",导入数据为:" + importVo.getStudentName();
+                // failureMsg.append(msg);
+                // continue;
+                // }
+
+                // 创建宿舍违纪记录
+                XgDormViolation violation = new XgDormViolation();
+                violation.setStudentId(student.getId());
+                violation.setStudentName(student.getName());
+                violation.setStudentNo(student.getStudentNo());
+                violation.setCollegeId(student.getCollegeId());
+                violation.setCollegeName(student.getCollegeName());
+                violation.setMajorId(student.getMajorId());
+                violation.setMajorName(student.getMajorName());
+                violation.setClassId(student.getClassId());
+                violation.setClassName(student.getClassName());
+                violation.setCampusId(student.getDormInfo().getCampusId());
+                violation.setCampusName(student.getDormInfo().getCampusName());
+                violation.setAreaId(student.getDormInfo().getAreaId());
+                violation.setAreaName(student.getDormInfo().getAreaName());
+                violation.setBuildingId(student.getDormInfo().getBuildingId());
+                violation.setBuildingName(student.getDormInfo().getBuildingName());
+                violation.setRoomId(student.getDormInfo().getRoomId());
+                violation.setRoomName(student.getDormInfo().getRoomName());
+                violation.setBedId(student.getDormInfo().getBedId());
+                violation.setBedName(student.getDormInfo().getBedName());
+                violation.setViolationType(importVo.getViolationType());
+                violation.setViolationLevel(importVo.getViolationLevel());
+                violation.setViolationTime(importVo.getViolationTime());
+                violation.setViolationDescription(importVo.getViolationDescription());
+                violation.setRemark(importVo.getRemark());
+
+                // 插入数据
+                baseMapper.insert(violation);
+                successNum++;
+                successMsg.append("<br/>" + successNum + "、学号 " + importVo.getStudentNo() + " 的宿舍违纪记录导入成功");
+
+            } catch (Exception e) {
+                failureNum++;
+                String msg = "<br/>" + failureNum + "、学号 " + importVo.getStudentNo() + " 导入失败:" + e.getMessage();
+                failureMsg.append(msg);
+                log.error("导入宿舍违纪记录失败", e);
+            }
+        }
+
+        if (failureNum > 0) {
+            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+            throw new RuntimeException(failureMsg.toString());
+        } else {
+            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+        }
+        return successMsg.toString();
+    }
 }
 }

+ 135 - 1
web/src/views/xg/xgss/dormViolation/dormViolation/index.vue

@@ -120,6 +120,20 @@
           <el-col :span="1.5">
           <el-col :span="1.5">
             <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['dormViolation:dormViolation:export']">导出</el-button>
             <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['dormViolation:dormViolation:export']">导出</el-button>
           </el-col>
           </el-col>
+          <el-col :span="1.5">
+            <el-dropdown class="mt-[1px]">
+              <el-button>
+                更多
+                <el-icon class="el-icon--right"><ArrowDown /></el-icon>
+              </el-button>
+              <template #dropdown>
+                <el-dropdown-menu>
+                  <el-dropdown-item icon="Download" @click="importTemplate">下载模板</el-dropdown-item>
+                  <el-dropdown-item icon="Top" @click="handleImport">导入数据</el-dropdown-item>
+                </el-dropdown-menu>
+              </template>
+            </el-dropdown>
+          </el-col>
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
         </el-row>
         </el-row>
       </template>
       </template>
@@ -551,6 +565,41 @@
         </div>
         </div>
       </template>
       </template>
     </el-dialog>
     </el-dialog>
+
+    <!-- 宿舍违纪导入对话框 -->
+    <el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
+      <el-upload
+        ref="uploadRef"
+        :limit="1"
+        accept=".xlsx, .xls"
+        :headers="upload.headers"
+        :action="getImportUrl()"
+        :disabled="upload.isUploading"
+        :on-progress="handleFileUploadProgress"
+        :on-success="handleFileSuccess"
+        :on-error="handleFileError"
+        :auto-upload="false"
+        drag
+      >
+        <el-icon class="el-icon--upload">
+          <UploadFilled />
+        </el-icon>
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+        <template #tip>
+          <div class="text-center el-upload__tip">
+            <div class="el-upload__tip" style="color: red">提示:不更新已经存在的违纪记录数据</div>
+            <span>仅允许导入xls、xlsx格式文件。</span>
+            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
+          </div>
+        </template>
+      </el-upload>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitFileForm">确 定</el-button>
+          <el-button @click="upload.open = false">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
   </div>
 </template>
 </template>
 
 
@@ -559,10 +608,11 @@ import { ref, reactive, toRefs, onMounted, getCurrentInstance, ComponentInternal
 import ElMessage from 'element-plus/es/components/message/index';
 import ElMessage from 'element-plus/es/components/message/index';
 import ElMessageBox from 'element-plus/es/components/message-box/index';
 import ElMessageBox from 'element-plus/es/components/message-box/index';
 import ElLoading from 'element-plus/es/components/loading/index';
 import ElLoading from 'element-plus/es/components/loading/index';
-import { UploadFilled, Document, Loading, Download, Delete, InfoFilled } from '@element-plus/icons-vue';
+import { UploadFilled, Document, Loading, Download, Delete, InfoFilled, ArrowDown } from '@element-plus/icons-vue';
 import { getToken } from '@/utils/auth';
 import { getToken } from '@/utils/auth';
 import ImagePreview from '@/components/ImagePreview/index.vue';
 import ImagePreview from '@/components/ImagePreview/index.vue';
 import request from '@/utils/request';
 import request from '@/utils/request';
+import { globalHeaders } from '@/utils/request';
 import {
 import {
   listDormViolation,
   listDormViolation,
   getDormViolation,
   getDormViolation,
@@ -709,12 +759,29 @@ const attachmentList = ref<any[]>([]);
 
 
 const queryFormRef = ref<ElFormInstance>();
 const queryFormRef = ref<ElFormInstance>();
 const dormViolationFormRef = ref<ElFormInstance>();
 const dormViolationFormRef = ref<ElFormInstance>();
+const uploadRef = ref<any>();
 
 
 const dialog = reactive<DialogOption>({
 const dialog = reactive<DialogOption>({
   visible: false,
   visible: false,
   title: ''
   title: ''
 });
 });
 
 
+// 导入参数
+const upload = reactive({
+  // 是否显示弹出层
+  open: false,
+  // 弹出层标题
+  title: '',
+  // 是否禁用上传
+  isUploading: false,
+  // 是否更新已经存在的数据
+  updateSupport: false,
+  // 设置上传的请求头部
+  headers: globalHeaders(),
+  // 上传的地址
+  url: import.meta.env.VITE_APP_BASE_API + '/dormViolation/dormViolation/importData'
+});
+
 const initFormData: DormViolationForm = {
 const initFormData: DormViolationForm = {
   id: undefined,
   id: undefined,
   studentId: undefined,
   studentId: undefined,
@@ -999,6 +1066,73 @@ const handleExport = () => {
   );
   );
 };
 };
 
 
+/** 导入按钮操作 */
+const handleImport = () => {
+  upload.title = '宿舍违纪导入';
+  upload.open = true;
+};
+
+/** 下载模板操作 */
+const importTemplate = () => {
+  proxy?.download('dormViolation/dormViolation/importTemplate', {}, `宿舍违纪导入模板_${new Date().getTime()}.xlsx`);
+};
+
+/** 文件上传中处理 */
+const handleFileUploadProgress = () => {
+  upload.isUploading = true;
+};
+
+/** 文件上传成功处理 */
+const handleFileSuccess = (response: any) => {
+  upload.open = false;
+  upload.isUploading = false;
+  uploadRef.value?.clearFiles();
+
+  // 关闭所有loading消息
+  ElMessage.closeAll();
+
+  ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', {
+    dangerouslyUseHTMLString: true
+  });
+  getList();
+};
+
+/** 文件上传失败处理 */
+const handleFileError = (error: any) => {
+  upload.isUploading = false;
+  uploadRef.value?.clearFiles();
+
+  // 关闭所有loading消息
+  ElMessage.closeAll();
+
+  console.error('文件上传失败:', error);
+  ElMessage.error('导入失败,请检查文件格式或网络连接');
+};
+
+/** 获取导入URL */
+const getImportUrl = () => {
+  const baseUrl = upload.url;
+  const params = new URLSearchParams();
+  params.append('updateSupport', 'false');
+  return `${baseUrl}?${params.toString()}`;
+};
+
+/** 提交上传文件 */
+const submitFileForm = () => {
+  // 显示导入loading效果
+  ElMessage({
+    message: '正在导入数据,请耐心等待...',
+    type: 'info',
+    duration: 0
+  });
+
+  // 设置上传中状态
+  upload.isUploading = true;
+
+  // 提交文件上传
+  uploadRef.value?.submit();
+};
+
 /** 远程搜索学生 */
 /** 远程搜索学生 */
 const remoteSearchStudents = async (query: string) => {
 const remoteSearchStudents = async (query: string) => {
   studentLoading.value = true;
   studentLoading.value = true;