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
599 lines
21 KiB
Dart
599 lines
21 KiB
Dart
// screens/partners/partners_screen.dart — 往来单位(照原型 partners.html 1:1 重建)。
|
|
// 版式:.head(标题+副标+刷新/新增) + .toolbar(搜索 260 + 类型 chips + 重置)
|
|
// + .table 7 列(名称双行/类型徽章/联系人/电话/应收/应付/操作) + .pager 仅文案。
|
|
// 行点击 → 详情抽屉(partner_detail_drawer);新增/编辑 → 弹窗(原型 frow 布局)。
|
|
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
|
|
|
import '../../core/config/app_constants.dart';
|
|
import '../../core/responsive/responsive.dart';
|
|
import '../../core/theme/app_dims.g.dart';
|
|
import '../../core/theme/context_tokens.dart';
|
|
import '../../core/utils/dialog_util.dart';
|
|
import '../../core/utils/export_util.dart';
|
|
import '../../models/partner.dart';
|
|
import '../../providers/partner_provider.dart';
|
|
import '../../widgets/ds/ds_atoms.dart';
|
|
import '../../widgets/ds/ds_table.dart';
|
|
import '../../widgets/mobile_list_card.dart';
|
|
import '../../widgets/partner_detail_drawer.dart';
|
|
import '../../widgets/write_guard.dart';
|
|
import '../../core/theme/app_fonts.dart';
|
|
import '../../widgets/ds/ds_toast.dart';
|
|
|
|
class PartnersScreen extends ConsumerStatefulWidget {
|
|
const PartnersScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<PartnersScreen> createState() => _PartnersScreenState();
|
|
}
|
|
|
|
class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
|
final _searchCtrl = TextEditingController();
|
|
Timer? _debounce;
|
|
// 类型 chips(原型 TYPES 筛选):全部 / 供应商 / 客户;「两者」在两侧均出现
|
|
String _typeChip = '全部';
|
|
|
|
static const _typeChips = ['全部', '供应商', '客户'];
|
|
|
|
@override
|
|
void dispose() {
|
|
_debounce?.cancel();
|
|
_searchCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _setType(String chip) {
|
|
setState(() => _typeChip = chip);
|
|
ref.read(partnersScreenProvider.notifier).setType(switch (chip) {
|
|
'供应商' => 'supplier',
|
|
'客户' => 'customer',
|
|
_ => null,
|
|
});
|
|
}
|
|
|
|
void _clearFilters() {
|
|
_searchCtrl.clear();
|
|
setState(() => _typeChip = '全部');
|
|
final n = ref.read(partnersScreenProvider.notifier);
|
|
n.setKeyword('');
|
|
n.setType(null);
|
|
}
|
|
|
|
void _reload() {
|
|
ref.read(partnersScreenProvider.notifier).reload();
|
|
ref.invalidate(partnerBalancesProvider);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final t = context.tokens;
|
|
final async = ref.watch(partnersScreenProvider);
|
|
final balances = ref.watch(partnerBalancesProvider).valueOrNull ?? const {};
|
|
|
|
return async.when(
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
|
error: (e, _) => Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(LucideIcons.cloudOff, size: 40, color: t.muted),
|
|
const SizedBox(height: 12),
|
|
Text('暂无数据,网络不可用', style: TextStyle(color: t.muted)),
|
|
const SizedBox(height: 12),
|
|
DsButton('重试', variant: DsBtnVariant.primary, onPressed: _reload),
|
|
],
|
|
),
|
|
),
|
|
data: (result) {
|
|
final partners = result.data;
|
|
final mobile = context.isMobile;
|
|
// 原型 .main{padding:22px 26px};窄屏保持紧凑
|
|
return Container(
|
|
color: t.bg,
|
|
padding: mobile
|
|
? EdgeInsets.zero
|
|
: 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} 个',
|
|
style: TextStyle(
|
|
fontSize: AppDims.fsSm, color: t.muted)),
|
|
),
|
|
const Spacer(),
|
|
if (!mobile) ...[
|
|
DsButton('刷新',
|
|
icon: LucideIcons.refreshCw, onPressed: _reload),
|
|
const SizedBox(width: 10),
|
|
WriteGuard(
|
|
child: DsButton('新增往来单位',
|
|
icon: LucideIcons.plus,
|
|
variant: DsBtnVariant.primary,
|
|
onPressed: () => _openForm()),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
Expanded(
|
|
child: DsTable(
|
|
pagerInfoText: '共 ${result.total} 个往来单位',
|
|
emptyText: '没有匹配的往来单位 · 试试调整筛选或搜索',
|
|
toolbar: _toolbar(mobile, partners),
|
|
mobileCards: partners.map((p) => _card(p, balances)).toList(),
|
|
columns: const [
|
|
DsColumn('name', '名称'),
|
|
DsColumn('type', '类型'),
|
|
DsColumn('contact', '联系人'),
|
|
DsColumn('phone', '电话'),
|
|
DsColumn('recv', '应收', numeric: true),
|
|
DsColumn('pay', '应付', numeric: true),
|
|
DsColumn('actions', '操作', action: true),
|
|
],
|
|
rows: partners
|
|
.map((p) => DsRow(
|
|
onTap: () => _openDetail(p, balances),
|
|
cells: _cells(p, balances),
|
|
))
|
|
.toList(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// ── toolbar(原型:searchbox 260 + chips + sp + 重置)──
|
|
Widget _toolbar(bool mobile, List<Partner> partners) {
|
|
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());
|
|
});
|
|
},
|
|
);
|
|
final chips = [
|
|
for (final c in _typeChips) ...[
|
|
DsChip(
|
|
label: c,
|
|
selected: _typeChip == c,
|
|
caret: false,
|
|
onTap: () => _setType(c)),
|
|
const SizedBox(width: 10),
|
|
],
|
|
];
|
|
final reset = DsButton('重置', small: true, onPressed: _clearFilters);
|
|
final export =
|
|
DsButton('导出', small: true, icon: LucideIcons.download, onPressed: () {
|
|
exportExcel(
|
|
filename: '往来单位',
|
|
headers: ['编码', '名称', '类型', '联系人', '电话', '地址', '备注'],
|
|
rows: partners
|
|
.map((p) => [
|
|
p.code ?? '',
|
|
p.name,
|
|
p.typeLabel,
|
|
p.contact ?? '',
|
|
p.phone ?? '',
|
|
p.address ?? '',
|
|
p.remark ?? '',
|
|
])
|
|
.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),
|
|
...chips,
|
|
const Spacer(),
|
|
export,
|
|
const SizedBox(width: 10),
|
|
reset
|
|
]);
|
|
}
|
|
|
|
// ── 表格单元格(原型 7 列)──
|
|
List<Widget> _cells(Partner p, Map<int, ({double recv, double pay})> bal) {
|
|
final t = context.tokens;
|
|
final b = bal[p.id];
|
|
return [
|
|
// 名称双行:.pname + .pcode(地址前 12 字…)
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(p.name,
|
|
style: TextStyle(fontWeight: FontWeight.w600, color: t.heading)),
|
|
// 地址为空则不渲染副行(不占位、不显示「—」),行高回落单行
|
|
if (p.address?.isNotEmpty == true)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 2),
|
|
child: Text(_addrBrief(p.address!),
|
|
style: TextStyle(
|
|
fontSize: AppDims.fsXs,
|
|
color: t.faint,
|
|
fontFamily: AppFonts.mono,
|
|
fontFamilyFallback: AppFonts.monoFallback)),
|
|
),
|
|
],
|
|
),
|
|
partnerTypeBadge(p),
|
|
Text((p.contact?.isNotEmpty == true) ? p.contact! : '—'),
|
|
Text((p.phone?.isNotEmpty == true) ? p.phone! : '—',
|
|
style: const TextStyle(
|
|
fontFamily: AppFonts.mono,
|
|
fontFamilyFallback: AppFonts.monoFallback)),
|
|
Text(fmtYuan(b?.recv ?? 0),
|
|
style: const TextStyle(
|
|
fontFamily: AppFonts.mono,
|
|
fontFamilyFallback: AppFonts.monoFallback)),
|
|
Text(fmtYuan(b?.pay ?? 0),
|
|
style: const TextStyle(
|
|
fontFamily: AppFonts.mono,
|
|
fontFamilyFallback: AppFonts.monoFallback)),
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_iconBtn(LucideIcons.eye, '详情', () => _openDetail(p, bal)),
|
|
const SizedBox(width: 8),
|
|
WriteGuard(
|
|
child: _iconBtn(LucideIcons.pencil, '编辑', () => _openForm(p)),
|
|
),
|
|
],
|
|
),
|
|
];
|
|
}
|
|
|
|
/// 原型 .pcode = addr.slice(0,12)…(地址前 12 字 + 省略号)
|
|
String _addrBrief(String addr) =>
|
|
addr.length > 12 ? '${addr.substring(0, 12)}…' : addr;
|
|
|
|
Widget _iconBtn(IconData icon, String tip, VoidCallback onTap) {
|
|
final t = context.tokens;
|
|
return Tooltip(
|
|
message: tip,
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
borderRadius: BorderRadius.circular(AppDims.rSm),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(2),
|
|
child: Icon(icon, size: 16, color: t.muted),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ── 窄屏卡片(m-partners:右侧类型徽章 + 应收/应付)──
|
|
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),
|
|
fields: [
|
|
if (p.contact?.isNotEmpty == true) MobileCardField('联系人', p.contact),
|
|
if (p.phone?.isNotEmpty == true) MobileCardField('电话', p.phone),
|
|
MobileCardField('应收', fmtYuan(b?.recv ?? 0)),
|
|
MobileCardField('应付', fmtYuan(b?.pay ?? 0)),
|
|
],
|
|
onTap: () => _openDetail(p, bal),
|
|
);
|
|
}
|
|
|
|
void _openDetail(Partner p, Map<int, ({double recv, double pay})> bal) {
|
|
showPartnerDetailDrawer(
|
|
context,
|
|
partner: p,
|
|
balance: bal[p.id],
|
|
onEdit: WriteGuard.isReadonly(ref) ? null : () => _openForm(p),
|
|
);
|
|
}
|
|
|
|
void _openForm([Partner? partner]) {
|
|
showAppDialog(
|
|
context: context,
|
|
builder: (ctx) => _PartnerFormDialog(
|
|
partner: partner,
|
|
onSaved: _reload,
|
|
onDelete: partner == null ? null : () => _confirmDelete(partner),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _confirmDelete(Partner partner) async {
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: const Text('确认删除'),
|
|
content: Text('确认删除「${partner.name}」?此操作不可恢复。'),
|
|
actions: [
|
|
DsButton('取消', onPressed: () => Navigator.of(ctx).pop(false)),
|
|
DsButton('删除',
|
|
variant: DsBtnVariant.danger,
|
|
onPressed: () => Navigator.of(ctx).pop(true)),
|
|
],
|
|
),
|
|
);
|
|
if (confirmed == true && mounted) {
|
|
try {
|
|
await ref
|
|
.read(partnersScreenProvider.notifier)
|
|
.deletePartner(partner.id);
|
|
if (mounted) {
|
|
showDsToast(context, '删除成功', bg: context.tokens.success);
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
showDsToast(context, '删除失败:$e', bg: context.tokens.danger);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── 新增/编辑弹窗(原型 openAdd 的 frow 布局:名称*+类型 / 联系人+电话 / 地址 /
|
|
// 期初余额 + 备注(备注为既有能力保留))──
|
|
class _PartnerFormDialog extends ConsumerStatefulWidget {
|
|
final Partner? partner;
|
|
final VoidCallback onSaved;
|
|
final VoidCallback? onDelete;
|
|
|
|
const _PartnerFormDialog(
|
|
{this.partner, required this.onSaved, this.onDelete});
|
|
|
|
@override
|
|
ConsumerState<_PartnerFormDialog> createState() => _PartnerFormDialogState();
|
|
}
|
|
|
|
class _PartnerFormDialogState extends ConsumerState<_PartnerFormDialog> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
late final TextEditingController _nameCtrl;
|
|
late final TextEditingController _contactCtrl;
|
|
late final TextEditingController _phoneCtrl;
|
|
late final TextEditingController _addressCtrl;
|
|
late final TextEditingController _balanceCtrl;
|
|
late final TextEditingController _remarkCtrl;
|
|
late String _type; // supplier / customer / supplier,customer
|
|
bool _saving = false;
|
|
|
|
static const _typeOptions = {
|
|
'supplier': '供应商',
|
|
'customer': '客户',
|
|
'supplier,customer': '两者',
|
|
};
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
final p = widget.partner;
|
|
_nameCtrl = TextEditingController(text: p?.name ?? '');
|
|
_contactCtrl = TextEditingController(text: p?.contact ?? '');
|
|
_phoneCtrl = TextEditingController(text: p?.phone ?? '');
|
|
_addressCtrl = TextEditingController(text: p?.address ?? '');
|
|
_balanceCtrl = TextEditingController(
|
|
text: (p?.balance ?? 0) == 0 ? '' : '${p!.balance}');
|
|
_remarkCtrl = TextEditingController(text: p?.remark ?? '');
|
|
// SET 值可能乱序(customer,supplier);归一化到选项 key
|
|
final raw = p?.type ?? 'supplier';
|
|
final isS = raw.contains('supplier'), isC = raw.contains('customer');
|
|
_type = isS && isC ? 'supplier,customer' : (isC ? 'customer' : 'supplier');
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_nameCtrl.dispose();
|
|
_contactCtrl.dispose();
|
|
_phoneCtrl.dispose();
|
|
_addressCtrl.dispose();
|
|
_balanceCtrl.dispose();
|
|
_remarkCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _save() async {
|
|
if (!_formKey.currentState!.validate()) return;
|
|
setState(() => _saving = true);
|
|
final data = {
|
|
'name': _nameCtrl.text.trim(),
|
|
'type': _type,
|
|
'contact': _contactCtrl.text.trim(),
|
|
'phone': _phoneCtrl.text.trim(),
|
|
'address': _addressCtrl.text.trim(),
|
|
'balance': double.tryParse(_balanceCtrl.text.trim()) ?? 0,
|
|
'remark': _remarkCtrl.text.trim(),
|
|
if (widget.partner?.code != null) 'code': widget.partner!.code,
|
|
};
|
|
try {
|
|
final notifier = ref.read(partnersScreenProvider.notifier);
|
|
if (widget.partner != null) {
|
|
await notifier.updatePartner(widget.partner!.id, data);
|
|
} else {
|
|
await notifier.createPartner(data);
|
|
}
|
|
if (mounted) {
|
|
Navigator.of(context).pop();
|
|
widget.onSaved();
|
|
showDsToast(context, widget.partner != null ? '更新成功' : '创建成功',
|
|
bg: context.tokens.success);
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
showDsToast(context, '保存失败:$e', bg: context.tokens.danger);
|
|
}
|
|
} finally {
|
|
if (mounted) setState(() => _saving = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isEdit = widget.partner != null;
|
|
return Dialog(
|
|
child: Container(
|
|
width: context.dialogWidth(560),
|
|
padding: const EdgeInsets.all(24),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(isEdit ? '编辑往来单位' : '新增往来单位',
|
|
style: const TextStyle(
|
|
fontSize: 18, fontWeight: FontWeight.w600)),
|
|
const SizedBox(height: 20),
|
|
// frow1:名称* + 类型*
|
|
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
Expanded(
|
|
child: DsField('名称',
|
|
required: true,
|
|
input: TextFormField(
|
|
controller: _nameCtrl,
|
|
decoration:
|
|
const InputDecoration(hintText: '如:茅台华东总代'),
|
|
validator: (v) =>
|
|
(v == null || v.trim().isEmpty) ? '不能为空' : null,
|
|
)),
|
|
),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: DsField('类型',
|
|
required: true,
|
|
input: DsSelect<String>(
|
|
value: _type,
|
|
options: [
|
|
for (final e in _typeOptions.entries)
|
|
(e.key, e.value),
|
|
],
|
|
onChanged: (v) => setState(() => _type = v),
|
|
)),
|
|
),
|
|
]),
|
|
const SizedBox(height: 14),
|
|
// frow2:联系人 + 电话
|
|
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
Expanded(
|
|
child: DsField('联系人',
|
|
input: TextFormField(
|
|
controller: _contactCtrl,
|
|
decoration: const InputDecoration(hintText: '姓名'),
|
|
)),
|
|
),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: DsField('电话',
|
|
input: TextFormField(
|
|
controller: _phoneCtrl,
|
|
decoration:
|
|
const InputDecoration(hintText: '手机 / 座机'),
|
|
)),
|
|
),
|
|
]),
|
|
const SizedBox(height: 14),
|
|
// frow3:地址(单列占满)
|
|
DsField('地址',
|
|
input: TextFormField(
|
|
controller: _addressCtrl,
|
|
decoration:
|
|
const InputDecoration(hintText: '省 / 市 / 详细地址'),
|
|
)),
|
|
const SizedBox(height: 14),
|
|
// frow4:期初余额 + 备注
|
|
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
Expanded(
|
|
child: DsField('期初余额',
|
|
input: TextFormField(
|
|
controller: _balanceCtrl,
|
|
keyboardType: const TextInputType.numberWithOptions(
|
|
decimal: true),
|
|
decoration: const InputDecoration(hintText: '0.00'),
|
|
)),
|
|
),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: DsField('备注',
|
|
input: TextFormField(
|
|
controller: _remarkCtrl,
|
|
decoration: const InputDecoration(hintText: '备注说明'),
|
|
)),
|
|
),
|
|
]),
|
|
const SizedBox(height: 24),
|
|
Row(
|
|
children: [
|
|
if (widget.onDelete != null)
|
|
WriteGuard(
|
|
child: DsButton('删除', variant: DsBtnVariant.danger,
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
widget.onDelete!();
|
|
}),
|
|
),
|
|
const Spacer(),
|
|
DsButton('取消',
|
|
onPressed: () => Navigator.of(context).pop()),
|
|
const SizedBox(width: 10),
|
|
DsButton(_saving ? '保存中…' : '保存',
|
|
variant: DsBtnVariant.primary,
|
|
onPressed: _saving ? null : _save),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|