measure_view.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. import 'package:fis_common/index.dart';
  2. import 'package:fis_jsonrpc/rpc.dart';
  3. import 'package:fis_measure/interfaces/process/workspace/exam_info.dart';
  4. import 'package:fis_measure/interfaces/process/workspace/measure_3d_view_controller.dart';
  5. import 'package:fis_measure/process/language/measure_language.dart';
  6. import 'package:fis_measure/process/layout/configuration.dart';
  7. import 'package:fis_measure/process/workspace/measure_controller.dart';
  8. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  9. import 'package:fis_measure/process/workspace/measure_handler.dart';
  10. import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart';
  11. import 'package:fis_measure/view/gesture/positioned_cursor.dart';
  12. import 'package:fis_measure/view/measure/measure_config/widgets/measure_configuration_style.dart';
  13. import 'package:fis_measure/view/measure/measure_images_bar.dart';
  14. import 'package:fis_measure/view/measure/measure_player.dart';
  15. import 'package:fis_measure/view/measure/measure_tool_panel.dart';
  16. import 'package:fis_measure/view/measure/measure_view_controller.dart';
  17. import 'package:fis_measure/view/player/control_board/operate_bar.dart';
  18. import 'package:fis_ui/index.dart';
  19. import 'package:fis_ui/interface/interactive_container.dart';
  20. import 'package:fis_ui/widgets/layout/offstage.dart';
  21. import 'package:flutter/material.dart';
  22. import 'package:get/get.dart';
  23. /// 测量主页面
  24. class MeasureMainPage extends StatefulWidget
  25. implements FWidget, FInteractiveContainer {
  26. const MeasureMainPage(
  27. this.token,
  28. this.recordCode,
  29. this.patientCode,
  30. this.remedicalCode, {
  31. this.needRouterBack,
  32. this.enableLoopPlayback = false,
  33. this.onLoopPlaybackEnableChanged,
  34. this.remedicalAISelectedInfoCode,
  35. Key? key,
  36. }) : super(key: key);
  37. final String token;
  38. final String patientCode;
  39. final String remedicalCode;
  40. final String recordCode;
  41. final bool? needRouterBack; // 需要返回按钮【一版用于返回到报告编辑】
  42. final bool enableLoopPlayback;
  43. final ValueCallback<bool>? onLoopPlaybackEnableChanged;
  44. final String? remedicalAISelectedInfoCode;
  45. @override
  46. final String pageName = 'MeasureMainPage';
  47. @override
  48. State<StatefulWidget> createState() => _MeasureMainPageState();
  49. }
  50. class _MeasureMainPageState extends State<MeasureMainPage> {
  51. /// 病人及图像信息
  52. String _curToken = '';
  53. String _curPatientCode = '';
  54. String _curRemedicalCode = '';
  55. String _curRecordCode = '';
  56. String? _curRemedicalAISelectedInfoCode = '';
  57. bool? _curNeedRouterBack;
  58. /// 数据
  59. final measureData = Get.find<MeasureDataController>();
  60. /// webview 控制器
  61. final measure3DViewController = Get.find<Measure3DViewController>();
  62. /// 测量项控制器
  63. final measureMetaController = Get.put(MeasureMetaController());
  64. /// 隐藏全屏loadding
  65. bool _hideFullScreenLoading = false;
  66. /// 图片loadding
  67. late bool imageLoaded = false;
  68. late final measureHandler = Get.find<MeasureHandler>();
  69. /// 测量控制器
  70. late MeasureController measureController;
  71. /// 响应图片切换事件通知
  72. void _onChangeImage(sender, e) {
  73. imageLoaded = e;
  74. setState(() {});
  75. }
  76. /// 响应全屏状态切换事件
  77. void _onChangeFullScreenState(sender, e) {
  78. setState(() {});
  79. }
  80. /// 响应工具面板状态切换事件
  81. void _onToolPanelStateChanged(sender, e) {
  82. setState(() {});
  83. }
  84. /// 在此处动态刷新测量窗口,而不是重置窗口
  85. void _onUpdateState(sender, Map<String, String> parameters) async {
  86. _curNeedRouterBack = false;
  87. bool needUpdate = false;
  88. if (_curRecordCode == (parameters['recordCode'] ?? '')) {
  89. if (_curRemedicalCode == (parameters['remedicalCode'] ?? '')) {
  90. } else {
  91. measureHandler.changeImageByRemedicalCode
  92. .emit(this, parameters['remedicalCode'] ?? '');
  93. }
  94. } else {
  95. needUpdate = true;
  96. }
  97. _curToken = parameters['token'] ?? '';
  98. _curPatientCode = parameters['patientCode'] ?? '';
  99. _curRemedicalCode = parameters['remedicalCode'] ?? '';
  100. _curRecordCode = parameters['recordCode'] ?? '';
  101. _curRemedicalAISelectedInfoCode =
  102. parameters['remedicalAISelectedInfoCode'] ?? '';
  103. if (needUpdate) {
  104. await LayoutConfiguration.ins.loadData();
  105. List<RemedicalInfoDTO> remedicals = await _getCurRemedicals();
  106. _initMouseModuel();
  107. _initCarotidModuel(remedicals);
  108. measureData.remedicalList = remedicals;
  109. var remedicalInfo = await measureData.getImageInfo
  110. .call(_curRemedicalCode, _curToken, _curRemedicalAISelectedInfoCode);
  111. if (remedicalInfo != null) {
  112. measureData.aiResults = remedicalInfo.diagnosisResult ?? '';
  113. if (remedicalInfo.terminalImages != null) {
  114. _hideFullScreenLoading = true;
  115. measureData.itemCurrentImage =
  116. measureData.chooseImageUrl(remedicalInfo.terminalImages!);
  117. getExamImageInfoList(remedicals);
  118. measureHandler.changeImageList.emit(this, remedicals);
  119. }
  120. }
  121. measureController.imageLoaded.removeListener(_onImageLoaded);
  122. measureController.imageLoaded.addListener(_onImageLoaded);
  123. }
  124. _setCurImageData();
  125. }
  126. /// 将当前的图像信息同步到 measureData 中
  127. void _setCurImageData() {
  128. measureData.measureImageData = MeasureImageData(
  129. patientCode: _curPatientCode,
  130. recordCode: _curRecordCode,
  131. remedicalCode: _curRemedicalCode,
  132. remedicalAISelectedInfoCode: _curRemedicalAISelectedInfoCode,
  133. );
  134. }
  135. /// 先加载图像布局配置,然后加载全局数据
  136. void _loadLayoutConfig() async {
  137. await LayoutConfiguration.ins.loadData();
  138. setState(() {
  139. _initData();
  140. });
  141. }
  142. @override
  143. void initState() {
  144. _curToken = widget.token;
  145. _curPatientCode = widget.patientCode;
  146. _curRemedicalCode = widget.remedicalCode;
  147. _curRecordCode = widget.recordCode;
  148. _curNeedRouterBack = widget.needRouterBack;
  149. _curRemedicalAISelectedInfoCode = widget.remedicalAISelectedInfoCode;
  150. _hideFullScreenLoading = false;
  151. _loadLayoutConfig();
  152. _setCurImageData();
  153. measureData.curMeasureDataChanged.addListener(_onUpdateState);
  154. measureHandler.onChangeImageLoaded.addListener(_onChangeImage);
  155. measureHandler.onChangeFullScreenState
  156. .addListener(_onChangeFullScreenState);
  157. measureHandler.onToolPanelStateChanged
  158. .addListener(_onToolPanelStateChanged);
  159. super.initState();
  160. }
  161. @override
  162. void dispose() {
  163. measureData.curMeasureDataChanged.removeListener(_onUpdateState);
  164. measureHandler.onChangeImageLoaded.removeListener(_onChangeImage);
  165. measureHandler.onChangeFullScreenState
  166. .removeListener(_onChangeFullScreenState);
  167. measureHandler.onToolPanelStateChanged
  168. .removeListener(_onToolPanelStateChanged);
  169. super.dispose();
  170. }
  171. @override
  172. FWidget build(BuildContext context) {
  173. FWidget body;
  174. if (!_hideFullScreenLoading) {
  175. const loadingWidget = FCenter(child: FCircularProgressIndicator());
  176. body = FRow(
  177. children: const [
  178. FExpanded(
  179. child: loadingWidget,
  180. ),
  181. ],
  182. );
  183. } else {
  184. body = FColumn(children: [
  185. FExpanded(
  186. child: FRow(
  187. children: [
  188. FOffstage(
  189. child: const MeasureToolPanel(),
  190. offstage: measureHandler.fullScreenState ||
  191. !measureHandler.toolPanelState),
  192. const FVerticalDivider(),
  193. FExpanded(
  194. child: FColumn(
  195. mainAxisSize: MainAxisSize.max,
  196. children: [
  197. FExpanded(
  198. child: FStack(
  199. children: [
  200. if (!imageLoaded)
  201. MeasureRightBoard(
  202. key: ValueKey(measureData.itemCurrentImage),
  203. ),
  204. if (imageLoaded)
  205. FContainer(
  206. color: Colors.black,
  207. child: const FCenter(
  208. child: FCircularProgressIndicator(),
  209. ),
  210. ),
  211. ],
  212. )),
  213. // if (!measureHandler.fullScreenState) const MeasureImagesBar(),
  214. ],
  215. ),
  216. ),
  217. ],
  218. ),
  219. ),
  220. FOffstage(
  221. child: FRow(children: [
  222. FColumn(children: [
  223. const OperateBar(),
  224. _curNeedRouterBack ?? false
  225. ? FContainer(
  226. width: 300,
  227. padding: const EdgeInsets.only(bottom: 20),
  228. child: FIconButton(
  229. businessParent: widget,
  230. name: "back",
  231. onPressed: () {
  232. Get.back();
  233. },
  234. icon: const FIcon(
  235. Icons.arrow_back,
  236. color: Colors.white,
  237. size: 36,
  238. ),
  239. ),
  240. )
  241. : const FSizedBox()
  242. ]),
  243. const FExpanded(child: MeasureImagesBar())
  244. ]),
  245. offstage: measureHandler.fullScreenState),
  246. ]);
  247. }
  248. return FCenter(
  249. child: FContainer(
  250. color: _hideFullScreenLoading
  251. ? const Color.fromRGBO(70, 70, 70, 1)
  252. : Colors.black,
  253. child: body,
  254. ),
  255. );
  256. }
  257. /// 获取当前状态下的图像集
  258. Future<List<RemedicalInfoDTO>> _getCurRemedicals() async {
  259. List<RemedicalInfoDTO> remedicals = [];
  260. var value = await measureData.getRemedicalList.call(
  261. _curPatientCode,
  262. _curRecordCode,
  263. _curToken,
  264. );
  265. for (var remedicalItemList in value) {
  266. remedicals.addAll(remedicalItemList.remedicalList ?? []);
  267. }
  268. return remedicals;
  269. }
  270. /// 初始化颈动脉模块
  271. /// [Carotid] ✅遍历出颈动脉信息列表,传给壳子
  272. void _initCarotidModuel(List<RemedicalInfoDTO> remedicals) {
  273. List<Ultra3DResourceInfo> ultra3DResourceInfos = [];
  274. for (var remedical in remedicals) {
  275. if (remedical.carotidResult != null) {
  276. ultra3DResourceInfos.add(Ultra3DResourceInfo(remedical.carotidResult!));
  277. }
  278. }
  279. measure3DViewController.ultra3DResourceInfoList = ultra3DResourceInfos;
  280. measure3DViewController.recordId = _curRecordCode;
  281. measure3DViewController.notifyShellLoadAllModel();
  282. }
  283. /// 初始化卡尺样式部分
  284. Future<void> _initMouseModuel() async {
  285. final mouseState = Get.put<IMouseState>(MouseState());
  286. final result = await measureData.getMeasureSystemSettingAsync()
  287. as MeasureSystemSettingDTO;
  288. measureData.measureSystemSetting = result;
  289. mouseState.cursorType =
  290. getMeasureSystemSettingCursorType(result.cursorType);
  291. mouseState.cursorSize = result.cursorSize.toDouble();
  292. }
  293. /// 测量页全局数据首次初始化
  294. void _initData() async {
  295. List<RemedicalInfoDTO> remedicals = await _getCurRemedicals();
  296. _initMouseModuel();
  297. _initCarotidModuel(remedicals);
  298. measureData.remedicalList = remedicals;
  299. var remedicalInfo = await measureData.getImageInfo
  300. .call(_curRemedicalCode, _curToken, _curRemedicalAISelectedInfoCode);
  301. if (remedicalInfo != null) {
  302. measureData.aiResults = remedicalInfo.diagnosisResult ?? '';
  303. if (remedicalInfo.terminalImages != null) {
  304. _hideFullScreenLoading = true;
  305. final imgInfo = remedicalInfo.terminalImages!;
  306. measureData.itemCurrentImage = measureData.chooseImageUrl(imgInfo);
  307. getExamImageInfoList(remedicals);
  308. }
  309. }
  310. measureController.imageLoaded.removeListener(_onImageLoaded);
  311. measureController.imageLoaded.addListener(_onImageLoaded);
  312. }
  313. /// 获取测量图片所需的图片组 并且写入控制器中 加载
  314. void getExamImageInfoList(List<RemedicalInfoDTO> remedicals) async {
  315. /// 检查中图片信息表
  316. List<ExamImageInfo> examImageInfoList = remedicals.map((e) {
  317. final imgInfo = e.terminalImages!;
  318. final vidUrl = measureData.chooseImageUrl(imgInfo);
  319. return ExamImageInfo(
  320. vidUrl,
  321. imgInfo.previewUrl!,
  322. remedicalCode: e.remedicalCode,
  323. );
  324. }).toList();
  325. Get.delete<MeasureController>();
  326. measureController = Get.put(MeasureController(
  327. "",
  328. imagesFetchFunc: (code) async {
  329. return examImageInfoList;
  330. },
  331. enableLoopPlayback: widget.enableLoopPlayback,
  332. onLoopPlaybackEnableChanged: widget.onLoopPlaybackEnableChanged,
  333. ));
  334. await measureController.load();
  335. final selectedImageIndex = examImageInfoList.indexWhere(
  336. (element) => element.url == measureData.itemCurrentImage,
  337. );
  338. if (selectedImageIndex > -1) {
  339. // 命中当前选择图像
  340. measureController.examInfo.selectedImageIndex = selectedImageIndex;
  341. }
  342. // MeasureLanguage.load(measureData.measureLanguage);
  343. }
  344. /// vid 切换时会触发到这里
  345. void _onImageLoaded(Object sender, ExamImageInfo? e) async {
  346. // measureHandler.changeImageLoaded = true;
  347. if (!mounted) return;
  348. final currentImage = measureData.remedicalList.firstWhereOrNull(
  349. (element) =>
  350. element.terminalImages!.imageUrl == e!.url ||
  351. element.terminalImages!.originImageUrl == e.url,
  352. );
  353. if (currentImage != null) {
  354. /// 获取图片详细信息
  355. var remedicalInfo = await measureData.getImageInfo(
  356. currentImage.remedicalCode ?? '',
  357. _curToken,
  358. e!.remedicalCode,
  359. );
  360. if (remedicalInfo != null) {
  361. measureData.aiResults = remedicalInfo.diagnosisResult ?? '';
  362. measure3DViewController.initParams();
  363. /// ai 良恶性 判断是否有ai
  364. measureData.diagnosisConclusion = remedicalInfo.diagnosisConclusion;
  365. var surfaceFile = remedicalInfo.carotidResult?.surfaceFile;
  366. var cDNMdlFile = remedicalInfo.carotidResult?.cDNMdlFile;
  367. if (remedicalInfo.carotidResult != null &&
  368. surfaceFile.isNotNullOrEmpty &&
  369. cDNMdlFile.isNotNullOrEmpty) {
  370. /// [Carotid] ✅详情传入测量信息
  371. measure3DViewController.carotidResult = remedicalInfo.carotidResult!;
  372. /// [Carotid] ✅需要在此通知 controller 存在颈动脉信息
  373. measure3DViewController.exist3DData = true;
  374. measure3DViewController
  375. .handleChangeCarotid2DImage(remedicalInfo.recordCode!);
  376. } else {
  377. measure3DViewController.exist3DData = false;
  378. }
  379. // [Carotid] ✅只要更换图片都要切换到Vid 2D模式
  380. if (measure3DViewController.curMeasureMode != MeasureMode.vidMode) {
  381. measure3DViewController.backToVidMode();
  382. }
  383. try {
  384. if (e != null) {
  385. Future.delayed(const Duration(milliseconds: 100), () {
  386. measureController.playerController.play();
  387. });
  388. setState(() {
  389. _hideFullScreenLoading = true;
  390. measureHandler.changeImageLoaded = false;
  391. });
  392. }
  393. } catch (error) {
  394. setState(() {
  395. _hideFullScreenLoading = true;
  396. measureHandler.changeImageLoaded = false;
  397. });
  398. }
  399. }
  400. }
  401. }
  402. }