measure_tool.dart 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. import 'package:fis_common/logger/logger.dart';
  2. import 'package:fis_i18n/i18n.dart';
  3. import 'package:fis_jsonrpc/rpc.dart';
  4. import 'package:fis_measure/define.dart';
  5. import 'package:fis_measure/interfaces/process/items/item.dart';
  6. import 'package:fis_measure/interfaces/process/items/item_metas.dart';
  7. import 'package:fis_measure/interfaces/process/items/terms.dart';
  8. import 'package:fis_measure/interfaces/process/items/types.dart';
  9. import 'package:fis_measure/interfaces/process/player/play_controller.dart';
  10. import 'package:fis_measure/interfaces/process/workspace/application.dart';
  11. import 'package:fis_measure/process/language/measure_language.dart';
  12. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  13. import 'package:fis_measure/process/workspace/measure_data_helper.dart';
  14. import 'package:fis_measure/process/workspace/measure_handler.dart';
  15. import 'package:fis_measure/view/measure/combo_widget.dart';
  16. import 'package:fis_measure/view/measure/measure_view_controller.dart';
  17. import 'package:fis_measure/view/player/controller.dart';
  18. import 'package:fis_ui/index.dart';
  19. import 'package:fis_ui/interface/interactive_container.dart';
  20. import 'package:flutter/material.dart';
  21. import 'package:get/get.dart';
  22. import 'package:vid/us/vid_us_image.dart';
  23. import 'custom_item_buttons/combo.dart';
  24. /// 测量项页面
  25. class LeftSiderSelectMeasure extends FStatefulWidget
  26. implements FInteractiveContainer {
  27. const LeftSiderSelectMeasure({Key? key}) : super(key: key);
  28. @override
  29. final String pageName = 'LeftSiderSelectMeasure';
  30. @override
  31. FState<LeftSiderSelectMeasure> createState() => LeftSiderSelectMeasureState();
  32. }
  33. class LeftSiderSelectMeasureState extends FState<LeftSiderSelectMeasure> {
  34. /// 数据
  35. IApplication get application => Get.find<IApplication>();
  36. VidPlayerController get playerController =>
  37. Get.find<IPlayerController>() as VidPlayerController;
  38. final measureHandler = Get.find<MeasureHandler>();
  39. final measureData = Get.find<MeasureDataController>();
  40. final measureMetaController = Get.find<MeasureMetaController>();
  41. /// 测量语言包
  42. final measureLanguage = MeasureLanguage();
  43. /// 当前选中的测量项下标
  44. int activeItemIndex = 0;
  45. /// 当前选中的子测量项下标
  46. int activeChildItemIndex = 0;
  47. /// 上一个测量项
  48. int lastActiveItemIndex = 0;
  49. /// 【TODO】 翻译
  50. String selectedType = "手动";
  51. String selectedLocation = "上";
  52. /// Styles
  53. late String fontFamily;
  54. static const Color buttonBackgroundColor = Color.fromRGBO(70, 70, 70, 1);
  55. static const Color buttonBorderColor = Color.fromRGBO(124, 124, 124, 1);
  56. static const Color buttonBorderHighlight = Color.fromRGBO(124, 124, 124, 1);
  57. static const Color buttonTextColor = Colors.white;
  58. static const Color childButtonHighlight = Color.fromRGBO(84, 144, 249, 1);
  59. static const Color childContainerBackground = Color.fromRGBO(60, 60, 60, 1);
  60. static const double buttonBorderHighlightWidth = 1.0;
  61. /// 触发测量项选中事件
  62. void selectItem(int selectIndex) {
  63. if (selectIndex == activeItemIndex) return;
  64. final itemMeta = measureData.curItemMetaList[selectIndex];
  65. /// 【TODO】 暂时屏蔽 buyStatus
  66. // if (itemMeta.buyStatus == WorkingItemStatusEnum.Unpaid) {
  67. // PromptBox.toast("如需继续使用该测量项,请联系Vinno申请试用或购买。");
  68. // return;
  69. // }
  70. changeItem(itemMeta);
  71. // setState(() {
  72. // activeItemIndex = selectIndex;
  73. // activeChildItemIndex = 0;
  74. // });
  75. }
  76. /// 触发子测量项选中事件
  77. void selectChildItem(int selectIndex) {
  78. if (selectIndex == activeChildItemIndex) return;
  79. final item = application.activeMeasureItem!;
  80. if (item is ITopMeasureItem) {
  81. item.switchChild(selectIndex);
  82. activeChildItemIndex = selectIndex;
  83. }
  84. setState(() {});
  85. }
  86. /// 切换测量项
  87. void changeItem(ItemMeta itemMeta) {
  88. int itemIndex =
  89. measureData.curItemMetaList.indexWhere((e) => e.name == itemMeta.name);
  90. int childItemIndex = activeChildItemIndex;
  91. try {
  92. // IMeasureItem? item = application.activeMeasureItem;
  93. // if (item != null &&
  94. // item.meta.name == itemMeta.name &&
  95. // item is ITopMeasureItem &&
  96. // item.isCrossFrameMode) {
  97. // // 跨帧测量,测量项无变化时,自动切到下一个子项
  98. // childItemIndex = item.workingChildIndex + 1;
  99. // if (childItemIndex < item.childItems.length) {
  100. // item.switchChild(childItemIndex);
  101. // item.workingChildChanged.addListener(_onWorkingChildChanged);
  102. // if (mounted) {
  103. // setState(() {
  104. // activeChildItemIndex = childItemIndex;
  105. // });
  106. // }
  107. // return;
  108. // }
  109. // }
  110. application.switchItem(itemMeta);
  111. final item = application.activeMeasureItem!;
  112. if (item is ITopMeasureItem) {
  113. item.switchChild(0);
  114. item.workingChildChanged.addListener(_onWorkingChildChanged);
  115. }
  116. } catch (e) {
  117. logger.e("changeItem failed", e);
  118. }
  119. if (mounted) {
  120. setState(() {
  121. activeItemIndex = itemIndex;
  122. activeChildItemIndex = childItemIndex;
  123. });
  124. }
  125. }
  126. /// 子测量项变化事件监听
  127. void _onWorkingChildChanged(sender, int e) {
  128. setState(() {
  129. activeChildItemIndex = e;
  130. });
  131. }
  132. void _onItemMetaListChanged(Object s, dynamic e) {
  133. try {
  134. if (e != null) {
  135. setState(() {});
  136. }
  137. } catch (e) {
  138. debugPrint("ItemMetaListChanged fail " + e.toString());
  139. }
  140. }
  141. /// 首帧加载完成事件监听: 获取当前图像的测量项,从第一帧取
  142. void _onFirstFrameLoaded(Object sender, VidUsImage e) async {
  143. if (!mounted) return;
  144. _getMeasureItemsList(e);
  145. _getAnnotationList();
  146. }
  147. //初始化时尝试加载第一项测量项(如果有的话)
  148. void _loadFirstItem() {
  149. if (measureData.curItemMetaList.isNotEmpty) {
  150. if (_checkAndHostCrossFrameMeasureItem()) {
  151. return;
  152. }
  153. final itemMeta = measureData.curItemMetaList.first;
  154. changeItem(itemMeta);
  155. // setState(() {
  156. // activeItemIndex = 0;
  157. // activeChildItemIndex = 0;
  158. // });
  159. }
  160. }
  161. /// 注释获取
  162. void _getAnnotationList() async {
  163. List<String> annotationList = [];
  164. var measureCommentItemResult =
  165. await MeasureDataHelper.getCommentsByApplicationAsync(
  166. application.applicationName,
  167. application.categoryName,
  168. );
  169. measureData.measureCommentItemResult =
  170. measureCommentItemResult?.commentItems ?? [];
  171. measureCommentItemResult?.commentItems?.forEach((element) {
  172. annotationList.add(element.text ?? '');
  173. });
  174. measureData.annotationList = annotationList;
  175. measureHandler.onAnnotationsLoaded();
  176. }
  177. void _getMeasureItemsList(VidUsImage e) async {
  178. List<String> getModes = [];
  179. for (var element in e.visuals[0].modes) {
  180. getModes.add(element.type.toString().split('.')[1]);
  181. }
  182. measureData.applicationModes = e.visuals[0].modes;
  183. measureData.currentMode = e.visuals[0].modes.first.type.name;
  184. var measureModeSelection = MeasureModeSelection(
  185. application.applicationName,
  186. application.categoryName,
  187. application.isThirdPart ? ['TPPTissue'] : getModes,
  188. );
  189. measureHandler.measureModeChanged = measureModeSelection;
  190. var measureApplicationDTO =
  191. await MeasureDataHelper.getMeasureApplication(measureModeSelection);
  192. if (measureApplicationDTO != null) {
  193. measureData.measureApplicationVersion =
  194. measureApplicationDTO.version ?? '';
  195. measureData.availableModes = measureApplicationDTO.availableModes ?? [];
  196. if (application.isThirdPart) {
  197. measureData.currentMode = 'TPPTissue';
  198. }
  199. measureMetaController.setAvailableModes(measureData.currentMode);
  200. setState(() {});
  201. }
  202. }
  203. /// 测量项列表变化事件监听
  204. void _onCurItemMetaListChanged(sender, e) {
  205. if (measureData.curItemMetaList.isNotEmpty) {
  206. if (_checkAndHostCrossFrameMeasureItem()) {
  207. return;
  208. }
  209. changeItem(measureData.curItemMetaList[0]);
  210. }
  211. // setState(() {
  212. // activeItemIndex = 0;
  213. // activeChildItemIndex = 0;
  214. // });
  215. }
  216. bool _checkAndHostCrossFrameMeasureItem() {
  217. final crossCxt = application.crossFrameContext;
  218. if (crossCxt != null && !crossCxt.isOver) {
  219. final activeItem = application.activeMeasureItem;
  220. if (activeItem != null &&
  221. activeItem is ITopMeasureItem &&
  222. activeItem.isCrossFrameMode) {
  223. // 跨帧时&当前是跨帧测量项,则保持此测量项
  224. changeItem(activeItem.meta);
  225. return true;
  226. }
  227. }
  228. return false;
  229. }
  230. /// 是否显示测量项翻译事件变化
  231. void _onShowItemTransStateChanged(_, e) {
  232. setState(() {});
  233. }
  234. /// 鼠标右键结束测量事件【即切换到空测量项】,此时缓存被结束的测量项,如果再次收到右击事件,恢复被结束的测量项
  235. void _onRightClickFinishMeasure(_, e) {
  236. if (activeItemIndex == -1) {
  237. changeItem(measureData.curItemMetaList[lastActiveItemIndex]);
  238. setState(() {
  239. activeItemIndex = lastActiveItemIndex;
  240. activeChildItemIndex = 0;
  241. });
  242. } else {
  243. lastActiveItemIndex = activeItemIndex;
  244. final emptyItem = ItemMeta(
  245. "EmptyItem",
  246. measureType: MeasureTypes.Empty,
  247. description: "",
  248. outputs: [],
  249. );
  250. changeItem(emptyItem);
  251. setState(() {
  252. activeItemIndex = -1;
  253. activeChildItemIndex = 0;
  254. });
  255. }
  256. }
  257. @override
  258. void initState() {
  259. measureData.curItemMetaListChanged.addListener(_onCurItemMetaListChanged);
  260. measureData.itemMetaListChanged.addListener(_onItemMetaListChanged);
  261. measureData.showItemTransStateChanged
  262. .addListener(_onShowItemTransStateChanged);
  263. measureHandler.onRightClickFinishMeasure
  264. .addListener(_onRightClickFinishMeasure);
  265. WidgetsBinding.instance.addPostFrameCallback((call) {
  266. _loadFirstItem();
  267. playerController.firstFrameLoaded.addListener(_onFirstFrameLoaded);
  268. });
  269. super.initState();
  270. }
  271. @override
  272. dispose() {
  273. super.dispose();
  274. playerController.firstFrameLoaded.removeListener(_onFirstFrameLoaded);
  275. measureData.curItemMetaListChanged
  276. .removeListener(_onCurItemMetaListChanged);
  277. measureData.itemMetaListChanged.removeListener(_onItemMetaListChanged);
  278. measureData.showItemTransStateChanged
  279. .removeListener(_onShowItemTransStateChanged);
  280. measureHandler.onRightClickFinishMeasure
  281. .removeListener(_onRightClickFinishMeasure);
  282. final item = application.activeMeasureItem;
  283. if (item != null && item is ITopMeasureItem) {
  284. item.workingChildChanged.removeListener(_onWorkingChildChanged);
  285. }
  286. }
  287. @override
  288. FWidget build(BuildContext context) {
  289. fontFamily = Theme.of(context).textTheme.labelLarge!.fontFamily!;
  290. return buildItemsListView(measureData.curItemMetaList);
  291. }
  292. /// 构建测量项列表
  293. FWidget buildItemsListView(List<ItemMeta> itemMetaList) {
  294. return FListView(
  295. shrinkWrap: true,
  296. controller: ScrollController(),
  297. children: List.generate(itemMetaList.length, (i) {
  298. return _buildItemContainer(i);
  299. }));
  300. }
  301. /// 构建测量项容器
  302. FWidget _buildItemContainer(int itemMetaIndex) {
  303. final itemMeta = measureData.curItemMetaList[itemMetaIndex];
  304. final isActived = itemMetaIndex == activeItemIndex; // 是否选中
  305. final hasChildItem = itemMeta.childItems.isNotEmpty; //是否为组合测量项
  306. return FContainer(
  307. decoration: BoxDecoration(
  308. borderRadius: BorderRadius.circular(5),
  309. border: isActived && hasChildItem
  310. ? Border.all(
  311. color: buttonBorderHighlight,
  312. width: buttonBorderHighlightWidth)
  313. : null,
  314. ),
  315. margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
  316. child: FStack(
  317. children: [
  318. FColumn(children: [
  319. if (isActived && hasChildItem) ...[
  320. _buildItemTitle(itemMetaIndex),
  321. _buildChildItemContainer(itemMeta),
  322. ] else
  323. FContainer(
  324. width: double.infinity,
  325. child: _buildItemButton(itemMetaIndex),
  326. ),
  327. ]),
  328. // if (itemMeta.buyStatus != null)
  329. // FPositioned(
  330. // top: 0,
  331. // right: 0,
  332. // child: _buildItemBuyStatusName(
  333. // itemMeta.buyStatus ?? WorkingItemStatusEnum.Free,
  334. // ),
  335. // )
  336. ],
  337. ));
  338. }
  339. // 构建测量项是否需要购买
  340. FWidget _buildItemBuyStatusName(WorkingItemStatusEnum buyStatus) {
  341. String name = "";
  342. Color textColor = Colors.white;
  343. switch (buyStatus) {
  344. case WorkingItemStatusEnum.Probation:
  345. name = i18nBook.measure.probation.t;
  346. textColor = Colors.yellow;
  347. break;
  348. case WorkingItemStatusEnum.Unpaid:
  349. name = i18nBook.measure.unpaid.t;
  350. textColor = Colors.red;
  351. break;
  352. case WorkingItemStatusEnum.Purchased:
  353. name = i18nBook.measure.purchased.t;
  354. textColor = childButtonHighlight;
  355. break;
  356. case WorkingItemStatusEnum.Expired:
  357. name = i18nBook.measure.expired.t;
  358. textColor = Colors.red;
  359. break;
  360. default:
  361. break;
  362. }
  363. if (name.isNotEmpty) {
  364. return QuickFWidget(
  365. RawChip(
  366. backgroundColor: textColor.withOpacity(0.8),
  367. label: FText(
  368. name,
  369. style: TextStyle(
  370. color: Colors.white,
  371. fontFamily: fontFamily,
  372. fontSize: 12,
  373. ),
  374. ),
  375. // isEnabled: false,//禁止点选状态
  376. labelPadding: const EdgeInsets.symmetric(horizontal: 4),
  377. shape: OutlinedBorder.lerp(
  378. RoundedRectangleBorder(
  379. borderRadius: BorderRadius.circular(4),
  380. ),
  381. RoundedRectangleBorder(
  382. borderRadius: BorderRadius.circular(4),
  383. ),
  384. 0,
  385. )
  386. // padding: EdgeInsets.only(left: 10, right: 10, top: 5),
  387. ),
  388. );
  389. }
  390. return const FSizedBox();
  391. }
  392. // 构建测量项按钮
  393. FWidget _buildItemButton(int itemIndex) {
  394. final itemMeta = measureData.curItemMetaList[itemIndex];
  395. final transValue = measureLanguage.t('measure', itemMeta.description);
  396. // 非英文环境下显示中英文,否则只显示英文
  397. final ifShowCN = !i18nBook.isCurrentEnglish &&
  398. transValue != itemMeta.description &&
  399. measureData.measureSystemSetting.showAnnotation;
  400. final isActived = itemIndex == activeItemIndex;
  401. if (AutomaticMeasureSupportedTerms.items.contains(itemMeta.description)) {
  402. return _buildItemAutomaticMeasure(
  403. itemIndex,
  404. itemMeta,
  405. ifShowCN,
  406. transValue,
  407. isActived,
  408. );
  409. }
  410. return FElevatedButton(
  411. name: itemMeta.name,
  412. businessParent: widget,
  413. onPressed: () => selectItem(itemIndex),
  414. child: FColumn(
  415. mainAxisAlignment: MainAxisAlignment.center,
  416. children: [
  417. if (ifShowCN)
  418. FText(
  419. transValue,
  420. maxLines: 1,
  421. overflow: TextOverflow.ellipsis,
  422. style: TextStyle(
  423. color: isActived ? null : buttonTextColor,
  424. fontFamily: fontFamily,
  425. fontSize: 12,
  426. ),
  427. ),
  428. FText(
  429. itemMeta.description,
  430. style: TextStyle(
  431. color: isActived ? null : buttonTextColor,
  432. ),
  433. ),
  434. ],
  435. ),
  436. style: ElevatedButton.styleFrom(
  437. backgroundColor: isActived ? null : buttonBackgroundColor,
  438. fixedSize: const Size.fromHeight(
  439. 50,
  440. ),
  441. side: BorderSide(
  442. color: isActived ? Colors.transparent : buttonBorderColor,
  443. ),
  444. ),
  445. );
  446. }
  447. /// 构建特殊的自动测量项
  448. FWidget _buildItemAutomaticMeasure(
  449. int itemIndex,
  450. ItemMeta itemMeta,
  451. bool ifShowCN,
  452. String transValue,
  453. bool isActived,
  454. ) {
  455. return FColumn(
  456. children: [
  457. FRow(
  458. mainAxisSize: MainAxisSize.max,
  459. children: [
  460. FExpanded(
  461. child: FElevatedButton(
  462. name: itemMeta.name,
  463. businessParent: widget,
  464. onPressed: () => selectItem(itemIndex),
  465. child: FColumn(
  466. mainAxisAlignment: MainAxisAlignment.center,
  467. children: [
  468. if (ifShowCN)
  469. FText(
  470. transValue + "($selectedType)",
  471. maxLines: 1,
  472. overflow: TextOverflow.ellipsis,
  473. style: TextStyle(
  474. color: isActived ? null : buttonTextColor,
  475. fontFamily: fontFamily,
  476. fontSize: 12,
  477. ),
  478. ),
  479. FText(
  480. itemMeta.description,
  481. style: TextStyle(
  482. color: isActived ? null : buttonTextColor,
  483. ),
  484. ),
  485. ],
  486. ),
  487. style: ElevatedButton.styleFrom(
  488. backgroundColor: isActived ? null : buttonBackgroundColor,
  489. fixedSize: const Size.fromHeight(
  490. 50,
  491. ),
  492. side: BorderSide(
  493. color: isActived ? Colors.transparent : buttonBorderColor,
  494. ),
  495. ),
  496. ),
  497. ),
  498. ],
  499. ),
  500. if (isActived) ...[
  501. FContainer(
  502. width: double.maxFinite,
  503. decoration: const BoxDecoration(
  504. color: childContainerBackground,
  505. borderRadius: BorderRadius.only(
  506. bottomLeft: Radius.circular(5),
  507. bottomRight: Radius.circular(5),
  508. ),
  509. ),
  510. padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 5),
  511. child: FColumn(
  512. crossAxisAlignment: CrossAxisAlignment.start,
  513. children: [
  514. /// 【TODO】 翻译 测量方式
  515. FContainer(
  516. margin:
  517. const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
  518. child: const FText(
  519. '测量方式',
  520. style: TextStyle(
  521. fontSize: 12,
  522. color: buttonTextColor,
  523. ),
  524. ),
  525. ),
  526. FRow(
  527. children: ["自动", "半自动", "手动"]
  528. .map((e) => _buildMeasureButton(e, true))
  529. .toList(),
  530. ),
  531. if (selectedType == "自动") ...[
  532. FContainer(
  533. margin:
  534. const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
  535. child: const FText(
  536. '跟踪方向',
  537. style: TextStyle(
  538. fontSize: 12,
  539. color: buttonTextColor,
  540. ),
  541. ),
  542. ),
  543. FRow(
  544. children: ["上", "下", "全部"]
  545. .map((e) => _buildMeasureButton(e, false))
  546. .toList(),
  547. ),
  548. ]
  549. ],
  550. ),
  551. ),
  552. ]
  553. ],
  554. );
  555. }
  556. FWidget _buildMeasureButton(String type, bool isType) {
  557. return FExpanded(
  558. child: FInkWell(
  559. onTap: () {
  560. if (isType) {
  561. selectedType = type;
  562. } else {
  563. selectedLocation = type;
  564. }
  565. setState(() {});
  566. },
  567. child: FContainer(
  568. height: 40,
  569. margin: const EdgeInsets.symmetric(horizontal: 2),
  570. decoration: BoxDecoration(
  571. color: (isType ? selectedType == type : selectedLocation == type)
  572. ? const Color.fromARGB(255, 101, 211, 71)
  573. : const Color.fromRGBO(70, 70, 70, 1),
  574. border: Border.all(color: const Color.fromRGBO(124, 124, 124, 1)),
  575. borderRadius: BorderRadius.circular(5),
  576. ),
  577. // margin: const EdgeInsets.symmetric(horizontal: 11, vertical: 5),
  578. child: FCenter(
  579. child: FText(
  580. type.toString(),
  581. style: const TextStyle(
  582. color: Colors.white,
  583. ),
  584. ),
  585. ),
  586. ),
  587. ),
  588. );
  589. }
  590. // 构建测量项标签【用于组合测量项顶部】
  591. FWidget _buildItemTitle(int itemIndex) {
  592. final itemMeta = measureData.curItemMetaList[itemIndex];
  593. final transValue = measureLanguage.t('measure', itemMeta.description);
  594. // 非英文环境下显示中英文,否则只显示英文
  595. final ifShowCN = !i18nBook.isCurrentEnglish &&
  596. transValue != itemMeta.description &&
  597. measureData.measureSystemSetting.showAnnotation;
  598. return FContainer(
  599. decoration: const BoxDecoration(
  600. border: Border(
  601. bottom: BorderSide(
  602. color: buttonBorderHighlight, width: buttonBorderHighlightWidth),
  603. ),
  604. ),
  605. child: FSizedBox(
  606. height: 50,
  607. width: double.infinity,
  608. child: FColumn(
  609. mainAxisAlignment: MainAxisAlignment.center,
  610. children: [
  611. if (ifShowCN)
  612. FText(
  613. transValue,
  614. maxLines: 1,
  615. overflow: TextOverflow.ellipsis,
  616. style: TextStyle(
  617. color: buttonTextColor,
  618. fontFamily: fontFamily,
  619. fontSize: 12,
  620. ),
  621. ),
  622. FText(
  623. itemMeta.description,
  624. style: const TextStyle(
  625. color: buttonTextColor,
  626. ),
  627. ),
  628. ],
  629. ),
  630. ),
  631. );
  632. }
  633. /// 子测量项容器
  634. FWidget _buildChildItemContainer(ItemMeta itemMeta) {
  635. final activeName = itemMeta.description;
  636. final isSpecial = MeasurespecialsupportedTerms.items.contains(activeName);
  637. if (isSpecial) {
  638. //是否为特殊组合测量项
  639. return const SpecialItemHR();
  640. } else if (CustomComboItemGroup.specialItemTypes
  641. .contains(itemMeta.measureType)) {
  642. return CustomComboItemGroup(
  643. activeIndex: activeChildItemIndex,
  644. businessParent: widget,
  645. itemMeta: itemMeta,
  646. onChildClick: (int childItemIndex) {
  647. selectChildItem(childItemIndex);
  648. },
  649. );
  650. } else {
  651. return FContainer(
  652. decoration: const BoxDecoration(
  653. color: childContainerBackground,
  654. borderRadius: BorderRadius.only(
  655. bottomLeft: Radius.circular(5), bottomRight: Radius.circular(5)),
  656. ),
  657. padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 5),
  658. child: FColumn(
  659. children: (itemMeta.childItems)
  660. .asMap()
  661. .entries
  662. .map(
  663. (e) => _buildChildItemButton(
  664. e.value,
  665. e.key,
  666. ),
  667. )
  668. .toList(),
  669. ),
  670. );
  671. }
  672. }
  673. // 构建子测量项按钮
  674. FWidget _buildChildItemButton(ItemMeta itemMeta, int childItemIndex) {
  675. final isActived = childItemIndex == activeChildItemIndex;
  676. return FContainer(
  677. width: double.infinity,
  678. margin: const EdgeInsetsDirectional.all(5),
  679. child: FElevatedButton(
  680. businessParent: widget,
  681. name: "selectChildItemIdnex:$childItemIndex",
  682. onPressed: () {
  683. selectChildItem(childItemIndex);
  684. },
  685. child: FText(
  686. itemMeta.description,
  687. ),
  688. style: ElevatedButton.styleFrom(
  689. backgroundColor: isActived ? null : buttonBackgroundColor,
  690. side: BorderSide(
  691. color: isActived ? Colors.transparent : buttonBorderColor,
  692. ),
  693. ),
  694. ));
  695. }
  696. }