measure_view.dart 20 KB

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