feat(client): 往来/基础数据/商品详情窄屏对齐移动原型(卡片流/图标徽章/sheet 表单)
- 往来单位:窄屏隐藏页内大标题(标题在壳顶栏);MSearchRow 搜索行; 类型 pills 全部/供应商/客户/两者(两者=客户端过滤,服务端 FIND_IN_SET 无独立值); 卡片流类型+状态徽章换图标变体、mc-sub mono 编码 - 基础数据:窄屏隐藏大标题;各 tab 搜索行换 MSearchRow(清空 × 并入,重置钮撤); 档案/字典卡片副行 mono 编码;系列/规格/仓库 新增编辑窄屏走 showMSheet 底部 sheet 轻表单 - 商品详情:窄屏 padding 收紧 sp4;信息卡 Wrap→单列;图片缩略图行窄屏横滚防溢出; 二维码弹窗宽度走 context.dialogWidth - golden:新增 m_partners / m_products / m_products_series(390×844 @2x 三主题); 重生成既有 partners_mobile / products_archive_mobile 基准(桌面 golden 零漂移) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,8 @@ import '../../models/partner.dart';
|
||||
import '../../providers/partner_provider.dart';
|
||||
import '../../widgets/ds/ds_atoms.dart';
|
||||
import '../../widgets/ds/ds_table.dart';
|
||||
import '../../widgets/ds/m_search_row.dart';
|
||||
import '../../widgets/ds/status_icon_map.dart';
|
||||
import '../../widgets/mobile_list_card.dart';
|
||||
import '../../widgets/partner_detail_drawer.dart';
|
||||
import '../../widgets/write_guard.dart';
|
||||
@@ -38,6 +40,9 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
String _typeChip = '全部';
|
||||
|
||||
static const _typeChips = ['全部', '供应商', '客户'];
|
||||
// 窄屏对齐 m-partners 原型 F=['全部','供应商','客户','两者'];
|
||||
// 「两者」后端无独立 type 值(FIND_IN_SET 语义),列表本就一页取全 → 客户端过滤。
|
||||
static const _typeChipsMobile = ['全部', '供应商', '客户', '两者'];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -89,41 +94,42 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
),
|
||||
),
|
||||
data: (result) {
|
||||
final partners = result.data;
|
||||
final mobile = context.isMobile;
|
||||
// 原型 .main{padding:22px 26px};窄屏保持紧凑
|
||||
// 「两者」为窄屏专属 chip:服务端 FIND_IN_SET 无独立值,客户端过滤
|
||||
final partners = _typeChip == '两者'
|
||||
? result.data.where((p) => p.typeLabel == '两者').toList()
|
||||
: result.data;
|
||||
// 原型 .main{padding:22px 26px};窄屏对齐 m-partners .m-scroll{padding:14px}
|
||||
return Container(
|
||||
color: t.bg,
|
||||
padding: mobile
|
||||
? EdgeInsets.zero
|
||||
? const EdgeInsets.fromLTRB(AppDims.sp3, AppDims.sp3, AppDims.sp3, 0)
|
||||
: const EdgeInsets.fromLTRB(26, 22, 26, 22),
|
||||
child: Column(
|
||||
children: [
|
||||
// ── 头部(原型 .head{margin-bottom:18px})──
|
||||
Container(
|
||||
width: double.infinity,
|
||||
color: t.bg,
|
||||
padding: mobile
|
||||
? const EdgeInsets.fromLTRB(
|
||||
AppDims.sp4, AppDims.sp4, AppDims.sp4, AppDims.sp2)
|
||||
: const EdgeInsets.only(bottom: 18),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text('往来单位',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsH1,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: t.heading)),
|
||||
const SizedBox(width: AppDims.sp3),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Text('供应商与客户档案 · 共 ${result.total} 个',
|
||||
// ── 头部(原型 .head{margin-bottom:18px});窄屏隐藏——
|
||||
// 标题由壳顶栏 .m-top data-title 提供(对齐 m-partners)──
|
||||
if (!mobile)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
color: t.bg,
|
||||
padding: const EdgeInsets.only(bottom: 18),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text('往来单位',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsSm, color: t.muted)),
|
||||
),
|
||||
const Spacer(),
|
||||
if (!mobile) ...[
|
||||
fontSize: AppDims.fsH1,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: t.heading)),
|
||||
const SizedBox(width: AppDims.sp3),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Text('供应商与客户档案 · 共 ${result.total} 个',
|
||||
style: TextStyle(
|
||||
fontSize: AppDims.fsSm, color: t.muted)),
|
||||
),
|
||||
const Spacer(),
|
||||
DsButton('刷新',
|
||||
icon: LucideIcons.refreshCw, onPressed: _reload),
|
||||
const SizedBox(width: 10),
|
||||
@@ -134,9 +140,8 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
onPressed: () => _openForm()),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: DsTable(
|
||||
pagerInfoText: '共 ${result.total} 个往来单位',
|
||||
@@ -167,18 +172,61 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// ── toolbar(原型:searchbox 260 + chips + sp + 重置)──
|
||||
// ── toolbar(原型:searchbox 260 + chips + sp + 重置;
|
||||
// 窄屏对齐 m-partners:.m-search 搜索行 + .m-pill 类型横排)──
|
||||
Widget _toolbar(bool mobile, List<Partner> partners) {
|
||||
void onSearch(String v) {
|
||||
_debounce?.cancel();
|
||||
_debounce = Timer(AppConstants.searchDebounce, () {
|
||||
ref.read(partnersScreenProvider.notifier).setKeyword(v.trim());
|
||||
});
|
||||
}
|
||||
|
||||
if (mobile) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
MSearchRow(
|
||||
controller: _searchCtrl,
|
||||
hint: '名称 / 拼音',
|
||||
onChanged: onSearch,
|
||||
),
|
||||
const SizedBox(height: AppDims.sp2),
|
||||
Row(children: [
|
||||
// 类型 pills(原型 m-pill 的既有对应物 DsChip,selected 态)
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(children: [
|
||||
for (final c in _typeChipsMobile) ...[
|
||||
DsChip(
|
||||
label: c,
|
||||
selected: _typeChip == c,
|
||||
caret: false,
|
||||
onTap: () => _setType(c)),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
WriteGuard(
|
||||
child: DsButton('新增',
|
||||
small: true,
|
||||
icon: LucideIcons.plus,
|
||||
variant: DsBtnVariant.primary,
|
||||
onPressed: () => _openForm()),
|
||||
),
|
||||
]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final search = DsSearchBox(
|
||||
controller: _searchCtrl,
|
||||
hint: '名称 / 拼音',
|
||||
width: mobile ? null : 260,
|
||||
onChanged: (v) {
|
||||
_debounce?.cancel();
|
||||
_debounce = Timer(AppConstants.searchDebounce, () {
|
||||
ref.read(partnersScreenProvider.notifier).setKeyword(v.trim());
|
||||
});
|
||||
},
|
||||
width: 260,
|
||||
onChanged: onSearch,
|
||||
);
|
||||
final chips = [
|
||||
for (final c in _typeChips) ...[
|
||||
@@ -209,27 +257,6 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
.toList(),
|
||||
);
|
||||
});
|
||||
if (mobile) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
search,
|
||||
const SizedBox(height: AppDims.sp2),
|
||||
Row(children: [
|
||||
...chips,
|
||||
const Spacer(),
|
||||
WriteGuard(
|
||||
child: DsButton('新增',
|
||||
small: true,
|
||||
icon: LucideIcons.plus,
|
||||
variant: DsBtnVariant.primary,
|
||||
onPressed: () => _openForm()),
|
||||
),
|
||||
]),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Row(children: [
|
||||
search,
|
||||
const SizedBox(width: 10),
|
||||
@@ -312,13 +339,40 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// ── 窄屏卡片(m-partners:右侧类型徽章 + 应收/应付)──
|
||||
/// 类型徽章图标变体(原型 m-partners:badge b-供应商/客户/两者 + BADGE_ICON)。
|
||||
/// 色调与 partnerTypeBadge(partner_detail_drawer)一致,仅多前导图标。
|
||||
DsBadge _typeBadgeIcon(Partner p) => DsBadge(p.typeLabel,
|
||||
tone: switch (p.typeLabel) {
|
||||
'两者' => DsBadgeTone.warn,
|
||||
'客户' => DsBadgeTone.accent,
|
||||
_ => DsBadgeTone.info,
|
||||
},
|
||||
icon: statusIcon(p.typeLabel));
|
||||
|
||||
/// 状态徽章图标变体(原型 badge b-启用/b-停用)。
|
||||
DsBadge _statusBadgeIcon(Partner p) {
|
||||
final label = p.status == 'disabled' ? '停用' : '启用';
|
||||
return DsBadge(label,
|
||||
tone: label == '启用' ? DsBadgeTone.ok : DsBadgeTone.muted,
|
||||
icon: statusIcon(label));
|
||||
}
|
||||
|
||||
// ── 窄屏卡片(m-partners:mc-sub mono 编码 + 右侧类型/状态图标徽章)──
|
||||
Widget _card(Partner p, Map<int, ({double recv, double pay})> bal) {
|
||||
final b = bal[p.id];
|
||||
return MobileListCard(
|
||||
title: Text(p.name),
|
||||
subtitle: (p.code?.isNotEmpty == true) ? Text(p.code!) : null,
|
||||
trailing: partnerTypeBadge(p),
|
||||
subtitle: (p.code?.isNotEmpty == true)
|
||||
? Text(p.code!,
|
||||
style: const TextStyle(
|
||||
fontFamily: AppFonts.mono,
|
||||
fontFamilyFallback: AppFonts.monoFallback))
|
||||
: null,
|
||||
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
_typeBadgeIcon(p),
|
||||
const SizedBox(width: 6),
|
||||
_statusBadgeIcon(p),
|
||||
]),
|
||||
fields: [
|
||||
if (p.contact?.isNotEmpty == true) MobileCardField('联系人', p.contact),
|
||||
if (p.phone?.isNotEmpty == true) MobileCardField('电话', p.phone),
|
||||
|
||||
Reference in New Issue
Block a user