feat(client): 库存/出入库移动卡片与详情 sheet 逐一对齐原型结构
- 新增 MCard(原型 .m-card 双栏:mc-main 标题+副行 mono | mc-right 徽章行+ 金额 mono | › | mc-foot ⓘ脚注)+ DsIconBadge(.badge.ico 22px 纯图标)/ DsTextIconBadge(.badge.bi 图标+文字) + statusToneOf 状态词配色表—— 替换原 MobileListCard「标签:值」布局与各屏本地 _icoBadge/_statusColors - 出入库卡:单号 + 供应商/客户·N 项 + 状态/退单/待定价图标徽章 + 合计金额 + 日期·经手人·审核 脚注;库存卡:品名 + 编码·规格 + 状态徽章 + 数量(缺货红/ 预警黄);移动卡不再有行内操作(对齐原型,操作统一在详情 sheet;打标签= 打印,移动端裁剪) - DsTable 窄屏卡片流去外层大卡容器(卡直接铺页面底色,对齐原型 m-scroll)—— 往来/基础数据等全部卡片流屏同步受益 - 出入库详情窄屏改 sheet 专属布局(原型 openOrder):drow 字段行 + 明细 pe-card(头行 图标+「明细 · N 项」+ 行=品名+系列·×数量 | 金额/待定价徽章)+ 合计 drow + 等分按钮行(无组标签);桌面右滑抽屉布局不变 - 相关移动 golden 全量重生成(出入库/库存/往来/基础数据 ×3 主题), 桌面 golden 零漂移;283 测试全绿 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -85,18 +85,22 @@ class _DsTableState extends State<DsTable> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.tokens;
|
||||
// 窄屏卡片流对齐原型 m-scroll:卡直接铺页面底色,无外层大卡容器
|
||||
final mobileCardsMode = context.isMobile && widget.mobileCards != null;
|
||||
return Column(
|
||||
mainAxisSize: widget.shrinkWrap ? MainAxisSize.min : MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_maybeExpand(
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: t.surface,
|
||||
border: Border.all(color: t.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rLg),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: mobileCardsMode
|
||||
? null
|
||||
: BoxDecoration(
|
||||
color: t.surface,
|
||||
border: Border.all(color: t.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rLg),
|
||||
),
|
||||
clipBehavior: mobileCardsMode ? Clip.none : Clip.antiAlias,
|
||||
child: Column(
|
||||
// 整宽拉伸:toolbar/表格都填满卡片宽度(否则 toolbar 按内容宽居中→左侧留白)。
|
||||
mainAxisSize:
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
// widgets/ds/m_card.dart — 移动列表卡(镜像原型 mobile-atoms .m-card / .m-row 结构)
|
||||
// 与 .badge.ico(22px 纯图标徽章)/.badge.bi(图标+文字徽章)。
|
||||
//
|
||||
// 卡结构:mc-main(mc-nm 标题 + mc-sub 副行 mono) | mc-right(徽章行 + mc-amt 金额)
|
||||
// | mc-chev ›;可选 mc-foot 脚注行(ⓘ 前缀,上分隔线)。
|
||||
// 徽章色调按状态词查表(statusToneOf),图标按 status_icon_map(原型 BADGE_ICON)。
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
|
||||
import '../../core/theme/app_dims.g.dart';
|
||||
import '../../core/theme/app_fonts.dart';
|
||||
import '../../core/theme/context_tokens.dart';
|
||||
import 'status_icon_map.dart';
|
||||
|
||||
/// 状态词 → (前景, 软底)。对齐原型 .badge.b-* 的语义配色。
|
||||
(Color fg, Color bg) statusToneOf(BuildContext context, String label) {
|
||||
final t = context.tokens;
|
||||
switch (label) {
|
||||
case '待审核':
|
||||
case '待定价':
|
||||
case '部分退单':
|
||||
case '预警':
|
||||
case '进行中':
|
||||
case '未结清':
|
||||
return (t.warn, t.warnBg);
|
||||
case '已审核':
|
||||
case '已通过':
|
||||
case '在售':
|
||||
case '启用':
|
||||
case '已完成':
|
||||
case '已结清':
|
||||
case '已兑换':
|
||||
case '在线':
|
||||
return (t.success, t.successBg);
|
||||
case '已拒绝':
|
||||
case '已驳回':
|
||||
case '已退单':
|
||||
case '缺货':
|
||||
case '停用':
|
||||
case '作废':
|
||||
case '离线':
|
||||
return (t.danger, t.dangerBg);
|
||||
case '草稿':
|
||||
return (t.muted, t.infoSoft);
|
||||
default:
|
||||
return (t.muted, t.borderSubtle);
|
||||
}
|
||||
}
|
||||
|
||||
/// 纯图标徽章(原型 .badge.ico):22×22 圆 pill,13px 状态图标,长按出状态词。
|
||||
class DsIconBadge extends StatelessWidget {
|
||||
final String label;
|
||||
const DsIconBadge(this.label, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final (fg, bg) = statusToneOf(context, label);
|
||||
return Tooltip(
|
||||
message: label,
|
||||
child: Container(
|
||||
width: 22,
|
||||
height: 22,
|
||||
decoration: BoxDecoration(
|
||||
color: bg, borderRadius: BorderRadius.circular(AppDims.rPill)),
|
||||
alignment: Alignment.center,
|
||||
child:
|
||||
Icon(statusIcon(label) ?? LucideIcons.circle, size: 13, color: fg),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 图标 + 文字徽章(原型 .badge.bi):h22 pill,12px 图标替圆点。
|
||||
class DsTextIconBadge extends StatelessWidget {
|
||||
final String label;
|
||||
const DsTextIconBadge(this.label, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final (fg, bg) = statusToneOf(context, label);
|
||||
return Container(
|
||||
height: 22,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 9),
|
||||
decoration: BoxDecoration(
|
||||
color: bg, borderRadius: BorderRadius.circular(AppDims.rPill)),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(statusIcon(label) ?? LucideIcons.circle, size: 12, color: fg),
|
||||
const SizedBox(width: 5),
|
||||
Text(label,
|
||||
style: TextStyle(
|
||||
color: fg,
|
||||
fontSize: AppDims.fsSm,
|
||||
fontWeight: FontWeight.w600)),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 移动列表卡(原型 .m-card 双栏结构)。
|
||||
class MCard extends StatelessWidget {
|
||||
/// mc-nm 标题(600 heading)。
|
||||
final String nm;
|
||||
|
||||
/// mc-sub 副行(xs muted mono);null 不渲染。
|
||||
final String? sub;
|
||||
|
||||
/// mc-right 上行徽章(DsIconBadge / DsBadge 等),横排 gap5。
|
||||
final List<Widget> badges;
|
||||
|
||||
/// mc-amt 金额/数量(700 heading mono);amtWidget 优先。
|
||||
final String? amt;
|
||||
final Widget? amtWidget;
|
||||
|
||||
/// mc-foot 脚注(ⓘ 前缀 + xs muted,上分隔线);null 不渲染。
|
||||
final String? foot;
|
||||
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const MCard({
|
||||
super.key,
|
||||
required this.nm,
|
||||
this.sub,
|
||||
this.badges = const [],
|
||||
this.amt,
|
||||
this.amtWidget,
|
||||
this.foot,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.tokens;
|
||||
return Material(
|
||||
color: t.surface,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(color: t.border),
|
||||
borderRadius: BorderRadius.circular(AppDims.rLg),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 13, 14, 13),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// mc-main
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(nm,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsBody,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: t.heading)),
|
||||
if (sub != null) ...[
|
||||
const SizedBox(height: 3),
|
||||
Text(sub!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsXs,
|
||||
color: t.muted,
|
||||
fontFamily: AppFonts.mono,
|
||||
fontFamilyFallback: AppFonts.monoFallback)),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// mc-right
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (badges.isNotEmpty)
|
||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
for (var i = 0; i < badges.length; i++) ...[
|
||||
if (i > 0) const SizedBox(width: 5),
|
||||
badges[i],
|
||||
],
|
||||
]),
|
||||
if (amtWidget != null || amt != null) ...[
|
||||
if (badges.isNotEmpty) const SizedBox(height: 5),
|
||||
amtWidget ??
|
||||
Text(amt!,
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsBody,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: t.heading,
|
||||
fontFamily: AppFonts.mono,
|
||||
fontFamilyFallback: AppFonts.monoFallback)),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// mc-chev
|
||||
Icon(LucideIcons.chevronRight, size: 18, color: t.faint),
|
||||
],
|
||||
),
|
||||
if (foot != null)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(top: BorderSide(color: t.borderSubtle)),
|
||||
),
|
||||
child: Row(children: [
|
||||
Icon(LucideIcons.info, size: 13, color: t.muted),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(foot!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsXs, color: t.muted)),
|
||||
),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user