feat(client): 全模块 API 对接 + 修复登陆跳转 + 菜单 UI 优化
API 对接: - 入库/出库/库存/财务/往来单位/基础数据全部对接后端 REST API - 新增 repositories、providers、models 层,统一分层架构 - auth 从 flutter_secure_storage 迁移到 shared_preferences 登陆跳转修复: - 将 _RouterNotifier 提取为独立 Riverpod provider,appRouterProvider 使用 ref.read 避免依赖链导致 router 重建后跳回 /login - redirect 函数新增 initialized 守卫,防止 auth 未恢复时误重定向 - 添加调试日志(Router/Auth/ApiClient)定位 401 触发的 logout 链路 退出菜单 UI: - 去掉 ListTile,改用 Row + 自定义 padding,文字左对齐 - MouseRegion + AnimatedContainer 实现 hover 高亮(普通项蓝底/退出红底) - 菜单圆角 6px,elevation 8,分割线高度 1px Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
class Inventory {
|
||||
final int warehouseId;
|
||||
final String? warehouseName;
|
||||
final int productId;
|
||||
final String? productName;
|
||||
final String? productCode;
|
||||
final String? productSpec;
|
||||
final String? productUnit;
|
||||
final String? productBrand;
|
||||
final int? minStock;
|
||||
final double quantity;
|
||||
|
||||
const Inventory({
|
||||
required this.warehouseId,
|
||||
this.warehouseName,
|
||||
required this.productId,
|
||||
this.productName,
|
||||
this.productCode,
|
||||
this.productSpec,
|
||||
this.productUnit,
|
||||
this.productBrand,
|
||||
this.minStock,
|
||||
required this.quantity,
|
||||
});
|
||||
|
||||
factory Inventory.fromJson(Map<String, dynamic> json) => Inventory(
|
||||
warehouseId: (json['warehouse_id'] as num).toInt(),
|
||||
warehouseName: json['warehouse_name'] as String?,
|
||||
productId: (json['product_id'] as num).toInt(),
|
||||
productName: json['product_name'] as String?,
|
||||
productCode: json['product_code'] as String?,
|
||||
productSpec: json['product_spec'] as String?,
|
||||
productUnit: json['product_unit'] as String?,
|
||||
productBrand: json['product_brand'] as String?,
|
||||
minStock: json['min_stock'] != null
|
||||
? (json['min_stock'] as num).toInt()
|
||||
: null,
|
||||
quantity: (json['quantity'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
class InventoryLog {
|
||||
final int warehouseId;
|
||||
final String? warehouseName;
|
||||
final int productId;
|
||||
final String? productName;
|
||||
final String direction; // in | out
|
||||
final double quantity;
|
||||
final double? qtyBefore;
|
||||
final double? qtyAfter;
|
||||
final String? refType;
|
||||
final int? refId;
|
||||
final String? createdAt;
|
||||
|
||||
const InventoryLog({
|
||||
required this.warehouseId,
|
||||
this.warehouseName,
|
||||
required this.productId,
|
||||
this.productName,
|
||||
required this.direction,
|
||||
required this.quantity,
|
||||
this.qtyBefore,
|
||||
this.qtyAfter,
|
||||
this.refType,
|
||||
this.refId,
|
||||
this.createdAt,
|
||||
});
|
||||
|
||||
factory InventoryLog.fromJson(Map<String, dynamic> json) => InventoryLog(
|
||||
warehouseId: (json['warehouse_id'] as num).toInt(),
|
||||
warehouseName: json['warehouse_name'] as String?,
|
||||
productId: (json['product_id'] as num).toInt(),
|
||||
productName: json['product_name'] as String?,
|
||||
direction: json['direction'] as String,
|
||||
quantity: (json['quantity'] as num).toDouble(),
|
||||
qtyBefore: json['qty_before'] != null
|
||||
? (json['qty_before'] as num).toDouble()
|
||||
: null,
|
||||
qtyAfter: json['qty_after'] != null
|
||||
? (json['qty_after'] as num).toDouble()
|
||||
: null,
|
||||
refType: json['ref_type'] as String?,
|
||||
refId: json['ref_id'] != null
|
||||
? (json['ref_id'] as num).toInt()
|
||||
: null,
|
||||
createdAt: json['created_at'] as String?,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
class Partner {
|
||||
final int id;
|
||||
final String? code;
|
||||
final String name;
|
||||
final String type; // supplier | customer
|
||||
final String? contact;
|
||||
final String? phone;
|
||||
final String? address;
|
||||
final String? bankAccount;
|
||||
final String? remark;
|
||||
|
||||
const Partner({
|
||||
required this.id,
|
||||
this.code,
|
||||
required this.name,
|
||||
required this.type,
|
||||
this.contact,
|
||||
this.phone,
|
||||
this.address,
|
||||
this.bankAccount,
|
||||
this.remark,
|
||||
});
|
||||
|
||||
factory Partner.fromJson(Map<String, dynamic> json) => Partner(
|
||||
id: (json['id'] as num).toInt(),
|
||||
code: json['code'] as String?,
|
||||
name: json['name'] as String,
|
||||
type: json['type'] as String,
|
||||
contact: json['contact'] as String?,
|
||||
phone: json['phone'] as String?,
|
||||
address: json['address'] as String?,
|
||||
bankAccount: json['bank_account'] as String?,
|
||||
remark: json['remark'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'type': type,
|
||||
if (code != null) 'code': code,
|
||||
if (contact != null) 'contact': contact,
|
||||
if (phone != null) 'phone': phone,
|
||||
if (address != null) 'address': address,
|
||||
if (bankAccount != null) 'bank_account': bankAccount,
|
||||
if (remark != null) 'remark': remark,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
class Product {
|
||||
final int id;
|
||||
final String code;
|
||||
final String? barcode;
|
||||
final String name;
|
||||
final String? series;
|
||||
final String? spec;
|
||||
final String unit;
|
||||
final int? categoryId;
|
||||
final String? brand;
|
||||
final double? purchasePrice;
|
||||
final double? salePrice;
|
||||
final int? minStock;
|
||||
final String? remark;
|
||||
final Map<String, dynamic>? customFields;
|
||||
|
||||
const Product({
|
||||
required this.id,
|
||||
required this.code,
|
||||
this.barcode,
|
||||
required this.name,
|
||||
this.series,
|
||||
this.spec,
|
||||
required this.unit,
|
||||
this.categoryId,
|
||||
this.brand,
|
||||
this.purchasePrice,
|
||||
this.salePrice,
|
||||
this.minStock,
|
||||
this.remark,
|
||||
this.customFields,
|
||||
});
|
||||
|
||||
factory Product.fromJson(Map<String, dynamic> json) => Product(
|
||||
id: (json['id'] as num).toInt(),
|
||||
code: json['code'] as String? ?? '',
|
||||
barcode: json['barcode'] as String?,
|
||||
name: json['name'] as String,
|
||||
series: json['series'] as String?,
|
||||
spec: json['spec'] as String?,
|
||||
unit: json['unit'] as String? ?? '个',
|
||||
categoryId: json['category_id'] != null
|
||||
? (json['category_id'] as num).toInt()
|
||||
: null,
|
||||
brand: json['brand'] as String?,
|
||||
purchasePrice: json['purchase_price'] != null
|
||||
? (json['purchase_price'] as num).toDouble()
|
||||
: null,
|
||||
salePrice: json['sale_price'] != null
|
||||
? (json['sale_price'] as num).toDouble()
|
||||
: null,
|
||||
minStock: json['min_stock'] != null
|
||||
? (json['min_stock'] as num).toInt()
|
||||
: null,
|
||||
remark: json['remark'] as String?,
|
||||
customFields: json['custom_fields'] as Map<String, dynamic>?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'code': code,
|
||||
if (barcode != null) 'barcode': barcode,
|
||||
'name': name,
|
||||
if (series != null) 'series': series,
|
||||
if (spec != null) 'spec': spec,
|
||||
'unit': unit,
|
||||
if (categoryId != null) 'category_id': categoryId,
|
||||
if (brand != null) 'brand': brand,
|
||||
if (purchasePrice != null) 'purchase_price': purchasePrice,
|
||||
if (salePrice != null) 'sale_price': salePrice,
|
||||
if (minStock != null) 'min_stock': minStock,
|
||||
if (remark != null) 'remark': remark,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
class StockInItem {
|
||||
final int? orderId;
|
||||
final int productId;
|
||||
final double quantity;
|
||||
final double unitPrice;
|
||||
final double totalPrice;
|
||||
final String? batchNo;
|
||||
// Denormalized for display
|
||||
final String? productName;
|
||||
final String? productCode;
|
||||
final String? productSpec;
|
||||
final String? productUnit;
|
||||
|
||||
const StockInItem({
|
||||
this.orderId,
|
||||
required this.productId,
|
||||
required this.quantity,
|
||||
required this.unitPrice,
|
||||
required this.totalPrice,
|
||||
this.batchNo,
|
||||
this.productName,
|
||||
this.productCode,
|
||||
this.productSpec,
|
||||
this.productUnit,
|
||||
});
|
||||
|
||||
factory StockInItem.fromJson(Map<String, dynamic> json) => StockInItem(
|
||||
orderId: json['order_id'] != null
|
||||
? (json['order_id'] as num).toInt()
|
||||
: null,
|
||||
productId: (json['product_id'] as num).toInt(),
|
||||
quantity: (json['quantity'] as num).toDouble(),
|
||||
unitPrice: (json['unit_price'] as num).toDouble(),
|
||||
totalPrice: (json['total_price'] as num).toDouble(),
|
||||
batchNo: json['batch_no'] as String?,
|
||||
productName: json['product_name'] as String?,
|
||||
productCode: json['product_code'] as String?,
|
||||
productSpec: json['product_spec'] as String?,
|
||||
productUnit: json['product_unit'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'product_id': productId,
|
||||
'quantity': quantity,
|
||||
'unit_price': unitPrice,
|
||||
'total_price': totalPrice,
|
||||
if (batchNo != null) 'batch_no': batchNo,
|
||||
};
|
||||
}
|
||||
|
||||
class StockInOrder {
|
||||
final int id;
|
||||
final String orderNo;
|
||||
final String? type;
|
||||
final int warehouseId;
|
||||
final String? warehouseName;
|
||||
final int? partnerId;
|
||||
final String? partnerName;
|
||||
final int? operatorId;
|
||||
final String status; // draft | pending | approved | rejected
|
||||
final String? orderDate;
|
||||
final double? totalAmount;
|
||||
final String? remark;
|
||||
final List<StockInItem> items;
|
||||
|
||||
const StockInOrder({
|
||||
required this.id,
|
||||
required this.orderNo,
|
||||
this.type,
|
||||
required this.warehouseId,
|
||||
this.warehouseName,
|
||||
this.partnerId,
|
||||
this.partnerName,
|
||||
this.operatorId,
|
||||
required this.status,
|
||||
this.orderDate,
|
||||
this.totalAmount,
|
||||
this.remark,
|
||||
this.items = const [],
|
||||
});
|
||||
|
||||
factory StockInOrder.fromJson(Map<String, dynamic> json) => StockInOrder(
|
||||
id: (json['id'] as num).toInt(),
|
||||
orderNo: json['order_no'] as String,
|
||||
type: json['type'] as String?,
|
||||
warehouseId: (json['warehouse_id'] as num).toInt(),
|
||||
warehouseName: json['warehouse_name'] as String?,
|
||||
partnerId: json['partner_id'] != null
|
||||
? (json['partner_id'] as num).toInt()
|
||||
: null,
|
||||
partnerName: json['partner_name'] as String?,
|
||||
operatorId: json['operator_id'] != null
|
||||
? (json['operator_id'] as num).toInt()
|
||||
: null,
|
||||
status: json['status'] as String,
|
||||
orderDate: json['order_date'] as String?,
|
||||
totalAmount: json['total_amount'] != null
|
||||
? (json['total_amount'] as num).toDouble()
|
||||
: null,
|
||||
remark: json['remark'] as String?,
|
||||
items: json['items'] != null
|
||||
? (json['items'] as List)
|
||||
.map((e) =>
|
||||
StockInItem.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: [],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
class StockOutItem {
|
||||
final int? orderId;
|
||||
final int productId;
|
||||
final double quantity;
|
||||
final double unitPrice;
|
||||
final double totalPrice;
|
||||
final String? productName;
|
||||
final String? productCode;
|
||||
final String? productSpec;
|
||||
final String? productUnit;
|
||||
|
||||
const StockOutItem({
|
||||
this.orderId,
|
||||
required this.productId,
|
||||
required this.quantity,
|
||||
required this.unitPrice,
|
||||
required this.totalPrice,
|
||||
this.productName,
|
||||
this.productCode,
|
||||
this.productSpec,
|
||||
this.productUnit,
|
||||
});
|
||||
|
||||
factory StockOutItem.fromJson(Map<String, dynamic> json) => StockOutItem(
|
||||
orderId: json['order_id'] != null
|
||||
? (json['order_id'] as num).toInt()
|
||||
: null,
|
||||
productId: (json['product_id'] as num).toInt(),
|
||||
quantity: (json['quantity'] as num).toDouble(),
|
||||
unitPrice: (json['unit_price'] as num).toDouble(),
|
||||
totalPrice: (json['total_price'] as num).toDouble(),
|
||||
productName: json['product_name'] as String?,
|
||||
productCode: json['product_code'] as String?,
|
||||
productSpec: json['product_spec'] as String?,
|
||||
productUnit: json['product_unit'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
class StockOutOrder {
|
||||
final int id;
|
||||
final String orderNo;
|
||||
final String? type;
|
||||
final int warehouseId;
|
||||
final String? warehouseName;
|
||||
final int? partnerId;
|
||||
final String? partnerName;
|
||||
final int? operatorId;
|
||||
final String status; // draft | pending | approved | rejected
|
||||
final String? orderDate;
|
||||
final double? totalAmount;
|
||||
final String? remark;
|
||||
final List<StockOutItem> items;
|
||||
|
||||
const StockOutOrder({
|
||||
required this.id,
|
||||
required this.orderNo,
|
||||
this.type,
|
||||
required this.warehouseId,
|
||||
this.warehouseName,
|
||||
this.partnerId,
|
||||
this.partnerName,
|
||||
this.operatorId,
|
||||
required this.status,
|
||||
this.orderDate,
|
||||
this.totalAmount,
|
||||
this.remark,
|
||||
this.items = const [],
|
||||
});
|
||||
|
||||
factory StockOutOrder.fromJson(Map<String, dynamic> json) => StockOutOrder(
|
||||
id: (json['id'] as num).toInt(),
|
||||
orderNo: json['order_no'] as String,
|
||||
type: json['type'] as String?,
|
||||
warehouseId: (json['warehouse_id'] as num).toInt(),
|
||||
warehouseName: json['warehouse_name'] as String?,
|
||||
partnerId: json['partner_id'] != null
|
||||
? (json['partner_id'] as num).toInt()
|
||||
: null,
|
||||
partnerName: json['partner_name'] as String?,
|
||||
operatorId: json['operator_id'] != null
|
||||
? (json['operator_id'] as num).toInt()
|
||||
: null,
|
||||
status: json['status'] as String,
|
||||
orderDate: json['order_date'] as String?,
|
||||
totalAmount: json['total_amount'] != null
|
||||
? (json['total_amount'] as num).toDouble()
|
||||
: null,
|
||||
remark: json['remark'] as String?,
|
||||
items: json['items'] != null
|
||||
? (json['items'] as List)
|
||||
.map((e) =>
|
||||
StockOutItem.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: [],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
class Warehouse {
|
||||
final int id;
|
||||
final String name;
|
||||
final String? location;
|
||||
final bool isDefault;
|
||||
|
||||
const Warehouse({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.location,
|
||||
required this.isDefault,
|
||||
});
|
||||
|
||||
factory Warehouse.fromJson(Map<String, dynamic> json) => Warehouse(
|
||||
id: (json['id'] as num).toInt(),
|
||||
name: json['name'] as String,
|
||||
location: json['location'] as String?,
|
||||
isDefault: json['is_default'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
if (location != null) 'location': location,
|
||||
'is_default': isDefault,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user