diff --git a/frontend/admin-app/lib/app/i18n/app_localizations.dart b/frontend/admin-app/lib/app/i18n/app_localizations.dart index 79a0b0f..6b764a0 100644 --- a/frontend/admin-app/lib/app/i18n/app_localizations.dart +++ b/frontend/admin-app/lib/app/i18n/app_localizations.dart @@ -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': '企業登録', diff --git a/frontend/admin-app/lib/core/services/auth_service.dart b/frontend/admin-app/lib/core/services/auth_service.dart index c05bd36..c01f823 100644 --- a/frontend/admin-app/lib/core/services/auth_service.dart +++ b/frontend/admin-app/lib/core/services/auth_service.dart @@ -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 sendSmsCode(String phone) async { + /// 发送短信验证码 (支持类型) + Future 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 ? response.data : {}; + final inner = data['data'] ?? data; + return (inner is Map) ? (inner['expiresIn'] ?? 300) as int : 300; } catch (e) { debugPrint('[AuthService] sendSmsCode 失败: $e'); rethrow; diff --git a/frontend/admin-app/lib/features/auth/presentation/pages/issuer_login_page.dart b/frontend/admin-app/lib/features/auth/presentation/pages/issuer_login_page.dart index e3aa4c8..1f9c934 100644 --- a/frontend/admin-app/lib/features/auth/presentation/pages/issuer_login_page.dart +++ b/frontend/admin-app/lib/features/auth/presentation/pages/issuer_login_page.dart @@ -44,7 +44,7 @@ class _IssuerLoginPageState extends State { }); try { - await _authService.sendSmsCode(phone); + await _authService.sendSmsCode(phone, type: SmsCodeType.login); if (!mounted) return; setState(() { _isSendingCode = false; diff --git a/frontend/miniapp/src/i18n/index.ts b/frontend/miniapp/src/i18n/index.ts index ca76be0..373a96e 100644 --- a/frontend/miniapp/src/i18n/index.ts +++ b/frontend/miniapp/src/i18n/index.ts @@ -210,6 +210,12 @@ const translations: Record> = { '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> = { '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> = { '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': '種類', diff --git a/frontend/miniapp/src/pages/h5-register/index.tsx b/frontend/miniapp/src/pages/h5-register/index.tsx index b8882d3..3503ed9 100644 --- a/frontend/miniapp/src/pages/h5-register/index.tsx +++ b/frontend/miniapp/src/pages/h5-register/index.tsx @@ -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' }); diff --git a/frontend/miniapp/src/pages/login/index.tsx b/frontend/miniapp/src/pages/login/index.tsx index f96fd34..8be78ea 100644 --- a/frontend/miniapp/src/pages/login/index.tsx +++ b/frontend/miniapp/src/pages/login/index.tsx @@ -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); diff --git a/frontend/miniapp/src/services/auth.ts b/frontend/miniapp/src/services/auth.ts index b1cd1af..ad3ce2f 100644 --- a/frontend/miniapp/src/services/auth.ts +++ b/frontend/miniapp/src/services/auth.ts @@ -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('/api/v1/auth/login-phone', { phone, smsCode }, false); +export function loginByPhone(phone: string, smsCode: string, deviceInfo?: string) { + return post('/api/v1/auth/login-phone', { phone, smsCode, ...(deviceInfo ? { deviceInfo } : {}) }, false); +} + +/** 密码登录 */ +export function loginByPassword(identifier: string, password: string) { + return post('/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('/api/v1/auth/register', { phone, smsCode }, false); +export function register(phone: string, smsCode: string, password?: string, nickname?: string) { + return post('/api/v1/auth/register', { + phone, smsCode, + ...(password ? { password } : {}), + ...(nickname ? { nickname } : {}), + }, false); +} + +/** 重置密码 */ +export function resetPassword(phone: string, smsCode: string, newPassword: string) { + return post('/api/v1/auth/reset-password', { phone, smsCode, newPassword }, false); } /** 登出 */ diff --git a/frontend/miniapp/src/store/auth.ts b/frontend/miniapp/src/store/auth.ts index 3303f9e..d50f103 100644 --- a/frontend/miniapp/src/store/auth.ts +++ b/frontend/miniapp/src/store/auth.ts @@ -53,9 +53,9 @@ export const authStore = { notify(); }, - /** 发送短信验证码 */ - async sendSmsCode(phone: string): Promise { - await post('/api/v1/auth/send-sms-code', { phone }, false); + /** 发送短信验证码 (支持类型) */ + async sendSmsCode(phone: string, type: 'REGISTER' | 'LOGIN' | 'RESET_PASSWORD' | 'CHANGE_PHONE' = 'LOGIN'): Promise { + await post('/api/v1/auth/sms/send', { phone, type }, false); }, /** 微信一键登录 */