event_manage.dart 16 KB

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