commit 5a74ab0b0de8d09631601118f3082b9877a7ed86
Author: liweijie
Date: Tue Aug 18 09:49:02 2026 +0800
初始化提交
diff --git a/backend/DmJdbcDriver8.jar b/backend/DmJdbcDriver8.jar
new file mode 100644
index 000000000..a710ec0c6
Binary files /dev/null and b/backend/DmJdbcDriver8.jar differ
diff --git a/backend/README.md b/backend/README.md
new file mode 100644
index 000000000..47503b221
--- /dev/null
+++ b/backend/README.md
@@ -0,0 +1,20 @@
+# zhjx 智慧教学项目
+# 项目介绍
+## 技术架构
+* jdk 21
+* maven 3.9.6
+* springboot 3.1.1
+* mysql 8.0.33
+* DM8
+* redis 7.2.13
+
+### 目录介绍
+* admin 启动类
+* common 实体等
+* quartz 定时任务
+* system 系统管理
+* sql目录下的jwgl.sql文件是未来所有新增功能需要在数据库执行更新的脚本
+
+
+## 前端框架
+* 前端采用Vue、Element UI。
\ No newline at end of file
diff --git a/backend/bin/clean.bat b/backend/bin/clean.bat
new file mode 100644
index 000000000..ac3c34c09
--- /dev/null
+++ b/backend/bin/clean.bat
@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 清理所有target目录文件
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+call mvn clean
+
+pause
\ No newline at end of file
diff --git a/backend/bin/package.bat b/backend/bin/package.bat
new file mode 100644
index 000000000..3f01f7f04
--- /dev/null
+++ b/backend/bin/package.bat
@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 打包Web项目,生成war/jar文件
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+call mvn clean package -Dmaven.test.skip=true
+
+pause
\ No newline at end of file
diff --git a/backend/bin/run.bat b/backend/bin/run.bat
new file mode 100644
index 000000000..e80a11a7b
--- /dev/null
+++ b/backend/bin/run.bat
@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [信息] 使用Jar方式启动Web项目。
+echo.
+
+cd %~dp0
+cd ../roomroot-admin/target
+
+set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+
+java -jar %JAVA_OPTS% roomroot-admin.jar
+
+cd bin
+pause
\ No newline at end of file
diff --git a/backend/pom.xml b/backend/pom.xml
new file mode 100644
index 000000000..2097f82b0
--- /dev/null
+++ b/backend/pom.xml
@@ -0,0 +1,225 @@
+
+
+ 4.0.0
+
+ com.roomroot
+ roomroot
+ 3.9.2
+
+ roomroot
+ 风影随行管理系统
+
+
+ 3.9.2
+ UTF-8
+ UTF-8
+ 17
+ 4.0.6
+ 3.5.7
+ 1.2.28
+ 8.1.1
+ 2.3.3
+ 2.0.62
+ 7.3.0
+ 2.22.0
+ 4.1.2
+ 2.3
+ 0.9.1
+ 2.3.1
+ 3.0.3
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+
+
+ com.alibaba
+ druid-spring-boot-4-starter
+ ${druid.version}
+
+
+
+
+ nl.basjes.parse.useragent
+ yauaa
+ ${yauaa.version}
+
+
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+ ${mybatis-plus.version}
+
+
+
+ javax.xml.bind
+ jaxb-api
+ ${jaxb-api.version}
+
+
+
+
+ com.github.oshi
+ oshi-core
+ ${oshi.version}
+
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ ${springdoc.version}
+
+
+
+
+ commons-io
+ commons-io
+ ${commons.io.version}
+
+
+
+
+ org.apache.poi
+ poi-ooxml
+ ${poi.version}
+
+
+
+
+ org.apache.velocity
+ velocity-engine-core
+ ${velocity.version}
+
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ ${fastjson.version}
+
+
+
+
+ io.jsonwebtoken
+ jjwt
+ ${jwt.version}
+
+
+
+
+ pro.fessional
+ kaptcha
+ ${kaptcha.version}
+
+
+
+
+ com.roomroot
+ roomroot-quartz
+ ${roomroot.version}
+
+
+
+
+ com.roomroot
+ roomroot-generator
+ ${roomroot.version}
+
+
+
+
+ com.roomroot
+ roomroot-framework
+ ${roomroot.version}
+
+
+
+
+ com.roomroot
+ roomroot-system
+ ${roomroot.version}
+
+
+
+
+ com.roomroot
+ roomroot-common
+ ${roomroot.version}
+
+
+
+
+
+
+ roomroot-admin
+ roomroot-framework
+ roomroot-system
+ roomroot-quartz
+ roomroot-generator
+ roomroot-common
+ roomroot-jwgl
+
+ pom
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+ true
+ ${java.version}
+ ${java.version}
+ ${project.build.sourceEncoding}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring-boot.version}
+
+
+
+
+
+
+ public
+ aliyun nexus
+ https://maven.aliyun.com/repository/public
+
+ true
+
+
+
+
+
+
+ public
+ aliyun nexus
+ https://maven.aliyun.com/repository/public
+
+ true
+
+
+ false
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/roomroot-admin/pom.xml b/backend/roomroot-admin/pom.xml
new file mode 100644
index 000000000..dfd6bd640
--- /dev/null
+++ b/backend/roomroot-admin/pom.xml
@@ -0,0 +1,101 @@
+
+
+
+ roomroot
+ com.roomroot
+ 3.9.2
+
+ 4.0.0
+ jar
+ roomroot-admin
+
+
+ web服务入口
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+
+
+
+
+ com.dameng
+ DmJdbcDriver18
+ 8.1.2.192
+
+
+
+
+ com.roomroot
+ roomroot-framework
+
+
+
+
+ com.roomroot
+ roomroot-quartz
+
+
+
+
+ com.roomroot
+ roomroot-generator
+
+
+
+ com.github.codemonstur
+ embedded-redis
+ 0.12.0
+
+
+
+ com.roomroot
+ roomroot-jwgl
+ 3.9.2
+ compile
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+
+
+
+
+ repackage
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.1.0
+
+ false
+ ${project.artifactId}
+
+
+
+ ${project.artifactId}
+
+
+
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/EmbeddedRedisConfig.java b/backend/roomroot-admin/src/main/java/com/roomroot/EmbeddedRedisConfig.java
new file mode 100644
index 000000000..d1379f112
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/EmbeddedRedisConfig.java
@@ -0,0 +1,49 @@
+package com.roomroot;
+
+import com.roomroot.web.core.config.EmbeddedRedisCondition;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Configuration;
+import redis.embedded.RedisServer;
+import java.io.IOException;
+
+@Configuration
+@Conditional(EmbeddedRedisCondition.class)
+public class EmbeddedRedisConfig {
+
+ private final int redisPort;
+ private final String maxMemory;
+ private RedisServer redisServer;
+
+ public EmbeddedRedisConfig(
+ @Value("${custom.embedded-redis.port}") int redisPort,
+ @Value("${custom.embedded-redis.max-memory}") String maxMemory
+ ) {
+ this.redisPort = redisPort;
+ this.maxMemory = maxMemory;
+ }
+
+ @PostConstruct
+ public void startEmbeddedRedis() throws IOException {
+ try {
+ redisServer = new RedisServer(redisPort);
+ // 可变参数追加redis配置
+ redisServer.start(
+ );
+ System.out.println("===== 内嵌Redis启动成功,端口:" + redisPort + " =====");
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.err.println("内嵌Redis启动失败!端口:" + redisPort);
+ }
+ }
+
+ @PreDestroy
+ public void stopEmbeddedRedis() throws IOException {
+ if (redisServer != null && redisServer.isActive()) {
+ redisServer.stop();
+ System.out.println("===== 内嵌Redis已停止 =====");
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootApplication.java b/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootApplication.java
new file mode 100644
index 000000000..cdf9c2c61
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootApplication.java
@@ -0,0 +1,24 @@
+package com.roomroot;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+
+/**
+ * 启动程序
+ *
+ * @author roomroot
+ */
+@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
+@MapperScan({"com.roomroot.**.mapper"})
+public class RoomRootApplication
+{
+ public static void main(String[] args)
+ {
+ // System.setProperty("spring.devtools.restart.enabled", "false");
+ SpringApplication.run(RoomRootApplication.class, args);
+ System.out.println("(♥◠‿◠)ノ゙ 风影随行启动成功 ლ(´ڡ`ლ)゙ \n");
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootServletInitializer.java b/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootServletInitializer.java
new file mode 100644
index 000000000..347d3fc97
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootServletInitializer.java
@@ -0,0 +1,18 @@
+package com.roomroot;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * web容器中进行部署
+ *
+ * @author roomroot
+ */
+public class RoomRootServletInitializer extends SpringBootServletInitializer
+{
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
+ {
+ return application.sources(RoomRootApplication.class);
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CaptchaController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CaptchaController.java
new file mode 100644
index 000000000..7e770f1d5
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CaptchaController.java
@@ -0,0 +1,94 @@
+package com.roomroot.web.controller.common;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import jakarta.annotation.Resource;
+import javax.imageio.ImageIO;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.FastByteArrayOutputStream;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.google.code.kaptcha.Producer;
+import com.roomroot.common.config.RoomRootConfig;
+import com.roomroot.common.constant.CacheConstants;
+import com.roomroot.common.constant.Constants;
+import com.roomroot.common.core.domain.AjaxResult;
+import com.roomroot.common.core.redis.RedisCache;
+import com.roomroot.common.utils.sign.Base64;
+import com.roomroot.common.utils.uuid.IdUtils;
+import com.roomroot.system.service.ISysConfigService;
+
+/**
+ * 验证码操作处理
+ *
+ * @author roomroot
+ */
+@RestController
+public class CaptchaController
+{
+ @Resource(name = "captchaProducer")
+ private Producer captchaProducer;
+
+ @Resource(name = "captchaProducerMath")
+ private Producer captchaProducerMath;
+
+ @Autowired
+ private RedisCache redisCache;
+
+ @Autowired
+ private ISysConfigService configService;
+ /**
+ * 生成验证码
+ */
+ @GetMapping("/captchaImage")
+ public AjaxResult getCode(HttpServletResponse response) throws IOException
+ {
+ AjaxResult ajax = AjaxResult.success();
+ boolean captchaEnabled = configService.selectCaptchaEnabled();
+ ajax.put("captchaEnabled", captchaEnabled);
+ if (!captchaEnabled)
+ {
+ return ajax;
+ }
+
+ // 保存验证码信息
+ String uuid = IdUtils.simpleUUID();
+ String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
+
+ String capStr = null, code = null;
+ BufferedImage image = null;
+
+ // 生成验证码
+ String captchaType = RoomRootConfig.getCaptchaType();
+ if ("math".equals(captchaType))
+ {
+ String capText = captchaProducerMath.createText();
+ capStr = capText.substring(0, capText.lastIndexOf("@"));
+ code = capText.substring(capText.lastIndexOf("@") + 1);
+ image = captchaProducerMath.createImage(capStr);
+ }
+ else if ("char".equals(captchaType))
+ {
+ capStr = code = captchaProducer.createText();
+ image = captchaProducer.createImage(capStr);
+ }
+
+ redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
+ // 转换流信息写出
+ FastByteArrayOutputStream os = new FastByteArrayOutputStream();
+ try
+ {
+ ImageIO.write(image, "jpg", os);
+ }
+ catch (IOException e)
+ {
+ return AjaxResult.error(e.getMessage());
+ }
+
+ ajax.put("uuid", uuid);
+ ajax.put("img", Base64.encode(os.toByteArray()));
+ return ajax;
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CommonController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CommonController.java
new file mode 100644
index 000000000..ce3d1c2f7
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CommonController.java
@@ -0,0 +1,166 @@
+package com.roomroot.web.controller.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.roomroot.system.service.ISysConfigService;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import com.roomroot.common.config.RoomRootConfig;
+import com.roomroot.common.core.domain.AjaxResult;
+import com.roomroot.common.utils.StringUtils;
+import com.roomroot.common.utils.file.FileUploadUtils;
+import com.roomroot.common.utils.file.FileUtils;
+import com.roomroot.framework.config.ServerConfig;
+
+/**
+ * 通用请求处理
+ *
+ * @author roomroot
+ */
+@RestController
+@RequestMapping("/common")
+public class CommonController
+{
+ private static final Logger log = LoggerFactory.getLogger(CommonController.class);
+
+ @Autowired
+ private ServerConfig serverConfig;
+
+ private static final String FILE_DELIMITER = ",";
+
+ @Autowired
+ private ISysConfigService configService;
+
+ /**
+ * 通用下载请求
+ *
+ * @param fileName 文件名称
+ * @param delete 是否删除
+ */
+ @GetMapping("/download")
+ public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request)
+ {
+ try
+ {
+ if (!FileUtils.checkAllowDownload(fileName))
+ {
+ throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
+ }
+ String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
+ String filePath = RoomRootConfig.getDownloadPath() + fileName;
+
+ response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+ FileUtils.setAttachmentResponseHeader(response, realFileName);
+ FileUtils.writeBytes(filePath, response.getOutputStream());
+ if (delete)
+ {
+ FileUtils.deleteFile(filePath);
+ }
+ }
+ catch (Exception e)
+ {
+ log.error("下载文件失败", e);
+ }
+ }
+
+ /**
+ * 通用上传请求(单个)
+ */
+ @PostMapping("/upload")
+ public AjaxResult uploadFile(MultipartFile file) throws Exception
+ {
+ try{
+ // 上传文件路径
+ String filePath = configService.selectConfigByKey("sys.document.path");
+ if(StringUtils.isEmpty(filePath)){
+ filePath = RoomRootConfig.getUploadPath();
+ }
+ // 上传并返回新文件名称
+ String fileName = FileUploadUtils.upload(filePath, file);
+ String url = serverConfig.getUrl() + fileName;
+ AjaxResult ajax = AjaxResult.success();
+ ajax.put("url", url);
+ ajax.put("fileName", fileName);
+ ajax.put("newFileName", FileUtils.getName(fileName));
+ ajax.put("originalFilename", file.getOriginalFilename());
+ return ajax;
+ } catch (Exception e) {
+ return AjaxResult.error(e.getMessage());
+ }
+ }
+
+ /**
+ * 通用上传请求(多个)
+ */
+ @PostMapping("/uploads")
+ public AjaxResult uploadFiles(List files) {
+ try{
+ // 上传文件路径
+ String filePath = configService.selectConfigByKey("sys.document.path");
+ if(StringUtils.isEmpty(filePath)){
+ filePath = RoomRootConfig.getUploadPath();
+ }
+ List urls = new ArrayList();
+ List fileNames = new ArrayList();
+ List newFileNames = new ArrayList();
+ List originalFilenames = new ArrayList();
+ for (MultipartFile file : files)
+ {
+ // 上传并返回新文件名称
+ String fileName = FileUploadUtils.upload(filePath, file);
+ String url = serverConfig.getUrl() + fileName;
+ urls.add(url);
+ fileNames.add(fileName);
+ newFileNames.add(FileUtils.getName(fileName));
+ originalFilenames.add(file.getOriginalFilename());
+ }
+ AjaxResult ajax = AjaxResult.success();
+ ajax.put("urls", StringUtils.join(urls, FILE_DELIMITER));
+ ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMITER));
+ ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMITER));
+ ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMITER));
+ return ajax;
+ } catch (Exception e) {
+ return AjaxResult.error(e.getMessage());
+ }
+ }
+
+ /**
+ * 本地资源通用下载
+ */
+ @GetMapping("/download/resource")
+ public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
+ throws Exception
+ {
+ try
+ {
+ if (!FileUtils.checkAllowDownload(resource))
+ {
+ throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource));
+ }
+ // 本地资源路径
+ String localPath = RoomRootConfig.getProfile();
+ // 数据库资源地址
+ String downloadPath = localPath + FileUtils.stripPrefix(resource);
+ // 下载名称
+ String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
+ response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+ FileUtils.setAttachmentResponseHeader(response, downloadName);
+ FileUtils.writeBytes(downloadPath, response.getOutputStream());
+ }
+ catch (Exception e)
+ {
+ log.error("下载文件失败", e);
+ }
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AccountManagementController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AccountManagementController.java
new file mode 100644
index 000000000..a122c7ca7
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AccountManagementController.java
@@ -0,0 +1,330 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.dto.accountmanagement.AccountCreateDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountDisableDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountIdDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountLoginDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountLoginInformationQueryDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountMergeDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountPasswordChangeDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountPasswordResetDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountQueryDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountRoleConfigurationDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountRoleOptionQueryDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountSsoSessionResolveDTO;
+import com.roomroot.jwgl.dto.accountmanagement.AccountUpdateDTO;
+import com.roomroot.jwgl.service.AccountManagementService;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import com.roomroot.jwgl.vo.accountmanagement.AccountMergeResultVO;
+import com.roomroot.jwgl.vo.accountmanagement.AccountLoginCaptchaVO;
+import com.roomroot.jwgl.vo.accountmanagement.AccountLoginInformationVO;
+import com.roomroot.jwgl.vo.accountmanagement.AccountLoginVO;
+import com.roomroot.jwgl.vo.accountmanagement.AccountRoleOptionVO;
+import com.roomroot.jwgl.vo.accountmanagement.AccountRoleTypeVO;
+import com.roomroot.jwgl.vo.accountmanagement.AccountRoleVO;
+import com.roomroot.jwgl.vo.accountmanagement.AccountSsoSessionVO;
+import com.roomroot.jwgl.vo.accountmanagement.AccountVO;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+import java.util.List;
+
+/**
+ * 账户管理接口。
+ */
+@RestController
+@RequestMapping("/account-management")
+public class AccountManagementController {
+
+ /**
+ * 账户管理业务服务。
+ */
+ @Resource
+ private AccountManagementService accountManagementService;
+
+ /**
+ * 查询账户列表。
+ *
+ * @param dto 账户查询条件
+ * @return 符合条件的账户分页结果
+ */
+ @GetMapping("/list")
+ public Result> list(
+ @Valid AccountQueryDTO dto) {
+ // 调用业务层完成账户条件筛选、数据库分页和当前页角色批量装配。
+ return Result.success(accountManagementService.list(dto));
+ }
+
+ /**
+ * 查询账户详情。
+ *
+ * @param dto 账户编号参数
+ * @return 包含角色和数据范围的账户详情
+ */
+ @GetMapping("/get")
+ public Result get(@Valid AccountIdDTO dto) {
+ // 根据账户编号查询完整信息,并确保密码和密码提示答案不向页面返回。
+ return Result.success(accountManagementService.get(dto));
+ }
+
+ /**
+ * 新增账户并配置初始角色。
+ *
+ * @param dto 账户新增参数
+ * @return 新增后的账户详情
+ */
+ @PostMapping("/add")
+ public Result add(
+ @Valid @RequestBody AccountCreateDTO dto) {
+ // 调用业务层完成唯一性校验、账户写入、状态初始化和初始角色配置。
+ return Result.success("新增成功", accountManagementService.add(dto));
+ }
+
+ /**
+ * 修改账户基本信息。
+ *
+ * @param dto 账户修改参数
+ * @return 修改后的账户详情
+ */
+ @PostMapping("/update")
+ public Result update(
+ @Valid @RequestBody AccountUpdateDTO dto) {
+ // 普通修改只维护账户资料,不在当前接口隐式修改密码、状态或角色。
+ return Result.success(
+ "修改成功", accountManagementService.update(dto));
+ }
+
+ /**
+ * 停用账户。
+ *
+ * @param dto 账户停用参数
+ * @return 停用后的账户详情
+ */
+ @PostMapping("/disable")
+ public Result disable(
+ @Valid @RequestBody AccountDisableDTO dto) {
+ // 使用账户状态表进行软停用,保留账户及历史业务引用。
+ return Result.success(
+ "停用成功", accountManagementService.disable(dto));
+ }
+
+ /**
+ * 启用普通停用账户。
+ *
+ * @param dto 账户编号参数
+ * @return 启用后的账户详情
+ */
+ @PostMapping("/enable")
+ public Result enable(
+ @Valid @RequestBody AccountIdDTO dto) {
+ // 调用业务层清除普通停用信息,已合并的源账户不允许重新启用。
+ return Result.success(
+ "启用成功", accountManagementService.enable(dto));
+ }
+
+ /**
+ * 重置账户密码。
+ *
+ * @param dto 密码重置参数
+ * @return 密码重置后的账户详情
+ */
+ @PostMapping("/reset-password")
+ public Result resetPassword(
+ @Valid @RequestBody AccountPasswordResetDTO dto) {
+ // 调用业务层重置密码,并同步清除临时登录失败次数。
+ return Result.success(
+ "密码重置成功",
+ accountManagementService.resetPassword(dto));
+ }
+
+ /**
+ * 当前登录账户修改自己的密码。
+ *
+ * @param dto 原密码、新密码和确认密码
+ * @return 密码修改后的脱敏账户信息
+ */
+ @PostMapping("/change-password")
+ public Result changePassword(
+ @Valid @RequestBody AccountPasswordChangeDTO dto) {
+ /*
+ * 账户编号只从服务端登录会话读取,不接收客户端指定的目标账户,
+ * 防止普通账户通过修改请求参数变更其他账户的密码。
+ */
+ return Result.success(
+ "密码修改成功,请重新登录",
+ accountManagementService.changePassword(dto));
+ }
+
+ /**
+ * 查询当前登录账户的登录信息和成功登录历史。
+ *
+ * @param dto 登录历史分页参数
+ * @return 当前账户的上次、本次登录时间和成功登录历史
+ */
+ @GetMapping("/login-information")
+ public Result loginInformation(
+ @Valid AccountLoginInformationQueryDTO dto) {
+ /*
+ * 查询目标只从服务端当前会话取得,不接收客户端账户编号,
+ * 因此普通登录账户只能查看自己的登录信息和登录历史。
+ */
+ return Result.success(
+ accountManagementService.getLoginInformation(dto));
+ }
+
+ /**
+ * 生成本地账户登录使用的一次性图形验证码。
+ *
+ * @param response 当前 HTTP 响应
+ * @return 验证码编号、PNG 图片数据和有效秒数
+ */
+ @GetMapping("/login-captcha")
+ public Result loginCaptcha(
+ HttpServletResponse response) {
+ /*
+ * 第一步:禁止浏览器和中间代理缓存验证码响应,
+ * 防止刷新页面时继续显示已经失效的旧验证码。
+ */
+ response.setHeader("Cache-Control",
+ "no-store, no-cache, must-revalidate, max-age=0");
+ response.setHeader("Pragma", "no-cache");
+ response.setDateHeader("Expires", 0L);
+
+ /*
+ * 第二步:生成验证码并保存到当前 Servlet Session,
+ * 页面只会收到验证码编号和图片,不会收到验证码答案。
+ */
+ return Result.success(
+ accountManagementService.generateLoginCaptcha());
+ }
+
+ /**
+ * 使用本地账号密码登录。
+ *
+ * @param dto 本地账号密码登录参数
+ * @return 脱敏后的本地账户信息和角色
+ */
+ @PostMapping("/login")
+ public Result login(
+ @Valid @RequestBody AccountLoginDTO dto) {
+ /*
+ * 本地登录接口只负责接收登录参数并返回脱敏结果;
+ * 验证码、密码、账户状态检查和 Servlet Session 建立全部由业务层完成。
+ */
+ return Result.success(
+ "登录成功", accountManagementService.login(dto));
+ }
+
+ /**
+ * 校验 SSO 会话、建立本地登录状态并解析唯一的本地账户。
+ *
+ * @param dto SSO 会话解析参数
+ * @return SSO 会话及其对应的本地账户信息;本地 Session 标识不直接返回
+ */
+ @PostMapping("/resolve-sso-session")
+ public Result resolveSsoSession(
+ @Valid @RequestBody AccountSsoSessionResolveDTO dto) {
+ /*
+ * 第一步:把会话标识交给业务层校验有效期。
+ * 第二步:同时使用 LoginID 和 UserID 解析唯一的本地统一账号。
+ * 第三步:仅返回不包含密码和 SSO 会话标识的核验结果。
+ */
+ return Result.success(
+ accountManagementService.resolveSsoSession(dto));
+ }
+
+ /**
+ * 退出当前登录会话。
+ *
+ * @return 不包含业务数据的退出结果
+ */
+ @PostMapping("/logout")
+ public Result logout() {
+ // 调用业务层清理当前在线记录并作废本地 Servlet Session。
+ accountManagementService.logout();
+
+ // 退出接口没有业务返回对象,因此使用 Result 返回统一成功结果。
+ return Result.success("退出登录成功", null);
+ }
+
+ /**
+ * 查询系统支持的角色类型。
+ *
+ * @return 角色类型列表
+ */
+ @GetMapping("/role-types")
+ public Result> roleTypes() {
+ // 返回与角色配置、查询和合并流程一致的七类角色定义。
+ return Result.success(accountManagementService.listRoleTypes());
+ }
+
+ /**
+ * 查询指定角色类型下的身份选项。
+ *
+ * @param dto 角色身份查询条件
+ * @return 角色身份选项分页结果
+ */
+ @GetMapping("/role-options")
+ public Result> roleOptions(
+ @Valid AccountRoleOptionQueryDTO dto) {
+ // 调用业务层完成角色类型、关键字、绑定状态筛选和数据库分页。
+ return Result.success(
+ accountManagementService.listRoleOptions(dto));
+ }
+
+ /**
+ * 查询账户已有角色和数据范围。
+ *
+ * @param dto 账户编号参数
+ * @return 账户角色列表
+ */
+ @GetMapping("/roles")
+ public Result> roles(
+ @Valid AccountIdDTO dto) {
+ // 查询账户现有全部身份绑定,并补充统一角色名称。
+ return Result.success(accountManagementService.listRoles(dto));
+ }
+
+ /**
+ * 整体配置账户角色和数据范围。
+ *
+ * @param dto 账户角色配置参数
+ * @return 配置完成后的账户详情
+ */
+ @PostMapping("/configure-roles")
+ public Result configureRoles(
+ @Valid @RequestBody AccountRoleConfigurationDTO dto) {
+ /*
+ * 业务层会先预校验全部身份,再在同一事务中替换角色,
+ * 避免部分身份无效时已经清除了账户原有角色。
+ */
+ return Result.success(
+ "角色配置成功",
+ accountManagementService.configureRoles(dto));
+ }
+
+ /**
+ * 合并重复账户。
+ *
+ * @param dto 账户合并参数
+ * @return 合并后的源账户和目标账户信息
+ */
+ @PostMapping("/merge")
+ public Result merge(
+ @Valid @RequestBody AccountMergeDTO dto) {
+ /*
+ * 合并只迁移角色和数据范围,不物理删除源账户,
+ * 从而保留其他历史业务表对源账户的原始引用。
+ */
+ return Result.success(
+ "账户合并成功", accountManagementService.merge(dto));
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AffiliationSettingController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AffiliationSettingController.java
new file mode 100644
index 000000000..7be874132
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AffiliationSettingController.java
@@ -0,0 +1,246 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.dto.affiliationsetting.PersonnelAffiliationDTO;
+import com.roomroot.jwgl.dto.affiliationsetting.PersonnelTransferDTO;
+import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationAddDTO;
+import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationIdDTO;
+import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationQueryDTO;
+import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeOptionQueryDTO;
+import com.roomroot.jwgl.dto.affiliationsetting.TeachingClassAffiliationDTO;
+import com.roomroot.jwgl.service.AffiliationSettingService;
+import com.roomroot.jwgl.unit.Result;
+import com.roomroot.jwgl.vo.affiliationsetting.AffiliationDepartmentVO;
+import com.roomroot.jwgl.vo.affiliationsetting.PersonnelAffiliationVO;
+import com.roomroot.jwgl.vo.affiliationsetting.ResearchOfficeAffiliationVO;
+import com.roomroot.jwgl.vo.affiliationsetting.ResearchOfficeVO;
+import com.roomroot.jwgl.vo.affiliationsetting.TeachingClassAffiliationVO;
+import com.roomroot.jwgl.vo.affiliationsetting.TransferHistoryVO;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+
+import java.util.List;
+
+/**
+ * 隶属关系设置统一接口。
+ *
+ * 该控制器统一承载教研室和教学班次两类隶属关系接口,
+ * 同时保留原有请求路径,避免前端调用方式发生变化。
+ */
+@RestController
+public class AffiliationSettingController {
+
+ /**
+ * 隶属关系设置统一业务服务。
+ */
+ @Resource
+ private AffiliationSettingService affiliationSettingService;
+
+ /**
+ * 查询可供页面选择的启用部别。
+ *
+ * @param dto 查询条件
+ * @return 可选部别列表
+ */
+ @GetMapping("/research-office-affiliation/departments")
+ public Result> researchOfficeDepartments(
+ @Valid ResearchOfficeOptionQueryDTO dto) {
+ // 调用业务层查询启用部别,并返回专用于页面展示的 VO 列表。
+ return Result.success(
+ affiliationSettingService.listResearchOfficeDepartments(dto));
+ }
+
+ /**
+ * 查询指定部别已经关联的教研室。
+ *
+ * @param dto 部别编号及查询条件
+ * @return 已建立的教研室隶属关系列表
+ */
+ @GetMapping("/research-office-affiliation/list")
+ public Result> researchOfficeList(
+ @Valid ResearchOfficeAffiliationQueryDTO dto) {
+ // 调用业务层查询当前部别的全部隶属关系,并返回包含教研室展示字段的 VO。
+ return Result.success(
+ affiliationSettingService.listResearchOfficeAffiliations(dto));
+ }
+
+ /**
+ * 查询尚未分配且处于启用状态的教研室。
+ *
+ * @param dto 查询条件
+ * @return 可添加的教研室列表
+ */
+ @GetMapping("/research-office-affiliation/available")
+ public Result> availableResearchOffices(
+ @Valid ResearchOfficeOptionQueryDTO dto) {
+ // 调用业务层排除已经存在隶属关系的教研室,返回可添加的教研室 VO。
+ return Result.success(
+ affiliationSettingService.listAvailableResearchOffices(dto));
+ }
+
+ /**
+ * 新增教研室隶属关系。
+ *
+ * @param dto 部别编号和教研室编号
+ * @return 新增后的隶属关系
+ */
+ @PostMapping("/research-office-affiliation/add")
+ public Result addResearchOfficeAffiliation(
+ @Valid @RequestBody ResearchOfficeAffiliationAddDTO dto) {
+ // 调用业务层完成校验和新增,并将持久化后的关系 VO 返回给前端。
+ return Result.success(
+ "添加成功",
+ affiliationSettingService.addResearchOfficeAffiliation(dto));
+ }
+
+ /**
+ * 解除教研室隶属关系。
+ *
+ * @param dto 隶属关系编号
+ * @return 被解除的隶属关系
+ */
+ @PostMapping("/research-office-affiliation/remove")
+ public Result removeResearchOfficeAffiliation(
+ @Valid @RequestBody ResearchOfficeAffiliationIdDTO dto) {
+ // 按关系编号解除隶属关系,避免仅凭教研室编号误删其他业务数据。
+ return Result.success(
+ "解除成功",
+ affiliationSettingService.removeResearchOfficeAffiliation(dto));
+ }
+
+ /**
+ * 查询可供教学班次隶属关系页面选择的启用部别。
+ *
+ * @param dto 查询条件
+ * @return 可选部别列表
+ */
+ @GetMapping("/teaching-class-affiliation/departments")
+ public Result> teachingClassDepartments(
+ @Valid TeachingClassAffiliationDTO dto) {
+ // 调用统一业务层查询启用部别,并返回与数据库实体隔离的共享部别 VO。
+ return Result.success(
+ affiliationSettingService.listTeachingClassDepartments(dto));
+ }
+
+ /**
+ * 查询指定部别已经关联的教学班次。
+ *
+ * @param dto 部别编号及查询条件
+ * @return 已建立的教学班次隶属关系列表
+ */
+ @GetMapping("/teaching-class-affiliation/list")
+ public Result> teachingClassList(
+ @Valid TeachingClassAffiliationDTO dto) {
+ // 调用统一业务层查询当前部别的全部教学班次关系,并组装页面展示 VO。
+ return Result.success(
+ affiliationSettingService.listTeachingClassAffiliations(dto));
+ }
+
+ /**
+ * 查询尚未分配给任何部别的教学班次。
+ *
+ * @param dto 查询条件
+ * @return 可添加的教学班次列表
+ */
+ @GetMapping("/teaching-class-affiliation/available")
+ public Result> availableTeachingClasses(
+ @Valid TeachingClassAffiliationDTO dto) {
+ // 调用统一业务层排除已经存在隶属关系的班次,并返回可添加的教学班次 VO。
+ return Result.success(
+ affiliationSettingService.listAvailableTeachingClasses(dto));
+ }
+
+ /**
+ * 新增教学班次隶属关系。
+ *
+ * @param dto 部别编号和教学班次编号
+ * @return 新增后的隶属关系
+ */
+ @PostMapping("/teaching-class-affiliation/add")
+ public Result addTeachingClassAffiliation(
+ @Valid @RequestBody TeachingClassAffiliationDTO dto) {
+ // 调用统一业务层完成参数校验、冲突检查和关系新增,并返回新增后的关系 VO。
+ return Result.success(
+ "添加成功",
+ affiliationSettingService.addTeachingClassAffiliation(dto));
+ }
+
+ /**
+ * 解除教学班次隶属关系。
+ *
+ * @param dto 部别编号和教学班次编号
+ * @return 被解除的隶属关系
+ */
+ @PostMapping("/teaching-class-affiliation/remove")
+ public Result removeTeachingClassAffiliation(
+ @Valid @RequestBody TeachingClassAffiliationDTO dto) {
+ // 按照部别编号和教学班次编号精确解除关系,避免旧页面请求误删新归属。
+ return Result.success(
+ "解除成功",
+ affiliationSettingService.removeTeachingClassAffiliation(dto));
+ }
+
+ // ============ 人员隶属关系统一接口(方向1新增)============
+
+ /**
+ * 查询人员当前隶属关系。
+ * 支持按人员类型+部门编号筛选,可附加关键字匹配姓名/拼音。
+ *
+ * @param dto 查询条件
+ * @return 人员隶属关系列表
+ */
+ @GetMapping("/affiliation/personnel/list")
+ public Result> personnelList(@Valid PersonnelAffiliationDTO dto) {
+ return Result.success(affiliationSettingService.listPersonnelAffiliations(dto));
+ }
+
+ /**
+ * 分配人员到部门/组织单元(首次归属)。
+ * 若人员已有归属,返回冲突错误,提示走异动接口。
+ *
+ * @param dto 分配参数
+ * @return 分配后的人员隶属关系
+ */
+ @PostMapping("/affiliation/personnel/assign")
+ public Result assignPersonnel(@Valid @RequestBody PersonnelAffiliationDTO dto) {
+ return Result.success("分配成功", affiliationSettingService.assignPersonnel(dto));
+ }
+
+ /**
+ * 解除人员隶属关系(清空当前归属)。
+ *
+ * @param dto 解除参数
+ * @return 解除后的人员隶属关系
+ */
+ @PostMapping("/affiliation/personnel/unassign")
+ public Result unassignPersonnel(@Valid @RequestBody PersonnelAffiliationDTO dto) {
+ return Result.success("解除成功", affiliationSettingService.unassignPersonnel(dto));
+ }
+
+ /**
+ * 人员异动调整(核心接口)。
+ * 记录异动历史 + 更新归属 + 校验关联表,必须填写异动原因。
+ *
+ * @param dto 异动参数
+ * @return 异动后的人员隶属关系
+ */
+ @PostMapping("/affiliation/personnel/transfer")
+ public Result transferPersonnel(@Valid @RequestBody PersonnelTransferDTO dto) {
+ return Result.success("异动成功", affiliationSettingService.transferPersonnel(dto));
+ }
+
+ /**
+ * 查询人员异动历史。
+ *
+ * @param dto 查询参数
+ * @return 异动历史VO(含JSON原文)
+ */
+ @GetMapping("/affiliation/personnel/history")
+ public Result personnelHistory(@Valid PersonnelAffiliationDTO dto) {
+ return Result.success(affiliationSettingService.getTransferHistory(dto));
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BZLBController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BZLBController.java
new file mode 100644
index 000000000..e415739ed
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BZLBController.java
@@ -0,0 +1,76 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.roomroot.jwgl.entity.BZLB;
+import com.roomroot.jwgl.service.BZLBService;
+import com.roomroot.jwgl.unit.Result;
+import jakarta.annotation.Resource;
+import org.springframework.web.bind.annotation.*;
+
+
+import java.util.List;
+
+/**
+ * 保障类别 Controller
+ */
+@RestController
+@RequestMapping("/bzlb")
+public class BZLBController {
+
+ @Resource
+ private BZLBService bzlbService;
+
+ /**
+ * 新增保障类别
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody BZLB bzlb) {
+ bzlbService.add(bzlb);
+ return Result.success();
+ }
+
+ /**
+ * 删除保障类别
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("bh") String bh) {
+ bzlbService.delete(bh);
+ return Result.success();
+ }
+
+ /**
+ * 更新保障类别
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody BZLB bzlb) {
+ bzlbService.update(bzlb);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询保障类别
+ */
+ @GetMapping("/get")
+ public Result get(@RequestParam("bh") String bh) {
+ return Result.success(bzlbService.getByBh(bh));
+ }
+
+ /**
+ * 分页查询保障类别
+ */
+ @GetMapping("/list")
+ public Result> list(
+ @RequestParam(defaultValue = "1") int pageNum,
+ @RequestParam(defaultValue = "10") int pageSize,
+ BZLB bzlb) {
+ return Result.success(bzlbService.list(pageNum, pageSize, bzlb));
+ }
+
+ /**
+ * 查询所有启用的保障类别
+ */
+ @GetMapping("/all")
+ public Result> all() {
+ return Result.success(bzlbService.all());
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BuildController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BuildController.java
new file mode 100644
index 000000000..4ed1955cb
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BuildController.java
@@ -0,0 +1,92 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.JXLB;
+import com.roomroot.jwgl.service.BuildService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * 教学楼表控制器
+ */
+@RestController
+@RequestMapping("/build")
+public class BuildController {
+
+ @Resource
+ private BuildService buildService;
+
+ /**
+ * 新增教学楼
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody JXLB jxlb) {
+ buildService.add(jxlb);
+ return Result.success();
+ }
+
+ /**
+ * 删除教学楼
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("id") String id) {
+ buildService.delete(id);
+ return Result.success();
+ }
+
+ /**
+ * 更新教学楼
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody JXLB jxlb) {
+ buildService.update(jxlb);
+ return Result.success();
+ }
+
+ /**
+ * 根据ID查询
+ */
+ @GetMapping("/get")
+ public Result getById(@RequestParam("id") String id) {
+ return Result.success(buildService.getById(id));
+ }
+
+ /**
+ * 分页查询教学楼列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, JXLB jxlb) {
+ return Result.success(buildService.pageList(query, jxlb));
+ }
+
+ /**
+ * 查询所有教学楼列表
+ */
+ @GetMapping("/all")
+ public Result> all(JXLB jxlb) {
+ return Result.success(buildService.list(jxlb));
+ }
+
+ /**
+ * 导入Excel文件
+ */
+ @PostMapping("/import")
+ public Result importExcel(@RequestParam("file") MultipartFile file) {
+ int count = buildService.importExcel(file);
+ return Result.success("导入成功", count);
+ }
+
+ /**
+ * 下载模板文件
+ */
+ @GetMapping("/template")
+ public void downloadTemplate(HttpServletResponse response) {
+ buildService.downloadTemplate(response);
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomCalendarController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomCalendarController.java
new file mode 100644
index 000000000..b31295e72
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomCalendarController.java
@@ -0,0 +1,89 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.JXCDL;
+import com.roomroot.jwgl.service.ClassRoomCalendarService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import jakarta.annotation.Resource;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 教学场地历控制器
+ */
+@RestController
+@RequestMapping("/classroom/jxcdl")
+public class ClassRoomCalendarController {
+
+ @Resource
+ private ClassRoomCalendarService calendarService;
+
+ /**
+ * 新增教学场地历
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody JXCDL jxcdl) {
+ calendarService.add(jxcdl);
+ return Result.success();
+ }
+
+ /**
+ * 删除教学场地历
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("id") String id) {
+ calendarService.delete(id);
+ return Result.success();
+ }
+
+ /**
+ * 更新教学场地历
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody JXCDL jxcdl) {
+ calendarService.update(jxcdl);
+ return Result.success();
+ }
+
+ /**
+ * 根据ID查询
+ */
+ @GetMapping("/get")
+ public Result getById(@RequestParam("id") String id) {
+ return Result.success(calendarService.getById(id));
+ }
+
+ /**
+ * 分页查询教学场地历列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, JXCDL jxcdl) {
+ return Result.success(calendarService.pageList(query, jxcdl));
+ }
+
+ /**
+ * 查询所有教学场地历列表
+ */
+ @GetMapping("/all")
+ public Result> all(JXCDL jxcdl) {
+ return Result.success(calendarService.list(jxcdl));
+ }
+
+ /**
+ * 根据教室编号查询教学场地历列表
+ */
+ @GetMapping("/listByJsbh")
+ public Result> listByJsbh(@RequestParam("jsbh") String jsbh) {
+ return Result.success(calendarService.listByJsbh(jsbh));
+ }
+
+ /**
+ * 根据日期查询教学场地历列表
+ */
+ @GetMapping("/listByRq")
+ public Result> listByRq(@RequestParam("rq") String rq) {
+ return Result.success(calendarService.listByRq(rq));
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomController.java
new file mode 100644
index 000000000..1dd781be6
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomController.java
@@ -0,0 +1,81 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.JSB;
+import com.roomroot.jwgl.service.ClassRoomService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import jakarta.annotation.Resource;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 教室表控制器
+ */
+@RestController
+@RequestMapping("/classroom")
+public class ClassRoomController {
+
+ @Resource
+ private ClassRoomService jsbService;
+
+ /**
+ * 新增教室
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody JSB jsb) {
+ jsbService.add(jsb);
+ return Result.success();
+ }
+
+ /**
+ * 删除教室
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("id") String id) {
+ jsbService.delete(id);
+ return Result.success();
+ }
+
+ /**
+ * 更新教室
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody JSB jsb) {
+ jsbService.update(jsb);
+ return Result.success();
+ }
+
+ /**
+ * 根据教室编号查询
+ */
+ @GetMapping("/get")
+ public Result getById(@RequestParam("id") String id) {
+ return Result.success(jsbService.getById(id));
+ }
+
+ /**
+ * 分页查询教室列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, JSB jsb) {
+ return Result.success(jsbService.pageList(query, jsb));
+ }
+
+ /**
+ * 查询所有教室列表
+ */
+ @GetMapping("/all")
+ public Result> all(JSB jsb) {
+ return Result.success(jsbService.list(jsb));
+ }
+
+ /**
+ * 根据教学楼代号查询教室列表
+ */
+ @GetMapping("/listByJxldh")
+ public Result> listByJxldh(@RequestParam("jxldh") String jxldh) {
+ return Result.success(jsbService.listByJxldh(jxldh));
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomLessonController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomLessonController.java
new file mode 100644
index 000000000..83ae33b04
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomLessonController.java
@@ -0,0 +1,83 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.JSBKCB;
+import com.roomroot.jwgl.service.ClassRoomLessonService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import jakarta.annotation.Resource;
+import org.springframework.web.bind.annotation.*;
+
+
+import java.util.List;
+
+/**
+ * 教室输出课程表控制器
+ */
+@RestController
+@RequestMapping("/classRoomLesson")
+public class ClassRoomLessonController {
+
+ @Resource
+ private ClassRoomLessonService lessonService;
+
+ /**
+ * 新增教室输出课程表
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody JSBKCB jsbkcb) {
+ lessonService.add(jsbkcb);
+ return Result.success();
+ }
+
+ /**
+ * 删除教室输出课程表
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("id") String id) {
+ lessonService.delete(id);
+ return Result.success();
+ }
+
+ /**
+ * 更新教室输出课程表
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody JSBKCB jsbkcb) {
+ lessonService.update(jsbkcb);
+ return Result.success();
+ }
+
+ /**
+ * 根据ID查询
+ */
+ @GetMapping("/get")
+ public Result getById(@RequestParam("id") String id) {
+ return Result.success(lessonService.getById(id));
+ }
+
+ /**
+ * 根据教室编号和年度查询
+ */
+ @GetMapping("/getByJsbhAndNd")
+ public Result getByJsbhAndNd(@RequestParam("jsbh") String jsbh,
+ @RequestParam("nd") Integer nd) {
+ return Result.success(lessonService.getByJsbhAndNd(jsbh, nd));
+ }
+
+ /**
+ * 分页查询教室输出课程表列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, JSBKCB jsbkcb) {
+ return Result.success(lessonService.pageList(query, jsbkcb));
+ }
+
+ /**
+ * 查询所有教室输出课程表列表
+ */
+ @GetMapping("/all")
+ public Result> all(JSBKCB jsbkcb) {
+ return Result.success(lessonService.list(jsbkcb));
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassSemesterCalendarController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassSemesterCalendarController.java
new file mode 100644
index 000000000..6fa62d72b
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassSemesterCalendarController.java
@@ -0,0 +1,82 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.XYDRWB;
+import com.roomroot.jwgl.service.ClassSemesterCalendarService;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 学员队任务表控制器
+ */
+@RestController
+@RequestMapping("/classCalendar")
+public class ClassSemesterCalendarController {
+
+ @Resource
+ private ClassSemesterCalendarService classSemesterCalendarService;
+
+ /**
+ * 更新
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody XYDRWB xydrwb) {
+ classSemesterCalendarService.update(xydrwb);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询
+ */
+ @GetMapping("/get")
+ public Result getByBh(@RequestParam("bh") String bh) {
+ return Result.success(classSemesterCalendarService.getByBh(bh));
+ }
+
+ /**
+ * 条目批次复制
+ *
+ * 根据源学员队任务表编号集合,复制到多个目标学员队期表下,
+ * 替换学员队学期编号和学员队编号为目标学员队期表的值
+ *
+ *
+ * @param params 包含sourceBhList(源编号列表)和targetBhList(目标学员队期表编号列表)
+ * @return 复制结果
+ */
+ @PostMapping("/batchCopy")
+ public Result
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param zymc 专业名称(可选)
+ * @param zyfx 专业方向(可选)
+ * @return 分页结果
+ */
+
+ @GetMapping("/list")
+ public Result> list(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String zymc,
+ @RequestParam(required = false) String zyfx,
+ @RequestParam(required = false) String zydm,
+ @RequestParam(required = false) Integer ty,
+ @RequestParam(required = false) String pxlx2,
+ @RequestParam(required = false) String xnz) {
+ PageQuery query = new PageQuery();
+ query.setPageNum(pageNum);
+ query.setPageSize(pageSize);
+ XKZYXX cond = new XKZYXX();
+ cond.setZymc(zymc);
+ cond.setZyfx(zyfx);
+ cond.setZydm(zydm);
+ cond.setTy(ty);
+ cond.setPxlx2(pxlx2);
+ cond.setXnz(xnz);
+ PageResult pageResult = disciplineService.pageList(query, cond);
+ return Result.success(pageResult);
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ElectiveController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ElectiveController.java
new file mode 100644
index 000000000..d19e1386a
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ElectiveController.java
@@ -0,0 +1,373 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.dto.elective.AddClassDTO;
+import com.roomroot.jwgl.entity.JSB;
+import com.roomroot.jwgl.entity.JXLB;
+import com.roomroot.jwgl.entity.XYDB;
+import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
+import com.roomroot.jwgl.entity.XYDQB;
+import com.roomroot.jwgl.entity.XYXX;
+import com.roomroot.jwgl.service.ElectiveService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import com.roomroot.jwgl.vo.elective.*;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+/**
+ * 教学班次管理控制器
+ *
+ * 提供教学班次(学员队表)的完整管理功能,包括:
+ *
+ * - 教学班次的增加、删除(停用)、更新、查询
+ * - 支持选修课临时教学班次的管理(通过学员队类型 xydlx 区分)
+ * - 教学班与管理班的区别管理
+ * - 学员自行注册开关设置
+ * - 学号前缀和学号自动累加位数配置
+ *
+ * 涉及数据表:学员队表(XYDB)、学员队期表(XYDQB)。
+ *
+ */
+@RestController
+@RequestMapping("/elective")
+public class ElectiveController {
+
+ @Resource
+ private ElectiveService electiveService;
+
+ // ======================== 教学班次 CRUD ========================
+
+ /**
+ * 新增教学班次(学员队)
+ *
+ * 创建一个新的教学班次,支持设置名称、专业、年级、计划人数、
+ * 任务类别(规划内/其他)、入学日期、毕业日期等信息。
+ *
+ * @return 操作结果
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody AddClassDTO dto) {
+ electiveService.add(dto);
+ return Result.success();
+ }
+
+ /**
+ * 删除教学班次(物理删除)
+ *
+ * 直接删除指定教学班次及其关联的学员队期记录(学号规则、注册设置等)。
+ *
+ *
+ * @param xydbh 学员队编号
+ * @return 操作结果
+ */
+ @PostMapping("/disable")
+ public Result disable(@RequestParam("xydbh") String xydbh) {
+ electiveService.disable(xydbh);
+ return Result.success();
+ }
+
+ /**
+ * 更新教学班次信息
+ *
+ * 支持修改班次名称、专业、年级、计划人数、任务类别、
+ * 入学日期、毕业日期等信息。
+ *
+ *
+ * @param entity 教学班次实体(需含学员队编号)
+ * @return 操作结果
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody XYDB entity) {
+ electiveService.update(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询教学班次详情
+ *
+ * @param xydbh 学员队编号
+ * @return 教学班次信息
+ */
+ @GetMapping("/get")
+ public Result getById(@RequestParam("xydbh") String xydbh) {
+ return Result.success(electiveService.getById(xydbh));
+ }
+
+ /**
+ * 分页条件查询教学班次列表(双表联查)
+ *
+ * 左连接学员队期表,展示字段包括:
+ *
+ * - 名称、专业、年级、计划人数
+ * - 任务类别(规划内、其他)
+ * - 入学日期、毕业日期
+ * - 学员学号前缀、学员学号自动累加位数
+ * - 是否允许学员自行注册开关
+ *
+ *
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param xydmc 班次名称(模糊匹配,可选)
+ * @param nj 年级(精确匹配,可选)
+ * @param zydh 专业代号(精确匹配,可选)
+ * @param rwlb 任务类别(规划内/其他,可选)
+ * @param xydlx 学员队类型(教学班/管理班/选修班,可选)
+ * @return 分页结果(含学号规则、注册开关)
+ */
+ @GetMapping("/list")
+ public Result> list(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String xydmc,
+ @RequestParam(required = false) String nj,
+ @RequestParam(required = false) String zydh,
+ @RequestParam(required = false) String rwlb,
+ @RequestParam(required = false) String xydlx,
+ @RequestParam(required = false) String xq,
+ @RequestParam(required = false) String pxrwbsh,
+ @RequestParam(required = false) Integer xslx,
+ @RequestParam(required = false) String zxzt,
+ @RequestParam(required = false) String pxlx,
+ @RequestParam(required = false) String pxcc,
+ @RequestParam(required = false) String pxlx2,
+ @RequestParam(required = false) String rxrqStart,
+ @RequestParam(required = false) String rxrqEnd,
+ @RequestParam(required = false) String byrqStart,
+ @RequestParam(required = false) String byrqEnd) {
+ PageQuery query = new PageQuery(pageNum, pageSize);
+ ElectiveClassVO cond = new ElectiveClassVO();
+ cond.setXydmc(xydmc);
+ cond.setNj(nj);
+ cond.setZydh(zydh);
+ cond.setRwlb(rwlb);
+ cond.setXydlx(xydlx);
+ cond.setXq(xq);
+ cond.setPxrwbsh(pxrwbsh);
+ cond.setXslx(xslx);
+ cond.setZxzt(zxzt);
+ cond.setPxlx(pxlx);
+ cond.setPxcc(pxcc);
+ cond.setPxlx2(pxlx2);
+ if (rxrqStart != null) cond.setRxrqStart(java.time.LocalDateTime.parse(rxrqStart + "T00:00:00"));
+ if (rxrqEnd != null) cond.setRxrqEnd(java.time.LocalDateTime.parse(rxrqEnd + "T23:59:59"));
+ if (byrqStart != null) cond.setByrqStart(java.time.LocalDateTime.parse(byrqStart + "T00:00:00"));
+ if (byrqEnd != null) cond.setByrqEnd(java.time.LocalDateTime.parse(byrqEnd + "T23:59:59"));
+ return Result.success(electiveService.pageListWithPeriod(query, cond));
+ }
+
+ // ======================== 学员队期/注册设置 ========================
+
+ /**
+ * 查询教学班次的学号规则与注册设置
+ *
+ * 返回指定班次在学员队期表中的附加信息,包括:
+ * 学号前缀、学号自动累加位数、允许自行注册开关等。
+ *
+ *
+ * @param xydbh 学员队编号
+ * @return 学员队期信息(含学号规则、注册设置)
+ */
+ @GetMapping("/period/get")
+ public Result getPeriod(@RequestParam("xydbh") String xydbh) {
+ return Result.success(electiveService.getPeriodByXydbh(xydbh));
+ }
+
+ /**
+ * 更新教学班次的学号规则与注册设置
+ *
+ * 支持修改学号前缀(xhqz)、学号自动累加位数(xhws)、
+ * 入学日期、毕业日期、允许注册开关(yxzc)等配置。
+ *
+ *
+ * @param entity 学员队期实体(需含编号)
+ * @return 操作结果
+ */
+ @PostMapping("/period/update")
+ public Result updatePeriod(@RequestBody XYDQB entity) {
+ electiveService.updatePeriod(entity);
+ return Result.success();
+ }
+
+ /**
+ * 生成下一个学员学号
+ *
+ * 格式:入学年份年月日 + 三位流水号,如 20240301001、202403010002。
+ * 流水号位数通过学员队期表的学号自动累加位数(xhws)控制,默认3位。
+ *
+ *
+ * @param xydbh 学员队编号
+ * @return 生成的学号
+ */
+ @GetMapping("/generate-student-number")
+ public Result generateStudentNumber(@RequestParam("xydbh") String xydbh) {
+ return Result.success(electiveService.generateNextStudentNumber(xydbh));
+ }
+ /**
+ * 设置是否允许学员自行注册
+ *
+ * 控制该教学班次是否允许学员通过网络自行注册。
+ * 0 = 关闭(不允许注册),1 = 开启(允许注册)。
+ *
+ *
+ * @param xydbh 学员队编号
+ * @param enabled 注册开关(0-关闭,1-开启)
+ * @return 操作结果
+ */
+ @PostMapping("/period/set-registration")
+ public Result setRegistration(
+ @RequestParam("xydbh") String xydbh,
+ @RequestParam("enabled") Integer enabled) {
+ electiveService.setRegistrationEnabled(xydbh, enabled);
+ return Result.success();
+ }
+
+ // ======================== 关联信息查询 ========================
+
+ /**
+ * 查询教学班次的课程列表
+ * 通过实施_课程学员队关联查询班次的课程安排。
+ *
+ * @param xydbh 学员队编号
+ * @param nd 年度(可选)
+ * @return 课程列表
+ */
+ @GetMapping("/courses")
+ public Result> getCourses(
+ @RequestParam("xydbh") String xydbh,
+ @RequestParam(required = false) Integer nd) {
+ return Result.success(electiveService.getClassCourses(xydbh, nd));
+ }
+
+ /**
+ * 查询教学班次的学期课程表
+ *
+ * @param xydbh 学员队编号
+ * @param nd 年度(可选)
+ * @return 课程表列表
+ */
+ @GetMapping("/schedule")
+ public Result> getSchedule(
+ @RequestParam("xydbh") String xydbh,
+ @RequestParam(required = false) Integer nd) {
+ return Result.success(electiveService.getClassSchedule(xydbh, nd));
+ }
+
+ /**
+ * 查询教学班次的学员列表
+ *
+ * @param xydbh 学员队编号
+ * @return 学员列表
+ */
+ @GetMapping("/students")
+ public Result> getStudents(@RequestParam("xydbh") String xydbh) {
+ return Result.success(electiveService.getClassStudents(xydbh));
+ }
+
+ /**
+ * 查询教学班次的学员成绩
+ *
+ * @param xydbh 学员队编号
+ * @param kmbh 科目编号(可选,按科目筛选)
+ * @return 成绩列表
+ */
+ @GetMapping("/grades")
+ public Result> getGrades(
+ @RequestParam("xydbh") String xydbh,
+ @RequestParam(required = false) String kmbh) {
+ return Result.success(electiveService.getClassGrades(xydbh, kmbh));
+ }
+
+ /**
+ * 查询教学班次的学员缺勤记录
+ * 关联学员教学请假表和请假学员表,返回请假学员明细。
+ *
+ * @param xydbh 学员队编号
+ * @param nd 年度(可选)
+ * @return 缺勤记录列表(含学员姓名学号)
+ */
+ @GetMapping("/absences")
+ public Result> getAbsences(
+ @RequestParam("xydbh") String xydbh,
+ @RequestParam(required = false) Integer nd) {
+ return Result.success(electiveService.getClassAbsences(xydbh, nd));
+ }
+
+ // ======================== 教学班次学期信息设置 ========================
+
+ /**
+ * 查询教学楼列表(教学班次学期信息设置窗口 - 教学楼下拉框)
+ *
+ * @return 教学楼列表
+ */
+ @GetMapping("/semester/buildings")
+ public Result> semesterBuildings() {
+ return Result.success(electiveService.listBuildings());
+ }
+
+ /**
+ * 查询教室列表(教学班次学期信息设置窗口 - 专用教室选择)
+ * 支持按教学楼代号过滤;在【查找教室】输入框输入教室名称拼音首字母可快速查找。
+ *
+ * @param jxldh 教学楼代号(可选)
+ * @param py 教室名称拼音首字母(可选)
+ * @return 教室列表
+ */
+ @GetMapping("/semester/rooms")
+ public Result> semesterRooms(
+ @RequestParam(required = false) String jxldh,
+ @RequestParam(required = false) String py) {
+ return Result.success(electiveService.listRooms(jxldh, py));
+ }
+
+ /**
+ * 获取教学班次学期信息设置窗口的初始化默认值
+ *
+ * 根据班次学制自动给出默认值:
+ *
+ * - 学制半年以内:学期第次固定为第一学期,开学日期=入学日期,结束日期=毕业日期
+ * - 学制半年以上:按入学日期智能推断当前学期第次(可手动指定),
+ * 开学/结束日期默认取窗口预设日期
+ * - 第二以上学期:默认预设上学期的专用教室
+ *
+ *
+ *
+ * @param xydbh 班次编号(学员队编号)
+ * @param presetKxrq 窗口预设开学日期(yyyy-MM-dd,可选)
+ * @param presetJsrq 窗口预设结束日期(yyyy-MM-dd,可选)
+ * @return 初始化默认值
+ */
+ @GetMapping("/semester/init")
+ public Result semesterInit(
+ @RequestParam("xydbh") String xydbh,
+ @RequestParam(required = false) String presetKxrq,
+ @RequestParam(required = false) String presetJsrq) {
+ return Result.success(electiveService.getSemesterInit(xydbh, presetKxrq, presetJsrq));
+ }
+
+ /**
+ * 创建教学班次学期信息(教学班次学期信息设置窗口 - 确定按钮)
+ *
+ * 服务端自动处理:
+ *
+ * - 学制半年以内:学期第次强制为第一学期(传入其他值校验失败),
+ * 开学/结束日期为空时默认取班次的入学/毕业日期
+ * - 学制半年以上:学期第次为空时按入学日期智能推断;
+ * 开学/结束日期使用窗口预设值,不允许为空
+ * - 第二以上学期:专用教室为空时默认预设上学期的专用教室
+ * - 同一班次+年度+学期第次已存在时拒绝重复创建
+ *
+ *
+ *
+ * @param xydndxqjbxxb 班次学期信息(必填:学员队编号;可选:年度、学期第次、开学/结束日期、专用教室编号、节次类别、备注等)
+ * @return 操作结果
+ */
+ @PostMapping("/semester/add")
+ public Result semesterAdd(@RequestBody XYDNDXQJBXXB xydndxqjbxxb) {
+ electiveService.addSemester(xydndxqjbxxb);
+ return Result.success();
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/FacultyController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/FacultyController.java
new file mode 100644
index 000000000..2a51e1763
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/FacultyController.java
@@ -0,0 +1,247 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.dto.faculty.AddTeacherDTO;
+import com.roomroot.jwgl.entity.*;
+import com.roomroot.jwgl.entity.*;
+import com.roomroot.jwgl.service.FacultyService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import com.roomroot.jwgl.vo.faculty.TeacherListVO;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+
+/**
+ * 教研室与教师信息管理控制器
+ *
+ * 提供教研室和教师信息的增加、删除(停用/离职)、更新、查询等管理接口。
+ * 覆盖教研室管理、教员档案管理、教员属性维护、年度综合评定查询、课程表查询等功能。
+ *
+ *
+ * 接口规范:仅使用 GET 和 POST 两种方法,参数通过请求体或查询参数传递,不拼接在路径中。
+ *
+ *
+ * @author management
+ */
+@RestController
+@RequestMapping("/faculty")
+public class FacultyController {
+
+ @Resource
+ private FacultyService facultyService;
+
+ // ======================== 教研室管理 ========================
+
+ /**
+ * 新增教研室
+ */
+ @PostMapping("/office/add")
+ public Result addOffice(@RequestBody JYSB entity) {
+ facultyService.addJYSB(entity);
+ return Result.success();
+ }
+
+ /**
+ * 停用(逻辑删除)教研室
+ *
+ * @param jysdh 教研室代号
+ */
+ @PostMapping("/office/disable")
+ public Result disableOffice(@RequestParam("jysdh") String jysdh) {
+ facultyService.disableJYSB(jysdh);
+ return Result.success();
+ }
+
+ /**
+ * 更新教研室
+ */
+ @PostMapping("/office/update")
+ public Result updateOffice(@RequestBody JYSB entity) {
+ facultyService.updateJYSB(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据代号查询教研室详情
+ *
+ * @param jysdh 教研室代号
+ */
+ @GetMapping("/office/get")
+ public Result getOffice(@RequestParam("jysdh") String jysdh) {
+ JYSB entity = facultyService.getJYSBById(jysdh);
+ return Result.success(entity);
+ }
+
+ /**
+ * 分页条件查询教研室列表
+ *
+ * 支持模糊查询参数:
+ *
+ * - jysmc — 教研室名称(模糊匹配)
+ * - bx — 部系(模糊匹配)
+ *
+ *
+ */
+ @GetMapping("/office/list")
+ public Result> listOffice(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String jysdh,
+ @RequestParam(required = false) String jysmc,
+ @RequestParam(required = false) String bx) {
+ PageQuery query = new PageQuery();
+ query.setPageNum(pageNum);
+ query.setPageSize(pageSize);
+ JYSB cond = new JYSB();
+ cond.setJysdh(jysdh);
+ cond.setJysmc(jysmc);
+ cond.setBx(bx);
+ PageResult pageResult = facultyService.pageJYSB(query, cond);
+ return Result.success(pageResult);
+ }
+
+ // ======================== 教员管理 ========================
+
+ /**
+ * 新增教员(同时写入教员表和教员属性表)
+ * 教员属性中的非空字段由前端一并传入。
+ */
+ @PostMapping("/teacher/add")
+ public Result addTeacher(@RequestBody AddTeacherDTO dto) {
+ facultyService.addJYB(dto);
+ return Result.success();
+ }
+
+ /**
+ * 离职(逻辑删除)教员
+ *
+ * @param jybh 教员编号
+ */
+ @PostMapping("/teacher/disable")
+ public Result disableTeacher(@RequestParam("jybh") String jybh) {
+ facultyService.disableJYB(jybh);
+ return Result.success();
+ }
+
+ /**
+ * 更新教员基本信息
+ * 支持教学单位调整(修改教研室代号)、职称调整(修改职称、专业技术职务等级)。
+ */
+ @PostMapping("/teacher/update")
+ public Result updateTeacher(@RequestBody JYB entity) {
+ facultyService.updateJYB(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询教员详情
+ *
+ * @param jybh 教员编号
+ */
+ @GetMapping("/teacher/get")
+ public Result getTeacher(@RequestParam("jybh") String jybh) {
+ JYB entity = facultyService.getJYBById(jybh);
+ return Result.success(entity);
+ }
+
+ /**
+ * 分页条件查询教员列表
+ *
+ * 支持模糊查询参数:
+ *
+ * - jyxm — 教员姓名(模糊匹配)
+ * - jysdh — 教研室代号(精确匹配)
+ * - zc — 职称(模糊匹配)
+ * - jylb — 教员类别(精确匹配)
+ *
+ *
+ */
+ @GetMapping("/teacher/list")
+ public Result> listTeacher(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String jyxm,
+ @RequestParam(required = false) String jysdh,
+ @RequestParam(required = false) String zc,
+ @RequestParam(required = false) String jylb) {
+ PageQuery query = new PageQuery();
+ query.setPageNum(pageNum);
+ query.setPageSize(pageSize);
+ JYB cond = new JYB();
+ cond.setJyxm(jyxm);
+ cond.setJysdh(jysdh);
+ cond.setZc(zc);
+ cond.setJylb(jylb);
+ PageResult pageResult = facultyService.pageJYBDetail(query, cond);
+ return Result.success(pageResult);
+ }
+
+ // ======================== 教员属性管理 ========================
+
+ /**
+ * 查询教员属性
+ *
+ * @param jybh 教员编号
+ */
+ @GetMapping("/teacher/attribute/get")
+ public Result getTeacherAttribute(@RequestParam("jybh") String jybh) {
+ JYSX entity = facultyService.getJYSXByJYBH(jybh);
+ return Result.success(entity);
+ }
+
+ /**
+ * 更新教员属性
+ * 支持岗位调整(修改职务、职务时间)和技术等级调整(修改技术等级、技术等级时间)。
+ */
+ @PostMapping("/teacher/attribute/update")
+ public Result updateTeacherAttribute(@RequestBody JYSX entity) {
+ facultyService.updateJYSX(entity);
+ return Result.success();
+ }
+
+ // ======================== 年度综合评定查询 ========================
+
+ /**
+ * 分页查询年度综合评定_教员
+ */
+ @GetMapping("/evaluation/list")
+ public Result> listEvaluation(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String jybh,
+ @RequestParam(required = false) String ndzhpdjysbh) {
+ PageQuery query = new PageQuery();
+ query.setPageNum(pageNum);
+ query.setPageSize(pageSize);
+ NDZHPDJY cond = new NDZHPDJY();
+ cond.setJybh(jybh);
+ cond.setNdzhpd_jysbh(ndzhpdjysbh);
+ PageResult pageResult = facultyService.pageNDZHPDJY(query, cond);
+ return Result.success(pageResult);
+ }
+
+ // ======================== 教员课程表查询 ========================
+
+ /**
+ * 分页查询教员输出课程表
+ *
+ * 支持按年度和教员编号筛选。
+ *
+ */
+ @GetMapping("/timetable/list")
+ public Result> listTimetable(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) Integer nd,
+ @RequestParam(required = false) String jybh) {
+ PageQuery query = new PageQuery();
+ query.setPageNum(pageNum);
+ query.setPageSize(pageSize);
+ JYSCKCB cond = new JYSCKCB();
+ cond.setNd(nd);
+ cond.setJybh(jybh);
+ PageResult pageResult = facultyService.pageJYSCKCB(query, cond);
+ return Result.success(pageResult);
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBController.java
new file mode 100644
index 000000000..a15091370
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBController.java
@@ -0,0 +1,72 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.KCB;
+import com.roomroot.jwgl.service.KCBService;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+/**
+ * 课程表控制器
+ */
+@RestController
+@RequestMapping("/kcb")
+public class KCBController {
+
+ @Resource
+ private KCBService kcbService;
+
+ /**
+ * 新增
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody KCB kcb) {
+ kcbService.add(kcb);
+ return Result.success();
+ }
+
+ /**
+ * 删除
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("kbh") String kbh) {
+ kcbService.delete(kbh);
+ return Result.success();
+ }
+
+ /**
+ * 批量删除
+ */
+ @PostMapping("/batchDelete")
+ public Result batchDelete(@RequestBody List kbhList) {
+ kcbService.batchDelete(kbhList);
+ return Result.success();
+ }
+
+ /**
+ * 更新
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody KCB kcb) {
+ kcbService.update(kcb);
+ return Result.success();
+ }
+
+ /**
+ * 根据课编号查询
+ */
+ @GetMapping("/get")
+ public Result getByKbh(@RequestParam("kbh") String kbh) {
+ return Result.success(kcbService.getByKbh(kbh));
+ }
+
+ /**
+ * 查询所有数据
+ */
+ @GetMapping("/list")
+ public Result> list() {
+ return Result.success(kcbService.list());
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LeaveController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LeaveController.java
new file mode 100644
index 000000000..d97fd1c2b
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LeaveController.java
@@ -0,0 +1,134 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.dto.LeaveDTO;
+import com.roomroot.jwgl.dto.LeaveQueryDTO;
+import com.roomroot.jwgl.entity.XYJXQJB;
+import com.roomroot.jwgl.entity.XYQJSPB;
+import com.roomroot.jwgl.service.LeaveService;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+/**
+ * 请假控制器
+ */
+@RestController
+@RequestMapping("/leave")
+public class LeaveController {
+
+ @Resource
+ private LeaveService leaveService;
+
+ // ===== 请假主表 =====
+
+ /**
+ * 新增请假(主表+课堂+学员)
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody LeaveDTO dto) {
+ leaveService.add(dto);
+ return Result.success();
+ }
+
+ /**
+ * 更新请假主表
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody XYJXQJB xyjxqjb) {
+ if (!leaveService.update(xyjxqjb)) {
+ return Result.error("该请假已有审批记录,不允许修改");
+ }
+ return Result.success();
+ }
+
+ /**
+ * 删除请假(级联删除课堂+学员+审批)
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("bh") String bh) {
+ if (!leaveService.delete(bh)) {
+ return Result.error("该请假已有审批记录,不允许删除");
+ }
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询请假
+ */
+ @GetMapping("/get")
+ public Result getByBh(@RequestParam("bh") String bh) {
+ return Result.success(leaveService.getByBh(bh));
+ }
+
+ /**
+ * 查询所有请假
+ * 支持按编号、名称、状态、学员队编号查询,起始时间、结束时间、创建时间支持范围查询
+ */
+ @GetMapping("/list")
+ public Result> list(LeaveQueryDTO queryDTO) {
+ return Result.success(leaveService.list(queryDTO));
+ }
+
+ /**
+ * 根据请假编号查询课堂编号列表
+ */
+ @GetMapping("/classrooms")
+ public Result> listClassrooms(@RequestParam("qjbh") String qjbh) {
+ return Result.success(leaveService.listClassroomBhsByQjbh(qjbh));
+ }
+
+ /**
+ * 根据请假编号查询学员编号列表
+ */
+ @GetMapping("/students")
+ public Result> listStudents(@RequestParam("qjbh") String qjbh) {
+ return Result.success(leaveService.listStudentBhsByQjbh(qjbh));
+ }
+
+ // ===== 审批表 =====
+
+ /**
+ * 新增审批
+ */
+ @PostMapping("/approval/add")
+ public Result addApproval(@RequestBody XYQJSPB xyqjspb) {
+ leaveService.addApproval(xyqjspb);
+ return Result.success();
+ }
+
+ /**
+ * 删除审批
+ */
+ @PostMapping("/approval/delete")
+ public Result deleteApproval(@RequestParam("bh") String bh) {
+ leaveService.deleteApproval(bh);
+ return Result.success();
+ }
+
+ /**
+ * 更新审批
+ */
+ @PostMapping("/approval/update")
+ public Result updateApproval(@RequestBody XYQJSPB xyqjspb) {
+ leaveService.updateApproval(xyqjspb);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询审批
+ */
+ @GetMapping("/approval/get")
+ public Result getApproval(@RequestParam("bh") String bh) {
+ return Result.success(leaveService.getApprovalByBh(bh));
+ }
+
+ /**
+ * 根据请假编号查询审批列表
+ */
+ @GetMapping("/approval/list")
+ public Result> listApprovals(@RequestParam("qjbh") String qjbh) {
+ return Result.success(leaveService.listApprovalsByQjbh(qjbh));
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LogController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LogController.java
new file mode 100644
index 000000000..102189501
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LogController.java
@@ -0,0 +1,883 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.*;
+import com.roomroot.jwgl.entity.*;
+import com.roomroot.jwgl.service.LogService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import com.roomroot.jwgl.vo.log.TeacherHoursVO;
+import com.roomroot.jwgl.vo.log.TeachingLogListVO;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 教学日志管理控制器
+ *
+ * 覆盖教学日志核心管理、课时核算标准、课时补助标准、联教联训、教师工作量等全功能。
+ * 接口路径前缀:/log
+ *
+ */
+@RestController
+@RequestMapping("/log")
+public class LogController {
+
+ @Resource private LogService logService;
+
+ // ======================== 教学日志核心 ========================
+
+ /**
+ * 新增教学日志
+ * 记录单次课堂教学情况,包含授课信息、学员出勤、教学效果等。
+ *
+ * @param entity 教学日志实体(班次课堂教学日志)
+ * @return 操作结果
+ */
+ @PostMapping("/teaching-log/add")
+ public Result addLog(@RequestBody SSKCB_RZ entity) {
+ logService.addLog(entity);
+ return Result.success();
+ }
+
+ /**
+ * 分页条件查询教学日志列表(待上报视图)
+ * 未指定状态 rzzt 时默认过滤已上报、已审核,仅展示系统创建/已退回的教学日志(含历史空状态);指定状态时按状态精确过滤。
+ */
+ @GetMapping("/teaching-log/list")
+ public Result> listTeachingLog(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String ksrq,
+ @RequestParam(required = false) String jsrq,
+ @RequestParam(required = false) String kcmc,
+ @RequestParam(required = false) String jys,
+ @RequestParam(required = false) String jxff,
+ @RequestParam(required = false) String skjy,
+ @RequestParam(required = false) String rzzt,
+ @RequestParam(required = false) String pxqb,
+ @RequestParam(required = false) String gdqk,
+ @RequestParam(required = false) String gdxxqk,
+ @RequestParam(required = false) String rwlb) {
+ PageQuery query = new PageQuery(pageNum, pageSize);
+ TeachingLogListVO cond = buildTeachingLogCond(ksrq, jsrq, kcmc, jys, jxff, skjy, rzzt, pxqb, gdqk, gdxxqk, rwlb);
+ return Result.success(logService.pageTeachingLogDetail(query, cond));
+ }
+
+ /**
+ * 分页条件查询已上报的全部教学日志列表
+ * 查询条件与 /teaching-log/list 一致,状态固定为已上报。
+ */
+ @GetMapping("/teaching-log/reported/list")
+ public Result> listReportedTeachingLog(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String ksrq,
+ @RequestParam(required = false) String jsrq,
+ @RequestParam(required = false) String kcmc,
+ @RequestParam(required = false) String jys,
+ @RequestParam(required = false) String jxff,
+ @RequestParam(required = false) String skjy,
+ @RequestParam(required = false) String rzzt,
+ @RequestParam(required = false) String pxqb,
+ @RequestParam(required = false) String gdqk,
+ @RequestParam(required = false) String gdxxqk,
+ @RequestParam(required = false) String rwlb) {
+ PageQuery query = new PageQuery(pageNum, pageSize);
+ TeachingLogListVO cond = buildTeachingLogCond(ksrq, jsrq, kcmc, jys, jxff, skjy, rzzt, pxqb, gdqk, gdxxqk, rwlb);
+ cond.setZt("已上报");
+ return Result.success(logService.pageTeachingLogDetail(query, cond));
+ }
+
+ /**
+ * 分页条件查询已审核的教学日志列表
+ * 查询条件与 /teaching-log/list 一致,状态固定为已审核。
+ */
+ @GetMapping("/teaching-log/audited/list")
+ public Result> listAuditedTeachingLog(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String ksrq,
+ @RequestParam(required = false) String jsrq,
+ @RequestParam(required = false) String kcmc,
+ @RequestParam(required = false) String jys,
+ @RequestParam(required = false) String jxff,
+ @RequestParam(required = false) String skjy,
+ @RequestParam(required = false) String rzzt,
+ @RequestParam(required = false) String pxqb,
+ @RequestParam(required = false) String gdqk,
+ @RequestParam(required = false) String gdxxqk,
+ @RequestParam(required = false) String rwlb) {
+ PageQuery query = new PageQuery(pageNum, pageSize);
+ TeachingLogListVO cond = buildTeachingLogCond(ksrq, jsrq, kcmc, jys, jxff, skjy, rzzt, pxqb, gdqk, gdxxqk, rwlb);
+ cond.setZt("已审核");
+ return Result.success(logService.pageTeachingLogDetail(query, cond));
+ }
+
+ /**
+ * 批量上报教学日志
+ * 根据主键编号组(bh)将状态修改为已上报,仅对待上报(系统创建/已退回)数据生效,返回实际更新条数。
+ */
+ @PostMapping("/teaching-log/batch-report")
+ public Result batchReportLogs(@RequestBody List bhList) {
+ return Result.success(logService.batchReportLogs(bhList));
+ }
+
+ /**
+ * 批量审核教学日志
+ * 根据主键编号组(bh)将状态修改为已审核,仅对已上报数据生效,返回实际更新条数。
+ */
+ @PostMapping("/teaching-log/batch-audit")
+ public Result batchAuditLogs(@RequestBody List bhList) {
+ return Result.success(logService.batchAuditLogs(bhList));
+ }
+
+ /**
+ * 构建教学日志列表查询条件(参数与 /teaching-log/list 保持一致)
+ */
+ private TeachingLogListVO buildTeachingLogCond(String ksrq, String jsrq, String kcmc, String jys, String jxff, String skjy, String rzzt, String pxqb, String gdqk, String gdxxqk, String rwlb) {
+ TeachingLogListVO cond = new TeachingLogListVO();
+ cond.setKcmc(kcmc); cond.setJys(jys); cond.setJsfs(jxff);
+ cond.setSkjy(skjy); cond.setZt(rzzt); cond.setPxqbxx(pxqb);
+ cond.setGdqk(gdqk); cond.setGdxxqk(gdxxqk); cond.setRwlb(rwlb);
+ if (gdqk != null && !gdqk.isEmpty()) cond.setGdqkList(java.util.Arrays.asList(gdqk.split(",")));
+ if (gdxxqk != null && !gdxxqk.isEmpty()) cond.setGdxxqkList(java.util.Arrays.asList(gdxxqk.split(",")));
+ if (ksrq != null && !ksrq.isEmpty()) cond.setKsrq(java.time.LocalDate.parse(ksrq).atStartOfDay());
+ if (jsrq != null && !jsrq.isEmpty()) cond.setJsrq(java.time.LocalDate.parse(jsrq).atTime(23, 59, 59));
+ return cond;
+ }
+
+ /**
+ * 退回教学日志
+ */
+ @PostMapping("/teaching-log/reject")
+ public Result rejectLog(
+ @RequestParam("bh") String bh,
+ @RequestParam("thyj") String thyj) {
+ logService.rejectLog(bh, thyj);
+ return Result.success();
+ }
+
+ /**
+ * 根据课表自动创建教学日志
+ */
+ @PostMapping("/teaching-log/auto-create")
+ public Result autoCreateLog(@RequestParam("tybh") String tybh) {
+ return Result.success(logService.autoCreateLog(tybh));
+ }
+
+ /**
+ * 批量导入教学日志
+ */
+ @PostMapping("/teaching-log/import")
+ public Result importLog(@RequestBody List list) {
+ logService.importLog(list);
+ return Result.success();
+ }
+
+ @PostMapping("/teaching-log/import-excel")
+ public Result importLogExcel(@RequestParam("file") MultipartFile file) {
+ logService.importLogFromExcel(file);
+ return Result.success();
+ }
+
+ /**
+ * 导出教学日志 Excel
+ */
+ @GetMapping("/teaching-log/export")
+ public void exportLog(TeachingLogListVO cond, HttpServletResponse response) {
+ logService.exportLogExcel(cond, response);
+ }
+
+ /**
+ * 下载导入模板
+ */
+ @GetMapping("/teaching-log/download-template")
+ public void downloadTemplate(HttpServletResponse response) {
+ try {
+ java.io.InputStream is = getClass().getClassLoader().getResourceAsStream("template/导入教学日志模板.xls");
+ if (is == null) {
+ response.setStatus(404);
+ return;
+ }
+ response.setContentType("application/vnd.ms-excel");
+ response.setHeader("Content-Disposition", "attachment; filename=导入教学日志模板.xls");
+ org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
+ response.getOutputStream().flush();
+ is.close();
+ } catch (Exception e) {
+ throw new RuntimeException("下载模板失败", e);
+ }
+ }
+
+ // ======================== 教学日志授课教员 ========================
+
+ /**
+ * 新增教学日志授课教员
+ * 为教学日志添加主讲教师或辅讲教师(通过主教员字段zjy区分)。
+ *
+ * @param entity 授课教员关联实体
+ * @return 操作结果
+ */
+ @PostMapping("/teaching-log/teacher/add")
+ public Result addLogTeacher(@RequestBody BCKTJXRZSKJY entity) {
+ logService.addLogTeacher(entity);
+ return Result.success();
+ }
+
+ /**
+ * 分页查询教学日志授课教员列表
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param bcktjxrzbh 教学日志编号(可选)
+ * @param fzjybh 辅助教员编号(可选)
+ * @return 分页结果
+ */
+ @GetMapping("/teaching-log/teacher/list")
+ public Result> listLogTeacher(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String bcktjxrzbh,
+ @RequestParam(required = false) String fzjybh) {
+ PageQuery query = new PageQuery(pageNum, pageSize);
+ BCKTJXRZSKJY cond = new BCKTJXRZSKJY();
+ cond.setBcktjxrzbh(bcktjxrzbh); cond.setFzjybh(fzjybh);
+ return Result.success(logService.pageLogTeacher(query, cond));
+ }
+
+ // ======================== 教学日志学员学习情况 ========================
+
+ /**
+ * 新增学员学习情况记录
+ * 记录教学日志中每位学员的学习效果(A-优秀 B-良好 C-合格 D-不合格)、到课和请假情况。
+ *
+ * @param entity 学员学习情况实体
+ * @return 操作结果
+ */
+ @PostMapping("/teaching-log/student-status/add")
+ public Result addStudentStatus(@RequestBody JXRZXYXXQK entity) {
+ logService.addStudentLearningStatus(entity);
+ return Result.success();
+ }
+
+ /**
+ * 分页查询学员学习情况列表
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param jxrzbh 教学日志编号(可选)
+ * @return 分页结果
+ */
+ @GetMapping("/teaching-log/student-status/list")
+ public Result> listStudentStatus(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String jxrzbh) {
+ return Result.success(logService.pageStudentLearningStatus(new PageQuery(pageNum, pageSize), jxrzbh));
+ }
+
+ // ======================== 课时核算标准 ========================
+
+ /**
+ * 新增课时核算标准
+ * 定义主讲教师课时系数、辅讲教师课时系数、讲授方式系数列表等核算参数。
+ * 可单独设置考试主讲系数和考试指导教师系数。
+ *
+ * @param entity 课时核算标准实体
+ * @return 操作结果
+ */
+ @PostMapping("/calc-standard/add")
+ public Result addCalcStandard(@RequestBody KSHBZ entity) {
+ logService.addCalculationStandard(entity);
+ return Result.success();
+ }
+
+ /**
+ * 更新课时核算标准
+ *
+ * @param entity 课时核算标准实体(需含编号)
+ * @return 操作结果
+ */
+ @PostMapping("/calc-standard/update")
+ public Result updateCalcStandard(@RequestBody KSHBZ entity) {
+ logService.updateCalculationStandard(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询课时核算标准详情
+ *
+ * @param bh 编号
+ * @return 课时核算标准
+ */
+ @GetMapping("/calc-standard/get")
+ public Result getCalcStandard(@RequestParam("bh") String bh) {
+ return Result.success(logService.getCalculationStandardById(bh));
+ }
+
+ /**
+ * 分页查询课时核算标准列表
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param mc 名称(模糊匹配,可选)
+ * @return 分页结果
+ */
+ @GetMapping("/calc-standard/list")
+ public Result> listCalcStandard(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String mc) {
+ KSHBZ cond = new KSHBZ(); cond.setMc(mc);
+ return Result.success(logService.pageCalculationStandard(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ // ======================== 课时系数方案 ========================
+
+ /**
+ * 新增课时系数方案
+ * 定义主讲/辅讲课时系数、班级系数、合班系数、各时段课时量等核算参数。
+ *
+ * @param entity 课时系数方案实体
+ * @return 操作结果
+ */
+ @PostMapping("/coefficient-plan/add")
+ public Result addCoefficientPlan(@RequestBody KSXSFA entity) {
+ logService.addCoefficientPlan(entity);
+ return Result.success();
+ }
+
+ /**
+ * 更新课时系数方案
+ *
+ * @param entity 课时系数方案实体(需含编号)
+ * @return 操作结果
+ */
+ @PostMapping("/coefficient-plan/update")
+ public Result updateCoefficientPlan(@RequestBody KSXSFA entity) {
+ logService.updateCoefficientPlan(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询课时系数方案详情
+ *
+ * @param bh 编号
+ * @return 课时系数方案
+ */
+ @GetMapping("/coefficient-plan/get")
+ public Result getCoefficientPlan(@RequestParam("bh") String bh) {
+ return Result.success(logService.getCoefficientPlanById(bh));
+ }
+
+ /**
+ * 分页查询课时系数方案列表
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param mc 名称(模糊匹配,可选)
+ * @return 分页结果
+ */
+ @GetMapping("/coefficient-plan/list")
+ public Result> listCoefficientPlan(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String mc) {
+ KSXSFA cond = new KSXSFA(); cond.setMc(mc);
+ return Result.success(logService.pageCoefficientPlan(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ // ======================== 课时费标准 ========================
+
+ /**
+ * 新增课时费标准
+ * 定义绩效等级对应的课时费标准,包括优秀/良好/中等/及格/不及格各档补助金额。
+ *
+ * @param entity 课时费标准实体
+ * @return 操作结果
+ */
+ @PostMapping("/fee-standard/add")
+ public Result addFeeStandard(@RequestBody KSFBZ entity) {
+ logService.addFeeStandard(entity);
+ return Result.success();
+ }
+
+ /**
+ * 更新课时费标准
+ *
+ * @param entity 课时费标准实体(需含编号)
+ * @return 操作结果
+ */
+ @PostMapping("/fee-standard/update")
+ public Result updateFeeStandard(@RequestBody KSFBZ entity) {
+ logService.updateFeeStandard(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询课时费标准详情
+ *
+ * @param bh 编号
+ * @return 课时费标准
+ */
+ @GetMapping("/fee-standard/get")
+ public Result getFeeStandard(@RequestParam("bh") String bh) {
+ return Result.success(logService.getFeeStandardById(bh));
+ }
+
+ /**
+ * 分页查询课时费标准列表
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param mc 名称(模糊匹配,可选)
+ * @return 分页结果
+ */
+ @GetMapping("/fee-standard/list")
+ public Result> listFeeStandard(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String mc) {
+ KSFBZ cond = new KSFBZ(); cond.setMc(mc);
+ return Result.success(logService.pageFeeStandard(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ // ======================== 技术职务课时费标准 ========================
+
+ /**
+ * 新增技术职务课时费标准
+ * 按技术职务(教授、副教授、讲师等)定义标准课时量、课时费和超量课时费。
+ *
+ * @param entity 技术职务课时费标准实体
+ * @return 操作结果
+ */
+ @PostMapping("/tech-pos-fee/add")
+ public Result addTechPosFee(@RequestBody JSZWKSFBZ entity) {
+ logService.addTechPosFeeStandard(entity);
+ return Result.success();
+ }
+
+ /**
+ * 更新技术职务课时费标准
+ *
+ * @param entity 技术职务课时费标准实体(需含编号)
+ * @return 操作结果
+ */
+ @PostMapping("/tech-pos-fee/update")
+ public Result updateTechPosFee(@RequestBody JSZWKSFBZ entity) {
+ logService.updateTechPosFeeStandard(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询技术职务课时费标准详情
+ *
+ * @param bh 编号
+ * @return 技术职务课时费标准
+ */
+ @GetMapping("/tech-pos-fee/get")
+ public Result getTechPosFee(@RequestParam("bh") String bh) {
+ return Result.success(logService.getTechPosFeeStandardById(bh));
+ }
+
+ /**
+ * 分页查询技术职务课时费标准列表
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param jszwbh 技术职务编号(可选)
+ * @return 分页结果
+ */
+ @GetMapping("/tech-pos-fee/list")
+ public Result> listTechPosFee(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String jszwbh) {
+ JSZWKSFBZ cond = new JSZWKSFBZ(); cond.setJszwbh(jszwbh);
+ return Result.success(logService.pageTechPosFeeStandard(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ // ======================== 课时补助标准 ========================
+
+ /**
+ * 新增课时补助项目
+ * 定义课时补助项目,支持联教联训等不同类型的补助。
+ *
+ * @param entity 课时补助项目实体
+ * @return 操作结果
+ */
+ @PostMapping("/subsidy-project/add")
+ public Result addSubsidyProject(@RequestBody KSBZXM entity) {
+ logService.addSubsidyProject(entity);
+ return Result.success();
+ }
+
+ /**
+ * 更新课时补助项目
+ *
+ * @param entity 课时补助项目实体(需含编号)
+ * @return 操作结果
+ */
+ @PostMapping("/subsidy-project/update")
+ public Result updateSubsidyProject(@RequestBody KSBZXM entity) {
+ logService.updateSubsidyProject(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询课时补助项目详情
+ *
+ * @param bh 编号
+ * @return 课时补助项目
+ */
+ @GetMapping("/subsidy-project/get")
+ public Result getSubsidyProject(@RequestParam("bh") String bh) {
+ return Result.success(logService.getSubsidyProjectById(bh));
+ }
+
+ /**
+ * 分页查询课时补助项目列表
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param mc 名称(模糊匹配,可选)
+ * @return 分页结果
+ */
+ @GetMapping("/subsidy-project/list")
+ public Result> listSubsidyProject(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String mc) {
+ KSBZXM cond = new KSBZXM(); cond.setMc(mc);
+ return Result.success(logService.pageSubsidyProject(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ // ======================== 教学日志课时补助审批表(汇总) ========================
+
+ /**
+ * 新增教学日志课时补助审批表(汇总)
+ * 创建一个核算周期内的课时补助汇总,包含学期、任务类别、核算起止日期等。
+ *
+ * @param entity 汇总审批表实体
+ * @return 操作结果
+ */
+ @PostMapping("/summary/add")
+ public Result addSummary(@RequestBody JXRZKSBZSPB entity) {
+ logService.addSubsidyApproval(entity);
+ return Result.success();
+ }
+
+ /**
+ * 更新教学日志课时补助审批表(汇总)
+ *
+ * @param entity 汇总审批表实体(需含编号)
+ * @return 操作结果
+ */
+ @PostMapping("/summary/update")
+ public Result updateSummary(@RequestBody JXRZKSBZSPB entity) {
+ logService.updateSubsidyApproval(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询教学日志课时补助审批表详情
+ *
+ * @param bh 编号
+ * @return 汇总审批表
+ */
+ @GetMapping("/summary/get")
+ public Result getSummary(@RequestParam("bh") String bh) {
+ return Result.success(logService.getSubsidyApprovalById(bh));
+ }
+
+ /**
+ * 分页查询教学日志课时补助审批表列表
+ * 支持按状态和任务类别筛选。
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param zt 状态(可选)
+ * @param rwlb 任务类别(可选)
+ * @return 分页结果
+ */
+ @GetMapping("/summary/list")
+ public Result> listSummary(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String zt,
+ @RequestParam(required = false) String rwlb) {
+ JXRZKSBZSPB cond = new JXRZKSBZSPB(); cond.setZt(zt); cond.setRwlb(rwlb);
+ return Result.success(logService.pageSubsidyApproval(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ /**
+ * 自动核算汇总数据
+ * 根据教学日志教员课时补助明细,自动计算总课时量、优质课时量、优质课时量占比、总补助金额。
+ *
+ * @param bh 汇总审批表编号
+ * @return 操作结果
+ */
+ @PostMapping("/summary/calculate")
+ public Result calculateSummary(@RequestParam("bh") String bh) {
+ logService.calculateSummary(bh);
+ return Result.success();
+ }
+
+ // ======================== 教学日志教员课时补助明细 ========================
+
+ /**
+ * 分页查询教学日志教员课时补助明细
+ * 返回每位教员的课时补助明细,包括课时量、基本标准、增发标准、补助金额等。
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param jxrzksbzspbh 汇总审批表编号(可选)
+ * @param jybh 教员编号(可选)
+ * @return 分页结果
+ */
+ @GetMapping("/summary/detail/list")
+ public Result> listSummaryDetail(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String jxrzksbzspbh,
+ @RequestParam(required = false) String jybh) {
+ JXRZJYKSBZ cond = new JXRZJYKSBZ(); cond.setJxrzksbzspbh(jxrzksbzspbh); cond.setJybh(jybh);
+ return Result.success(logService.pageSubsidyDetail(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ // ======================== 教员课时补助 ========================
+
+ /**
+ * 新增教员课时补助记录
+ *
+ * @param entity 教员课时补助实体
+ * @return 操作结果
+ */
+ @PostMapping("/teacher-subsidy/add")
+ public Result addTeacherSubsidy(@RequestBody JYKSBZ entity) {
+ logService.addTeacherSubsidy(entity);
+ return Result.success();
+ }
+
+ /**
+ * 分页查询教员课时补助列表
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param jybh 教员编号(可选)
+ * @return 分页结果
+ */
+ @GetMapping("/teacher-subsidy/list")
+ public Result> listTeacherSubsidy(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String jybh) {
+ JYKSBZ cond = new JYKSBZ(); cond.setJybh(jybh);
+ return Result.success(logService.pageTeacherSubsidy(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ // ======================== 年终课时费 ========================
+
+ /**
+ * 新增年终课时费
+ * 创建学年课时费核算主表,按学期统计总课时量。
+ *
+ * @param entity 年终课时费实体
+ * @return 操作结果
+ */
+ @PostMapping("/year-end-fee/add")
+ public Result addYearEndFee(@RequestBody NZKSF entity) {
+ logService.addYearEndFee(entity);
+ return Result.success();
+ }
+
+ /**
+ * 更新年终课时费
+ *
+ * @param entity 年终课时费实体(需含编号)
+ * @return 操作结果
+ */
+ @PostMapping("/year-end-fee/update")
+ public Result updateYearEndFee(@RequestBody NZKSF entity) {
+ logService.updateYearEndFee(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询年终课时费详情
+ *
+ * @param bh 编号
+ * @return 年终课时费
+ */
+ @GetMapping("/year-end-fee/get")
+ public Result getYearEndFee(@RequestParam("bh") String bh) {
+ return Result.success(logService.getYearEndFeeById(bh));
+ }
+
+ /**
+ * 分页查询年终课时费列表
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param mc 名称(模糊匹配,可选)
+ * @return 分页结果
+ */
+ @GetMapping("/year-end-fee/list")
+ public Result> listYearEndFee(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String mc) {
+ NZKSF cond = new NZKSF(); cond.setMc(mc);
+ return Result.success(logService.pageYearEndFee(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ /**
+ * 分页查询教员年终课时费明细
+ * 每位教员的课时费核算明细,包含学期课时量、超课时量、计算公式、课时费金额等。
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param jybh 教员编号(可选)
+ * @param nzksfbh 年终课时费编号(可选)
+ * @return 分页结果
+ */
+ @GetMapping("/year-end-fee/teacher/list")
+ public Result> listTeacherYearEndFee(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String jybh,
+ @RequestParam(required = false) String nzksfbh) {
+ JYNZKSF cond = new JYNZKSF(); cond.setJybh(jybh); cond.setNzksfbh(nzksfbh);
+ return Result.success(logService.pageTeacherYearEndFee(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ // ======================== 教师工作量 ========================
+
+ /**
+ * 新增教师工作量记录
+ * 支持录入五类工作量:课程教学、科研学术、指导学员、教学建设、咨询服务。
+ *
+ * @param entity 教师工作量实体
+ * @return 操作结果
+ */
+ @PostMapping("/workload/add")
+ public Result addWorkload(@RequestBody JZGZL entity) {
+ logService.addWorkload(entity);
+ return Result.success();
+ }
+
+ /**
+ * 更新教师工作量记录
+ *
+ * @param entity 教师工作量实体(需含编号)
+ * @return 操作结果
+ */
+ @PostMapping("/workload/update")
+ public Result updateWorkload(@RequestBody JZGZL entity) {
+ logService.updateWorkload(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询教师工作量详情
+ *
+ * @param bh 编号
+ * @return 教师工作量
+ */
+ @GetMapping("/workload/get")
+ public Result getWorkload(@RequestParam("bh") String bh) {
+ return Result.success(logService.getWorkloadById(bh));
+ }
+
+ /**
+ * 分页查询教师工作量列表
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param jybh 教员编号(可选)
+ * @param nd 年度(可选)
+ * @param xq 学期(可选)
+ * @return 分页结果
+ */
+ @GetMapping("/workload/list")
+ public Result> listWorkload(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String jybh,
+ @RequestParam(required = false) Integer nd,
+ @RequestParam(required = false) String xq) {
+ JZGZL cond = new JZGZL(); cond.setJybh(jybh); cond.setNd(nd); cond.setXq(xq);
+ return Result.success(logService.pageWorkload(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ /**
+ * 比对各类教师工作量情况
+ * 按总工作量降序排列,支持按年度和学期筛选,用于分析教师工作量分布。
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param nd 年度(可选)
+ * @param xq 学期(可选)
+ * @return 排序后的工作量列表
+ */
+ @GetMapping("/workload/compare")
+ public Result> compareWorkload(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) Integer nd,
+ @RequestParam(required = false) String xq) {
+ return Result.success(logService.compareWorkload(new PageQuery(pageNum, pageSize), nd, xq));
+ }
+
+ // ======================== 教员课时查询管理 ========================
+
+ /**
+ * 分页查询教员课时信息
+ *
+ * 数据来源:学期教员信息表 JOIN 教员表 JOIN 教研室表。
+ * 列表展示:教研室名称、教员姓名、教员类别、当前职称、学期职称、
+ * 当前专业技术职务等级、学期专业技术职务等级、主讲课时、辅讲课时、
+ * 课程总课时、课程折算总课时、项目补助课时、总课时。
+ *
+ * 查询条件(均为可选):教员姓名(模糊)、教研室(代号精确)、教研室名称(模糊)、
+ * 当前职称/职务、学期职称、当前/学期专业技术职务等级、部系名称(模糊)、年度(学期);
+ * 各课时量支持数字范围(xx课时至xx课时):主讲课时、辅讲课时、课程总课时、
+ * 课程折算总课时、项目补助课时、总课时(传 xxxMin/xxxMax 参数)。
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param cond 查询条件对象(GET 参数自动绑定)
+ * @return 分页结果
+ */
+ @GetMapping("/teacher-hours/list")
+ public Result> listTeacherHours(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ TeacherHoursVO cond) {
+ return Result.success(logService.pageTeacherHours(new PageQuery(pageNum, pageSize), cond));
+ }
+
+ // ======================== 教学实施计划统计 ========================
+
+ /**
+ * 分页查询教学实施计划统计数据
+ * 按天统计教学运行数据,包括教学日志填写数、审核数、听查课数、调课数、缺课人次等。
+ *
+ * @param pageNum 页码(默认1)
+ * @param pageSize 每页条数(默认20)
+ * @param rq 日期(可选)
+ * @return 分页结果
+ */
+ @GetMapping("/stats/list")
+ public Result> listStats(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "20") Integer pageSize,
+ @RequestParam(required = false) String rq) {
+ JXSSJHTJB cond = new JXSSJHTJB();
+ return Result.success(logService.pageTeachingStats(new PageQuery(pageNum, pageSize), cond));
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LoginController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LoginController.java
new file mode 100644
index 000000000..775cedc64
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LoginController.java
@@ -0,0 +1,132 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.dto.accountmanagement.AccountLoginDTO;
+import com.roomroot.jwgl.dto.login.LoginDTO;
+import com.roomroot.jwgl.entity.SSJG;
+import com.roomroot.jwgl.mapper.SSJGMapper;
+import com.roomroot.jwgl.service.AccountManagementService;
+import com.roomroot.jwgl.unit.Result;
+import com.roomroot.jwgl.utils.AccountManagementConstants;
+import com.roomroot.jwgl.vo.accountmanagement.AccountLoginVO;
+import com.roomroot.jwgl.vo.accountmanagement.AccountRoleVO;
+import com.roomroot.jwgl.vo.login.LoginResultVO;
+import com.roomroot.common.annotation.Anonymous;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 登录认证控制器
+ *
+ * 支持行政、教员、学员、管理员四种用户类型登录。
+ * 管理员登录不需要单位(orgId),其余类型需选择单位。
+ * 使用数据表:登录信息表(用户凭证)、教学管理机构/实施_机关(单位信息)。
+ *
+ */
+@RestController
+@RequestMapping("/auth")
+@Anonymous
+public class LoginController {
+
+ @Resource
+ private AccountManagementService accountManagementService;
+
+ @Resource
+ private SSJGMapper ssjgMapper;
+
+ /**
+ * 用户登录
+ *
+ * 根据登录名和密码验证身份,返回用户信息、角色和单位信息。
+ *
+ */
+ @PostMapping("/login")
+ public Result login(@RequestBody LoginDTO loginDTO) {
+ // 1. 参数校验
+ if (loginDTO.getLoginName() == null || loginDTO.getLoginName().isEmpty()) {
+ return Result.error(400, "登录名不能为空");
+ }
+ if (loginDTO.getPassword() == null || loginDTO.getPassword().isEmpty()) {
+ return Result.error(400, "密码不能为空");
+ }
+ if (loginDTO.getUserType() == null || loginDTO.getUserType().isEmpty()) {
+ return Result.error(400, "用户类型不能为空");
+ }
+
+ // 管理员登录不需要单位
+ if (!"MANAGER".equals(loginDTO.getUserType()) && (loginDTO.getOrgId() == null || loginDTO.getOrgId().isEmpty())) {
+ return Result.error(400, "单位不能为空");
+ }
+
+ // 2. 参数转换(LoginDTO → AccountLoginDTO)
+ AccountLoginDTO accountLoginDTO = new AccountLoginDTO();
+ accountLoginDTO.setLoginName(loginDTO.getLoginName());
+ accountLoginDTO.setPassword(loginDTO.getPassword());
+
+ // 3. 委托 AccountManagementService 处理登录
+ AccountLoginVO loginVO = accountManagementService.login(accountLoginDTO);
+
+ // 4. 匹配用户类型
+ String roleTypeCode = mapUserTypeToRoleCode(loginDTO.getUserType());
+ List roles = loginVO.getRoles();
+ boolean hasRole = roles.stream().anyMatch(r -> roleTypeCode.equals(r.getRoleType()));
+ if (!hasRole) {
+ return Result.error(403, "当前用户没有" + loginDTO.getUserType() + "权限");
+ }
+
+ // 5. 查询单位信息
+ String orgName = null;
+ if (!"MANAGER".equals(loginDTO.getUserType())) {
+ for (AccountRoleVO role : roles) {
+ if (roleTypeCode.equals(role.getRoleType())) {
+ SSJG org = ssjgMapper.selectById(role.getTargetId());
+ if (org != null) {
+ orgName = org.getJGMC();
+ }
+ break;
+ }
+ }
+ }
+
+ // 6. 返回值转换(AccountLoginVO → LoginResultVO)
+ LoginResultVO result = new LoginResultVO();
+ result.setUserId(loginVO.getAccountId());
+ result.setUserName(loginVO.getUserName());
+ result.setLoginName(loginVO.getLoginName());
+ result.setUserType(loginDTO.getUserType());
+ result.setOrgId(loginDTO.getOrgId());
+ result.setOrgName(orgName);
+
+ List roleInfos = roles.stream().map(r -> {
+ LoginResultVO.RoleInfo ri = new LoginResultVO.RoleInfo();
+ ri.setRoleType(r.getRoleType());
+ ri.setRoleName(r.getRoleName());
+ ri.setTargetId(r.getTargetId());
+ ri.setTargetName(r.getTargetName());
+ return ri;
+ }).collect(Collectors.toList());
+ result.setRoles(roleInfos);
+
+ return Result.success(result);
+ }
+
+ /**
+ * 将前端用户类型映射为角色编码
+ */
+ private String mapUserTypeToRoleCode(String userType) {
+ switch (userType) {
+ case "ADMIN":
+ return AccountManagementConstants.ROLE_DEPARTMENT_PERSONNEL;
+ case "TEACHER":
+ return AccountManagementConstants.ROLE_TEACHER;
+ case "STUDENT":
+ return AccountManagementConstants.ROLE_STUDENT;
+ case "MANAGER":
+ return AccountManagementConstants.ROLE_DEPARTMENT_PERSONNEL;
+ default:
+ return userType;
+ }
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NoticeAnnouncementController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NoticeAnnouncementController.java
new file mode 100644
index 000000000..478f0f2c9
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NoticeAnnouncementController.java
@@ -0,0 +1,200 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementCreateDTO;
+import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementIdDTO;
+import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPriorityDTO;
+import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPublishDTO;
+import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementQueryDTO;
+import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientQueryDTO;
+import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementUpdateDTO;
+import com.roomroot.jwgl.dto.noticeannouncement.UserNoticeAnnouncementQueryDTO;
+import com.roomroot.jwgl.service.NoticeAnnouncementService;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementDetailVO;
+import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementPublishResultVO;
+import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementRecipientVO;
+import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementStatisticsVO;
+import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementVO;
+import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementDetailVO;
+import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementVO;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+
+/**
+ * 通知公告管理接口。
+ *
+ * 后台公告维护、发布统计和用户阅读入口统一保留在当前控制器中,
+ * 不为同一通知公告功能重复创建控制器。
+ */
+@RestController
+@RequestMapping("/notice-announcement")
+public class NoticeAnnouncementController {
+
+ /**
+ * 通知公告管理业务服务。
+ */
+ @Resource
+ private NoticeAnnouncementService noticeAnnouncementService;
+
+ /**
+ * 分页查询后台通知公告。
+ */
+ @GetMapping("/list")
+ public Result> list(
+ @Valid NoticeAnnouncementQueryDTO dto) {
+ // 调用业务层完成筛选、接收阅读统计和分页结果封装。
+ return Result.success(
+ noticeAnnouncementService.list(dto));
+ }
+
+ /**
+ * 查询通知公告后台详情。
+ */
+ @GetMapping("/get")
+ public Result get(
+ @Valid NoticeAnnouncementIdDTO dto) {
+ // 调用业务层查询公告内容、状态、发布范围和阅读统计。
+ return Result.success(
+ noticeAnnouncementService.get(dto));
+ }
+
+ /**
+ * 新增一条拟制公告。
+ */
+ @PostMapping("/add")
+ public Result add(
+ @Valid @RequestBody
+ NoticeAnnouncementCreateDTO dto) {
+ // 调用业务层固定创建拟制状态公告。
+ return Result.success(
+ "新增成功",
+ noticeAnnouncementService.add(dto));
+ }
+
+ /**
+ * 修改拟制或已下架公告。
+ */
+ @PostMapping("/update")
+ public Result update(
+ @Valid @RequestBody
+ NoticeAnnouncementUpdateDTO dto) {
+ // 调用业务层校验状态并修改公告可编辑内容。
+ return Result.success(
+ "修改成功",
+ noticeAnnouncementService.update(dto));
+ }
+
+ /**
+ * 发布或重新发布公告。
+ */
+ @PostMapping("/publish")
+ public Result publish(
+ @Valid @RequestBody
+ NoticeAnnouncementPublishDTO dto) {
+ /*
+ * 业务层会解析全校、全体教员或指定部别账户,
+ * 在同一事务中替换接收人快照并更新公告状态。
+ */
+ return Result.success(
+ "发布成功",
+ noticeAnnouncementService.publish(dto));
+ }
+
+ /**
+ * 下架已发布公告。
+ */
+ @PostMapping("/down")
+ public Result down(
+ @Valid @RequestBody
+ NoticeAnnouncementIdDTO dto) {
+ // 调用业务层锁定公告并执行已发布到已下架状态流转。
+ return Result.success(
+ "下架成功",
+ noticeAnnouncementService.down(dto));
+ }
+
+ /**
+ * 删除拟制或已下架公告。
+ */
+ @PostMapping("/delete")
+ public Result delete(
+ @Valid @RequestBody
+ NoticeAnnouncementIdDTO dto) {
+ // 第一步:调用业务层校验状态并删除接收人快照和公告主体。
+ noticeAnnouncementService.delete(dto);
+
+ // 第二步:删除操作没有业务返回对象,返回统一成功结果。
+ return Result.success("删除成功", null);
+ }
+
+ /**
+ * 调整公告优先级或取消置顶。
+ */
+ @PostMapping("/priority")
+ public Result priority(
+ @Valid @RequestBody
+ NoticeAnnouncementPriorityDTO dto) {
+ // 优先级为0表示取消置顶,大于0时参与公告列表优先排序。
+ return Result.success(
+ "优先级调整成功",
+ noticeAnnouncementService
+ .updatePriority(dto));
+ }
+
+ /**
+ * 分页查询公告接收和阅读明细。
+ */
+ @GetMapping("/recipients")
+ public Result>
+ recipients(
+ @Valid NoticeAnnouncementRecipientQueryDTO dto) {
+ // 调用业务层按账户去重查询接收时间、阅读状态和首次阅读时间。
+ return Result.success(
+ noticeAnnouncementService.recipients(dto));
+ }
+
+ /**
+ * 查询公告接收和阅读统计。
+ */
+ @GetMapping("/statistics")
+ public Result statistics(
+ @Valid NoticeAnnouncementIdDTO dto) {
+ // 调用业务层计算接收、已读、未读人数和阅读率。
+ return Result.success(
+ noticeAnnouncementService.statistics(dto));
+ }
+
+ /**
+ * 分页查询当前登录账户可见的已发布公告。
+ */
+ @GetMapping("/user/list")
+ public Result>
+ userList(
+ @Valid UserNoticeAnnouncementQueryDTO dto) {
+ // 查询账户身份只从服务端 Session 读取,不接收客户端账户编号。
+ return Result.success(
+ noticeAnnouncementService.userList(dto));
+ }
+
+ /**
+ * 当前登录账户打开并阅读公告。
+ */
+ @PostMapping("/user/read")
+ public Result read(
+ @Valid @RequestBody
+ NoticeAnnouncementIdDTO dto) {
+ /*
+ * 首次打开会记录阅读状态和首次阅读时间;
+ * 重复打开只增加公告累计阅读次数,不重复增加阅读人数。
+ */
+ return Result.success(
+ noticeAnnouncementService.read(dto));
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NotificationLogController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NotificationLogController.java
new file mode 100644
index 000000000..9684422e7
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NotificationLogController.java
@@ -0,0 +1,122 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.dto.notificationlog.NotificationLogExportDTO;
+import com.roomroot.jwgl.dto.notificationlog.NotificationLogIdDTO;
+import com.roomroot.jwgl.dto.notificationlog.NotificationLogQueryDTO;
+import com.roomroot.jwgl.dto.notificationlog.NotificationLogRecipientQueryDTO;
+import com.roomroot.jwgl.service.NotificationLogService;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import com.roomroot.jwgl.utils.NotificationLogUtil;
+import com.roomroot.jwgl.vo.notificationlog.NotificationLogDetailVO;
+import com.roomroot.jwgl.vo.notificationlog.NotificationLogRecipientVO;
+import com.roomroot.jwgl.vo.notificationlog.NotificationLogStatisticsVO;
+import com.roomroot.jwgl.vo.notificationlog.NotificationLogVO;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import java.util.List;
+
+import static com.roomroot.jwgl.utils.NotificationLogUtil.writeNotificationLogExcel;
+
+/**
+ * 通知日志管理接口。
+ *
+ * 当前控制器统一承载通知发送历史追踪接口,
+ * 不修改通知公告管理已有的发布和阅读入口。
+ */
+@RestController
+@RequestMapping("/notification-log")
+public class NotificationLogController {
+
+ /**
+ * 通知日志管理业务服务。
+ */
+ @Resource
+ private NotificationLogService notificationLogService;
+
+ /**
+ * 分页查询通知发送历史。
+ *
+ * @param dto 通知日志查询条件
+ * @return 通知发送历史分页结果
+ */
+ @GetMapping("/list")
+ public Result> list(
+ @Valid NotificationLogQueryDTO dto) {
+ // 调用业务层完成筛选、发送接收统计和统一分页结果封装。
+ return Result.success(
+ notificationLogService.list(dto));
+ }
+
+ /**
+ * 根据通知编号查询发送日志详情。
+ *
+ * @param dto 通知日志编号参数
+ * @return 通知主体和完整发送、阅读统计
+ */
+ @GetMapping("/get")
+ public Result get(
+ @Valid NotificationLogIdDTO dto) {
+ // 调用业务层校验日志编号并查询完整发送日志详情。
+ return Result.success(
+ notificationLogService.get(dto));
+ }
+
+ /**
+ * 分页查询指定通知的接收对象和阅读明细。
+ *
+ * @param dto 通知编号、分页和接收阅读筛选条件
+ * @return 按账户去重的接收与阅读明细
+ */
+ @GetMapping("/recipients")
+ public Result>
+ recipients(
+ @Valid NotificationLogRecipientQueryDTO dto) {
+ // 调用业务层完成日志存在性校验、账户去重和分页结果封装。
+ return Result.success(
+ notificationLogService.recipients(dto));
+ }
+
+ /**
+ * 按当前筛选条件汇总通知发送和阅读情况。
+ *
+ * @param dto 通知日志筛选条件
+ * @return 通知数量、发送结果和阅读情况汇总
+ */
+ @GetMapping("/statistics")
+ public Result statistics(
+ @Valid NotificationLogExportDTO dto) {
+ // 调用业务层复用列表筛选规则并计算完整汇总结果。
+ return Result.success(
+ notificationLogService.statistics(dto));
+ }
+
+ /**
+ * 按当前筛选条件导出全部通知日志。
+ *
+ * @param dto 通知日志导出筛选条件
+ * @param response 当前 HTTP 响应
+ */
+ @GetMapping("/export")
+ public void export(
+ @Valid NotificationLogExportDTO dto,
+ HttpServletResponse response) {
+ /*
+ * 第一步:调用业务层按照固定批次查询全部符合条件的通知日志,
+ * 并复用列表的筛选、稳定排序和统计字段补全规则。
+ */
+ List records =
+ notificationLogService.export(dto);
+
+ /*
+ * 第二步:由通知日志工具类统一生成 Excel 并写入响应,
+ * 控制器不重复维护工作簿样式、文件名编码和安全文本规则。
+ */
+ NotificationLogUtil.writeNotificationLogExcel(response, records);
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OfficeTaskBookController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OfficeTaskBookController.java
new file mode 100644
index 000000000..baba2ed06
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OfficeTaskBookController.java
@@ -0,0 +1,90 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.JYSRWS;
+import com.roomroot.jwgl.service.OfficeTaskBookService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+/**
+ * 教研室任务书控制器
+ */
+@RestController
+@RequestMapping("/officeTaskBook")
+public class OfficeTaskBookController {
+
+ @Resource
+ private OfficeTaskBookService officeTaskBookService;
+
+ /**
+ * 新增
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody JYSRWS jysrws) {
+ officeTaskBookService.add(jysrws);
+ return Result.success();
+ }
+
+ /**
+ * 删除
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("bh") String bh) {
+ officeTaskBookService.delete(bh);
+ return Result.success();
+ }
+
+ /**
+ * 批量删除
+ */
+ @PostMapping("/batchDelete")
+ public Result batchDelete(@RequestBody List bhList) {
+ officeTaskBookService.batchDelete(bhList);
+ return Result.success();
+ }
+
+ /**
+ * 更新
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody JYSRWS jysrws) {
+ officeTaskBookService.update(jysrws);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询
+ */
+ @GetMapping("/get")
+ public Result getByBh(@RequestParam("bh") String bh) {
+ return Result.success(officeTaskBookService.getByBh(bh));
+ }
+
+ /**
+ * 分页查询列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, JYSRWS jysrws) {
+ return Result.success(officeTaskBookService.pageList(query, jysrws));
+ }
+
+ /**
+ * 根据教学任务编号查询
+ */
+ @GetMapping("/listByJxrwbh")
+ public Result> listByJxrwbh(@RequestParam("jxrwbh") String jxrwbh) {
+ return Result.success(officeTaskBookService.listByJxrwbh(jxrwbh));
+ }
+
+ /**
+ * 根据教研室代号查询
+ */
+ @GetMapping("/listByJysdh")
+ public Result> listByJysdh(@RequestParam("jysdh") String jysdh) {
+ return Result.success(officeTaskBookService.listByJysdh(jysdh));
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OperationLogController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OperationLogController.java
new file mode 100644
index 000000000..dc56be4b9
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OperationLogController.java
@@ -0,0 +1,99 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.dto.operationlog.OperationLogExportDTO;
+import com.roomroot.jwgl.dto.operationlog.OperationLogIdDTO;
+import com.roomroot.jwgl.dto.operationlog.OperationLogQueryDTO;
+import com.roomroot.jwgl.service.OperationLogService;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import com.roomroot.jwgl.utils.OperationLogUtil;
+import com.roomroot.jwgl.vo.operationlog.OperationLogDetailVO;
+import com.roomroot.jwgl.vo.operationlog.OperationLogModuleVO;
+import com.roomroot.jwgl.vo.operationlog.OperationLogVO;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import java.util.List;
+
+
+/**
+ * 操作日志管理接口。
+ *
+ * 统一提供分页查询、详情查询和 Excel 导出接口,
+ * 操作日志自动采集由请求拦截器完成,不拆分重复的业务控制器。
+ */
+@RestController
+@RequestMapping("/operation-log")
+public class OperationLogController {
+
+ /**
+ * 操作日志管理业务服务。
+ */
+ @Resource
+ private OperationLogService operationLogService;
+
+ /**
+ * 分页查询操作日志。
+ *
+ * @param dto 查询条件
+ * @return 操作日志分页结果
+ */
+ @GetMapping("/list")
+ public Result> list(
+ @Valid OperationLogQueryDTO dto) {
+ // 调用业务层完成参数校验、三表联查、模块识别和分页结果封装。
+ return Result.success(operationLogService.list(dto));
+ }
+
+ /**
+ * 查询操作日志页面可使用的模块选项。
+ *
+ * @return 与模块筛选白名单保持一致的模块选项
+ */
+ @GetMapping("/modules")
+ public Result> modules() {
+ // 调用业务层取得按系统业务顺序排列的操作日志模块选项。
+ return Result.success(operationLogService.modules());
+ }
+
+ /**
+ * 根据编号查询操作日志详情。
+ *
+ * @param dto 操作日志编号参数
+ * @return 操作日志详情
+ */
+ @GetMapping("/get")
+ public Result get(
+ @Valid OperationLogIdDTO dto) {
+ // 调用业务层校验日志编号并查询完整的操作、登录和账户信息。
+ return Result.success(operationLogService.get(dto));
+ }
+
+ /**
+ * 按当前筛选条件导出全部操作日志数据。
+ *
+ * @param dto 导出筛选条件
+ * @param response 当前 HTTP 响应
+ */
+ @GetMapping("/export")
+ public void export(
+ @Valid OperationLogExportDTO dto,
+ HttpServletResponse response) {
+ /*
+ * 第一步:调用业务层复用列表筛选、稳定排序和模块识别规则,
+ * 查询全部符合当前导出条件的操作日志。
+ */
+ List records =
+ operationLogService.export(dto);
+
+ /*
+ * 第二步:由操作日志工具类统一生成 Excel 文件并写入响应,
+ * 控制器不重复维护工作簿样式、文件名编码和安全文本处理。
+ */
+ OperationLogUtil.writeOperationLogExcel(response, records);
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterCalendarController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterCalendarController.java
new file mode 100644
index 000000000..8138230a9
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterCalendarController.java
@@ -0,0 +1,74 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.XQXLB;
+import com.roomroot.jwgl.service.SemesterCalendarService;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+/**
+ * 学期校历表控制器
+ */
+@RestController
+@RequestMapping("/xqxlb")
+public class SemesterCalendarController {
+
+ @Resource
+ private SemesterCalendarService semesterCalendarService;
+
+ /**
+ * 新增学期校历
+ */
+ @PostMapping("/add")
+ public Result add(@RequestParam("startDate") String startDate,@RequestParam("endDate") String endDate) {
+ semesterCalendarService.add(startDate,endDate);
+ return Result.success();
+ }
+
+ /**
+ * 删除学期校历
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("bh") String bh) {
+ semesterCalendarService.delete(bh);
+ return Result.success();
+ }
+
+ /**
+ * 更新学期校历
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody XQXLB xqxlb) {
+ semesterCalendarService.update(xqxlb);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询学期校历
+ */
+ @GetMapping("/get")
+ public Result getById(@RequestParam("bh") String bh) {
+ return Result.success(semesterCalendarService.getById(bh));
+ }
+
+ /**
+ * 根据年度查询学期校历列表(全量)
+ */
+ @GetMapping("/list")
+ public Result> list(@RequestParam("nd") Integer nd) {
+ return Result.success(semesterCalendarService.listByNd(nd));
+ }
+
+ /**
+ * 导入Excel文件
+ */
+ @PostMapping("/import")
+ public Result importExcel(@RequestParam("file") MultipartFile file) {
+ int count = semesterCalendarService.importExcel(file);
+ return Result.success("导入成功", count);
+ }
+
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterController.java
new file mode 100644
index 000000000..304b6cc92
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterController.java
@@ -0,0 +1,73 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.XQ;
+import com.roomroot.jwgl.service.SemesterService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+/**
+ * 学期控制器
+ */
+@RestController
+@RequestMapping("/semester")
+public class SemesterController {
+
+ @Resource
+ private SemesterService semesterService;
+
+ /**
+ * 新增学期
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody XQ xq) {
+ semesterService.add(xq);
+ return Result.success();
+ }
+
+ /**
+ * 删除学期
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("nd") Integer nd) {
+ semesterService.delete(nd);
+ return Result.success();
+ }
+
+ /**
+ * 更新学期
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody XQ xq) {
+ semesterService.update(xq);
+ return Result.success();
+ }
+
+ /**
+ * 根据年度查询
+ */
+ @GetMapping("/get")
+ public Result getByNd(@RequestParam("nd") Integer nd) {
+ return Result.success(semesterService.getByNd(nd));
+ }
+
+ /**
+ * 分页查询学期列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, XQ xq) {
+ return Result.success(semesterService.pageList(query, xq));
+ }
+
+ /**
+ * 查询所有学期列表
+ */
+ @GetMapping("/all")
+ public Result> all() {
+ return Result.success(semesterService.list());
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentRecordsController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentRecordsController.java
new file mode 100644
index 000000000..80e5df2a5
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentRecordsController.java
@@ -0,0 +1,114 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.XYXX;
+import com.roomroot.jwgl.entity.XYKCCJ;
+import com.roomroot.jwgl.entity.XYKCGCCJ;
+import com.roomroot.jwgl.service.StudentRecordsService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 学员档案管理控制器
+ *
+ * 提供学员信息的增删改查、学籍异动管理,
+ * 以及学员课程成绩查询、课程过程成绩查询、课程表查询和 Excel 导出功能。
+ *
+ */
+@RestController
+@RequestMapping("/student-records")
+public class StudentRecordsController {
+
+ @Resource
+ private StudentRecordsService studentRecordsService;
+
+ // ======================== 学员 CRUD ========================
+
+ @PostMapping("/add")
+ public Result add(@RequestBody XYXX xyxx) {
+ studentRecordsService.add(xyxx);
+ return Result.success();
+ }
+
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("bh") String bh) {
+ studentRecordsService.delete(bh);
+ return Result.success();
+ }
+
+ @PostMapping("/update")
+ public Result update(@RequestBody XYXX xyxx) {
+ studentRecordsService.update(xyxx);
+ return Result.success();
+ }
+
+ @GetMapping("/get")
+ public Result getById(@RequestParam("bh") String bh) {
+ return Result.success(studentRecordsService.getById(bh));
+ }
+
+ @GetMapping("/list")
+ public Result> list(PageQuery query, XYXX xyxx) {
+ return Result.success(studentRecordsService.pageList(query, xyxx));
+ }
+
+ // ======================== 学籍异动 ========================
+
+ @PostMapping("/retain-grade")
+ public Result retainGrade(@RequestParam("bh") String bh) {
+ studentRecordsService.retainGrade(bh);
+ return Result.success();
+ }
+
+ @PostMapping("/downgrade")
+ public Result downgrade(@RequestParam("bh") String bh) {
+ studentRecordsService.downgrade(bh);
+ return Result.success();
+ }
+
+ @PostMapping("/drop-out")
+ public Result dropOut(@RequestParam("bh") String bh) {
+ studentRecordsService.dropOut(bh);
+ return Result.success();
+ }
+
+ // ======================== 课程成绩查询 ========================
+
+ /**
+ * 查询学员的课程成绩列表JW
+ *
+ * @param xybh 学员编号
+ */
+ @GetMapping("/grades")
+ public Result> getGrades(@RequestParam("xybh") String xybh) {
+ return Result.success(studentRecordsService.getCourseGrades(xybh));
+ }
+
+ /**
+ * 查询学员的课程过程成绩列表
+ *
+ * @param xybh 学员编号
+ */
+ @GetMapping("/process-grades")
+ public Result> getProcessGrades(@RequestParam("xybh") String xybh) {
+ return Result.success(studentRecordsService.getCourseProcessGrades(xybh));
+ }
+
+ // ======================== Excel 导出 ========================
+
+ /**
+ * 导出学员课程成绩 Excel
+ *
+ * @param xybh 学员编号
+ * @param response HTTP 响应
+ */
+ @GetMapping("/export-grades")
+ public void exportGrades(@RequestParam("xybh") String xybh, HttpServletResponse response) {
+ studentRecordsService.exportGradesExcel(xybh, response);
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamOutputScheduleController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamOutputScheduleController.java
new file mode 100644
index 000000000..9fc89ff8c
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamOutputScheduleController.java
@@ -0,0 +1,89 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.roomroot.jwgl.entity.XYDSCKCB;
+import com.roomroot.jwgl.service.StudentTeamOutputScheduleService;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 学员队输出课程表 Controller
+ */
+@RestController
+@RequestMapping("/studentTeamOutputSchedule")
+public class StudentTeamOutputScheduleController {
+
+ @Autowired
+ private StudentTeamOutputScheduleService studentTeamOutputScheduleService;
+
+ /**
+ * 新增
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody XYDSCKCB xydsckcb) {
+ studentTeamOutputScheduleService.add(xydsckcb);
+ return Result.success();
+ }
+
+ /**
+ * 删除
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("bh") String bh) {
+ studentTeamOutputScheduleService.delete(bh);
+ return Result.success();
+ }
+
+ /**
+ * 更新
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody XYDSCKCB xydsckcb) {
+ studentTeamOutputScheduleService.update(xydsckcb);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询
+ */
+ @GetMapping("/get")
+ public Result get(@RequestParam("bh") String bh) {
+ XYDSCKCB xydsckcb = studentTeamOutputScheduleService.getByBh(bh);
+ return Result.success(xydsckcb);
+ }
+
+ /**
+ * 根据年度和学员队编号查询
+ */
+ @GetMapping("/getByNdAndXydbh")
+ public Result getByNdAndXydbh(
+ @RequestParam("nd") Integer nd,
+ @RequestParam("xydbh") String xydbh) {
+ XYDSCKCB xydsckcb = studentTeamOutputScheduleService.getByNdAndXydbh(nd, xydbh);
+ return Result.success(xydsckcb);
+ }
+
+ /**
+ * 分页查询
+ */
+ @GetMapping("/list")
+ public Result> list(
+ @RequestParam(defaultValue = "1") int pageNum,
+ @RequestParam(defaultValue = "10") int pageSize,
+ XYDSCKCB xydsckcb) {
+ Page page = studentTeamOutputScheduleService.list(pageNum, pageSize, xydsckcb);
+ return Result.success(page);
+ }
+
+ /**
+ * 查询所有
+ */
+ @GetMapping("/all")
+ public Result> all() {
+ List list = studentTeamOutputScheduleService.all();
+ return Result.success(list);
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamTaskController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamTaskController.java
new file mode 100644
index 000000000..7164fb83f
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamTaskController.java
@@ -0,0 +1,162 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.roomroot.jwgl.entity.XYDRWB;
+import com.roomroot.jwgl.service.StudentTeamTaskService;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 学员队任务表 Controller
+ */
+@RestController
+@RequestMapping("/studentTeamTask")
+public class StudentTeamTaskController {
+
+ @Autowired
+ private StudentTeamTaskService studentTeamTaskService;
+
+ /**
+ * 新增
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody XYDRWB xydrwb) {
+ studentTeamTaskService.add(xydrwb);
+ return Result.success();
+ }
+
+ /**
+ * 删除
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("bh") String bh) {
+ studentTeamTaskService.delete(bh);
+ return Result.success();
+ }
+
+ /**
+ * 更新
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody XYDRWB xydrwb) {
+ studentTeamTaskService.update(xydrwb);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询
+ */
+ @GetMapping("/get")
+ public Result get(@RequestParam("bh") String bh) {
+ XYDRWB xydrwb = studentTeamTaskService.getByBh(bh);
+ return Result.success(xydrwb);
+ }
+
+ /**
+ * 分页查询(按序号标识排序)
+ */
+ @GetMapping("/list")
+ public Result> list(
+ @RequestParam(defaultValue = "1") int pageNum,
+ @RequestParam(defaultValue = "10") int pageSize,
+ XYDRWB xydrwb) {
+ Page page = studentTeamTaskService.list(pageNum, pageSize, xydrwb);
+ return Result.success(page);
+ }
+
+ /**
+ * 分页查询(按计划课次序号排序)
+ */
+ @GetMapping("/groupList")
+ public Result> groupList(
+ @RequestParam(defaultValue = "1") int pageNum,
+ @RequestParam(defaultValue = "10") int pageSize,
+ XYDRWB xydrwb) {
+ Page page = studentTeamTaskService.groupList(pageNum, pageSize, xydrwb);
+ return Result.success(page);
+ }
+
+ /**
+ * 批量预设合班
+ *
+ * 根据编号列表,按年度排序生成统一的计划课次序号
+ *
+ *
+ * @param params 包含bhList(编号列表)
+ * @return 更新数量
+ */
+ @PostMapping("/batchPresetMerge")
+ public Result batchPresetMerge(@RequestBody Map params) {
+ List bhList = (List) params.get("bhList");
+ int count = studentTeamTaskService.batchPresetMerge(bhList);
+ return Result.success(count);
+ }
+
+ /**
+ * 批量预设拆班
+ *
+ * 根据编号列表,清空计划课次序号
+ *
+ *
+ * @param params 包含bhList(编号列表)
+ * @return 更新数量
+ */
+ @PostMapping("/batchPresetSplit")
+ public Result batchPresetSplit(@RequestBody Map params) {
+ List bhList = (List) params.get("bhList");
+ int count = studentTeamTaskService.batchPresetSplit(bhList);
+ return Result.success(count);
+ }
+
+ /**
+ * 查询所有
+ */
+ @GetMapping("/all")
+ public Result> all() {
+ List list = studentTeamTaskService.all();
+ return Result.success(list);
+ }
+
+ /**
+ * 自动生成必修课程
+ *
+ * 根据学员队编号从学员队表获取专业代号,
+ * 再根据专业代号和学期第次查询专业教学计划表,
+ * 为当前班次自动生成必修课程,插入到学员队任务表。
+ * 已存在的课程(按课编号判断)不会重复添加。
+ *
+ *
+ * @param xydbh 学员队编号
+ * @param xqdc 学期第次
+ * @return 新增数量
+ */
+ @PostMapping("/autoGenerateRequiredCourses")
+ public Result autoGenerateRequiredCourses(
+ @RequestParam("xydbh") String xydbh,
+ @RequestParam("xqdc") Integer xqdc) {
+ int count = studentTeamTaskService.autoGenerateRequiredCourses(xydbh, xqdc);
+ return Result.success(count);
+ }
+
+ /**
+ * 批量复制课程
+ *
+ * 根据传入的学员队任务表编号集合,复制课程信息,
+ * 替换学员队编号为新的值
+ *
+ *
+ * @param params 包含bhList(源编号集合)和xydbh(新学员队编号)
+ * @return 新生成的编号集合
+ */
+ @PostMapping("/copy")
+ public Result> copy(@RequestBody Map params) {
+ List bhList = (List) params.get("bhList");
+ String newXydbh = (String) params.get("xydbh");
+ List newBhList = studentTeamTaskService.copy(bhList, newXydbh);
+ return Result.success(newBhList);
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SystemInitializationController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SystemInitializationController.java
new file mode 100644
index 000000000..39615935f
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SystemInitializationController.java
@@ -0,0 +1,149 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.dto.systeminitialization.SemesterInitializationSaveDTO;
+import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationConfigurationSaveDTO;
+import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationExecutionDTO;
+import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationHistoricalImportDTO;
+import com.roomroot.jwgl.service.SystemInitializationService;
+import com.roomroot.jwgl.unit.Result;
+import com.roomroot.jwgl.vo.systeminitialization.SemesterInitializationVO;
+import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationConfigurationVO;
+import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationExecutionResultVO;
+import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationHistoricalImportResultVO;
+import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationPrecheckVO;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+
+/**
+ * 系统初始化管理接口。
+ *
+ * 系统基础参数、学期参数、历史数据导入、预检查和最终执行确认
+ * 统一保留在同一个控制器中,不为同一业务菜单重复创建控制器。
+ */
+@RestController
+@RequestMapping("/system-initialization")
+public class SystemInitializationController {
+
+ /**
+ * 系统初始化业务服务。
+ */
+ @Resource
+ private SystemInitializationService systemInitializationService;
+
+ /**
+ * 查询当前系统基础参数。
+ *
+ * @return 当前生效的系统基础参数
+ */
+ @GetMapping("/configuration")
+ public Result configuration() {
+ // 调用业务层查询当前生效配置,并使用统一结果对象返回给前端。
+ return Result.success(
+ systemInitializationService.getCurrentConfiguration());
+ }
+
+ /**
+ * 保存新的系统基础参数版本。
+ *
+ * @param dto 系统基础参数
+ * @return 保存后立即生效的新版本
+ */
+ @PostMapping("/configuration/save")
+ public Result
+ saveConfiguration(
+ @Valid @RequestBody
+ SystemInitializationConfigurationSaveDTO dto) {
+ // 调用业务层校验期望版本并新增一条系统配置版本记录。
+ return Result.success(
+ "系统基础参数保存成功",
+ systemInitializationService.saveConfiguration(dto));
+ }
+
+ /**
+ * 查询当前学期初始化参数。
+ *
+ * @return 当前学期
+ */
+ @GetMapping("/semester")
+ public Result semester() {
+ // 调用业务层读取当前学期并补充统一展示名称。
+ return Result.success(
+ systemInitializationService.getCurrentSemester());
+ }
+
+ /**
+ * 新增或修改指定年度和序号的学期参数。
+ *
+ * @param dto 学期初始化参数
+ * @return 保存后的学期
+ */
+ @PostMapping("/semester/save")
+ public Result saveSemester(
+ @Valid @RequestBody
+ SemesterInitializationSaveDTO dto) {
+ // 调用业务层完成日期冲突检查、当前学期切换和学期保存。
+ return Result.success(
+ "学期参数保存成功",
+ systemInitializationService.saveSemester(dto));
+ }
+
+ /**
+ * 导入历史部别、教研室隶属关系和部别人员基础数据。
+ *
+ * @param dto 历史数据 Excel 文件
+ * @return 各类数据实际导入数量
+ */
+ @PostMapping(
+ value = "/history/import",
+ consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public Result
+ importHistoricalData(
+ @Valid SystemInitializationHistoricalImportDTO dto) {
+ /*
+ * 调用业务层解析并预校验整份工作簿,
+ * 全部数据通过后在同一事务中写入四张基础数据表。
+ */
+ return Result.success(
+ "历史基础数据导入成功",
+ systemInitializationService
+ .importHistoricalData(dto));
+ }
+
+ /**
+ * 执行系统初始化前的完整预检查。
+ *
+ * @return 配置、学期、组织数据和引用完整性检查结果
+ */
+ @GetMapping("/precheck")
+ public Result precheck() {
+ // 调用业务层汇总所有初始化必需检查项。
+ return Result.success(
+ systemInitializationService.precheck());
+ }
+
+ /**
+ * 在明确确认且预检查通过后完成系统初始化确认。
+ *
+ * @param dto 页面预检查快照和确认标志
+ * @return 初始化执行结果
+ */
+ @PostMapping("/execute")
+ public Result execute(
+ @Valid @RequestBody
+ SystemInitializationExecutionDTO dto) {
+ /*
+ * 调用业务层重新执行预检查,
+ * 同时校验配置版本和当前学期未在页面确认后发生变化。
+ */
+ return Result.success(
+ "系统初始化完成",
+ systemInitializationService.execute(dto));
+ }
+}
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingMaterialController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingMaterialController.java
new file mode 100644
index 000000000..021cb3cf3
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingMaterialController.java
@@ -0,0 +1,100 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.JCXX;
+import com.roomroot.jwgl.service.TeachingMaterialService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 教材信息控制器
+ */
+@RestController
+@RequestMapping("/teachingMaterial")
+public class TeachingMaterialController {
+
+ @Resource
+ private TeachingMaterialService teachingMaterialService;
+
+ /**
+ * 新增教材信息
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody JCXX jcxx) {
+ teachingMaterialService.add(jcxx);
+ return Result.success();
+ }
+
+ /**
+ * 删除教材信息
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("id") String id) {
+ teachingMaterialService.delete(id);
+ return Result.success();
+ }
+
+ /**
+ * 更新教材信息
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody JCXX jcxx) {
+ teachingMaterialService.update(jcxx);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询
+ */
+ @GetMapping("/getByBh")
+ public Result getByBh(@RequestParam("bh") String bh) {
+ return Result.success(teachingMaterialService.getByBh(bh));
+ }
+
+ /**
+ * 根据ID查询
+ */
+ @GetMapping("/getById")
+ public Result getById(@RequestParam("id") String id) {
+ return Result.success(teachingMaterialService.getById(id));
+ }
+
+ /**
+ * 分页查询教材信息列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, JCXX jcxx) {
+ return Result.success(teachingMaterialService.pageList(query, jcxx));
+ }
+
+ /**
+ * 查询所有教材信息列表
+ */
+ @GetMapping("/all")
+ public Result> all(JCXX jcxx) {
+ return Result.success(teachingMaterialService.list(jcxx));
+ }
+
+ /**
+ * 导入Excel文件
+ */
+ @PostMapping("/import")
+ public Result importExcel(@RequestParam("file") MultipartFile file) {
+ int count = teachingMaterialService.importExcel(file);
+ return Result.success("导入成功", count);
+ }
+
+ /**
+ * 下载模板文件
+ */
+ @GetMapping("/template")
+ public void downloadTemplate(HttpServletResponse response) {
+ teachingMaterialService.downloadTemplate(response);
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingPlanController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingPlanController.java
new file mode 100644
index 000000000..cb363ceee
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingPlanController.java
@@ -0,0 +1,110 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.JXSSJH;
+import com.roomroot.jwgl.service.TeachingPlanService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.annotation.Resource;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.List;
+
+/**
+ * 导入数据表-教学实施计划控制器
+ */
+@RestController
+@RequestMapping("/teachingPlan")
+public class TeachingPlanController {
+
+ @Resource
+ private TeachingPlanService teachingPlanService;
+
+ /**
+ * 新增
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody JXSSJH jxssjh) {
+ teachingPlanService.add(jxssjh);
+ return Result.success();
+ }
+
+ /**
+ * 删除
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("bsh") String bsh) {
+ teachingPlanService.delete(bsh);
+ return Result.success();
+ }
+
+ /**
+ * 更新
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody JXSSJH drsjbjxssjh) {
+ teachingPlanService.update(drsjbjxssjh);
+ return Result.success();
+ }
+
+ /**
+ * 根据标识号查询
+ */
+ @GetMapping("/get")
+ public Result getByBsh(@RequestParam("bsh") String bsh) {
+ return Result.success(teachingPlanService.getByBsh(bsh));
+ }
+
+ /**
+ * 分页查询列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, JXSSJH jxssjh) {
+ return Result.success(teachingPlanService.pageList(query, jxssjh));
+ }
+
+ /**
+ * 查询所有列表
+ */
+ @GetMapping("/all")
+ public Result> all(JXSSJH jxssjh) {
+ return Result.success(teachingPlanService.list(jxssjh));
+ }
+
+ /**
+ * Excel导入教学实施计划。
+ * 标识号和登录编号自动生成,其他字段从Excel读取。
+ */
+ @PostMapping("/import")
+ public Result importFromExcel(@RequestParam("file") MultipartFile file) throws Exception {
+ int count = teachingPlanService.importFromExcel(file);
+ return Result.success("导入成功,共" + count + "条记录", count);
+ }
+
+ /**
+ * 下载教学实施计划导入模板
+ */
+ @GetMapping("/template")
+ public ResponseEntity downloadTemplate() throws IOException {
+ ClassPathResource resource = new ClassPathResource("template/教学实施计划导入模板.xlsx");
+ if (!resource.exists()) {
+ return ResponseEntity.notFound().build();
+ }
+ String fileName = URLEncoder.encode("教学实施计划导入模板.xlsx", "UTF-8");
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+ headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);
+
+ return ResponseEntity.ok()
+ .headers(headers)
+ .body(resource);
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockController.java
new file mode 100644
index 000000000..d329595d6
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockController.java
@@ -0,0 +1,81 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.JCKCD;
+import com.roomroot.jwgl.service.TeachingStockService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+/**
+ * 教材库存单控制器
+ */
+@RestController
+@RequestMapping("/teachingStock")
+public class TeachingStockController {
+
+ @Resource
+ private TeachingStockService teachingStockService;
+
+ /**
+ * 新增教材库存单
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody JCKCD jckcd) {
+ teachingStockService.add(jckcd);
+ return Result.success();
+ }
+
+ /**
+ * 删除教材库存单
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("id") String id) {
+ teachingStockService.delete(id);
+ return Result.success();
+ }
+
+ /**
+ * 更新教材库存单
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody JCKCD jckcd) {
+ teachingStockService.update(jckcd);
+ return Result.success();
+ }
+
+ /**
+ * 根据ID查询
+ */
+ @GetMapping("/get")
+ public Result getById(@RequestParam("id") String id) {
+ return Result.success(teachingStockService.getById(id));
+ }
+
+ /**
+ * 根据编号查询
+ */
+ @GetMapping("/getByBh")
+ public Result getByBh(@RequestParam("bh") String bh) {
+ return Result.success(teachingStockService.getByBh(bh));
+ }
+
+ /**
+ * 分页查询教材库存单列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, JCKCD jckcd) {
+ return Result.success(teachingStockService.pageList(query, jckcd));
+ }
+
+ /**
+ * 查询所有教材库存单列表
+ */
+ @GetMapping("/all")
+ public Result> all(JCKCD jckcd) {
+ return Result.success(teachingStockService.list(jckcd));
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockDetailController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockDetailController.java
new file mode 100644
index 000000000..361207fa2
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockDetailController.java
@@ -0,0 +1,89 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.JCKCMXX;
+import com.roomroot.jwgl.service.TeachingStockDetailService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+/**
+ * 教材库存明细项控制器
+ */
+@RestController
+@RequestMapping("/teachingStock/detail")
+public class TeachingStockDetailController {
+
+ @Resource
+ private TeachingStockDetailService teachingStockDetailService;
+
+ /**
+ * 新增教材库存明细项
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody JCKCMXX jckcmxx) {
+ teachingStockDetailService.add(jckcmxx);
+ return Result.success();
+ }
+
+ /**
+ * 删除教材库存明细项
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("id") String id) {
+ teachingStockDetailService.delete(id);
+ return Result.success();
+ }
+
+ /**
+ * 更新教材库存明细项
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody JCKCMXX jckcmxx) {
+ teachingStockDetailService.update(jckcmxx);
+ return Result.success();
+ }
+
+ /**
+ * 根据ID查询
+ */
+ @GetMapping("/get")
+ public Result getById(@RequestParam("id") String id) {
+ return Result.success(teachingStockDetailService.getById(id));
+ }
+
+ /**
+ * 根据编号查询
+ */
+ @GetMapping("/getByBh")
+ public Result getByBh(@RequestParam("bh") String bh) {
+ return Result.success(teachingStockDetailService.getByBh(bh));
+ }
+
+ /**
+ * 根据教材库存单编号查询
+ */
+ @GetMapping("/getByJckcdbbh")
+ public Result> getByJckcdbbh(@RequestParam("jckcdbbh") String jckcdbbh) {
+ return Result.success(teachingStockDetailService.getByJckcdbbh(jckcdbbh));
+ }
+
+ /**
+ * 分页查询教材库存明细项列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, JCKCMXX jckcmxx) {
+ return Result.success(teachingStockDetailService.pageList(query, jckcmxx));
+ }
+
+ /**
+ * 查询所有教材库存明细项列表
+ */
+ @GetMapping("/all")
+ public Result> all(JCKCMXX jckcmxx) {
+ return Result.success(teachingStockDetailService.list(jckcmxx));
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingTaskController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingTaskController.java
new file mode 100644
index 000000000..1b56835f3
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingTaskController.java
@@ -0,0 +1,95 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.JXRW;
+import com.roomroot.jwgl.service.TeachingTaskService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+/**
+ * 教学任务控制器
+ */
+@RestController
+@RequestMapping("/teachingTask")
+public class TeachingTaskController {
+
+ @Resource
+ private TeachingTaskService teachingTaskService;
+
+ /**
+ * 新增
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody JXRW jxrw) {
+ teachingTaskService.add(jxrw);
+ return Result.success();
+ }
+
+ /**
+ * 删除
+ */
+ @PostMapping("/delete")
+ public Result delete(@RequestParam("bh") String bh) {
+ teachingTaskService.delete(bh);
+ return Result.success();
+ }
+
+ /**
+ * 批量删除
+ */
+ @PostMapping("/batchDelete")
+ public Result batchDelete(@RequestBody List bhList) {
+ teachingTaskService.batchDelete(bhList);
+ return Result.success();
+ }
+
+ /**
+ * 更新
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody JXRW jxrw) {
+ teachingTaskService.update(jxrw);
+ return Result.success();
+ }
+
+ /**
+ * 根据编号查询
+ */
+ @GetMapping("/get")
+ public Result getByBh(@RequestParam("bh") String bh) {
+ return Result.success(teachingTaskService.getByBh(bh));
+ }
+
+ /**
+ * 分页查询列表
+ */
+ @GetMapping("/list")
+ public Result> list(PageQuery query, JXRW jxrw) {
+ return Result.success(teachingTaskService.pageList(query, jxrw));
+ }
+
+ /**
+ * 查询所有有效数据(DEL_FLAG = 0)
+ */
+ @GetMapping("/all")
+ public Result> all() {
+ return Result.success(teachingTaskService.listAllValid());
+ }
+
+ /**
+ * 发布教学任务及教研室任务书
+ * 根据教学任务编号修改状态为"发布",并批量更新所有关联的教研室任务书状态为"发布"
+ *
+ * @param bh 教学任务编号
+ * @return 更新数量
+ */
+ @PostMapping("/batchPublish")
+ public Result batchPublish(@RequestParam("bh") String bh) {
+ int count = teachingTaskService.batchPublish(bh);
+ return Result.success(count);
+ }
+}
\ No newline at end of file
diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TrainingProgramController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TrainingProgramController.java
new file mode 100644
index 000000000..c5c6d27bd
--- /dev/null
+++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TrainingProgramController.java
@@ -0,0 +1,118 @@
+package com.roomroot.web.controller.jwgl;
+
+import com.roomroot.jwgl.entity.ZYB;
+import com.roomroot.jwgl.service.TrainingProgramService;
+import com.roomroot.jwgl.unit.PageQuery;
+import com.roomroot.jwgl.unit.PageResult;
+import com.roomroot.jwgl.unit.Result;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+
+/**
+ * 培养方案管理控制器
+ *
+ * 提供专业/培养方案的增加、删除(逻辑删除)、更新、查询等功能管理接口。
+ * 对应数据表:专业表
+ * 多版本通过培训类型(培训类型)字段区分。
+ *
+ *
+ * 接口规范:仅使用 GET 和 POST 两种方法,参数通过请求体或查询参数传递,不拼接在路径中。
+ *
+ */
+@RestController
+@RequestMapping("/training")
+public class TrainingProgramController {
+
+ @Resource
+ private TrainingProgramService trainingProgramService;
+
+ /**
+ * 新增培养方案(专业)
+ *
+ * @param entity 专业表实体
+ * @return 操作结果
+ */
+ @PostMapping("/add")
+ public Result add(@RequestBody ZYB entity) {
+ trainingProgramService.add(entity);
+ return Result.success();
+ }
+
+ /**
+ * 停用(逻辑删除)培养方案
+ * 将指定记录的 停用 字段置为 true(停用=false为启用,停用=true为已停用/删除)。
+ *
+ * @param zydh 专业代号
+ * @return 操作结果
+ */
+ @PostMapping("/disable")
+ public Result disable(@RequestParam("zydh") String zydh) {
+ trainingProgramService.disable(zydh);
+ return Result.success();
+ }
+
+ /**
+ * 更新培养方案
+ *
+ * @param entity 专业表实体(含专业代号)
+ * @return 操作结果
+ */
+ @PostMapping("/update")
+ public Result update(@RequestBody ZYB entity) {
+ trainingProgramService.update(entity);
+ return Result.success();
+ }
+
+ /**
+ * 根据专业代号查询培养方案详情
+ *
+ * @param zydh 专业代号
+ * @return 培养方案详情
+ */
+ @GetMapping("/get")
+ public Result getById(@RequestParam("zydh") String zydh) {
+ ZYB entity = trainingProgramService.getById(zydh);
+ return Result.success(entity);
+ }
+
+ /**
+ * 分页条件查询培养方案列表
+ *
+ * 支持模糊查询参数:
+ *
+ *