view.dart 23 KB

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