import 'dart:convert'; import 'package:async/async.dart'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:logistics_tools/utils/request.dart'; import 'package:uuid/uuid.dart'; class Logistics extends StatefulWidget { const Logistics({super.key}); @override State createState() => _LogisticsState(); } class _LogisticsState extends State { List goodsList = []; String _orderPayWay = ""; // String _logistics = ""; List _packages = []; bool _packageInit = false; int _activePackage = 0; final TextEditingController _goodsNameController = TextEditingController(text: ""); final TextEditingController _goodsSpecController = TextEditingController(text: ""); final TextEditingController _goodsNumController = TextEditingController(); final _memoizer = AsyncMemoizer(); @override void initState() { // TODO: implement initState super.initState(); setState(() { _packages = [ {"id": const Uuid().v4(), "logisticsNumber": "", "goods": []} ]; }); } Future fetchGoodsList(BuildContext context) async { String? orderNumber = GoRouterState.of(context).uri.queryParameters["orderNumber"]; String? orderPayWay = GoRouterState.of(context).uri.queryParameters["orderPayWay"]; String? logistics = GoRouterState.of(context).uri.queryParameters["logistics"]; _orderPayWay = orderPayWay!; if (orderNumber?.isEmpty ?? true) { context.pop(); } Response response = await _memoizer.runOnce(() async { return await dio.get( "https://www.sanpinhuicai.com/wisdommining/api/order/goodsOfOrder", queryParameters: {"orderNumber": orderNumber}); }); goodsList = response.data["value"]; if (logistics != null && logistics.isNotEmpty && !_packageInit) { try { List packages = jsonDecode(logistics); _packages = packages; if (_orderPayWay != "套餐商品") { for (var p in _packages) { p["goods"].forEach((g) { var targetGoods = goodsList .firstWhere((tg) => g["id"] == tg["id"], orElse: () => null); if (targetGoods != null) { targetGoods["goodsNum"] -= g["num"]; } }); } } _packageInit = true; } catch (e) { e; } } return response; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( "填写物流信息 (${GoRouterState.of(context).uri.queryParameters["orderPayWay"]})"), actions: [ ElevatedButton( onPressed: () { // 数据验证 if (_orderPayWay != "套餐商品") { bool hasUnAddedGoods = goodsList.every((el) => el["goodsNum"] > 0); if (hasUnAddedGoods) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text("有未添加至包裹的商品"), showCloseIcon: true, backgroundColor: Colors.redAccent, )); return; } } bool result = _packages.every((package) { return package["logisticsNumber"].toString().isNotEmpty && package["goods"].length > 0 && package["goods"].every((goods) { return goods["name"].toString().isNotEmpty && goods["num"].toString().isNotEmpty && goods["spec"].toString().isNotEmpty; }); }); if (!result) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( backgroundColor: Colors.redAccent, showCloseIcon: true, content: Text("有未填写的数据"))); return; } String logisticsJson = jsonEncode(_packages); context.pop(logisticsJson); }, child: const Row( children: [Text("保存"), Icon(Icons.save)], )), const SizedBox( width: 4, ) ], ), body: Container( padding: const EdgeInsets.all(40), width: double.infinity, child: FutureBuilder( future: fetchGoodsList(context), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { return Card( color: Colors.white, child: Padding( padding: const EdgeInsets.all(20), child: Row( children: [ Expanded( flex: 1, child: _orderPayWay == "套餐商品" ? Column( children: [ Card( color: Colors.white70, child: Row( children: [ Image.network( width: 80, height: 80, goodsList[0]["goodsPhoto"]), const SizedBox( width: 16, ), Text( "${goodsList[0]["goodsName"]}") ], ), ), const SizedBox( height: 20, ), TextField( controller: _goodsNameController, decoration: const InputDecoration( labelText: "商品名称", border: OutlineInputBorder()), ), const SizedBox( height: 20, ), TextField( controller: _goodsSpecController, decoration: const InputDecoration( labelText: "商品规格", border: OutlineInputBorder()), ), const SizedBox( height: 20, ), TextField( controller: _goodsNumController, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter .digitsOnly ], decoration: const InputDecoration( labelText: "商品数量", border: OutlineInputBorder()), ), const SizedBox( height: 20, ), ElevatedButton( onPressed: () { if (_goodsNumController .text.isEmpty || _goodsNameController .text.isEmpty || _goodsSpecController .text.isEmpty) { ScaffoldMessenger.of(context) .showSnackBar( const SnackBar( content: Text("请填写完整商品信息"), showCloseIcon: true, )); return; } int goodsNum = int.parse( _goodsNumController.text); String goodsName = _goodsNameController.text; String goodsSpec = _goodsSpecController.text; var goods = { "id": const Uuid().v4(), "name": goodsName, "num": goodsNum, "specId": const Uuid().v4(), "spec": goodsSpec }; var packagesList = _packages; packagesList[_activePackage] ["goods"] .add(goods); setState(() { _packages = packagesList; }); _goodsNumController.clear(); _goodsNameController.clear(); _goodsSpecController.clear(); }, child: const Text("添加到包裹")) ], ) : goodsList .where((el) => el["goodsNum"] > 0) .isNotEmpty ? ListView.builder( itemCount: goodsList .where( (el) => el["goodsNum"] > 0) .length, itemBuilder: (context, index) { final goods = goodsList .where((el) => el["goodsNum"] > 0) .toList()[index]; return ListTile( trailing: IconButton( icon: const Icon(Icons.add), onPressed: () { // 检查商品及规格是否已存在于目标包裹 int targetIndex = _packages[ _activePackage] ["goods"] .indexWhere((el) => el["id"] == goods["id"] && goods["wisdGoodsSpec"] ["id"] == el["specId"]); if (targetIndex != -1) { setState(() { _packages[_activePackage] ["goods"] [targetIndex] ["num"] += 1; }); } else { setState(() { _packages[_activePackage] ["goods"] .add({ "id": goods["id"], "specId": goods[ "wisdGoodsSpec"] ["id"], "name": goods[ "goodsName"], "spec": goods?[ "wisdGoodsSpec"] ?[ "specName"] ?? "", "num": 1 }); }); } var curGoods = goodsList.firstWhere( (el) => el["id"] == goods["id"], orElse: () => -1); setState(() { curGoods["goodsNum"] -= 1; }); }, ), leading: Image.network( goods["goodsPhoto"], width: 100, height: 100, ), title: Text( "${goods["goodsName"]} - ${goods?["wisdGoodsSpec"]?["specName"] ?? ''}"), subtitle: Text( "数量 : ${goods?["goodsNum"]}"), ); }) : const Center( child: Icon( CupertinoIcons.circle, size: 36, ), )), const SizedBox( width: 20, ), Expanded( flex: 1, child: Column( children: [ Expanded( child: ListView.builder( itemCount: _packages.length, itemBuilder: (BuildContext context, int index) { return PackageCard( logisticsNumber: _packages[index] ["logisticsNumber"], onDeleteGoods: (goods) { if (goods["num"] == 1) { setState(() { _packages[index]["goods"] = _packages[index]["goods"] .where((el) => el["id"] != goods["id"]) .toList(); }); } else { setState(() { goods["num"] -= 1; }); } if (_orderPayWay != "套餐商品") { setState(() { goodsList.firstWhere( (e) => goods["id"] == e["id"], orElse: () => null)?[ "goodsNum"] += 1; }); } }, onLogisticsChanged: (value) { _packages[index] ["logisticsNumber"] = value; }, orderPayWay: _orderPayWay, onTap: () { setState(() { _activePackage = index; }); }, onClose: () { var curId = _packages[index]["id"]; if (_orderPayWay != "套餐商品") { _packages[index]["goods"] .forEach((el) { var targetGoods = goodsList.firstWhere( (goodsItem) => goodsItem["id"] == el["id"], orElse: () => -1); if (targetGoods != -1) { setState(() { targetGoods["goodsNum"] += el["num"]; }); } }); } setState(() { _activePackage = _activePackage >= index ? _activePackage - 1 : _activePackage; _packages = _packages .where((el) => el["id"] != curId) .toList(); }); }, totalPackagesCount: _packages.length, isActive: _activePackage == index, package: _packages[index], index: index); }, )), ElevatedButton( onPressed: () { setState(() { _packages = [ ..._packages, { "logisticsNumber": "", "id": const Uuid().v4(), "goods": [] } ]; _activePackage += 1; }); }, child: const Text("添加包裹")) ], )) ], )), ); } else if (snapshot.hasError) { return const Center( child: Column( children: [ Icon( Icons.error_outline, color: Colors.red, size: 60, ), ], ), ); } else { return const Center(child: CircularProgressIndicator()); } }, ))); } } class PackageCard extends StatefulWidget { const PackageCard({ super.key, required this.onTap, required this.isActive, required this.package, required this.index, required this.onLogisticsChanged, required this.totalPackagesCount, required this.onClose, required this.orderPayWay, required this.onDeleteGoods, required this.logisticsNumber, }); final bool isActive; final Function() onTap; final Function() onClose; final Function(Map goods) onDeleteGoods; final Map package; final int index; final Function(String) onLogisticsChanged; final int totalPackagesCount; final String orderPayWay; final String logisticsNumber; @override State createState() => _PackageCardState(); } class _PackageCardState extends State { final TextEditingController _logisticsController = TextEditingController(); @override void initState() { // TODO: implement initState super.initState(); _logisticsController.text = widget.logisticsNumber; } @override Widget build(BuildContext context) { return GestureDetector( onTap: widget.onTap, child: Card( elevation: widget.isActive ? 5 : null, shape: widget.isActive ? RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), side: const BorderSide( color: Colors.blue, width: 1.0, ), ) : null, child: Stack( // clipBehavior: Clip.none, children: [ const SizedBox( height: 100, ), Padding( padding: const EdgeInsets.fromLTRB(20, 30, 20, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextField( controller: _logisticsController, onChanged: widget.onLogisticsChanged, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: "物流单号"), ), const SizedBox( height: 20, ), DataTable(columns: const [ DataColumn(label: Text("名称")), DataColumn(label: Text("规格")), DataColumn(label: Text("数量")), DataColumn(label: Text("操作")) ], rows: [ for (var goodsItem in widget.package["goods"].asMap().entries) DataRow(cells: [ DataCell(Text("${goodsItem.value["name"]}")), DataCell(Text("${goodsItem.value["spec"]}")), DataCell(Text("${goodsItem.value["num"]}")), DataCell(SizedBox( width: 24, height: 24, child: IconButton( icon: const Icon(CupertinoIcons.delete), iconSize: 16, padding: const EdgeInsets.all(4), onPressed: () { widget.onDeleteGoods(goodsItem.value); }), )), ]) ]), ], ), ), Positioned( left: 20, top: 2, child: Text("包裹${widget.index + 1}"), ), widget.totalPackagesCount > 1 ? Positioned( top: 2, right: 2, child: SizedBox( width: 20, height: 20, child: IconButton( color: Colors.redAccent, iconSize: 20, padding: EdgeInsets.zero, icon: const Icon(Icons.close), onPressed: widget.onClose, ), )) : const SizedBox( width: 0, ) ], ), ), ); } }