view.dart 26 KB

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