measure_view.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. import 'package:fis_i18n/i18n.dart';
  2. import 'package:fis_jsonrpc/rpc.dart';
  3. import 'package:fis_measure/interfaces/enums/annotation.dart';
  4. import 'package:fis_measure/interfaces/process/player/play_controller.dart';
  5. import 'package:fis_measure/interfaces/process/workspace/application.dart';
  6. import 'package:fis_measure/interfaces/process/workspace/exam_info.dart';
  7. import 'package:fis_measure/interfaces/process/workspace/measure_3d_view_controller.dart';
  8. import 'package:fis_measure/process/language/measure_language.dart';
  9. import 'package:fis_measure/process/layout/configuration.dart';
  10. import 'package:fis_measure/process/workspace/measure_controller.dart';
  11. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  12. import 'package:fis_measure/process/workspace/measure_handler.dart';
  13. import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart';
  14. import 'package:fis_measure/view/cursor.dart';
  15. import 'package:fis_measure/view/gesture/positioned_cursor.dart';
  16. import 'package:fis_measure/view/measure/measure_images_bar.dart';
  17. import 'package:fis_measure/view/measure/measure_left_annotation.dart';
  18. import 'package:fis_measure/view/measure/measure_player.dart';
  19. import 'package:fis_measure/view/measure/measure_search_input.dart';
  20. import 'package:fis_measure/view/measure/measure_tool.dart';
  21. import 'package:fis_measure/view/measure/carotid_measure_tool.dart';
  22. import 'package:fis_measure/view/measure/measure_tools_title.dart';
  23. import 'package:fis_measure/view/measure/measure_view_controller.dart';
  24. import 'package:fis_measure/view/measure/tool_chest_title.dart';
  25. import 'package:fis_measure/view/player/control_board/operate_bar.dart';
  26. import 'package:fis_ui/index.dart';
  27. import 'package:flutter/material.dart';
  28. import 'package:get/get.dart';
  29. import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart';
  30. /// 测量主页面
  31. class MeasureMainPage extends StatefulWidget implements FWidget {
  32. const MeasureMainPage(
  33. this.token, this.recordCode, this.patientCode, this.remedicalCode,
  34. {this.needRouterBack, Key? key})
  35. : super(key: key);
  36. final String token;
  37. final String patientCode;
  38. final String remedicalCode;
  39. final String recordCode;
  40. final bool? needRouterBack; // 需要返回按钮【一版用于返回到报告编辑】
  41. @override
  42. State<StatefulWidget> createState() => _MeasureMainPageState();
  43. }
  44. class _MeasureMainPageState extends State<MeasureMainPage> {
  45. /// 数据
  46. final measureData = Get.find<MeasureDataController>();
  47. /// webview 控制器
  48. final measure3DViewController = Get.find<Measure3DViewController>();
  49. final mouseState = Get.put<IMouseState>(MouseState());
  50. /// 测量项控制器
  51. final measureMetaController = Get.put(MeasureMetaController());
  52. /// 页面loadding
  53. bool loaded = false;
  54. // /// 图片loadding
  55. late bool imageLoaded = false;
  56. ///检查图片信息表
  57. List<ExamImageInfo> examImageInfoList = [];
  58. late final measureHandler = Get.find<MeasureHandler>();
  59. /// 测量控制器
  60. late MeasureController measureController = Get.put(MeasureController(
  61. "",
  62. imagesFetchFunc: (code) async {
  63. return examImageInfoList;
  64. },
  65. ));
  66. /// 获取测量图片所需的图片组 并且写入控制器中 加载
  67. void getExamImageInfoList(List<RemedicalInfoDTO> remedicals) async {
  68. for (var element in remedicals) {
  69. examImageInfoList.add(
  70. ExamImageInfo(
  71. element.terminalImages!.imageUrl!,
  72. element.terminalImages!.previewUrl!,
  73. ),
  74. );
  75. }
  76. measureController = Get.put(MeasureController(
  77. "",
  78. imagesFetchFunc: (code) async {
  79. return examImageInfoList;
  80. },
  81. ));
  82. await measureController.load();
  83. int selectedImageIndex = -1;
  84. if (examImageInfoList
  85. .any((element) => element.url == measureData.itemCurrentImage)) {
  86. ExamImageInfo selectedImage = examImageInfoList.firstWhere(
  87. (element) => element.url == measureData.itemCurrentImage,
  88. );
  89. selectedImageIndex = examImageInfoList.indexOf(selectedImage);
  90. measureController.examInfo.selectedImageIndex = selectedImageIndex;
  91. }
  92. MeasureLanguage.load(measureData.measureLanguage);
  93. }
  94. void changeImage(sender, e) {
  95. imageLoaded = e;
  96. setState(() {});
  97. }
  98. void changeFullScreenState(sender, e) {
  99. setState(() {});
  100. }
  101. @override
  102. void initState() {
  103. loadLayoutConfig();
  104. getImageInfo();
  105. measureHandler.onChangeImageLoaded.addListener(changeImage);
  106. measureHandler.onChangeFullScreenState.addListener(changeFullScreenState);
  107. super.initState();
  108. }
  109. void getImageInfo() {
  110. measureData.measureImageData = MeasureImageData(
  111. patientCode: widget.patientCode,
  112. recordCode: widget.recordCode,
  113. remedicalCode: widget.remedicalCode,
  114. );
  115. }
  116. /// 加载图像布局配置
  117. void loadLayoutConfig() async {
  118. await LayoutConfiguration.ins.loadData();
  119. setState(() {
  120. // 加载图像数据
  121. _initData();
  122. });
  123. }
  124. void onImageLoaded(Object sender, ExamImageInfo? e) async {
  125. // measureHandler.changeImageLoaded = true;
  126. if (!mounted) return;
  127. final currentImage = measureData.remedicalList.firstWhereOrNull(
  128. (element) => element.terminalImages!.imageUrl == e!.url,
  129. );
  130. if (currentImage != null) {
  131. /// 获取图片详细信息
  132. var remedicalInfo = await measureData.getImageInfo(
  133. currentImage.remedicalCode ?? '',
  134. widget.token,
  135. );
  136. if (remedicalInfo != null) {
  137. measureData.aiResults = remedicalInfo.diagnosisResult ?? '';
  138. measure3DViewController.initParams();
  139. /// ai 良恶性 判断是否有ai
  140. measureData.diagnosisConclusion = remedicalInfo.diagnosisConclusion;
  141. if (remedicalInfo.carotidResult != null) {
  142. /// [Carotid] ✅详情传入测量信息
  143. measure3DViewController.carotidResult = remedicalInfo.carotidResult!;
  144. /// [Carotid] ✅需要在此通知 controller 存在颈动脉信息
  145. measure3DViewController.exist3DData = true;
  146. measure3DViewController
  147. .handleChangeCarotid2DImage(remedicalInfo.recordCode!);
  148. } else {
  149. measure3DViewController.exist3DData = false;
  150. }
  151. // [Carotid] ✅只要更换图片都要切换到Vid 2D模式
  152. if (measure3DViewController.curMeasureMode != MeasureMode.vidMode) {
  153. measure3DViewController.backToVidMode();
  154. }
  155. try {
  156. if (e != null) {
  157. Future.delayed(const Duration(milliseconds: 100), () {
  158. measureController.playerController.play();
  159. });
  160. setState(() {
  161. loaded = true;
  162. measureHandler.changeImageLoaded = false;
  163. });
  164. }
  165. } catch (error) {
  166. setState(() {
  167. loaded = true;
  168. measureHandler.changeImageLoaded = false;
  169. });
  170. }
  171. }
  172. }
  173. }
  174. @override
  175. void dispose() {
  176. measureHandler.onChangeImageLoaded.removeListener(changeImage);
  177. measureHandler.onChangeFullScreenState
  178. .removeListener(changeFullScreenState);
  179. super.dispose();
  180. }
  181. @override
  182. FWidget build(BuildContext context) {
  183. FWidget body;
  184. if (!loaded) {
  185. const loadingWidget = FCenter(child: FCircularProgressIndicator());
  186. body = FRow(
  187. children: const [
  188. FExpanded(
  189. child: loadingWidget,
  190. ),
  191. ],
  192. );
  193. } else {
  194. body = FRow(
  195. children: [
  196. if (!measureHandler.fullScreenState)
  197. _MeasureLeftBoard(
  198. needRouterBack: widget.needRouterBack,
  199. ),
  200. const FVerticalDivider(),
  201. FExpanded(
  202. child: FColumn(
  203. mainAxisSize: MainAxisSize.max,
  204. children: [
  205. FExpanded(
  206. child: FStack(
  207. children: [
  208. if (!imageLoaded)
  209. MeasureRightBoard(
  210. key: ValueKey(measureData.itemCurrentImage),
  211. ),
  212. if (imageLoaded)
  213. const FCenter(
  214. child: FCircularProgressIndicator(),
  215. ),
  216. ],
  217. )),
  218. if (!measureHandler.fullScreenState) const MeasureImagesBar(),
  219. ],
  220. ),
  221. ),
  222. ],
  223. );
  224. }
  225. return FCenter(
  226. child: FContainer(
  227. color: const Color.fromRGBO(70, 70, 70, 1),
  228. child: body,
  229. ),
  230. );
  231. }
  232. MeasureCursorType _getMeasureSystemSettingCursorType(
  233. CursorTypeEnum cursorType,
  234. ) {
  235. switch (cursorType) {
  236. case CursorTypeEnum.CursorType1Icon:
  237. return MeasureCursorType.cursor01;
  238. case CursorTypeEnum.CursorType2Icon:
  239. return MeasureCursorType.cursor02;
  240. case CursorTypeEnum.CursorType3Icon:
  241. return MeasureCursorType.cursor03;
  242. case CursorTypeEnum.CursorType4Icon:
  243. return MeasureCursorType.cursor04;
  244. case CursorTypeEnum.CursorType5Icon:
  245. return MeasureCursorType.cursor05;
  246. default:
  247. return MeasureCursorType.cursor01;
  248. }
  249. }
  250. /// 初始化卡尺样式部分
  251. Future<void> _getMeasureSystemSetting() async {
  252. final result = await measureData.getMeasureSystemSettingAsync();
  253. measureData.measureSystemSetting = result as MeasureSystemSettingDTO;
  254. mouseState.cursorType = MeasureCursorType.cursor01;
  255. mouseState.cursorSize = 16.0;
  256. }
  257. void _initData() async {
  258. List<RemedicalInfoDTO> remedicals = [];
  259. List<Ultra3DResourceInfo> ultra3DResourceInfos = [];
  260. loaded = false;
  261. var value = await measureData.getRemedicalList.call(
  262. widget.patientCode,
  263. widget.recordCode,
  264. widget.token,
  265. );
  266. for (var remedicalItemList in value) {
  267. remedicals.addAll(remedicalItemList.remedicalList ?? []);
  268. }
  269. /// 获取样式
  270. _getMeasureSystemSetting();
  271. /// [Carotid] ✅遍历出颈动脉信息列表,传给壳子
  272. for (var remedical in remedicals) {
  273. if (remedical.carotidResult != null) {
  274. ultra3DResourceInfos.add(Ultra3DResourceInfo(remedical.carotidResult!));
  275. }
  276. }
  277. measure3DViewController.ultra3DResourceInfoList = ultra3DResourceInfos;
  278. measure3DViewController.recordId = widget.recordCode;
  279. measure3DViewController.notifyShellLoadAllModel();
  280. measureData.remedicalList = remedicals;
  281. var remedicalInfo =
  282. await measureData.getImageInfo.call(widget.remedicalCode, widget.token);
  283. if (remedicalInfo != null) {
  284. measureData.aiResults = remedicalInfo.diagnosisResult ?? '';
  285. if (remedicalInfo.terminalImages != null) {
  286. loaded = true;
  287. measureData.itemCurrentImage =
  288. remedicalInfo.terminalImages!.imageUrl ?? '';
  289. getExamImageInfoList(remedicals);
  290. }
  291. }
  292. measureController.imageLoaded.removeListener(onImageLoaded);
  293. measureController.imageLoaded.addListener(onImageLoaded);
  294. }
  295. }
  296. /// 测量左边操作页面
  297. class _MeasureLeftBoard extends StatefulWidget implements FWidget {
  298. const _MeasureLeftBoard({Key? key, this.needRouterBack}) : super(key: key);
  299. final bool? needRouterBack;
  300. @override
  301. State<_MeasureLeftBoard> createState() => _MeasureLeftBoardState();
  302. }
  303. class _MeasureLeftBoardState extends State<_MeasureLeftBoard> {
  304. final measureHandler = Get.find<MeasureHandler>();
  305. final playerController = Get.find<IPlayerController>();
  306. final measure3DViewController = Get.find<Measure3DViewController>();
  307. /// 数据
  308. late final measureData = Get.find<MeasureDataController>();
  309. /// 测量项控制器
  310. final measureMetaController = Get.find<MeasureMetaController>();
  311. /// 是否显示颈动脉2D指定的测量项
  312. bool showCarotid2DSelectMeasure = false;
  313. /// 是否显示测量项
  314. bool showMeasureItems = true;
  315. bool isMeasureTool = true;
  316. bool get isArrowMeasureAnnotationType =>
  317. measureHandler.changedAnnotationType == AnnotationType.arrow;
  318. void _onChangedTab(
  319. Object sender,
  320. TabEnum? e,
  321. ) {
  322. setState(() {});
  323. }
  324. void _onCurItemMetaListChanged(sender, e) {
  325. if (mounted) {
  326. setState(() {});
  327. }
  328. }
  329. ///图像发生变化
  330. void changeImage(sender, e) {
  331. setState(() {});
  332. }
  333. @override
  334. void initState() {
  335. measureHandler.onChangedTab.addListener(_onChangedTab);
  336. measure3DViewController.updatePlayerMode.addListener(_onModeChanged);
  337. measureData.curItemMetaListChanged.addListener(_onCurItemMetaListChanged);
  338. measureHandler.onChangeImageLoaded.addListener(changeImage);
  339. super.initState();
  340. }
  341. @override
  342. void dispose() {
  343. measureHandler.onChangedTab.removeListener(_onChangedTab);
  344. measure3DViewController.updatePlayerMode.removeListener(_onModeChanged);
  345. measureData.curItemMetaListChanged
  346. .removeListener(_onCurItemMetaListChanged);
  347. measureHandler.onChangeImageLoaded.removeListener(changeImage);
  348. super.dispose();
  349. }
  350. /// 模式改变触发更新
  351. void _onModeChanged(Object s, MeasureMode mode) {
  352. switch (mode) {
  353. case MeasureMode.vidMode:
  354. final playerController = Get.find<IPlayerController>();
  355. // 通过帧加载完成事件通知,去更新测量项
  356. if (playerController.currentFrame != null) {
  357. playerController.firstFrameLoaded
  358. .emit(this, playerController.currentFrame!);
  359. }
  360. setState(() {
  361. showCarotid2DSelectMeasure = false;
  362. showMeasureItems = true;
  363. });
  364. break;
  365. case MeasureMode.carotid2DMode:
  366. setState(() {
  367. showCarotid2DSelectMeasure = true;
  368. showMeasureItems = true;
  369. });
  370. break;
  371. case MeasureMode.carotid3DMode:
  372. setState(() {
  373. showCarotid2DSelectMeasure = true;
  374. showMeasureItems = false;
  375. });
  376. break;
  377. }
  378. }
  379. @override
  380. FWidget build(BuildContext context) {
  381. final newPlayerController = Get.find<IPlayerController>();
  382. return FColumn(
  383. mainAxisSize: MainAxisSize.max,
  384. crossAxisAlignment: CrossAxisAlignment.start,
  385. children: [
  386. if (showMeasureItems) ...[
  387. LeftSiderHold(ifHideConfig: showCarotid2DSelectMeasure),
  388. if (!(measureHandler.changedTab == TabEnum.tabMeasureTool)) ...[
  389. FContainer(
  390. width: 300,
  391. key: UniqueKey(),
  392. child: LeftSelectInput(),
  393. ),
  394. FExpanded(
  395. child: FContainer(
  396. width: 300,
  397. key: UniqueKey(),
  398. child: const MeasureLeftAnnotation(),
  399. ),
  400. ),
  401. _MeasureArrow(),
  402. ] else ...[
  403. const LeftMeasureTools(),
  404. FExpanded(
  405. child: FContainer(
  406. width: 300,
  407. child: showCarotid2DSelectMeasure
  408. ? const CarotidLeftSiderSelectMeasure()
  409. : FContainer(
  410. key: Key(newPlayerController.url),
  411. child: const LeftSiderSelectMeasure()),
  412. )),
  413. const OperateBar(),
  414. ]
  415. ] else ...[
  416. FExpanded(
  417. child: FContainer(
  418. width: 300,
  419. ),
  420. ),
  421. FContainer(
  422. width: 300,
  423. child: FCenter(
  424. child: FElevatedButton(
  425. onPressed: () => measure3DViewController.backToVidMode(),
  426. child: FText(
  427. i18nBook.measure.vidMode.t,
  428. style: const TextStyle(color: Colors.white),
  429. ),
  430. ),
  431. ),
  432. ),
  433. const OperateBar(),
  434. ],
  435. widget.needRouterBack ?? false
  436. ? FContainer(
  437. width: 300,
  438. padding: const EdgeInsets.only(bottom: 20),
  439. child: FIconButton(
  440. onPressed: () {
  441. Get.back();
  442. },
  443. icon: const FIcon(
  444. Icons.arrow_back,
  445. color: Colors.white,
  446. size: 36,
  447. ),
  448. ),
  449. )
  450. : const FSizedBox()
  451. ],
  452. );
  453. }
  454. }
  455. class _MeasureArrow extends FStatefulWidget {
  456. @override
  457. FState<_MeasureArrow> createState() => _MeasureArrowState();
  458. }
  459. class _MeasureArrowState extends FState<_MeasureArrow> {
  460. final measureHandler = Get.find<MeasureHandler>();
  461. bool get isArrowMeasureAnnotationType =>
  462. measureHandler.changedAnnotationType == AnnotationType.arrow;
  463. void onChangedAnnotationType(
  464. Object sender,
  465. AnnotationType? e,
  466. ) {
  467. setState(() {});
  468. }
  469. @override
  470. void initState() {
  471. measureHandler.onChangedAnnotationType.addListener(onChangedAnnotationType);
  472. super.initState();
  473. }
  474. @override
  475. void dispose() {
  476. measureHandler.onChangedAnnotationType
  477. .removeListener(onChangedAnnotationType);
  478. super.dispose();
  479. }
  480. @override
  481. FWidget build(BuildContext context) {
  482. return FContainer(
  483. height: 100,
  484. width: 300,
  485. child: FCenter(
  486. child: FColumn(
  487. children: [
  488. FInk(
  489. child: FInkWell(
  490. onTap: () {
  491. measureHandler.changedAnnotationType = AnnotationType.arrow;
  492. final application = Get.find<IApplication>();
  493. application.switchAnnotation(AnnotationType.arrow);
  494. },
  495. child: FIcon(
  496. Icons.call_made_rounded,
  497. color:
  498. isArrowMeasureAnnotationType ? Colors.blue : Colors.white,
  499. size: 30,
  500. ),
  501. ),
  502. ),
  503. FText(
  504. i18nBook.measure.arrow.t,
  505. style: isArrowMeasureAnnotationType
  506. ? const TextStyle(
  507. color: Colors.blue,
  508. fontSize: 14,
  509. )
  510. : const TextStyle(
  511. color: Colors.white,
  512. fontSize: 14,
  513. ),
  514. ),
  515. ],
  516. ),
  517. ),
  518. );
  519. }
  520. }