online_consultation.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. import 'package:cached_network_image/cached_network_image.dart';
  2. import 'package:dio/dio.dart';
  3. import 'package:eitc_erm_app/chat/chat_home.dart';
  4. import 'package:eitc_erm_app/utils/Component.dart';
  5. import 'package:eitc_erm_app/utils/Constants.dart';
  6. import 'package:eitc_erm_app/utils/logger.dart';
  7. import 'package:eitc_erm_app/widget/circular_loading.dart';
  8. import 'package:eitc_erm_app/widget/image_error.dart';
  9. import 'package:file_picker/file_picker.dart';
  10. import 'package:flutter/material.dart';
  11. import 'package:http/http.dart' as http;
  12. import 'package:permission_handler/permission_handler.dart';
  13. import 'bean/normal_response.dart';
  14. import 'bean/normal_response2.dart';
  15. void main() {
  16. WidgetsFlutterBinding.ensureInitialized();
  17. runApp(OnlineConsultation());
  18. }
  19. class OnlineConsultation extends StatefulWidget {
  20. @override
  21. State<StatefulWidget> createState() => OnlineConsultationState();
  22. }
  23. class OnlineConsultationState extends State<OnlineConsultation> {
  24. List<String> uploadImg = ["assets/images/upload_photo.png"];
  25. TextEditingController resourceController = TextEditingController();
  26. String age = "请选择年龄";
  27. String sex = "请选择性别";
  28. List<int> ageList = List.generate(101, (index) => index);
  29. @override
  30. void initState() {
  31. super.initState();
  32. // initPlatformState();
  33. }
  34. Future<void> initPlatformState() async {
  35. //相机权限
  36. if (await requestCameraPermission() == false) {
  37. return;
  38. }
  39. //录音权限
  40. if (await requestMicrophonePermission() == false) {
  41. return;
  42. }
  43. }
  44. Future<bool> requestCameraPermission() async {
  45. var status = await Permission.camera.status;
  46. if (status == PermissionStatus.granted) {
  47. return true;
  48. } else {
  49. status = await Permission.camera.request();
  50. if (status == PermissionStatus.granted) {
  51. return true;
  52. } else {
  53. return false;
  54. }
  55. }
  56. }
  57. Future<bool> requestMicrophonePermission() async {
  58. var status = await Permission.microphone.status;
  59. if (status == PermissionStatus.granted) {
  60. return true;
  61. } else {
  62. status = await Permission.microphone.request();
  63. if (status == PermissionStatus.granted) {
  64. return true;
  65. } else {
  66. return false;
  67. }
  68. }
  69. }
  70. @override
  71. Widget build(BuildContext context) {
  72. return Scaffold(
  73. resizeToAvoidBottomInset: false,
  74. backgroundColor: Global.BackgroundColor,
  75. appBar: new AppBar(
  76. title: new Text('在线问诊',
  77. style: TextStyle(
  78. color: Colors.white,
  79. )),
  80. centerTitle: true,
  81. elevation: 0.5,
  82. backgroundColor: Global.StatusBarColor,
  83. leading: new IconButton(
  84. tooltip: '返回上一页',
  85. icon: const Icon(
  86. Icons.arrow_back_ios,
  87. color: Colors.white,
  88. ),
  89. onPressed: () {
  90. Navigator.of(context).pop();
  91. //_nextPage(-1);
  92. },
  93. ),
  94. ),
  95. body: SingleChildScrollView(
  96. child: Container(
  97. padding: EdgeInsets.all(10),
  98. child: Column(
  99. mainAxisSize: MainAxisSize.min,
  100. crossAxisAlignment: CrossAxisAlignment.start,
  101. children: <Widget>[
  102. Padding(
  103. padding: EdgeInsets.all(10),
  104. child: Row(
  105. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  106. children: [
  107. Text(
  108. '问诊资料',
  109. style: TextStyle(
  110. fontSize: 20, color: Colors.black),
  111. ),
  112. ]),
  113. ),
  114. ClipRRect(
  115. borderRadius: BorderRadius.circular(10.0),
  116. child: Container(
  117. color: Colors.white,
  118. padding: const EdgeInsets.all(10),
  119. child: Column(
  120. mainAxisSize: MainAxisSize.min,
  121. crossAxisAlignment: CrossAxisAlignment.start,
  122. children: <Widget>[
  123. /*Padding(
  124. padding: EdgeInsets.all(10),
  125. child: Row(
  126. mainAxisAlignment:
  127. MainAxisAlignment.spaceBetween,
  128. children: [
  129. Text(
  130. '挂号费',
  131. style: TextStyle(
  132. fontSize: 15, color: Colors.grey),
  133. ),
  134. Text(
  135. '¥12.5',
  136. style: TextStyle(fontSize: 15),
  137. ),
  138. ]),
  139. ),*/
  140. Padding(
  141. padding: EdgeInsets.all(10),
  142. child: Row(
  143. mainAxisAlignment:
  144. MainAxisAlignment.spaceBetween,
  145. children: [
  146. Text(
  147. '就诊人',
  148. style: TextStyle(
  149. fontSize: 15, color: Colors.grey),
  150. ),
  151. Text(
  152. Global
  153. .patient
  154. .data![Global.selectPatient]
  155. .patientName
  156. .toString(),
  157. style: TextStyle(fontSize: 15),
  158. ),
  159. ]),
  160. ),
  161. Padding(
  162. padding: EdgeInsets.all(10),
  163. child: Row(
  164. mainAxisAlignment:
  165. MainAxisAlignment.spaceBetween,
  166. children: [
  167. Text(
  168. '年龄',
  169. style: TextStyle(
  170. fontSize: 15, color: Colors.grey),
  171. ),
  172. GestureDetector(
  173. onTap: () {
  174. selectItem(ageList, 0);
  175. },
  176. child: Row(children: <Widget>[
  177. Text(
  178. age,
  179. style: TextStyle(
  180. color: Colors.black,
  181. ),
  182. ),
  183. Icon(
  184. Icons.keyboard_arrow_down,
  185. color: Colors.black,
  186. ),
  187. ])),
  188. ]),
  189. ),
  190. Padding(
  191. padding: EdgeInsets.all(10),
  192. child: GestureDetector(
  193. onTap: () {
  194. selectItem(['男', '女'], 1);
  195. },
  196. child: Row(
  197. mainAxisAlignment:
  198. MainAxisAlignment.spaceBetween,
  199. children: [
  200. Text(
  201. '性别',
  202. style: TextStyle(
  203. fontSize: 15,
  204. color: Colors.grey),
  205. ),
  206. Row(children: <Widget>[
  207. Text(
  208. sex,
  209. style: TextStyle(
  210. color: Colors.black,
  211. ),
  212. ),
  213. Icon(
  214. Icons.keyboard_arrow_down,
  215. color: Colors.black,
  216. ),
  217. ])
  218. ]),
  219. ),
  220. ),
  221. Padding(
  222. padding: EdgeInsets.all(10),
  223. child: Row(
  224. mainAxisAlignment:
  225. MainAxisAlignment.spaceBetween,
  226. children: [
  227. Text(
  228. '咨询医生',
  229. style: TextStyle(
  230. fontSize: 15, color: Colors.grey),
  231. ),
  232. Text(
  233. Global
  234. .doctor
  235. .data![Global.selectDoctor]
  236. .nickName
  237. .toString(),
  238. style: TextStyle(fontSize: 15),
  239. ),
  240. ]),
  241. ),
  242. ]),
  243. ),
  244. ),
  245. SizedBox(height: 8),
  246. Padding(
  247. padding: EdgeInsets.all(10),
  248. child: Row(
  249. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  250. children: [
  251. Text(
  252. '病情资料',
  253. style: TextStyle(
  254. fontSize: 20, color: Colors.black),
  255. ),
  256. ]),
  257. ),
  258. ClipRRect(
  259. borderRadius: BorderRadius.circular(10.0),
  260. child: Container(
  261. color: Colors.white,
  262. padding: const EdgeInsets.all(10),
  263. child: Column(
  264. mainAxisSize: MainAxisSize.min,
  265. crossAxisAlignment: CrossAxisAlignment.start,
  266. children: <Widget>[
  267. Padding(
  268. padding: EdgeInsets.all(10),
  269. child: Row(
  270. mainAxisAlignment:
  271. MainAxisAlignment.spaceBetween,
  272. children: [
  273. Expanded(
  274. child: TextField(
  275. maxLines: 5,
  276. enabled: true,
  277. controller: resourceController,
  278. decoration: InputDecoration(
  279. border: OutlineInputBorder(),
  280. hintText:
  281. '请简单描述病情,录入:主要症状、发病时间、治疗过程、病情变化、想得到什么样的帮助等',
  282. ),
  283. ),
  284. ),
  285. ]),
  286. ),
  287. ]),
  288. ),
  289. ),
  290. Padding(
  291. padding: EdgeInsets.all(5),
  292. child: Column(
  293. crossAxisAlignment: CrossAxisAlignment.start,
  294. children: [
  295. Text(
  296. '上传患处、病历、检查单等照片',
  297. style: TextStyle(fontSize: 10),
  298. ),
  299. SizedBox(height: 3),
  300. GridView.builder(
  301. padding: const EdgeInsets.all(10),
  302. shrinkWrap: true,
  303. physics: NeverScrollableScrollPhysics(),
  304. gridDelegate:
  305. const SliverGridDelegateWithFixedCrossAxisCount(
  306. //注意此行
  307. crossAxisCount: 3, //每行 widget 数量
  308. crossAxisSpacing: 10, //widget 水平之间的距离
  309. mainAxisSpacing: 10, //widget 垂直之间的距离
  310. ),
  311. itemCount: uploadImg.length,
  312. // itemCount: 2,
  313. itemBuilder: _customWidget,
  314. ),
  315. /*GestureDetector(
  316. onTap: () {
  317. uploadFile();
  318. },
  319. child:
  320. Image(
  321. image: AssetImage('assets/images/upload_photo.png'),
  322. ),),*/
  323. ]),
  324. ),
  325. ElevatedButton(
  326. child: Text("下一步",
  327. style:
  328. TextStyle(color: Colors.white, fontSize: 15)),
  329. style: ButtonStyle(
  330. backgroundColor:
  331. MaterialStateProperty.resolveWith<Color>(
  332. (states) {
  333. return Colors.blue; // Regular color
  334. }),
  335. fixedSize: MaterialStateProperty.all<Size>(
  336. Size(MediaQuery.of(context).size.width - 30,
  337. 30), // 设置宽度和高度
  338. ),
  339. ),
  340. onPressed: () {
  341. editChatDisease();
  342. },
  343. ),
  344. ]))));
  345. }
  346. Widget _customWidget(BuildContext context, int index) {
  347. return GestureDetector(
  348. onTap: () {
  349. if (index == 0) {
  350. uploadFile();
  351. }
  352. },
  353. child: index == 0
  354. ? const Image(
  355. image: AssetImage(
  356. 'assets/images/upload_photo.png',
  357. ),
  358. width: 80,
  359. )
  360. : CachedNetworkImage(
  361. width: 80,
  362. height: 80,
  363. fit: BoxFit.cover,
  364. imageUrl: uploadImg[index],
  365. progressIndicatorBuilder: (ctx, _, progress) => Circularloading(
  366. value: progress.progress,
  367. ),
  368. errorWidget: (ctx, _, __) => const ImageError(
  369. size: 80,
  370. ),
  371. ),
  372. );
  373. }
  374. void uploadFile() async {
  375. FilePickerResult? result = await FilePicker.platform
  376. .pickFiles(allowedExtensions: ["jpg"], type: FileType.custom);
  377. if (result != null) {
  378. if (result.files.single.extension != "jpg") {
  379. Component.toast("请选择jpg格式的图片", 2);
  380. return;
  381. }
  382. String fileName = result.files.single.name;
  383. String? filePath = result.files.single.path;
  384. FormData formData = FormData.fromMap({
  385. "file": await MultipartFile.fromFile(filePath!, filename: fileName),
  386. "path": "chat",
  387. });
  388. try {
  389. Component.toast("正在上传,请稍后", 2);
  390. Map<String, String> headers = {
  391. 'token': Global.token,
  392. };
  393. Response response = await Dio().post(
  394. '${Global.BaseUrl}common/minioUploadImage',
  395. data: formData,
  396. options: Options(headers: headers));
  397. if (response.statusCode == 200) {
  398. var json = decodeBodyToJson(response.data);
  399. logd("上传图片结果=$json");
  400. Normal2Response mNormal2Response = new Normal2Response.fromJson(json);
  401. if (mNormal2Response.code == Global.responseSuccessCode) {
  402. // Component.toast("上传成功!", 2);
  403. uploadImg.add(mNormal2Response.msg.toString());
  404. setState(() {});
  405. } else {
  406. Component.toast(mNormal2Response.msg.toString(), 0);
  407. }
  408. } else {
  409. Component.toast("出错了,请稍后再试!", 0);
  410. return null;
  411. }
  412. } catch (e) {
  413. logd(e);
  414. }
  415. }
  416. }
  417. Future<void> editChatDisease() async {
  418. if (age == "请选择年龄") {
  419. Component.toast("请选择患者年龄!", 0);
  420. return;
  421. }
  422. if (sex == "请选择性别") {
  423. Component.toast("请选择患者性别!", 0);
  424. return;
  425. }
  426. if (resourceController.text.isEmpty) {
  427. Component.toast("请填写病情资料!", 0);
  428. return;
  429. }
  430. if (uploadImg.length < 1) {
  431. Component.toast("请上传病历图片!", 0);
  432. return;
  433. }
  434. String uploadImgStr = uploadImg
  435. .sublist(1, uploadImg.length)
  436. .toString()
  437. .replaceAll("[", "")
  438. .replaceAll("]", "");
  439. logd(uploadImgStr);
  440. int ageInt = int.parse(age);
  441. int sexInt = 0;
  442. if (sex == "女") sexInt = 1;
  443. var params = {
  444. 'name': Global.patient.data![Global.selectPatient].patientName.toString(),
  445. 'age': ageInt,
  446. 'sex': sexInt,
  447. 'docker': Global.doctor.data![Global.selectDoctor].nickName.toString(),
  448. 'resource': resourceController.text,
  449. 'patientId': Global.patient.data![Global.selectPatient].patientId,
  450. 'url': uploadImgStr,
  451. };
  452. logd(params);
  453. var response = await http.put(
  454. Uri.parse('${Global.BaseUrl}chat/editChatDisease'),
  455. body: encodeBody(params),
  456. headers: jsonHeaders(withToken: true));
  457. if (response.statusCode == 200) {
  458. final json = decodeBodyToJson(response.bodyBytes);
  459. logd("在线问诊提交结果=$json");
  460. NormalResponse mNormalResponse = NormalResponse.fromJson(json);
  461. if (mNormalResponse.code == Global.responseSuccessCode) {
  462. Navigator.pushReplacement(
  463. context,
  464. MaterialPageRoute(
  465. builder: (context) => ChatHome(
  466. doctorId: Global.doctor.data![Global.selectDoctor].userId
  467. .toString(),
  468. doctorName:
  469. Global.doctor.data![Global.selectDoctor].nickName,
  470. )),
  471. );
  472. /*Navigator.pushAndRemoveUntil(
  473. context,
  474. MaterialPageRoute(builder: (context) => ChatHome()), (route) => false,
  475. );*/
  476. } else {
  477. Component.toast(mNormalResponse.msg.toString(), 0);
  478. }
  479. } else {
  480. Component.toast("出错了,请稍后再试!", 0);
  481. return null;
  482. }
  483. }
  484. selectItem(List list, int item) {
  485. showModalBottomSheet(
  486. context: context,
  487. builder: (BuildContext context) {
  488. return SingleChildScrollView(
  489. child: Container(
  490. width: double.infinity,
  491. height: list.length * 50,
  492. child: Column(
  493. children: List.generate(
  494. list.length,
  495. (index) => GestureDetector(
  496. onTap: () {
  497. setState(() {
  498. switch (item) {
  499. case 0:
  500. age = list[index].toString();
  501. break;
  502. case 1:
  503. sex = list[index];
  504. break;
  505. }
  506. });
  507. Navigator.pop(context);
  508. },
  509. child: Padding(
  510. padding: const EdgeInsets.all(10),
  511. child: Text(
  512. ' ${list[index]} ',
  513. style: TextStyle(fontSize: 18),
  514. ),
  515. ),
  516. ),
  517. ),
  518. ),
  519. ),
  520. );
  521. },
  522. );
  523. }
  524. }