event_manage.dart 15 KB

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