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
93 lines
2.9 KiB
Dart
93 lines
2.9 KiB
Dart
// widgets/ds/ds_toast.dart — 原型 .toast(atoms.css)1:1:
|
||
// fixed bottom:26 水平居中 / --toast-bg 底 + 白字 fs-body / pad 11 18 / r-md /
|
||
// **内容自适应宽**(不定宽、不贯穿)/ .25s 淡入+上滑 / 2.2s 自动消失。
|
||
// 单例复用:新消息顶替旧消息(对齐原型同一 #toast 节点 + 计时器重置),
|
||
// 天然避免多条提示叠罗汉。语义底色(成功/失败)由调用点传 [bg] 覆盖。
|
||
import 'dart:async';
|
||
|
||
import 'package:flutter/material.dart';
|
||
|
||
import '../../core/theme/app_dims.g.dart';
|
||
import '../../core/theme/context_tokens.dart';
|
||
|
||
OverlayEntry? _entry;
|
||
Timer? _timer;
|
||
|
||
void showDsToast(
|
||
BuildContext context,
|
||
String message, {
|
||
Color? bg,
|
||
Duration duration = const Duration(milliseconds: 2200),
|
||
}) {
|
||
final overlay = Overlay.maybeOf(context, rootOverlay: true);
|
||
if (overlay == null) return;
|
||
final color = bg ?? context.tokens.toastBg;
|
||
|
||
_timer?.cancel();
|
||
if (_entry?.mounted ?? false) _entry!.remove();
|
||
_entry = null;
|
||
final entry = OverlayEntry(
|
||
builder: (_) => _DsToast(message: message, bg: color),
|
||
);
|
||
_entry = entry;
|
||
overlay.insert(entry);
|
||
_timer = Timer(duration, () {
|
||
if (_entry == entry) {
|
||
if (entry.mounted) entry.remove();
|
||
_entry = null;
|
||
}
|
||
});
|
||
}
|
||
|
||
class _DsToast extends StatelessWidget {
|
||
final String message;
|
||
final Color bg;
|
||
const _DsToast({required this.message, required this.bg});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Positioned(
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 26,
|
||
child: IgnorePointer(
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
TweenAnimationBuilder<double>(
|
||
tween: Tween(begin: 0, end: 1),
|
||
duration: const Duration(milliseconds: 250),
|
||
curve: Curves.easeOut,
|
||
builder: (_, v, child) => Opacity(
|
||
opacity: v,
|
||
child: Transform.translate(
|
||
offset: Offset(0, 20 * (1 - v)), child: child),
|
||
),
|
||
child: Material(
|
||
color: Colors.transparent,
|
||
child: Container(
|
||
constraints: const BoxConstraints(maxWidth: 560),
|
||
padding:
|
||
const EdgeInsets.symmetric(horizontal: 18, vertical: 11),
|
||
decoration: BoxDecoration(
|
||
color: bg,
|
||
borderRadius: BorderRadius.circular(AppDims.rMd),
|
||
),
|
||
child: Text(
|
||
message,
|
||
style: const TextStyle(
|
||
fontSize: AppDims.fsBody,
|
||
color: Colors
|
||
.white, // ds-ignore: toast 白字固定(承 toast-bg/语义色底)
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|