Files
logistics_tool/lib/logistics.dart

596 lines
28 KiB
Dart
Raw Permalink Normal View History

2024-06-18 16:52:17 +08:00
import 'dart:convert';
import 'package:async/async.dart';
2024-06-17 17:28:54 +08:00
import 'package:dio/dio.dart';
2024-06-20 10:06:05 +08:00
import 'package:flutter/cupertino.dart';
2024-06-14 17:22:52 +08:00
import 'package:flutter/material.dart';
2024-06-18 16:52:17 +08:00
import 'package:flutter/services.dart';
2024-06-17 17:28:54 +08:00
import 'package:go_router/go_router.dart';
import 'package:logistics_tools/utils/request.dart';
2024-06-18 16:52:17 +08:00
import 'package:uuid/uuid.dart';
2024-06-14 17:22:52 +08:00
class Logistics extends StatefulWidget {
const Logistics({super.key});
@override
State<Logistics> createState() => _LogisticsState();
}
class _LogisticsState extends State<Logistics> {
2024-06-17 17:28:54 +08:00
List<dynamic> goodsList = [];
2024-06-18 16:52:17 +08:00
String _orderPayWay = "";
2024-06-20 10:06:05 +08:00
// String _logistics = "";
2024-06-18 16:52:17 +08:00
List _packages = [];
2024-06-20 10:06:05 +08:00
bool _packageInit = false;
2024-06-18 16:52:17 +08:00
int _activePackage = 0;
final TextEditingController _goodsNameController =
TextEditingController(text: "");
final TextEditingController _goodsSpecController =
TextEditingController(text: "");
final TextEditingController _goodsNumController = TextEditingController();
final _memoizer = AsyncMemoizer();
2024-06-17 17:28:54 +08:00
@override
void initState() {
// TODO: implement initState
super.initState();
2024-06-18 16:52:17 +08:00
setState(() {
_packages = [
{"id": const Uuid().v4(), "logisticsNumber": "", "goods": []}
];
});
2024-06-17 17:28:54 +08:00
}
Future<Response> fetchGoodsList(BuildContext context) async {
String? orderNumber =
GoRouterState.of(context).uri.queryParameters["orderNumber"];
String? orderPayWay =
GoRouterState.of(context).uri.queryParameters["orderPayWay"];
2024-06-20 10:06:05 +08:00
String? logistics =
GoRouterState.of(context).uri.queryParameters["logistics"];
2024-06-18 16:52:17 +08:00
_orderPayWay = orderPayWay!;
2024-06-17 17:28:54 +08:00
if (orderNumber?.isEmpty ?? true) {
context.pop();
}
2024-06-18 16:52:17 +08:00
Response response = await _memoizer.runOnce(() async {
return await dio.get(
"https://www.sanpinhuicai.com/wisdommining/api/order/goodsOfOrder",
queryParameters: {"orderNumber": orderNumber});
});
2024-06-17 17:28:54 +08:00
goodsList = response.data["value"];
2024-06-20 10:06:05 +08:00
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;
}
}
2024-06-17 17:28:54 +08:00
return response;
}
2024-06-14 17:22:52 +08:00
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
2024-06-18 16:52:17 +08:00
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("有未添加至包裹的商品"),
2024-06-20 10:06:05 +08:00
showCloseIcon: true,
2024-06-18 16:52:17 +08:00
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,
2024-06-20 10:06:05 +08:00
showCloseIcon: true,
2024-06-18 16:52:17 +08:00
content: Text("有未填写的数据")));
return;
}
String logisticsJson = jsonEncode(_packages);
context.pop(logisticsJson);
},
child: const Row(
children: [Text("保存"), Icon(Icons.save)],
)),
const SizedBox(
width: 4,
)
],
2024-06-14 17:22:52 +08:00
),
body: Container(
2024-06-17 17:28:54 +08:00
padding: const EdgeInsets.all(40),
width: double.infinity,
child: FutureBuilder(
future: fetchGoodsList(context),
2024-06-18 16:52:17 +08:00
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
2024-06-17 17:28:54 +08:00
if (snapshot.hasData) {
return Card(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Expanded(
flex: 1,
2024-06-18 16:52:17 +08:00
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: <TextInputFormatter>[
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(
2024-06-20 10:06:05 +08:00
content: Text("请填写完整商品信息"),
showCloseIcon: true,
));
2024-06-18 16:52:17 +08:00
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("添加到包裹"))
],
)
2024-06-20 10:06:05 +08:00
: goodsList
2024-06-18 16:52:17 +08:00
.where((el) => el["goodsNum"] > 0)
2024-06-20 10:06:05 +08:00
.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]
2024-06-18 16:52:17 +08:00
["goods"]
2024-06-20 10:06:05 +08:00
.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"]
2024-06-18 16:52:17 +08:00
["id"],
2024-06-20 10:06:05 +08:00
"name": goods[
"goodsName"],
"spec": goods?[
"wisdGoodsSpec"]
2024-06-18 16:52:17 +08:00
?[
"specName"] ??
"",
2024-06-20 10:06:05 +08:00
"num": 1
});
});
}
var curGoods =
goodsList.firstWhere(
(el) =>
el["id"] ==
goods["id"],
orElse: () => -1);
2024-06-18 16:52:17 +08:00
2024-06-20 10:06:05 +08:00
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,
2024-06-18 16:52:17 +08:00
),
2024-06-20 10:06:05 +08:00
)),
2024-06-18 16:52:17 +08:00
const SizedBox(
width: 20,
),
Expanded(
flex: 1,
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _packages.length,
itemBuilder:
(BuildContext context, int index) {
return PackageCard(
2024-06-20 10:06:05 +08:00
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;
});
}
},
2024-06-18 17:18:35 +08:00
onLogisticsChanged: (value) {
_packages[index]
["logisticsNumber"] = value;
},
2024-06-20 10:06:05 +08:00
orderPayWay: _orderPayWay,
2024-06-18 17:18:35 +08:00
onTap: () {
setState(() {
_activePackage = index;
});
},
2024-06-18 17:26:27 +08:00
onClose: () {
var curId =
_packages[index]["id"];
2024-06-20 10:06:05 +08:00
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"];
});
}
});
}
2024-06-18 17:26:27 +08:00
setState(() {
_activePackage =
_activePackage >= index
? _activePackage - 1
: _activePackage;
_packages = _packages
.where((el) =>
el["id"] != curId)
.toList();
});
},
totalPackagesCount:
_packages.length,
2024-06-18 17:18:35 +08:00
isActive: _activePackage == index,
package: _packages[index],
index: index);
2024-06-18 16:52:17 +08:00
},
)),
ElevatedButton(
onPressed: () {
setState(() {
_packages = [
..._packages,
{
"logisticsNumber": "",
"id": const Uuid().v4(),
"goods": []
}
];
2024-06-20 10:06:05 +08:00
_activePackage += 1;
2024-06-18 16:52:17 +08:00
});
},
child: const Text("添加包裹"))
],
))
2024-06-17 17:28:54 +08:00
],
)),
);
} else if (snapshot.hasError) {
return const Center(
2024-06-20 10:06:05 +08:00
child: Column(
children: [
Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
],
2024-06-14 17:22:52 +08:00
),
2024-06-17 17:28:54 +08:00
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
)));
2024-06-14 17:22:52 +08:00
}
}
2024-06-18 16:52:17 +08:00
class PackageCard extends StatefulWidget {
const PackageCard({
super.key,
required this.onTap,
required this.isActive,
required this.package,
required this.index,
required this.onLogisticsChanged,
2024-06-18 17:26:27 +08:00
required this.totalPackagesCount,
required this.onClose,
2024-06-20 10:06:05 +08:00
required this.orderPayWay,
required this.onDeleteGoods,
required this.logisticsNumber,
2024-06-18 16:52:17 +08:00
});
final bool isActive;
final Function() onTap;
2024-06-18 17:26:27 +08:00
final Function() onClose;
2024-06-20 10:06:05 +08:00
final Function(Map<String, dynamic> goods) onDeleteGoods;
2024-06-18 16:52:17 +08:00
final Map<String, dynamic> package;
final int index;
final Function(String) onLogisticsChanged;
2024-06-18 17:26:27 +08:00
final int totalPackagesCount;
2024-06-20 10:06:05 +08:00
final String orderPayWay;
final String logisticsNumber;
2024-06-18 16:52:17 +08:00
@override
State<PackageCard> createState() => _PackageCardState();
}
class _PackageCardState extends State<PackageCard> {
2024-06-20 10:06:05 +08:00
final TextEditingController _logisticsController = TextEditingController();
@override
void initState() {
// TODO: implement initState
super.initState();
_logisticsController.text = widget.logisticsNumber;
}
2024-06-18 16:52:17 +08:00
@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(
2024-06-20 10:06:05 +08:00
controller: _logisticsController,
2024-06-18 16:52:17 +08:00
onChanged: widget.onLogisticsChanged,
decoration: const InputDecoration(
border: OutlineInputBorder(), labelText: "物流单号"),
),
const SizedBox(
height: 20,
),
DataTable(columns: const [
DataColumn(label: Text("名称")),
DataColumn(label: Text("规格")),
2024-06-20 10:06:05 +08:00
DataColumn(label: Text("数量")),
DataColumn(label: Text("操作"))
2024-06-18 16:52:17 +08:00
], rows: [
2024-06-20 10:06:05 +08:00
for (var goodsItem
in widget.package["goods"].asMap().entries)
2024-06-18 16:52:17 +08:00
DataRow(cells: [
2024-06-20 10:06:05 +08:00
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);
}),
)),
2024-06-18 16:52:17 +08:00
])
]),
],
),
),
Positioned(
left: 20,
top: 2,
child: Text("包裹${widget.index + 1}"),
),
2024-06-18 17:26:27 +08:00
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,
)
2024-06-18 16:52:17 +08:00
],
),
),
);
}
}