فهرست منبع

wuzhen update

wuzhen 1 سال پیش
والد
کامیت
cbfe6be210

+ 131 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/controller/ZsjyInternshipVisitController.java

@@ -0,0 +1,131 @@
+package org.dromara.zsjy.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.zsjy.domain.bo.ZsjyInternshipVisitBo;
+import org.dromara.zsjy.domain.bo.ZsjyInternshipVisitQueryBo;
+import org.dromara.zsjy.domain.vo.ZsjyInternshipVisitVo;
+import org.dromara.zsjy.service.IZsjyInternshipVisitService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 班主任走访Controller
+ *
+ * @author system
+ * @date 2025-01-03
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/zsjy/internshipVisit")
+@Tag(name = "班主任走访管理", description = "班主任走访管理")
+public class ZsjyInternshipVisitController extends BaseController {
+
+    private final IZsjyInternshipVisitService zsjyInternshipVisitService;
+
+    /**
+     * 查询班主任走访列表
+     */
+    @Operation(summary = "查询班主任走访列表")
+    @SaCheckPermission("zsjy:internshipVisit:list")
+    @GetMapping("/list")
+    public TableDataInfo<ZsjyInternshipVisitVo> list(ZsjyInternshipVisitQueryBo bo, PageQuery pageQuery) {
+        return zsjyInternshipVisitService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出班主任走访列表
+     */
+    @Operation(summary = "导出班主任走访列表")
+    @SaCheckPermission("zsjy:internshipVisit:export")
+    @Log(title = "班主任走访", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ZsjyInternshipVisitQueryBo bo, HttpServletResponse response) {
+        List<ZsjyInternshipVisitVo> list = zsjyInternshipVisitService.queryList(bo);
+        ExcelUtil.exportExcel(list, "班主任走访", ZsjyInternshipVisitVo.class, response);
+    }
+
+    /**
+     * 获取班主任走访详细信息
+     */
+    @Operation(summary = "获取班主任走访详细信息")
+    @SaCheckPermission("zsjy:internshipVisit:query")
+    @GetMapping("/{visitId}")
+    @Parameters({
+        @Parameter(name = "visitId", description = "走访记录ID", required = true)
+    })
+    public R<ZsjyInternshipVisitVo> getInfo(@NotNull(message = "走访记录ID不能为空")
+                                     @PathVariable Long visitId) {
+        return R.ok(zsjyInternshipVisitService.queryById(visitId));
+    }
+
+    /**
+     * 新增班主任走访
+     */
+    @Operation(summary = "新增班主任走访")
+    @SaCheckPermission("zsjy:internshipVisit:add")
+    @Log(title = "班主任走访", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ZsjyInternshipVisitBo bo) {
+        return toAjax(zsjyInternshipVisitService.insertByBo(bo));
+    }
+
+    /**
+     * 修改班主任走访
+     */
+    @Operation(summary = "修改班主任走访")
+    @SaCheckPermission("zsjy:internshipVisit:edit")
+    @Log(title = "班主任走访", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ZsjyInternshipVisitBo bo) {
+        return toAjax(zsjyInternshipVisitService.updateByBo(bo));
+    }
+
+    /**
+     * 删除班主任走访
+     */
+    @Operation(summary = "删除班主任走访")
+    @SaCheckPermission("zsjy:internshipVisit:remove")
+    @Log(title = "班主任走访", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{visitIds}")
+    @Parameters({
+        @Parameter(name = "visitIds", description = "走访记录ID串", required = true)
+    })
+    public R<Void> remove(@NotEmpty(message = "走访记录ID不能为空")
+                          @PathVariable Long[] visitIds) {
+        return toAjax(zsjyInternshipVisitService.deleteWithValidByIds(List.of(visitIds), true));
+    }
+
+    /**
+     * 获取可选项列表(学生选择)
+     */
+    @Operation(summary = "获取可选项列表")
+    @SaCheckPermission("zsjy:internshipVisit:list")
+    @GetMapping("/options")
+    public R<List<ZsjyInternshipVisitVo>> getOptions() {
+        List<ZsjyInternshipVisitVo> list = zsjyInternshipVisitService.getStudentOptions();
+        return R.ok(list);
+    }
+}

+ 173 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/ZsjyInternshipVisit.java

@@ -0,0 +1,173 @@
+package org.dromara.zsjy.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 班主任走访实体对象 zsjy_internship_visit
+ *
+ * @author system
+ * @date 2025-01-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("zsjy_internship_visit")
+public class ZsjyInternshipVisit extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 走访记录ID
+     */
+    @TableId(value = "visit_id", type = IdType.ASSIGN_ID)
+    private Long visitId;
+
+    /**
+     * 租户ID
+     */
+    @TableField("tenant_id")
+    private String tenantId;
+
+    /**
+     * 实习ID
+     */
+    @TableField("internship_id")
+    private Long internshipId;
+
+    /**
+     * 学生ID
+     */
+    @TableField("student_id")
+    private Long studentId;
+
+    /**
+     * 企业名称
+     */
+    @TableField("company_name")
+    private String companyName;
+
+    /**
+     * 企业地址
+     */
+    @TableField("company_address")
+    private String companyAddress;
+
+    /**
+     * 走访时间
+     */
+    @TableField("visit_date")
+    private Date visitDate;
+
+    /**
+     * 走访人员
+     */
+    @TableField("visitor_name")
+    private String visitorName;
+
+    /**
+     * 走访人员职务
+     */
+    @TableField("visitor_position")
+    private String visitorPosition;
+
+    /**
+     * 走访目的
+     */
+    @TableField("visit_purpose")
+    private String visitPurpose;
+
+    /**
+     * 企业情况
+     */
+    @TableField("company_situation")
+    private String companySituation;
+
+    /**
+     * 学生表现
+     */
+    @TableField("student_performance")
+    private String studentPerformance;
+
+    /**
+     * 发现问题
+     */
+    @TableField("problems_found")
+    private String problemsFound;
+
+    /**
+     * 改进建议
+     */
+    @TableField("suggestions")
+    private String suggestions;
+
+    /**
+     * 走访总结
+     */
+    @TableField("visit_summary")
+    private String visitSummary;
+
+    /**
+     * 现场照片(JSON格式)
+     */
+    @TableField("visit_images")
+    private String visitImages;
+
+    /**
+     * 走访文件附件(JSON格式)
+     */
+    @TableField("visit_files")
+    private String visitFiles;
+
+    /**
+     * 走访状态:数据库存储数字编码(0-草稿 1-已提交 2-已审核)
+     */
+    @TableField("status")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @TableField("remark")
+    private String remark;
+
+    // ==================== 扩展字段(数据库中不存在,通过关联查询获取) ====================
+
+    /**
+     * 学生姓名 (通过student_id关联查询获取)
+     */
+    @TableField(exist = false)
+    private String studentName;
+
+    /**
+     * 学号 (通过student_id关联查询获取)
+     */
+    @TableField(exist = false)
+    private String studentNumber;
+
+    /**
+     * 班级名称 (通过student_id关联查询获取)
+     */
+    @TableField(exist = false)
+    private String className;
+
+    /**
+     * 专业名称 (通过student_id关联查询获取)
+     */
+    @TableField(exist = false)
+    private String majorName;
+
+    /**
+     * 学院名称 (通过student_id关联查询获取)
+     */
+    @TableField(exist = false)
+    private String collegeName;
+}

+ 131 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/ZsjyInternshipVisitBo.java

@@ -0,0 +1,131 @@
+package org.dromara.zsjy.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.zsjy.domain.ZsjyInternshipVisit;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import java.util.Date;
+
+/**
+ * 班主任走访业务对象 ZsjyInternshipVisitBo
+ *
+ * @author system
+ * @date 2025-01-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ZsjyInternshipVisit.class, reverseConvertGenerate = false)
+public class ZsjyInternshipVisitBo extends BaseEntity {
+
+    /**
+     * 走访记录ID
+     */
+    @NotNull(message = "走访记录ID不能为空", groups = { EditGroup.class })
+    private Long visitId;
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+    /**
+     * 实习ID
+     */
+    @NotNull(message = "实习ID不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long internshipId;
+
+    /**
+     * 学生ID
+     */
+    @NotNull(message = "学生ID不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long studentId;
+
+    /**
+     * 企业名称
+     */
+    @NotBlank(message = "企业名称不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String companyName;
+
+    /**
+     * 企业地址
+     */
+    private String companyAddress;
+
+    /**
+     * 走访时间
+     */
+    @NotNull(message = "走访时间不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Date visitDate;
+
+    /**
+     * 走访人员
+     */
+    @NotBlank(message = "走访人员不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String visitorName;
+
+    /**
+     * 走访人员职务
+     */
+    private String visitorPosition;
+
+    /**
+     * 走访目的
+     */
+    @NotBlank(message = "走访目的不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String visitPurpose;
+
+    /**
+     * 企业情况
+     */
+    private String companySituation;
+
+    /**
+     * 学生表现
+     */
+    private String studentPerformance;
+
+    /**
+     * 发现问题
+     */
+    private String problemsFound;
+
+    /**
+     * 改进建议
+     */
+    private String suggestions;
+
+    /**
+     * 走访总结
+     */
+    private String visitSummary;
+
+    /**
+     * 现场照片(JSON格式)
+     */
+    private String visitImages;
+
+    /**
+     * 走访文件附件(JSON格式)
+     */
+    private String visitFiles;
+
+    /**
+     * 走访状态:数据库存储数字编码(0-草稿 1-已提交 2-已审核)
+     */
+    @Pattern(regexp = "^[0-2]$",
+             message = "走访状态只能是:0(草稿)、1(已提交)、2(已审核)",
+             groups = { AddGroup.class, EditGroup.class })
+    private String status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 86 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/ZsjyInternshipVisitQueryBo.java

@@ -0,0 +1,86 @@
+package org.dromara.zsjy.domain.bo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.util.Date;
+
+/**
+ * 班主任走访查询业务对象 ZsjyInternshipVisitQueryBo
+ *
+ * @author system
+ * @date 2025-01-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "班主任走访查询业务对象")
+public class ZsjyInternshipVisitQueryBo extends BaseEntity {
+
+    /**
+     * 走访记录ID
+     */
+    @Schema(description = "走访记录ID")
+    private Long visitId;
+
+    /**
+     * 实习ID
+     */
+    @Schema(description = "实习ID")
+    private Long internshipId;
+
+    /**
+     * 学生ID
+     */
+    @Schema(description = "学生ID")
+    private Long studentId;
+
+    /**
+     * 学生姓名
+     */
+    @Schema(description = "学生姓名")
+    private String studentName;
+
+    /**
+     * 学号
+     */
+    @Schema(description = "学号")
+    private String studentNumber;
+
+    /**
+     * 班级名称
+     */
+    @Schema(description = "班级名称")
+    private String className;
+
+    /**
+     * 企业名称
+     */
+    @Schema(description = "企业名称")
+    private String companyName;
+
+    /**
+     * 走访人员
+     */
+    @Schema(description = "走访人员")
+    private String visitorName;
+
+    /**
+     * 走访状态:数据库存储数字编码(0-草稿 1-已提交 2-已审核),前端通过字典显示为中文
+     */
+    @Schema(description = "走访状态")
+    private String status;
+
+    /**
+     * 走访开始时间
+     */
+    @Schema(description = "走访开始时间")
+    private Date visitStartDate;
+
+    /**
+     * 走访结束时间
+     */
+    @Schema(description = "走访结束时间")
+    private Date visitEndDate;
+}

+ 201 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/vo/ZsjyInternshipVisitVo.java

@@ -0,0 +1,201 @@
+package org.dromara.zsjy.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.dromara.zsjy.domain.ZsjyInternshipVisit;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 班主任走访视图对象 ZsjyInternshipVisitVo
+ *
+ * @author system
+ * @date 2025-01-03
+ */
+@Data
+@AutoMapper(target = ZsjyInternshipVisit.class)
+@Schema(description = "班主任走访视图对象")
+public class ZsjyInternshipVisitVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 走访记录ID
+     */
+    @Schema(description = "走访记录ID")
+    private Long visitId;
+
+    /**
+     * 租户编号
+     */
+    @Schema(description = "租户编号")
+    private String tenantId;
+
+    /**
+     * 实习ID
+     */
+    @Schema(description = "实习ID")
+    private Long internshipId;
+
+    /**
+     * 学生ID
+     */
+    @Schema(description = "学生ID")
+    private Long studentId;
+
+    /**
+     * 企业名称
+     */
+    @Schema(description = "企业名称")
+    private String companyName;
+
+    /**
+     * 企业地址
+     */
+    @Schema(description = "企业地址")
+    private String companyAddress;
+
+    /**
+     * 走访时间
+     */
+    @Schema(description = "走访时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date visitDate;
+
+    /**
+     * 走访人员
+     */
+    @Schema(description = "走访人员")
+    private String visitorName;
+
+    /**
+     * 走访人员职务
+     */
+    @Schema(description = "走访人员职务")
+    private String visitorPosition;
+
+    /**
+     * 走访目的
+     */
+    @Schema(description = "走访目的")
+    private String visitPurpose;
+
+    /**
+     * 企业情况
+     */
+    @Schema(description = "企业情况")
+    private String companySituation;
+
+    /**
+     * 学生表现
+     */
+    @Schema(description = "学生表现")
+    private String studentPerformance;
+
+    /**
+     * 发现问题
+     */
+    @Schema(description = "发现问题")
+    private String problemsFound;
+
+    /**
+     * 改进建议
+     */
+    @Schema(description = "改进建议")
+    private String suggestions;
+
+    /**
+     * 走访总结
+     */
+    @Schema(description = "走访总结")
+    private String visitSummary;
+
+    /**
+     * 现场照片(JSON格式)
+     */
+    @Schema(description = "现场照片")
+    private String visitImages;
+
+    /**
+     * 走访文件附件(JSON格式)
+     */
+    @Schema(description = "走访文件附件")
+    private String visitFiles;
+
+    /**
+     * 走访状态:数据库存储数字编码(0-草稿 1-已提交 2-已审核),前端通过字典显示为中文
+     */
+    @Schema(description = "走访状态")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @Schema(description = "备注")
+    private String remark;
+
+    // ==================== 关联查询字段 ====================
+
+    /**
+     * 学生姓名
+     */
+    @Schema(description = "学生姓名")
+    private String studentName;
+
+    /**
+     * 学号
+     */
+    @Schema(description = "学号")
+    private String studentNumber;
+
+    /**
+     * 班级名称
+     */
+    @Schema(description = "班级名称")
+    private String className;
+
+    /**
+     * 专业名称
+     */
+    @Schema(description = "专业名称")
+    private String majorName;
+
+    /**
+     * 学院名称
+     */
+    @Schema(description = "学院名称")
+    private String collegeName;
+
+    // ==================== 统一基础字段(强制性,避免MyBatis映射错误) ====================
+
+    /**
+     * 创建者
+     */
+    @Schema(description = "创建者")
+    private Long createBy;
+
+    /**
+     * 创建时间
+     */
+    @Schema(description = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /**
+     * 更新者
+     */
+    @Schema(description = "更新者")
+    private Long updateBy;
+
+    /**
+     * 更新时间
+     */
+    @Schema(description = "更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+}

+ 40 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/mapper/ZsjyInternshipVisitMapper.java

@@ -0,0 +1,40 @@
+package org.dromara.zsjy.mapper;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.zsjy.domain.ZsjyInternshipVisit;
+import org.dromara.zsjy.domain.bo.ZsjyInternshipVisitQueryBo;
+import org.dromara.zsjy.domain.vo.ZsjyInternshipVisitVo;
+
+import java.util.List;
+
+/**
+ * 班主任走访Mapper接口
+ *
+ * @author system
+ * @date 2025-01-03
+ */
+public interface ZsjyInternshipVisitMapper extends BaseMapperPlus<ZsjyInternshipVisit, ZsjyInternshipVisitVo> {
+
+    /**
+     * 根据ID查询班主任走访详情(包含关联学生信息)
+     * @param visitId 走访记录ID
+     * @return 走访详情VO
+     */
+    ZsjyInternshipVisitVo selectById(Long visitId);
+
+    /**
+     * 查询班主任走访列表(包含关联学生信息)
+     * @param bo 查询条件
+     * @return 走访记录列表
+     */
+    List<ZsjyInternshipVisitVo> queryList(ZsjyInternshipVisitQueryBo bo);
+
+    /**
+     * 分页查询班主任走访列表(包含关联学生信息)
+     * @param page 分页参数
+     * @param bo 查询条件
+     * @return 走访记录分页结果
+     */
+    Page<ZsjyInternshipVisitVo> queryPageList(Page<?> page, ZsjyInternshipVisitQueryBo bo);
+}

+ 54 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/IZsjyInternshipVisitService.java

@@ -0,0 +1,54 @@
+package org.dromara.zsjy.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.zsjy.domain.bo.ZsjyInternshipVisitBo;
+import org.dromara.zsjy.domain.bo.ZsjyInternshipVisitQueryBo;
+import org.dromara.zsjy.domain.vo.ZsjyInternshipVisitVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 班主任走访Service接口
+ *
+ * @author system
+ * @date 2025-01-03
+ */
+public interface IZsjyInternshipVisitService {
+
+    /**
+     * 查询班主任走访
+     */
+    ZsjyInternshipVisitVo queryById(Long visitId);
+
+    /**
+     * 查询班主任走访列表
+     */
+    TableDataInfo<ZsjyInternshipVisitVo> queryPageList(ZsjyInternshipVisitQueryBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询班主任走访列表
+     */
+    List<ZsjyInternshipVisitVo> queryList(ZsjyInternshipVisitQueryBo bo);
+
+    /**
+     * 新增班主任走访
+     */
+    Boolean insertByBo(ZsjyInternshipVisitBo bo);
+
+    /**
+     * 修改班主任走访
+     */
+    Boolean updateByBo(ZsjyInternshipVisitBo bo);
+
+    /**
+     * 校验并批量删除班主任走访信息
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 获取学生选项列表(用于下拉选择)
+     */
+    List<ZsjyInternshipVisitVo> getStudentOptions();
+}

+ 116 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/impl/ZsjyInternshipVisitServiceImpl.java

@@ -0,0 +1,116 @@
+package org.dromara.zsjy.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.zsjy.domain.ZsjyInternshipVisit;
+import org.dromara.zsjy.domain.bo.ZsjyInternshipVisitBo;
+import org.dromara.zsjy.domain.bo.ZsjyInternshipVisitQueryBo;
+import org.dromara.zsjy.domain.vo.ZsjyInternshipVisitVo;
+import org.dromara.zsjy.mapper.ZsjyInternshipVisitMapper;
+import org.dromara.zsjy.service.IZsjyInternshipVisitService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 班主任走访Service业务层处理
+ *
+ * @author system
+ * @date 2025-01-03
+ */
+@RequiredArgsConstructor
+@Service
+public class ZsjyInternshipVisitServiceImpl implements IZsjyInternshipVisitService {
+
+    private final ZsjyInternshipVisitMapper baseMapper;
+
+    /**
+     * 查询班主任走访
+     */
+    @Override
+    public ZsjyInternshipVisitVo queryById(Long visitId){
+        return baseMapper.selectById(visitId);
+    }
+
+        /**
+     * 查询班主任走访列表
+     */
+    @Override
+    public TableDataInfo<ZsjyInternshipVisitVo> queryPageList(ZsjyInternshipVisitQueryBo bo, PageQuery pageQuery) {
+        // 使用自定义分页查询方法获取包含学生信息的完整数据
+        Page<ZsjyInternshipVisitVo> result = baseMapper.queryPageList(pageQuery.build(), bo);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询班主任走访列表
+     */
+    @Override
+    public List<ZsjyInternshipVisitVo> queryList(ZsjyInternshipVisitQueryBo bo) {
+        // 使用自定义查询方法获取包含学生信息的完整数据
+        return baseMapper.queryList(bo);
+    }
+
+    /**
+     * 新增班主任走访
+     */
+    @Override
+    public Boolean insertByBo(ZsjyInternshipVisitBo bo) {
+        ZsjyInternshipVisit add = MapstructUtils.convert(bo, ZsjyInternshipVisit.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setVisitId(add.getVisitId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改班主任走访
+     */
+    @Override
+    public Boolean updateByBo(ZsjyInternshipVisitBo bo) {
+        ZsjyInternshipVisit update = MapstructUtils.convert(bo, ZsjyInternshipVisit.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ZsjyInternshipVisit entity){
+        // TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 批量删除班主任走访
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            // TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    /**
+     * 获取学生选项列表(用于下拉选择)
+     */
+    @Override
+    public List<ZsjyInternshipVisitVo> getStudentOptions() {
+        // TODO 实现获取有实习信息的学生列表
+        LambdaQueryWrapper<ZsjyInternshipVisit> lqw = Wrappers.lambdaQuery();
+        lqw.select(ZsjyInternshipVisit::getStudentId, ZsjyInternshipVisit::getInternshipId);
+        lqw.groupBy(ZsjyInternshipVisit::getStudentId);
+        return baseMapper.selectVoList(lqw);
+    }
+}

+ 162 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/resources/mapper/ZsjyInternshipVisitMapper.xml

@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.zsjy.mapper.ZsjyInternshipVisitMapper">
+
+    <resultMap type="org.dromara.zsjy.domain.vo.ZsjyInternshipVisitVo" id="ZsjyInternshipVisitResult">
+        <result property="visitId"              column="visit_id"               />
+        <result property="tenantId"             column="tenant_id"              />
+        <result property="internshipId"         column="internship_id"          />
+        <result property="studentId"            column="student_id"             />
+        <result property="companyName"          column="company_name"           />
+        <result property="companyAddress"       column="company_address"        />
+        <result property="visitDate"            column="visit_date"             />
+        <result property="visitorName"          column="visitor_name"           />
+        <result property="visitorPosition"      column="visitor_position"       />
+        <result property="visitPurpose"         column="visit_purpose"          />
+        <result property="companySituation"     column="company_situation"      />
+        <result property="studentPerformance"   column="student_performance"    />
+        <result property="problemsFound"        column="problems_found"         />
+        <result property="suggestions"          column="suggestions"            />
+        <result property="visitSummary"         column="visit_summary"          />
+        <result property="visitImages"          column="visit_images"           />
+        <result property="visitFiles"           column="visit_files"            />
+        <result property="status"               column="status"                 />
+        <result property="remark"               column="remark"                 />
+        <result property="createBy"             column="create_by"              />
+        <result property="createTime"           column="create_time"            />
+        <result property="updateBy"             column="update_by"              />
+        <result property="updateTime"           column="update_time"            />
+        <!-- 关联查询字段 -->
+        <result property="studentName"          column="student_name"           />
+        <result property="studentNumber"        column="student_number"         />
+        <result property="className"            column="class_name"             />
+        <result property="majorName"            column="major_name"             />
+        <result property="collegeName"          column="college_name"           />
+    </resultMap>
+
+    <sql id="selectZsjyInternshipVisitVo">
+        select
+            v.visit_id,
+            v.tenant_id,
+            v.internship_id,
+            v.student_id,
+            v.company_name,
+            v.company_address,
+            v.visit_date,
+            v.visitor_name,
+            v.visitor_position,
+            v.visit_purpose,
+            v.company_situation,
+            v.student_performance,
+            v.problems_found,
+            v.suggestions,
+            v.visit_summary,
+            v.visit_images,
+            v.visit_files,
+            v.status,
+            v.remark,
+            v.create_by,
+            v.create_time,
+            v.update_by,
+            v.update_time,
+            -- 关联查询学生信息
+            s.name as student_name,
+            s.student_no as student_number,
+            s.class_name as class_name,
+            s.major_name as major_name,
+            s.college_name as college_name
+        from zsjy_internship_visit v
+        left join zsjy_internship_info ii on v.internship_id = ii.internship_id and ii.tenant_id = '000000'
+        left join xg_student_msg s on v.student_id = s.id and s.tenant_id = '000000' and s.deleted = 0
+    </sql>
+
+    <select id="queryList" parameterType="org.dromara.zsjy.domain.bo.ZsjyInternshipVisitQueryBo" resultMap="ZsjyInternshipVisitResult">
+        <include refid="selectZsjyInternshipVisitVo"/>
+        <where>
+            v.deleted = 0 and v.tenant_id = '000000'
+            <if test="visitId != null">
+                and v.visit_id = #{visitId}
+            </if>
+            <if test="internshipId != null">
+                and v.internship_id = #{internshipId}
+            </if>
+            <if test="studentId != null">
+                and v.student_id = #{studentId}
+            </if>
+            <if test="studentName != null and studentName != ''">
+                and s.name like concat('%', #{studentName}, '%')
+            </if>
+            <if test="studentNumber != null and studentNumber != ''">
+                and s.student_no like concat('%', #{studentNumber}, '%')
+            </if>
+            <if test="className != null and className != ''">
+                and s.class_name like concat('%', #{className}, '%')
+            </if>
+            <if test="companyName != null and companyName != ''">
+                and v.company_name like concat('%', #{companyName}, '%')
+            </if>
+            <if test="visitorName != null and visitorName != ''">
+                and v.visitor_name like concat('%', #{visitorName}, '%')
+            </if>
+            <if test="status != null and status != ''">
+                and v.status = #{status}
+            </if>
+            <if test="visitStartDate != null">
+                and v.visit_date &gt;= #{visitStartDate}
+            </if>
+            <if test="visitEndDate != null">
+                and v.visit_date &lt;= #{visitEndDate}
+            </if>
+        </where>
+        order by v.visit_date desc, v.create_time desc
+    </select>
+
+    <select id="queryPageList" parameterType="org.dromara.zsjy.domain.bo.ZsjyInternshipVisitQueryBo" resultMap="ZsjyInternshipVisitResult">
+        <include refid="selectZsjyInternshipVisitVo"/>
+        <where>
+            v.deleted = 0 and v.tenant_id = '000000'
+            <if test="bo.visitId != null">
+                and v.visit_id = #{bo.visitId}
+            </if>
+            <if test="bo.internshipId != null">
+                and v.internship_id = #{bo.internshipId}
+            </if>
+            <if test="bo.studentId != null">
+                and v.student_id = #{bo.studentId}
+            </if>
+            <if test="bo.studentName != null and bo.studentName != ''">
+                and s.name like concat('%', #{bo.studentName}, '%')
+            </if>
+            <if test="bo.studentNumber != null and bo.studentNumber != ''">
+                and s.student_no like concat('%', #{bo.studentNumber}, '%')
+            </if>
+            <if test="bo.className != null and bo.className != ''">
+                and s.class_name like concat('%', #{bo.className}, '%')
+            </if>
+            <if test="bo.companyName != null and bo.companyName != ''">
+                and v.company_name like concat('%', #{bo.companyName}, '%')
+            </if>
+            <if test="bo.visitorName != null and bo.visitorName != ''">
+                and v.visitor_name like concat('%', #{bo.visitorName}, '%')
+            </if>
+            <if test="bo.status != null and bo.status != ''">
+                and v.status = #{bo.status}
+            </if>
+            <if test="bo.visitStartDate != null">
+                and v.visit_date &gt;= #{bo.visitStartDate}
+            </if>
+            <if test="bo.visitEndDate != null">
+                and v.visit_date &lt;= #{bo.visitEndDate}
+            </if>
+        </where>
+        order by v.visit_date desc, v.create_time desc
+    </select>
+
+    <select id="selectById" parameterType="Long" resultMap="ZsjyInternshipVisitResult">
+        <include refid="selectZsjyInternshipVisitVo"/>
+        where v.visit_id = #{visitId} and v.deleted = 0 and v.tenant_id = '000000'
+    </select>
+
+</mapper>

+ 85 - 0
web/src/api/zsjy/internshipVisit.ts

@@ -0,0 +1,85 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { InternshipVisitVO, InternshipVisitQuery, InternshipVisitAddBO, InternshipVisitEditBO } from '@/api/zsjy/types';
+import { TableDataInfo, R } from '@/api/types';
+
+/**
+ * 查询班主任走访记录列表
+ * @param query 查询参数
+ */
+export const listInternshipVisit = (query?: InternshipVisitQuery): AxiosPromise<TableDataInfo<InternshipVisitVO>> => {
+  return request({
+    url: '/zsjy/internshipVisit/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询班主任走访记录详细信息
+ * @param visitId 走访记录ID
+ */
+export const getInternshipVisit = (visitId: string | number): AxiosPromise<R<InternshipVisitVO>> => {
+  return request({
+    url: `/zsjy/internshipVisit/${visitId}`,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增班主任走访记录
+ * @param data 走访记录信息
+ */
+export const addInternshipVisit = (data: InternshipVisitAddBO): AxiosPromise<R<void>> => {
+  return request({
+    url: '/zsjy/internshipVisit',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改班主任走访记录
+ * @param data 走访记录信息
+ */
+export const updateInternshipVisit = (data: InternshipVisitEditBO): AxiosPromise<R<void>> => {
+  return request({
+    url: '/zsjy/internshipVisit',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除班主任走访记录
+ * @param visitIds 走访记录ID
+ */
+export const delInternshipVisit = (visitIds: string | number | Array<string | number>): AxiosPromise<R<void>> => {
+  return request({
+    url: `/zsjy/internshipVisit/${visitIds}`,
+    method: 'delete'
+  });
+};
+
+/**
+ * 导出班主任走访记录列表
+ * @param query 查询参数
+ */
+export const exportInternshipVisit = (query: InternshipVisitQuery) => {
+  return request({
+    url: '/zsjy/internshipVisit/export',
+    method: 'post',
+    data: query,
+    responseType: 'blob'
+  });
+};
+
+/**
+ * 获取可选项列表(学生选择)
+ */
+export const getInternshipVisitOptions = (): AxiosPromise<R<InternshipVisitVO[]>> => {
+  return request({
+    url: '/zsjy/internshipVisit/options',
+    method: 'get'
+  });
+};

+ 1056 - 0
web/src/views/zsjy/internship/visit/index.vue

@@ -0,0 +1,1056 @@
+<template>
+  <div class="app-container">
+    <!-- 统计卡片 -->
+    <el-row :gutter="16" class="stats-row">
+      <el-col :xl="6" :lg="12" :md="12" :sm="12" :xs="24">
+        <div class="stat-card total">
+          <div class="stat-icon">
+            <el-icon><User /></el-icon>
+          </div>
+          <div class="stat-content">
+            <div class="stat-number">{{ statistics.total }}</div>
+            <div class="stat-label">走访总数</div>
+          </div>
+        </div>
+      </el-col>
+      <el-col :xl="6" :lg="12" :md="12" :sm="12" :xs="24">
+        <div class="stat-card month">
+          <div class="stat-icon">
+            <el-icon><Calendar /></el-icon>
+          </div>
+          <div class="stat-content">
+            <div class="stat-number">{{ statistics.monthCount }}</div>
+            <div class="stat-label">本月走访</div>
+          </div>
+        </div>
+      </el-col>
+      <el-col :xl="6" :lg="12" :md="12" :sm="12" :xs="24">
+        <div class="stat-card completed">
+          <div class="stat-icon">
+            <el-icon><CircleCheck /></el-icon>
+          </div>
+          <div class="stat-content">
+            <div class="stat-number">{{ statistics.completedCount }}</div>
+            <div class="stat-label">已完成</div>
+          </div>
+        </div>
+      </el-col>
+      <el-col :xl="6" :lg="12" :md="12" :sm="12" :xs="24">
+        <div class="stat-card pending">
+          <div class="stat-icon">
+            <el-icon><Clock /></el-icon>
+          </div>
+          <div class="stat-content">
+            <div class="stat-number">{{ statistics.pendingCount }}</div>
+            <div class="stat-label">待走访</div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 搜索区域 -->
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" label-width="100px">
+            <!-- 搜索条件 -->
+            <el-row :gutter="20">
+              <el-col :span="6">
+                <el-form-item label="学生信息" prop="studentName">
+                  <el-input
+                    v-model="queryParams.studentName"
+                    placeholder="请输入学生姓名或学号"
+                    clearable
+                    style="width: 100%"
+                    @keyup.enter="handleQuery"
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="实习单位" prop="companyName">
+                  <el-input v-model="queryParams.companyName" placeholder="请输入实习单位名称" clearable style="width: 100%" @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="走访人" prop="visitorName">
+                  <el-input v-model="queryParams.visitorName" placeholder="请输入走访人姓名" clearable style="width: 100%" @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="走访状态" prop="status">
+                  <el-select v-model="queryParams.status" placeholder="请选择走访状态" clearable style="width: 100%">
+                    <el-option v-for="dict in visitStatusOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-row>
+
+            <!-- 第二行:日期范围 + 操作按钮 -->
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="走访日期" prop="visitDateRange">
+                  <el-date-picker
+                    v-model="queryParams.visitDateRange"
+                    type="daterange"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    value-format="YYYY-MM-DD"
+                    style="width: 100%"
+                  />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <div class="search-btn-container">
+                  <el-form-item>
+                                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+
+                    <el-button type="info" plain icon="Search" @click="showSearch = !showSearch">
+                      {{ showSearch ? '隐藏' : '显示' }}搜索
+                    </el-button>
+                  </el-form-item>
+                </div>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <!-- 操作按钮栏 -->
+    <el-card shadow="hover" class="button-card">
+      <template #header>
+        <div class="card-header-vertical">
+          <div class="toolbar-title">班主任走访列表</div>
+          <div class="button-group">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:internshipVisit:add']">新增走访</el-button>
+            <el-button type="success" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:internshipVisit:export']">导出</el-button>
+            <el-button type="info" plain icon="Search" @click="showSearch = !showSearch">
+              {{ showSearch ? '隐藏' : '显示' }}搜索
+            </el-button>
+            <el-tooltip content="刷新" placement="top">
+              <el-button icon="Refresh" circle @click="getList" />
+            </el-tooltip>
+          </div>
+        </div>
+      </template>
+    </el-card>
+
+    <!-- 数据表格 -->
+    <el-card shadow="hover" class="table-card">
+      <div class="table-container">
+        <el-table
+          v-loading="loading"
+          :data="visitList"
+          row-key="visitId"
+          border
+          stripe
+          highlight-current-row
+          @selection-change="handleSelectionChange"
+        >
+          <el-table-column type="selection" width="50" align="center" />
+
+          <el-table-column label="学生信息" min-width="150" align="left">
+            <template #default="scope">
+              <div class="student-info">
+                <div class="student-name">{{ scope.row.studentName }}</div>
+                <div class="student-number">学号:{{ scope.row.studentNumber }}</div>
+                <div class="student-class">班级:{{ scope.row.className }}</div>
+              </div>
+            </template>
+          </el-table-column>
+
+          <el-table-column label="实习单位" prop="companyName" min-width="150" align="left" show-overflow-tooltip />
+
+          <el-table-column label="走访人" prop="visitorName" min-width="100" align="center" />
+
+          <el-table-column label="走访日期" prop="visitDate" min-width="120" align="center">
+            <template #default="scope">
+              {{ parseTime(scope.row.visitDate, '{y}-{m}-{d}') }}
+            </template>
+          </el-table-column>
+
+
+
+          <el-table-column label="走访状态" prop="status" min-width="100" align="center">
+            <template #default="scope">
+              <dict-tag :options="visitStatusOptions" :value="scope.row.status || ''" />
+            </template>
+          </el-table-column>
+
+          <el-table-column label="创建时间" prop="createTime" min-width="160" align="center">
+            <template #default="scope">
+              {{ parseTime(scope.row.createTime) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+              <div class="action-buttons">
+                <el-tooltip content="查看详情" placement="top">
+                  <el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['zsjy:internshipVisit:query']" />
+                </el-tooltip>
+                <el-tooltip content="编辑" placement="top">
+                  <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['zsjy:internshipVisit:edit']" />
+                </el-tooltip>
+                <el-tooltip content="删除" placement="top">
+                  <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['zsjy:internshipVisit:remove']" />
+                </el-tooltip>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <!-- 分页组件 -->
+      <div class="pagination-container">
+        <pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
+      </div>
+    </el-card>
+
+    <!-- 详情对话框 -->
+    <el-dialog title="走访详情" v-model="viewDialogVisible" width="800px" append-to-body>
+      <div v-if="currentRecord" class="detail-container">
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="学生姓名">{{ currentRecord.studentName }}</el-descriptions-item>
+          <el-descriptions-item label="学号">{{ currentRecord.studentNumber }}</el-descriptions-item>
+          <el-descriptions-item label="班级">{{ currentRecord.className }}</el-descriptions-item>
+          <el-descriptions-item label="实习单位">{{ currentRecord.companyName }}</el-descriptions-item>
+          <el-descriptions-item label="走访人">{{ currentRecord.visitorName }}</el-descriptions-item>
+          <el-descriptions-item label="走访日期">
+            {{ parseTime(currentRecord.visitDate, '{y}-{m}-{d}') }}
+          </el-descriptions-item>
+          <el-descriptions-item label="走访状态">
+            <dict-tag :options="visitStatusOptions" :value="currentRecord.status || ''" />
+          </el-descriptions-item>
+          <el-descriptions-item label="走访目的" :span="2">{{ currentRecord.visitPurpose || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="企业情况" :span="2">
+            <div style="white-space: pre-wrap">{{ currentRecord.companySituation || '-' }}</div>
+          </el-descriptions-item>
+          <el-descriptions-item label="学生表现" :span="2">
+            <div style="white-space: pre-wrap">{{ currentRecord.studentPerformance || '-' }}</div>
+          </el-descriptions-item>
+          <el-descriptions-item label="发现问题" :span="2">
+            <div style="white-space: pre-wrap">{{ currentRecord.problemsFound || '-' }}</div>
+          </el-descriptions-item>
+          <el-descriptions-item label="改进建议" :span="2">
+            <div style="white-space: pre-wrap">{{ currentRecord.suggestions || '-' }}</div>
+          </el-descriptions-item>
+          <el-descriptions-item label="走访总结" :span="2">
+            <div style="white-space: pre-wrap">{{ currentRecord.visitSummary || '-' }}</div>
+          </el-descriptions-item>
+          <el-descriptions-item label="备注" :span="2">
+            <div style="white-space: pre-wrap">{{ currentRecord.remark || '-' }}</div>
+          </el-descriptions-item>
+          <el-descriptions-item label="创建时间">
+            {{ parseTime(currentRecord.createTime) }}
+          </el-descriptions-item>
+          <el-descriptions-item label="更新时间">
+            {{ parseTime(currentRecord.updateTime) }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+      <template #footer>
+        <div class="dialog-footer-right">
+          <el-button @click="viewDialogVisible = false">关闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 新增/编辑对话框 -->
+    <el-dialog :title="formTitle" v-model="formDialogVisible" width="800px" append-to-body>
+      <el-form ref="formRef" :model="form" :rules="formRules" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="学生" prop="studentId" required>
+              <el-select
+                v-model="form.studentId"
+                placeholder="请选择学生"
+                filterable
+                remote
+                :remote-method="searchStudents"
+                :loading="studentLoading"
+                style="width: 100%"
+                @change="handleStudentChange"
+                @focus="handleStudentFocus"
+              >
+                <el-option
+                  v-for="student in studentOptions"
+                  :key="student.studentId"
+                  :label="`${student.studentName}(${student.studentNo})- ${student.className}`"
+                  :value="student.studentId"
+                />
+                <!-- 空数据提示 -->
+                <el-option
+                  v-if="studentOptions.length === 0 && !studentLoading"
+                  disabled
+                  label="暂无学生数据"
+                  value=""
+                />
+              </el-select>
+              <!-- 提示信息 -->
+              <div class="form-item-tip">
+                系统将自动显示您所管理班级的学生,支持输入姓名或学号搜索
+              </div>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="走访人" prop="visitorName" required>
+              <el-input v-model="form.visitorName" placeholder="请输入走访人姓名" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="走访日期" prop="visitDate" required>
+              <el-date-picker v-model="form.visitDate" type="date" placeholder="请选择走访日期" value-format="YYYY-MM-DD" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="走访状态" prop="status" required>
+              <el-select v-model="form.status" placeholder="请选择走访状态" style="width: 100%">
+                <el-option v-for="dict in visitStatusOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="走访目的" prop="visitPurpose" required>
+          <el-input v-model="form.visitPurpose" placeholder="请输入走访目的" />
+        </el-form-item>
+        <el-form-item label="企业情况" prop="companySituation">
+          <el-input v-model="form.companySituation" type="textarea" :rows="3" placeholder="请输入企业基本情况" />
+        </el-form-item>
+        <el-form-item label="学生表现" prop="studentPerformance">
+          <el-input v-model="form.studentPerformance" type="textarea" :rows="3" placeholder="请输入学生在企业的表现情况" />
+        </el-form-item>
+        <el-form-item label="发现问题" prop="problemsFound">
+          <el-input v-model="form.problemsFound" type="textarea" :rows="3" placeholder="请输入发现的问题" />
+        </el-form-item>
+        <el-form-item label="改进建议" prop="suggestions">
+          <el-input v-model="form.suggestions" type="textarea" :rows="3" placeholder="请输入改进建议" />
+        </el-form-item>
+        <el-form-item label="走访总结" prop="visitSummary">
+          <el-input v-model="form.visitSummary" type="textarea" :rows="4" placeholder="请输入走访总结" />
+        </el-form-item>
+        <el-form-item label="现场照片" prop="visitImages">
+          <image-upload v-model="form.visitImages" :limit="10" :file-size="10" />
+          <div class="form-item-tip">支持上传jpg、png、gif格式,单张图片不超过2MB,总大小不超过10MB</div>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注信息" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer-right">
+          <el-button @click="formDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitForm" :loading="formLoading">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="InternshipVisit">
+import { ref, reactive, onMounted, computed, getCurrentInstance } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { parseTime } from '@/utils/ruoyi';
+import { getDicts } from '@/api/system/dict/data/index';
+import { useUserStore } from '@/store/modules/user';
+import {
+  listInternshipVisit,
+  getInternshipVisit,
+  addInternshipVisit,
+  updateInternshipVisit,
+  delInternshipVisit,
+  exportInternshipVisit
+} from '@/api/zsjy/internshipVisit';
+import { listInternshipInfo } from '@/api/zsjy/internship';
+import ImageUpload from '@/components/ImageUpload/index.vue';
+
+// 获取组件实例和store
+const { proxy } = getCurrentInstance() as any;
+const userStore = useUserStore();
+
+// 响应式数据
+const loading = ref(true);
+const showSearch = ref(true);
+const formLoading = ref(false);
+const studentLoading = ref(false);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const visitList = ref([]);
+const formTitle = ref('');
+const viewDialogVisible = ref(false);
+const formDialogVisible = ref(false);
+const currentRecord = ref(null);
+
+// 统计数据
+const statistics = ref({
+  total: 0,
+  monthCount: 0,
+  completedCount: 0,
+  pendingCount: 0
+});
+
+// 字典数据
+const visitStatusOptions = ref([]);
+const studentOptions = ref([]);
+
+// 查询参数
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  studentName: undefined,
+  companyName: undefined,
+  visitorName: undefined,
+  status: undefined,
+  visitDateRange: [],
+  visitStartDate: undefined,
+  visitEndDate: undefined
+});
+
+// 表单数据
+const form = reactive({
+  visitId: undefined,
+  internshipId: undefined, // 实习ID
+  studentId: undefined,
+  studentName: undefined,
+  studentNumber: undefined,
+  className: undefined,
+  companyName: undefined,
+  visitorName: undefined,
+  visitDate: undefined,
+  status: '0', // 0-草稿(对应字典的planned)
+  visitPurpose: undefined,
+  companySituation: undefined,
+  studentPerformance: undefined,
+  problemsFound: undefined,
+  suggestions: undefined,
+  visitSummary: undefined,
+  visitImages: undefined,
+  remark: undefined
+});
+
+// 表单验证规则
+const formRules = {
+  studentId: [{ required: true, message: '请选择学生', trigger: 'change' }],
+  visitorName: [{ required: true, message: '请输入走访人姓名', trigger: 'blur' }],
+  visitDate: [{ required: true, message: '请选择走访日期', trigger: 'change' }],
+  visitPurpose: [{ required: true, message: '请输入走访目的', trigger: 'blur' }],
+  status: [{ required: true, message: '请选择走访状态', trigger: 'change' }]
+};
+
+// 引用
+const queryRef = ref();
+const formRef = ref();
+
+/** 查询走访记录列表 */
+function getList() {
+  loading.value = true;
+  const params = { ...queryParams } as any;
+  if (params.visitDateRange && params.visitDateRange.length === 2) {
+    params.visitStartDate = params.visitDateRange[0];
+    params.visitEndDate = params.visitDateRange[1];
+  }
+  delete params.visitDateRange;
+
+  listInternshipVisit(params).then((response) => {
+    console.log('API响应数据:', response); // 用于调试
+
+    // 健壮的响应处理逻辑
+    if (response && typeof response === 'object') {
+      // 情况1:直接的TableDataInfo格式 {total: number, rows: array}
+      if (response.rows !== undefined && response.total !== undefined) {
+        visitList.value = response.rows || [];
+        total.value = response.total || 0;
+      }
+      // 情况2:嵌套在data中的TableDataInfo格式 {data: {total: number, rows: array}}
+      else if (response.data && response.data.rows !== undefined && response.data.total !== undefined) {
+        visitList.value = response.data.rows || [];
+        total.value = response.data.total || 0;
+      }
+      // 情况3:直接返回数组格式
+      else if (Array.isArray(response)) {
+        visitList.value = response;
+        total.value = response.length;
+      }
+      // 情况4:嵌套的数组格式
+      else if (response.data && Array.isArray(response.data)) {
+        visitList.value = response.data;
+        total.value = response.data.length;
+      }
+      // 其他未知格式
+      else {
+        console.warn('未识别的API响应格式:', response);
+        visitList.value = [];
+        total.value = 0;
+      }
+    } else {
+      console.warn('API响应不是对象类型:', response);
+      visitList.value = [];
+      total.value = 0;
+    }
+
+    loading.value = false;
+    // 计算统计数据
+    calculateStatistics();
+  }).catch((error) => {
+    console.error('获取走访记录列表失败:', error);
+    visitList.value = [];
+    total.value = 0;
+    loading.value = false;
+    // 可以添加用户友好的错误提示
+    proxy?.$modal.msgError('获取数据失败,请稍后重试');
+  });
+}
+
+/** 计算统计数据 */
+function calculateStatistics() {
+  const now = new Date();
+  const currentMonth = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0');
+
+  statistics.value = {
+    total: visitList.value.length,
+    monthCount: visitList.value.filter((item) => item.visitDate && item.visitDate.startsWith(currentMonth)).length,
+    completedCount: visitList.value.filter((item) => item.status === '2').length, // 2-已审核
+    pendingCount: visitList.value.filter((item) => item.status === '0').length // 0-草稿
+  };
+}
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  queryParams.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+  queryRef.value.resetFields();
+  queryParams.visitDateRange = [];
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+function handleSelectionChange(selection) {
+  ids.value = selection.map((item) => item.visitId);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+function handleAdd() {
+  reset();
+  formDialogVisible.value = true;
+  formTitle.value = '新增走访记录';
+  // 新增:自动加载当前班主任班级的学生列表
+  loadCurrentTeacherStudents();
+}
+
+/** 查看详情 */
+function handleView(row) {
+  currentRecord.value = { ...row };
+  viewDialogVisible.value = true;
+}
+
+/** 修改按钮操作 */
+function handleUpdate(row) {
+  reset();
+  const visitId = row.visitId;
+  getInternshipVisit(visitId).then((response) => {
+    Object.assign(form, response.data);
+    formDialogVisible.value = true;
+    formTitle.value = '修改走访记录';
+  });
+}
+
+/** 提交按钮 */
+function submitForm() {
+  formRef.value.validate((valid) => {
+    if (valid) {
+      // 检查实习ID是否已设置
+      if (!form.internshipId) {
+        ElMessage.error('请先选择学生,系统将自动设置实习ID');
+        return;
+      }
+
+      console.log('提交表单数据:', form);
+      formLoading.value = true;
+
+      if (form.visitId != undefined) {
+        updateInternshipVisit(form)
+          .then((response) => {
+            ElMessage.success('修改成功');
+            formDialogVisible.value = false;
+            getList();
+          })
+          .finally(() => {
+            formLoading.value = false;
+          });
+      } else {
+        addInternshipVisit(form)
+          .then((response) => {
+            ElMessage.success('新增成功');
+            formDialogVisible.value = false;
+            getList();
+          })
+          .finally(() => {
+            formLoading.value = false;
+          });
+      }
+    }
+  });
+}
+
+/** 删除按钮操作 */
+function handleDelete(row) {
+  const visitIds = row.visitId || ids.value;
+  ElMessageBox.confirm('是否确认删除走访记录编号为"' + visitIds + '"的数据项?')
+    .then(function () {
+      return delInternshipVisit(visitIds);
+    })
+    .then(() => {
+      getList();
+      ElMessage.success('删除成功');
+    })
+    .catch(() => {});
+}
+
+/** 导出按钮操作 */
+function handleExport() {
+  const params = { ...queryParams } as any;
+  if (params.visitDateRange && params.visitDateRange.length === 2) {
+    params.visitStartDate = params.visitDateRange[0];
+    params.visitEndDate = params.visitDateRange[1];
+  }
+  delete params.visitDateRange;
+
+  ElMessageBox.confirm('是否确认导出所有走访记录数据项?')
+    .then(function () {
+      return exportInternshipVisit(params);
+    })
+    .then((response) => {
+      // 处理下载
+      const blob = new Blob([response.data]);
+      const url = window.URL.createObjectURL(blob);
+      const link = document.createElement('a');
+      link.href = url;
+      link.download = '班主任走访记录_' + parseTime(new Date(), '{y}{m}{d}{h}{i}{s}') + '.xlsx';
+      link.click();
+      window.URL.revokeObjectURL(url);
+    })
+    .catch(() => {});
+}
+
+/** 搜索学生 */
+function searchStudents(query) {
+  studentLoading.value = true;
+
+  // 获取当前登录用户的班主任ID
+  const teacherId = userStore.userId;
+
+  // 构建查询参数
+  const params: any = {
+    pageNum: 1,
+    pageSize: 100,
+    classTeacherId: teacherId // 始终传递班主任ID
+  };
+
+  // 如果有搜索关键词,添加搜索条件
+  if (query && query.trim() !== '') {
+    const trimmedQuery = query.trim();
+    // 支持按姓名或学号搜索
+    if (/^\d+$/.test(trimmedQuery)) {
+      // 纯数字,按学号搜索
+      params.studentNo = trimmedQuery;
+    } else {
+      // 包含字母或中文,按姓名搜索
+      params.studentName = trimmedQuery;
+    }
+  }
+
+  console.log('搜索学生参数:', params);
+
+  listInternshipInfo(params)
+    .then((response) => {
+      if (response.rows && Array.isArray(response.rows)) {
+        studentOptions.value = response.rows;
+        console.log('搜索到学生数据:', response.rows.length, '条');
+        console.log('学生数据示例:', response.rows.slice(0, 3));
+      } else {
+        studentOptions.value = [];
+        console.warn('未搜索到学生数据');
+      }
+      studentLoading.value = false;
+    })
+    .catch((error) => {
+      console.error('搜索学生失败:', error);
+      studentOptions.value = [];
+      studentLoading.value = false;
+      ElMessage.error('搜索学生失败,请稍后重试');
+    });
+}
+
+/** 学生选择变化 */
+function handleStudentChange(studentId) {
+  const student = studentOptions.value.find((item) => item.studentId === studentId);
+  if (student) {
+    console.log('选择的学生完整数据:', student);
+    form.studentName = student.studentName;
+    form.studentNumber = student.studentNo;
+    form.className = student.className;
+    form.companyName = student.companyName;
+
+    // 尝试多种可能的实习ID字段名
+    if (student.internshipId) {
+      form.internshipId = student.internshipId;
+    } else if (student.id) {
+      form.internshipId = student.id;
+    } else if (student.internshipInfoId) {
+      form.internshipId = student.internshipInfoId;
+    } else {
+      console.warn('未找到实习ID字段,学生数据:', student);
+      form.internshipId = undefined;
+    }
+
+    console.log('设置后的表单数据:', {
+      studentId: form.studentId,
+      internshipId: form.internshipId,
+      studentName: form.studentName
+    });
+  }
+}
+
+/** 学生选择器获得焦点时的处理 */
+function handleStudentFocus() {
+  // 如果当前没有学生数据,自动加载一次
+  if (studentOptions.value.length === 0) {
+    loadCurrentTeacherStudents();
+  }
+}
+
+/** 表单重置 */
+function reset() {
+  Object.assign(form, {
+    visitId: undefined,
+    internshipId: undefined, // 实习ID
+    studentId: undefined,
+    studentName: undefined,
+    studentNumber: undefined,
+    className: undefined,
+    companyName: undefined,
+    visitorName: undefined,
+    visitDate: undefined,
+    status: '0', // 0-草稿(对应字典的planned)
+    visitPurpose: undefined,
+    companySituation: undefined,
+    studentPerformance: undefined,
+    problemsFound: undefined,
+    suggestions: undefined,
+    visitSummary: undefined,
+    visitImages: undefined,
+    remark: undefined
+  });
+  formRef.value?.resetFields();
+}
+
+/** 初始化字典数据 */
+function initDictData() {
+  // 获取走访状态字典
+  getDicts('zsjy_visit_status').then((response) => {
+    visitStatusOptions.value = response.data.map(item => ({
+      label: item.dictLabel,
+      value: item.dictValue
+    }));
+  });
+
+
+}
+
+
+
+/** 加载当前班主任班级的学生列表 */
+function loadCurrentTeacherStudents() {
+  const teacherId = userStore.userId;
+  if (teacherId) {
+    studentLoading.value = true;
+    console.log('开始加载班主任ID为', teacherId, '的学生列表');
+
+    listInternshipInfo({
+      pageNum: 1,
+      pageSize: 100, // 加载尽可能多的学生
+      classTeacherId: teacherId
+    })
+      .then((response) => {
+        console.log('API响应完整结构:', response);
+        console.log('response.data:', response.data);
+        console.log('response直接访问:', response);
+
+        // 根据msgmanage/index.vue的用法,直接使用response.rows
+        if (response.rows && Array.isArray(response.rows)) {
+          studentOptions.value = response.rows;
+          console.log('成功加载学生数据:', response.rows.length, '条');
+          console.log('学生数据示例:', response.rows.slice(0, 3));
+        } else {
+          studentOptions.value = [];
+          console.warn('未获取到学生数据,响应结构:', response);
+          console.warn('response.rows类型:', typeof response.rows);
+        }
+        studentLoading.value = false;
+      })
+      .catch((error) => {
+        console.error('加载学生数据失败:', error);
+        studentOptions.value = [];
+        studentLoading.value = false;
+        ElMessage.error('加载学生数据失败,请稍后重试');
+      });
+  } else {
+    console.warn('未获取到班主任ID');
+    ElMessage.warning('未获取到班主任ID,无法加载学生列表');
+  }
+}
+
+// 组件挂载
+onMounted(() => {
+  initDictData();
+  getList();
+  // 预加载当前班主任班级的学生列表
+  loadCurrentTeacherStudents();
+});
+</script>
+
+<style scoped>
+/* 搜索按钮容器样式 */
+.search-btn-container {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  height: 100%;
+}
+
+/* 按钮卡片样式 */
+.button-card {
+  margin-bottom: 16px;
+}
+
+.form-item-tip {
+  font-size: 12px;
+  color: #909399;
+  line-height: 1.4;
+  margin-top: 4px;
+}
+
+.card-header-vertical {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+}
+
+.toolbar-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.button-group {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+
+.table-card {
+  margin-bottom: 16px;
+}
+
+/* 统计卡片样式 */
+.stats-row {
+  margin-bottom: 20px;
+}
+
+.stat-card {
+  background: white;
+  border-radius: 8px;
+  padding: 20px;
+  display: flex;
+  align-items: center;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s ease;
+  margin-bottom: 16px;
+}
+
+.stat-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+}
+
+.stat-icon {
+  width: 60px;
+  height: 60px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16px;
+  font-size: 24px;
+  color: white;
+}
+
+.stat-card.total .stat-icon {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.stat-card.month .stat-icon {
+  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+}
+
+.stat-card.completed .stat-icon {
+  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+}
+
+.stat-card.pending .stat-icon {
+  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
+}
+
+.stat-content {
+  flex: 1;
+}
+
+.stat-number {
+  font-size: 28px;
+  font-weight: bold;
+  color: #303133;
+  margin-bottom: 4px;
+}
+
+.stat-label {
+  font-size: 14px;
+  color: #909399;
+}
+
+/* 主卡片样式 */
+.main-card {
+  margin-top: 20px;
+}
+
+.main-card .el-card__body {
+  padding: 20px;
+}
+
+/* 搜索容器样式 */
+.search-container {
+  background: #f8f9fa;
+  padding: 16px;
+  border-radius: 6px;
+  margin-bottom: 16px;
+}
+
+/* 工具栏样式 */
+.toolbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+  padding: 0 4px;
+}
+
+.toolbar-left {
+  display: flex;
+  gap: 12px;
+}
+
+.toolbar-right {
+  display: flex;
+  gap: 8px;
+}
+
+/* 表格容器样式 */
+.table-container {
+  border-radius: 6px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.el-table {
+  border-radius: 6px;
+}
+
+.el-table th {
+  background-color: #f5f7fa;
+  color: #606266;
+  font-weight: 600;
+}
+
+.el-table .el-table__row:hover {
+  background-color: #f5f7fa;
+}
+
+/* 分页容器样式 */
+.pagination-container {
+  display: flex;
+  justify-content: center;
+  margin-top: 20px;
+  padding: 16px 0;
+}
+
+/* 学生信息样式 */
+.student-info {
+  .student-name {
+    font-weight: 600;
+    color: #303133;
+    margin-bottom: 2px;
+  }
+
+  .student-number,
+  .student-class {
+    font-size: 12px;
+    color: #909399;
+    line-height: 1.2;
+  }
+}
+
+/* 操作按钮样式 */
+.action-buttons {
+  display: flex;
+  gap: 8px;
+  justify-content: center;
+  align-items: center;
+}
+
+/* 对话框样式 */
+.dialog-footer-right {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+
+/* 详情容器样式 */
+.detail-container {
+  padding: 8px 0;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .stat-card {
+    margin-bottom: 10px;
+  }
+
+  .toolbar {
+    flex-direction: column;
+    gap: 12px;
+    align-items: stretch;
+  }
+
+  .toolbar-left,
+  .toolbar-right {
+    justify-content: center;
+  }
+
+  .action-buttons {
+    flex-direction: column;
+    gap: 4px;
+  }
+}
+</style>