feat(miniapp+admin-app): 同步 SMS 认证 API 变更
Phase 7 — Taro miniapp + admin-app 前端同步后端 SMS 认证系统: miniapp (Taro/React): - auth.ts: 新增 SmsCodeType 类型,sendSmsCode 支持 type 参数 · 端点从 /auth/send-sms-code → /auth/sms/send · 新增 loginByPassword / resetPassword API · register 支持 password + nickname 可选参数 - store/auth.ts: sendSmsCode 同步 type 参数 + 新端点 - login/index.tsx: 发送验证码时指定 type='LOGIN' - h5-register/index.tsx: 发送验证码时指定 type='REGISTER' · 修复注册后 token 存储使用 config.TOKEN_KEY 而非硬编码 - i18n: 3语言新增 7 key (login_code_sent, login_error_*, register_success, register_error_agree) admin-app (Flutter): - auth_service.dart: 新增 SmsCodeType 枚举 · sendSmsCode 支持 type 参数,端点同步 /auth/sms/send · 返回 expiresIn (秒) - issuer_login_page.dart: 发送验证码时指定 SmsCodeType.login - i18n: 3语言新增 4 key (login_error_phone, login_error_code, login_error_network, login_code_sent) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4b1cdf9fb3
commit
c29067eee7
|
|
@ -78,6 +78,10 @@ class AppLocalizations {
|
|||
'login_agreement': '《发行方服务协议》',
|
||||
'login_button': '登录',
|
||||
'login_register': '还没有账号?申请入驻',
|
||||
'login_error_phone': '请输入手机号',
|
||||
'login_error_code': '请输入6位验证码',
|
||||
'login_error_network': '网络错误,请稍后重试',
|
||||
'login_code_sent': '验证码已发送',
|
||||
|
||||
// ── Onboarding ──
|
||||
'onboarding_title': '企业入驻',
|
||||
|
|
@ -639,6 +643,10 @@ class AppLocalizations {
|
|||
'login_agreement': 'Issuer Service Agreement',
|
||||
'login_button': 'Sign In',
|
||||
'login_register': 'No account? Apply to join',
|
||||
'login_error_phone': 'Please enter your phone number',
|
||||
'login_error_code': 'Please enter the 6-digit code',
|
||||
'login_error_network': 'Network error, please try again',
|
||||
'login_code_sent': 'Code sent',
|
||||
|
||||
// ── Onboarding ──
|
||||
'onboarding_title': 'Business Onboarding',
|
||||
|
|
@ -1200,6 +1208,10 @@ class AppLocalizations {
|
|||
'login_agreement': '発行者サービス規約',
|
||||
'login_button': 'ログイン',
|
||||
'login_register': 'アカウントがない場合は申請',
|
||||
'login_error_phone': '電話番号を入力してください',
|
||||
'login_error_code': '6桁の認証コードを入力してください',
|
||||
'login_error_network': 'ネットワークエラーです。後でもう一度お試しください',
|
||||
'login_code_sent': '認証コードを送信しました',
|
||||
|
||||
// ── Onboarding ──
|
||||
'onboarding_title': '企業登録',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import '../network/api_client.dart';
|
||||
|
||||
/// SMS 验证码类型
|
||||
enum SmsCodeType {
|
||||
register('REGISTER'),
|
||||
login('LOGIN'),
|
||||
resetPassword('RESET_PASSWORD'),
|
||||
changePhone('CHANGE_PHONE');
|
||||
|
||||
final String value;
|
||||
const SmsCodeType(this.value);
|
||||
}
|
||||
|
||||
/// 认证服务
|
||||
class AuthService {
|
||||
final ApiClient _apiClient;
|
||||
|
|
@ -8,13 +19,16 @@ class AuthService {
|
|||
AuthService({ApiClient? apiClient})
|
||||
: _apiClient = apiClient ?? ApiClient.instance;
|
||||
|
||||
/// 发送短信验证码
|
||||
Future<bool> sendSmsCode(String phone) async {
|
||||
/// 发送短信验证码 (支持类型)
|
||||
Future<int> sendSmsCode(String phone, {SmsCodeType type = SmsCodeType.login}) async {
|
||||
try {
|
||||
await _apiClient.post('/api/v1/auth/send-sms-code', data: {
|
||||
final response = await _apiClient.post('/api/v1/auth/sms/send', data: {
|
||||
'phone': phone,
|
||||
'type': type.value,
|
||||
});
|
||||
return true;
|
||||
final data = response.data is Map<String, dynamic> ? response.data : {};
|
||||
final inner = data['data'] ?? data;
|
||||
return (inner is Map<String, dynamic>) ? (inner['expiresIn'] ?? 300) as int : 300;
|
||||
} catch (e) {
|
||||
debugPrint('[AuthService] sendSmsCode 失败: $e');
|
||||
rethrow;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class _IssuerLoginPageState extends State<IssuerLoginPage> {
|
|||
});
|
||||
|
||||
try {
|
||||
await _authService.sendSmsCode(phone);
|
||||
await _authService.sendSmsCode(phone, type: SmsCodeType.login);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isSendingCode = false;
|
||||
|
|
|
|||
|
|
@ -210,6 +210,12 @@ const translations: Record<Locale, Record<string, string>> = {
|
|||
'login_code': '验证码',
|
||||
'login_btn': '登录',
|
||||
'login_agree': '登录即表示同意',
|
||||
'login_code_sent': '验证码已发送',
|
||||
'login_error_phone': '请输入正确的手机号',
|
||||
'login_error_code': '请输入6位验证码',
|
||||
'login_error_network': '网络错误,请稍后重试',
|
||||
'register_success': '注册成功',
|
||||
'register_error_agree': '请先同意用户协议',
|
||||
|
||||
// ── Detail (additional) ──
|
||||
'coupon_type_label': '类型',
|
||||
|
|
@ -660,6 +666,12 @@ const translations: Record<Locale, Record<string, string>> = {
|
|||
'login_code': 'Verification code',
|
||||
'login_btn': 'Log In',
|
||||
'login_agree': 'By logging in, you agree to the',
|
||||
'login_code_sent': 'Code sent',
|
||||
'login_error_phone': 'Please enter a valid phone number',
|
||||
'login_error_code': 'Please enter a 6-digit code',
|
||||
'login_error_network': 'Network error, please try again',
|
||||
'register_success': 'Registration successful',
|
||||
'register_error_agree': 'Please agree to the terms first',
|
||||
|
||||
// ── Detail (additional) ──
|
||||
'coupon_type_label': 'Type',
|
||||
|
|
@ -1110,6 +1122,12 @@ const translations: Record<Locale, Record<string, string>> = {
|
|||
'login_code': '認証コード',
|
||||
'login_btn': 'ログイン',
|
||||
'login_agree': 'ログインすると以下に同意したものとみなされます',
|
||||
'login_code_sent': '認証コードを送信しました',
|
||||
'login_error_phone': '正しい電話番号を入力してください',
|
||||
'login_error_code': '6桁の認証コードを入力してください',
|
||||
'login_error_network': 'ネットワークエラーです。後でもう一度お試しください',
|
||||
'register_success': '登録が完了しました',
|
||||
'register_error_agree': '利用規約に同意してください',
|
||||
|
||||
// ── Detail (additional) ──
|
||||
'coupon_type_label': '種類',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { t } from '@/i18n';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { register, sendSmsCode } from '../../services/auth';
|
||||
import { register, sendSmsCode, type SmsCodeType } from '../../services/auth';
|
||||
import { authStore } from '../../store/auth';
|
||||
import logoIcon from '@/assets/images/logo_icon.png';
|
||||
// Taro mini-program component
|
||||
|
|
@ -24,7 +24,7 @@ const H5RegisterPage: React.FC = () => {
|
|||
const handleSendCode = () => {
|
||||
if (!phone || phone.length < 11 || codeSending || codeCountdown > 0) return;
|
||||
setCodeSending(true);
|
||||
sendSmsCode(phone)
|
||||
sendSmsCode(phone, 'REGISTER')
|
||||
.then(() => {
|
||||
Taro.showToast({ title: t('login_code_sent') || '验证码已发送', icon: 'success' });
|
||||
setCodeCountdown(60);
|
||||
|
|
@ -47,8 +47,10 @@ const H5RegisterPage: React.FC = () => {
|
|||
setSubmitting(true);
|
||||
register(phone, smsCode)
|
||||
.then((result) => {
|
||||
// Store tokens via auth store if needed
|
||||
Taro.setStorageSync('token', result.accessToken);
|
||||
// Store tokens via config keys for consistency
|
||||
const { config } = require('../../config');
|
||||
Taro.setStorageSync(config.TOKEN_KEY, result.accessToken);
|
||||
Taro.setStorageSync(config.USER_KEY, JSON.stringify(result.user));
|
||||
Taro.showToast({ title: t('register_success') || '注册成功', icon: 'success' });
|
||||
setTimeout(() => {
|
||||
Taro.reLaunch({ url: '/pages/home/index' });
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||
import { t } from '@/i18n';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { authStore } from '../../store/auth';
|
||||
import { sendSmsCode } from '../../services/auth';
|
||||
import { sendSmsCode, type SmsCodeType } from '../../services/auth';
|
||||
import logoIcon from '@/assets/images/logo_icon.png';
|
||||
// Taro mini-program component
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ const LoginPage: React.FC = () => {
|
|||
const handleSendCode = () => {
|
||||
if (!phone || phone.length < 11 || codeSending || codeCountdown > 0) return;
|
||||
setCodeSending(true);
|
||||
sendSmsCode(phone)
|
||||
sendSmsCode(phone, 'LOGIN')
|
||||
.then(() => {
|
||||
Taro.showToast({ title: t('login_code_sent') || '验证码已发送', icon: 'success' });
|
||||
setCodeCountdown(60);
|
||||
|
|
|
|||
|
|
@ -6,14 +6,22 @@ interface LoginResult {
|
|||
user: { id: string; phone?: string; nickname?: string; kycLevel: number };
|
||||
}
|
||||
|
||||
/** SMS 验证码类型 */
|
||||
export type SmsCodeType = 'REGISTER' | 'LOGIN' | 'RESET_PASSWORD' | 'CHANGE_PHONE';
|
||||
|
||||
/** 手机号+验证码登录 */
|
||||
export function loginByPhone(phone: string, smsCode: string) {
|
||||
return post<LoginResult>('/api/v1/auth/login-phone', { phone, smsCode }, false);
|
||||
export function loginByPhone(phone: string, smsCode: string, deviceInfo?: string) {
|
||||
return post<LoginResult>('/api/v1/auth/login-phone', { phone, smsCode, ...(deviceInfo ? { deviceInfo } : {}) }, false);
|
||||
}
|
||||
|
||||
/** 密码登录 */
|
||||
export function loginByPassword(identifier: string, password: string) {
|
||||
return post<LoginResult>('/api/v1/auth/login', { identifier, password }, false);
|
||||
}
|
||||
|
||||
/** 发送短信验证码 */
|
||||
export function sendSmsCode(phone: string) {
|
||||
return post<{ success: boolean }>('/api/v1/auth/send-sms-code', { phone }, false);
|
||||
export function sendSmsCode(phone: string, type: SmsCodeType = 'LOGIN') {
|
||||
return post<{ expiresIn: number }>('/api/v1/auth/sms/send', { phone, type }, false);
|
||||
}
|
||||
|
||||
/** 微信登录 */
|
||||
|
|
@ -22,8 +30,17 @@ export function loginByWechat(code: string) {
|
|||
}
|
||||
|
||||
/** 注册 */
|
||||
export function register(phone: string, smsCode: string) {
|
||||
return post<LoginResult>('/api/v1/auth/register', { phone, smsCode }, false);
|
||||
export function register(phone: string, smsCode: string, password?: string, nickname?: string) {
|
||||
return post<LoginResult>('/api/v1/auth/register', {
|
||||
phone, smsCode,
|
||||
...(password ? { password } : {}),
|
||||
...(nickname ? { nickname } : {}),
|
||||
}, false);
|
||||
}
|
||||
|
||||
/** 重置密码 */
|
||||
export function resetPassword(phone: string, smsCode: string, newPassword: string) {
|
||||
return post<void>('/api/v1/auth/reset-password', { phone, smsCode, newPassword }, false);
|
||||
}
|
||||
|
||||
/** 登出 */
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ export const authStore = {
|
|||
notify();
|
||||
},
|
||||
|
||||
/** 发送短信验证码 */
|
||||
async sendSmsCode(phone: string): Promise<void> {
|
||||
await post('/api/v1/auth/send-sms-code', { phone }, false);
|
||||
/** 发送短信验证码 (支持类型) */
|
||||
async sendSmsCode(phone: string, type: 'REGISTER' | 'LOGIN' | 'RESET_PASSWORD' | 'CHANGE_PHONE' = 'LOGIN'): Promise<void> {
|
||||
await post('/api/v1/auth/sms/send', { phone, type }, false);
|
||||
},
|
||||
|
||||
/** 微信一键登录 */
|
||||
|
|
|
|||
Loading…
Reference in New Issue