data_table.dart 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import 'package:flutter/material.dart';
  2. import 'package:get/get.dart';
  3. import 'package:vitalapp/architecture/utils/advance_debounce.dart';
  4. import 'package:vitalapp/components/no_data_view.dart';
  5. import 'package:vitalapp/database/entities/defines.dart';
  6. import 'package:vitalapp/database/entities/patient.dart';
  7. import '../controller.dart';
  8. import '../state.dart';
  9. enum _SyncCategoryEnum {
  10. patient,
  11. diagnosis,
  12. exam,
  13. gxyFollowUp,
  14. tnbFollowUp,
  15. tcmConsitution,
  16. }
  17. class _TableUtils {
  18. static Map<int, TableColumnWidth> columnWidths = {
  19. 0: const FixedColumnWidth(100),
  20. 1: const FixedColumnWidth(200),
  21. 2: const FixedColumnWidth(250),
  22. 3: const FixedColumnWidth(100),
  23. 4: const FlexColumnWidth(),
  24. 5: const FixedColumnWidth(100),
  25. };
  26. /// 行高
  27. static const double rowHeight = 48;
  28. /// 字号
  29. static const double textFontSize = 18;
  30. }
  31. class SyncDataTableHeader extends GetView<SyncCenterController> {
  32. const SyncDataTableHeader({super.key});
  33. @override
  34. Widget build(BuildContext context) {
  35. return Table(
  36. columnWidths: _TableUtils.columnWidths,
  37. children: [
  38. TableRow(
  39. decoration: BoxDecoration(
  40. color: Theme.of(context).primaryColor,
  41. ),
  42. children: const [
  43. SyncTableTextCell(text: "序号", isBlod: true),
  44. SyncTableTextCell(text: "姓名", isBlod: true),
  45. SyncTableTextCell(text: "身份证号", isBlod: true),
  46. SyncTableTextCell(text: "状态", isBlod: true),
  47. SyncTableTextCell(text: "待上传项目", isBlod: true),
  48. SyncTableTextCell(text: "操作", isBlod: true),
  49. ],
  50. ),
  51. ],
  52. );
  53. }
  54. }
  55. class SyncDataTableBody extends GetView<SyncCenterController> {
  56. const SyncDataTableBody({super.key});
  57. @override
  58. Widget build(BuildContext context) {
  59. final scrollController = ScrollController();
  60. scrollController.addListener(
  61. () {
  62. // 如果滑动到底部
  63. try {
  64. if (scrollController.position.atEdge) {
  65. if (scrollController.position.pixels != 0) {
  66. if (controller.state.hasNextPage) {
  67. controller.loadNextPageList();
  68. }
  69. }
  70. }
  71. } catch (e) {
  72. // logger.e("listViewScrollController exception:", e);
  73. }
  74. },
  75. );
  76. return RefreshIndicator(
  77. onRefresh: () async {
  78. await controller.reloadPageList();
  79. },
  80. child: Obx(() {
  81. final dataList = controller.state.dataList;
  82. if (dataList.isEmpty) {
  83. return const VNoDataView();
  84. }
  85. final List<TableRow> rows = [];
  86. for (var i = 0; i < dataList.length; i++) {
  87. final model = dataList[i];
  88. rows.add(_buildRow(context, i, model));
  89. }
  90. return SingleChildScrollView(
  91. controller: scrollController,
  92. child: Table(
  93. columnWidths: _TableUtils.columnWidths,
  94. children: rows,
  95. ),
  96. );
  97. }),
  98. );
  99. }
  100. TableRow _buildRow(
  101. BuildContext context, int index, SyncCenterDataModel model) {
  102. final no = (index + 1).toString();
  103. final data = model.data;
  104. return TableRow(
  105. key: PageStorageKey<String>(data.code),
  106. decoration: BoxDecoration(
  107. color: index % 2 == 0 ? null : Colors.blue.shade100,
  108. ),
  109. children: [
  110. SyncTableTextCell(text: no),
  111. SyncTableTextCell(text: data.name),
  112. SyncTableTextCell(text: data.code),
  113. SyncTableTextCell(text: data.overallSyncState.getDescription()),
  114. _buildTagsCell(data),
  115. _buildOperateCell(context, index, model),
  116. ],
  117. );
  118. }
  119. /// 操作栏
  120. Widget _buildOperateCell(
  121. BuildContext context, int index, SyncCenterDataModel model) {
  122. final widgets = <Widget>[];
  123. if (!model.isSynchronized) {
  124. widgets.add(
  125. TextButton.icon(
  126. onPressed: () {
  127. Debouncer.run(
  128. () {
  129. controller.uploadSingle(index);
  130. },
  131. );
  132. },
  133. icon: const Icon(
  134. Icons.cloud_upload_outlined,
  135. size: _TableUtils.textFontSize + 6,
  136. ),
  137. label: Text(
  138. "上传",
  139. style: TextStyle(
  140. color: Theme.of(context).primaryColor,
  141. fontSize: _TableUtils.textFontSize,
  142. ),
  143. ),
  144. ),
  145. );
  146. }
  147. return _SyncTableCell(
  148. child: Row(
  149. mainAxisAlignment: MainAxisAlignment.center,
  150. children: widgets,
  151. ),
  152. );
  153. }
  154. /// 标签栏
  155. Widget _buildTagsCell(PatientEntity data) {
  156. final List<Widget> tags = [];
  157. if (data.syncState != OfflineDataSyncState.success) {
  158. tags.add(const _SyncCategoryTag(category: _SyncCategoryEnum.patient));
  159. }
  160. if (data.examCount > 0) {
  161. tags.add(const _SyncCategoryTag(category: _SyncCategoryEnum.exam));
  162. }
  163. if (data.tcmConsitutionCount > 0) {
  164. tags.add(
  165. const _SyncCategoryTag(category: _SyncCategoryEnum.tcmConsitution));
  166. }
  167. if (data.gxyFollowUpCount > 0) {
  168. tags.add(const _SyncCategoryTag(category: _SyncCategoryEnum.gxyFollowUp));
  169. }
  170. if (data.tnbFollowUpCount > 0) {
  171. tags.add(const _SyncCategoryTag(category: _SyncCategoryEnum.tnbFollowUp));
  172. }
  173. if (data.diagnosisCount > 0) {
  174. tags.add(const _SyncCategoryTag(category: _SyncCategoryEnum.diagnosis));
  175. }
  176. return _SyncTableCell(
  177. child: Wrap(
  178. spacing: 4,
  179. runSpacing: 4,
  180. children: tags,
  181. ),
  182. );
  183. }
  184. }
  185. class SyncTableTextCell extends StatelessWidget {
  186. final String? text;
  187. final bool isBlod;
  188. final AlignmentGeometry? alignment;
  189. const SyncTableTextCell({
  190. super.key,
  191. this.text,
  192. this.isBlod = false,
  193. this.alignment,
  194. });
  195. @override
  196. Widget build(BuildContext context) {
  197. final child = Text(
  198. text ?? "",
  199. style: TextStyle(
  200. fontSize: _TableUtils.textFontSize,
  201. fontWeight: isBlod ? FontWeight.bold : FontWeight.normal,
  202. ),
  203. );
  204. return _SyncTableCell(
  205. alignment: alignment,
  206. child: child,
  207. );
  208. }
  209. }
  210. class _SyncTableCell extends StatelessWidget {
  211. final Widget? child;
  212. final AlignmentGeometry? alignment;
  213. const _SyncTableCell({this.alignment, this.child});
  214. @override
  215. Widget build(BuildContext context) {
  216. return TableCell(
  217. child: Container(
  218. alignment: alignment ?? Alignment.center,
  219. height: _TableUtils.rowHeight,
  220. child: child,
  221. ),
  222. );
  223. }
  224. }
  225. class _SyncCategoryTag extends StatelessWidget {
  226. static final Map<_SyncCategoryEnum, List> _configMap = {
  227. _SyncCategoryEnum.patient: ["档案", Colors.blue],
  228. _SyncCategoryEnum.diagnosis: ["检测", Colors.orange],
  229. _SyncCategoryEnum.exam: ["检查", Colors.green],
  230. _SyncCategoryEnum.gxyFollowUp: ["高血压随访", Colors.purple],
  231. _SyncCategoryEnum.tnbFollowUp: ["糖尿病随访", Colors.purple],
  232. _SyncCategoryEnum.tcmConsitution: ["中医体质", Colors.brown.shade400],
  233. };
  234. final _SyncCategoryEnum category;
  235. const _SyncCategoryTag({required this.category});
  236. @override
  237. Widget build(BuildContext context) {
  238. final configs = _configMap[category]!;
  239. final title = configs[0];
  240. final color = configs[1];
  241. return Container(
  242. padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
  243. decoration: BoxDecoration(
  244. borderRadius: BorderRadius.circular(4),
  245. color: color,
  246. ),
  247. child: Text(
  248. title,
  249. style: const TextStyle(color: Colors.white, fontSize: 14),
  250. ),
  251. );
  252. }
  253. }