6238b86dcb
- 登录/注册(login.html/register.html 1:1):两栏卡片+品牌渐变面板+主题小衣服 pill(onSurface 变体)+记住我(记录并预填最近账号)+原型式 toast 校验; 登录 fidelity 1.4–2.1% 三主题全绿;注册暂不入闸(少 门店编号/兑换券 字段, 已记 CONTRACT,screens.mjs 留存根) - ds 原子补齐:DsToast(.toast 单例)/DsCheck(.check/.agree)/DsButton lg 档/ DsSelect 替换全部旧 DropdownButton/DsField label 在上/DsInput 后缀与密码形态 - 全屏统一:对话框按钮全 DsButton、盒式输入主题钉死(visualDensity.standard、 h38、InputDecorationTheme 渗漏修复)、图标全 lucide、JetBrains Mono 三端同源、 BrandMark 真相源 logo、只读模式写操作全量守卫(WriteGuard+DsToast) - 出入库列表:版式对齐原型(卡片对齐+搜索框居中)、KPI 近30天滚动、结清后 失效财务应收应付表;商品编辑抽屉介绍库改搜索下拉、图片双击全屏预览 - 删除旧 UI 死代码:DataTableCard/FormDialog/PageScaffold/SearchChip/ SelectProductDialog/tabStateProvider - golden/fidelity:stock-in/out 补注册(9%)、login 入册(8%)、goldens 全量重打; 修复 pubspec flutter_web_plugins 非法声明(CI pub get 阻断) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
126 lines
4.3 KiB
Dart
126 lines
4.3 KiB
Dart
// screens/auth/auth_shared.dart — 登录/注册两屏共用的原型 1:1 骨架。
|
||
// 对应 login.html / register.html 的公共部分:
|
||
// body:--page 底、居中、pad 24;
|
||
// 主题切换:主界面同款小衣服 ThemePickerPill(onSurface 浅色变体),
|
||
// 放表单面板右上角(2026-07-03 用户拍板,替代原型的 A/B/C 圆钮);
|
||
// .auth:两栏卡片(surface / 1px border / r-xl / sh-3,overflow hidden);
|
||
// .brand:左侧品牌渐变面板 linear-gradient(150deg, side-bg, side-active-bg)。
|
||
// 窄屏(≤ context.isMobile)折叠为单列,隐藏 pts/steps(原型 @media 760px 同构,
|
||
// 断点遵项目规则统一走 isMobile)。
|
||
import 'package:flutter/material.dart';
|
||
|
||
import '../../core/responsive/responsive.dart';
|
||
import '../../core/theme/app_dims.g.dart';
|
||
import '../../core/theme/context_tokens.dart';
|
||
import '../../widgets/theme_picker_pill.dart';
|
||
|
||
/// 整页骨架:page 底 + 居中滚动 + 两栏卡片 + 右上角主题切换器。
|
||
/// [brand]/[form] 为两栏内容;宽屏按 [brandWidth]/[formWidth] 定宽
|
||
/// (其一为 null 表示该栏弹性 1fr),窄屏纵向堆叠。
|
||
class AuthPageScaffold extends StatelessWidget {
|
||
final Widget brand;
|
||
final Widget form;
|
||
final double maxWidth;
|
||
final double narrowMaxWidth;
|
||
final double? brandWidth; // null = 弹性栏
|
||
final double? formWidth; // null = 弹性栏
|
||
const AuthPageScaffold({
|
||
super.key,
|
||
required this.brand,
|
||
required this.form,
|
||
required this.maxWidth,
|
||
required this.narrowMaxWidth,
|
||
this.brandWidth,
|
||
this.formWidth,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final t = context.tokens;
|
||
final narrow = context.isMobile;
|
||
// 表单面板右上角叠主题小衣服(宽/窄屏都落在白底表单区上)
|
||
final formWithPill = Stack(
|
||
children: [
|
||
form,
|
||
const Positioned(
|
||
top: 14, right: 16, child: ThemePickerPill(onSurface: true)),
|
||
],
|
||
);
|
||
final card = Container(
|
||
clipBehavior: Clip.antiAlias,
|
||
decoration: BoxDecoration(
|
||
color: t.surface,
|
||
border: Border.all(color: t.border),
|
||
borderRadius: BorderRadius.circular(AppDims.rXl),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: t.shadow, offset: const Offset(0, 12), blurRadius: 34),
|
||
],
|
||
),
|
||
child: narrow
|
||
? Column(
|
||
mainAxisSize: MainAxisSize.min, children: [brand, formWithPill])
|
||
: IntrinsicHeight(
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
if (brandWidth != null)
|
||
SizedBox(width: brandWidth, child: brand)
|
||
else
|
||
Expanded(child: brand),
|
||
if (formWidth != null)
|
||
SizedBox(width: formWidth, child: formWithPill)
|
||
else
|
||
Expanded(child: formWithPill),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
return Scaffold(
|
||
backgroundColor: t.page,
|
||
body: SingleChildScrollView(
|
||
padding: const EdgeInsets.all(24),
|
||
child: ConstrainedBox(
|
||
constraints: BoxConstraints(
|
||
minHeight: MediaQuery.of(context).size.height - 48),
|
||
child: Center(
|
||
child: ConstrainedBox(
|
||
constraints:
|
||
BoxConstraints(maxWidth: narrow ? narrowMaxWidth : maxWidth),
|
||
child: card,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 品牌渐变面板容器(.brand):linear-gradient(150deg, side-bg, side-active-bg)。
|
||
class AuthBrandPanel extends StatelessWidget {
|
||
final EdgeInsets padding;
|
||
final double? minHeight;
|
||
final Widget child;
|
||
const AuthBrandPanel(
|
||
{super.key, required this.padding, this.minHeight, required this.child});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final t = context.tokens;
|
||
return Container(
|
||
constraints:
|
||
minHeight != null ? BoxConstraints(minHeight: minHeight!) : null,
|
||
padding: padding,
|
||
decoration: BoxDecoration(
|
||
// 150deg ≈ 从左上偏上往右下(CSS 角度顺时针自 12 点起)
|
||
gradient: LinearGradient(
|
||
begin: const Alignment(-0.5, -1),
|
||
end: const Alignment(0.5, 1),
|
||
colors: [t.sideBg, t.sideActiveBg],
|
||
),
|
||
),
|
||
child: child,
|
||
);
|
||
}
|
||
}
|