add_patient_page.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  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: Theme.of(context).textTheme.titleMedium,
  19. );
  20. }
  21. TextStyle? _getTextStyle(BuildContext context) {
  22. return Theme.of(context).textTheme.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: Theme.of(context)
  126. .textTheme
  127. .bodySmall
  128. ?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
  129. ),
  130. );
  131. }
  132. ///获取名字输入框
  133. Widget _getNameInput() {
  134. return Padding(
  135. padding: _itemPadding,
  136. child: Row(
  137. children: [
  138. _getTitleText(context, getS().name),
  139. SizedBox(
  140. width: 15.w,
  141. ),
  142. Expanded(
  143. child:
  144. _getTextInput(_nameController, getS().pleaseInputRealName)),
  145. ],
  146. ),
  147. );
  148. }
  149. ///获取年龄输入框
  150. Widget _getAgeInput() {
  151. return Padding(
  152. padding: _itemPadding,
  153. child: Row(
  154. children: [
  155. _getTitleText(context, getS().age),
  156. SizedBox(
  157. width: 15.w,
  158. ),
  159. Expanded(
  160. child: _getTextInput(_ageController, getS().pleaseInputAge,
  161. maxLength: 3,
  162. keyboardType: TextInputType.number,
  163. formatters: [FilteringTextInputFormatter.digitsOnly])),
  164. ],
  165. ),
  166. );
  167. }
  168. ///获取身份证输入框
  169. Widget _getIdCardInput() {
  170. return Padding(
  171. padding: _itemPadding,
  172. child: Row(
  173. children: [
  174. _getTitleText(context, getS().idCard),
  175. SizedBox(
  176. width: 15.w,
  177. ),
  178. Expanded(
  179. child: _getTextInput(
  180. _idCardController, getS().pleaseInputValidIdCard,
  181. maxLength: 18,
  182. formatters: [
  183. FilteringTextInputFormatter.allow(RegExp(r'[0-9Xx]'))
  184. ])),
  185. ],
  186. ),
  187. );
  188. }
  189. ///当选择了关系
  190. void _onRelationSelect(String relation) {
  191. _relation = relation;
  192. }
  193. ///当选择了性别
  194. void _onGenderChecked(int gender) {
  195. logd("选择了性别=$gender");
  196. _gender = gender;
  197. }
  198. bool _checkInput() {
  199. if (_relation.isEmpty) {
  200. showToast(text: getS().pleaseSelectRelation);
  201. return false;
  202. }
  203. if (_nameController.text.isEmpty) {
  204. showToast(text: getS().pleaseInputRealName);
  205. return false;
  206. }
  207. if (_ageController.text.isEmpty) {
  208. showToast(text: getS().pleaseInputAge);
  209. return false;
  210. }
  211. if (!validIdCard(_idCardController.text)) {
  212. showToast(text: getS().pleaseInputValidIdCard);
  213. return false;
  214. }
  215. String idCard = _idCardController.text;
  216. try {
  217. int age = getAgeFromIdCard(idCard);
  218. int sex = getGenderFromIdCard(idCard);
  219. //判断年龄
  220. if (age != int.parse(_ageController.text)) {
  221. showToast(text: getS().ageNotMatchIdCard);
  222. return false;
  223. }
  224. if (!((sex == 1 && _gender == 1) || (sex == 0 && _gender == 0))) {
  225. showToast(text: getS().genderNotMatchIdCard);
  226. return false;
  227. }
  228. } catch (e) {
  229. loge("校验身份证异常", error: e);
  230. }
  231. return true;
  232. }
  233. ///保存
  234. void _onSave() async {
  235. if (!_checkInput()) {
  236. return;
  237. }
  238. var cancelFunc = BotToast.showLoading(
  239. clickClose: false,
  240. crossPage: false,
  241. backButtonBehavior: BackButtonBehavior.ignore);
  242. bool bo = await ref
  243. .read(patientListProvider.notifier)
  244. .addPatient(_nameController.text, _idCardController.text, _relation);
  245. cancelFunc();
  246. if (bo) {
  247. showToast(text: getS().saveSuccess);
  248. if (mounted) {
  249. Navigator.pop(context, true);
  250. }
  251. } else {
  252. showToast(text: getS().saveFailed);
  253. }
  254. }
  255. }
  256. ///关系选择器
  257. class _RelationSelector extends ConsumerStatefulWidget {
  258. final void Function(String) onSelect;
  259. const _RelationSelector({required this.onSelect});
  260. @override
  261. ConsumerState createState() => __RelationSelectorState();
  262. }
  263. class __RelationSelectorState extends ConsumerState<_RelationSelector> {
  264. String _selectedRelation = "";
  265. bool _isLoading = true;
  266. List<String> _relationList = [];
  267. @override
  268. void initState() {
  269. super.initState();
  270. ref.listenManual(patientRelationListProvider, (_, value) {
  271. if (value is AsyncLoading) {
  272. return;
  273. }
  274. if (value is AsyncData<List<String>>) {
  275. AsyncData<List<String>> data = value;
  276. _relationList = data.value;
  277. }
  278. setState(() {
  279. _isLoading = false;
  280. });
  281. });
  282. }
  283. @override
  284. Widget build(BuildContext context) {
  285. return GestureDetector(
  286. behavior: HitTestBehavior.opaque,
  287. onTap: _onTap,
  288. child: Padding(
  289. padding: _itemPadding,
  290. child: Row(
  291. children: [
  292. _getTitleText(context, getS().relation),
  293. Spacer(),
  294. if (_isLoading)
  295. SizedBox.fromSize(
  296. size: Size.square(15.r),
  297. child: CircularProgressIndicator(),
  298. )
  299. else
  300. Row(
  301. children: [
  302. Text(
  303. _selectedRelation.isEmpty
  304. ? getS().pleaseSelectRelation
  305. : _selectedRelation,
  306. style: _getTextStyle(context),
  307. ),
  308. SizedBox(
  309. width: 5.w,
  310. ),
  311. Icon(
  312. Icons.arrow_forward_ios,
  313. size: 15.r,
  314. )
  315. ],
  316. )
  317. ],
  318. ),
  319. ),
  320. );
  321. }
  322. void _onTap() async {
  323. List<String> list = _relationList;
  324. String? result = await showModalBottomSheet(
  325. context: context,
  326. builder: (_) {
  327. return Container(
  328. padding: EdgeInsets.symmetric(vertical: 15.h),
  329. width: double.infinity,
  330. decoration: BoxDecoration(
  331. color: Colors.white, borderRadius: BorderRadius.circular(8.r)),
  332. child: ListView.builder(
  333. shrinkWrap: true,
  334. itemBuilder: (ctx, index) {
  335. String relation = list[index];
  336. return GestureDetector(
  337. onTap: () => Navigator.pop(ctx, relation),
  338. child: Padding(
  339. padding:
  340. EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
  341. child: Text(
  342. relation,
  343. textAlign: TextAlign.center,
  344. style: Theme.of(ctx).textTheme.titleMedium,
  345. ),
  346. ),
  347. );
  348. },
  349. itemCount: list.length,
  350. ),
  351. );
  352. });
  353. logd("选择了$result");
  354. if (result.isNullOrEmpty) {
  355. return;
  356. }
  357. setState(() {
  358. _selectedRelation = result!;
  359. });
  360. widget.onSelect(result!);
  361. }
  362. }
  363. ///性别选择器
  364. class _GenderSelector extends StatefulWidget {
  365. final void Function(int gender) onChecked;
  366. const _GenderSelector({required this.onChecked});
  367. @override
  368. State<_GenderSelector> createState() => _GenderSelectorState();
  369. }
  370. class _GenderSelectorState extends State<_GenderSelector> {
  371. int _gender = 0;
  372. @override
  373. Widget build(BuildContext context) {
  374. return Padding(
  375. padding: _itemPadding,
  376. child: Row(
  377. children: [
  378. _getTitleText(context, getS().gender),
  379. Spacer(),
  380. Row(
  381. children: [
  382. _getCheck(0, getS().male),
  383. SizedBox(
  384. width: 20.w,
  385. ),
  386. _getCheck(1, getS().female),
  387. ],
  388. )
  389. ],
  390. ),
  391. );
  392. }
  393. Widget _getCheck(int checkedValue, String text) {
  394. return GestureDetector(
  395. onTap: () {
  396. setState(() {
  397. _gender = checkedValue;
  398. });
  399. widget.onChecked(checkedValue);
  400. },
  401. child: Row(
  402. children: [
  403. SizedBox(
  404. width: 24.r,
  405. height: 24.r,
  406. child: Image.asset(_gender == checkedValue
  407. ? Assets.imagesIconGenderChecked
  408. : Assets.imagesIconGenderUnchecked),
  409. ),
  410. Text(
  411. text,
  412. style: _getTextStyle(context),
  413. )
  414. ],
  415. ),
  416. );
  417. }
  418. }