diff --git a/assets/icons/facility.png b/assets/icons/facility.png new file mode 100644 index 00000000..16af290d Binary files /dev/null and b/assets/icons/facility.png differ diff --git a/lib/const/resource.dart b/lib/const/resource.dart index a67013de..4f561a1e 100644 --- a/lib/const/resource.dart +++ b/lib/const/resource.dart @@ -158,6 +158,9 @@ class R { /// ![preview](file:///Users/akufe/Documents/aku_community/assets/icons/consult.png) static const String ASSETS_ICONS_CONSULT_PNG = 'assets/icons/consult.png'; + /// ![preview](file:///Users/akufe/Documents/aku_community/assets/icons/facility.png) + static const String ASSETS_ICONS_FACILITY_PNG = 'assets/icons/facility.png'; + /// ![preview](file:///Users/akufe/Documents/aku_community/assets/icons/file.png) static const String ASSETS_ICONS_FILE_PNG = 'assets/icons/file.png'; diff --git a/lib/constants/api.dart b/lib/constants/api.dart index df5bd990..f6d25e02 100644 --- a/lib/constants/api.dart +++ b/lib/constants/api.dart @@ -1,6 +1,6 @@ class API { ///HOST - static const String host = 'http://test.kaidalai.cn'; + static const String host = 'http://39.103.177.88:8804'; ///接口基础地址 static const String baseURL = '$host/IntelligentCommunity/app'; @@ -296,4 +296,16 @@ class _Facility { ///设施预约:查询所有的设施预约 (包含搜索条件) String get appointment => '/user/facilitiesAppointment/list'; + + ///设施预约:添加设施预约 + String get add => '/user/facilitiesAppointment/insert'; + + ///设施预约:扫码签到 + String get scan => '/user/facilitiesAppointment/signIn'; + + ///设施预约:取消预约 + String get cancel => '/user/facilitiesAppointment/cancel'; + + ///设施预约:结束使用 + String get stop => '/user/facilitiesAppointment/useStop'; } diff --git a/lib/models/facility/facility_appointment_model.dart b/lib/models/facility/facility_appointment_model.dart index f6e2a14c..a541f8b4 100644 --- a/lib/models/facility/facility_appointment_model.dart +++ b/lib/models/facility/facility_appointment_model.dart @@ -1,3 +1,4 @@ +import 'package:aku_community/base/base_style.dart'; import 'package:flustars/flustars.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -39,7 +40,7 @@ class FacilityAppointmentModel { Color get statusColor { switch (status) { case 1: - return Colors.blue; + return kPrimaryColor; case 2: return Color(0xFF2576E5); case 3: @@ -56,7 +57,7 @@ class FacilityAppointmentModel { String get statusValue { switch (status) { case 1: - return '未签到'; + return '预约成功'; case 2: return '签到成功'; case 3: diff --git a/lib/ui/common/qr_scan.dart b/lib/ui/common/qr_scan.dart new file mode 100644 index 00000000..3d89d0af --- /dev/null +++ b/lib/ui/common/qr_scan.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; + +class BeeQR { + static Future scan() async { + return await Get.to(() => _QRScanPage()); + } +} + +class _QRScanPage extends StatefulWidget { + _QRScanPage({Key? key}) : super(key: key); + + @override + __QRScanPageState createState() => __QRScanPageState(); +} + +class __QRScanPageState extends State<_QRScanPage> { + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + QRViewController? _controller; + bool _doneTag = false; + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: SizedBox( + width: double.infinity, + height: double.infinity, + child: QRView( + key: qrKey, + overlay: QrScannerOverlayShape( + borderRadius: 8, + ), + onQRViewCreated: (controller) { + _controller = controller; + controller.scannedDataStream.listen((event) { + if (!_doneTag) { + _doneTag = true; + Get.back(result: event.code); + } + }); + }, + ), + ), + ); + } +} diff --git a/lib/ui/community/facility/facility_appointment_card.dart b/lib/ui/community/facility/facility_appointment_card.dart index 58151089..37e89d43 100644 --- a/lib/ui/community/facility/facility_appointment_card.dart +++ b/lib/ui/community/facility/facility_appointment_card.dart @@ -1,11 +1,20 @@ +import 'package:aku_community/base/base_style.dart'; +import 'package:aku_community/constants/api.dart'; import 'package:aku_community/models/facility/facility_appointment_model.dart'; +import 'package:aku_community/ui/common/qr_scan.dart'; +import 'package:aku_community/utils/network/net_util.dart'; import 'package:aku_community/widget/bee_divider.dart'; +import 'package:bot_toast/bot_toast.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:aku_community/utils/headers.dart'; +import 'package:get/get.dart'; class FacilityAppointmentCard extends StatelessWidget { final FacilityAppointmentModel model; - const FacilityAppointmentCard({Key? key, required this.model}) + final VoidCallback onUpdate; + const FacilityAppointmentCard( + {Key? key, required this.model, required this.onUpdate}) : super(key: key); Widget _renderTile({ @@ -21,7 +30,12 @@ class FacilityAppointmentCard extends StatelessWidget { width: 40.w, ), 12.wb, - Text(name), + Text( + name, + style: TextStyle( + color: ktextSubColor, + ), + ), Spacer(), Text(subTitle), ], @@ -30,9 +44,95 @@ class FacilityAppointmentCard extends StatelessWidget { Widget _renderButton() { var showTip = model.status == 1 || model.status == 2; + late Widget button; + switch (model.status) { + case 1: + //1.未签到(预约时间前30分钟显示扫码签到,之前为取消预约), + if (model.appointmentStart == null) button = SizedBox(); + int diffTime = + model.appointmentStart!.difference(DateTime.now()).inMinutes; + bool inTime = diffTime >= 0 && diffTime <= 30; + if (inTime) + button = _FacilityButton( + onPressed: () async { + var result = await BeeQR.scan(); + if (result != null) { + final cancel = BotToast.showLoading(); + await NetUtil().get( + API.manager.facility.scan, + params: {'appointmentCode': result}, + showMessage: true, + ); + cancel(); + onUpdate(); + } + }, + text: '扫码签到', + ); + else + button = _FacilityButton( + onPressed: () async { + bool? result = await Get.dialog( + CupertinoAlertDialog( + title: Text('取消预约'), + content: Text('您确定要取消预约吗?'), + actions: [ + CupertinoDialogAction( + child: Text('先等等'), + onPressed: () => Get.back(), + ), + CupertinoDialogAction( + child: Text('取消预约'), + onPressed: () => Get.back(result: true), + ), + ], + ), + ); + if (result == true) { + final cancel = BotToast.showLoading(); + await NetUtil().get( + API.manager.facility.cancel, + params: {'facilitiesAppointmentId': model.id}, + showMessage: true, + ); + cancel(); + onUpdate(); + } + }, + text: '取消预约', + ); + break; + case 2: + button = _FacilityButton( + onPressed: () async { + final cancel = BotToast.showLoading(); + await NetUtil().get( + API.manager.facility.stop, + params: {'facilitiesAppointmentId': model.id}, + showMessage: true, + ); + cancel(); + onUpdate(); + }, + text: '使用结束', + ); + break; + + default: + button = SizedBox(); + } return Row( children: [ - if (showTip) Text('请在预约时间前30分钟内到场扫码'), + if (showTip) + Text( + '请在预约时间前30分钟内到场扫码', + style: TextStyle( + color: ktextSubColor, + fontSize: 24.sp, + ), + ), + Spacer(), + button, ], ); } @@ -92,3 +192,39 @@ class FacilityAppointmentCard extends StatelessWidget { ); } } + +class _FacilityButton extends StatelessWidget { + final Color color; + final Color textColor; + final VoidCallback onPressed; + final String text; + final bool outline; + const _FacilityButton({ + Key? key, + this.color = kPrimaryColor, + required this.onPressed, + required this.text, + this.outline = false, + this.textColor = ktextPrimary, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialButton( + color: outline ? null : color, + shape: StadiumBorder(), + elevation: 0, + height: 60.w, + minWidth: 168.w, + padding: EdgeInsets.zero, + onPressed: onPressed, + child: Text( + text, + style: TextStyle( + color: textColor, + fontSize: 26.sp, + ), + ), + ); + } +} diff --git a/lib/ui/community/facility/facility_appointment_page.dart b/lib/ui/community/facility/facility_appointment_page.dart index 757899f7..b5793573 100644 --- a/lib/ui/community/facility/facility_appointment_page.dart +++ b/lib/ui/community/facility/facility_appointment_page.dart @@ -1,8 +1,11 @@ import 'package:aku_community/ui/community/facility/facility_appointment_view.dart'; +import 'package:aku_community/ui/community/facility/facility_preview_page.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:aku_community/widget/bee_scaffold.dart'; import 'package:aku_community/widget/tab_bar/bee_tab_bar.dart'; +import 'package:get/get.dart'; class FacilityAppointmentPage extends StatefulWidget { FacilityAppointmentPage({Key? key}) : super(key: key); @@ -31,6 +34,12 @@ class _FacilityAppointmentPageState extends State Widget build(BuildContext context) { return BeeScaffold( title: '设施预约', + actions: [ + IconButton( + icon: Icon(CupertinoIcons.add_circled), + onPressed: () => Get.to(() => FacilityPreorderPage()), + ), + ], appBarBottom: BeeTabBar( controller: _tabController, tabs: ['我的预约', '历史预约'], diff --git a/lib/ui/community/facility/facility_appointment_view.dart b/lib/ui/community/facility/facility_appointment_view.dart index c9a3796e..571a5ea0 100644 --- a/lib/ui/community/facility/facility_appointment_view.dart +++ b/lib/ui/community/facility/facility_appointment_view.dart @@ -50,7 +50,12 @@ class _FacilityAppointmentViewState extends State { return ListView.separated( padding: EdgeInsets.all(32.w), itemBuilder: (context, index) { - return FacilityAppointmentCard(model: items[index]); + return FacilityAppointmentCard( + model: items[index], + onUpdate: () { + _refreshController.callRefresh(); + }, + ); }, separatorBuilder: (_, __) => 32.hb, itemCount: items.length, diff --git a/lib/ui/community/facility/facility_page.dart b/lib/ui/community/facility/facility_page.dart deleted file mode 100644 index 73791eb8..00000000 --- a/lib/ui/community/facility/facility_page.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:aku_community/widget/bee_scaffold.dart'; - -class FacilityPage extends StatefulWidget { - FacilityPage({Key? key}) : super(key: key); - - @override - _FacilityPageState createState() => _FacilityPageState(); -} - -class _FacilityPageState extends State { - @override - Widget build(BuildContext context) { - return BeeScaffold( - title: '设施预约', - actions: [ - IconButton( - icon: Icon(Icons.add_circle_outline_rounded, color: Colors.black87), - onPressed: () {}, - ), - ], - ); - } -} diff --git a/lib/ui/community/facility/facility_preview_page.dart b/lib/ui/community/facility/facility_preview_page.dart new file mode 100644 index 00000000..0df61fe0 --- /dev/null +++ b/lib/ui/community/facility/facility_preview_page.dart @@ -0,0 +1,177 @@ +import 'package:aku_community/base/base_style.dart'; +import 'package:aku_community/constants/api.dart'; +import 'package:aku_community/constants/app_theme.dart'; +import 'package:aku_community/provider/app_provider.dart'; +import 'package:aku_community/ui/community/facility/pick_facility_page.dart'; +import 'package:aku_community/ui/profile/house/pick_my_house_page.dart'; +import 'package:aku_community/utils/headers.dart'; +import 'package:aku_community/utils/network/net_util.dart'; +import 'package:aku_community/widget/bee_divider.dart'; +import 'package:aku_community/widget/bee_scaffold.dart'; +import 'package:aku_community/widget/buttons/bottom_button.dart'; +import 'package:aku_community/widget/picker/bee_date_picker.dart'; +import 'package:bot_toast/bot_toast.dart'; +import 'package:flustars/flustars.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:provider/provider.dart'; +import 'package:aku_community/models/facility/facility_type_model.dart'; + +class FacilityPreorderPage extends StatefulWidget { + FacilityPreorderPage({Key? key}) : super(key: key); + + @override + _FacilityPreorderPageState createState() => _FacilityPreorderPageState(); +} + +class _FacilityPreorderPageState extends State { + FacilityTypeModel? typeModel; + DateTime? startDate; + DateTime? endDate; + + bool get canTap => startDate != null && endDate != null && typeModel != null; + @override + Widget build(BuildContext context) { + final appProvider = Provider.of(context); + return BeeScaffold( + title: '添加预订', + bodyColor: Colors.white, + systemStyle: SystemStyle.yellowBottomBar, + body: ListView( + padding: EdgeInsets.symmetric(vertical: 32.w), + children: [ + Text('业主房屋').pSymmetric(h: 32.w), + ListTile( + leading: Image.asset( + R.ASSETS_ICONS_HOUSE_PNG, + height: 60.w, + width: 60.w, + ), + onTap: () => Get.to(() => PickMyHousePage()), + title: Text(S.of(context)!.tempPlotName), + subtitle: Text(appProvider.selectedHouse?.roomName ?? '选择房间'), + trailing: Icon(CupertinoIcons.chevron_forward), + ), + BeeDivider( + indent: 32.w, + endIndent: 32.w, + ), + 32.hb, + Text('选择设施').pSymmetric(h: 32.w), + ListTile( + leading: Image.asset( + R.ASSETS_ICONS_FACILITY_PNG, + height: 60.w, + width: 60.w, + ), + onTap: () async { + FacilityTypeModel? model = await Get.to(() => PickFacilityPage()); + if (model != null) typeModel = model; + setState(() {}); + }, + title: Text(S.of(context)!.tempPlotName), + subtitle: Text(typeModel?.name ?? '选择设施'), + trailing: Icon(CupertinoIcons.chevron_forward), + ), + BeeDivider( + indent: 32.w, + endIndent: 32.w, + ), + 32.hb, + Text('预约时间').pSymmetric(h: 32.w), + SizedBox( + height: 120.w, + child: Row( + children: [ + MaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + height: 120.w, + onPressed: () async { + DateTime? date = await BeeDatePicker.pick( + DateTime.now(), + mode: CupertinoDatePickerMode.dateAndTime, + min: DateTime.now().subtract(Duration(seconds: 1)), + max: DateTime.now().add(Duration(days: 30)), + ); + if (date != null) { + startDate = date; + setState(() {}); + } + }, + child: Text( + startDate == null + ? '请选择开始时间' + : DateUtil.formatDate( + startDate, + format: 'yyyy-MM-dd HH:mm', + ), + style: TextStyle( + color: ktextSubColor, + ), + ), + ).expand(), + Icon(Icons.arrow_forward), + MaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + height: 120.w, + onPressed: () async { + DateTime? date = await BeeDatePicker.pick( + startDate == null ? DateTime.now() : startDate!, + min: startDate == null + ? DateTime.now().subtract(Duration(seconds: 1)) + : startDate!, + max: startDate == null + ? DateTime.now().add(Duration(days: 1)) + : (startDate!).add(Duration(days: 1)), + mode: CupertinoDatePickerMode.dateAndTime, + ); + if (date != null) { + endDate = date; + setState(() {}); + } + }, + child: Text( + endDate == null + ? '请选择结束时间' + : DateUtil.formatDate( + endDate, + format: 'yyyy-MM-dd HH:mm', + ), + style: TextStyle( + color: ktextSubColor, + ), + ), + ).expand(), + ], + ), + ), + BeeDivider( + indent: 32.w, + endIndent: 32.w, + ), + ], + ), + bottomNavi: BottomButton( + onPressed: canTap + ? () async { + final cancel = BotToast.showLoading(); + var model = await NetUtil().post( + API.manager.facility.add, + params: { + 'estateId': appProvider.selectedHouse?.estateId ?? 0, + 'facilitiesManageId': typeModel!.id, + 'appointmentStartDate': NetUtil.getDate(startDate!), + 'appointmentEndDate': NetUtil.getDate(endDate!), + }, + showMessage: true, + ); + cancel(); + if (model.status == true) Get.back(result: true); + } + : null, + child: Text('确认提交'), + ), + ); + } +} diff --git a/lib/ui/community/facility/facility_type_card.dart b/lib/ui/community/facility/facility_type_card.dart index 597adb30..4c25e448 100644 --- a/lib/ui/community/facility/facility_type_card.dart +++ b/lib/ui/community/facility/facility_type_card.dart @@ -5,6 +5,7 @@ import 'package:aku_community/constants/api.dart'; import 'package:aku_community/model/common/img_model.dart'; import 'package:aku_community/models/facility/facility_type_model.dart'; import 'package:aku_community/utils/headers.dart'; +import 'package:get/get.dart'; class FacilityTypeCard extends StatelessWidget { final FacilityTypeModel model; @@ -13,6 +14,8 @@ class FacilityTypeCard extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialButton( + color: Colors.white, + elevation: 0, padding: EdgeInsets.zero, clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( @@ -64,29 +67,13 @@ class FacilityTypeCard extends StatelessWidget { ], ), ), - MaterialButton( - height: 52.w, - minWidth: 168.w, - padding: EdgeInsets.zero, - elevation: 0, - shape: StadiumBorder(), - color: kPrimaryColor, - onPressed: () {}, - child: Text( - '填写预约', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 26.sp, - ), - ), - ), 32.wb, ], ), 24.hb, ], ), - onPressed: () {}, + onPressed: () => Get.back(result: model), ); } } diff --git a/lib/widget/picker/bee_date_picker.dart b/lib/widget/picker/bee_date_picker.dart index 70a02351..04e24b7a 100644 --- a/lib/widget/picker/bee_date_picker.dart +++ b/lib/widget/picker/bee_date_picker.dart @@ -10,11 +10,14 @@ class BeeDatePicker { static Future pick( DateTime initDate, { CupertinoDatePickerMode mode = CupertinoDatePickerMode.date, + DateTime? min, + DateTime? max, }) async { return await Get.bottomSheet(_BeeDatePicker( date: initDate, mode: mode, - min: DateTime.now().subtract(Duration(days: 1)), + min: min ?? DateTime.now().subtract(Duration(days: 1)), + max: max, )); } diff --git a/pubspec.lock b/pubspec.lock index e3bbbfb4..c874fc0e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -858,6 +858,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" + qr_code_scanner: + dependency: "direct main" + description: + name: qr_code_scanner + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.0" qr_flutter: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 55203378..27c0c8bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,6 +67,7 @@ dependencies: collection: ^1.15.0 json_annotation: ^4.0.1 waterfall_flow: ^3.0.1 + qr_code_scanner: ^0.4.0 dev_dependencies: flutter_test: