diff --git a/internal/accounts/service.go b/internal/accounts/service.go index 6cdf726..e911343 100644 --- a/internal/accounts/service.go +++ b/internal/accounts/service.go @@ -155,7 +155,7 @@ func (s *Service) Register(ctx context.Context, input RegisterInput) (*Result, e if len([]rune(input.Password)) < 6 || len([]byte(input.Password)) > 72 { return nil, ErrPasswordInvalid } - if input.ReferralCode != "" && !referralPattern.MatchString(input.ReferralCode) { + if !referralPattern.MatchString(input.ReferralCode) { return nil, ErrReferralInvalid } if !input.PrivacyAccepted { diff --git a/internal/accounts/service_test.go b/internal/accounts/service_test.go index 476c945..398a665 100644 --- a/internal/accounts/service_test.go +++ b/internal/accounts/service_test.go @@ -46,8 +46,9 @@ func TestRegisterValidation(t *testing.T) { {name: "username", input: RegisterInput{Username: "张 三", Phone: "13800138000", Password: "secret1", PrivacyAccepted: true}, want: ErrUsernameInvalid}, {name: "phone", input: RegisterInput{Username: "alice", Phone: "12800138000", Password: "secret1", PrivacyAccepted: true}, want: ErrPhoneInvalid}, {name: "password", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "123", PrivacyAccepted: true}, want: ErrPasswordInvalid}, + {name: "referral required", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "secret1", PrivacyAccepted: true}, want: ErrReferralInvalid}, {name: "referral", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "secret1", ReferralCode: "bad!", PrivacyAccepted: true}, want: ErrReferralInvalid}, - {name: "privacy", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "secret1"}, want: ErrPrivacyNotAccepted}, + {name: "privacy", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "secret1", ReferralCode: "ABC123"}, want: ErrPrivacyNotAccepted}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/internal/store/accounts.go b/internal/store/accounts.go index c5c1985..364808c 100644 --- a/internal/store/accounts.go +++ b/internal/store/accounts.go @@ -129,7 +129,7 @@ func accountByMobile(ctx context.Context, db queryer, tenantID int64, mobile str func referralParent(ctx context.Context, tx *sqlx.Tx, tenantID int64, code string) (int64, int, string, error) { code = strings.TrimSpace(code) if code == "" { - return 0, 1, "", nil + return 0, 0, "", ErrReferralInvalid } var parent struct { ID int64 `db:"id"` diff --git a/web/src/mobile/api.ts b/web/src/mobile/api.ts index d44a971..c787efc 100644 --- a/web/src/mobile/api.ts +++ b/web/src/mobile/api.ts @@ -9,6 +9,7 @@ const reasonMessages: Record = { FORBIDDEN: '当前账户无权执行此操作', INVALID_CREDENTIALS: '手机号或密码错误', ACCOUNT_ALREADY_REGISTERED: '该账户或手机号已经注册', + REFERRAL_CODE_INVALID: '邀请码无效,请检查后重新输入', RATE_LIMITED: '操作过于频繁,请稍后再试', TASK_DISABLED: '领取功能暂未开放', OUTSIDE_BUSINESS_HOURS: '当前不在营业时间内', diff --git a/web/src/mobile/views/AuthView.vue b/web/src/mobile/views/AuthView.vue index 0158f13..ea355dd 100644 --- a/web/src/mobile/views/AuthView.vue +++ b/web/src/mobile/views/AuthView.vue @@ -48,7 +48,8 @@ { + function referralCodeFromLink() { + const value = + route.query.inviteCode ?? route.query.invite_code ?? route.query.referral_code; + return typeof value === 'string' ? value.trim() : ''; + } + + watch(mode, (value) => { form.username = ''; form.phone = ''; form.password = ''; - form.referral_code = ''; + form.referral_code = value === 'register' ? referralCodeFromLink() : ''; accepted.value = true; error.value = ''; }); + watch( + () => [route.query.inviteCode, route.query.invite_code, route.query.referral_code], + () => { + const referralCode = referralCodeFromLink(); + if (!referralCode) return; + mode.value = 'register'; + form.referral_code = referralCode; + }, + { immediate: true } + ); + async function submit() { error.value = ''; if (!/^1\d{10}$/.test(form.phone)) return void (error.value = '请输入正确的手机号码'); @@ -116,6 +134,9 @@ if (mode.value === 'register' && !/^[\p{L}\p{M}·]{1,10}$/u.test(form.username)) { return void (error.value = '名字须为 1 至 10 个字符,支持汉字'); } + if (mode.value === 'register' && !/^[A-Za-z0-9]{1,32}$/.test(form.referral_code)) { + return void (error.value = '请输入有效的邀请码'); + } submitting.value = true; try { const data = await request({ diff --git a/web/src/mobile/views/ClaimView.vue b/web/src/mobile/views/ClaimView.vue index f0ee6f1..09aa4c3 100644 --- a/web/src/mobile/views/ClaimView.vue +++ b/web/src/mobile/views/ClaimView.vue @@ -45,7 +45,7 @@
@@ -141,7 +141,7 @@ const matchingHint = computed(() => { if (order.value?.dispatch_mode === 'manual') return '订单已领取,正在等待后台匹配...'; if (matchRemainingSeconds.value > 0) return `系统正在派单... ${formatCountdown(matchRemainingSeconds.value)}`; - return pollFinished.value ? '任务仍在匹配,可稍后前往记录查看' : '系统正在为您匹配任务,请稍等...'; + return pollFinished.value ? '订单仍在匹配,可稍后前往记录查看' : '系统正在为您匹配订单,请稍等...'; }); const resultIcon = computed(() => resultState.value === 'success' ? mobileAssets.claimSuccess : resultState.value === 'failed' ? mobileAssets.claimFailed : mobileAssets.claimCancelled); const resultTitle = computed(() => resultState.value === 'success' ? '任务获取成功' : resultState.value === 'failed' ? '领取失败' : '已取消任务');