d1b7e8e2e9
- DsFilterHeader 删除(筛选已全部上移工具栏 chips,屏内零引用) - mobile_list_card 标 LEGACY 禁新用(6 屏迁移 MCard 登记 todo #13) - fidelity 阈值按 2026-07-07 实测收紧:一刀切 8% → 各屏实测最大残差+2pp (4%~7%),新阈下 36 屏×主题全过;proto 基准同步重摄(含库存改版) - CLAUDE.md 修正:表格筛选口径(工具栏 chips+列头仅排序)、响应式导航 (Drawer 退役→底部 5 tab,修文字滞后) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
134 lines
4.3 KiB
Dart
134 lines
4.3 KiB
Dart
import 'package:flutter/material.dart';
|
||
import '../core/theme/context_tokens.dart';
|
||
|
||
// ⚠️ LEGACY(2026-07-07 标注):本组件为旧移动卡片范式(label:value 竖排字段),
|
||
// 新范式 = widgets/ds/m_card.dart 的 MCard(镜像原型 mobile-atoms .m-card,
|
||
// 图标徽章 + 双栏紧凑)。**新屏禁用本组件,一律用 MCard**;存量 6 屏
|
||
// (出入库录单/往来/盘点/基础数据/财务)迁移已登记 todo,迁完删除本文件。
|
||
|
||
/// 移动端列表卡片的单个字段(label: value)。
|
||
class MobileCardField {
|
||
final String label;
|
||
final String? value;
|
||
|
||
/// 自定义值控件(如带颜色的金额、状态徽章);提供后忽略 [value]。
|
||
final Widget? valueWidget;
|
||
const MobileCardField(this.label, this.value, {this.valueWidget});
|
||
}
|
||
|
||
/// 窄屏(手机)列表的通用卡片:标题 + 右上角徽章 + 字段竖排 + 底部操作。
|
||
/// 替代宽屏的表格行,使一行数据在手机上可一屏读完、操作可点。
|
||
class MobileListCard extends StatelessWidget {
|
||
final Widget title;
|
||
final Widget? subtitle;
|
||
final Widget? trailing;
|
||
final List<MobileCardField> fields;
|
||
final List<Widget>? actions;
|
||
final VoidCallback? onTap;
|
||
|
||
const MobileListCard({
|
||
super.key,
|
||
required this.title,
|
||
this.subtitle,
|
||
this.trailing,
|
||
this.fields = const [],
|
||
this.actions,
|
||
this.onTap,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Card(
|
||
margin: EdgeInsets.zero,
|
||
elevation: 0,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
side: BorderSide(color: context.tokens.borderSubtle),
|
||
),
|
||
child: InkWell(
|
||
borderRadius: BorderRadius.circular(8),
|
||
onTap: onTap,
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(12),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
DefaultTextStyle(
|
||
style: TextStyle(
|
||
fontSize: 15,
|
||
fontWeight: FontWeight.w600,
|
||
color: context.tokens.text,
|
||
),
|
||
child: title,
|
||
),
|
||
if (subtitle != null) ...[
|
||
const SizedBox(height: 2),
|
||
DefaultTextStyle(
|
||
style: TextStyle(
|
||
fontSize: 12, color: context.tokens.muted),
|
||
child: subtitle!,
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
if (trailing != null) ...[
|
||
const SizedBox(width: 8),
|
||
trailing!,
|
||
],
|
||
],
|
||
),
|
||
if (fields.isNotEmpty) ...[
|
||
const SizedBox(height: 10),
|
||
...fields.map((f) => _buildField(context, f)),
|
||
],
|
||
if (actions != null && actions!.isNotEmpty) ...[
|
||
const Divider(height: 18),
|
||
Align(
|
||
alignment: Alignment.centerRight,
|
||
child: Wrap(
|
||
spacing: 4,
|
||
children: actions!,
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildField(BuildContext context, MobileCardField f) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 3),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
SizedBox(
|
||
width: 64,
|
||
child: Text(
|
||
f.label,
|
||
style: TextStyle(fontSize: 13, color: context.tokens.muted),
|
||
),
|
||
),
|
||
Expanded(
|
||
child: f.valueWidget ??
|
||
Text(
|
||
f.value ?? '—',
|
||
style: TextStyle(fontSize: 13, color: context.tokens.text),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|