view.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:fis_jsonrpc/rpc.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:get/get.dart';
  6. import 'package:intl/intl.dart';
  7. import 'package:vnoteapp/components/alert_dialog.dart';
  8. import 'package:vnoteapp/components/appbar.dart';
  9. import 'package:vnoteapp/components/cell.dart';
  10. import 'package:vnoteapp/components/dialog_date.dart';
  11. import 'package:vnoteapp/components/dialog_input.dart';
  12. import 'package:vnoteapp/components/dialog_select.dart';
  13. import 'package:vnoteapp/components/panel.dart';
  14. import 'package:vnoteapp/store/store.dart';
  15. import 'controller.dart';
  16. import 'package:image_picker/image_picker.dart';
  17. class ServicePackageContractPage
  18. extends GetView<ServicePackageContractController> {
  19. const ServicePackageContractPage({super.key});
  20. static const double labelSize = 20;
  21. @override
  22. Widget build(BuildContext context) {
  23. return Scaffold(
  24. backgroundColor: const Color.fromRGBO(238, 238, 238, 1),
  25. appBar: VAppBar(
  26. titleWidget: const Text(
  27. "签约",
  28. style: TextStyle(fontSize: 24),
  29. ),
  30. actions: [
  31. TextButton(
  32. onPressed: () {
  33. Get.back();
  34. Get.toNamed(
  35. "/contract/contract_template",
  36. parameters: {
  37. "templateCode": "53C3323BB6444A109B2369703EFFDFF9",
  38. "patientInfo": json.encode(controller.patient.toJson()),
  39. "servicePackageCodes":
  40. controller.state.selectedServicePackageCode,
  41. "servicePackageNames":
  42. controller.state.selectedServicePackageName,
  43. "serviceTime": controller.state.serviceTime.toString(),
  44. "serviceStartDate":
  45. controller.state.serviceStartDate.toString(),
  46. "base64Image": controller.base64Image,
  47. "notes": controller.state.notes,
  48. },
  49. );
  50. },
  51. child: const Text(
  52. '下一步',
  53. style: TextStyle(color: Colors.white, fontSize: 20),
  54. ),
  55. ),
  56. const SizedBox(
  57. width: 15,
  58. ),
  59. ],
  60. ),
  61. endDrawer: _servicePackageDrawer(context),
  62. body: Builder(builder: (context) {
  63. return Padding(
  64. padding: const EdgeInsets.symmetric(horizontal: 200, vertical: 8),
  65. child: ListView(
  66. children: [
  67. Column(
  68. children: [
  69. _buildPhotoVPanel(),
  70. const SizedBox(height: 16),
  71. _buildContractDoctorVPanel(),
  72. const SizedBox(height: 16),
  73. _buildPatientVPanel(),
  74. const SizedBox(height: 16),
  75. _buildServicePackageVPanel(context),
  76. ],
  77. ),
  78. ],
  79. ),
  80. );
  81. }),
  82. );
  83. }
  84. Drawer _servicePackageDrawer(BuildContext context) {
  85. const double titleSize = 20;
  86. const double labelSize = 18;
  87. Widget buildAlertDialog(ServicePackDTO servicePackDTO) {
  88. return VAlertDialog(
  89. title: '${servicePackDTO.name}详情',
  90. content: Container(
  91. height: 200,
  92. alignment: Alignment.topLeft,
  93. padding: const EdgeInsets.symmetric(horizontal: 15),
  94. child: Column(
  95. crossAxisAlignment: CrossAxisAlignment.start,
  96. children: [
  97. LayoutBuilder(
  98. builder: (BuildContext context, BoxConstraints constraints) {
  99. return ConstrainedBox(
  100. constraints: const BoxConstraints(
  101. maxHeight: 100,
  102. ),
  103. child: Scrollbar(
  104. thumbVisibility: true,
  105. child: ListView(
  106. shrinkWrap: true,
  107. children: [
  108. Text(
  109. servicePackDTO.content ?? "",
  110. style: const TextStyle(fontSize: 16),
  111. ),
  112. ],
  113. ),
  114. ),
  115. );
  116. },
  117. ),
  118. const SizedBox(
  119. height: 5,
  120. ),
  121. const Text(
  122. '服务项目',
  123. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
  124. ),
  125. const SizedBox(
  126. height: 5,
  127. ),
  128. Expanded(
  129. child: Text(
  130. controller.getServiceItemsName(
  131. servicePackDTO.items ?? [],
  132. ),
  133. ),
  134. )
  135. ],
  136. )),
  137. // actions: <Widget>[
  138. // TextButton(
  139. // child: const Text('取消'),
  140. // onPressed: () {
  141. // Navigator.of(context).pop(); // 关闭对话框
  142. // },
  143. // ),
  144. // TextButton(
  145. // child: const Text('确定'),
  146. // onPressed: () {
  147. // // 在这里处理确定按钮的逻辑
  148. // Navigator.of(context).pop(); // 关闭对话框
  149. // },
  150. // ),
  151. // ],
  152. );
  153. }
  154. Widget buildItem(ServicePackDTO servicePackDTO) {
  155. return InkWell(
  156. onTap: () {
  157. controller.changeServicePackage(servicePackDTO);
  158. },
  159. child: Container(
  160. margin: const EdgeInsets.symmetric(
  161. vertical: 10,
  162. horizontal: 50,
  163. ),
  164. padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15),
  165. decoration: BoxDecoration(
  166. color: Colors.white,
  167. borderRadius: const BorderRadius.all(
  168. Radius.circular(
  169. 8,
  170. ),
  171. ),
  172. border: Border.all(
  173. color: controller.state.selectedServicePackage
  174. .contains(servicePackDTO)
  175. ? Theme.of(context).primaryColor
  176. : Colors.transparent,
  177. width: 2,
  178. ),
  179. ),
  180. child: Row(
  181. children: [
  182. Container(
  183. padding: const EdgeInsets.only(right: 20),
  184. child: Obx(
  185. () => Icon(
  186. Icons.check_circle_outline,
  187. size: 35,
  188. color: controller.state.selectedServicePackage
  189. .contains(servicePackDTO)
  190. ? Theme.of(context).primaryColor
  191. : Colors.grey.shade500,
  192. ),
  193. ),
  194. ),
  195. Expanded(
  196. child: Column(
  197. children: [
  198. Row(
  199. children: [
  200. Expanded(
  201. child: Text(
  202. servicePackDTO.name ?? '',
  203. style: const TextStyle(
  204. fontSize: 25,
  205. fontWeight: FontWeight.bold,
  206. ),
  207. ),
  208. ),
  209. Expanded(
  210. child: Row(
  211. children: [
  212. const Text(
  213. '服务人群:',
  214. style: TextStyle(fontSize: titleSize),
  215. ),
  216. Text(
  217. controller.setNormalLabels(
  218. servicePackDTO.labels ?? [],
  219. ),
  220. style: const TextStyle(fontSize: labelSize),
  221. ),
  222. ],
  223. ),
  224. ),
  225. ],
  226. ),
  227. const SizedBox(
  228. height: 10,
  229. ),
  230. Row(
  231. mainAxisSize: MainAxisSize.max,
  232. children: [
  233. Expanded(
  234. child: Row(
  235. mainAxisSize: MainAxisSize.max,
  236. children: [
  237. const Text(
  238. '服务包介绍:',
  239. style: TextStyle(fontSize: titleSize),
  240. ),
  241. Expanded(
  242. child: Container(
  243. alignment: Alignment.centerLeft,
  244. height: 50,
  245. child: Text(
  246. servicePackDTO.content ?? '',
  247. overflow: TextOverflow.ellipsis,
  248. maxLines: 2,
  249. style: const TextStyle(fontSize: labelSize),
  250. ),
  251. ),
  252. ),
  253. ],
  254. ),
  255. ),
  256. ],
  257. ),
  258. ],
  259. ),
  260. ),
  261. Container(
  262. padding: const EdgeInsets.only(left: 20),
  263. child: TextButton(
  264. child: const Text(
  265. '查看',
  266. style: TextStyle(fontSize: 18),
  267. ),
  268. onPressed: () async {
  269. // await Get.toNamed(
  270. // '/contract/package_info',
  271. // parameters: {
  272. // "servicePack": json.encode(servicePackDTO.toJson()),
  273. // },
  274. // );
  275. showDialog(
  276. context: context,
  277. builder: (BuildContext context) {
  278. return buildAlertDialog(servicePackDTO);
  279. },
  280. );
  281. },
  282. ),
  283. ),
  284. ],
  285. ),
  286. ),
  287. );
  288. }
  289. Widget buildCancelButton() {
  290. return TextButton(
  291. onPressed: () {
  292. Get.back();
  293. },
  294. child: const Text(
  295. '取消',
  296. style: TextStyle(fontSize: 25),
  297. ),
  298. );
  299. }
  300. Widget buildConfirmButton() {
  301. return TextButton(
  302. onPressed: () {},
  303. child: const Text(
  304. '确定',
  305. style: TextStyle(fontSize: 25),
  306. ),
  307. );
  308. }
  309. Widget buildHeader() {
  310. return Container(
  311. decoration: const BoxDecoration(
  312. color: Colors.white,
  313. ),
  314. height: 90,
  315. padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
  316. child: Row(
  317. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  318. children: [
  319. buildCancelButton(),
  320. buildConfirmButton(),
  321. ],
  322. ),
  323. );
  324. }
  325. Widget buildServicePackageList() {
  326. return Obx(
  327. () => Expanded(
  328. child: controller.state.servicePackageItems.isEmpty
  329. ? Container(
  330. margin: const EdgeInsets.only(top: 80),
  331. child: Column(
  332. children: [
  333. Center(
  334. child: Image.asset(
  335. "assets/images/no_data.png",
  336. width: 300,
  337. height: 300,
  338. fit: BoxFit.cover,
  339. ),
  340. ),
  341. const Text(
  342. "暂无数据,先看看别的吧",
  343. style: TextStyle(fontSize: 18),
  344. ),
  345. ],
  346. ),
  347. )
  348. : ListView(
  349. children: controller.state.servicePackageItems
  350. .map((ServicePackDTO e) => buildItem(e))
  351. .toList(),
  352. ),
  353. ),
  354. );
  355. }
  356. return Drawer(
  357. shape: const RoundedRectangleBorder(
  358. borderRadius: BorderRadiusDirectional.horizontal(
  359. end: Radius.circular(0),
  360. ),
  361. ),
  362. width: MediaQuery.of(context).size.width * 0.7,
  363. child: Container(
  364. color: Colors.grey.shade300,
  365. child: Column(
  366. mainAxisSize: MainAxisSize.max,
  367. children: [
  368. // buildHeader(),
  369. const SizedBox(
  370. height: 30,
  371. ),
  372. buildServicePackageList(),
  373. const SizedBox(
  374. height: 10,
  375. ),
  376. ],
  377. ),
  378. ),
  379. );
  380. }
  381. Future<String> convertImageToBase64(XFile image) async {
  382. List<int> imageBytes = await image.readAsBytes();
  383. String base64Image = base64Encode(imageBytes);
  384. return base64Image;
  385. }
  386. Widget _buildPhotoVPanel() {
  387. return VListFormCellGroup(
  388. children: [
  389. Obx(
  390. () => VListFormCell(
  391. label: '拍照',
  392. height: 70,
  393. contentWidget: controller.state.userImage != null
  394. ? Image.file(
  395. File(controller.state.userImage!.path),
  396. )
  397. : const Center(
  398. child: Icon(
  399. Icons.account_box_rounded,
  400. size: 70,
  401. ),
  402. ),
  403. onTap: () async {
  404. XFile? image =
  405. await ImagePicker().pickImage(source: ImageSource.camera);
  406. controller.state.userImage = image;
  407. String base64Image = await convertImageToBase64(image!);
  408. controller.base64Image = base64Image;
  409. },
  410. ),
  411. )
  412. ],
  413. );
  414. }
  415. Widget _buildContractDoctorVPanel() {
  416. return VListFormCellGroup(
  417. children: [
  418. Obx(
  419. () => VListFormCell(
  420. label: '服务日期',
  421. content: DateFormat('yyyy-MM-dd')
  422. .format(controller.state.serviceStartDate),
  423. onTap: () async {
  424. final result = await VDialogDate(
  425. title: '服务日期',
  426. initialValue: controller.state.serviceStartDate,
  427. ).show();
  428. controller.state.serviceStartDate = result;
  429. },
  430. ),
  431. ),
  432. Obx(
  433. () => VListFormCell(
  434. label: '服务年限',
  435. content: '${controller.state.serviceTime}年',
  436. onTap: () async {
  437. String? result = await VDialogSelect<VSelectModel, String>(
  438. source: [
  439. VSelectModel(code: "1", name: "1年"),
  440. VSelectModel(code: "2", name: "2年"),
  441. VSelectModel(code: "3", name: "3年"),
  442. ],
  443. labelGetter: (data) => data.name,
  444. valueGetter: (data) => data.code,
  445. ).show();
  446. controller.state.serviceTime = int.parse(result ?? '1');
  447. },
  448. ),
  449. ),
  450. VListFormCell(
  451. label: '签约医生',
  452. content: Store.user.principalName,
  453. ),
  454. VListFormCell(
  455. label: '医生电话',
  456. content: Store.user.principalPhone,
  457. ),
  458. ],
  459. );
  460. }
  461. Widget _buildPatientVPanel() {
  462. return Stack(
  463. children: [
  464. Obx(
  465. () => VListFormCellGroup(
  466. children: [
  467. VListFormCell(
  468. label: '姓名',
  469. content: controller.state.name,
  470. ),
  471. VListFormCell(
  472. label: '联系电话',
  473. content: controller.state.phone,
  474. ),
  475. if (controller.state.isExpendPatient) ...[
  476. VListFormCell(
  477. label: '身份证号码',
  478. content: controller.state.cardNo,
  479. ),
  480. const VListFormCell(
  481. label: '民族',
  482. content: '汉族',
  483. ),
  484. VListFormCell(
  485. label: '性别',
  486. content: controller.state.genderDesc,
  487. ),
  488. VListFormCell(
  489. label: '出生日期',
  490. content: DateFormat('yyyy-MM-dd').format(
  491. controller.state.birthday,
  492. ),
  493. ),
  494. ]
  495. ],
  496. ),
  497. ),
  498. Positioned(
  499. right: 0,
  500. top: 0,
  501. child: IconButton(
  502. onPressed: () {
  503. controller.state.isExpendPatient =
  504. !controller.state.isExpendPatient;
  505. },
  506. icon: Obx(
  507. () => Icon(
  508. controller.state.isExpendPatient
  509. ? Icons.keyboard_arrow_up_rounded
  510. : Icons.keyboard_arrow_down_rounded,
  511. color: Colors.grey.shade400,
  512. size: 30,
  513. ),
  514. ),
  515. ),
  516. )
  517. ],
  518. );
  519. }
  520. Widget _buildServicePackageVPanel(BuildContext context) {
  521. return VPanel(
  522. child: VListFormCellGroup(
  523. children: [
  524. Obx(
  525. () => VListFormCell(
  526. label: '家庭医生服务包',
  527. height: 70,
  528. content: controller.state.selectedServicePackageName,
  529. onTap: () {
  530. Scaffold.of(context).openEndDrawer();
  531. },
  532. ),
  533. ),
  534. Obx(
  535. () => VListFormCell(
  536. label: '备注',
  537. content: controller.state.notes,
  538. onTap: () async {
  539. String? result = await VDialogInput(
  540. title: '备注',
  541. initialValue: controller.state.notes,
  542. ).show();
  543. controller.state.notes = result ?? controller.state.notes;
  544. },
  545. ),
  546. ),
  547. ],
  548. ),
  549. );
  550. }
  551. }