import 'dart:math' as math; import 'package:flutter/material.dart'; import '../core/theme/context_tokens.dart'; import '../core/theme/app_dims.g.dart'; /// 右侧滑入抽屉呈现(对齐原型 .drawer / .drawer-mask)。 /// useRootNavigator:false → 只覆盖内容区(分支 Navigator),不盖侧栏/顶栏。 Future showOrderDetailDrawer( BuildContext context, { required WidgetBuilder builder, }) { return showGeneralDialog( context: context, useRootNavigator: false, barrierDismissible: true, barrierLabel: '关闭', barrierColor: Colors.black.withValues(alpha: 0.32), // .drawer-mask scrim transitionDuration: const Duration(milliseconds: 250), pageBuilder: (ctx, _, __) { final w = math.min(600.0, MediaQuery.of(ctx).size.width * 0.94); return Align( alignment: Alignment.centerRight, child: Material( color: ctx.tokens.surface, elevation: 16, // 抽屉在 app_shell 的全局 SelectionArea 之外(独立 overlay)→ 自带一层,文字可选中复制。 child: SelectionArea( child: SizedBox( width: w, height: double.infinity, child: builder(ctx)), ), ), ); }, transitionBuilder: (ctx, anim, _, child) => SlideTransition( position: Tween(begin: const Offset(1, 0), end: Offset.zero) .animate(CurvedAnimation(parent: anim, curve: Curves.easeOutCubic)), child: child, ), ); } /// 抽屉明细行(对齐原型 .lines .lr,5 列:商品·编码 / 系列·规格 / 数量 / 单价 / 金额)。 class OrderLine { final String name; final String code; final String series; final String spec; final String qty; final String price; // 已算好文案(或「待定价」) final String amount; final bool pending; // 单价<=0:单价/金额显示待定价样式 const OrderLine({ required this.name, required this.code, required this.series, required this.spec, required this.qty, required this.price, required this.amount, this.pending = false, }); } /// 抽屉底部操作分组(对齐原型 .dact-group:标签 + 一排按钮)。 class DrawerActionGroup { final String label; final List buttons; const DrawerActionGroup(this.label, this.buttons); } /// 详情抽屉骨架(对齐原型 openDetail 布局): /// 头(单号+X) / 信息行(.drow) / 明细标题 / 明细表(.lines) / 合计(.ltotal) / 操作组(.dactions)。 class OrderDetailDrawer extends StatelessWidget { final String title; final List<({String label, Widget value})> infoRows; final String linesLabel; // 入库明细 / 出库明细 final List lines; final String totalText; // 合计金额(已格式化) final List actionGroups; const OrderDetailDrawer({ super.key, required this.title, required this.infoRows, required this.linesLabel, required this.lines, required this.totalText, required this.actionGroups, }); @override Widget build(BuildContext context) { final t = context.tokens; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // ── 头:单号 + 关闭 ──(.drawer-head) Container( padding: const EdgeInsets.fromLTRB(20, 18, 16, 18), decoration: BoxDecoration(border: Border(bottom: BorderSide(color: t.border))), child: Row( children: [ Expanded( child: Text(title, style: TextStyle( fontSize: AppDims.fsH2, fontWeight: FontWeight.w700, color: t.heading)), ), IconButton( icon: Icon(Icons.close, size: 22, color: t.muted), splashRadius: 20, onPressed: () => Navigator.of(context).pop(), ), ], ), ), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 18, 20, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // ── 信息行(.drow)── for (final r in infoRows) _DrawerRow(label: r.label, value: r.value), // ── 明细标题 ── Padding( padding: const EdgeInsets.fromLTRB(0, 16, 0, 8), child: Text(linesLabel, style: TextStyle(fontSize: AppDims.fsSm, color: t.muted)), ), _LinesTable(lines: lines), // ── 合计(.ltotal)── Padding( padding: const EdgeInsets.fromLTRB(4, 8, 4, 0), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Text('合计金额 ', style: TextStyle( fontSize: AppDims.fsBody, color: t.muted)), const SizedBox(width: 6), Text(totalText, style: TextStyle( fontSize: 18, fontFamily: 'monospace', fontWeight: FontWeight.w700, color: t.accent)), ], ), ), // ── 操作组(.dactions)── if (actionGroups.isNotEmpty) const SizedBox(height: 18), for (var i = 0; i < actionGroups.length; i++) ...[ if (i > 0) const SizedBox(height: 14), _ActionGroup(group: actionGroups[i]), ], ], ), ), ), ], ); } } class _DrawerRow extends StatelessWidget { final String label; final Widget value; const _DrawerRow({required this.label, required this.value}); @override Widget build(BuildContext context) { final t = context.tokens; return Container( padding: const EdgeInsets.symmetric(vertical: 11), decoration: BoxDecoration( border: Border(bottom: BorderSide(color: t.borderSubtle))), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: TextStyle(fontSize: AppDims.fsBody, color: t.muted)), const Spacer(), DefaultTextStyle( style: TextStyle( fontSize: AppDims.fsBody, fontWeight: FontWeight.w600, color: t.text), child: value, ), ], ), ); } } class _LinesTable extends StatelessWidget { final List lines; const _LinesTable({required this.lines}); @override Widget build(BuildContext context) { final t = context.tokens; return Container( decoration: BoxDecoration( border: Border.all(color: t.border), borderRadius: BorderRadius.circular(AppDims.rMd), ), child: Column( children: [ // 表头 Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9), decoration: BoxDecoration( color: t.thBg, borderRadius: const BorderRadius.vertical(top: Radius.circular(6)), ), child: _lineRow( context, c1: Text('商品 · 编码', style: _hStyle(t), overflow: TextOverflow.ellipsis), c2: Text('系列 / 规格', style: _hStyle(t)), c3: Text('数量', textAlign: TextAlign.right, style: _hStyle(t)), c4: Text('单价', textAlign: TextAlign.right, style: _hStyle(t)), c5: Text('金额', textAlign: TextAlign.right, style: _hStyle(t)), ), ), for (var i = 0; i < lines.length; i++) Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9), decoration: BoxDecoration( border: i == 0 ? null : Border(top: BorderSide(color: t.borderSubtle)), ), child: _lineRow( context, c1: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text(lines[i].name, style: TextStyle( fontSize: AppDims.fsBody, fontWeight: FontWeight.w600, color: t.text)), Text(lines[i].code, style: TextStyle( fontSize: AppDims.fsXs, fontFamily: 'monospace', color: t.faint)), ], ), c2: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text(lines[i].series, style: TextStyle(fontSize: AppDims.fsSm, color: t.text)), Text(lines[i].spec, style: TextStyle(fontSize: AppDims.fsXs, color: t.muted)), ], ), c3: Text(lines[i].qty, textAlign: TextAlign.right, style: const TextStyle(fontFamily: 'monospace')), c4: _priceCell(context, lines[i].price, lines[i].pending), c5: _priceCell(context, lines[i].amount, lines[i].pending, accent: true), ), ), ], ), ); } TextStyle _hStyle(dynamic t) => TextStyle( fontSize: AppDims.fsSm, color: t.muted, fontWeight: FontWeight.w600); Widget _priceCell(BuildContext context, String text, bool pending, {bool accent = false}) { final t = context.tokens; if (pending) { return Text(text, textAlign: TextAlign.right, style: TextStyle( fontSize: AppDims.fsXs, color: t.warn, fontWeight: FontWeight.w600)); } return Text(text, textAlign: TextAlign.right, style: TextStyle( fontFamily: 'monospace', fontWeight: accent ? FontWeight.w700 : FontWeight.w400, color: accent ? t.text : t.text)); } // 5 列布局(对齐原型 grid 1fr 124 40 64 78)。 Widget _lineRow(BuildContext context, {required Widget c1, required Widget c2, required Widget c3, required Widget c4, required Widget c5}) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: c1), const SizedBox(width: 8), SizedBox(width: 96, child: c2), const SizedBox(width: 8), SizedBox(width: 34, child: c3), const SizedBox(width: 8), SizedBox(width: 60, child: c4), const SizedBox(width: 8), SizedBox(width: 72, child: c5), ], ); } } class _ActionGroup extends StatelessWidget { final DrawerActionGroup group; const _ActionGroup({required this.group}); @override Widget build(BuildContext context) { final t = context.tokens; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(group.label, style: TextStyle( fontSize: AppDims.fsXs, color: t.muted, fontWeight: FontWeight.w600, letterSpacing: 0.4)), const SizedBox(height: 8), Wrap(spacing: 10, runSpacing: 10, children: group.buttons), ], ); } }