fix:金条详情和编辑,添加一致

This commit is contained in:
jingyun
2026-06-23 14:42:13 +08:00
parent 2e10f01496
commit 428969245d
4 changed files with 1133 additions and 1619 deletions
@@ -239,11 +239,19 @@ class HoldingDetailPage extends ConsumerWidget {
),
const PrototypeDivider(margin: EdgeInsets.zero),
_InfoRow(label: '购买渠道', value: holding.channel ?? '未填'),
const PrototypeDivider(margin: EdgeInsets.zero),
_InfoRow(
label: '备注',
value: holding.note == null ||
holding.note!.trim().isEmpty
? '未填'
: holding.note!.trim(),
),
],
),
),
const SizedBox(height: 12),
_buildImageSection(context, ref, holding),
_buildImageSection(context, holding),
const SizedBox(height: 12),
Row(
children: [
@@ -370,7 +378,6 @@ class HoldingDetailPage extends ConsumerWidget {
Widget _buildImageSection(
BuildContext context,
WidgetRef ref,
GoldAssetHolding holding,
) {
final images = holding.images;
@@ -397,7 +404,6 @@ class HoldingDetailPage extends ConsumerWidget {
for (var index = 0; index < images.length; index++)
_buildDetailImageTile(
context,
ref,
holding,
images[index],
index,
@@ -411,38 +417,19 @@ class HoldingDetailPage extends ConsumerWidget {
Widget _buildDetailImageTile(
BuildContext context,
WidgetRef ref,
GoldAssetHolding holding,
AssetImage image,
int index,
) {
return GestureDetector(
onTap: () => _openImagePreview(context, holding.images, index),
onLongPress: () => _confirmDeleteImage(context, ref, holding, image),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Stack(
children: [
Image.file(
File(image.localPath),
width: 84,
height: 84,
fit: BoxFit.cover,
),
Positioned(
right: 4,
top: 4,
child: Container(
width: 20,
height: 20,
decoration: const BoxDecoration(
color: Color(0xAA000000),
shape: BoxShape.circle,
),
child: const Icon(Icons.remove, size: 14, color: Colors.white),
),
),
],
child: Image.file(
File(image.localPath),
width: 84,
height: 84,
fit: BoxFit.cover,
),
),
);
@@ -463,39 +450,6 @@ class HoldingDetailPage extends ConsumerWidget {
);
}
void _confirmDeleteImage(
BuildContext context,
WidgetRef ref,
GoldAssetHolding holding,
AssetImage image,
) {
showDialog<void>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('删除照片'),
content: const Text('确认删除这张凭证照片吗?'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.of(ctx).pop();
final updated = holding.copyWith(
images: List<AssetImage>.unmodifiable(
holding.images.where((item) => item.localId != image.localId).toList(),
),
updatedAt: DateTime.now(),
);
ref.read(assetPortfolioControllerProvider.notifier).updateHolding(updated);
},
child: const Text('删除'),
),
],
),
);
}
}
class _ActionMenuTile extends StatelessWidget {
@@ -609,233 +563,6 @@ class _ChangeChip extends StatelessWidget {
}
}
class _EditHoldingSheet extends StatefulWidget {
const _EditHoldingSheet({required this.holding, required this.onSave});
final GoldAssetHolding holding;
final ValueChanged<GoldAssetHolding> onSave;
@override
State<_EditHoldingSheet> createState() => _EditHoldingSheetState();
}
class _EditHoldingSheetState extends State<_EditHoldingSheet> {
late final TextEditingController _weightCtrl;
late final TextEditingController _costCtrl;
late final TextEditingController _dateCtrl;
late final TextEditingController _channelCtrl;
late final TextEditingController _noteCtrl;
@override
void initState() {
super.initState();
_weightCtrl = TextEditingController(
text: widget.holding.weightGram.toStringAsFixed(
widget.holding.weightGram % 1 == 0 ? 0 : 1,
),
);
_costCtrl = TextEditingController(
text: widget.holding.costAmount?.toStringAsFixed(0) ?? '',
);
_dateCtrl = TextEditingController(
text: widget.holding.purchaseDate == null
? ''
: '${widget.holding.purchaseDate!.year}-${widget.holding.purchaseDate!.month.toString().padLeft(2, '0')}-${widget.holding.purchaseDate!.day.toString().padLeft(2, '0')}',
);
_channelCtrl = TextEditingController(text: widget.holding.channel ?? '');
_noteCtrl = TextEditingController(text: widget.holding.note ?? '');
}
@override
void dispose() {
_weightCtrl.dispose();
_costCtrl.dispose();
_dateCtrl.dispose();
_channelCtrl.dispose();
_noteCtrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
return SafeArea(
child: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.viewInsetsOf(context).bottom,
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Container(
width: 38,
height: 4,
margin: const EdgeInsets.only(bottom: 14),
decoration: BoxDecoration(
color: const Color(0xFFE3DFD4),
borderRadius: BorderRadius.circular(999),
),
),
),
Row(
children: [
const Spacer(),
const Text(
'编辑持仓',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const Spacer(),
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: const Icon(
Icons.close,
size: 20,
color: Color(0xFF8A8780),
),
),
],
),
const SizedBox(height: 16),
_FieldRow(
label: '金属 / 纯度',
child: Text(
'${widget.holding.metal.label} · ${widget.holding.purityLabel}',
textAlign: TextAlign.right,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(height: 10),
_FieldRow(
label: '克重(克)',
child: TextField(
controller: _weightCtrl,
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
textAlign: TextAlign.right,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
),
),
),
const SizedBox(height: 10),
_FieldRow(
label: '买入成本(含工费)',
child: TextField(
controller: _costCtrl,
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
textAlign: TextAlign.right,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
hintText: '选填',
),
),
),
const SizedBox(height: 10),
_FieldRow(
label: '购买日期',
child: TextField(
controller: _dateCtrl,
textAlign: TextAlign.right,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
hintText: 'YYYY-MM-DD',
),
),
),
const SizedBox(height: 10),
_FieldRow(
label: '购买渠道',
child: TextField(
controller: _channelCtrl,
textAlign: TextAlign.right,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
hintText: '选填',
),
),
),
const SizedBox(height: 10),
_FieldRow(
label: '备注',
child: TextField(
controller: _noteCtrl,
textAlign: TextAlign.right,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
hintText: '点击填写',
),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: () {
final grams = double.tryParse(_weightCtrl.text.trim());
final cost = double.tryParse(_costCtrl.text.trim());
final parsedDate = DateTime.tryParse(
_dateCtrl.text.trim(),
);
widget.onSave(
GoldAssetHolding(
id: widget.holding.id,
name: widget.holding.name,
metal: widget.holding.metal,
category: widget.holding.category,
purity: widget.holding.purity,
purityLabel: widget.holding.purityLabel,
weightGram: grams ?? widget.holding.weightGram,
costAmount: cost,
purchaseDate: parsedDate,
channel: _channelCtrl.text.trim().isEmpty
? null
: _channelCtrl.text.trim(),
note: _noteCtrl.text.trim().isEmpty
? null
: _noteCtrl.text.trim(),
createdAt: widget.holding.createdAt,
updatedAt: DateTime.now(),
status: widget.holding.status,
),
);
},
child: const Text('保存修改'),
),
),
const SizedBox(height: 10),
Text(
'修改后会即时重算资产总值',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11, color: cs.mutedForeground),
),
],
),
),
),
),
);
}
}
class _FieldRow extends StatelessWidget {
const _FieldRow({required this.label, required this.child});