diff --git a/backend/pom.xml b/backend/pom.xml
index a99a54ce..dbeb0c46 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -407,6 +407,12 @@
${revision}
+
+
+ org.dromara
+ ruoyi-stgl
+ ${revision}
+
diff --git a/backend/ruoyi-admin/pom.xml b/backend/ruoyi-admin/pom.xml
index 6e1fa000..eef9838f 100644
--- a/backend/ruoyi-admin/pom.xml
+++ b/backend/ruoyi-admin/pom.xml
@@ -135,6 +135,13 @@
${revision}
+
+
+ org.dromara
+ ruoyi-stgl
+ ${revision}
+
+
de.codecentric
spring-boot-admin-starter-client
diff --git a/backend/ruoyi-admin/src/main/resources/add_batch_id.sql b/backend/ruoyi-admin/src/main/resources/add_batch_id.sql
new file mode 100644
index 00000000..fab91af0
--- /dev/null
+++ b/backend/ruoyi-admin/src/main/resources/add_batch_id.sql
@@ -0,0 +1,2 @@
+-- 添加批次ID字段
+ALTER TABLE xljk_consultation_schedule ADD COLUMN batch_id VARCHAR(50) DEFAULT NULL COMMENT '批次ID,用于标识同一批次创建的预约时段';
\ No newline at end of file
diff --git a/backend/ruoyi-admin/src/main/resources/application-prod.yml b/backend/ruoyi-admin/src/main/resources/application-prod.yml
index 72ce4d13..b9b8a1f4 100644
--- a/backend/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/backend/ruoyi-admin/src/main/resources/application-prod.yml
@@ -52,9 +52,9 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
- url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+ url: jdbc:mysql://47.99.164.66:3306/hzdyjs_xg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
- password: root
+ password: 'hn02le.34lkdLKD'
# # 从库数据源
# slave:
# lazy: true
diff --git a/backend/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDateConverter.java b/backend/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDateConverter.java
new file mode 100644
index 00000000..7bd800d1
--- /dev/null
+++ b/backend/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDateConverter.java
@@ -0,0 +1,49 @@
+package org.dromara.common.excel.convert;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Date;
+
+/**
+ * Excel日期转换器
+ * 日期格式转换为yyyy-MM-dd格式
+ *
+ * @author Cursor AI
+ */
+@Slf4j
+public class ExcelDateConverter implements Converter {
+
+ @Override
+ public Class supportJavaTypeKey() {
+ return Date.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return CellDataTypeEnum.STRING;
+ }
+
+ @Override
+ public Date convertToJavaData(ReadCellData> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ if (ObjectUtil.isNull(cellData.getData())) {
+ return null;
+ }
+ return DateUtil.parse(cellData.getStringValue());
+ }
+
+ @Override
+ public WriteCellData convertToExcelData(Date value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ if (ObjectUtil.isNull(value)) {
+ return new WriteCellData<>("");
+ }
+ return new WriteCellData<>(DateUtil.format(value, "yyyy-MM-dd"));
+ }
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/pom.xml b/backend/ruoyi-modules/pom.xml
index 6dfc9f47..2e93dacc 100644
--- a/backend/ruoyi-modules/pom.xml
+++ b/backend/ruoyi-modules/pom.xml
@@ -22,6 +22,7 @@
ruoyi-cw
ruoyi-rs
ruoyi-xljk
+ ruoyi-stgl
ruoyi-modules
diff --git a/backend/ruoyi-modules/ruoyi-stgl/pom.xml b/backend/ruoyi-modules/ruoyi-stgl/pom.xml
new file mode 100644
index 00000000..f7e28e60
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/pom.xml
@@ -0,0 +1,93 @@
+
+
+
+ ruoyi-modules
+ org.dromara
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-stgl
+
+
+ 社团管理模块
+
+
+
+
+
+ org.dromara
+ ruoyi-common-core
+
+
+
+
+ org.dromara
+ ruoyi-common-security
+
+
+
+
+ org.dromara
+ ruoyi-common-web
+
+
+
+
+ org.dromara
+ ruoyi-common-excel
+
+
+
+ org.apache.poi
+ poi-ooxml
+ 5.2.3
+
+
+
+
+ org.dromara
+ ruoyi-common-mybatis
+
+
+
+
+ org.dromara
+ ruoyi-common-tenant
+
+
+
+
+ org.dromara
+ ruoyi-common-idempotent
+
+
+
+
+ org.dromara
+ ruoyi-common-log
+
+
+
+
+ org.dromara
+ ruoyi-common-translation
+
+
+
+
+ org.dromara
+ ruoyi-common-oss
+
+
+
+
+ org.dromara
+ ruoyi-common-doc
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubController.java
new file mode 100644
index 00000000..a5604d00
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubController.java
@@ -0,0 +1,176 @@
+package org.dromara.stgl.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.stgl.domain.vo.StglClubVo;
+import org.dromara.stgl.domain.bo.StglClubBo;
+import org.dromara.stgl.service.IStglClubService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * 社团信息管理
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/stgl/club")
+@Tag(name = "社团信息管理", description = "社团信息管理")
+public class StglClubController extends BaseController {
+
+ private final IStglClubService stglClubService;
+
+ /**
+ * 查询社团信息列表
+ */
+ @Operation(summary = "查询社团信息列表")
+ @SaCheckPermission("stgl:club:list")
+ @GetMapping("/list")
+ public TableDataInfo list(StglClubBo bo, PageQuery pageQuery) {
+ return stglClubService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 导出社团信息列表
+ */
+ @Operation(summary = "导出社团信息列表")
+ @SaCheckPermission("stgl:club:export")
+ @Log(title = "社团信息", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(StglClubBo bo, HttpServletResponse response) {
+ List list = stglClubService.queryList(bo);
+ ExcelUtil.exportExcel(list, "社团信息", StglClubVo.class, response);
+ }
+
+ /**
+ * 获取社团信息详细信息
+ */
+ @Operation(summary = "获取社团信息详细信息")
+ @SaCheckPermission("stgl:club:query")
+ @GetMapping("/{id}")
+ public R getInfo(@NotNull(message = "主键不能为空")
+ @PathVariable Long id) {
+ return R.ok(stglClubService.queryById(id));
+ }
+
+ /**
+ * 新增社团信息
+ */
+ @Operation(summary = "新增社团信息")
+ @SaCheckPermission("stgl:club:add")
+ @Log(title = "社团信息", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R add(@Validated(AddGroup.class) @RequestBody StglClubBo bo) {
+ return toAjax(stglClubService.insertByBo(bo));
+ }
+
+ /**
+ * 修改社团信息
+ */
+ @Operation(summary = "修改社团信息")
+ @SaCheckPermission("stgl:club:edit")
+ @Log(title = "社团信息", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R edit(@Validated(EditGroup.class) @RequestBody StglClubBo bo) {
+ return toAjax(stglClubService.updateByBo(bo));
+ }
+
+ /**
+ * 删除社团信息
+ */
+ @Operation(summary = "删除社团信息")
+ @SaCheckPermission("stgl:club:remove")
+ @Log(title = "社团信息", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R remove(@NotEmpty(message = "主键不能为空")
+ @PathVariable Long[] ids) {
+ return toAjax(stglClubService.deleteWithValidByIds(List.of(ids), true));
+ }
+
+ /**
+ * 查询可用的社团选项
+ */
+ @Operation(summary = "查询可用的社团选项")
+ @SaCheckPermission("stgl:club:list")
+ @GetMapping("/options")
+ public R> getOptions() {
+ return R.ok(stglClubService.queryAvailableOptions());
+ }
+
+ /**
+ * 根据社团类型查询社团列表
+ */
+ @Operation(summary = "根据社团类型查询社团列表")
+ @SaCheckPermission("stgl:club:list")
+ @GetMapping("/listByType/{clubType}")
+ @Parameters({
+ @Parameter(name = "clubType", description = "社团类型", required = true)
+ })
+ public R> listByType(@PathVariable String clubType) {
+ return R.ok(stglClubService.queryByClubType(clubType));
+ }
+
+ /**
+ * 查询我管理的社团列表
+ */
+ @Operation(summary = "查询我管理的社团列表")
+ @SaCheckPermission("stgl:club:list")
+ @GetMapping("/myClubs")
+ public R> getMyClubs() {
+ return R.ok(stglClubService.queryMyClubs());
+ }
+
+ /**
+ * 检查社团名称是否重复
+ */
+ @Operation(summary = "检查社团名称是否重复")
+ @SaCheckPermission("stgl:club:list")
+ @GetMapping("/checkClubNameUnique")
+ @Parameters({
+ @Parameter(name = "clubName", description = "社团名称", required = true),
+ @Parameter(name = "excludeId", description = "排除的ID", required = false)
+ })
+ public R checkClubNameUnique(@RequestParam String clubName,
+ @RequestParam(required = false) Long excludeId) {
+ return R.ok(stglClubService.checkClubNameUnique(clubName, excludeId));
+ }
+
+ /**
+ * 启用/禁用社团
+ */
+ @Operation(summary = "启用/禁用社团")
+ @SaCheckPermission("stgl:club:edit")
+ @Log(title = "社团信息", businessType = BusinessType.UPDATE)
+ @PutMapping("/changeStatus")
+ @Parameters({
+ @Parameter(name = "id", description = "社团ID", required = true),
+ @Parameter(name = "status", description = "状态", required = true)
+ })
+ public R changeStatus(@RequestParam Long id, @RequestParam Integer status) {
+ return toAjax(stglClubService.changeStatus(id, status));
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubRegisterController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubRegisterController.java
new file mode 100644
index 00000000..b0c57795
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglClubRegisterController.java
@@ -0,0 +1,145 @@
+package org.dromara.stgl.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.stgl.domain.vo.StglClubRegisterVo;
+import org.dromara.stgl.service.IStglClubRegisterService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * 社团报名大厅
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/stgl/register")
+@Tag(name = "社团报名大厅", description = "社团报名大厅")
+public class StglClubRegisterController extends BaseController {
+
+ private final IStglClubRegisterService stglClubRegisterService;
+
+ /**
+ * 查询社团报名大厅列表
+ */
+ @Operation(summary = "查询社团报名大厅列表")
+ @GetMapping("/list")
+ public TableDataInfo list(@RequestParam(required = false) String clubType,
+ @RequestParam(required = false) String clubName,
+ PageQuery pageQuery) {
+ return stglClubRegisterService.queryPageList(clubType, clubName, pageQuery);
+ }
+
+ /**
+ * 查询社团报名大厅列表(不分页)
+ */
+ @Operation(summary = "查询社团报名大厅列表(不分页)")
+ @GetMapping("/listAll")
+ public R> listAll(@RequestParam(required = false) String clubType,
+ @RequestParam(required = false) String clubName) {
+ return R.ok(stglClubRegisterService.queryList(clubType, clubName));
+ }
+
+ /**
+ * 获取社团详细信息
+ */
+ @Operation(summary = "获取社团详细信息")
+ @GetMapping("/{clubId}")
+ @Parameters({
+ @Parameter(name = "clubId", description = "社团ID", required = true)
+ })
+ public R getClubDetail(@PathVariable Long clubId) {
+ return R.ok(stglClubRegisterService.queryClubDetail(clubId));
+ }
+
+ /**
+ * 报名加入社团
+ */
+ @Operation(summary = "报名加入社团")
+ @SaCheckPermission("stgl:register:join")
+ @Log(title = "社团报名", businessType = BusinessType.INSERT)
+ @PostMapping("/join/{clubId}")
+ @Parameters({
+ @Parameter(name = "clubId", description = "社团ID", required = true)
+ })
+ public R registerClub(@PathVariable Long clubId) {
+ Long userId = LoginHelper.getUserId();
+ return toAjax(stglClubRegisterService.registerClub(clubId, userId));
+ }
+
+ /**
+ * 退出社团
+ */
+ @Operation(summary = "退出社团")
+ @SaCheckPermission("stgl:register:quit")
+ @Log(title = "社团退出", businessType = BusinessType.UPDATE)
+ @PostMapping("/quit/{clubId}")
+ @Parameters({
+ @Parameter(name = "clubId", description = "社团ID", required = true)
+ })
+ public R quitClub(@PathVariable Long clubId) {
+ Long userId = LoginHelper.getUserId();
+ return toAjax(stglClubRegisterService.quitClub(clubId, userId));
+ }
+
+ /**
+ * 查询我参与的社团列表
+ */
+ @Operation(summary = "查询我参与的社团列表")
+ @SaCheckPermission("stgl:register:list")
+ @GetMapping("/myClubs")
+ public R> getMyClubs() {
+ Long userId = LoginHelper.getUserId();
+ return R.ok(stglClubRegisterService.queryMyClubs(userId));
+ }
+
+ /**
+ * 检查是否已报名某个社团
+ */
+ @Operation(summary = "检查是否已报名某个社团")
+ @GetMapping("/checkRegistered/{clubId}")
+ @Parameters({
+ @Parameter(name = "clubId", description = "社团ID", required = true)
+ })
+ public R checkRegistered(@PathVariable Long clubId) {
+ try {
+ Long userId = LoginHelper.getUserId();
+ return R.ok(stglClubRegisterService.checkRegistered(clubId, userId));
+ } catch (Exception e) {
+ // 未登录用户
+ return R.ok(false);
+ }
+ }
+
+ /**
+ * 查询推荐社团
+ */
+ @Operation(summary = "查询推荐社团")
+ @GetMapping("/recommend")
+ @Parameters({
+ @Parameter(name = "clubType", description = "社团类型", required = false),
+ @Parameter(name = "limit", description = "推荐数量", required = false)
+ })
+ public R> getRecommendClubs(@RequestParam(required = false) String clubType,
+ @RequestParam(required = false, defaultValue = "6") Integer limit) {
+ return R.ok(stglClubRegisterService.queryRecommendClubs(clubType, limit));
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglMemberController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglMemberController.java
new file mode 100644
index 00000000..e91085c7
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglMemberController.java
@@ -0,0 +1,205 @@
+package org.dromara.stgl.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.stgl.domain.vo.StglMemberVo;
+import org.dromara.stgl.domain.bo.StglMemberBo;
+import org.dromara.stgl.service.IStglMemberService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * 社团成员管理
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/stgl/member")
+@Tag(name = "社团成员管理", description = "社团成员管理")
+public class StglMemberController extends BaseController {
+
+ private final IStglMemberService stglMemberService;
+
+ /**
+ * 查询社团成员列表
+ */
+ @Operation(summary = "查询社团成员列表")
+ @SaCheckPermission("stgl:member:list")
+ @GetMapping("/list")
+ public TableDataInfo list(StglMemberBo bo, PageQuery pageQuery) {
+ return stglMemberService.queryPageList(bo, pageQuery);
+ }
+
+ /**
+ * 导出社团成员列表
+ */
+ @Operation(summary = "导出社团成员列表")
+ @SaCheckPermission("stgl:member:export")
+ @Log(title = "社团成员", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(StglMemberBo bo, HttpServletResponse response) {
+ List list = stglMemberService.queryList(bo);
+ ExcelUtil.exportExcel(list, "社团成员", StglMemberVo.class, response);
+ }
+
+ /**
+ * 获取社团成员详细信息
+ */
+ @Operation(summary = "获取社团成员详细信息")
+ @SaCheckPermission("stgl:member:query")
+ @GetMapping("/{id}")
+ public R getInfo(@NotNull(message = "主键不能为空")
+ @PathVariable Long id) {
+ return R.ok(stglMemberService.queryById(id));
+ }
+
+ /**
+ * 新增社团成员
+ */
+ @Operation(summary = "新增社团成员")
+ @SaCheckPermission("stgl:member:add")
+ @Log(title = "社团成员", businessType = BusinessType.INSERT)
+ @RepeatSubmit()
+ @PostMapping()
+ public R add(@Validated(AddGroup.class) @RequestBody StglMemberBo bo) {
+ return toAjax(stglMemberService.insertByBo(bo));
+ }
+
+ /**
+ * 修改社团成员
+ */
+ @Operation(summary = "修改社团成员")
+ @SaCheckPermission("stgl:member:edit")
+ @Log(title = "社团成员", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping()
+ public R edit(@Validated(EditGroup.class) @RequestBody StglMemberBo bo) {
+ return toAjax(stglMemberService.updateByBo(bo));
+ }
+
+ /**
+ * 删除社团成员
+ */
+ @Operation(summary = "删除社团成员")
+ @SaCheckPermission("stgl:member:remove")
+ @Log(title = "社团成员", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ public R remove(@NotEmpty(message = "主键不能为空")
+ @PathVariable Long[] ids) {
+ return toAjax(stglMemberService.deleteWithValidByIds(List.of(ids), true));
+ }
+
+ /**
+ * 根据社团ID查询成员列表
+ */
+ @Operation(summary = "根据社团ID查询成员列表")
+ @SaCheckPermission("stgl:member:list")
+ @GetMapping("/club/{clubId}")
+ @Parameters({
+ @Parameter(name = "clubId", description = "社团ID", required = true)
+ })
+ public R> getByClubId(@PathVariable Long clubId) {
+ return R.ok(stglMemberService.queryByClubId(clubId));
+ }
+
+ /**
+ * 根据用户ID查询参与的社团列表
+ */
+ @Operation(summary = "根据用户ID查询参与的社团列表")
+ @SaCheckPermission("stgl:member:list")
+ @GetMapping("/user/{userId}")
+ @Parameters({
+ @Parameter(name = "userId", description = "用户ID", required = true)
+ })
+ public R> getByUserId(@PathVariable Long userId) {
+ return R.ok(stglMemberService.queryByUserId(userId));
+ }
+
+ /**
+ * 检查用户是否已是某个社团/队伍的成员
+ */
+ @Operation(summary = "检查用户是否已是某个社团/队伍的成员")
+ @SaCheckPermission("stgl:member:list")
+ @GetMapping("/checkMemberExists")
+ @Parameters({
+ @Parameter(name = "memberType", description = "成员类型", required = true),
+ @Parameter(name = "relationId", description = "关联ID", required = true),
+ @Parameter(name = "userId", description = "用户ID", required = true)
+ })
+ public R checkMemberExists(@RequestParam Integer memberType,
+ @RequestParam Long relationId,
+ @RequestParam Long userId) {
+ return R.ok(stglMemberService.checkMemberExists(memberType, relationId, userId));
+ }
+
+ /**
+ * 批量添加成员
+ */
+ @Operation(summary = "批量添加成员")
+ @SaCheckPermission("stgl:member:add")
+ @Log(title = "社团成员", businessType = BusinessType.INSERT)
+ @PostMapping("/batchAdd")
+ @Parameters({
+ @Parameter(name = "memberType", description = "成员类型", required = true),
+ @Parameter(name = "relationId", description = "关联ID", required = true),
+ @Parameter(name = "userIds", description = "用户ID列表", required = true),
+ @Parameter(name = "roleType", description = "角色类型", required = false)
+ })
+ public R batchAddMembers(@RequestParam Integer memberType,
+ @RequestParam Long relationId,
+ @RequestParam List userIds,
+ @RequestParam(required = false) Integer roleType) {
+ return toAjax(stglMemberService.batchAddMembers(memberType, relationId, userIds, roleType));
+ }
+
+ /**
+ * 更新成员角色
+ */
+ @Operation(summary = "更新成员角色")
+ @SaCheckPermission("stgl:member:edit")
+ @Log(title = "社团成员", businessType = BusinessType.UPDATE)
+ @PutMapping("/updateRole")
+ @Parameters({
+ @Parameter(name = "id", description = "成员ID", required = true),
+ @Parameter(name = "roleType", description = "角色类型", required = true)
+ })
+ public R updateMemberRole(@RequestParam Long id, @RequestParam Integer roleType) {
+ return toAjax(stglMemberService.updateMemberRole(id, roleType));
+ }
+
+ /**
+ * 退出社团/队伍
+ */
+ @Operation(summary = "退出社团/队伍")
+ @SaCheckPermission("stgl:member:edit")
+ @Log(title = "社团成员", businessType = BusinessType.UPDATE)
+ @PutMapping("/quit/{id}")
+ @Parameters({
+ @Parameter(name = "id", description = "成员ID", required = true)
+ })
+ public R quitMember(@PathVariable Long id) {
+ return toAjax(stglMemberService.quitMember(id));
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClub.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClub.java
new file mode 100644
index 00000000..28125ae5
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglClub.java
@@ -0,0 +1,160 @@
+package org.dromara.stgl.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 社团信息对象 stgl_club
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("stgl_club")
+public class StglClub extends TenantEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 社团名称
+ */
+ private String clubName;
+
+ /**
+ * 社团类型
+ */
+ private String clubType;
+
+ /**
+ * 成立日期
+ */
+ private Date establishDate;
+
+ /**
+ * 社团简介(500字以内)
+ */
+ private String description;
+
+ /**
+ * 邮箱
+ */
+ private String email;
+
+ /**
+ * 电话
+ */
+ private String phone;
+
+ /**
+ * logo图片路径
+ */
+ private String logoUrl;
+
+ /**
+ * 社长姓名
+ */
+ private String presidentName;
+
+ /**
+ * 社长学号
+ */
+ private String presidentStudentNo;
+
+ /**
+ * 社长院系
+ */
+ private String presidentDept;
+
+ /**
+ * 社长专业
+ */
+ private String presidentMajor;
+
+ /**
+ * 社长班级
+ */
+ private String presidentClass;
+
+ /**
+ * 社长联系方式
+ */
+ private String presidentPhone;
+
+ /**
+ * 副社长姓名
+ */
+ private String vicePresidentName;
+
+ /**
+ * 副社长学号
+ */
+ private String vicePresidentStudentNo;
+
+ /**
+ * 指导老师姓名
+ */
+ private String advisorName;
+
+ /**
+ * 指导老师所属院系
+ */
+ private String advisorDept;
+
+ /**
+ * 成员规模上限
+ */
+ private Integer memberLimit;
+
+ /**
+ * 组织架构(部门设置及职责)
+ */
+ private String departments;
+
+ /**
+ * 招新周期
+ */
+ private String recruitCycle;
+
+ /**
+ * 招新时间
+ */
+ private String recruitTime;
+
+ /**
+ * 活动频率
+ */
+ private String activityFrequency;
+
+ /**
+ * 经费来源(多选)
+ */
+ private String fundingSources;
+
+ /**
+ * 章程文档路径
+ */
+ private String charterUrl;
+
+ /**
+ * 状态(1-正常 0-停用)
+ */
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglMember.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglMember.java
new file mode 100644
index 00000000..748e9006
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglMember.java
@@ -0,0 +1,75 @@
+package org.dromara.stgl.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.dromara.common.tenant.core.TenantEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 社团成员对象 stgl_member
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("stgl_member")
+public class StglMember extends TenantEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id")
+ private Long id;
+
+ /**
+ * 成员类型(1-社团成员 2-志愿者)
+ */
+ private Integer memberType;
+
+ /**
+ * 关联ID(社团ID或志愿者队伍ID)
+ */
+ private Long relationId;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 学号
+ */
+ private String studentNo;
+
+ /**
+ * 学生姓名
+ */
+ private String studentName;
+
+ /**
+ * 角色类型(1-社长 2-副社长 3-成员)
+ */
+ private Integer roleType;
+
+ /**
+ * 加入时间
+ */
+ private Date joinTime;
+
+ /**
+ * 状态(1-正常 0-退出)
+ */
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubBo.java
new file mode 100644
index 00000000..82ff495d
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClubBo.java
@@ -0,0 +1,188 @@
+package org.dromara.stgl.domain.bo;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import org.dromara.stgl.domain.StglClub;
+
+import java.util.Date;
+
+/**
+ * 社团信息业务对象 stgl_club
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = StglClub.class, reverseConvertGenerate = false)
+public class StglClubBo extends BaseEntity {
+
+ /**
+ * 主键ID
+ */
+ @NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
+ private Long id;
+
+ /**
+ * 社团名称
+ */
+ @NotBlank(message = "社团名称不能为空", groups = { AddGroup.class, EditGroup.class })
+ @Size(max = 100, message = "社团名称长度不能超过100个字符")
+ private String clubName;
+
+ /**
+ * 社团类型
+ */
+ @NotBlank(message = "社团类型不能为空", groups = { AddGroup.class, EditGroup.class })
+ @Size(max = 50, message = "社团类型长度不能超过50个字符")
+ private String clubType;
+
+ /**
+ * 成立日期
+ */
+ private Date establishDate;
+
+ /**
+ * 社团简介(500字以内)
+ */
+ @Size(max = 500, message = "社团简介长度不能超过500个字符")
+ private String description;
+
+ /**
+ * 邮箱
+ */
+ @Email(message = "邮箱格式不正确")
+ @Size(max = 100, message = "邮箱长度不能超过100个字符")
+ private String email;
+
+ /**
+ * 电话
+ */
+ @Pattern(regexp = "^[0-9-]+$", message = "电话格式不正确")
+ @Size(max = 20, message = "电话长度不能超过20个字符")
+ private String phone;
+
+ /**
+ * logo图片路径
+ */
+ @Size(max = 500, message = "logo图片路径长度不能超过500个字符")
+ private String logoUrl;
+
+ /**
+ * 社长姓名
+ */
+ @Size(max = 50, message = "社长姓名长度不能超过50个字符")
+ private String presidentName;
+
+ /**
+ * 社长学号
+ */
+ @Size(max = 20, message = "社长学号长度不能超过20个字符")
+ private String presidentStudentNo;
+
+ /**
+ * 社长院系
+ */
+ @Size(max = 100, message = "社长院系长度不能超过100个字符")
+ private String presidentDept;
+
+ /**
+ * 社长专业
+ */
+ @Size(max = 100, message = "社长专业长度不能超过100个字符")
+ private String presidentMajor;
+
+ /**
+ * 社长班级
+ */
+ @Size(max = 100, message = "社长班级长度不能超过100个字符")
+ private String presidentClass;
+
+ /**
+ * 社长联系方式
+ */
+ @Pattern(regexp = "^[0-9-]+$", message = "社长联系方式格式不正确")
+ @Size(max = 20, message = "社长联系方式长度不能超过20个字符")
+ private String presidentPhone;
+
+ /**
+ * 副社长姓名
+ */
+ @Size(max = 50, message = "副社长姓名长度不能超过50个字符")
+ private String vicePresidentName;
+
+ /**
+ * 副社长学号
+ */
+ @Size(max = 20, message = "副社长学号长度不能超过20个字符")
+ private String vicePresidentStudentNo;
+
+ /**
+ * 指导老师姓名
+ */
+ @Size(max = 50, message = "指导老师姓名长度不能超过50个字符")
+ private String advisorName;
+
+ /**
+ * 指导老师所属院系
+ */
+ @Size(max = 100, message = "指导老师所属院系长度不能超过100个字符")
+ private String advisorDept;
+
+ /**
+ * 成员规模上限
+ */
+ @Min(value = 0, message = "成员规模上限不能小于0")
+ private Integer memberLimit;
+
+ /**
+ * 组织架构(部门设置及职责)
+ */
+ private String departments;
+
+ /**
+ * 招新周期
+ */
+ @Size(max = 50, message = "招新周期长度不能超过50个字符")
+ private String recruitCycle;
+
+ /**
+ * 招新时间
+ */
+ @Size(max = 50, message = "招新时间长度不能超过50个字符")
+ private String recruitTime;
+
+ /**
+ * 活动频率
+ */
+ @Size(max = 50, message = "活动频率长度不能超过50个字符")
+ private String activityFrequency;
+
+ /**
+ * 经费来源(多选)
+ */
+ @Size(max = 200, message = "经费来源长度不能超过200个字符")
+ private String fundingSources;
+
+ /**
+ * 章程文档路径
+ */
+ @Size(max = 500, message = "章程文档路径长度不能超过500个字符")
+ private String charterUrl;
+
+ /**
+ * 状态(1-正常 0-停用)
+ */
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglMemberBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglMemberBo.java
new file mode 100644
index 00000000..df0913b5
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglMemberBo.java
@@ -0,0 +1,85 @@
+package org.dromara.stgl.domain.bo;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import org.dromara.stgl.domain.StglMember;
+
+import java.util.Date;
+
+/**
+ * 社团成员业务对象 stgl_member
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = StglMember.class, reverseConvertGenerate = false)
+public class StglMemberBo extends BaseEntity {
+
+ /**
+ * 主键ID
+ */
+ @NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
+ private Long id;
+
+ /**
+ * 成员类型(1-社团成员 2-志愿者)
+ */
+ @NotNull(message = "成员类型不能为空", groups = { AddGroup.class, EditGroup.class })
+ @Min(value = 1, message = "成员类型值无效")
+ @Max(value = 2, message = "成员类型值无效")
+ private Integer memberType;
+
+ /**
+ * 关联ID(社团ID或志愿者队伍ID)
+ */
+ @NotNull(message = "关联ID不能为空", groups = { AddGroup.class, EditGroup.class })
+ private Long relationId;
+
+ /**
+ * 用户ID
+ */
+ @NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class })
+ private Long userId;
+
+ /**
+ * 学号
+ */
+ @Size(max = 20, message = "学号长度不能超过20个字符")
+ private String studentNo;
+
+ /**
+ * 学生姓名
+ */
+ @Size(max = 50, message = "学生姓名长度不能超过50个字符")
+ private String studentName;
+
+ /**
+ * 角色类型(1-社长 2-副社长 3-成员)
+ */
+ @Min(value = 1, message = "角色类型值无效")
+ @Max(value = 3, message = "角色类型值无效")
+ private Integer roleType;
+
+ /**
+ * 加入时间
+ */
+ private Date joinTime;
+
+ /**
+ * 状态(1-正常 0-退出)
+ */
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubRegisterVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubRegisterVo.java
new file mode 100644
index 00000000..cbd264bb
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubRegisterVo.java
@@ -0,0 +1,111 @@
+package org.dromara.stgl.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 社团报名大厅视图对象
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+public class StglClubRegisterVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ private Long id;
+
+ /**
+ * 社团名称
+ */
+ private String clubName;
+
+ /**
+ * 社团类型
+ */
+ private String clubType;
+
+ /**
+ * 成立日期
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date establishDate;
+
+ /**
+ * 社团简介(500字以内)
+ */
+ private String description;
+
+ /**
+ * logo图片路径
+ */
+ private String logoUrl;
+
+ /**
+ * 社长姓名
+ */
+ private String presidentName;
+
+ /**
+ * 社长院系
+ */
+ private String presidentDept;
+
+ /**
+ * 指导老师姓名
+ */
+ private String advisorName;
+
+ /**
+ * 指导老师所属院系
+ */
+ private String advisorDept;
+
+ /**
+ * 成员规模上限
+ */
+ private Integer memberLimit;
+
+ /**
+ * 当前成员数量
+ */
+ private Integer currentMemberCount;
+
+ /**
+ * 招新周期
+ */
+ private String recruitCycle;
+
+ /**
+ * 招新时间
+ */
+ private String recruitTime;
+
+ /**
+ * 活动频率
+ */
+ private String activityFrequency;
+
+ /**
+ * 是否已报名
+ */
+ private Boolean hasRegistered;
+
+ /**
+ * 报名状态(1-已报名 0-未报名)
+ */
+ private Integer registrationStatus;
+
+ /**
+ * 是否可报名(基于成员上限等条件)
+ */
+ private Boolean canRegister;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubVo.java
new file mode 100644
index 00000000..9dcc55ea
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglClubVo.java
@@ -0,0 +1,211 @@
+package org.dromara.stgl.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.stgl.domain.StglClub;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 社团信息视图对象 stgl_club
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = StglClub.class)
+public class StglClubVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @ExcelProperty(value = "主键ID")
+ private Long id;
+
+ /**
+ * 社团名称
+ */
+ @ExcelProperty(value = "社团名称")
+ private String clubName;
+
+ /**
+ * 社团类型
+ */
+ @ExcelProperty(value = "社团类型")
+ private String clubType;
+
+ /**
+ * 成立日期
+ */
+ @ExcelProperty(value = "成立日期")
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date establishDate;
+
+ /**
+ * 社团简介(500字以内)
+ */
+ @ExcelProperty(value = "社团简介")
+ private String description;
+
+ /**
+ * 邮箱
+ */
+ @ExcelProperty(value = "邮箱")
+ private String email;
+
+ /**
+ * 电话
+ */
+ @ExcelProperty(value = "电话")
+ private String phone;
+
+ /**
+ * logo图片路径
+ */
+ @ExcelProperty(value = "社团Logo")
+ private String logoUrl;
+
+ /**
+ * 社长姓名
+ */
+ @ExcelProperty(value = "社长姓名")
+ private String presidentName;
+
+ /**
+ * 社长学号
+ */
+ @ExcelProperty(value = "社长学号")
+ private String presidentStudentNo;
+
+ /**
+ * 社长院系
+ */
+ @ExcelProperty(value = "社长院系")
+ private String presidentDept;
+
+ /**
+ * 社长专业
+ */
+ @ExcelProperty(value = "社长专业")
+ private String presidentMajor;
+
+ /**
+ * 社长班级
+ */
+ @ExcelProperty(value = "社长班级")
+ private String presidentClass;
+
+ /**
+ * 社长联系方式
+ */
+ @ExcelProperty(value = "社长联系方式")
+ private String presidentPhone;
+
+ /**
+ * 副社长姓名
+ */
+ @ExcelProperty(value = "副社长姓名")
+ private String vicePresidentName;
+
+ /**
+ * 副社长学号
+ */
+ @ExcelProperty(value = "副社长学号")
+ private String vicePresidentStudentNo;
+
+ /**
+ * 指导老师姓名
+ */
+ @ExcelProperty(value = "指导老师姓名")
+ private String advisorName;
+
+ /**
+ * 指导老师所属院系
+ */
+ @ExcelProperty(value = "指导老师所属院系")
+ private String advisorDept;
+
+ /**
+ * 成员规模上限
+ */
+ @ExcelProperty(value = "成员规模上限")
+ private Integer memberLimit;
+
+ /**
+ * 组织架构(部门设置及职责)
+ */
+ @ExcelProperty(value = "组织架构")
+ private String departments;
+
+ /**
+ * 招新周期
+ */
+ @ExcelProperty(value = "招新周期")
+ private String recruitCycle;
+
+ /**
+ * 招新时间
+ */
+ @ExcelProperty(value = "招新时间")
+ private String recruitTime;
+
+ /**
+ * 活动频率
+ */
+ @ExcelProperty(value = "活动频率")
+ private String activityFrequency;
+
+ /**
+ * 经费来源(多选)
+ */
+ @ExcelProperty(value = "经费来源")
+ private String fundingSources;
+
+ /**
+ * 章程文档路径
+ */
+ @ExcelProperty(value = "章程文档")
+ private String charterUrl;
+
+ /**
+ * 状态(1-正常 0-停用)
+ */
+ @ExcelProperty(value = "状态")
+ private Integer status;
+
+ /**
+ * 备注
+ */
+ @ExcelProperty(value = "备注")
+ private String remark;
+
+ /**
+ * 创建者
+ */
+ private Long createBy;
+
+ /**
+ * 创建时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date createTime;
+
+ /**
+ * 更新者
+ */
+ private Long updateBy;
+
+ /**
+ * 更新时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date updateTime;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglMemberVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglMemberVo.java
new file mode 100644
index 00000000..54007c89
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglMemberVo.java
@@ -0,0 +1,133 @@
+package org.dromara.stgl.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.stgl.domain.StglMember;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 社团成员视图对象 stgl_member
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = StglMember.class)
+public class StglMemberVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @ExcelProperty(value = "主键ID")
+ private Long id;
+
+ /**
+ * 成员类型(1-社团成员 2-志愿者)
+ */
+ @ExcelProperty(value = "成员类型")
+ private Integer memberType;
+
+ /**
+ * 成员类型名称
+ */
+ @ExcelProperty(value = "成员类型名称")
+ private String memberTypeName;
+
+ /**
+ * 关联ID(社团ID或志愿者队伍ID)
+ */
+ @ExcelProperty(value = "关联ID")
+ private Long relationId;
+
+ /**
+ * 关联名称(社团名称或志愿者队伍名称)
+ */
+ @ExcelProperty(value = "所属组织")
+ private String relationName;
+
+ /**
+ * 用户ID
+ */
+ @ExcelProperty(value = "用户ID")
+ private Long userId;
+
+ /**
+ * 学号
+ */
+ @ExcelProperty(value = "学号")
+ private String studentNo;
+
+ /**
+ * 学生姓名
+ */
+ @ExcelProperty(value = "学生姓名")
+ private String studentName;
+
+ /**
+ * 角色类型(1-社长 2-副社长 3-成员)
+ */
+ @ExcelProperty(value = "角色类型")
+ private Integer roleType;
+
+ /**
+ * 角色类型名称
+ */
+ @ExcelProperty(value = "角色名称")
+ private String roleTypeName;
+
+ /**
+ * 加入时间
+ */
+ @ExcelProperty(value = "加入时间")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date joinTime;
+
+ /**
+ * 状态(1-正常 0-退出)
+ */
+ @ExcelProperty(value = "状态")
+ private Integer status;
+
+ /**
+ * 状态名称
+ */
+ @ExcelProperty(value = "状态名称")
+ private String statusName;
+
+ /**
+ * 备注
+ */
+ @ExcelProperty(value = "备注")
+ private String remark;
+
+ /**
+ * 创建者
+ */
+ private Long createBy;
+
+ /**
+ * 创建时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date createTime;
+
+ /**
+ * 更新者
+ */
+ private Long updateBy;
+
+ /**
+ * 更新时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date updateTime;
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubMapper.java
new file mode 100644
index 00000000..0e244ffb
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglClubMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.stgl.mapper;
+
+import org.dromara.stgl.domain.StglClub;
+import org.dromara.stgl.domain.vo.StglClubVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 社团信息Mapper接口
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+public interface StglClubMapper extends BaseMapperPlus {
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglMemberMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglMemberMapper.java
new file mode 100644
index 00000000..673b898b
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglMemberMapper.java
@@ -0,0 +1,15 @@
+package org.dromara.stgl.mapper;
+
+import org.dromara.stgl.domain.StglMember;
+import org.dromara.stgl.domain.vo.StglMemberVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 社团成员Mapper接口
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+public interface StglMemberMapper extends BaseMapperPlus {
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubRegisterService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubRegisterService.java
new file mode 100644
index 00000000..f6b80259
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubRegisterService.java
@@ -0,0 +1,88 @@
+package org.dromara.stgl.service;
+
+import org.dromara.stgl.domain.vo.StglClubRegisterVo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.List;
+
+/**
+ * 社团报名大厅Service接口
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+public interface IStglClubRegisterService {
+
+ /**
+ * 分页查询社团报名大厅列表
+ *
+ * @param clubType 社团类型
+ * @param clubName 社团名称
+ * @param pageQuery 分页参数
+ * @return 社团报名大厅分页列表
+ */
+ TableDataInfo queryPageList(String clubType, String clubName, PageQuery pageQuery);
+
+ /**
+ * 查询社团报名大厅列表
+ *
+ * @param clubType 社团类型
+ * @param clubName 社团名称
+ * @return 社团报名大厅列表
+ */
+ List queryList(String clubType, String clubName);
+
+ /**
+ * 查询社团详细信息(用于报名)
+ *
+ * @param clubId 社团ID
+ * @return 社团详细信息
+ */
+ StglClubRegisterVo queryClubDetail(Long clubId);
+
+ /**
+ * 报名加入社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否报名成功
+ */
+ Boolean registerClub(Long clubId, Long userId);
+
+ /**
+ * 退出社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否退出成功
+ */
+ Boolean quitClub(Long clubId, Long userId);
+
+ /**
+ * 查询我参与的社团列表
+ *
+ * @param userId 用户ID
+ * @return 社团列表
+ */
+ List queryMyClubs(Long userId);
+
+ /**
+ * 检查是否已报名某个社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否已报名
+ */
+ Boolean checkRegistered(Long clubId, Long userId);
+
+ /**
+ * 根据社团类型查询推荐社团
+ *
+ * @param clubType 社团类型
+ * @param limit 推荐数量
+ * @return 推荐社团列表
+ */
+ List queryRecommendClubs(String clubType, Integer limit);
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubService.java
new file mode 100644
index 00000000..2bb93775
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglClubService.java
@@ -0,0 +1,109 @@
+package org.dromara.stgl.service;
+
+import org.dromara.stgl.domain.vo.StglClubVo;
+import org.dromara.stgl.domain.bo.StglClubBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 社团信息Service接口
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+public interface IStglClubService {
+
+ /**
+ * 查询社团信息
+ *
+ * @param id 主键
+ * @return 社团信息
+ */
+ StglClubVo queryById(Long id);
+
+ /**
+ * 分页查询社团信息列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return 社团信息分页列表
+ */
+ TableDataInfo queryPageList(StglClubBo bo, PageQuery pageQuery);
+
+ /**
+ * 查询符合条件的社团信息列表
+ *
+ * @param bo 查询条件
+ * @return 社团信息列表
+ */
+ List queryList(StglClubBo bo);
+
+ /**
+ * 新增社团信息
+ *
+ * @param bo 社团信息
+ * @return 是否新增成功
+ */
+ Boolean insertByBo(StglClubBo bo);
+
+ /**
+ * 修改社团信息
+ *
+ * @param bo 社团信息
+ * @return 是否修改成功
+ */
+ Boolean updateByBo(StglClubBo bo);
+
+ /**
+ * 校验并批量删除社团信息
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ Boolean deleteWithValidByIds(Collection ids, Boolean isValid);
+
+ /**
+ * 查询可用的社团选项
+ *
+ * @return 社团选项列表
+ */
+ List queryAvailableOptions();
+
+ /**
+ * 根据社团类型查询社团列表
+ *
+ * @param clubType 社团类型
+ * @return 社团列表
+ */
+ List queryByClubType(String clubType);
+
+ /**
+ * 查询我管理的社团列表
+ *
+ * @return 社团列表
+ */
+ List queryMyClubs();
+
+ /**
+ * 检查社团名称是否重复
+ *
+ * @param clubName 社团名称
+ * @param excludeId 排除的ID(修改时使用)
+ * @return 是否重复
+ */
+ Boolean checkClubNameUnique(String clubName, Long excludeId);
+
+ /**
+ * 启用/禁用社团
+ *
+ * @param id 社团ID
+ * @param status 状态
+ * @return 是否操作成功
+ */
+ Boolean changeStatus(Long id, Integer status);
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglMemberService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglMemberService.java
new file mode 100644
index 00000000..50847b9a
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglMemberService.java
@@ -0,0 +1,123 @@
+package org.dromara.stgl.service;
+
+import org.dromara.stgl.domain.vo.StglMemberVo;
+import org.dromara.stgl.domain.bo.StglMemberBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 社团成员Service接口
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+public interface IStglMemberService {
+
+ /**
+ * 查询社团成员
+ *
+ * @param id 主键
+ * @return 社团成员
+ */
+ StglMemberVo queryById(Long id);
+
+ /**
+ * 分页查询社团成员列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return 社团成员分页列表
+ */
+ TableDataInfo queryPageList(StglMemberBo bo, PageQuery pageQuery);
+
+ /**
+ * 查询符合条件的社团成员列表
+ *
+ * @param bo 查询条件
+ * @return 社团成员列表
+ */
+ List queryList(StglMemberBo bo);
+
+ /**
+ * 新增社团成员
+ *
+ * @param bo 社团成员
+ * @return 是否新增成功
+ */
+ Boolean insertByBo(StglMemberBo bo);
+
+ /**
+ * 修改社团成员
+ *
+ * @param bo 社团成员
+ * @return 是否修改成功
+ */
+ Boolean updateByBo(StglMemberBo bo);
+
+ /**
+ * 校验并批量删除社团成员
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ Boolean deleteWithValidByIds(Collection ids, Boolean isValid);
+
+ /**
+ * 根据社团ID查询成员列表
+ *
+ * @param clubId 社团ID
+ * @return 成员列表
+ */
+ List queryByClubId(Long clubId);
+
+ /**
+ * 根据用户ID查询参与的社团列表
+ *
+ * @param userId 用户ID
+ * @return 社团成员关系列表
+ */
+ List queryByUserId(Long userId);
+
+ /**
+ * 检查用户是否已是某个社团/队伍的成员
+ *
+ * @param memberType 成员类型
+ * @param relationId 关联ID
+ * @param userId 用户ID
+ * @return 是否已是成员
+ */
+ Boolean checkMemberExists(Integer memberType, Long relationId, Long userId);
+
+ /**
+ * 批量添加成员
+ *
+ * @param memberType 成员类型
+ * @param relationId 关联ID
+ * @param userIds 用户ID列表
+ * @param roleType 角色类型
+ * @return 是否添加成功
+ */
+ Boolean batchAddMembers(Integer memberType, Long relationId, List userIds, Integer roleType);
+
+ /**
+ * 更新成员角色
+ *
+ * @param id 成员ID
+ * @param roleType 角色类型
+ * @return 是否更新成功
+ */
+ Boolean updateMemberRole(Long id, Integer roleType);
+
+ /**
+ * 退出社团/队伍
+ *
+ * @param id 成员ID
+ * @return 是否退出成功
+ */
+ Boolean quitMember(Long id);
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubRegisterServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubRegisterServiceImpl.java
new file mode 100644
index 00000000..7fe1c67f
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubRegisterServiceImpl.java
@@ -0,0 +1,306 @@
+package org.dromara.stgl.service.impl;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.dromara.stgl.domain.vo.StglClubRegisterVo;
+import org.dromara.stgl.domain.StglClub;
+import org.dromara.stgl.domain.StglMember;
+import org.dromara.stgl.mapper.StglClubMapper;
+import org.dromara.stgl.mapper.StglMemberMapper;
+import org.dromara.stgl.service.IStglClubRegisterService;
+import org.dromara.common.satoken.utils.LoginHelper;
+
+import java.util.List;
+import java.util.Date;
+import java.util.stream.Collectors;
+
+/**
+ * 社团报名大厅Service业务层处理
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@RequiredArgsConstructor
+@Service
+public class StglClubRegisterServiceImpl implements IStglClubRegisterService {
+
+ private final StglClubMapper clubMapper;
+ private final StglMemberMapper memberMapper;
+
+ /**
+ * 分页查询社团报名大厅列表
+ *
+ * @param clubType 社团类型
+ * @param clubName 社团名称
+ * @param pageQuery 分页参数
+ * @return 社团报名大厅分页列表
+ */
+ @Override
+ public TableDataInfo queryPageList(String clubType, String clubName, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(clubType, clubName);
+ Page page = clubMapper.selectPage(pageQuery.build(), lqw);
+
+ List voList = convertToRegisterVo(page.getRecords());
+
+ Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
+ result.setRecords(voList);
+
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 查询社团报名大厅列表
+ *
+ * @param clubType 社团类型
+ * @param clubName 社团名称
+ * @return 社团报名大厅列表
+ */
+ @Override
+ public List queryList(String clubType, String clubName) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(clubType, clubName);
+ List clubs = clubMapper.selectList(lqw);
+ return convertToRegisterVo(clubs);
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(String clubType, String clubName) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getStatus, 1); // 只查询正常状态的社团
+ lqw.eq(StringUtils.isNotBlank(clubType), StglClub::getClubType, clubType);
+ lqw.like(StringUtils.isNotBlank(clubName), StglClub::getClubName, clubName);
+ lqw.orderByDesc(StglClub::getCreateTime);
+ return lqw;
+ }
+
+ private List convertToRegisterVo(List clubs) {
+ Long currentUserId = null;
+ try {
+ currentUserId = LoginHelper.getUserId();
+ } catch (Exception e) {
+ // 未登录用户
+ }
+
+ final Long userId = currentUserId;
+
+ return clubs.stream().map(club -> {
+ StglClubRegisterVo vo = new StglClubRegisterVo();
+ vo.setId(club.getId());
+ vo.setClubName(club.getClubName());
+ vo.setClubType(club.getClubType());
+ vo.setEstablishDate(club.getEstablishDate());
+ vo.setDescription(club.getDescription());
+ vo.setLogoUrl(club.getLogoUrl());
+ vo.setPresidentName(club.getPresidentName());
+ vo.setPresidentDept(club.getPresidentDept());
+ vo.setAdvisorName(club.getAdvisorName());
+ vo.setAdvisorDept(club.getAdvisorDept());
+ vo.setMemberLimit(club.getMemberLimit());
+ vo.setRecruitCycle(club.getRecruitCycle());
+ vo.setRecruitTime(club.getRecruitTime());
+ vo.setActivityFrequency(club.getActivityFrequency());
+
+ // 查询当前成员数量
+ LambdaQueryWrapper memberCountLqw = Wrappers.lambdaQuery();
+ memberCountLqw.eq(StglMember::getMemberType, 1);
+ memberCountLqw.eq(StglMember::getRelationId, club.getId());
+ memberCountLqw.eq(StglMember::getStatus, 1);
+ long memberCount = memberMapper.selectCount(memberCountLqw);
+ vo.setCurrentMemberCount((int) memberCount);
+
+ // 检查是否可报名
+ boolean canRegister = club.getMemberLimit() == null || club.getMemberLimit() == 0 || memberCount < club.getMemberLimit();
+ vo.setCanRegister(canRegister);
+
+ // 检查当前用户是否已报名
+ if (userId != null) {
+ LambdaQueryWrapper userMemberLqw = Wrappers.lambdaQuery();
+ userMemberLqw.eq(StglMember::getMemberType, 1);
+ userMemberLqw.eq(StglMember::getRelationId, club.getId());
+ userMemberLqw.eq(StglMember::getUserId, userId);
+ userMemberLqw.eq(StglMember::getStatus, 1);
+ long registeredCount = memberMapper.selectCount(userMemberLqw);
+ vo.setHasRegistered(registeredCount > 0);
+ vo.setRegistrationStatus(registeredCount > 0 ? 1 : 0);
+ } else {
+ vo.setHasRegistered(false);
+ vo.setRegistrationStatus(0);
+ }
+
+ return vo;
+ }).collect(Collectors.toList());
+ }
+
+ /**
+ * 查询社团详细信息(用于报名)
+ *
+ * @param clubId 社团ID
+ * @return 社团详细信息
+ */
+ @Override
+ public StglClubRegisterVo queryClubDetail(Long clubId) {
+ StglClub club = clubMapper.selectById(clubId);
+ if (club == null) {
+ return null;
+ }
+ return convertToRegisterVo(List.of(club)).get(0);
+ }
+
+ /**
+ * 报名加入社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否报名成功
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean registerClub(Long clubId, Long userId) {
+ // 检查社团是否存在
+ StglClub club = clubMapper.selectById(clubId);
+ if (club == null || club.getStatus() != 1) {
+ throw new RuntimeException("社团不存在或已停用");
+ }
+
+ // 检查是否已是成员
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, 1);
+ lqw.eq(StglMember::getRelationId, clubId);
+ lqw.eq(StglMember::getUserId, userId);
+ lqw.eq(StglMember::getStatus, 1);
+ long count = memberMapper.selectCount(lqw);
+ if (count > 0) {
+ throw new RuntimeException("您已是该社团的成员");
+ }
+
+ // 检查成员上限
+ if (club.getMemberLimit() != null && club.getMemberLimit() > 0) {
+ LambdaQueryWrapper memberLqw = Wrappers.lambdaQuery();
+ memberLqw.eq(StglMember::getMemberType, 1);
+ memberLqw.eq(StglMember::getRelationId, clubId);
+ memberLqw.eq(StglMember::getStatus, 1);
+ long memberCount = memberMapper.selectCount(memberLqw);
+ if (memberCount >= club.getMemberLimit()) {
+ throw new RuntimeException("该社团成员已满,无法加入");
+ }
+ }
+
+ // 创建成员记录
+ StglMember member = new StglMember();
+ member.setMemberType(1); // 社团成员
+ member.setRelationId(clubId);
+ member.setUserId(userId);
+ member.setRoleType(3); // 普通成员
+ member.setJoinTime(new Date());
+ member.setStatus(1);
+
+ return memberMapper.insert(member) > 0;
+ }
+
+ /**
+ * 退出社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否退出成功
+ */
+ @Override
+ public Boolean quitClub(Long clubId, Long userId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, 1);
+ lqw.eq(StglMember::getRelationId, clubId);
+ lqw.eq(StglMember::getUserId, userId);
+ lqw.eq(StglMember::getStatus, 1);
+
+ StglMember member = memberMapper.selectOne(lqw);
+ if (member == null) {
+ throw new RuntimeException("您不是该社团的成员");
+ }
+
+ // 检查是否是社长或副社长
+ if (member.getRoleType() != null && member.getRoleType() <= 2) {
+ throw new RuntimeException("社长和副社长不能直接退出,请先转让职务");
+ }
+
+ member.setStatus(0);
+ return memberMapper.updateById(member) > 0;
+ }
+
+ /**
+ * 查询我参与的社团列表
+ *
+ * @param userId 用户ID
+ * @return 社团列表
+ */
+ @Override
+ public List queryMyClubs(Long userId) {
+ // 查询用户参与的社团ID
+ LambdaQueryWrapper memberLqw = Wrappers.lambdaQuery();
+ memberLqw.eq(StglMember::getMemberType, 1);
+ memberLqw.eq(StglMember::getUserId, userId);
+ memberLqw.eq(StglMember::getStatus, 1);
+ List members = memberMapper.selectList(memberLqw);
+
+ if (members.isEmpty()) {
+ return List.of();
+ }
+
+ List clubIds = members.stream().map(StglMember::getRelationId).collect(Collectors.toList());
+
+ // 查询社团信息
+ LambdaQueryWrapper clubLqw = Wrappers.lambdaQuery();
+ clubLqw.in(StglClub::getId, clubIds);
+ clubLqw.eq(StglClub::getStatus, 1);
+ clubLqw.orderByDesc(StglClub::getCreateTime);
+ List clubs = clubMapper.selectList(clubLqw);
+
+ return convertToRegisterVo(clubs);
+ }
+
+ /**
+ * 检查是否已报名某个社团
+ *
+ * @param clubId 社团ID
+ * @param userId 用户ID
+ * @return 是否已报名
+ */
+ @Override
+ public Boolean checkRegistered(Long clubId, Long userId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, 1);
+ lqw.eq(StglMember::getRelationId, clubId);
+ lqw.eq(StglMember::getUserId, userId);
+ lqw.eq(StglMember::getStatus, 1);
+ long count = memberMapper.selectCount(lqw);
+ return count > 0;
+ }
+
+ /**
+ * 根据社团类型查询推荐社团
+ *
+ * @param clubType 社团类型
+ * @param limit 推荐数量
+ * @return 推荐社团列表
+ */
+ @Override
+ public List queryRecommendClubs(String clubType, Integer limit) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getStatus, 1);
+ if (StringUtils.isNotBlank(clubType)) {
+ lqw.eq(StglClub::getClubType, clubType);
+ }
+ lqw.orderByDesc(StglClub::getCreateTime);
+ if (limit != null && limit > 0) {
+ lqw.last("LIMIT " + limit);
+ }
+
+ List clubs = clubMapper.selectList(lqw);
+ return convertToRegisterVo(clubs);
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java
new file mode 100644
index 00000000..b7098b14
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java
@@ -0,0 +1,233 @@
+package org.dromara.stgl.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.dromara.stgl.domain.bo.StglClubBo;
+import org.dromara.stgl.domain.vo.StglClubVo;
+import org.dromara.stgl.domain.StglClub;
+import org.dromara.stgl.mapper.StglClubMapper;
+import org.dromara.stgl.service.IStglClubService;
+import org.dromara.common.satoken.utils.LoginHelper;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 社团信息Service业务层处理
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@RequiredArgsConstructor
+@Service
+public class StglClubServiceImpl implements IStglClubService {
+
+ private final StglClubMapper baseMapper;
+
+ /**
+ * 查询社团信息
+ *
+ * @param id 主键
+ * @return 社团信息
+ */
+ @Override
+ public StglClubVo queryById(Long id){
+ return baseMapper.selectVoById(id);
+ }
+
+ /**
+ * 分页查询社团信息列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return 社团信息分页列表
+ */
+ @Override
+ public TableDataInfo queryPageList(StglClubBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ Page result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 查询符合条件的社团信息列表
+ *
+ * @param bo 查询条件
+ * @return 社团信息列表
+ */
+ @Override
+ public List queryList(StglClubBo bo) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(StglClubBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.like(StringUtils.isNotBlank(bo.getClubName()), StglClub::getClubName, bo.getClubName());
+ lqw.eq(StringUtils.isNotBlank(bo.getClubType()), StglClub::getClubType, bo.getClubType());
+ lqw.like(StringUtils.isNotBlank(bo.getPresidentName()), StglClub::getPresidentName, bo.getPresidentName());
+ lqw.like(StringUtils.isNotBlank(bo.getPresidentStudentNo()), StglClub::getPresidentStudentNo, bo.getPresidentStudentNo());
+ lqw.like(StringUtils.isNotBlank(bo.getAdvisorName()), StglClub::getAdvisorName, bo.getAdvisorName());
+ lqw.eq(bo.getStatus() != null, StglClub::getStatus, bo.getStatus());
+ lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
+ StglClub::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
+ lqw.orderByDesc(StglClub::getCreateTime);
+ return lqw;
+ }
+
+ /**
+ * 新增社团信息
+ *
+ * @param bo 社团信息
+ * @return 是否新增成功
+ */
+ @Override
+ public Boolean insertByBo(StglClubBo bo) {
+ StglClub add = MapstructUtils.convert(bo, StglClub.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setId(add.getId());
+ }
+ return flag;
+ }
+
+ /**
+ * 修改社团信息
+ *
+ * @param bo 社团信息
+ * @return 是否修改成功
+ */
+ @Override
+ public Boolean updateByBo(StglClubBo bo) {
+ StglClub update = MapstructUtils.convert(bo, StglClub.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 保存前的数据校验
+ */
+ private void validEntityBeforeSave(StglClub entity){
+ // 检查社团名称是否重复
+ if (StringUtils.isNotBlank(entity.getClubName())) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getClubName, entity.getClubName());
+ if (entity.getId() != null) {
+ lqw.ne(StglClub::getId, entity.getId());
+ }
+ long count = baseMapper.selectCount(lqw);
+ if (count > 0) {
+ throw new RuntimeException("社团名称已存在,请重新输入");
+ }
+ }
+
+ // 设置默认状态
+ if (entity.getStatus() == null) {
+ entity.setStatus(1);
+ }
+ }
+
+ /**
+ * 校验并批量删除社团信息
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ // 可以添加删除前的业务校验
+ }
+ return baseMapper.deleteBatchIds(ids) > 0;
+ }
+
+ /**
+ * 查询可用的社团选项
+ *
+ * @return 社团选项列表
+ */
+ @Override
+ public List queryAvailableOptions() {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getStatus, 1);
+ lqw.select(StglClub::getId, StglClub::getClubName, StglClub::getClubType);
+ lqw.orderByAsc(StglClub::getClubName);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 根据社团类型查询社团列表
+ *
+ * @param clubType 社团类型
+ * @return 社团列表
+ */
+ @Override
+ public List queryByClubType(String clubType) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getClubType, clubType);
+ lqw.eq(StglClub::getStatus, 1);
+ lqw.orderByAsc(StglClub::getClubName);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 查询我管理的社团列表
+ *
+ * @return 社团列表
+ */
+ @Override
+ public List queryMyClubs() {
+ // 根据当前用户查询其管理的社团
+ Long userId = LoginHelper.getUserId();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getCreateBy, userId);
+ lqw.eq(StglClub::getStatus, 1);
+ lqw.orderByDesc(StglClub::getCreateTime);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 检查社团名称是否重复
+ *
+ * @param clubName 社团名称
+ * @param excludeId 排除的ID(修改时使用)
+ * @return 是否重复
+ */
+ @Override
+ public Boolean checkClubNameUnique(String clubName, Long excludeId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglClub::getClubName, clubName);
+ if (excludeId != null) {
+ lqw.ne(StglClub::getId, excludeId);
+ }
+ long count = baseMapper.selectCount(lqw);
+ return count == 0;
+ }
+
+ /**
+ * 启用/禁用社团
+ *
+ * @param id 社团ID
+ * @param status 状态
+ * @return 是否操作成功
+ */
+ @Override
+ public Boolean changeStatus(Long id, Integer status) {
+ StglClub club = new StglClub();
+ club.setId(id);
+ club.setStatus(status);
+ return baseMapper.updateById(club) > 0;
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglMemberServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglMemberServiceImpl.java
new file mode 100644
index 00000000..e6001ae7
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglMemberServiceImpl.java
@@ -0,0 +1,272 @@
+package org.dromara.stgl.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.dromara.stgl.domain.bo.StglMemberBo;
+import org.dromara.stgl.domain.vo.StglMemberVo;
+import org.dromara.stgl.domain.StglMember;
+import org.dromara.stgl.mapper.StglMemberMapper;
+import org.dromara.stgl.service.IStglMemberService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+import java.util.Date;
+import java.util.stream.Collectors;
+
+/**
+ * 社团成员Service业务层处理
+ *
+ * @author Cursor AI
+ * @date 2025-01-15
+ */
+@RequiredArgsConstructor
+@Service
+public class StglMemberServiceImpl implements IStglMemberService {
+
+ private final StglMemberMapper baseMapper;
+
+ /**
+ * 查询社团成员
+ *
+ * @param id 主键
+ * @return 社团成员
+ */
+ @Override
+ public StglMemberVo queryById(Long id){
+ return baseMapper.selectVoById(id);
+ }
+
+ /**
+ * 分页查询社团成员列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return 社团成员分页列表
+ */
+ @Override
+ public TableDataInfo queryPageList(StglMemberBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ Page result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 查询符合条件的社团成员列表
+ *
+ * @param bo 查询条件
+ * @return 社团成员列表
+ */
+ @Override
+ public List queryList(StglMemberBo bo) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(StglMemberBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(bo.getMemberType() != null, StglMember::getMemberType, bo.getMemberType());
+ lqw.eq(bo.getRelationId() != null, StglMember::getRelationId, bo.getRelationId());
+ lqw.eq(bo.getUserId() != null, StglMember::getUserId, bo.getUserId());
+ lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), StglMember::getStudentNo, bo.getStudentNo());
+ lqw.like(StringUtils.isNotBlank(bo.getStudentName()), StglMember::getStudentName, bo.getStudentName());
+ lqw.eq(bo.getRoleType() != null, StglMember::getRoleType, bo.getRoleType());
+ lqw.eq(bo.getStatus() != null, StglMember::getStatus, bo.getStatus());
+ lqw.between(params.get("beginJoinTime") != null && params.get("endJoinTime") != null,
+ StglMember::getJoinTime, params.get("beginJoinTime"), params.get("endJoinTime"));
+ lqw.orderByDesc(StglMember::getJoinTime);
+ return lqw;
+ }
+
+ /**
+ * 新增社团成员
+ *
+ * @param bo 社团成员
+ * @return 是否新增成功
+ */
+ @Override
+ public Boolean insertByBo(StglMemberBo bo) {
+ StglMember add = MapstructUtils.convert(bo, StglMember.class);
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setId(add.getId());
+ }
+ return flag;
+ }
+
+ /**
+ * 修改社团成员
+ *
+ * @param bo 社团成员
+ * @return 是否修改成功
+ */
+ @Override
+ public Boolean updateByBo(StglMemberBo bo) {
+ StglMember update = MapstructUtils.convert(bo, StglMember.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 保存前的数据校验
+ */
+ private void validEntityBeforeSave(StglMember entity){
+ // 检查用户是否已是成员
+ if (entity.getMemberType() != null && entity.getRelationId() != null && entity.getUserId() != null) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, entity.getMemberType());
+ lqw.eq(StglMember::getRelationId, entity.getRelationId());
+ lqw.eq(StglMember::getUserId, entity.getUserId());
+ if (entity.getId() != null) {
+ lqw.ne(StglMember::getId, entity.getId());
+ }
+ long count = baseMapper.selectCount(lqw);
+ if (count > 0) {
+ throw new RuntimeException("用户已是该组织的成员,不能重复添加");
+ }
+ }
+
+ // 设置默认值
+ if (entity.getJoinTime() == null) {
+ entity.setJoinTime(new Date());
+ }
+ if (entity.getStatus() == null) {
+ entity.setStatus(1);
+ }
+ if (entity.getRoleType() == null) {
+ entity.setRoleType(3); // 默认为普通成员
+ }
+ }
+
+ /**
+ * 校验并批量删除社团成员
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ // 可以添加删除前的业务校验
+ }
+ return baseMapper.deleteBatchIds(ids) > 0;
+ }
+
+ /**
+ * 根据社团ID查询成员列表
+ *
+ * @param clubId 社团ID
+ * @return 成员列表
+ */
+ @Override
+ public List queryByClubId(Long clubId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, 1); // 社团成员
+ lqw.eq(StglMember::getRelationId, clubId);
+ lqw.eq(StglMember::getStatus, 1);
+ lqw.orderByAsc(StglMember::getRoleType).orderByAsc(StglMember::getJoinTime);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 根据用户ID查询参与的社团列表
+ *
+ * @param userId 用户ID
+ * @return 社团成员关系列表
+ */
+ @Override
+ public List queryByUserId(Long userId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getUserId, userId);
+ lqw.eq(StglMember::getStatus, 1);
+ lqw.orderByDesc(StglMember::getJoinTime);
+ return baseMapper.selectVoList(lqw);
+ }
+
+ /**
+ * 检查用户是否已是某个社团/队伍的成员
+ *
+ * @param memberType 成员类型
+ * @param relationId 关联ID
+ * @param userId 用户ID
+ * @return 是否已是成员
+ */
+ @Override
+ public Boolean checkMemberExists(Integer memberType, Long relationId, Long userId) {
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(StglMember::getMemberType, memberType);
+ lqw.eq(StglMember::getRelationId, relationId);
+ lqw.eq(StglMember::getUserId, userId);
+ lqw.eq(StglMember::getStatus, 1);
+ long count = baseMapper.selectCount(lqw);
+ return count > 0;
+ }
+
+ /**
+ * 批量添加成员
+ *
+ * @param memberType 成员类型
+ * @param relationId 关联ID
+ * @param userIds 用户ID列表
+ * @param roleType 角色类型
+ * @return 是否添加成功
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean batchAddMembers(Integer memberType, Long relationId, List userIds, Integer roleType) {
+ Date now = new Date();
+ List members = userIds.stream().map(userId -> {
+ StglMember member = new StglMember();
+ member.setMemberType(memberType);
+ member.setRelationId(relationId);
+ member.setUserId(userId);
+ member.setRoleType(roleType != null ? roleType : 3);
+ member.setJoinTime(now);
+ member.setStatus(1);
+ return member;
+ }).collect(Collectors.toList());
+
+ return baseMapper.insertBatch(members);
+ }
+
+ /**
+ * 更新成员角色
+ *
+ * @param id 成员ID
+ * @param roleType 角色类型
+ * @return 是否更新成功
+ */
+ @Override
+ public Boolean updateMemberRole(Long id, Integer roleType) {
+ StglMember member = new StglMember();
+ member.setId(id);
+ member.setRoleType(roleType);
+ return baseMapper.updateById(member) > 0;
+ }
+
+ /**
+ * 退出社团/队伍
+ *
+ * @param id 成员ID
+ * @return 是否退出成功
+ */
+ @Override
+ public Boolean quitMember(Long id) {
+ StglMember member = new StglMember();
+ member.setId(id);
+ member.setStatus(0);
+ return baseMapper.updateById(member) > 0;
+ }
+
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-xljk/pom.xml b/backend/ruoyi-modules/ruoyi-xljk/pom.xml
index cc47738b..4d56485f 100644
--- a/backend/ruoyi-modules/ruoyi-xljk/pom.xml
+++ b/backend/ruoyi-modules/ruoyi-xljk/pom.xml
@@ -39,6 +39,12 @@
org.dromara
ruoyi-common-excel
+
+
+ org.apache.poi
+ poi-ooxml
+ 5.2.3
+
@@ -76,6 +82,18 @@
ruoyi-common-translation
+
+
+ org.dromara
+ ruoyi-common-satoken
+
+
+
+
+ org.dromara
+ ruoyi-system
+
+
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationAppointmentController.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationAppointmentController.java
new file mode 100644
index 00000000..d9657177
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/controller/XljkConsultationAppointmentController.java
@@ -0,0 +1,366 @@
+package org.dromara.xljk.controller;
+
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.xljk.domain.bo.XljkConsultationAppointmentBo;
+import org.dromara.xljk.domain.vo.XljkConsultationAppointmentVo;
+import org.dromara.xljk.domain.vo.TimeSlotDetailVo;
+import org.dromara.xljk.service.IXljkConsultationAppointmentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RequestMethod;
+import jakarta.servlet.http.HttpServletResponse;
+
+@RestController
+@RequestMapping("/api/xljk/consultation/appointment")
+public class XljkConsultationAppointmentController extends BaseController {
+ private static final Logger log = LoggerFactory.getLogger(XljkConsultationAppointmentController.class);
+
+ @Autowired
+ private IXljkConsultationAppointmentService service;
+
+ @GetMapping("/list")
+ public TableDataInfo list(XljkConsultationAppointmentBo bo, PageQuery pageQuery) {
+ return service.selectPageList(bo, pageQuery);
+ }
+
+ @GetMapping("/{id}")
+ public R get(@PathVariable Long id) {
+ log.info("查询预约记录详情,ID: {}", id);
+ XljkConsultationAppointmentVo vo = service.selectById(id);
+ log.info("查询结果: {}", vo);
+ if (vo == null) {
+ log.warn("未找到ID为{}的预约记录", id);
+ return R.fail("未找到预约记录");
+ }
+ return R.ok(vo);
+ }
+
+ @PostMapping
+ public R add(@RequestBody XljkConsultationAppointmentBo bo) {
+ boolean result = service.insert(bo);
+ if (result) {
+ return R.ok(true);
+ } else {
+ return R.fail("新增失败");
+ }
+ }
+
+ @PutMapping
+ public R edit(@RequestBody XljkConsultationAppointmentBo bo) {
+ boolean result = service.update(bo);
+ if (result) {
+ return R.ok(true);
+ } else {
+ return R.fail("更新失败,预约记录不存在或已被修改");
+ }
+ }
+
+ @DeleteMapping("/{id}")
+ public R remove(@PathVariable Long id) {
+ boolean result = service.deleteById(id);
+ if (result) {
+ return R.ok(true);
+ } else {
+ return R.fail("删除失败,预约记录不存在");
+ }
+ }
+
+ /**
+ * 获取可预约时段列表(用于日历视图)
+ */
+ @GetMapping("/available-slots")
+ public R>> getAvailableTimeSlots(
+ @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+ @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
+ List