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:
qzl
2026-03-13 14:10:13 +08:00
parent fb3c649db7
commit a10a2db27a
100 changed files with 6333 additions and 4800 deletions
@@ -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());
}
}