add_patient_page.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. import 'package:auto_route/annotations.dart';
  2. import 'package:bot_toast/bot_toast.dart';
  3. import 'package:eitc_erm_dental_flutter/exts.dart';
  4. import 'package:eitc_erm_dental_flutter/funcs.dart';
  5. import 'package:eitc_erm_dental_flutter/generated/assets.dart';
  6. import 'package:eitc_erm_dental_flutter/pages/patient/vm/patient_view_model.dart';
  7. import 'package:eitc_erm_dental_flutter/widget/custom_divider.dart';
  8. import 'package:eitc_erm_dental_flutter/widget/main_button.dart';
  9. import 'package:flutter/material.dart';
  10. import 'package:flutter/services.dart';
  11. import 'package:flutter_riverpod/flutter_riverpod.dart';
  12. import 'package:flutter_screenutil/flutter_screenutil.dart';
  13. EdgeInsets get _itemPadding =>
  14. EdgeInsets.symmetric(horizontal: 12.w, vertical: 15.h);
  15. Widget _getTitleText(BuildContext context, String text) {
  16. return Text(
  17. text,
  18. style: context.titleMedium,
  19. );
  20. }
  21. TextStyle? _getTextStyle(BuildContext context) {
  22. return context.bodySmall;
  23. }
  24. ///添加咨询人页面
  25. @RoutePage(name: "addPatientRoute")
  26. class AddPatientPage extends ConsumerStatefulWidget {
  27. const AddPatientPage({super.key});
  28. @override
  29. ConsumerState createState() => _AddPatientPageState();
  30. }
  31. class _AddPatientPageState extends ConsumerState<AddPatientPage> {
  32. String _relation = "";
  33. //性别,0男性,1女性
  34. int _gender = 0;
  35. final TextEditingController _nameController = TextEditingController();
  36. final TextEditingController _ageController = TextEditingController();
  37. final TextEditingController _idCardController = TextEditingController();
  38. @override
  39. Widget build(BuildContext context) {
  40. return Scaffold(
  41. appBar: AppBar(
  42. title: Text(getS().addPatient),
  43. centerTitle: true,
  44. forceMaterialTransparency: true,
  45. ),
  46. body: SafeArea(
  47. child: Column(
  48. children: [
  49. Expanded(
  50. child: Container(
  51. padding: EdgeInsets.fromLTRB(16.w, 12.h, 16.w, 12.h),
  52. color: Color(0xFFF4F4F4),
  53. child: SingleChildScrollView(
  54. child: Column(
  55. crossAxisAlignment: CrossAxisAlignment.start,
  56. children: [
  57. Text(
  58. getS().addPatientHint,
  59. style: _getTextStyle(context),
  60. ),
  61. SizedBox(
  62. height: 8.h,
  63. ),
  64. Container(
  65. decoration: BoxDecoration(
  66. color: Colors.white,
  67. borderRadius: BorderRadius.circular(10.r),
  68. ),
  69. child: Column(
  70. children: [
  71. //性别
  72. _RelationSelector(onSelect: _onRelationSelect),
  73. CustomDivider(
  74. height: 0.0,
  75. ),
  76. //名字
  77. _getNameInput(),
  78. CustomDivider(
  79. height: 0.0,
  80. ),
  81. //性别
  82. _GenderSelector(onChecked: _onGenderChecked),
  83. CustomDivider(
  84. height: 0.0,
  85. ),
  86. //年龄
  87. _getAgeInput(),
  88. CustomDivider(
  89. height: 0.0,
  90. ),
  91. //身份证
  92. _getIdCardInput(),
  93. ],
  94. ),
  95. )
  96. ],
  97. ),
  98. ),
  99. )),
  100. Container(
  101. padding: EdgeInsets.fromLTRB(16.w, 14.h, 16.w, 34.h),
  102. width: double.infinity,
  103. child: MainButton(text: getS().submit, onPressed: _onSave),
  104. )
  105. ],
  106. )),
  107. );
  108. }
  109. Widget _getTextInput(TextEditingController controller, String hint,
  110. {TextInputType? keyboardType,
  111. int? maxLength,
  112. List<TextInputFormatter>? formatters}) {
  113. return TextField(
  114. textAlign: TextAlign.end,
  115. maxLength: maxLength,
  116. keyboardType: keyboardType,
  117. controller: controller,
  118. inputFormatters: formatters,
  119. style: _getTextStyle(context),
  120. decoration: InputDecoration(
  121. isDense: true,
  122. border: InputBorder.none,
  123. hintText: hint,
  124. counterText: "",
  125. hintStyle:
  126. context.bodySmall?.copyWith(color: context.onSurfaceVariantColor),
  127. ),
  128. );
  129. }
  130. ///获取名字输入框
  131. Widget _getNameInput() {
  132. return Padding(
  133. padding: _itemPadding,
  134. child: Row(
  135. children: [
  136. _getTitleText(context, getS().name),
  137. SizedBox(
  138. width: 15.w,
  139. ),
  140. Expanded(
  141. child:
  142. _getTextInput(_nameController, getS().pleaseInputRealName)),
  143. ],
  144. ),
  145. );
  146. }
  147. ///获取年龄输入框
  148. Widget _getAgeInput() {
  149. return Padding(
  150. padding: _itemPadding,
  151. child: Row(
  152. children: [
  153. _getTitleText(context, getS().age),
  154. SizedBox(
  155. width: 15.w,
  156. ),
  157. Expanded(
  158. child: _getTextInput(_ageController, getS().pleaseInputAge,
  159. maxLength: 3,
  160. keyboardType: TextInputType.number,
  161. formatters: [FilteringTextInputFormatter.digitsOnly])),
  162. ],
  163. ),
  164. );
  165. }
  166. ///获取身份证输入框
  167. Widget _getIdCardInput() {
  168. return Padding(
  169. padding: _itemPadding,
  170. child: Row(
  171. children: [
  172. _getTitleText(context, getS().idCard),
  173. SizedBox(
  174. width: 15.w,
  175. ),
  176. Expanded(
  177. child: _getTextInput(
  178. _idCardController, getS().pleaseInputValidIdCard,
  179. maxLength: 18,
  180. formatters: [
  181. FilteringTextInputFormatter.allow(RegExp(r'[0-9Xx]'))
  182. ])),
  183. ],
  184. ),
  185. );
  186. }
  187. ///当选择了关系
  188. void _onRelationSelect(String relation) {
  189. _relation = relation;
  190. }
  191. ///当选择了性别
  192. void _onGenderChecked(int gender) {
  193. logd("选择了性别=$gender");
  194. _gender = gender;
  195. }
  196. bool _checkInput() {
  197. if (_relation.isEmpty) {
  198. showToast(text: getS().pleaseSelectRelation);
  199. return false;
  200. }
  201. if (_nameController.text.isEmpty) {
  202. showToast(text: getS().pleaseInputRealName);
  203. return false;
  204. }
  205. if (_ageController.text.isEmpty) {
  206. showToast(text: getS().pleaseInputAge);
  207. return false;
  208. }
  209. if (!validIdCard(_idCardController.text)) {
  210. showToast(text: getS().pleaseInputValidIdCard);
  211. return false;
  212. }
  213. String idCard = _idCardController.text;
  214. try {
  215. int age = getAgeFromIdCard(idCard);
  216. int sex = getGenderFromIdCard(idCard);
  217. //判断年龄
  218. if (age != int.parse(_ageController.text)) {
  219. showToast(text: getS().ageNotMatchIdCard);
  220. return false;
  221. }
  222. if (!((sex == 1 && _gender == 1) || (sex == 0 && _gender == 0))) {
  223. showToast(text: getS().genderNotMatchIdCard);
  224. return false;
  225. }
  226. } catch (e) {
  227. loge("校验身份证异常", error: e);
  228. }
  229. return true;
  230. }
  231. ///保存
  232. void _onSave() async {
  233. if (!_checkInput()) {
  234. return;
  235. }
  236. var cancelFunc = BotToast.showLoading(
  237. clickClose: false,
  238. crossPage: false,
  239. backButtonBehavior: BackButtonBehavior.ignore);
  240. bool bo = await ref
  241. .read(patientListProvider.notifier)
  242. .addPatient(_nameController.text, _idCardController.text, _relation);
  243. cancelFunc();
  244. if (bo) {
  245. showToast(text: getS().saveSuccess);
  246. if (mounted) {
  247. Navigator.pop(context, true);
  248. }
  249. } else {
  250. showToast(text: getS().saveFailed);
  251. }
  252. }
  253. }
  254. ///关系选择器
  255. class _RelationSelector extends ConsumerStatefulWidget {
  256. final void Function(String) onSelect;
  257. const _RelationSelector({required this.onSelect});
  258. @override
  259. ConsumerState createState() => __RelationSelectorState();
  260. }
  261. class __RelationSelectorState extends ConsumerState<_RelationSelector> {
  262. String _selectedRelation = "";
  263. bool _isLoading = true;
  264. List<String> _relationList = [];
  265. @override
  266. void initState() {
  267. super.initState();
  268. ref.listenManual(patientRelationListProvider, (_, value) {
  269. if (value is AsyncLoading) {
  270. return;
  271. }
  272. if (value is AsyncData<List<String>>) {
  273. AsyncData<List<String>> data = value;
  274. _relationList = data.value;
  275. }
  276. setState(() {
  277. _isLoading = false;
  278. });
  279. });
  280. }
  281. @override
  282. Widget build(BuildContext context) {
  283. return GestureDetector(
  284. behavior: HitTestBehavior.opaque,
  285. onTap: _onTap,
  286. child: Padding(
  287. padding: _itemPadding,
  288. child: Row(
  289. children: [
  290. _getTitleText(context, getS().relation),
  291. Spacer(),
  292. if (_isLoading)
  293. SizedBox.fromSize(
  294. size: Size.square(15.r),
  295. child: CircularProgressIndicator(),
  296. )
  297. else
  298. Row(
  299. children: [
  300. Text(
  301. _selectedRelation.isEmpty
  302. ? getS().pleaseSelectRelation
  303. : _selectedRelation,
  304. style: _getTextStyle(context),
  305. ),
  306. SizedBox(
  307. width: 5.w,
  308. ),
  309. Icon(
  310. Icons.arrow_forward_ios,
  311. size: 15.r,
  312. )
  313. ],
  314. )
  315. ],
  316. ),
  317. ),
  318. );
  319. }
  320. void _onTap() async {
  321. List<String> list = _relationList;
  322. String? result = await showModalBottomSheet(
  323. context: context,
  324. builder: (_) {
  325. return Container(
  326. padding: EdgeInsets.symmetric(vertical: 15.h),
  327. width: double.infinity,
  328. decoration: BoxDecoration(
  329. color: Colors.white, borderRadius: BorderRadius.circular(8.r)),
  330. child: ListView.builder(
  331. shrinkWrap: true,
  332. itemBuilder: (ctx, index) {
  333. String relation = list[index];
  334. return GestureDetector(
  335. onTap: () => Navigator.pop(ctx, relation),
  336. child: Padding(
  337. padding:
  338. EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
  339. child: Text(
  340. relation,
  341. textAlign: TextAlign.center,
  342. style: ctx.titleMedium,
  343. ),
  344. ),
  345. );
  346. },
  347. itemCount: list.length,
  348. ),
  349. );
  350. });
  351. logd("选择了$result");
  352. if (result.isNullOrEmpty) {
  353. return;
  354. }
  355. setState(() {
  356. _selectedRelation = result!;
  357. });
  358. widget.onSelect(result!);
  359. }
  360. }
  361. ///性别选择器
  362. class _GenderSelector extends StatefulWidget {
  363. final void Function(int gender) onChecked;
  364. const _GenderSelector({required this.onChecked});
  365. @override
  366. State<_GenderSelector> createState() => _GenderSelectorState();
  367. }
  368. class _GenderSelectorState extends State<_GenderSelector> {
  369. int _gender = 0;
  370. @override
  371. Widget build(BuildContext context) {
  372. return Padding(
  373. padding: _itemPadding,
  374. child: Row(
  375. children: [
  376. _getTitleText(context, getS().gender),
  377. Spacer(),
  378. Row(
  379. children: [
  380. _getCheck(0, getS().male),
  381. SizedBox(
  382. width: 20.w,
  383. ),
  384. _getCheck(1, getS().female),
  385. ],
  386. )
  387. ],
  388. ),
  389. );
  390. }
  391. Widget _getCheck(int checkedValue, String text) {
  392. return GestureDetector(
  393. onTap: () {
  394. setState(() {
  395. _gender = checkedValue;
  396. });
  397. widget.onChecked(checkedValue);
  398. },
  399. child: Row(
  400. children: [
  401. SizedBox(
  402. width: 24.r,
  403. height: 24.r,
  404. child: Image.asset(_gender == checkedValue
  405. ? Assets.imagesIconGenderChecked
  406. : Assets.imagesIconGenderUnchecked),
  407. ),
  408. Text(
  409. text,
  410. style: _getTextStyle(context),
  411. )
  412. ],
  413. ),
  414. );
  415. }
  416. }