fix(family-card): 收紧身份证校验并调整关系箭头
| | |
| | | <view class="input-box"><input v-model="form.name" maxlength="20" placeholder="请输入姓名" placeholder-class="input-placeholder" /></view> |
| | | |
| | | <text class="field-label">身份证号</text> |
| | | <view class="input-box"><input v-model="form.idCard" maxlength="18" placeholder="请输入身份证号" placeholder-class="input-placeholder" /></view> |
| | | <view class="input-box"><input :value="form.idCard" maxlength="18" placeholder="请输入身份证号" placeholder-class="input-placeholder" @input="handleIdCardInput" /></view> |
| | | |
| | | <text class="field-label">手机号</text> |
| | | <view class="input-box"><input v-model="form.phone" type="number" maxlength="11" placeholder="请输入手机号" placeholder-class="input-placeholder" /></view> |
| | | |
| | | <text class="field-label">关系</text> |
| | | <picker class="relation-picker" :range="relations" :value="relationIndex" @change="changeRelation"> |
| | | <view class="picker-box"><text>{{form.relation}}</text><text class="picker-arrow">⌄</text></view> |
| | | <view class="picker-box"><text>{{form.relation}}</text><view class="picker-arrow"></view></view> |
| | | </picker> |
| | | |
| | | <view v-if="applicationId != '' && auditRemark != ''" class="reject-tip"><text>驳回原因:{{auditRemark}}</text></view> |
| | |
| | | next.relation = relations[selectedIndex] |
| | | form.value = next |
| | | } |
| | | const handleIdCardInput = (event: any) => { |
| | | const raw = (event.detail.value as string).toUpperCase() |
| | | let normalized = raw.replace(/[^0-9X]/g, '').slice(0, 18) |
| | | if (normalized.indexOf('X') >= 0 && (normalized.length != 18 || !normalized.endsWith('X'))) { |
| | | normalized = normalized.replace(/X/g, '') |
| | | } |
| | | const next = form.value |
| | | next.idCard = normalized |
| | | form.value = next |
| | | return normalized |
| | | } |
| | | const submitApplication = () => { |
| | | if (isSubmitting.value) return |
| | | if (form.value.name.trim().length == 0) { |
| | | uni.showToast({ title: '请填写姓名', icon: 'none' }) |
| | | return |
| | | } |
| | | if (!(/^(?:[0-9]{15}|[0-9]{17}[0-9Xx])$/).test(form.value.idCard.trim())) { |
| | | if (!(/^[0-9]{17}[0-9X]$/).test(form.value.idCard.trim())) { |
| | | uni.showToast({ title: '请填写正确的身份证号', icon: 'none' }) |
| | | return |
| | | } |
| | |
| | | .field-label:not(:first-child) { margin-top: 45rpx; } |
| | | .relation-picker { display: block; } |
| | | .picker-box { margin-top: 14rpx; color: #4E5867; font-size: 28rpx; line-height: 40rpx; justify-content: space-between; } |
| | | .picker-arrow { color: #9FA9B6; font-size: 40rpx; line-height: 38rpx; } |
| | | .picker-arrow { width: 12rpx; height: 12rpx; margin-right: 4rpx; border-right: 2rpx solid #AAB3BE; border-bottom: 2rpx solid #AAB3BE; transform: rotate(45deg); } |
| | | .submit-button { display: flex; height: 84rpx; margin-top: 43rpx; border-radius: 44rpx; background-image: linear-gradient(90deg, #FF4E35 0%, #FF8318 100%); justify-content: center; align-items: center; } |
| | | .submit-button text { color: #FFFFFF; font-size: 28rpx; line-height: 40rpx; font-weight: 700; } |
| | | .submit-button-active { opacity: .8; transform: scale(.99); } |
| | |
| | | public record FamilyCardApplicationRequest( |
| | | @NotNull Long talentId, |
| | | @NotBlank String name, |
| | | @NotBlank @Pattern(regexp = "^(?:[0-9]{15}|[0-9]{17}[0-9Xx])$") String idCard, |
| | | @NotBlank @Pattern(regexp = "^[0-9]{17}[0-9X]$") String idCard, |
| | | @NotBlank @Pattern(regexp = "^1[3-9][0-9]{9}$") String mobile, |
| | | @NotBlank String relationship |
| | | ) {} |
| | |
| | | private String unbindReason(Map<String, Object> row) { if ("待审核".equals(string(row.get("applicationStatus")))) return "家属卡正在审核,暂不可解绑"; if (!"已通过".equals(string(row.get("applicationStatus")))) return "当前家属卡状态不可解绑"; if (number(row.get("cardStatus")) == null || number(row.get("cardStatus")) != 1) return "家属卡未处于有效状态"; return "可解除绑定"; } |
| | | private String statusText(String applicationStatus, Object cardStatus) { if ("待审核".equals(applicationStatus)) return "审核中"; if ("已驳回".equals(applicationStatus)) return "已驳回"; if ("已解绑".equals(applicationStatus)) return "已解除"; return number(cardStatus) != null && number(cardStatus) == 1 ? "已生效" : "已失效"; } |
| | | private Long number(Object value) { return value instanceof Number number ? number.longValue() : null; } |
| | | private boolean validIdCard(String value) { return value.matches("[0-9]{15}") || value.matches("[0-9]{17}[0-9Xx]"); } |
| | | private boolean validIdCard(String value) { return value.matches("[0-9]{17}[0-9X]"); } |
| | | private String string(Object value) { return value == null ? "" : String.valueOf(value); } |
| | | private String maskName(String value) { if (value.isBlank()) return ""; return value.length() == 1 ? "*" : value.substring(0, 1) + "*".repeat(value.length() - 1); } |
| | | private String maskMobile(String value) { return value.length() == 11 ? value.substring(0, 3) + "****" + value.substring(7) : ""; } |
| | |
| | | @Schema(description = "家属身份证号", example = "510105199001011234", maxLength = 32, requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @NotBlank(message = "家属证件号不能为空") |
| | | @Size(max = 32, message = "家属证件号不能超过32个字符") |
| | | @Pattern(regexp = "^(?:[0-9]{15}|[0-9]{17}[0-9Xx])$", message = "家属证件号格式不正确") |
| | | @Pattern(regexp = "^[0-9]{17}[0-9X]$", message = "家属证件号格式不正确") |
| | | String dependentIdCardNo, |
| | | |
| | | @Schema(description = "家属手机号", example = "13800138000", pattern = "^1[3-9][0-9]{9}$", requiredMode = Schema.RequiredMode.REQUIRED) |
| | |
| | | @Size(max = 32, message = "家属关系不能超过32个字符") |
| | | String relation |
| | | ) { |
| | | } |
| | | } |
| New file |
| | |
| | | package com.talentcard.server.request; |
| | | |
| | | import com.talentcard.server.business.portal.FamilyCardApplicationRequest; |
| | | import jakarta.validation.Validation; |
| | | import jakarta.validation.Validator; |
| | | import org.junit.jupiter.api.AfterAll; |
| | | import org.junit.jupiter.api.BeforeAll; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import static org.assertj.core.api.Assertions.assertThat; |
| | | |
| | | class FamilyCardIdCardValidationTests { |
| | | |
| | | private static jakarta.validation.ValidatorFactory validatorFactory; |
| | | private static Validator validator; |
| | | |
| | | @BeforeAll |
| | | static void setUpValidator() { |
| | | validatorFactory = Validation.buildDefaultValidatorFactory(); |
| | | validator = validatorFactory.getValidator(); |
| | | } |
| | | |
| | | @AfterAll |
| | | static void closeValidator() { |
| | | validatorFactory.close(); |
| | | } |
| | | |
| | | @Test |
| | | void acceptsEighteenDigitsOrUppercaseXAsCheckCode() { |
| | | assertThat(violationsForDependentRequest("510105199001011234")).isZero(); |
| | | assertThat(violationsForDependentRequest("51010519900101123X")).isZero(); |
| | | assertThat(violationsForPortalRequest("510105199001011234")).isZero(); |
| | | assertThat(violationsForPortalRequest("51010519900101123X")).isZero(); |
| | | } |
| | | |
| | | @Test |
| | | void rejectsLegacyAndInvalidIdCardFormats() { |
| | | for (String idCard : new String[] { "510105900101123", "51010519900101123x", "51010519900101123A" }) { |
| | | assertThat(violationsForDependentRequest(idCard)).isPositive(); |
| | | assertThat(violationsForPortalRequest(idCard)).isPositive(); |
| | | } |
| | | } |
| | | |
| | | private int violationsForDependentRequest(String idCard) { |
| | | return validator.validate(new DependentCardApplyRequest(1L, "家属", idCard, "13800138000", "配偶")).size(); |
| | | } |
| | | |
| | | private int violationsForPortalRequest(String idCard) { |
| | | return validator.validate(new FamilyCardApplicationRequest(1L, "家属", idCard, "13800138000", "配偶")).size(); |
| | | } |
| | | } |
| | |
| | | new DependentCardApplyRequest( |
| | | 34L, |
| | | " 新家属 ", |
| | | "51010519900101123x", |
| | | "51010519900101123X", |
| | | "13700000000", |
| | | " 子女 " |
| | | ) |