view.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. import 'dart:math';
  2. import 'package:fis_i18n/i18n.dart';
  3. import 'package:fis_jsonrpc/rpc.dart';
  4. import 'package:fis_lib_business_components/index.dart';
  5. import 'package:fis_lib_report/report_edit.dart';
  6. import 'package:fis_theme/theme.dart';
  7. import 'package:fis_ui/base_define/page.dart';
  8. import 'package:fis_ui/index.dart';
  9. import 'package:fis_ui/usual/tab_button.dart';
  10. import 'package:fis_ui/usual/tab_button_group.dart';
  11. import 'package:fis_ui/widgets/layout/button_group.dart';
  12. import 'package:flutter/gestures.dart';
  13. import 'package:flutter/material.dart';
  14. import 'package:get/get.dart';
  15. import 'package:fis_common/index.dart';
  16. import 'package:fis_measure/process/workspace/rpc_bridge.dart';
  17. import 'package:vitalapp/pages/image_report_inner_view/widgets/cloud_image_item.dart';
  18. import 'package:vitalapp/pages/report_edit/controller.dart';
  19. import 'package:vitalapp/pages/report_edit/widgets/vocabulary_entry_report.dart';
  20. import 'package:vitalapp/store/store.dart';
  21. class ReporteditPage extends GetView<ReportEditController> implements FPage {
  22. const ReporteditPage({this.pageName = 'ReporteditPage', Key? key})
  23. : super(key: key);
  24. @override
  25. final String pageName;
  26. @override
  27. Widget build(BuildContext context) {
  28. return _DesktopLayout(this);
  29. }
  30. }
  31. class _DesktopLayout extends StatefulWidget {
  32. ///父级节点
  33. final FPage businessParent;
  34. _DesktopLayout(this.businessParent);
  35. @override
  36. State<_DesktopLayout> createState() => _DesktopLayoutState();
  37. }
  38. class _DesktopLayoutState extends State<_DesktopLayout> {
  39. final controller = Get.find<ReportEditController>();
  40. final ScrollController _scrollController = ScrollController();
  41. double _scrollSensitivityFactor = 1.0;
  42. @override
  43. void initState() {
  44. if (kIsMobile) {
  45. _scrollSensitivityFactor = 10.0;
  46. }
  47. super.initState();
  48. }
  49. @override
  50. Widget build(BuildContext context) {
  51. if (Store.user.token?.isEmpty ?? true) {
  52. controller.initParameters();
  53. }
  54. final body = _buildBodyView();
  55. return Scaffold(
  56. body: Stack(
  57. children: [
  58. body,
  59. Positioned(
  60. right: 0,
  61. top: 0,
  62. bottom: 60,
  63. child: Obx(() {
  64. if (controller.state.isEntryDisplayed) {
  65. return VocabularyEntryReport(onClose: () {
  66. controller.state.isEntryDisplayed = false;
  67. });
  68. } else {
  69. return FSizedBox();
  70. }
  71. }),
  72. ),
  73. ],
  74. ),
  75. );
  76. }
  77. ///报告内容视图
  78. Widget _buildReportContentView(BuildContext context) {
  79. final state = controller.state;
  80. var body = Obx(() {
  81. final jsonStr =
  82. controller.state.selectedTemplate.reportTemplateJson ?? '';
  83. if (jsonStr.isEmpty) {
  84. return FSizedBox(
  85. width: 540,
  86. );
  87. }
  88. return Container(
  89. color: Colors.white,
  90. margin: EdgeInsets.only(top: 60),
  91. child: Listener(
  92. onPointerSignal: (pointerSignal) {
  93. if (pointerSignal is PointerScrollEvent) {
  94. final newOffset = _scrollController.offset +
  95. pointerSignal.scrollDelta.dy * _scrollSensitivityFactor;
  96. if (newOffset <= _scrollController.position.maxScrollExtent &&
  97. newOffset >= _scrollController.position.minScrollExtent) {
  98. _scrollController.jumpTo(newOffset);
  99. }
  100. }
  101. },
  102. child: ListView(
  103. controller: _scrollController,
  104. shrinkWrap: true,
  105. children: [
  106. FReportEditPage(
  107. reporter: Store.user.displayName,
  108. reportDate: DateTime.now(),
  109. jsonStr: jsonStr,
  110. onSelect: controller.onSelect,
  111. patinentAge: state.patientAge,
  112. patinentName: state.patientName,
  113. patinentSex: state.patientSex,
  114. selectEntry: i18nBook.remedical.selectWord.t,
  115. revoke: i18nBook.common.revoke.t,
  116. selectImageHint: i18nBook.remedical.clickAndSelectImage.t,
  117. ),
  118. ],
  119. ),
  120. ),
  121. );
  122. });
  123. return Stack(
  124. children: [
  125. body,
  126. Positioned(
  127. left: 15,
  128. top: 15,
  129. child: Obx(() {
  130. ///编辑报告不支持修改模板
  131. if (controller.state.templates.isEmpty ||
  132. controller.state.reportCode.isNotEmpty) {
  133. return FSizedBox();
  134. }
  135. return FSelect<FSelectOptionModel, String>(
  136. source: controller.state.templates,
  137. fontFamily: FTheme.ins.localeSetting.fontFamily,
  138. width: 320,
  139. optionValueExtractor: (t) {
  140. return t.value;
  141. },
  142. optionLabelExtractor: (t) {
  143. return t.title;
  144. },
  145. value: controller.state.selectedTemplate.reportTemplateCode,
  146. onSelectChanged: (value, index) async {
  147. controller.onSelectedTemplateChange(value);
  148. },
  149. );
  150. }),
  151. ),
  152. ],
  153. );
  154. }
  155. ///图像展示
  156. Widget _buildImagsView(BuildContext context) {
  157. return Obx(
  158. () {
  159. if (controller.state.selectImageTabIndex == 0) {
  160. return _buildCloudImagesView(context);
  161. } else if (controller.state.selectImageTabIndex == 1) {
  162. if (controller.state.remedicalMeasured.length == 0) {
  163. return FExpanded(
  164. child: NoDataWidget(),
  165. );
  166. }
  167. return _buildMeasureImageOrAiImageContentView(context);
  168. } else if (controller.state.selectImageTabIndex == 3) {
  169. if (controller.state.remedicalAISelectedInfos.length == 0) {
  170. return FExpanded(
  171. child: NoDataWidget(),
  172. );
  173. }
  174. return _buildAiImagesView(context);
  175. }
  176. return SizedBox();
  177. },
  178. );
  179. }
  180. ///一排操作按钮(提交、预览、返回)
  181. Widget _buildOperateLine() {
  182. var buttonStyle = ButtonStyle(
  183. padding: MaterialStateProperty.all(EdgeInsets.symmetric(vertical: 15)),
  184. backgroundColor: MaterialStateProperty.all(Colors.white),
  185. foregroundColor: MaterialStateProperty.all(Colors.black),
  186. );
  187. final mediaQuery = MediaQuery.of(context);
  188. ///屏幕缩放比例
  189. final devicePixelRatio = mediaQuery.devicePixelRatio;
  190. final textStyle = TextStyle(
  191. fontSize: (devicePixelRatio > 1 && !i18nBook.isCurrentChinese) ? 10 : 14,
  192. );
  193. return SizedBox(
  194. height: 50,
  195. child: FButtonGroup(
  196. buttonPadding: 25,
  197. children: [
  198. FElevatedButton(
  199. name: "submitReport",
  200. businessParent: this.widget.businessParent,
  201. onPressed: () async {
  202. await controller.submitReport();
  203. },
  204. style: ButtonStyle(
  205. padding: MaterialStateProperty.all(
  206. EdgeInsets.symmetric(vertical: 15),
  207. ),
  208. ),
  209. child: FText(
  210. i18nBook.common.submit.t,
  211. style: textStyle,
  212. ),
  213. ),
  214. FElevatedButton(
  215. businessParent: this.widget.businessParent,
  216. name: "printDirectly",
  217. onPressed: () async {
  218. await controller.printReportDirectly();
  219. },
  220. style: buttonStyle,
  221. child: FText(
  222. i18nBook.remedical.print.t,
  223. style: textStyle,
  224. ),
  225. ),
  226. FElevatedButton(
  227. businessParent: this.widget.businessParent,
  228. name: "exportDirectly",
  229. onPressed: () async {
  230. await controller.exportReportDirectly();
  231. },
  232. style: buttonStyle,
  233. child: FText(
  234. i18nBook.remedical.export.t,
  235. style: textStyle,
  236. ),
  237. ),
  238. FElevatedButton(
  239. businessParent: this.widget.businessParent,
  240. name: "refreshImages",
  241. onPressed: () async {
  242. await controller.refreshImages();
  243. setState(() {});
  244. },
  245. style: buttonStyle,
  246. child: FText(
  247. i18nBook.common.refresh.t,
  248. style: textStyle,
  249. ),
  250. ),
  251. ],
  252. ),
  253. );
  254. }
  255. ///云端图像
  256. FWidget _buildCloudImagesView(BuildContext context) {
  257. final leftWidth = (MediaQuery.of(context).size.width - 90) * 7 / 15;
  258. final imageWidth = leftWidth / 3 + 10;
  259. bool carotidLeftIsNotEmpty =
  260. controller.state.cloudCarotidLeftImages.isNotEmpty;
  261. bool carotidRightIsNotEmpty =
  262. controller.state.cloudCarotidRightImages.isNotEmpty;
  263. List<FWidget> children = [];
  264. if (controller.state.remedicalItemList.isNotEmpty) {
  265. children.add(
  266. _buildGridCloudImagesView(
  267. imageWidth, controller.state.remedicalItemList),
  268. );
  269. }
  270. if (carotidLeftIsNotEmpty || carotidRightIsNotEmpty) {
  271. children.add(_buildCarotidTabs());
  272. if (controller.state.carotidVAS == CarotidScanTypeEnum.CarotidLeft) {
  273. children.add(_buildGridCloudImagesView(
  274. imageWidth, controller.state.cloudCarotidLeftImages));
  275. } else if (controller.state.carotidVAS ==
  276. CarotidScanTypeEnum.CarotidRight) {
  277. children.add(_buildGridCloudImagesView(
  278. imageWidth, controller.state.cloudCarotidRightImages));
  279. }
  280. }
  281. if (children.length == 0) {
  282. return FExpanded(
  283. child: NoDataWidget(),
  284. );
  285. }
  286. return FExpanded(
  287. child: FContainer(
  288. width: leftWidth,
  289. color: Colors.white,
  290. child: FListView(
  291. shrinkWrap: true,
  292. children: children,
  293. ),
  294. ),
  295. );
  296. }
  297. FWidget _buildCarotidTabs() {
  298. bool carotidLeftIsNotEmpty =
  299. controller.state.cloudCarotidLeftImages.isNotEmpty;
  300. bool carotidRightIsNotEmpty =
  301. controller.state.cloudCarotidRightImages.isNotEmpty;
  302. return FContainer(
  303. margin: EdgeInsets.only(bottom: 5, top: 15, left: 10),
  304. child: FRow(
  305. children: [
  306. if (carotidLeftIsNotEmpty) ...[
  307. _buildInkWell(
  308. CarotidScanTypeEnum.CarotidLeft,
  309. ),
  310. ],
  311. if (carotidRightIsNotEmpty) ...[
  312. _buildInkWell(
  313. CarotidScanTypeEnum.CarotidRight,
  314. )
  315. ],
  316. ],
  317. ),
  318. );
  319. }
  320. FWidget _buildInkWell(
  321. CarotidScanTypeEnum carotidScanType,
  322. ) {
  323. final isChooseLeftCarotid = controller.state.carotidVAS == carotidScanType;
  324. final isLeftCarotid = carotidScanType == CarotidScanTypeEnum.CarotidLeft;
  325. return FInkWell(
  326. onTap: () {
  327. controller.state.carotidVAS = carotidScanType;
  328. },
  329. child: FContainer(
  330. decoration: BoxDecoration(
  331. border: Border.all(
  332. width: 0.5,
  333. color: Colors.grey,
  334. ),
  335. color: isChooseLeftCarotid
  336. ? FTheme.ins.colorScheme.primary
  337. : Colors.transparent,
  338. borderRadius: isLeftCarotid
  339. ? BorderRadius.only(
  340. topLeft: Radius.circular(4),
  341. bottomLeft: Radius.circular(4),
  342. )
  343. : BorderRadius.only(
  344. topRight: Radius.circular(4),
  345. bottomRight: Radius.circular(4),
  346. ),
  347. ),
  348. padding: EdgeInsets.symmetric(
  349. horizontal: 20,
  350. vertical: 5,
  351. ),
  352. child: FText(
  353. isLeftCarotid
  354. ? i18nBook.remedical.leftNeck.t
  355. : i18nBook.remedical.rightNeck.t,
  356. style: TextStyle(
  357. color: isChooseLeftCarotid ? Colors.white : Colors.black,
  358. ),
  359. ),
  360. ),
  361. );
  362. }
  363. void _handleDoubleClick(
  364. String imageUrl,
  365. int index,
  366. String remedicalCode,
  367. String? remedicalAISelectedInfoCode, {
  368. VidImageSource vidImageSource = VidImageSource.Remedical,
  369. }) {
  370. controller.enterVidMeasurePage(
  371. imageUrl,
  372. index,
  373. remedicalCode,
  374. remedicalAISelectedInfoCode,
  375. );
  376. controller.getRemedicalList();
  377. }
  378. ///测量图像
  379. Widget _buildMeasureImageOrAiImageContentView(BuildContext context,
  380. [bool? isAiImage = false]) {
  381. final leftWidth = (MediaQuery.of(context).size.width - 90) * 7 / 15;
  382. final imageWidth = leftWidth / 3 - 40;
  383. return Expanded(
  384. child: Container(
  385. width: leftWidth,
  386. child: Obx(() {
  387. return GridView.builder(
  388. controller: ScrollController(),
  389. itemCount: isAiImage!
  390. ? controller.state.remedicalAISelectedInfos.length
  391. : controller.state.remedicalMeasured.length,
  392. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  393. crossAxisCount: 3,
  394. ),
  395. itemBuilder: (BuildContext context, int index) {
  396. return _buildMeasurelImage(
  397. index,
  398. imageWidth,
  399. );
  400. },
  401. );
  402. }),
  403. ),
  404. );
  405. }
  406. /// AI图像
  407. Widget _buildAiImagesView(BuildContext context) {
  408. final leftWidth = (MediaQuery.of(context).size.width - 90) * 7 / 15;
  409. final imageWidth = leftWidth / 3 + 10;
  410. return Expanded(
  411. child: Container(
  412. width: leftWidth,
  413. color: Colors.white,
  414. child: Obx(
  415. () => FGridView.builder(
  416. controller: ScrollController(),
  417. itemCount: controller.state.remedicalAISelectedInfos.length,
  418. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  419. crossAxisCount: 3,
  420. ),
  421. itemBuilder: (BuildContext context, int index) {
  422. var imageRemedicalAISelectedInfo =
  423. controller.state.remedicalAISelectedInfos[index];
  424. var imageInfo = RemedicalInfoDTO(
  425. recordCode: imageRemedicalAISelectedInfo.recordCode,
  426. remedicalCode:
  427. imageRemedicalAISelectedInfo.remedicalAISelectedInfoCode,
  428. diagnosisConclusion:
  429. imageRemedicalAISelectedInfo.diagnosisConclusion,
  430. diagnosisOrgans: imageRemedicalAISelectedInfo.diagnosisOrgans,
  431. terminalImages: TerminalImageDTO(
  432. previewUrl: imageRemedicalAISelectedInfo.previewFileToken,
  433. imageUrl: imageRemedicalAISelectedInfo.aICdnFileToken,
  434. coverImageUrl: imageRemedicalAISelectedInfo.aIFileToken,
  435. originImageUrl: imageRemedicalAISelectedInfo.orginalFileToken,
  436. ),
  437. );
  438. var terminalImage =
  439. imageInfo.terminalImages ?? TerminalImageDTO();
  440. return CloudImageItem(
  441. index,
  442. imageInfo,
  443. onTap: () {
  444. var coverImageUrl = terminalImage.coverImageUrl ?? '';
  445. if (coverImageUrl.isNullOrEmpty) {
  446. coverImageUrl = terminalImage.previewUrl ?? '';
  447. }
  448. controller.onSelect.emit(this, coverImageUrl);
  449. final selectedImages = controller.state.selectedImages;
  450. if (selectedImages.contains(coverImageUrl)) {
  451. controller.state.selectedImages.remove(coverImageUrl);
  452. } else {
  453. controller.state.selectedImages.add(coverImageUrl);
  454. }
  455. },
  456. onDoubleTap: () {
  457. _handleDoubleClick(
  458. terminalImage.imageUrl ?? '',
  459. index,
  460. imageInfo.remedicalCode ?? '',
  461. imageRemedicalAISelectedInfo.remedicalAISelectedInfoCode,
  462. vidImageSource: VidImageSource.AiResultModifier,
  463. );
  464. },
  465. imageWidth: imageWidth,
  466. // description: translation_delegate_helper.translateDescription(
  467. // imageInfo.application,
  468. // imageInfo.applicationCategory,
  469. // ),
  470. isSelected: controller.state.selectedImages
  471. .contains(terminalImage.coverImageUrl),
  472. onSelectedInputChange: controller.onSelectedInputChange,
  473. );
  474. },
  475. ),
  476. ),
  477. ),
  478. );
  479. }
  480. /// 单个图像
  481. Widget _buildMeasurelImage(
  482. int index,
  483. double imageWidth,
  484. ) {
  485. return Obx(() {
  486. final measureImage = controller.state.remedicalMeasured[index];
  487. final fileToken = measureImage.measuredFileToken ?? '';
  488. final previewFileToken = measureImage.previewFileToken ?? '';
  489. bool _isSelect = controller.state.selectedImages.contains(fileToken);
  490. return FStack(
  491. children: [
  492. FMouseRegion(
  493. cursor: SystemMouseCursors.click,
  494. child: FGestureDetector(
  495. businessParent: this.widget.businessParent,
  496. name: "measureImage:$index",
  497. onTap: () {
  498. controller.onMersureImageTap(fileToken, index);
  499. },
  500. child: FContainer(
  501. decoration: BoxDecoration(
  502. border: Border.all(
  503. width: 4,
  504. color: _isSelect
  505. ? FTheme.ins.colorScheme.primary
  506. : Colors.transparent),
  507. color: Colors.black,
  508. ),
  509. width: imageWidth,
  510. height: imageWidth,
  511. margin: const EdgeInsets.all(10),
  512. child: FImage.network(
  513. previewFileToken,
  514. fit: BoxFit.fitWidth,
  515. width: imageWidth,
  516. height: imageWidth,
  517. ),
  518. ),
  519. ),
  520. ),
  521. FPositioned(
  522. left: 20,
  523. top: 15,
  524. child: FText(
  525. (index + 1).toString(),
  526. style: TextStyle(color: Colors.white),
  527. ),
  528. ),
  529. ],
  530. );
  531. });
  532. }
  533. FWidget _buildGridCloudImagesView(
  534. double imageWidth, List<RemedicalInfoDTO> remedicalItemList) {
  535. return FGridView.builder(
  536. itemCount: remedicalItemList.length,
  537. shrinkWrap: true,
  538. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  539. crossAxisCount: 3,
  540. ),
  541. itemBuilder: (BuildContext context, int index) {
  542. var imageInfo = remedicalItemList[index];
  543. var terminalImage =
  544. remedicalItemList[index].terminalImages ?? TerminalImageDTO();
  545. return Obx(() {
  546. bool isSelected = controller.state.selectedImages
  547. .contains(terminalImage.coverImageUrl);
  548. return CloudImageItem(
  549. index,
  550. imageInfo,
  551. onTap: () {
  552. var coverImageUrl = terminalImage.coverImageUrl ?? '';
  553. if (coverImageUrl.isNullOrEmpty) {
  554. coverImageUrl = terminalImage.previewUrl ?? '';
  555. }
  556. controller.onSelect.emit(this, coverImageUrl);
  557. final selectedImages = controller.state.selectedImages;
  558. if (selectedImages.contains(coverImageUrl)) {
  559. controller.state.selectedImages.remove(coverImageUrl);
  560. } else {
  561. controller.state.selectedImages.add(coverImageUrl);
  562. }
  563. },
  564. onDoubleTap: () {
  565. _handleDoubleClick(
  566. terminalImage.imageUrl ?? '',
  567. index,
  568. imageInfo.remedicalCode ?? '',
  569. imageInfo.remedicalCode ?? '',
  570. );
  571. },
  572. imageWidth: imageWidth,
  573. // description: translation_delegate_helper.translateDescription(
  574. // imageInfo.application,
  575. // imageInfo.applicationCategory,
  576. // ),
  577. isSelected: isSelected,
  578. onSelectedInputChange: controller.onSelectedInputChange,
  579. );
  580. });
  581. },
  582. );
  583. }
  584. Widget _buildBodyView() {
  585. var padding = EdgeInsets.symmetric(vertical: 8, horizontal: 0);
  586. final allWidth = MediaQuery.of(context).size.width;
  587. // 930 = 左侧菜单栏(90)+ 报告编辑区域(540)+ 分割线(30)
  588. final fittedWidth = allWidth - 670;
  589. // 600 是图像区域最大宽度限制,防止在大分辨率下,图像区域过大造成不美观
  590. const maxWidth = 600.0;
  591. final imagePoolWidth = min(fittedWidth, maxWidth);
  592. return Container(
  593. decoration: BoxDecoration(
  594. border: Border.all(
  595. width: 1,
  596. color: Colors.grey,
  597. )),
  598. child: Row(
  599. children: [
  600. Expanded(child: _buildReportContentView(context)),
  601. const VerticalDivider(
  602. width: 1,
  603. color: Colors.grey,
  604. ),
  605. const SizedBox(width: 10),
  606. Container(
  607. width: imagePoolWidth,
  608. child: Column(
  609. children: [
  610. const SizedBox(height: 10),
  611. Container(
  612. child: Obx(
  613. () {
  614. return FTabButtonGroup(
  615. [
  616. FTabButton(
  617. name: "cloudImage",
  618. padding: padding,
  619. businessParent: widget.businessParent,
  620. tabTitle: i18nBook.remedical.cloudImageShort.t,
  621. index: 0,
  622. activeIndex: controller.state.selectImageTabIndex,
  623. onCallbackTap: () {
  624. controller.state.selectImageTabIndex = 0;
  625. RPCBridge.ins.source = VidImageSource.Remedical;
  626. },
  627. ),
  628. FSizedBox(width: 10),
  629. FTabButton(
  630. name: "measureImage",
  631. businessParent: widget.businessParent,
  632. padding: padding,
  633. tabTitle: i18nBook.measure.measureImageShort.t,
  634. index: 1,
  635. activeIndex: controller.state.selectImageTabIndex,
  636. onCallbackTap: () {
  637. controller.remedicalsController
  638. .findRemedicalMeasuredInfoAsync();
  639. controller.state.selectImageTabIndex = 1;
  640. },
  641. ),
  642. if (controller.state.consultationCode.isNotEmpty) ...[
  643. FSizedBox(width: 10),
  644. FTabButton(
  645. name: "consultationImage",
  646. businessParent: widget.businessParent,
  647. padding: padding,
  648. tabTitle: i18nBook.realTimeConsultation
  649. .consultationImageShort.t,
  650. index: 2,
  651. activeIndex: controller.state.selectImageTabIndex,
  652. onCallbackTap: () {
  653. controller.state.selectImageTabIndex = 2;
  654. },
  655. )
  656. ],
  657. ],
  658. );
  659. },
  660. ),
  661. ),
  662. _buildImagsView(context),
  663. const SizedBox(height: 5),
  664. _buildOperateLine(),
  665. const SizedBox(height: 15),
  666. ],
  667. ),
  668. ),
  669. ],
  670. ),
  671. );
  672. }
  673. }