backend/src/main/java/com/tourguide/modules/user/mapper/GuideUserMapper.java
@@ -21,10 +21,20 @@ int anonymizeCancelledGuide(@Param("guideId") Long guideId, @Param("username") String username); @Update("UPDATE guide_user SET id_card_no = NULL, id_card_front_url = NULL, " + "id_card_back_url = NULL, updated_at = NOW() " + "id_card_back_url = NULL, certificate_no = NULL, updated_at = NOW() " + "WHERE id_card_no = #{idCardNo} AND status = 3 AND deleted = 0") int clearCancelledIdentityByIdCard(@Param("idCardNo") String idCardNo); @Update("UPDATE guide_user SET id_card_no = NULL, id_card_front_url = NULL, " + "id_card_back_url = NULL, certificate_no = NULL, updated_at = NOW() " + "WHERE id = #{guideId} AND status = 2 AND deleted = 0") int releasePendingIdentity(@Param("guideId") Long guideId); @Update("UPDATE guide_user SET id_card_no = NULL, id_card_front_url = NULL, " + "id_card_back_url = NULL, certificate_no = NULL, updated_at = NOW() " + "WHERE id_card_no = #{idCardNo} AND status = 2 AND deleted = 0") int clearPendingIdentityByIdCard(@Param("idCardNo") String idCardNo); @Select("SELECT * FROM guide_user WHERE id = #{guideId} AND deleted = 0 FOR UPDATE") GuideUser selectForUpdate(@Param("guideId") Long guideId); backend/src/main/java/com/tourguide/modules/user/service/GuideApplicationService.java
@@ -86,7 +86,11 @@ if (application.getAuditStatus() == null || application.getAuditStatus() != PENDING) { throw new BusinessException(409, "该申请已审核,请勿重复操作"); } if (auditStatus == APPROVED) guideUserService.createFromApprovedApplication(application); if (auditStatus == APPROVED) { guideUserService.createFromApprovedApplication(application); } else if (auditStatus == REJECTED) { guideUserService.releaseIdentityForRejectedApplication(application.getMobile()); } LambdaUpdateWrapper<GuideApplication> update = new LambdaUpdateWrapper<GuideApplication>() .eq(GuideApplication::getId, id) .eq(GuideApplication::getAuditStatus, PENDING) backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java
@@ -338,8 +338,11 @@ private void ensureUniqueGuideIdentity(GuideApplication application, GuideUser existingUser) { if (StringUtils.hasText(application.getIdCardNo())) { String idCardNo = application.getIdCardNo().trim().toUpperCase(); // 兼容旧注销数据:释放已注销账号占用的身份证唯一索引。 userMapper.clearCancelledIdentityByIdCard(application.getIdCardNo().trim().toUpperCase()); userMapper.clearCancelledIdentityByIdCard(idCardNo); // 兼容旧驳回数据:释放待审核账号占用的身份证唯一索引,避免其阻塞重新注册。 userMapper.clearPendingIdentityByIdCard(idCardNo); } if (StringUtils.hasText(application.getCertificateNo()) && userMapper.selectCount(new LambdaQueryWrapper<GuideUser>() @@ -359,6 +362,15 @@ } } /** 申请被驳回后释放待审核账号占用的身份证/导游证唯一索引,避免其阻塞重新注册。 */ public void releaseIdentityForRejectedApplication(String mobile) { if (!StringUtils.hasText(mobile)) return; GuideUser user = findByPhone(mobile); if (user != null && Integer.valueOf(STATUS_PENDING).equals(user.getStatus())) { userMapper.releasePendingIdentity(user.getId()); } } private void copyApplicationProfile(GuideUser user, GuideApplication application) { user.setRealName(application.getRealName()); user.setMobile(application.getMobile()); backend/src/test/java/com/tourguide/modules/user/service/GuideUserServiceProfileTest.java
@@ -144,6 +144,34 @@ } @Test void shouldReleaseIdentityFromStalePendingGuideBeforeNewRegistration() { GuideApplication application = new GuideApplication(); application.setMobile("13900139000"); application.setRealName("新导游"); application.setIdCardNo("11010519491231002X"); when(userMapper.selectOne(any())).thenReturn(null); service.createPendingFromApplication(application); verify(userMapper).clearCancelledIdentityByIdCard("11010519491231002X"); verify(userMapper).clearPendingIdentityByIdCard("11010519491231002X"); verify(userMapper).insert(any(GuideUser.class)); } @Test void shouldReleasePendingIdentityWhenRejectingApplication() { GuideUser pendingGuide = new GuideUser(); pendingGuide.setId(12L); pendingGuide.setStatus(GuideUserService.STATUS_PENDING); pendingGuide.setMobile("13800138000"); when(userMapper.selectOne(any())).thenReturn(pendingGuide); service.releaseIdentityForRejectedApplication("13800138000"); verify(userMapper).releasePendingIdentity(12L); } @Test void shouldTreatOneYearOrLessAsZeroWorkYears() { GuideApplication application = new GuideApplication(); application.setMobile("13800138000"); frontend/src/views/DashboardView.vue
@@ -115,7 +115,7 @@ <el-button link type="primary" @click="router.push('/finance/details')">查看更多</el-button> </div> <div class="line-chart"> <svg viewBox="0 0 680 225" preserveAspectRatio="none" role="img" aria-label="近7日平台收益趋势"> <svg viewBox="0 0 680 225" preserveAspectRatio="xMidYMid meet" role="img" aria-label="近7日平台收益趋势"> <defs> <linearGradient id="revenueArea" x1="0" y1="0" x2="0" y2="1"> <stop offset="0%" stop-color="#5eb8ff" stop-opacity=".38" /> @@ -222,7 +222,7 @@ .chart-heading div { display: flex; align-items: center; gap: 8px; } .chart-heading strong, .section-heading h2, .quick-panel h2 { margin: 0; color: #232932; font-size: 14px; } .chart-heading span { color: #8491a3; font-size: 11px; } .line-chart { height: 190px; } .line-chart { aspect-ratio: 680 / 225; } .line-chart svg { display: block; width: 100%; height: 100%; overflow: visible; } .grid-line { stroke: #e8eef5; stroke-width: 1; stroke-dasharray: 3 3; } .axis-label { fill: #8491a3; font-size: 10px; }