event_manage.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import 'package:app_business/service/api.dart';
  2. import 'package:app_business/view/home/dialog_event_register.dart';
  3. import 'package:pretty_qr_code/pretty_qr_code.dart';
  4. import 'package:track_common/model/event_state.dart';
  5. import 'package:track_common/widget.dart';
  6. import 'package:track_common/widget/prelude.dart';
  7. import 'dialog_settings.dart';
  8. import 'event_manage_controller.dart';
  9. class EventManage extends GetView<EventManagerController> {
  10. const EventManage({super.key});
  11. @override
  12. Widget build(BuildContext context) {
  13. return Level2View(
  14. level1: level1(),
  15. level2: level2(),
  16. level1Title: '赛事列表',
  17. level1Action: wDate(context),
  18. level2Title: '用户列表',
  19. level2SubTitle: Row(
  20. children: [
  21. Obx(() => Text(
  22. controller.selected?.name != null
  23. ? '(${controller.selected!.name})'
  24. : '',
  25. style: const TextStyle(color: Colors.grey, fontSize: 14.22),
  26. )),
  27. const Spacer(),
  28. SizedBox(
  29. // height: 27.73,
  30. width: 360,
  31. child: wTopButtons(context),
  32. ),
  33. const SizedBox(width: 20)
  34. ],
  35. ));
  36. }
  37. Widget wTopButtons(BuildContext context) {
  38. return Obx(() {
  39. final enable = controller.selected?.state == EventState.start;
  40. return Row(
  41. mainAxisSize: MainAxisSize.min,
  42. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  43. children: [
  44. AppButton.outlined(
  45. color: Colors.orange,
  46. onPressed: enable ? controller.userRestartAll : null,
  47. child: const Text('一键重赛'),
  48. ),
  49. AppButton.outlined(
  50. color: Colors.blue,
  51. onPressed: enable ? controller.routeAllocAll : null,
  52. child: const Text('一键分发'),
  53. ),
  54. AppButton.outlined(
  55. color: Colors.green,
  56. onPressed: enable ? controller.userStartAll : null,
  57. child: const Text('一键开始'),
  58. ),
  59. AppButton.outlined(
  60. color: Colors.red,
  61. onPressed: enable ? controller.userDelAll : null,
  62. child: const Text('一键删除'),
  63. )
  64. ],
  65. );
  66. });
  67. }
  68. Widget wDate(BuildContext context) {
  69. return GestureDetector(
  70. onTap: () => _onTapDate(context),
  71. child: Obx(() => Container(
  72. height: 22.04,
  73. padding: const EdgeInsets.symmetric(horizontal: 8),
  74. decoration: BoxDecoration(
  75. border:
  76. Border.all(color: const Color(0xffe3e3e3), width: 0.71),
  77. borderRadius: BorderRadius.circular(2.13)),
  78. child: Text(controller.dateStr),
  79. )));
  80. }
  81. Future<void> _onTapDate(BuildContext context) async {
  82. final date = await showDatePicker(
  83. context: context,
  84. initialDate: controller.filterDate.value,
  85. firstDate: DateTime.now(),
  86. lastDate: DateTime.now().add(365.days));
  87. if (date != null) {
  88. controller.filterDate.value = date;
  89. controller.flushList();
  90. }
  91. }
  92. Widget level1() {
  93. return Obx(() => ListView(
  94. children: controller.eventList
  95. .map((e) => EventTitle(
  96. data: e,
  97. selected: controller.selectedId.value == e.id,
  98. onTap: () => controller.selectedId.value = e.id,
  99. ))
  100. .toList()));
  101. }
  102. Widget level2() {
  103. return Column(
  104. children: [
  105. Expanded(
  106. child: Obx(
  107. () => LineChart(titles: rightTitles(), children: rightUsers())))
  108. ],
  109. );
  110. }
  111. Iterable<LineChartTitle> rightTitles() {
  112. return [
  113. LineChartTitle(
  114. title: Checkbox(
  115. value: false,
  116. onChanged: (v) {
  117. controller.selectedUser.update((val) {
  118. final all = controller.userList.map((e) => e.checkId);
  119. val?.assignAll(all);
  120. });
  121. }),
  122. width: 32),
  123. const LineChartTitle(title: Text('序号'), width: 42),
  124. const LineChartTitle(title: Text('用户名'), width: 70),
  125. const LineChartTitle(title: Text('手机号'), width: 98),
  126. const LineChartTitle(title: Text('签到时间'), width: 78),
  127. const LineChartTitle(title: Text('手环'), width: 67),
  128. const LineChartTitle(title: Text('路线'), flex: 1),
  129. const LineChartTitle(title: Text('状态'), width: 67),
  130. const LineChartTitle(title: Text('操作'), width: 67),
  131. ];
  132. }
  133. Iterable<LineChartElem> rightUsers() {
  134. return controller.userList.indexed.map((e) {
  135. final (i, one) = e;
  136. var stateStr = '';
  137. var stateColor = Colors.white;
  138. var optStr = '删除';
  139. var optColor = Colors.red;
  140. VoidCallback? opt;
  141. if (one.isAllowDel) {
  142. opt = () => controller.deleteSignIn(one);
  143. }
  144. switch (one.state) {
  145. case UserState.idle:
  146. stateStr = '未分发';
  147. stateColor = Colors.blue;
  148. break;
  149. case UserState.isStart:
  150. stateStr = '已开始';
  151. stateColor = Colors.green;
  152. optStr = '结束';
  153. opt = () => controller.userStopGame(one);
  154. break;
  155. case UserState.isFinish:
  156. stateStr = '已结束';
  157. stateColor = Colors.orange;
  158. optStr = '重赛';
  159. optColor = Colors.orange;
  160. opt = () => controller.userRestartGame(one);
  161. break;
  162. default:
  163. }
  164. var snStr = '--';
  165. const n = 4;
  166. if (one.bandSN.length > n) {
  167. snStr = '-${one.bandSN.substring(one.bandSN.length - n)}';
  168. } else if (one.bandSN.isNotEmpty) {
  169. snStr = one.bandSN;
  170. }
  171. return LineChartElem([
  172. _CheckBox(one),
  173. Text('${i + 1}'),
  174. Text(one.name),
  175. Text(one.phone),
  176. Text(one.checkTime),
  177. Text(snStr),
  178. one.state == UserState.idle
  179. ? button(
  180. color: Colors.blue,
  181. onPressed: () => routeSelect(one),
  182. text: '分发')
  183. : Row(mainAxisSize: MainAxisSize.min, children: [
  184. Expanded(
  185. child: Text(
  186. one.routeName,
  187. maxLines: 1,
  188. style: const TextStyle(overflow: TextOverflow.ellipsis),
  189. )),
  190. SizedBox(
  191. width: 32,
  192. child: one.state == UserState.hasRoute
  193. ? GestureDetector(
  194. onTap: () => routeSelect(one),
  195. child: const Icon(Icons.mode_edit_outline))
  196. : const SizedBox())
  197. ]),
  198. one.state == UserState.hasRoute
  199. ? button(
  200. color: Colors.green,
  201. onPressed: () => controller.userStart(one),
  202. text: '开始')
  203. : Text(stateStr, style: TextStyle(color: stateColor)),
  204. button(text: optStr, color: optColor, isOutline: true, onPressed: opt)
  205. ]);
  206. });
  207. }
  208. Widget button(
  209. {Color? color,
  210. VoidCallback? onPressed,
  211. isOutline = false,
  212. required String text}) {
  213. return SizedBox(
  214. height: 22.78,
  215. width: 51.2,
  216. child: SmallButton(
  217. color: color,
  218. onPressed: onPressed,
  219. isOutline: isOutline,
  220. child: Text(text),
  221. ));
  222. }
  223. Future<void> routeSelect(UserInManage user) async {
  224. final list = await controller.routeList(user);
  225. await Get.dialog(_RouteSelectDialog(list: list));
  226. final route = controller.tmpSelectRoute;
  227. if (route != null) {
  228. await controller.routeAlloc(user, route);
  229. }
  230. }
  231. }
  232. class _CheckBox extends GetView<EventManagerController> {
  233. const _CheckBox(this.one);
  234. final UserInManage one;
  235. @override
  236. Widget build(BuildContext context) {
  237. return Obx(() => Checkbox(
  238. value: controller.selectedUser.value.contains(one.checkId),
  239. onChanged: (v) {
  240. controller.selectedUser.update((val) {
  241. if (v == true) {
  242. val?.add(one.checkId);
  243. } else {
  244. val?.remove(one.checkId);
  245. }
  246. });
  247. }));
  248. }
  249. }
  250. class _RouteSelectDialog extends GetView<EventManagerController> {
  251. const _RouteSelectDialog({required this.list});
  252. final Iterable<RouteInfo> list;
  253. @override
  254. Widget build(BuildContext context) {
  255. return AlertDialog(
  256. title: const Center(
  257. child: Text(
  258. '选择路线',
  259. style: TextStyle(fontSize: 17),
  260. )),
  261. backgroundColor: Colors.white,
  262. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(17.78)),
  263. content: Column(
  264. mainAxisSize: MainAxisSize.min,
  265. crossAxisAlignment: CrossAxisAlignment.center,
  266. children: [
  267. SizedBox(
  268. width: 303.64,
  269. child: DropdownMenu<RouteInfo>(
  270. key: GlobalKey(),
  271. width: 303,
  272. hintText: '请选择路线',
  273. onSelected: (one) {
  274. controller.tmpSelectRoute = one;
  275. },
  276. inputDecorationTheme: InputDecorationTheme(
  277. border: textBorder,
  278. isDense: true,
  279. ),
  280. dropdownMenuEntries: list
  281. .map((e) =>
  282. DropdownMenuEntry<RouteInfo>(value: e, label: e.name))
  283. .toList())),
  284. const SizedBox(height: 30),
  285. Row(
  286. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  287. children: [
  288. SizedBox(
  289. height: 38,
  290. width: 106.67,
  291. child: SmallButton(
  292. onPressed: () {
  293. Get.back();
  294. },
  295. color: Colors.orange,
  296. child: const Text('随机'))),
  297. SizedBox(
  298. height: 38,
  299. width: 106.67,
  300. child: SmallButton(
  301. onPressed: () {
  302. Get.back();
  303. },
  304. color: Colors.blue,
  305. child: const Text('确认'))),
  306. ],
  307. )
  308. ],
  309. ),
  310. );
  311. }
  312. }
  313. class EventTitle extends GetView<EventManagerController> {
  314. final bool selected;
  315. final EventInManage data;
  316. final VoidCallback onTap;
  317. const EventTitle(
  318. {super.key,
  319. required this.selected,
  320. required this.data,
  321. required this.onTap});
  322. @override
  323. Widget build(BuildContext context) {
  324. var children = <Widget>[
  325. AppTitleList(
  326. title: data.name,
  327. tail: Text('${data.userList.length}'),
  328. subtitle: Text.rich(TextSpan(
  329. text: '比赛时间:${data.startAt} - ${data.endAt} ',
  330. style: const TextStyle(
  331. fontSize: 9.9,
  332. fontWeight: FontWeight.w500,
  333. color: Color(0xff818181)),
  334. children: [
  335. TextSpan(
  336. text: data.state.toString(),
  337. style: TextStyle(color: data.state.toColor()))
  338. ])),
  339. isSelected: selected,
  340. onTap: onTap,
  341. )
  342. ];
  343. const buttonWidth = 52.0;
  344. if (selected && data.state != EventState.finish) {
  345. children.add(const SizedBox(height: 2));
  346. children.add(Container(
  347. decoration: const BoxDecoration(
  348. color: Color(0xfff1f1f1),
  349. borderRadius: BorderRadius.only(
  350. bottomLeft: Radius.circular(14.22),
  351. bottomRight: Radius.circular(14.22))),
  352. padding: const EdgeInsets.all(16.3),
  353. width: 221.87,
  354. child: Column(
  355. mainAxisSize: MainAxisSize.min,
  356. children: [
  357. SizedBox(
  358. height: 25,
  359. child: Row(
  360. children: [
  361. AppButton.cancel(
  362. width: buttonWidth,
  363. onPressed: data.isAllowDel
  364. ? () => controller.deleteEvent(data)
  365. : null,
  366. child: const Text('删除')),
  367. const Spacer(),
  368. AppButton.confirm(
  369. width: buttonWidth,
  370. onPressed: data.isAllowEdit
  371. ? () async {
  372. final r = await showEventSettingsDialog(data);
  373. if (r != null) {
  374. final msg =
  375. await controller.eventSettingsEdit(r);
  376. if (context.mounted) {
  377. ScaffoldMessenger.of(context).showSnackBar(
  378. SnackBar(content: Text(msg)));
  379. }
  380. }
  381. }
  382. : null,
  383. child: const Text('设置')),
  384. const SizedBox(width: 7),
  385. AppButton.confirm(
  386. width: buttonWidth,
  387. onPressed: data.isAllowEdit
  388. ? () async {
  389. final r = await showEventEditDialog(
  390. controller.mapId!,
  391. EventRegisterInfo()..name = data.name);
  392. if (r != null) {
  393. controller.eventEdit(data.id, r);
  394. }
  395. }
  396. : null,
  397. child: const Text('修改')),
  398. ],
  399. )),
  400. const SizedBox(height: 24),
  401. SizedBox.square(
  402. dimension: 128, child: PrettyQrView.data(data: data.qrCode)),
  403. const SizedBox(height: 12),
  404. const Text(
  405. '用彩图奔跑APP扫码签到',
  406. style: TextStyle(color: Colors.red, fontSize: 14.22),
  407. )
  408. ],
  409. ),
  410. ));
  411. }
  412. return Column(
  413. mainAxisSize: MainAxisSize.min,
  414. children: children,
  415. );
  416. }
  417. }
  418. Future<Iterable<Rule>?> showEventSettingsDialog(EventInManage event) async {
  419. return await Get.dialog(const DialogSettings(), arguments: event.id);
  420. }