feat: 新增多模块后端管理、数据同步工具与鸿蒙路由适配
本次提交新增了以下核心内容: 1. 后端管理模块:包含字体同步、插件元数据、插件用户设置、稍后读消息/共享列表的控制器、模型、验证器与多语言配置 2. Flutter数据同步模块:统一的事件总线与兼容层,替代分散的StreamController 3. 鸿蒙端路由适配:完整的路由定义、构建器与占位组件 4. 后端API接口:字体同步与插件更新的服务端API,支持自动建表与跨域请求 5. 鸿蒙权限校验脚本:用于校验module.json5与string.json的权限声明一致性
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 注册区域组件
|
||||
/// 创建时间: 2026-05-10
|
||||
/// 更新时间: 2026-05-23
|
||||
/// 更新时间: 2026-06-01
|
||||
/// 作用: 登录页面的注册区域,分步式注册流程
|
||||
/// 上次更新: 添加Tips提醒卡片,邮箱已注册跳转登录弹窗
|
||||
/// 上次更新: 接入多语言翻译系统,替换硬编码中文文本
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -14,6 +14,8 @@ import '../../../../core/theme/app_spacing.dart';
|
||||
import '../../../../core/theme/app_typography.dart';
|
||||
import '../../../../core/theme/app_radius.dart';
|
||||
import '../../../../core/utils/logger.dart';
|
||||
import '../../../../l10n/translation_resolver.dart';
|
||||
import '../../../../l10n/types/t.dart';
|
||||
import '../../../../shared/widgets/feedback/app_toast.dart';
|
||||
import '../../../../shared/widgets/containers/glass_container.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
@@ -94,16 +96,19 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final authState = ref.watch(authProvider);
|
||||
final t = ref.watch(translationsProvider);
|
||||
final auth = t.auth;
|
||||
final common = t.common;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_buildHeader(),
|
||||
_buildHeader(auth),
|
||||
_buildStepIndicator(),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
GlassContainer(
|
||||
depth: GlassDepth.elevated,
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: _buildRegStepContent(authState),
|
||||
child: _buildRegStepContent(authState, auth, common),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
CupertinoButton(
|
||||
@@ -112,7 +117,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
setState(() => _regStep = 1);
|
||||
},
|
||||
child: Text(
|
||||
'已有账号?去登录',
|
||||
auth.hasAccountLogin,
|
||||
style: AppTypography.subhead.copyWith(color: ext.accent),
|
||||
),
|
||||
),
|
||||
@@ -120,7 +125,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
Widget _buildHeader(TAuth auth) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: AppSpacing.xl),
|
||||
child: Row(
|
||||
@@ -148,7 +153,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'创建账号',
|
||||
auth.createAccount,
|
||||
style: AppTypography.title2.copyWith(
|
||||
color: ext.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -156,7 +161,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'注册一个新账号开始使用',
|
||||
auth.registerNewAccount,
|
||||
style: AppTypography.subhead.copyWith(
|
||||
color: ext.textSecondary,
|
||||
),
|
||||
@@ -196,25 +201,25 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRegStepContent(AuthState authState) {
|
||||
Widget _buildRegStepContent(AuthState authState, TAuth auth, TCommon common) {
|
||||
switch (_regStep) {
|
||||
case 1:
|
||||
return Column(
|
||||
children: [
|
||||
LabeledInputField(
|
||||
label: '用户名',
|
||||
label: auth.username,
|
||||
labelIcon: CupertinoIcons.person_add,
|
||||
controller: _usernameController,
|
||||
placeholder: '3-30位,字母/数字/下划线/中文',
|
||||
placeholder: auth.usernameHint,
|
||||
icon: CupertinoIcons.person_add,
|
||||
ext: ext,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
LabeledInputField(
|
||||
label: '邮箱(必填)',
|
||||
label: auth.emailRequired,
|
||||
labelIcon: CupertinoIcons.mail,
|
||||
controller: _emailController,
|
||||
placeholder: '用于验证和找回密码',
|
||||
placeholder: auth.emailHint,
|
||||
icon: CupertinoIcons.mail,
|
||||
ext: ext,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
@@ -224,6 +229,8 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: Size.zero,
|
||||
color: ext.accent,
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
onPressed:
|
||||
@@ -242,7 +249,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'下一步',
|
||||
auth.nextStep,
|
||||
style: AppTypography.callout.copyWith(
|
||||
color: CupertinoColors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -265,7 +272,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
Icon(CupertinoIcons.mail, size: 36, color: ext.accent),
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
'验证码已发送至 ${_emailController.text.trim()}',
|
||||
'${auth.codeSentTo} ${_emailController.text.trim()}',
|
||||
style: AppTypography.footnote.copyWith(
|
||||
color: ext.textSecondary,
|
||||
),
|
||||
@@ -275,10 +282,10 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
),
|
||||
LabeledInputField(
|
||||
label: '验证码',
|
||||
label: auth.verifyCode,
|
||||
labelIcon: CupertinoIcons.number,
|
||||
controller: _regCodeController,
|
||||
placeholder: '输入6位验证码',
|
||||
placeholder: auth.enterCodeHint,
|
||||
icon: CupertinoIcons.number,
|
||||
ext: ext,
|
||||
keyboardType: TextInputType.number,
|
||||
@@ -294,7 +301,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
child: _emsSending
|
||||
? CupertinoActivityIndicator(radius: 6, color: ext.accent)
|
||||
: Text(
|
||||
_emsCountdown > 0 ? '${_emsCountdown}s' : '重新发送',
|
||||
_emsCountdown > 0 ? '${_emsCountdown}s' : auth.resend,
|
||||
style: AppTypography.caption1.copyWith(
|
||||
color: ext.accent,
|
||||
),
|
||||
@@ -331,7 +338,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'若未收到验证码,请检查垃圾邮箱。如需帮助,请联系客服。',
|
||||
auth.codeNotReceivedTip,
|
||||
style: AppTypography.caption2.copyWith(
|
||||
color: ext.textSecondary,
|
||||
),
|
||||
@@ -349,6 +356,8 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: Size.zero,
|
||||
color: ext.bgSecondary,
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
onPressed: () => setState(() => _regStep = 1),
|
||||
@@ -363,7 +372,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'上一步',
|
||||
auth.prevStep,
|
||||
style: AppTypography.callout.copyWith(
|
||||
color: ext.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -379,6 +388,8 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: Size.zero,
|
||||
color: ext.accent,
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
onPressed: _regCodeController.text.trim().isNotEmpty
|
||||
@@ -395,7 +406,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'下一步',
|
||||
auth.nextStep,
|
||||
style: AppTypography.callout.copyWith(
|
||||
color: CupertinoColors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -415,10 +426,10 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
return Column(
|
||||
children: [
|
||||
LabeledInputField(
|
||||
label: '设置密码',
|
||||
label: auth.setPassword,
|
||||
labelIcon: CupertinoIcons.lock,
|
||||
controller: _regPasswordController,
|
||||
placeholder: '6-30位密码',
|
||||
placeholder: auth.passwordHint,
|
||||
icon: CupertinoIcons.lock,
|
||||
ext: ext,
|
||||
obscureText: _obscureRegPassword,
|
||||
@@ -427,7 +438,10 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
onTap: () =>
|
||||
setState(() => _obscureRegPassword = !_obscureRegPassword),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 44, minHeight: 44),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 44,
|
||||
minHeight: 44,
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
_obscureRegPassword
|
||||
@@ -442,10 +456,10 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
LabeledInputField(
|
||||
label: '确认密码',
|
||||
label: auth.confirmPassword,
|
||||
labelIcon: CupertinoIcons.lock_shield,
|
||||
controller: _regConfirmPasswordController,
|
||||
placeholder: '再次输入密码',
|
||||
placeholder: auth.confirmPasswordHint,
|
||||
icon: CupertinoIcons.lock_shield,
|
||||
ext: ext,
|
||||
obscureText: true,
|
||||
@@ -477,7 +491,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Text(
|
||||
'🛡️ 密保问题(选填)',
|
||||
auth.secQuestionOptional,
|
||||
style: AppTypography.subhead.copyWith(
|
||||
color: _showSecQuestion
|
||||
? ext.accent
|
||||
@@ -486,7 +500,9 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
_selectedSecQuestionText.isNotEmpty ? '已选择' : '增强账号安全',
|
||||
_selectedSecQuestionText.isNotEmpty
|
||||
? auth.selected
|
||||
: auth.enhanceSecurity,
|
||||
style: AppTypography.caption1.copyWith(
|
||||
color: ext.textHint,
|
||||
),
|
||||
@@ -516,7 +532,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
onPressed: _secQuestions.isEmpty
|
||||
? null
|
||||
: () => _showSecQuestionPicker(context),
|
||||
: () => _showSecQuestionPicker(context, auth, common),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
@@ -529,7 +545,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
child: Text(
|
||||
_selectedSecQuestionText.isNotEmpty
|
||||
? _selectedSecQuestionText
|
||||
: '选择密保问题',
|
||||
: auth.selectSecQuestion,
|
||||
style: AppTypography.subhead.copyWith(
|
||||
color: _selectedSecQuestionText.isNotEmpty
|
||||
? ext.textPrimary
|
||||
@@ -554,7 +570,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
child: CupertinoTextField(
|
||||
controller: _secAnswerController,
|
||||
placeholder: '输入密保答案(1-50位)',
|
||||
placeholder: auth.enterSecAnswerHint,
|
||||
placeholderStyle: AppTypography.body.copyWith(
|
||||
color: ext.textHint,
|
||||
),
|
||||
@@ -598,7 +614,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Text(
|
||||
'订阅闲言团队的邮箱推送',
|
||||
auth.subscribeEmail,
|
||||
style: AppTypography.footnote.copyWith(
|
||||
color: ext.textSecondary,
|
||||
),
|
||||
@@ -613,6 +629,8 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: Size.zero,
|
||||
color: ext.bgSecondary,
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
onPressed: () => setState(() => _regStep = 2),
|
||||
@@ -627,7 +645,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'上一步',
|
||||
auth.prevStep,
|
||||
style: AppTypography.callout.copyWith(
|
||||
color: ext.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -643,6 +661,8 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: Size.zero,
|
||||
color: ext.accent,
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
onPressed:
|
||||
@@ -666,7 +686,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'完成注册',
|
||||
auth.completeRegister,
|
||||
style: AppTypography.callout.copyWith(
|
||||
color: CupertinoColors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -688,6 +708,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
}
|
||||
|
||||
Future<void> _handleRegister() async {
|
||||
final t = ref.read(translationsProvider);
|
||||
final username = _usernameController.text.trim();
|
||||
final password = _regPasswordController.text;
|
||||
final confirmPassword = _regConfirmPasswordController.text;
|
||||
@@ -695,22 +716,22 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
final code = _regCodeController.text.trim();
|
||||
|
||||
if (username.isEmpty || password.isEmpty || email.isEmpty) {
|
||||
AppToast.showWarning('请填写所有必填项');
|
||||
AppToast.showWarning(t.auth.pleaseFillRequired);
|
||||
return;
|
||||
}
|
||||
|
||||
if (password != confirmPassword) {
|
||||
AppToast.showError('两次密码不一致');
|
||||
AppToast.showError(t.auth.passwordMismatch);
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
AppToast.showWarning('密码长度不能少于6位');
|
||||
AppToast.showWarning(t.auth.passwordTooShort);
|
||||
return;
|
||||
}
|
||||
|
||||
if (code.isNotEmpty && !EmailService.verifyCode(email: email, code: code)) {
|
||||
AppToast.showError('验证码错误');
|
||||
AppToast.showError(t.auth.codeError);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -726,16 +747,17 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
: null,
|
||||
);
|
||||
if (success && mounted) {
|
||||
AppToast.showSuccess('注册成功,欢迎加入!');
|
||||
AppToast.showSuccess(t.auth.registerSuccess);
|
||||
widget.onRegisterSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _sendCode(String email) async {
|
||||
final t = ref.read(translationsProvider);
|
||||
if (_emsSending || _emsCountdown > 0 || email.isEmpty) return;
|
||||
|
||||
if (!email.contains('@')) {
|
||||
AppToast.showWarning('请输入有效的邮箱地址');
|
||||
AppToast.showWarning(t.auth.pleaseEnterValidEmail);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -751,13 +773,13 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
code: code,
|
||||
);
|
||||
if (!ok && mounted) {
|
||||
AppToast.showError('验证码发送失败,请检查邮箱地址');
|
||||
AppToast.showError(t.auth.codeSendFailed);
|
||||
setState(() {
|
||||
_emsSending = false;
|
||||
_emsCountdown = 0;
|
||||
});
|
||||
} else if (mounted) {
|
||||
AppToast.showSuccess('验证码已发送');
|
||||
AppToast.showSuccess(t.auth.codeSent);
|
||||
setState(() => _emsSending = false);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -769,7 +791,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
errorMsg.contains('exists')) {
|
||||
_showEmailRegisteredDialog(email);
|
||||
} else {
|
||||
AppToast.showError('验证码发送失败');
|
||||
AppToast.showError(t.auth.codeSendFailedShort);
|
||||
}
|
||||
setState(() {
|
||||
_emsSending = false;
|
||||
@@ -789,19 +811,20 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
}
|
||||
|
||||
void _showEmailRegisteredDialog(String email) {
|
||||
final t = ref.read(translationsProvider);
|
||||
showCupertinoDialog<void>(
|
||||
context: context,
|
||||
builder: (ctx) => CupertinoAlertDialog(
|
||||
title: const Text('邮箱已注册'),
|
||||
content: Text('该邮箱 ($email) 已被注册。是否跳转到登录页面?'),
|
||||
title: Text(t.auth.emailRegistered),
|
||||
content: Text(t.auth.emailRegisteredHint.replaceAll('{email}', email)),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text('取消'),
|
||||
child: Text(t.common.cancel),
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: const Text('去登录'),
|
||||
child: Text(t.auth.goLogin),
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
widget.onSwitchToLogin();
|
||||
@@ -812,7 +835,11 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showSecQuestionPicker(BuildContext context) {
|
||||
void _showSecQuestionPicker(
|
||||
BuildContext context,
|
||||
TAuth auth,
|
||||
TCommon common,
|
||||
) {
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (ctx) => Container(
|
||||
@@ -825,7 +852,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
children: [
|
||||
CupertinoButton(
|
||||
child: Text(
|
||||
'取消',
|
||||
common.cancel,
|
||||
style: AppTypography.subhead.copyWith(
|
||||
color: ext.textSecondary,
|
||||
),
|
||||
@@ -833,7 +860,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
),
|
||||
Text(
|
||||
'选择密保问题',
|
||||
auth.selectSecQuestion,
|
||||
style: AppTypography.headline.copyWith(
|
||||
color: ext.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -841,7 +868,7 @@ class _RegisterSectionState extends ConsumerState<RegisterSection> {
|
||||
),
|
||||
CupertinoButton(
|
||||
child: Text(
|
||||
'确定',
|
||||
common.confirm,
|
||||
style: AppTypography.subhead.copyWith(
|
||||
color: ext.accent,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
Reference in New Issue
Block a user