From ea65b55332915f140448644e83f18a4111ebdbaa Mon Sep 17 00:00:00 2001 From: zhonghua Date: Thu, 2 Apr 2026 18:30:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A1=A5=E9=BD=90=E4=BD=9C=E5=93=81?= =?UTF-8?q?=E7=A7=9F=E6=88=B7=E9=9A=94=E7=A6=BB=E4=B8=8E=E7=A7=9F=E6=88=B7?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E6=A0=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 作品列表/统计补齐 validState 与租户条件,关键字支持报名/队伍信息匹配 - 新增租户菜单树接口与服务实现,结构对齐用户菜单树 - t_biz_contest_work 增加 deleted 字段,补充 flyway 迁移与启动时轻量修复 Made-with: Cursor --- .../config/ContestWorkSchemaRepair.java | 49 +++++++ .../controller/ContestWorkController.java | 4 +- .../contest/service/IContestWorkService.java | 2 +- .../service/impl/ContestWorkServiceImpl.java | 120 +++++++++++++++--- .../sys/controller/SysTenantController.java | 27 ++++ .../modules/sys/service/ISysMenuService.java | 5 + .../sys/service/impl/SysMenuServiceImpl.java | 28 ++++ backend-java/src/main/resources/db/init.sql | 3 +- .../V1__add_deleted_to_t_biz_contest_work.sql | 21 +++ docs/legacy/TENANT_IMPLEMENTATION.md | 2 +- frontend/src/views/system/roles/Index.vue | 3 +- frontend/src/views/system/users/Index.vue | 85 ++++--------- 12 files changed, 266 insertions(+), 83 deletions(-) create mode 100644 backend-java/src/main/java/com/competition/common/config/ContestWorkSchemaRepair.java create mode 100644 backend-java/src/main/resources/db/migration/V1__add_deleted_to_t_biz_contest_work.sql diff --git a/backend-java/src/main/java/com/competition/common/config/ContestWorkSchemaRepair.java b/backend-java/src/main/java/com/competition/common/config/ContestWorkSchemaRepair.java new file mode 100644 index 0000000..d706d6a --- /dev/null +++ b/backend-java/src/main/java/com/competition/common/config/ContestWorkSchemaRepair.java @@ -0,0 +1,49 @@ +package com.competition.common.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +/** + * 数据库启动时轻量修复: + * 由于本项目部分环境可能没有跑过最新的 init.sql, + * 导致 `t_biz_contest_work` 缺少 `deleted` 字段,从而 MyBatis-Plus 逻辑删除查询报 500。 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ContestWorkSchemaRepair implements ApplicationRunner { + + private final JdbcTemplate jdbcTemplate; + + @Override + public void run(ApplicationArguments args) { + try { + Integer columnCnt = jdbcTemplate.queryForObject( + "SELECT COUNT(*) " + + "FROM information_schema.columns " + + "WHERE table_schema = DATABASE() " + + "AND table_name = 't_biz_contest_work' " + + "AND column_name = 'deleted'", + Integer.class + ); + + if (columnCnt == null || columnCnt == 0) { + log.warn("检测到表 `t_biz_contest_work` 缺少字段 `deleted`,尝试补齐..."); + jdbcTemplate.execute( + "ALTER TABLE t_biz_contest_work " + + "ADD COLUMN deleted tinyint NOT NULL DEFAULT '0' " + + "COMMENT '逻辑删除:0-未删除,1-已删除'" + ); + log.info("补齐字段 `t_biz_contest_work.deleted` 完成"); + } + } catch (Exception e) { + // 不阻断启动,给调用方避免 500 需要依赖数据库权限/执行成功 + log.warn("补齐 `t_biz_contest_work.deleted` 失败:{}", e.getMessage()); + } + } +} + diff --git a/backend-java/src/main/java/com/competition/modules/biz/contest/controller/ContestWorkController.java b/backend-java/src/main/java/com/competition/modules/biz/contest/controller/ContestWorkController.java index 9166846..f94ddab 100644 --- a/backend-java/src/main/java/com/competition/modules/biz/contest/controller/ContestWorkController.java +++ b/backend-java/src/main/java/com/competition/modules/biz/contest/controller/ContestWorkController.java @@ -37,7 +37,9 @@ public class ContestWorkController { @RequirePermission("work:read") @Operation(summary = "获取作品统计") public Result> getStats(@RequestParam(required = false) Long contestId) { - return Result.success(workService.getStats(contestId, SecurityUtil.getCurrentTenantId())); + return Result.success( + workService.getStats(contestId, SecurityUtil.getCurrentTenantId(), SecurityUtil.isSuperAdmin()) + ); } @GetMapping diff --git a/backend-java/src/main/java/com/competition/modules/biz/contest/service/IContestWorkService.java b/backend-java/src/main/java/com/competition/modules/biz/contest/service/IContestWorkService.java index 1a8e73f..3d4f9cf 100644 --- a/backend-java/src/main/java/com/competition/modules/biz/contest/service/IContestWorkService.java +++ b/backend-java/src/main/java/com/competition/modules/biz/contest/service/IContestWorkService.java @@ -15,7 +15,7 @@ public interface IContestWorkService extends IService { PageResult> findAll(QueryWorkDto dto, Long tenantId, boolean isSuperTenant); - Map getStats(Long contestId, Long tenantId); + Map getStats(Long contestId, Long tenantId, boolean isSuperTenant); Map findDetail(Long id); diff --git a/backend-java/src/main/java/com/competition/modules/biz/contest/service/impl/ContestWorkServiceImpl.java b/backend-java/src/main/java/com/competition/modules/biz/contest/service/impl/ContestWorkServiceImpl.java index 50d9c9f..6452f8f 100644 --- a/backend-java/src/main/java/com/competition/modules/biz/contest/service/impl/ContestWorkServiceImpl.java +++ b/backend-java/src/main/java/com/competition/modules/biz/contest/service/impl/ContestWorkServiceImpl.java @@ -160,9 +160,44 @@ public class ContestWorkServiceImpl extends ServiceImpl keywordRegistrationIds = Collections.emptySet(); if (StringUtils.hasText(dto.getKeyword())) { - wrapper.and(w -> w.like(BizContestWork::getTitle, dto.getKeyword()) - .or().like(BizContestWork::getWorkNo, dto.getKeyword())); + String keyword = dto.getKeyword(); + + // keyword 命中口径:作品编号(work_no)/作者姓名(account_name)/报名账号(account_no)/队伍名称(team_name) + LambdaQueryWrapper regWrapper = new LambdaQueryWrapper<>(); + if (dto.getContestId() != null) { + regWrapper.eq(BizContestRegistration::getContestId, dto.getContestId()); + } + regWrapper.eq(BizContestRegistration::getValidState, 1); + + if (dto.getTenantId() != null) { + regWrapper.eq(BizContestRegistration::getTenantId, dto.getTenantId()); + } else if (!isSuperTenant && tenantId != null) { + regWrapper.eq(BizContestRegistration::getTenantId, tenantId); + } + + regWrapper.and(w -> w.like(BizContestRegistration::getAccountNo, keyword) + .or().like(BizContestRegistration::getAccountName, keyword) + .or().like(BizContestRegistration::getTeamName, keyword)); + + List regs = contestRegistrationMapper.selectList(regWrapper); + keywordRegistrationIds = regs.stream() + .map(BizContestRegistration::getId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + if (!keywordRegistrationIds.isEmpty()) { + // 使用 apply 拼接 IN 条件,避免当前 MyBatis-Plus LambdaQueryWrapper 的 + // or().in() 链式泛型推断导致的编译失败。 + String registrationIdIn = keywordRegistrationIds.stream() + .map(String::valueOf) + .collect(Collectors.joining(",")); + wrapper.and(w -> w.like(BizContestWork::getWorkNo, keyword) + .or().apply("registration_id IN (" + registrationIdIn + ")")); + } else { + wrapper.and(w -> w.like(BizContestWork::getWorkNo, keyword)); + } } if (StringUtils.hasText(dto.getSubmitStartTime())) { wrapper.ge(BizContestWork::getSubmitTime, @@ -175,17 +210,23 @@ public class ContestWorkServiceImpl extends ServiceImpl page = new Page<>(dto.getPage(), dto.getPageSize()); Page result = contestWorkMapper.selectPage(page, wrapper); - // 批量查询报名信息 + // 批量查询报名/赛事信息 Set registrationIds = result.getRecords().stream() .map(BizContestWork::getRegistrationId) .filter(Objects::nonNull) .collect(Collectors.toSet()); + Set contestIds = result.getRecords().stream() + .map(BizContestWork::getContestId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + Map registrationMap = new HashMap<>(); if (!registrationIds.isEmpty()) { List registrations = contestRegistrationMapper.selectBatchIds(registrationIds); @@ -193,12 +234,47 @@ public class ContestWorkServiceImpl extends ServiceImpl r)); } + Map contestMap = new HashMap<>(); + if (!contestIds.isEmpty()) { + List contests = contestMapper.selectBatchIds(contestIds); + contestMap = contests.stream() + .collect(Collectors.toMap(BizContest::getId, c -> c)); + } + Map finalRegistrationMap = registrationMap; + Map finalContestMap = contestMap; List> voList = result.getRecords().stream() .map(work -> { Map map = workToMap(work); + + BizContest contest = finalContestMap.get(work.getContestId()); + if (contest != null) { + Map contestVo = new LinkedHashMap<>(); + contestVo.put("id", contest.getId()); + contestVo.put("contestName", contest.getContestName()); + map.put("contest", contestVo); + } + BizContestRegistration reg = finalRegistrationMap.get(work.getRegistrationId()); if (reg != null) { + Map userVo = new LinkedHashMap<>(); + userVo.put("id", reg.getUserId()); + userVo.put("username", reg.getAccountNo()); + userVo.put("nickname", reg.getAccountName()); + + Map regVo = new LinkedHashMap<>(); + regVo.put("id", reg.getId()); + regVo.put("user", userVo); + + if (StringUtils.hasText(reg.getTeamName()) || reg.getTeamId() != null) { + Map teamVo = new LinkedHashMap<>(); + teamVo.put("teamName", reg.getTeamName()); + regVo.put("team", teamVo); + } + + map.put("registration", regVo); + + // 兼容旧字段:保留扁平账号信息 map.put("accountNo", reg.getAccountNo()); map.put("accountName", reg.getAccountName()); map.put("userId", reg.getUserId()); @@ -211,7 +287,7 @@ public class ContestWorkServiceImpl extends ServiceImpl getStats(Long contestId, Long tenantId) { + public Map getStats(Long contestId, Long tenantId, boolean isSuperTenant) { log.info("获取作品统计,赛事ID:{}", contestId); LambdaQueryWrapper baseWrapper = new LambdaQueryWrapper<>(); @@ -219,6 +295,10 @@ public class ContestWorkServiceImpl extends ServiceImpl submittedWrapper = new LambdaQueryWrapper<>(); @@ -226,6 +306,10 @@ public class ContestWorkServiceImpl extends ServiceImpl acceptedWrapper = new LambdaQueryWrapper<>(); + LambdaQueryWrapper reviewedWrapper = new LambdaQueryWrapper<>(); if (contestId != null) { - acceptedWrapper.eq(BizContestWork::getContestId, contestId); + reviewedWrapper.eq(BizContestWork::getContestId, contestId); } - acceptedWrapper.eq(BizContestWork::getIsLatest, true); - acceptedWrapper.eq(BizContestWork::getStatus, "accepted"); - long accepted = count(acceptedWrapper); - - LambdaQueryWrapper rejectedWrapper = new LambdaQueryWrapper<>(); - if (contestId != null) { - rejectedWrapper.eq(BizContestWork::getContestId, contestId); + reviewedWrapper.eq(BizContestWork::getIsLatest, true); + reviewedWrapper.eq(BizContestWork::getValidState, 1); + if (!isSuperTenant && tenantId != null) { + reviewedWrapper.eq(BizContestWork::getTenantId, tenantId); } - rejectedWrapper.eq(BizContestWork::getIsLatest, true); - rejectedWrapper.eq(BizContestWork::getStatus, "rejected"); - long rejected = count(rejectedWrapper); + // 已评完口径:兼容 accepted/awarded 两种结果状态 + reviewedWrapper.in(BizContestWork::getStatus, Arrays.asList("accepted", "awarded")); + long reviewed = count(reviewedWrapper); Map stats = new LinkedHashMap<>(); stats.put("total", total); stats.put("submitted", submitted); stats.put("reviewing", reviewing); - stats.put("accepted", accepted); - stats.put("rejected", rejected); + stats.put("reviewed", reviewed); return stats; } diff --git a/backend-java/src/main/java/com/competition/modules/sys/controller/SysTenantController.java b/backend-java/src/main/java/com/competition/modules/sys/controller/SysTenantController.java index fd122b6..4b57d27 100644 --- a/backend-java/src/main/java/com/competition/modules/sys/controller/SysTenantController.java +++ b/backend-java/src/main/java/com/competition/modules/sys/controller/SysTenantController.java @@ -2,10 +2,14 @@ package com.competition.modules.sys.controller; import com.competition.common.result.PageResult; import com.competition.common.result.Result; +import com.competition.common.exception.BusinessException; +import com.competition.common.enums.ErrorCode; import com.competition.common.util.SecurityUtil; import com.competition.modules.sys.dto.CreateTenantDto; import com.competition.modules.sys.dto.UpdateTenantDto; +import com.competition.modules.sys.entity.SysMenu; import com.competition.modules.sys.entity.SysTenant; +import com.competition.modules.sys.service.ISysMenuService; import com.competition.modules.sys.service.ISysTenantService; import com.competition.security.annotation.RequirePermission; import io.swagger.v3.oas.annotations.Operation; @@ -15,6 +19,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.Map; +import java.util.List; +import java.util.Objects; @Tag(name = "租户管理") @RestController @@ -23,6 +29,7 @@ import java.util.Map; public class SysTenantController { private final ISysTenantService tenantService; + private final ISysMenuService menuService; @PostMapping @RequirePermission("tenant:create") @@ -90,4 +97,24 @@ public class SysTenantController { tenantService.removeTenant(id, currentTenantId); return Result.success(); } + + @GetMapping("/{id}/menus") + @RequirePermission("tenant:read") + @Operation(summary = "获取租户菜单树") + public Result> getTenantMenus(@PathVariable Long id) { + Long currentTenantId = SecurityUtil.getCurrentTenantId(); + boolean isSuperAdmin = SecurityUtil.isSuperAdmin(); + + // 非超管只能查询自身租户 + if (!isSuperAdmin && !Objects.equals(id, currentTenantId)) { + throw BusinessException.of(ErrorCode.FORBIDDEN, "只能查询当前租户菜单"); + } + + // 校验租户是否存在 + if (tenantService.getById(id) == null) { + throw BusinessException.of(ErrorCode.NOT_FOUND, "租户不存在"); + } + + return Result.success(menuService.getTenantMenus(id)); + } } diff --git a/backend-java/src/main/java/com/competition/modules/sys/service/ISysMenuService.java b/backend-java/src/main/java/com/competition/modules/sys/service/ISysMenuService.java index a3c0d64..0ee258e 100644 --- a/backend-java/src/main/java/com/competition/modules/sys/service/ISysMenuService.java +++ b/backend-java/src/main/java/com/competition/modules/sys/service/ISysMenuService.java @@ -12,6 +12,11 @@ public interface ISysMenuService extends IService { List findAllTree(); + /** + * 获取指定租户可用的菜单树(基于 t_sys_tenant_menu 分配) + */ + List getTenantMenus(Long tenantId); + List getUserMenus(Long userId, Long tenantId, boolean isSuperAdmin); SysMenu findDetail(Long id); diff --git a/backend-java/src/main/java/com/competition/modules/sys/service/impl/SysMenuServiceImpl.java b/backend-java/src/main/java/com/competition/modules/sys/service/impl/SysMenuServiceImpl.java index a8e51fa..e9e1182 100644 --- a/backend-java/src/main/java/com/competition/modules/sys/service/impl/SysMenuServiceImpl.java +++ b/backend-java/src/main/java/com/competition/modules/sys/service/impl/SysMenuServiceImpl.java @@ -95,6 +95,34 @@ public class SysMenuServiceImpl extends ServiceImpl impl return buildTree(filteredMenus, null); } + @Override + public List getTenantMenus(Long tenantId) { + if (tenantId == null) return Collections.emptyList(); + + // 获取所有有效菜单 + List allMenus = list(new LambdaQueryWrapper() + .eq(SysMenu::getValidState, 1) + .orderByAsc(SysMenu::getSort)); + + // 获取租户已分配的菜单 ID + List tenantMenus = tenantMenuMapper.selectList( + new LambdaQueryWrapper().eq(SysTenantMenu::getTenantId, tenantId)); + Set tenantMenuIds = tenantMenus.stream().map(SysTenantMenu::getMenuId).collect(Collectors.toSet()); + + // 过滤出租户可用的菜单 + List filteredMenus = allMenus.stream() + .filter(menu -> tenantMenuIds.contains(menu.getId())) + .collect(Collectors.toList()); + + // 补全父菜单(确保树结构完整) + Set filteredIds = filteredMenus.stream().map(SysMenu::getId).collect(Collectors.toSet()); + for (SysMenu menu : new ArrayList<>(filteredMenus)) { + addParentsIfMissing(menu, allMenus, filteredMenus, filteredIds); + } + + return buildTree(filteredMenus, null); + } + @Override public SysMenu findDetail(Long id) { SysMenu menu = getById(id); diff --git a/backend-java/src/main/resources/db/init.sql b/backend-java/src/main/resources/db/init.sql index 3a73e45..8a29b41 100644 --- a/backend-java/src/main/resources/db/init.sql +++ b/backend-java/src/main/resources/db/init.sql @@ -568,6 +568,7 @@ CREATE TABLE `t_biz_contest_work` ( `user_work_id` int DEFAULT NULL, `create_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人账号', `update_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人账号', + `deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除:0-未删除,1-已删除', PRIMARY KEY (`id`), UNIQUE KEY `t_contest_work_work_no_key` (`work_no`), KEY `t_contest_work_tenant_id_contest_id_is_latest_idx` (`tenant_id`,`contest_id`,`is_latest`), @@ -579,7 +580,7 @@ CREATE TABLE `t_biz_contest_work` ( KEY `t_contest_work_user_work_id_fkey` (`user_work_id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `t_biz_contest_work` VALUES (1,9,4,4,NULL,'森林里的音乐会','小动物们在森林里举办了一场盛大的音乐会',NULL,1,1,'awarded','2026-03-18 10:00:00.000',12,NULL,'student','https://picsum.photos/seed/work-c1-1/400/533',NULL,12,NULL,'2026-03-31 16:23:06.000','2026-03-31 11:43:55.394',1,'一等奖','一等奖',NULL,90.33,NULL,1,NULL,NULL,NULL),(2,9,4,5,NULL,'海边的贝壳梦','小女孩在海边捡到了一个会说话的贝壳',NULL,1,1,'awarded','2026-03-20 14:00:00.000',13,NULL,'student','https://picsum.photos/seed/work-c1-2/400/533',NULL,13,NULL,'2026-03-31 16:23:06.000','2026-03-31 11:43:55.401',1,'三等奖','三等奖',NULL,79.00,NULL,3,NULL,NULL,NULL),(3,9,4,6,NULL,'云端上的图书馆','一座建在云朵上的神奇图书馆',NULL,1,1,'awarded','2026-03-22 16:30:00.000',14,NULL,'student','https://picsum.photos/seed/work-c1-3/400/533',NULL,14,NULL,'2026-03-31 16:23:06.000','2026-03-31 11:43:55.398',1,'二等奖','二等奖',NULL,85.33,NULL,2,NULL,NULL,NULL),(4,9,5,9,NULL,'和妈妈一起画星星','记录了和妈妈一起画画的温馨夜晚',NULL,1,1,'submitted','2026-03-05 10:00:00.000',12,NULL,'student','https://picsum.photos/seed/work-c2-1/400/533',NULL,12,NULL,'2026-03-31 16:23:06.000','2026-03-31 16:23:06.000',1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),(5,9,5,10,NULL,'爸爸的大手','用绘画记录爸爸温暖的大手牵着我的小手',NULL,1,1,'submitted','2026-03-08 11:30:00.000',13,NULL,'student','https://picsum.photos/seed/work-c2-2/400/533',NULL,13,NULL,'2026-03-31 16:23:06.000','2026-03-31 16:23:06.000',1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),(6,9,5,11,NULL,'全家福的故事','一幅全家福背后的温暖故事',NULL,1,1,'submitted','2026-03-10 09:00:00.000',14,NULL,'student','https://picsum.photos/seed/work-c2-3/400/533',NULL,14,NULL,'2026-03-31 16:23:06.000','2026-03-31 16:23:06.000',1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),(7,9,6,13,NULL,'我的寒假阅读日记','记录了寒假30天的阅读旅程',NULL,1,1,'accepted','2026-02-10 10:00:00.000',12,NULL,'student','https://picsum.photos/seed/work-c3-1/400/533',NULL,12,NULL,'2026-03-31 16:23:06.000','2026-03-31 16:23:06.000',1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),(8,9,6,14,NULL,'绘本里的四季','用绘画展现四季的变化',NULL,1,1,'accepted','2026-02-12 15:00:00.000',13,NULL,'student','https://picsum.photos/seed/work-c3-2/400/533',NULL,13,NULL,'2026-03-31 16:23:06.000','2026-03-31 16:23:06.000',1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO `t_biz_contest_work` (id,tenant_id,contest_id,registration_id,work_no,title,description,files,version,is_latest,status,submit_time,submitter_user_id,submitter_account_no,submit_source,preview_url,ai_model_meta,creator,modifier,create_time,modify_time,valid_state,award_level,award_name,certificate_url,final_score,preview_urls,rank,user_work_id,create_by,update_by) VALUES (1,9,4,4,NULL,'森林里的音乐会','小动物们在森林里举办了一场盛大的音乐会',NULL,1,1,'awarded','2026-03-18 10:00:00.000',12,NULL,'student','https://picsum.photos/seed/work-c1-1/400/533',NULL,12,NULL,'2026-03-31 16:23:06.000','2026-03-31 11:43:55.394',1,'一等奖','一等奖',NULL,90.33,NULL,1,NULL,NULL,NULL),(2,9,4,5,NULL,'海边的贝壳梦','小女孩在海边捡到了一个会说话的贝壳',NULL,1,1,'awarded','2026-03-20 14:00:00.000',13,NULL,'student','https://picsum.photos/seed/work-c1-2/400/533',NULL,13,NULL,'2026-03-31 16:23:06.000','2026-03-31 11:43:55.401',1,'三等奖','三等奖',NULL,79.00,NULL,3,NULL,NULL,NULL),(3,9,4,6,NULL,'云端上的图书馆','一座建在云朵上的神奇图书馆',NULL,1,1,'awarded','2026-03-22 16:30:00.000',14,NULL,'student','https://picsum.photos/seed/work-c1-3/400/533',NULL,14,NULL,'2026-03-31 16:23:06.000','2026-03-31 11:43:55.398',1,'二等奖','二等奖',NULL,85.33,NULL,2,NULL,NULL,NULL),(4,9,5,9,NULL,'和妈妈一起画星星','记录了和妈妈一起画画的温馨夜晚',NULL,1,1,'submitted','2026-03-05 10:00:00.000',12,NULL,'student','https://picsum.photos/seed/work-c2-1/400/533',NULL,12,NULL,'2026-03-31 16:23:06.000','2026-03-31 16:23:06.000',1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),(5,9,5,10,NULL,'爸爸的大手','用绘画记录爸爸温暖的大手牵着我的小手',NULL,1,1,'submitted','2026-03-08 11:30:00.000',13,NULL,'student','https://picsum.photos/seed/work-c2-2/400/533',NULL,13,NULL,'2026-03-31 16:23:06.000','2026-03-31 16:23:06.000',1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),(6,9,5,11,NULL,'全家福的故事','一幅全家福背后的温暖故事',NULL,1,1,'submitted','2026-03-10 09:00:00.000',14,NULL,'student','https://picsum.photos/seed/work-c2-3/400/533',NULL,14,NULL,'2026-03-31 16:23:06.000','2026-03-31 16:23:06.000',1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),(7,9,6,13,NULL,'我的寒假阅读日记','记录了寒假30天的阅读旅程',NULL,1,1,'accepted','2026-02-10 10:00:00.000',12,NULL,'student','https://picsum.photos/seed/work-c3-1/400/533',NULL,12,NULL,'2026-03-31 16:23:06.000','2026-03-31 16:23:06.000',1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),(8,9,6,14,NULL,'绘本里的四季','用绘画展现四季的变化',NULL,1,1,'accepted','2026-02-12 15:00:00.000',13,NULL,'student','https://picsum.photos/seed/work-c3-2/400/533',NULL,13,NULL,'2026-03-31 16:23:06.000','2026-03-31 16:23:06.000',1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `t_biz_contest_work_attachment` ( diff --git a/backend-java/src/main/resources/db/migration/V1__add_deleted_to_t_biz_contest_work.sql b/backend-java/src/main/resources/db/migration/V1__add_deleted_to_t_biz_contest_work.sql new file mode 100644 index 0000000..e775554 --- /dev/null +++ b/backend-java/src/main/resources/db/migration/V1__add_deleted_to_t_biz_contest_work.sql @@ -0,0 +1,21 @@ +-- Flyway migration: +-- 兼容部分环境没有跑过最新的 init.sql, +-- 导致 t_biz_contest_work 缺少 deleted 字段,从而逻辑删除查询报错。 +SET @column_cnt := ( + SELECT COUNT(*) + FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = 't_biz_contest_work' + AND column_name = 'deleted' +); + +SET @sql := IF( + @column_cnt = 0, + 'ALTER TABLE t_biz_contest_work ADD COLUMN deleted tinyint NOT NULL DEFAULT ''0'' COMMENT ''逻辑删除:0-未删除,1-已删除''', + 'SELECT 1' +); + +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + diff --git a/docs/legacy/TENANT_IMPLEMENTATION.md b/docs/legacy/TENANT_IMPLEMENTATION.md index 973fab2..ca00689 100644 --- a/docs/legacy/TENANT_IMPLEMENTATION.md +++ b/docs/legacy/TENANT_IMPLEMENTATION.md @@ -145,7 +145,7 @@ const users = await prisma.user.findMany({ where }); - `GET /api/tenants/:id` - 获取租户详情 - `PATCH /api/tenants/:id` - 更新租户(包括菜单分配) - `DELETE /api/tenants/:id` - 删除租户 -- `GET /api/tenants/:id/menus` - 获取租户菜单树 +- `GET /api/tenants/:id/menus` - 获取租户菜单树(返回与 `/api/menus/user-menus` 相同的菜单树结构;数据来源 `t_sys_tenant_menu`;非超管只能查询自身租户) ### 其他接口 diff --git a/frontend/src/views/system/roles/Index.vue b/frontend/src/views/system/roles/Index.vue index 9d40dce..b3d4aaa 100644 --- a/frontend/src/views/system/roles/Index.vue +++ b/frontend/src/views/system/roles/Index.vue @@ -275,8 +275,9 @@ const handleEdit = async (record: Role) => { form.code = detail.code form.description = detail.description || '' // 回显权限 - form.permissionIds = detail.permissions?.map((rp) => rp.permission.id) || [] + form.permissionIds = detail.permissions?.map((rp) => rp.id) || [] } catch (error) { + console.error(error) message.error('获取角色详情失败') modalVisible.value = false } finally { diff --git a/frontend/src/views/system/users/Index.vue b/frontend/src/views/system/users/Index.vue index 53bc893..2e68d3e 100644 --- a/frontend/src/views/system/users/Index.vue +++ b/frontend/src/views/system/users/Index.vue @@ -7,12 +7,8 @@
-
+
@@ -27,43 +23,21 @@
- + - + - + 管理创建 自主注册 - + 正常 禁用 @@ -71,11 +45,15 @@ - + 搜索 - + 重置 @@ -84,15 +62,8 @@
- +