feat: 添加视觉设计语言系统并重构认证页面UI
- 新增 visual_design_language.md 设计规范文档 - 新增 auth 设计 tokens (authBackground, authCard, authInput, feedback 系列等) - 重构登录/注册/验证码/重置密码页面为新设计系统 - 新增 AuthHeroHeader, AuthSurfaceCard, AuthSection, AuthField, PasswordField 组件 - 重构 AppBanner 和 Toast 支持多类型配置 (info/success/warning/error) - 后端 AgentScope: 重整 schemas/prompts/tools 作用域, 新增协议文档 - 更新 AGENTS.md 集成视觉设计语言约束
This commit is contained in:
@@ -4,17 +4,20 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../../core/theme/design_tokens.dart';
|
||||
|
||||
import '../../../../core/di/injection.dart';
|
||||
import '../../../../core/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/app_button.dart';
|
||||
import '../../../../shared/widgets/banner/app_banner.dart';
|
||||
import '../../../../shared/widgets/fixed_length_code_input.dart';
|
||||
import '../../../../shared/widgets/link_button.dart';
|
||||
import '../../../../shared/widgets/toast/toast.dart';
|
||||
import '../../../../shared/widgets/toast/toast_type.dart';
|
||||
import '../../presentation/cubits/register_cubit.dart';
|
||||
import '../../data/auth_repository.dart';
|
||||
import '../../presentation/cubits/register_cubit.dart';
|
||||
import '../widgets/auth_field.dart';
|
||||
import '../widgets/auth_page_scaffold.dart';
|
||||
import '../widgets/password_field.dart';
|
||||
|
||||
class RegisterScreen extends StatelessWidget {
|
||||
const RegisterScreen({super.key});
|
||||
@@ -75,7 +78,6 @@ class _RegisterViewState extends State<RegisterView> {
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final _inviteCodeController = TextEditingController();
|
||||
bool _obscureText = true;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -131,223 +133,164 @@ class _RegisterViewState extends State<RegisterView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AuthPageScaffold(
|
||||
mainContent: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainContent: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 388),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const AuthHeroHeader(showBrand: true),
|
||||
SizedBox(height: AppSpacing.xxl),
|
||||
BlocBuilder<RegisterCubit, RegisterState>(
|
||||
builder: (context, state) {
|
||||
return AuthSurfaceCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
'创建账号',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.slate900,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
AuthSection(
|
||||
title: '基础信息',
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
AuthField(
|
||||
label: '昵称',
|
||||
hint: '请输入昵称(3-30字符)',
|
||||
controller: _nicknameController,
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
AuthField(
|
||||
label: '邮箱',
|
||||
hint: 'name@example.com',
|
||||
controller: _emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
PasswordField(
|
||||
controller: _passwordController,
|
||||
label: '密码',
|
||||
hint: '请输入至少 6 位密码',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
AuthSection(
|
||||
title: '邀请码(选填)',
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
FixedLengthCodeInput(
|
||||
controller: _inviteCodeController,
|
||||
length: _inviteCodeLength,
|
||||
semanticLabel: '邀请码输入框',
|
||||
uppercase: true,
|
||||
allowedCharacters: _inviteAllowedChars,
|
||||
onChanged: (value) {
|
||||
context
|
||||
.read<RegisterCubit>()
|
||||
.inviteCodeChanged(value);
|
||||
},
|
||||
),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
AppBanner(
|
||||
title: '邀请码',
|
||||
message: '4 位,支持 A-H/J-N/P-Z 与 2-9。',
|
||||
type: ToastType.info,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.errorMessage != null) ...[
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
AppBanner(
|
||||
title: '注册失败',
|
||||
message: state.errorMessage!,
|
||||
type: ToastType.error,
|
||||
),
|
||||
],
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.authPrimaryButton,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppRadius.full,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.authSectionBorder,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppRadius.full,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
const Text(
|
||||
'第 1 步:完善基础信息',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.authLinkMuted,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.lg),
|
||||
AppButton(
|
||||
text: '下一步',
|
||||
onPressed:
|
||||
state.status ==
|
||||
FormzSubmissionStatus.inProgress ||
|
||||
state.isSending
|
||||
? null
|
||||
: _handleNext,
|
||||
isLoading: state.isSending,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
footer: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildAppIcon(),
|
||||
const SizedBox(height: 24),
|
||||
_buildAppTitle(),
|
||||
const SizedBox(height: 24),
|
||||
_buildFormContainer(),
|
||||
const Text(
|
||||
'已有账号?',
|
||||
style: TextStyle(fontSize: 14, color: AppColors.authLinkMuted),
|
||||
),
|
||||
LinkButton(text: '去登录', onTap: () => context.pop()),
|
||||
],
|
||||
),
|
||||
footer: _buildFooter(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAppIcon() {
|
||||
return Container(
|
||||
width: 104,
|
||||
height: 104,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.appIconRing,
|
||||
borderRadius: BorderRadius.circular(52),
|
||||
border: Border.all(color: AppColors.appIconBorder, width: 1),
|
||||
),
|
||||
child: Center(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(38),
|
||||
child: Image.asset(
|
||||
'assets/images/logo.png',
|
||||
width: 76,
|
||||
height: 76,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAppTitle() {
|
||||
return const Text(
|
||||
'linksy',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Playfair Display',
|
||||
fontSize: 34,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: AppColors.appTitle,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFormContainer() {
|
||||
return BlocBuilder<RegisterCubit, RegisterState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
width: 327,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildInput('昵称', '请输入昵称', _nicknameController),
|
||||
const SizedBox(height: 12),
|
||||
_buildInput('邮箱', '请输入邮箱', _emailController),
|
||||
const SizedBox(height: 12),
|
||||
_buildPasswordInput(),
|
||||
const SizedBox(height: 12),
|
||||
_buildInviteCodeInput(),
|
||||
const SizedBox(height: 12),
|
||||
_buildStepIndicator(),
|
||||
if (state.errorMessage != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: AppBanner(
|
||||
message: state.errorMessage!,
|
||||
type: ToastType.error,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
AppButton(
|
||||
text: '下一步',
|
||||
onPressed:
|
||||
state.status == FormzSubmissionStatus.inProgress ||
|
||||
state.isSending
|
||||
? null
|
||||
: _handleNext,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInviteCodeInput() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'邀请码(选填)',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.slate600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
FixedLengthCodeInput(
|
||||
controller: _inviteCodeController,
|
||||
length: _inviteCodeLength,
|
||||
semanticLabel: '邀请码输入框',
|
||||
uppercase: true,
|
||||
allowedCharacters: _inviteAllowedChars,
|
||||
onChanged: (value) {
|
||||
context.read<RegisterCubit>().inviteCodeChanged(value);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'4 位邀请码,支持 A-H/J-N/P-Z 与 2-9',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.slate500,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInput(
|
||||
String label,
|
||||
String hint,
|
||||
TextEditingController controller,
|
||||
) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.slate600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(hintText: hint),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPasswordInput() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'密码',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.slate600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
TextField(
|
||||
controller: _passwordController,
|
||||
obscureText: _obscureText,
|
||||
decoration: InputDecoration(
|
||||
hintText: '请输入至少 6 位密码',
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscureText ? Icons.visibility_off : Icons.visibility,
|
||||
size: 20,
|
||||
color: AppColors.slate400,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscureText = !_obscureText;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStepIndicator() {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary,
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFDCE3EC),
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFooter() {
|
||||
return LinkButton(text: '已有账号?去登录', onTap: () => context.pop());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user