feat(client): 登录/注册页照原型重建,ds 真相源组件族统一全部屏
- 登录/注册(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
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import '../../core/utils/dialog_util.dart';
|
||||
import 'dart:typed_data';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -21,6 +21,9 @@ import '../../providers/product_provider.dart';
|
||||
import '../../providers/inventory_provider.dart';
|
||||
import '../../providers/shop_provider.dart' show shopInfoProvider;
|
||||
import '../../widgets/kpi_card.dart';
|
||||
import '../../core/theme/app_fonts.dart';
|
||||
import '../../widgets/ds/ds_atoms.dart';
|
||||
import '../../widgets/ds/ds_toast.dart';
|
||||
|
||||
class ProductDetailScreen extends ConsumerStatefulWidget {
|
||||
final int productId;
|
||||
@@ -114,15 +117,11 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
_descChanged = false;
|
||||
});
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('描述已保存')),
|
||||
);
|
||||
showDsToast(context, '描述已保存');
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('保存失败:$e'), backgroundColor: context.tokens.danger),
|
||||
);
|
||||
showDsToast(context, '保存失败:$e', bg: context.tokens.danger);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _savingDesc = false);
|
||||
@@ -153,12 +152,12 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.camera_alt_outlined),
|
||||
leading: const Icon(LucideIcons.camera),
|
||||
title: const Text('拍照'),
|
||||
onTap: () => Navigator.pop(ctx, ImageSource.camera),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_library_outlined),
|
||||
leading: const Icon(LucideIcons.images),
|
||||
title: const Text('从相册选择'),
|
||||
onTap: () => Navigator.pop(ctx, ImageSource.gallery),
|
||||
),
|
||||
@@ -202,15 +201,11 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
bytes: bytes, fileName: fileName);
|
||||
_updateImages([..._product!.images, img]);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('图片上传成功')),
|
||||
);
|
||||
showDsToast(context, '图片上传成功');
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('上传失败:$e'), backgroundColor: context.tokens.danger),
|
||||
);
|
||||
showDsToast(context, '上传失败:$e', bg: context.tokens.danger);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _uploading = false);
|
||||
@@ -219,13 +214,13 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
|
||||
Future<void> _deleteImage(ProductImage img) async {
|
||||
try {
|
||||
await ref.read(productRepositoryProvider).deleteImage(_product!.id, img.id);
|
||||
await ref
|
||||
.read(productRepositoryProvider)
|
||||
.deleteImage(_product!.id, img.id);
|
||||
_updateImages(_product!.images.where((i) => i.id != img.id).toList());
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('删除失败:$e'), backgroundColor: context.tokens.danger),
|
||||
);
|
||||
showDsToast(context, '删除失败:$e', bg: context.tokens.danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,13 +229,27 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
final p = _product!;
|
||||
setState(() {
|
||||
_product = Product(
|
||||
id: p.id, publicId: p.publicId, code: p.code, barcode: p.barcode,
|
||||
name: p.name, series: p.series, spec: p.spec, unit: p.unit,
|
||||
categoryId: p.categoryId, brand: p.brand, purchasePrice: p.purchasePrice,
|
||||
salePrice: p.salePrice, minStock: p.minStock, description: p.description,
|
||||
remark: p.remark, customFields: p.customFields, images: images,
|
||||
originName: p.originName, shelfLifeName: p.shelfLifeName,
|
||||
storageName: p.storageName, categoryName: p.categoryName,
|
||||
id: p.id,
|
||||
publicId: p.publicId,
|
||||
code: p.code,
|
||||
barcode: p.barcode,
|
||||
name: p.name,
|
||||
series: p.series,
|
||||
spec: p.spec,
|
||||
unit: p.unit,
|
||||
categoryId: p.categoryId,
|
||||
brand: p.brand,
|
||||
purchasePrice: p.purchasePrice,
|
||||
salePrice: p.salePrice,
|
||||
minStock: p.minStock,
|
||||
description: p.description,
|
||||
remark: p.remark,
|
||||
customFields: p.customFields,
|
||||
images: images,
|
||||
originName: p.originName,
|
||||
shelfLifeName: p.shelfLifeName,
|
||||
storageName: p.storageName,
|
||||
categoryName: p.categoryName,
|
||||
descriptionDocTitle: p.descriptionDocTitle,
|
||||
descriptionDocContent: p.descriptionDocContent,
|
||||
);
|
||||
@@ -250,7 +259,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
void _showQRCode() {
|
||||
showAppDialog(
|
||||
context: context,
|
||||
builder: (_) => _QRCodeDialog(productId: widget.productId, product: _product!),
|
||||
builder: (_) =>
|
||||
_QRCodeDialog(productId: widget.productId, product: _product!),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -260,7 +270,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
body: FutureBuilder<Product>(
|
||||
future: _future,
|
||||
builder: (context, snap) {
|
||||
if (snap.connectionState == ConnectionState.waiting && _product == null) {
|
||||
if (snap.connectionState == ConnectionState.waiting &&
|
||||
_product == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (snap.hasError && _product == null) {
|
||||
@@ -268,7 +279,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.error_outline, size: 40, color: context.tokens.danger),
|
||||
Icon(LucideIcons.circleAlert,
|
||||
size: 40, color: context.tokens.danger),
|
||||
const SizedBox(height: 12),
|
||||
Text('加载失败:${snap.error}'),
|
||||
const SizedBox(height: 12),
|
||||
@@ -287,7 +299,7 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
icon: const Icon(LucideIcons.arrowLeft),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
tooltip: '返回',
|
||||
padding: EdgeInsets.zero,
|
||||
@@ -301,7 +313,7 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _showQRCode,
|
||||
icon: const Icon(Icons.qr_code, size: 16),
|
||||
icon: const Icon(LucideIcons.qrCode, size: 16),
|
||||
label: const Text('二维码'),
|
||||
),
|
||||
],
|
||||
@@ -346,7 +358,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
|
||||
// ── 富数据:KPI / 各仓库分布 / 近期流水(还原原型详情页) ──────────────
|
||||
|
||||
Widget _statCard({required String title, Widget? hint, required Widget body}) {
|
||||
Widget _statCard(
|
||||
{required String title, Widget? hint, required Widget body}) {
|
||||
return Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -392,18 +405,18 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
KpiCard(
|
||||
title: '当前库存',
|
||||
value: currentStock.toStringAsFixed(0),
|
||||
icon: Icons.inventory_2,
|
||||
icon: LucideIcons.box,
|
||||
tone: KpiTone.info,
|
||||
delta: '件'),
|
||||
KpiCard(
|
||||
title: '库存货值',
|
||||
value: '¥${(stockValue / 10000).toStringAsFixed(2)}万',
|
||||
icon: Icons.account_balance_wallet,
|
||||
icon: LucideIcons.wallet,
|
||||
tone: KpiTone.ok),
|
||||
KpiCard(
|
||||
title: '近30天出库',
|
||||
value: out30.toStringAsFixed(0),
|
||||
icon: Icons.upload_outlined,
|
||||
icon: LucideIcons.upload,
|
||||
tone: KpiTone.blue,
|
||||
delta: '件'),
|
||||
];
|
||||
@@ -435,7 +448,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
return _statCard(
|
||||
title: '各仓库库存分布',
|
||||
hint: Text('合计 ${total.toStringAsFixed(0)} 件',
|
||||
style: TextStyle(fontSize: AppDims.fsXs, color: context.tokens.muted)),
|
||||
style:
|
||||
TextStyle(fontSize: AppDims.fsXs, color: context.tokens.muted)),
|
||||
body: Column(
|
||||
children: byWh.entries.map((e) {
|
||||
return Padding(
|
||||
@@ -465,7 +479,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
return _statCard(
|
||||
title: '价格历史',
|
||||
hint: Text('进价变更',
|
||||
style: TextStyle(fontSize: AppDims.fsXs, color: context.tokens.muted)),
|
||||
style:
|
||||
TextStyle(fontSize: AppDims.fsXs, color: context.tokens.muted)),
|
||||
body: Column(
|
||||
children: _prices.asMap().entries.map((e) {
|
||||
final i = e.key;
|
||||
@@ -487,16 +502,18 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
width: 96,
|
||||
child: Text(p.date,
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: AppFonts.mono,
|
||||
fontFamilyFallback: AppFonts.monoFallback,
|
||||
fontSize: AppDims.fsSm,
|
||||
color: context.tokens.muted)),
|
||||
),
|
||||
if (up || down)
|
||||
Icon(up ? Icons.arrow_drop_up : Icons.arrow_drop_down,
|
||||
Icon(up ? LucideIcons.chevronUp : LucideIcons.chevronDown,
|
||||
size: 18, color: color),
|
||||
Text('¥${p.price.toStringAsFixed(2)}',
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: AppFonts.mono,
|
||||
fontFamilyFallback: AppFonts.monoFallback,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: color)),
|
||||
],
|
||||
@@ -511,7 +528,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
return _statCard(
|
||||
title: '近期流水',
|
||||
hint: Text('最近 ${_logs.length > 8 ? 8 : _logs.length} 条',
|
||||
style: TextStyle(fontSize: AppDims.fsXs, color: context.tokens.muted)),
|
||||
style:
|
||||
TextStyle(fontSize: AppDims.fsXs, color: context.tokens.muted)),
|
||||
body: Column(
|
||||
children: _logs.take(8).map((l) {
|
||||
final isIn = l.direction == 'in';
|
||||
@@ -523,7 +541,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
width: 96,
|
||||
child: Text(l.createdAt?.substring(0, 10) ?? '-',
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: AppFonts.mono,
|
||||
fontFamilyFallback: AppFonts.monoFallback,
|
||||
fontSize: AppDims.fsSm,
|
||||
color: context.tokens.muted)),
|
||||
),
|
||||
@@ -538,7 +557,9 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
'${isIn ? '+' : '-'}${l.quantity.toStringAsFixed(0)}',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isIn ? context.tokens.success : context.tokens.danger),
|
||||
color: isIn
|
||||
? context.tokens.success
|
||||
: context.tokens.danger),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -575,8 +596,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
)),
|
||||
if (p.images.length < 5)
|
||||
WriteGuard(
|
||||
child:
|
||||
_UploadButton(uploading: _uploading, onTap: _pickAndUpload),
|
||||
child: _UploadButton(
|
||||
uploading: _uploading, onTap: _pickAndUpload),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -621,14 +642,28 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
spacing: 32,
|
||||
runSpacing: 0,
|
||||
children: [
|
||||
SizedBox(width: 200, child: row('编号', p.code.isEmpty ? '-' : p.code)),
|
||||
SizedBox(width: 200, child: row('系列', p.series?.isEmpty ?? true ? '-' : p.series!)),
|
||||
SizedBox(width: 200, child: row('规格', p.spec?.isEmpty ?? true ? '-' : p.spec!)),
|
||||
SizedBox(width: 200, child: row('品牌', p.brand?.isEmpty ?? true ? '-' : p.brand!)),
|
||||
SizedBox(width: 200, child: row('单位', p.unit.isEmpty ? '-' : p.unit)),
|
||||
SizedBox(
|
||||
width: 200, child: row('编号', p.code.isEmpty ? '-' : p.code)),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: row('系列', p.series?.isEmpty ?? true ? '-' : p.series!)),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: row('规格', p.spec?.isEmpty ?? true ? '-' : p.spec!)),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: row('品牌', p.brand?.isEmpty ?? true ? '-' : p.brand!)),
|
||||
SizedBox(
|
||||
width: 200, child: row('单位', p.unit.isEmpty ? '-' : p.unit)),
|
||||
if (p.categoryName?.isNotEmpty ?? false)
|
||||
SizedBox(width: 200, child: row('分类', p.categoryName!)),
|
||||
SizedBox(width: 200, child: row('进价', p.purchasePrice != null && p.purchasePrice! > 0 ? '¥${p.purchasePrice!.toStringAsFixed(2)}' : '-')),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: row(
|
||||
'进价',
|
||||
p.purchasePrice != null && p.purchasePrice! > 0
|
||||
? '¥${p.purchasePrice!.toStringAsFixed(2)}'
|
||||
: '-')),
|
||||
if (p.minStock != null && p.minStock! > 0)
|
||||
SizedBox(width: 200, child: row('库存预警', '${p.minStock}')),
|
||||
if (p.originName?.isNotEmpty ?? false)
|
||||
@@ -654,8 +689,7 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
if (fields == null || fields.isEmpty) return null;
|
||||
return fields.entries
|
||||
.where((e) => e.value != null && e.value.toString().isNotEmpty)
|
||||
.map((e) =>
|
||||
SizedBox(width: 200, child: row(e.key, e.value.toString())))
|
||||
.map((e) => SizedBox(width: 200, child: row(e.key, e.value.toString())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -672,8 +706,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(p.descriptionDocTitle!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
style:
|
||||
const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
if (p.descriptionDocContent?.isNotEmpty ?? false) ...[
|
||||
const SizedBox(height: 10),
|
||||
Text(p.descriptionDocContent!,
|
||||
@@ -688,9 +722,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
|
||||
Widget _buildPriceSection(Product p) {
|
||||
final price = p.salePrice!;
|
||||
final priceStr = price % 1 == 0
|
||||
? price.toInt().toString()
|
||||
: price.toStringAsFixed(2);
|
||||
final priceStr =
|
||||
price % 1 == 0 ? price.toInt().toString() : price.toStringAsFixed(2);
|
||||
return Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -730,8 +763,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
Row(
|
||||
children: [
|
||||
const Text('描述',
|
||||
style: TextStyle(
|
||||
fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
style:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
const Spacer(),
|
||||
if (_descChanged)
|
||||
WriteGuard(
|
||||
@@ -748,8 +781,8 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap),
|
||||
child:
|
||||
const Text('保存', style: TextStyle(fontSize: 13)),
|
||||
child: const Text('保存',
|
||||
style: TextStyle(fontSize: 13)),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -761,8 +794,7 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
minLines: 3,
|
||||
decoration: InputDecoration(
|
||||
hintText: '输入商品描述,例如酒的产地、工艺、口感等...',
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 13, color: context.tokens.muted),
|
||||
hintStyle: TextStyle(fontSize: 13, color: context.tokens.muted),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: BorderSide(color: context.tokens.border)),
|
||||
@@ -792,8 +824,7 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('公开链接',
|
||||
style:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
@@ -802,17 +833,16 @@ class _ProductDetailScreenState extends ConsumerState<ProductDetailScreen> {
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: context.tokens.primary,
|
||||
fontFamily: 'monospace')),
|
||||
fontFamily: AppFonts.mono,
|
||||
fontFamilyFallback: AppFonts.monoFallback)),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: publicUrl));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('链接已复制')),
|
||||
);
|
||||
showDsToast(context, '链接已复制');
|
||||
},
|
||||
icon: const Icon(Icons.copy, size: 14),
|
||||
icon: const Icon(LucideIcons.copy, size: 14),
|
||||
label: const Text('复制'),
|
||||
),
|
||||
],
|
||||
@@ -850,8 +880,8 @@ class _ImageThumbnail extends StatelessWidget {
|
||||
width: 110,
|
||||
height: 110,
|
||||
color: context.tokens.border,
|
||||
child: Icon(Icons.broken_image,
|
||||
color: context.tokens.muted),
|
||||
child:
|
||||
Icon(LucideIcons.imageOff, color: context.tokens.muted),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -861,19 +891,19 @@ class _ImageThumbnail extends StatelessWidget {
|
||||
right: 4,
|
||||
child: WriteGuard(
|
||||
child: GestureDetector(
|
||||
onTap: onDelete,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black54,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: onDelete,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black54, // ds-ignore: 图片遮罩媒体固定色
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(LucideIcons.x,
|
||||
size: 14, color: Colors.white), // ds-ignore: 图片遮罩媒体固定色
|
||||
),
|
||||
child:
|
||||
const Icon(Icons.close, size: 14, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -907,12 +937,12 @@ class _UploadButton extends StatelessWidget {
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.add_photo_alternate_outlined,
|
||||
Icon(LucideIcons.imagePlus,
|
||||
size: 28, color: context.tokens.muted),
|
||||
const SizedBox(height: 4),
|
||||
Text('上传图片',
|
||||
style: TextStyle(
|
||||
fontSize: 11, color: context.tokens.muted)),
|
||||
style:
|
||||
TextStyle(fontSize: 11, color: context.tokens.muted)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -936,12 +966,11 @@ class _QRCodeDialogState extends ConsumerState<_QRCodeDialog> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_future = ref
|
||||
.read(productRepositoryProvider)
|
||||
.getQRCodeBytes(widget.productId)
|
||||
..then((b) {
|
||||
if (mounted) setState(() => _bytes = b);
|
||||
});
|
||||
_future =
|
||||
ref.read(productRepositoryProvider).getQRCodeBytes(widget.productId)
|
||||
..then((b) {
|
||||
if (mounted) setState(() => _bytes = b);
|
||||
});
|
||||
}
|
||||
|
||||
void _print() {
|
||||
@@ -986,14 +1015,12 @@ class _QRCodeDialogState extends ConsumerState<_QRCodeDialog> {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('关闭'),
|
||||
),
|
||||
FilledButton.icon(
|
||||
DsButton('关闭', onPressed: () => Navigator.of(context).pop()),
|
||||
DsButton(
|
||||
'打印',
|
||||
icon: LucideIcons.printer,
|
||||
variant: DsBtnVariant.primary,
|
||||
onPressed: _bytes == null ? null : _print,
|
||||
icon: const Icon(Icons.print, size: 16),
|
||||
label: const Text('打印'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user