measure_view.dart 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  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:fis_ui/widgets/layout/offstage.dart';
  28. import 'package:flutter/material.dart';
  29. import 'package:get/get.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. TabEnum curSelectedTab = TabEnum.tabMeasureTool;
  319. void _onChangedTab(
  320. Object sender,
  321. TabEnum e,
  322. ) {
  323. setState(() {
  324. curSelectedTab = e;
  325. });
  326. }
  327. void _onCurItemMetaListChanged(sender, e) {
  328. if (mounted) {
  329. setState(() {});
  330. }
  331. }
  332. ///图像发生变化
  333. void changeImage(sender, e) {
  334. setState(() {});
  335. }
  336. @override
  337. void initState() {
  338. measureHandler.onChangedTab.addListener(_onChangedTab);
  339. measure3DViewController.updatePlayerMode.addListener(_onModeChanged);
  340. measureData.curItemMetaListChanged.addListener(_onCurItemMetaListChanged);
  341. measureHandler.onChangeImageLoaded.addListener(changeImage);
  342. super.initState();
  343. }
  344. @override
  345. void dispose() {
  346. measureHandler.onChangedTab.removeListener(_onChangedTab);
  347. measure3DViewController.updatePlayerMode.removeListener(_onModeChanged);
  348. measureData.curItemMetaListChanged
  349. .removeListener(_onCurItemMetaListChanged);
  350. measureHandler.onChangeImageLoaded.removeListener(changeImage);
  351. super.dispose();
  352. }
  353. /// 模式改变触发更新
  354. void _onModeChanged(Object s, MeasureMode mode) {
  355. switch (mode) {
  356. case MeasureMode.vidMode:
  357. final playerController = Get.find<IPlayerController>();
  358. // 通过帧加载完成事件通知,去更新测量项
  359. if (playerController.currentFrame != null) {
  360. playerController.firstFrameLoaded
  361. .emit(this, playerController.currentFrame!);
  362. }
  363. setState(() {
  364. showCarotid2DSelectMeasure = false;
  365. showMeasureItems = true;
  366. });
  367. break;
  368. case MeasureMode.carotid2DMode:
  369. setState(() {
  370. showCarotid2DSelectMeasure = true;
  371. showMeasureItems = true;
  372. });
  373. break;
  374. case MeasureMode.carotid3DMode:
  375. setState(() {
  376. showCarotid2DSelectMeasure = true;
  377. showMeasureItems = false;
  378. });
  379. break;
  380. }
  381. }
  382. bool get hideMeasureTab => (curSelectedTab == TabEnum.tabNodesTool);
  383. bool get hideCommentTab => !hideMeasureTab;
  384. @override
  385. FWidget build(BuildContext context) {
  386. final newPlayerController = Get.find<IPlayerController>();
  387. return FColumn(
  388. mainAxisSize: MainAxisSize.max,
  389. crossAxisAlignment: CrossAxisAlignment.start,
  390. children: [
  391. if (showMeasureItems) ...[
  392. LeftSiderHold(ifHideConfig: showCarotid2DSelectMeasure),
  393. FOffstage(
  394. offstage: hideCommentTab,
  395. child: FContainer(
  396. width: 300,
  397. key: UniqueKey(),
  398. child: LeftSelectInput(),
  399. ),
  400. ),
  401. FOffstage(
  402. offstage: hideMeasureTab,
  403. child: const LeftMeasureTools(),
  404. ),
  405. FExpanded(
  406. child: FStack(
  407. fit: StackFit.passthrough,
  408. children: [
  409. FOffstage(
  410. offstage: hideMeasureTab,
  411. child: FContainer(
  412. width: 300,
  413. child: showCarotid2DSelectMeasure
  414. ? const CarotidLeftSiderSelectMeasure()
  415. : FContainer(
  416. key: Key(newPlayerController.url),
  417. child: const LeftSiderSelectMeasure()),
  418. ),
  419. ),
  420. FOffstage(
  421. offstage: hideCommentTab,
  422. child: FContainer(
  423. width: 300,
  424. key: UniqueKey(),
  425. child: const MeasureLeftAnnotation(),
  426. ),
  427. ),
  428. ],
  429. ),
  430. ),
  431. FOffstage(offstage: hideCommentTab, child: _MeasureArrow()),
  432. FOffstage(
  433. offstage: hideMeasureTab,
  434. child: const OperateBar(),
  435. ),
  436. ] else ...[
  437. FExpanded(
  438. child: FContainer(
  439. width: 300,
  440. ),
  441. ),
  442. FContainer(
  443. width: 300,
  444. child: FCenter(
  445. child: FElevatedButton(
  446. description: "MeasureMainPage backToVidMode",
  447. onPressed: () => measure3DViewController.backToVidMode(),
  448. child: FText(
  449. i18nBook.measure.vidMode.t,
  450. style: const TextStyle(color: Colors.white),
  451. ),
  452. ),
  453. ),
  454. ),
  455. const OperateBar(),
  456. ],
  457. widget.needRouterBack ?? false
  458. ? FContainer(
  459. width: 300,
  460. padding: const EdgeInsets.only(bottom: 20),
  461. child: FIconButton(
  462. description: "MeasureMainPage back",
  463. onPressed: () {
  464. Get.back();
  465. },
  466. icon: const FIcon(
  467. Icons.arrow_back,
  468. color: Colors.white,
  469. size: 36,
  470. ),
  471. ),
  472. )
  473. : const FSizedBox()
  474. ],
  475. );
  476. }
  477. }
  478. class _MeasureArrow extends FStatefulWidget {
  479. @override
  480. FState<_MeasureArrow> createState() => _MeasureArrowState();
  481. }
  482. class _MeasureArrowState extends FState<_MeasureArrow> {
  483. final measureHandler = Get.find<MeasureHandler>();
  484. bool get isArrowMeasureAnnotationType =>
  485. measureHandler.changedAnnotationType == AnnotationType.arrow;
  486. void onChangedAnnotationType(
  487. Object sender,
  488. AnnotationType? e,
  489. ) {
  490. setState(() {});
  491. }
  492. @override
  493. void initState() {
  494. measureHandler.onChangedAnnotationType.addListener(onChangedAnnotationType);
  495. super.initState();
  496. }
  497. @override
  498. void dispose() {
  499. measureHandler.onChangedAnnotationType
  500. .removeListener(onChangedAnnotationType);
  501. super.dispose();
  502. }
  503. @override
  504. FWidget build(BuildContext context) {
  505. return FContainer(
  506. height: 100,
  507. width: 300,
  508. child: FCenter(
  509. child: FColumn(
  510. children: [
  511. FInk(
  512. child: FInkWell(
  513. onTap: () {
  514. measureHandler.changedAnnotationType = AnnotationType.arrow;
  515. final application = Get.find<IApplication>();
  516. application.switchAnnotation(AnnotationType.arrow);
  517. },
  518. child: FIcon(
  519. Icons.call_made_rounded,
  520. color:
  521. isArrowMeasureAnnotationType ? Colors.blue : Colors.white,
  522. size: 30,
  523. ),
  524. ),
  525. ),
  526. FText(
  527. i18nBook.measure.arrow.t,
  528. style: isArrowMeasureAnnotationType
  529. ? const TextStyle(
  530. color: Colors.blue,
  531. fontSize: 14,
  532. )
  533. : const TextStyle(
  534. color: Colors.white,
  535. fontSize: 14,
  536. ),
  537. ),
  538. ],
  539. ),
  540. ),
  541. );
  542. }
  543. }