From 27a92960c5b8b6e33bc5c7fceea119f276a8c7da Mon Sep 17 00:00:00 2001 From: louis <869322496@qq.com> Date: Mon, 1 Apr 2024 14:27:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=B5=E6=B6=B2=E6=8E=A7=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sale_quotation/sale_quotation.dart | 364 +++++++++++------- lib/util/modal.util.dart | 17 +- lib/widgets/sk_appbar.dart | 8 +- pubspec.lock | 8 + pubspec.yaml | 1 + test/widget_test.dart | 29 -- 6 files changed, 253 insertions(+), 174 deletions(-) delete mode 100644 test/widget_test.dart diff --git a/lib/screens/sale_quotation/sale_quotation.dart b/lib/screens/sale_quotation/sale_quotation.dart index 0d85e5e..18d1c8f 100644 --- a/lib/screens/sale_quotation/sale_quotation.dart +++ b/lib/screens/sale_quotation/sale_quotation.dart @@ -1,4 +1,8 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:get/get.dart'; import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/screens/sale_quotation/components/data_table.dart'; @@ -16,17 +20,38 @@ class SaleQuotationPage extends StatelessWidget { final headerTitleStyle = TextStyle( fontSize: ScreenAdaper.sp(40), fontWeight: FontWeight.w600, - color: AppTheme.secondPrimaryTextColorWithBg); + color: AppTheme.nearlyBlack); + final headerBgcolor = Color.fromARGB(255, 238, 238, 238); @override Widget build(BuildContext context) { return Scaffold( - appBar: const SkAppbar(title: '报价计算'), + appBar: SkAppbar( + title: '报价计算', + action: [ + TextButton( + onPressed: () {}, + child: Row( + children: [ + Icon(Icons.add, color: AppTheme.white), + Text( + '添加组', + style: TextStyle( + color: AppTheme.white, + fontSize: ScreenAdaper.sp(35), + fontWeight: FontWeight.w600), + ) + ], + )) + ], + ), body: Column( children: [ Container( decoration: BoxDecoration( - color: AppTheme.secondPrimaryColorLight, - border: Border(bottom: BorderSide(color: Colors.grey[200]!))), + color: headerBgcolor, + border: Border( + bottom: BorderSide( + color: AppTheme.nearlyBlack.withOpacity(0.5)))), padding: EdgeInsets.only( left: ScreenAdaper.width(20), top: ScreenAdaper.height(10), @@ -67,18 +92,20 @@ class SaleQuotationPage extends StatelessWidget { ], )), Expanded( - child: CustomScrollView( - slivers: controller.groups.map((e) => buildBody(e)).toList(), - )), + child: Obx(() => CustomScrollView( + slivers: controller.groups + .mapIndexed((index, e) => buildBody(e, index)) + .toList(), + ))), ], ), ); } - Widget buildBody(SaleQuotationModel group) { + Widget buildBody(SaleQuotationModel group, int index) { final titleStyle = TextStyle( fontSize: ScreenAdaper.sp(40), - color: AppTheme.secondPrimaryTextColorWithBg, + color: AppTheme.nearlyBlack, fontWeight: FontWeight.w600); final subTextStyle = TextStyle(color: AppTheme.grey, fontSize: ScreenAdaper.sp(30)); @@ -86,127 +113,192 @@ class SaleQuotationPage extends StatelessWidget { return SliverStickyHeader.builder( builder: (context, state) => Container( height: ScreenAdaper.height(80), - color: AppTheme.secondPrimaryColorLight - .withOpacity(1.0 - state.scrollPercentage), - padding: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(20)), + color: headerBgcolor.withOpacity(1.0 - state.scrollPercentage), alignment: Alignment.centerLeft, - child: Row( - children: [ - Text( - group.name, - style: titleStyle, + child: InkWell( + onTap: () { + controller.groups[index].isExpanded.value = + !controller.groups[index].isExpanded.value; + }, + child: Row( + children: [ + Obx(() => controller.groups[index].isExpanded.value + ? IconButton( + padding: EdgeInsets.zero, + onPressed: () { + controller.groups[index].isExpanded.value = false; + }, + icon: const Icon( + Icons.expand_more, + color: AppTheme.nearlyBlack, + ), + ) + : IconButton( + padding: EdgeInsets.zero, + onPressed: () { + controller.groups[index].isExpanded.value = true; + }, + icon: const Icon( + Icons.chevron_right, + color: AppTheme.nearlyBlack, + ), + )), + Text( + group.name, + style: titleStyle, + ), + const Spacer(), + IconButton( + onPressed: () { + ModalUtil.confirm(title: '确定要删除此组吗?', onConfirm: () {}); + }, + icon: const Icon( + Icons.remove_circle_outline_outlined, + color: AppTheme.nearlyBlack, + )), + IconButton( + onPressed: () {}, + icon: const Icon( + Icons.add_circle_outline, + color: AppTheme.nearlyBlack, + )), + ], + ))), + sliver: Obx(() => !controller.groups[index].isExpanded.value + ? SliverList( + delegate: SliverChildBuilderDelegate( + (context, i) => SizedBox(), + childCount: 0, + )) + : SliverList( + delegate: SliverChildBuilderDelegate( + (context, i) => Slidable( + key: UniqueKey(), + endActionPane: ActionPane( + extentRatio: 0.2, + motion: const DrawerMotion(), + children: [ + SlidableAction( + padding: EdgeInsets.zero, + onPressed: (_) {}, + backgroundColor: AppTheme.dangerColor, + foregroundColor: Colors.white, + icon: Icons.delete, + label: 'Delete', + ), + ], + ), + child: Container( + padding: EdgeInsets.only( + left: ScreenAdaper.width(20), + top: ScreenAdaper.height(15), + bottom: ScreenAdaper.height(15), + ), + constraints: + BoxConstraints(minHeight: ScreenAdaper.height(100)), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(width: 1, color: Colors.grey[200]!), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + '${group.items[i].name}', + style: TextStyle( + fontSize: ScreenAdaper.sp(35)), + ), + ), + ], + ), + if (group.items[i].spec != null) + Row( + children: [ + Text( + '规格、型号: ', + style: subTextStyle, + ), + Text( + '${group.items[i].spec ?? ''}', + style: subTextStyle, + ), + ], + ), + if (group.items[i].unit != null) + Row( + children: [ + Text( + '单位: ', + style: subTextStyle, + ), + Text( + '${group.items[i].unit ?? ''}', + style: subTextStyle, + ), + ], + ), + if (group.items[i].remark != null) + Row( + children: [ + Text( + '备注: ', + style: subTextStyle, + ), + Expanded( + child: Text( + '${group.items[i].remark ?? ''}${group.items[i].remark ?? ''}${controller.products[i].remark ?? ''}', + overflow: TextOverflow.ellipsis, + style: subTextStyle, + ), + ), + ], + ), + ], + ), + ), + VerticalDivider(), + Container( + alignment: Alignment.center, + width: quantityWidth, + child: Text('13500'), + ), + Container( + alignment: Alignment.center, + width: unitPriceWidth, + child: Text('¥470000'), + ), + Container( + alignment: Alignment.center, + width: amountWidth, + child: Text('¥63450000'), + ), + ], + ), + ), + ), + childCount: !controller.groups[index].isExpanded.value + ? 0 + : group.items.length, ), - Spacer(), - IconButton( - onPressed: () { - ModalUtil.confirm(title: '确定要删除此组吗?', onConfirm: () {}); - }, - icon: Icon( - // Icons.add_circle_outline, - Icons.remove_circle_outline_outlined, - color: AppTheme.secondPrimaryTextColorWithBg, - )) - ], - )), - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, i) => Container( - padding: EdgeInsets.only( - left: ScreenAdaper.width(20), - top: ScreenAdaper.height(15), - bottom: ScreenAdaper.height(15)), - constraints: BoxConstraints(minHeight: ScreenAdaper.height(80)), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(width: 1, color: Colors.grey[200]!))), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row(children: [ - Expanded( - child: Text( - '${controller.products[i].name}', - style: TextStyle(fontSize: ScreenAdaper.sp(35)), - )) - ]), - if (controller.products[i].spec != null) - Row( - children: [ - Text( - '规格、型号: ', - style: subTextStyle, - ), - Text( - '${controller.products[i].spec ?? ''}', - style: subTextStyle, - ) - ], - ), - if (controller.products[i].unit != null) - Row( - children: [ - Text( - '单位: ', - style: subTextStyle, - ), - Text( - '${controller.products[i].unit ?? ''}', - style: subTextStyle, - ) - ], - ), - if (controller.products[i].remark != null) - Row( - children: [ - Text( - '备注: ', - style: subTextStyle, - ), - Expanded( - child: Text( - '${controller.products[i].remark ?? ''}${controller.products[i].remark ?? ''}${controller.products[i].remark ?? ''}', - overflow: TextOverflow.ellipsis, - style: subTextStyle, - )) - ], - ) - ], - )), - VerticalDivider(), - Container( - alignment: Alignment.center, - width: quantityWidth, - child: Text('13500'), - ), - Container( - alignment: Alignment.center, - width: unitPriceWidth, - child: Text('¥470000'), - ), - Container( - alignment: Alignment.center, - width: amountWidth, - child: Text('¥63450000'), - ) - ], - ), - ), - childCount: controller.products.length, - ), - ), + )), ); } } class SaleQuotationModel { final String name; - List? items; - SaleQuotationModel({required this.name, this.items}); + RxBool isExpanded = true.obs; + List items; + SaleQuotationModel({required this.name, required this.items}); } class SaleQuotationItemModel { @@ -224,7 +316,7 @@ class SaleQuotationItemModel { class SaleQuotationController extends GetxController { static SaleQuotationController get to => Get.find(); - List products = [ + RxList products = RxList([ SaleQuotationItemModel(name: '矿用本安型支架控制器', unit: '台', spec: 'ZDYZ-Z'), SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器'), SaleQuotationItemModel(name: '矿用隔爆兼本安型电源'), @@ -245,15 +337,21 @@ class SaleQuotationController extends GetxController { SaleQuotationItemModel(name: '矿用本安型LED信号灯'), SaleQuotationItemModel(name: '倾角传感器'), SaleQuotationItemModel(name: '各类安装附件'), - ]; - RxList groups = RxList([ - SaleQuotationModel(name: '中间过渡架电控部分'), - SaleQuotationModel(name: '端头架电控部分'), - SaleQuotationModel(name: '主阀部分'), - SaleQuotationModel(name: '自动反冲洗过滤器部分'), - SaleQuotationModel(name: '位移测量部分'), - SaleQuotationModel(name: '压力检测部分'), - SaleQuotationModel(name: '煤机定位部分'), - SaleQuotationModel(name: '姿态检测部分'), ]); + RxList groups = RxList([]); + @override + void onReady() { + groups.assignAll([ + SaleQuotationModel(name: '中间过渡架电控部分', items: products), + SaleQuotationModel(name: '端头架电控部分', items: products), + SaleQuotationModel(name: '主阀部分', items: products), + SaleQuotationModel(name: '自动反冲洗过滤器部分', items: products), + SaleQuotationModel(name: '位移测量部分', items: products), + SaleQuotationModel(name: '压力检测部分', items: products), + SaleQuotationModel(name: '煤机定位部分', items: products), + SaleQuotationModel(name: '姿态检测部分', items: products), + ]); + + super.onReady(); + } } diff --git a/lib/util/modal.util.dart b/lib/util/modal.util.dart index e312422..c3005b1 100644 --- a/lib/util/modal.util.dart +++ b/lib/util/modal.util.dart @@ -18,15 +18,13 @@ class ModalUtil { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(ScreenAdaper.sp(20)), ), - actionsPadding: EdgeInsets.symmetric(), - contentPadding: EdgeInsets.symmetric( - horizontal: ScreenAdaper.width(20), - vertical: ScreenAdaper.height(20)), - titlePadding: EdgeInsets.symmetric(), + actionsPadding: EdgeInsets.zero, + titlePadding: EdgeInsets.zero, title: title != null ? Container( - padding: - EdgeInsets.symmetric(vertical: ScreenAdaper.height(20)), + padding: EdgeInsets.symmetric( + vertical: ScreenAdaper.height(30), + horizontal: ScreenAdaper.width(30)), decoration: BoxDecoration( border: Border( bottom: BorderSide( @@ -36,7 +34,7 @@ class ModalUtil { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( + const Icon( Icons.error_outline, color: AppTheme.dangerColor, ), @@ -52,7 +50,6 @@ class ModalUtil { ]), ) : null, - // actionsAlignment: MainAxisAlignment.spaceEvenly, actions: [ Row( children: [ @@ -72,7 +69,7 @@ class ModalUtil { color: Colors.black, fontSize: ScreenAdaper.sp(40)), )), )), - VerticalDivider(), + const VerticalDivider(), Expanded( child: InkWell( onTap: () { diff --git a/lib/widgets/sk_appbar.dart b/lib/widgets/sk_appbar.dart index 235224c..4b89b45 100644 --- a/lib/widgets/sk_appbar.dart +++ b/lib/widgets/sk_appbar.dart @@ -2,11 +2,15 @@ import 'package:flutter/material.dart'; class SkAppbar extends StatelessWidget implements PreferredSizeWidget { final String title; - const SkAppbar({super.key, required this.title}); + final List? action; + const SkAppbar({super.key, required this.title, this.action}); @override Widget build(BuildContext context) { - return AppBar(title: Text(title)); + return AppBar( + title: Text(title), + actions: action, + ); } @override diff --git a/pubspec.lock b/pubspec.lock index 80834f4..387fcd6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -366,6 +366,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.9.0" + flutter_slidable: + dependency: "direct main" + description: + name: flutter_slidable + sha256: "673403d2eeef1f9e8483bd6d8d92aae73b1d8bd71f382bc3930f699c731bc27c" + url: "https://pub.dev" + source: hosted + version: "3.1.0" flutter_staggered_grid_view: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 416fa25..7da03b9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: flutter_svg: ^2.0.10+1 flutter_sticky_header: ^0.6.5 data_table_2: ^2.5.12 + flutter_slidable: ^3.1.0 dev_dependencies: flutter_test: sdk: flutter diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index d0a2706..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:sk_base_mobile/index.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const IndexPage()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}