measure_view.dart 14 KB

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