shinnosuke
2026-08-13 6d4ac9680c02148ee9e438e0ba44e370f67fd063
fix(family-card): 收紧身份证校验并调整关系箭头
1个文件已添加
5个文件已修改
80 ■■■■■ 已修改文件
rencaika/pages/family-card-apply/family-card-apply.uvue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardApplicationRequest.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/request/DependentCardApplyRequest.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/test/java/com/talentcard/server/request/FamilyCardIdCardValidationTests.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/test/java/com/talentcard/server/service/DependentCardApplyServiceTests.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika/pages/family-card-apply/family-card-apply.uvue
@@ -15,14 +15,14 @@
                <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>
@@ -97,13 +97,24 @@
        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
        }
@@ -162,7 +173,7 @@
    .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); }
talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardApplicationRequest.java
@@ -7,7 +7,7 @@
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
) {}
talent-card-server/src/main/java/com/talentcard/server/business/portal/FamilyCardPortalService.java
@@ -274,7 +274,7 @@
    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) : ""; }
talent-card-server/src/main/java/com/talentcard/server/request/DependentCardApplyRequest.java
@@ -20,7 +20,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)
@@ -33,4 +33,4 @@
        @Size(max = 32, message = "家属关系不能超过32个字符")
        String relation
) {
}
}
talent-card-server/src/test/java/com/talentcard/server/request/FamilyCardIdCardValidationTests.java
New file
@@ -0,0 +1,51 @@
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();
    }
}
talent-card-server/src/test/java/com/talentcard/server/service/DependentCardApplyServiceTests.java
@@ -86,7 +86,7 @@
                new DependentCardApplyRequest(
                        34L,
                        " 新家属 ",
                        "51010519900101123x",
                        "51010519900101123X",
                        "13700000000",
                        " 子女 "
                )