view.dart 25 KB

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