view.dart 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. import 'package:fis_jsonrpc/rpc.dart';
  2. import 'package:flutter/foundation.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:get/get.dart';
  5. import 'package:intl/intl.dart';
  6. import 'package:vitalapp/architecture/app_parameters.dart';
  7. import 'package:vitalapp/architecture/utils/advance_debounce.dart';
  8. import 'package:vitalapp/architecture/utils/datetime.dart';
  9. import 'package:vitalapp/architecture/utils/prompt_box.dart';
  10. import 'package:vitalapp/architecture/utils/sensitive.dart';
  11. import 'package:vitalapp/architecture/values/features.dart';
  12. import 'package:vitalapp/components/alert_dialog.dart';
  13. import 'package:vitalapp/components/button.dart';
  14. import 'package:vitalapp/components/dialog_date.dart';
  15. import 'package:vitalapp/components/dynamic_drawer.dart';
  16. import 'package:vitalapp/components/input.dart';
  17. import 'package:vitalapp/components/search_input.dart';
  18. import 'package:vitalapp/components/tag_widget.dart';
  19. import 'package:vitalapp/consts/rpc_enum_labels.dart';
  20. import 'package:vitalapp/consts/styles.dart';
  21. import 'package:vitalapp/global.dart';
  22. import 'package:vitalapp/managers/contract/index.dart';
  23. import 'package:vitalapp/managers/interfaces/diagnosis.dart';
  24. import 'package:vitalapp/managers/interfaces/exam.dart';
  25. import 'package:vitalapp/managers/interfaces/models/crowd_labels.dart';
  26. import 'package:vitalapp/managers/interfaces/models/patient_model_dto.dart';
  27. import 'package:vitalapp/pages/home/controller.dart';
  28. import 'package:vitalapp/pages/patient/list/widgets/crowd_select_label.dart';
  29. import 'package:vitalapp/pages/patient/list/widgets/status.dart';
  30. import 'package:vitalapp/pages/widgets/icon_button.dart';
  31. import 'package:vitalapp/store/store.dart';
  32. import 'controller.dart';
  33. class PatientListPage extends GetView<PatientListController> {
  34. const PatientListPage({super.key});
  35. @override
  36. Widget build(BuildContext context) {
  37. return Container(
  38. margin: const EdgeInsets.all(16),
  39. child: Column(
  40. children: [
  41. _HeaderWidget(
  42. onFilterPressed: () {
  43. VDynamicDrawerWrapper.show(
  44. scaffoldKey: Get.find<HomeController>().homeScaffoldKey,
  45. builder: (_) => _filterdrawer(context),
  46. );
  47. // scaffoldKey.currentState?.openEndDrawer();
  48. },
  49. ),
  50. const SizedBox(height: 20),
  51. Expanded(child: _buildListView()),
  52. ],
  53. ),
  54. );
  55. }
  56. VDrawer _filterdrawer(BuildContext context) {
  57. final scrollController = ScrollController();
  58. controller.crowdLabelsController.onReady();
  59. return VDrawer(
  60. width: 600,
  61. title: "筛选",
  62. scaffoldKey: Get.find<HomeController>().homeScaffoldKey,
  63. onConfirm: () {
  64. var state = controller.state;
  65. var startTime = state.startTime.value;
  66. var endTime = state.endTime.value;
  67. if (startTime != null &&
  68. endTime != null &&
  69. startTime.isAfter(endTime)) {
  70. PromptBox.toast('起始时间不能晚于结束时间');
  71. return;
  72. }
  73. controller.reloadList(isFilter: true);
  74. // Get.back();
  75. VDynamicDrawerWrapper.hide(
  76. scaffoldKey: Get.find<HomeController>().homeScaffoldKey,
  77. );
  78. },
  79. onCancel: () {
  80. // Get.back();
  81. VDynamicDrawerWrapper.hide(
  82. scaffoldKey: Get.find<HomeController>().homeScaffoldKey,
  83. );
  84. },
  85. child: Scrollbar(
  86. controller: scrollController,
  87. thumbVisibility: true,
  88. child: SingleChildScrollView(
  89. controller: scrollController,
  90. child: Padding(
  91. padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
  92. child: Column(
  93. crossAxisAlignment: CrossAxisAlignment.start,
  94. children: [
  95. const Text(
  96. '居民创建时间:',
  97. style: TextStyle(fontSize: 20),
  98. ),
  99. const SizedBox(
  100. height: 20,
  101. ),
  102. Row(
  103. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  104. mainAxisSize: MainAxisSize.max,
  105. children: [
  106. Expanded(
  107. child: Obx(
  108. () => VInput(
  109. readOnly: true,
  110. controller: TextEditingController(
  111. text: DateFormat('yyyy-MM-dd').format(
  112. (controller.state.startTime.value ??
  113. DateTime.now())
  114. .toLocal(),
  115. ),
  116. ),
  117. radius: 4,
  118. onTap: () async {
  119. DateTime? result;
  120. bool _isLocalStation =
  121. AppParameters.data.isLocalStation;
  122. if (kIsWeb || _isLocalStation) {
  123. result = await showDatePicker(
  124. context: Get.context!,
  125. initialDate: controller.state.startTime.value ??
  126. DateTime.now(),
  127. firstDate: DateTime(1900),
  128. lastDate: DateTime(2100),
  129. );
  130. } else {
  131. result = await VDialogDate(
  132. maxValue: controller.state.endTime.value,
  133. title: '起始时间',
  134. initialValue: controller.state.startTime.value,
  135. ).show();
  136. }
  137. if (result != null) {
  138. controller.state.startTime.value = result;
  139. }
  140. },
  141. ),
  142. ),
  143. ),
  144. Container(
  145. margin: const EdgeInsets.symmetric(horizontal: 16),
  146. child: const Text('一')),
  147. Expanded(
  148. child: Obx(
  149. () => VInput(
  150. readOnly: true,
  151. controller: TextEditingController(
  152. text: DateFormat('yyyy-MM-dd').format(
  153. (controller.state.endTime.value ?? DateTime.now())
  154. .toLocal(),
  155. ),
  156. ),
  157. radius: 4,
  158. onTap: () async {
  159. DateTime? result;
  160. bool _isLocalStation =
  161. AppParameters.data.isLocalStation;
  162. if (kIsWeb || _isLocalStation) {
  163. result = await showDatePicker(
  164. context: Get.context!,
  165. initialDate: controller.state.endTime.value ??
  166. DateTime.now(),
  167. firstDate: DateTime(1900),
  168. lastDate: DateTime(2100),
  169. );
  170. } else {
  171. result = await VDialogDate(
  172. title: '结束时间',
  173. initialValue: controller.state.endTime.value,
  174. ).show();
  175. }
  176. if (result != null) {
  177. controller.state.endTime.value = result;
  178. }
  179. },
  180. ),
  181. ),
  182. )
  183. ],
  184. ),
  185. const SizedBox(
  186. height: 20,
  187. ),
  188. const Text(
  189. '居民查询范围:',
  190. style: TextStyle(fontSize: 20),
  191. ),
  192. const SizedBox(
  193. height: 20,
  194. ),
  195. ScopeEnquiryResidents(
  196. selectRaidoChange: (int value) {
  197. controller.state.selectBoxFilterFounder = value;
  198. },
  199. selectIndex: controller.state.selectBoxFilterFounder,
  200. ),
  201. const SizedBox(
  202. height: 20,
  203. ),
  204. const Text(
  205. '人群分类:',
  206. style: TextStyle(fontSize: 20),
  207. ),
  208. const SizedBox(
  209. height: 20,
  210. ),
  211. CrowdSelectLabelView(
  212. controller: controller.crowdLabelsController,
  213. ),
  214. const SizedBox(
  215. height: 20,
  216. ),
  217. const Text(
  218. '签约状态:',
  219. style: TextStyle(fontSize: 20),
  220. ),
  221. const SizedBox(
  222. height: 20,
  223. ),
  224. ContractStateSelectLabelView(
  225. ContractStateEnums: [
  226. ContractStateEnum.Signed,
  227. ContractStateEnum.Cancelled,
  228. ContractStateEnum.Unsigned,
  229. ContractStateEnum.Expired,
  230. ContractStateEnum.Refused,
  231. ],
  232. selectContractState:
  233. controller.state.contractStateSelectedItem,
  234. selectRaidoChange: (String value) {
  235. if (value == "0") {
  236. controller.state.contractStateSelectedItem = null;
  237. return;
  238. }
  239. controller.state.contractStateSelectedItem =
  240. ContractStateEnum.values
  241. .where((element) => element.name == value)
  242. .first;
  243. },
  244. ),
  245. ],
  246. ),
  247. ),
  248. ),
  249. ),
  250. );
  251. }
  252. Widget _buildListView() {
  253. final scrollController = ScrollController();
  254. scrollController.addListener(
  255. () {
  256. // 如果滑动到底部
  257. try {
  258. if (scrollController.position.atEdge) {
  259. if (scrollController.position.pixels != 0) {
  260. if (controller.state.hasNextPage) {
  261. controller.loadNextPageList();
  262. }
  263. }
  264. }
  265. } catch (e) {
  266. // logger.e("listViewScrollController exception:", e);
  267. }
  268. },
  269. );
  270. return RefreshIndicator(
  271. onRefresh: () async {
  272. controller.reloadList();
  273. },
  274. child: Obx(
  275. () {
  276. final list = controller.state.dataList;
  277. final children = <Widget>[];
  278. for (var i = 0; i < list.length; i++) {
  279. children.add(_PatientCard(dto: list[i]));
  280. }
  281. return children.isEmpty
  282. ? Container(
  283. margin: const EdgeInsets.only(top: 80),
  284. child: Column(
  285. children: [
  286. Center(
  287. child: Image.asset(
  288. "assets/images/no_data.png",
  289. width: 300,
  290. height: 300,
  291. fit: BoxFit.cover,
  292. ),
  293. ),
  294. const Text(
  295. "暂无数据,先看看别的吧",
  296. style: TextStyle(fontSize: 18),
  297. ),
  298. ],
  299. ),
  300. )
  301. : Scrollbar(
  302. trackVisibility: true,
  303. controller: scrollController,
  304. child: GridView(
  305. shrinkWrap: true,
  306. controller: scrollController,
  307. gridDelegate:
  308. const SliverGridDelegateWithFixedCrossAxisCount(
  309. crossAxisCount: 3,
  310. mainAxisSpacing: 16,
  311. crossAxisSpacing: 20,
  312. childAspectRatio: 360 / 200,
  313. ),
  314. children: children,
  315. ),
  316. );
  317. },
  318. ),
  319. );
  320. }
  321. }
  322. class _HeaderWidget extends GetView<PatientListController> {
  323. final searchTextEditingController = TextEditingController();
  324. final VoidCallback onFilterPressed;
  325. _HeaderWidget({
  326. required this.onFilterPressed,
  327. });
  328. @override
  329. Widget build(BuildContext context) {
  330. return SizedBox(
  331. height: 76,
  332. child: Row(
  333. children: [
  334. _PatientStatisticWidget(),
  335. if (Store.user.hasFeature(FeatureKeys.FaceRecognition))
  336. VIconButton(
  337. iconData: Icons.sensor_occupied,
  338. textString: '人脸识别',
  339. voidCallback: () {
  340. if (!kIsOnline) {
  341. PromptBox.toast("当前为离线模式,不支持此功能");
  342. return;
  343. }
  344. Debouncer.run(
  345. controller.onFaceIdLoginClicked,
  346. );
  347. },
  348. ),
  349. if (Store.user.hasFeature(FeatureKeys.IdCardPhotoOCR))
  350. VIconButton(
  351. iconData: Icons.perm_contact_cal_rounded,
  352. textString: '拍照识别',
  353. voidCallback: () {
  354. if (!Store.user
  355. .hasFeature(FeatureKeys.IdCardOfflineRecognition)) {
  356. if (!kIsOnline) {
  357. PromptBox.toast("当前为离线模式,不支持此功能");
  358. return;
  359. }
  360. }
  361. Debouncer.run(
  362. controller.onIdCardScanClickedToDetail,
  363. );
  364. },
  365. ),
  366. if (Store.user.hasFeature(FeatureKeys.IDCardReader))
  367. VIconButton(
  368. iconData: Icons.chrome_reader_mode,
  369. textString: '读卡识别',
  370. voidCallback: () {
  371. Debouncer.run(
  372. controller.onReadCardClickedToDetail,
  373. );
  374. },
  375. ),
  376. VIconButton(
  377. iconData: Icons.edit_document,
  378. textString: '手动录入',
  379. voidCallback: () {
  380. Debouncer.run(
  381. controller.onManualInputPatient,
  382. );
  383. },
  384. ),
  385. Expanded(
  386. child: SizedBox(
  387. height: 70,
  388. child: Obx(
  389. () => VSearchInput(
  390. textEditingController: searchTextEditingController,
  391. placeholder:
  392. "身份证号码/姓名${controller.state.isOnline ? '/手机号/地址' : ''}",
  393. clearable: true,
  394. onClear: () {},
  395. onSearch: (value) {
  396. controller.state.searchString = value;
  397. controller.reloadList();
  398. },
  399. ),
  400. ),
  401. ),
  402. ),
  403. const SizedBox(width: 8),
  404. SizedBox(
  405. width: 180,
  406. height: 70,
  407. child: VButton(
  408. onTap: onFilterPressed,
  409. child: Row(
  410. mainAxisAlignment: MainAxisAlignment.center,
  411. children: const [
  412. Icon(Icons.filter_alt, size: 24),
  413. Text("筛选", style: TextStyle(fontSize: 20)),
  414. ],
  415. ),
  416. ),
  417. ),
  418. ],
  419. ),
  420. );
  421. }
  422. }
  423. class _PatientCard extends StatelessWidget {
  424. final PatientModelDTO dto;
  425. const _PatientCard({required this.dto});
  426. @override
  427. Widget build(BuildContext context) {
  428. final body = Stack(
  429. children: [
  430. Container(
  431. padding: const EdgeInsets.symmetric(
  432. horizontal: 16,
  433. vertical: 12,
  434. ),
  435. child: Column(
  436. crossAxisAlignment: CrossAxisAlignment.start,
  437. children: [
  438. const SizedBox(height: 8),
  439. SizedBox(
  440. child: Text(
  441. dto.patientName!,
  442. overflow: TextOverflow.ellipsis,
  443. style: const TextStyle(
  444. color: Colors.black,
  445. fontSize: 26,
  446. fontWeight: FontWeight.bold,
  447. ),
  448. ),
  449. ),
  450. const SizedBox(height: 8),
  451. LayoutBuilder(
  452. builder: (context, c) {
  453. final width = c.maxWidth - 80;
  454. // 不和状态标签重叠,并保持一定距离
  455. return SizedBox(width: width, child: _buildBaseInfoRow());
  456. },
  457. ),
  458. const SizedBox(height: 4),
  459. _buildPhone(),
  460. const SizedBox(height: 4),
  461. _buildCardNo(),
  462. if (Store.user.hasFeature(FeatureKeys.CrowdClassification) &&
  463. (dto.labelNames?.isNotEmpty ?? false)) ...[
  464. const Expanded(child: SizedBox()),
  465. _buildClassTags(),
  466. ],
  467. ],
  468. ),
  469. ),
  470. Positioned(
  471. top: 0,
  472. right: 0,
  473. child: _PatientSignStatusTag(
  474. dto: dto,
  475. ),
  476. ),
  477. if (kIsOnline || dto.isExistLocalData == true)
  478. Positioned(
  479. right: 0,
  480. bottom: 0,
  481. child: _PatientRemoveMarkButton(dto: dto),
  482. ),
  483. ],
  484. );
  485. return Material(
  486. borderRadius: GlobalStyles.borderRadius,
  487. child: Ink(
  488. decoration: BoxDecoration(
  489. color: Colors.white,
  490. borderRadius: GlobalStyles.borderRadius,
  491. border: Border.all(color: Colors.grey.shade400),
  492. ),
  493. child: InkWell(
  494. borderRadius: GlobalStyles.borderRadius,
  495. onTap: () {
  496. // Get.back();
  497. Get.find<PatientListController>().patientListGotoDetail(dto);
  498. },
  499. child: body,
  500. ),
  501. ),
  502. );
  503. }
  504. Widget _buildBaseInfoRow() {
  505. final birthday = dto.birthday!.toLocal();
  506. final age = DataTimeUtils.calculateAge(birthday);
  507. return Row(
  508. mainAxisSize: MainAxisSize.max,
  509. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  510. children: [
  511. Expanded(
  512. child: Row(
  513. mainAxisSize: MainAxisSize.max,
  514. mainAxisAlignment: MainAxisAlignment.start,
  515. children: [
  516. Expanded(
  517. flex: 1,
  518. child: Text(
  519. RpcEnumLabels.gender[dto.patientGender] ?? "未知",
  520. style: const TextStyle(
  521. color: Colors.grey,
  522. fontSize: 20,
  523. fontWeight: FontWeight.bold,
  524. ),
  525. ),
  526. ),
  527. Expanded(
  528. flex: 1,
  529. child: Text(
  530. "$age岁",
  531. style: const TextStyle(
  532. color: Colors.grey,
  533. fontSize: 20,
  534. ),
  535. ),
  536. ),
  537. ],
  538. ),
  539. ),
  540. ],
  541. );
  542. }
  543. Widget _buildClassTags() {
  544. return Column(
  545. children: [
  546. ConstrainedBox(
  547. constraints: const BoxConstraints(
  548. minWidth: double.infinity,
  549. maxHeight: 28,
  550. ),
  551. child: ListView(
  552. controller: ScrollController(),
  553. scrollDirection: Axis.horizontal,
  554. children: [
  555. Row(
  556. children: dto.labelNames!
  557. .map(
  558. (e) => TagWidget(
  559. height: 50,
  560. label: e,
  561. borderColor: (e == CrowdLabels.CHILDREN ||
  562. e == CrowdLabels.ELDERLY)
  563. ? Colors.blue
  564. : Colors.orange,
  565. backgroundColor: Colors.transparent,
  566. textColor: Colors.black,
  567. padding: EdgeInsets.only(
  568. top: 2,
  569. bottom: 2,
  570. right: 8,
  571. left: 4,
  572. ),
  573. ),
  574. )
  575. .toList(),
  576. )
  577. ]),
  578. ),
  579. ],
  580. );
  581. }
  582. Widget _buildPhone() {
  583. if (dto.phone != null && dto.phone!.isNotEmpty) {
  584. String phone = dto.phone!;
  585. if (Store.app.enableEncryptSensitiveInfo) {
  586. phone = SensitiveUtils.desensitizeMobilePhone(phone);
  587. }
  588. return Text(
  589. '手机号:$phone',
  590. style: const TextStyle(color: Colors.grey, fontSize: 18),
  591. );
  592. } else {
  593. return const SizedBox();
  594. }
  595. }
  596. Widget _buildCardNo() {
  597. if (dto.cardNo != null && dto.cardNo!.isNotEmpty) {
  598. String cardNo = dto.cardNo!;
  599. if (Store.app.enableEncryptSensitiveInfo) {
  600. cardNo = SensitiveUtils.desensitizeIdCard(cardNo);
  601. }
  602. return Text(
  603. '证件号码:$cardNo',
  604. style: const TextStyle(color: Colors.grey, fontSize: 18),
  605. );
  606. } else {
  607. return const SizedBox();
  608. }
  609. }
  610. }
  611. class _PatientSignStatusTag extends StatelessWidget {
  612. final PatientModelDTO dto;
  613. _PatientSignStatusTag({required this.dto});
  614. final ContractUtils _contractUtils = ContractUtils();
  615. @override
  616. Widget build(BuildContext context) {
  617. return Container(
  618. alignment: Alignment.centerRight,
  619. width: 120,
  620. padding: const EdgeInsets.only(top: 18),
  621. child: StatusLabel(
  622. title: _contractUtils.dataOfflineStatus(dto.isExistLocalData!),
  623. color: _contractUtils.dataOfflineColor(dto.isExistLocalData!),
  624. ),
  625. );
  626. }
  627. }
  628. class _PatientRemoveMarkButton extends StatelessWidget {
  629. final PatientModelDTO dto;
  630. const _PatientRemoveMarkButton({required this.dto});
  631. @override
  632. Widget build(BuildContext context) {
  633. return GestureDetector(
  634. onTap: () async {
  635. var message = await _getDeleteShowMessage();
  636. Get.dialog(
  637. VAlertDialog(
  638. title: "提示",
  639. width: 300,
  640. content: Container(
  641. height: 64,
  642. padding: const EdgeInsets.symmetric(horizontal: 24),
  643. alignment: Alignment.center,
  644. child: Text(
  645. " “${dto.patientName}”$message是否确定删除?",
  646. style: TextStyle(fontSize: 20),
  647. ),
  648. ),
  649. onConfirm: () async {
  650. Get.find<PatientListController>().removePatient(dto.code!);
  651. Get.back();
  652. },
  653. ),
  654. barrierDismissible: false,
  655. barrierColor: Colors.black.withOpacity(.4),
  656. );
  657. },
  658. child: Container(
  659. padding: EdgeInsets.only(top: 10, left: 10, right: 2, bottom: 2),
  660. decoration: BoxDecoration(
  661. color: Colors.red,
  662. borderRadius: BorderRadius.only(
  663. topLeft: Radius.circular(32),
  664. bottomRight: Radius.circular(4),
  665. ),
  666. ),
  667. child: Icon(
  668. Icons.delete_forever,
  669. size: 26,
  670. color: Colors.white,
  671. ),
  672. ),
  673. );
  674. }
  675. Future<String> _getDeleteShowMessage() async {
  676. String message = "";
  677. var diagnosisManager = Get.find<IDiagnosisManager>();
  678. var diagnosisList = await diagnosisManager.getDiagnosisAggregationPageAsync(
  679. dto.code!, 1, 10);
  680. var listRecords = await diagnosisManager.getListByPatientCode(dto.code!);
  681. if ((diagnosisList != null && diagnosisList.dataCount > 0) ||
  682. (listRecords != null && listRecords.length > 0)) {
  683. message += "检测记录";
  684. }
  685. var examManager = Get.find<IExamManager>();
  686. var examList =
  687. await examManager.getPatientExamByPageAsync(dto.code!, "HEITCMC");
  688. if (examList != null && examList.length > 0) {
  689. if (message.length > 0) {
  690. message += "和中医体质记录";
  691. } else {
  692. message += "中医体质记录";
  693. }
  694. }
  695. if (message.length > 0) {
  696. message = "有" + message + ",";
  697. }
  698. return message;
  699. }
  700. }
  701. class _PatientStatisticWidget extends StatelessWidget {
  702. @override
  703. Widget build(BuildContext context) {
  704. final controller = Get.find<PatientListController>();
  705. final state = controller.state;
  706. return Container(
  707. width: 160,
  708. alignment: Alignment.centerLeft,
  709. child: Obx(() {
  710. return Column(
  711. mainAxisAlignment: MainAxisAlignment.center,
  712. crossAxisAlignment: CrossAxisAlignment.start,
  713. children: [
  714. _buildItem(context, "当日建档数量: ", state.statisticTodayCount),
  715. const SizedBox(height: 8),
  716. _buildItem(context, "总共建档数量: ", state.statisticTotalCount),
  717. const SizedBox(height: 8),
  718. _buildItem(context, "查询结果数量: ", state.queryResultTotalCount),
  719. ],
  720. );
  721. }),
  722. );
  723. }
  724. Widget _buildItem(BuildContext context, String label, int count) {
  725. return Expanded(
  726. child: RichText(
  727. text: TextSpan(
  728. style: TextStyle(
  729. fontSize: 16,
  730. color: Colors.black,
  731. fontFamily: "NotoSansSC",
  732. fontFamilyFallback: const ["NotoSansSC"],
  733. ),
  734. children: [
  735. TextSpan(text: label),
  736. TextSpan(text: " "),
  737. TextSpan(
  738. text: "${count}",
  739. style: TextStyle(
  740. fontSize: 16,
  741. color: Theme.of(context).primaryColor,
  742. ),
  743. ),
  744. ],
  745. ),
  746. ),
  747. );
  748. }
  749. }