diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index dce4021..533f5bb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,7 +9,8 @@ - + + createState() => _AppState(); +} + +class _AppState extends State { @override Widget build(BuildContext context) { return ScreenUtilInit( @@ -48,20 +55,11 @@ class App extends StatelessWidget { fallbackLocale: const Locale('zh', 'CN'), // 必须设置 fallback 没有该语言时使用 getPages: AppPages.pages, - initialRoute: _checkIsLogin(), + initialRoute: widget.initialRoute, debugShowCheckedModeBanner: false, builder: EasyLoading.init(), ); }, ); } - - String _checkIsLogin() { - var token = SharedPreferencesUtils.getString(CacheKeys.token); - if (token != null && token.isNotEmpty) { - return AppRoutes.main; - } else { - return AppRoutes.login; - } - } } diff --git a/lib/base/app_permission.dart b/lib/base/app_permission.dart index 57a990c..2d02f33 100644 --- a/lib/base/app_permission.dart +++ b/lib/base/app_permission.dart @@ -20,4 +20,30 @@ class AppPermission { } return true; } + + static Future requestPermission({ + required Permission permission, + }) async { + var status = await permission.request(); + return status == PermissionStatus.granted; + } + + static Future requestNotificationPermission() async { + final PermissionStatus status = await Permission.notification.request(); + + if (status == PermissionStatus.granted) { + print("通知权限已授予"); + return true; + } else if (status == PermissionStatus.denied) { + // 第一次被拒绝,可以再次请求 + return false; + } else if (status.isPermanentlyDenied) { + // 用户勾选了“不再提示”或被永久拒绝 + print("权限被永久拒绝,跳转到设置页面"); + openAppSettings(); // 跳转到应用设置页,手动开启 + return false; + } + + return false; + } } diff --git a/lib/common/constant/app_images.dart b/lib/common/constant/app_images.dart new file mode 100644 index 0000000..c464f2d --- /dev/null +++ b/lib/common/constant/app_images.dart @@ -0,0 +1,6 @@ +class AppImages{ + + static const String iconOneKeyDoor = 'assets/icon/icon_one_key_door.png'; + static const String iconOneKeyDoorKey = 'assets/icon/icon_one_key_door_key.png'; + static const String bgOneKeyDoor = 'assets/images/bg_one_key_door.png'; +} \ No newline at end of file diff --git a/lib/flavors.dart b/lib/flavors.dart index 99d932f..e64b804 100644 --- a/lib/flavors.dart +++ b/lib/flavors.dart @@ -32,7 +32,7 @@ class F { static String get apiHost { switch (appFlavor) { case Flavor.skyDev: - return 'http://192.168.1.136/api'; + return 'http://10.0.2.2/api'; case Flavor.skyPre: return 'https://loacl.work.star-lock.cn/api'; case Flavor.skyRelease: diff --git a/lib/main.dart b/lib/main.dart index 6bc3646..2606b1a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:starwork_flutter/base/app_initialization.dart'; +import 'package:starwork_flutter/common/constant/cache_keys.dart'; +import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart'; +import 'package:starwork_flutter/routes/app_routes.dart'; import 'app.dart'; import 'flavors.dart'; @@ -14,5 +17,16 @@ void main() async { await AppInitialization.initializeApp(); - runApp(const App()); + var initRoute = await _handleInitialRoute(); + + runApp(App(initialRoute: initRoute)); +} + +Future _handleInitialRoute() async { + var token = await SharedPreferencesUtils.getString(CacheKeys.token); + if (token != null && token.isNotEmpty) { + return AppRoutes.main; + } else { + return AppRoutes.login; + } } diff --git a/lib/views/home/home_controller.dart b/lib/views/home/home_controller.dart index a7f7638..69c0622 100644 --- a/lib/views/home/home_controller.dart +++ b/lib/views/home/home_controller.dart @@ -1,3 +1,5 @@ +import 'package:carousel_slider/carousel_controller.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:starwork_flutter/base/app_permission.dart'; @@ -6,11 +8,39 @@ import 'package:starwork_flutter/base/base_controller.dart'; class HomeController extends BaseController { final isOpenNotificationPermission = false.obs; + var carouselCurrentIndex = 0.obs; + + // 渐变颜色列表 + final List gradientColors = [ + const Color(0xFFBFCBEF), // #bfcbef + const Color(0xFFECBE9B), // 原来的颜色 + const Color(0xFFD5F3D5), // 清新绿色 + const Color(0xFFFFB6C1), // 温柔粉色 + ]; + + // 当前渐变颜色 + var currentGradientColor = const Color(0xFFBFCBEF).obs; + @override void onInit() async { super.onInit(); isOpenNotificationPermission.value = await AppPermission.checkPermission( permission: Permission.notification, ); + + // 监听轮播图切换,更新渐变颜色 + carouselCurrentIndex.listen((index) { + updateGradientColor(index); + }); + } + + // 根据轮播图索引更新渐变颜色 + void updateGradientColor(int index) { + if (index < gradientColors.length) { + currentGradientColor.value = gradientColors[index]; + } else { + // 如果索引超出颜色数量,使用模运算轮环 + currentGradientColor.value = gradientColors[index % gradientColors.length]; + } } } diff --git a/lib/views/home/home_view.dart b/lib/views/home/home_view.dart index c43d8cb..e7e120b 100644 --- a/lib/views/home/home_view.dart +++ b/lib/views/home/home_view.dart @@ -1,86 +1,200 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:starwork_flutter/base/app_permission.dart'; +import 'package:starwork_flutter/views/home/widget/home_attendance_chart_area_widget.dart'; +import 'package:starwork_flutter/views/home/widget/home_carousel_area_widget.dart'; +import 'package:starwork_flutter/views/home/widget/home_function_list_area_widget.dart'; +import 'package:starwork_flutter/views/home/widget/home_left_drawer_widget.dart'; +import 'package:starwork_flutter/views/home/widget/home_one_button_door_opening_widget.dart'; +import 'package:starwork_flutter/views/home/widget/home_statistics_row_widget.dart'; +import 'package:starwork_flutter/views/home/widget/home_team_notice_row_widget.dart'; import 'home_controller.dart'; class HomeView extends GetView { const HomeView({super.key}); + static final GlobalKey _scaffoldKey = + GlobalKey(); + @override Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Container( - width: 1.sw, - padding: EdgeInsets.symmetric(horizontal: 15.w), - child: Column( - children: [ - _buildPageHead(), - _buildSystemNotificationPermissionRow(), + return Obx( + () => Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, // 渐变起点:顶部中心 + end: Alignment.bottomCenter, // 渐变终点:底部中心 + colors: [ + controller.currentGradientColor.value, // 动态颜色 + const Color(0xFFF6F7FB), // 底部颜色保持不变 ], + stops: const [0.1, 1.0], // 第一个颜色到10%,然后第二个颜色开始直到100% + ), + ), + child: Scaffold( + key: _scaffoldKey, + backgroundColor: Colors.transparent, + body: SafeArea( + child: Container( + width: 1.sw, + padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 4.h), + child: Column( + children: [ + // 固定的上半部分 + _buildPageHead(context), + SizedBox( + height: 10.h, + ), + _buildSystemNotificationPermissionRow(), + + // 可滚动的下半部分 + Expanded( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + HomeCarouselAreaWidget( + carouselCurrentIndex: + controller.carouselCurrentIndex, + ), + HomeTeamNoticeRowWidget(), + HomeStatisticsRowWidget( + personCount: 12, + deviceCount: 1, + ), + SizedBox(height: 10.h), + HomeFunctionListAreaWidget(), + SizedBox( + height: 10.h, + ), + HomeOnButtonDoorOpeningWidget( + doorList: const [ + '主门门禁', + '车库门禁', + '后门门禁', + '单元门门禁', + ], + ), + SizedBox( + height: 10.h, + ), + HomeAttendanceChartAreaWidget(), + SizedBox( + height: 10.h, + ), + ], + ), + ), + ), + ], + ), + ), + ), + drawer: HomeLeftDrawerWidget( + teamList: ['家庭群组', '测试团队1', '测试团队2'], ), ), ), ); } - _buildPageHead() { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '19104656的互联', - style: TextStyle( - fontSize: 18.sp, - fontWeight: FontWeight.w500, + _buildPageHead(BuildContext context) { + return GestureDetector( + onTap: () { + _scaffoldKey.currentState?.openDrawer(); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '19104656的互联', + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w500, + ), ), - ), - Icon( - Icons.arrow_right_rounded, - size: 24.sp, - ) - ], - ), - Icon( - Icons.add_circle_outline_rounded, - size: 24.sp, - ), - ], + Icon( + Icons.arrow_right_rounded, + size: 22.sp, + ) + ], + ), + Icon( + Icons.add_circle_outline, + size: 22.sp, + ), + ], + ), ); } _buildSystemNotificationPermissionRow() { - return Visibility( - visible: !controller.isOpenNotificationPermission.value, - child: Container( - decoration: const BoxDecoration( - color: Color(0xFFfdefdf), - ), - child: Row( - children: [ - Text( - '系统通知未开启,报警消息无法通知'.tr, - style: TextStyle( - color: Color(0xFFea8720), - ), + return Obx( + () => Visibility( + visible: !controller.isOpenNotificationPermission.value, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h), + decoration: BoxDecoration( + color: const Color(0xFFFEF2E5), + borderRadius: BorderRadius.all( + Radius.circular(8.r), ), - Container( - child: Text( - '去开启'.tr, + ), + margin: EdgeInsets.symmetric(vertical: 8.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '系统通知未开启,报警消息无法通知'.tr, style: TextStyle( - color: Colors.white, + color: const Color(0xFFEE9846), + fontSize: 12.sp, ), ), - ) - ], + const Spacer(), + GestureDetector( + onTap: () async { + controller.isOpenNotificationPermission.value = + await AppPermission.requestNotificationPermission(); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 4.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(4.r), + ), + ), + child: Text( + '去开启'.tr, + style: TextStyle( + color: const Color(0xFFEE9846), + fontSize: 12.sp, + ), + ), + ), + ), + SizedBox( + width: 14.w, + ), + Icon( + Icons.cancel, + color: const Color(0xFFEE9846), + size: 18.sp, + ) + ], + ), ), ), ); diff --git a/lib/views/home/widget/home_attendance_chart_area_widget.dart b/lib/views/home/widget/home_attendance_chart_area_widget.dart new file mode 100644 index 0000000..612a5e3 --- /dev/null +++ b/lib/views/home/widget/home_attendance_chart_area_widget.dart @@ -0,0 +1,243 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/common/constant/app_images.dart'; +import 'dart:math' as math; + +class HomeAttendanceChartAreaWidget extends StatefulWidget { + const HomeAttendanceChartAreaWidget({super.key}); + + @override + State createState() => + _HomeAttendanceChartAreaWidgetState(); +} + +class _HomeAttendanceChartAreaWidgetState + extends State { + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8.r), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + _buildTitle(), + SizedBox(height: 16.h), + _buildContent(), + SizedBox(height: 16.h), + ], + ), + ); + } + + Widget _buildTitle() { + return Row( + children: [ + Image.asset( + AppImages.iconOneKeyDoor, + width: 16.w, + height: 16.h, + ), + SizedBox(width: 8.w), + Text( + '考勤'.tr, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + const Spacer(), + Icon( + Icons.arrow_forward_ios, + size: 14.sp, + color: const Color(0xFFBFBFBF), + ), + ], + ); + } + + Widget _buildContent() { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 左侧圆形进度图表 + _buildCircularChart(), + SizedBox(width: 24.w), + // 右侧统计信息 + _buildStatistics(), + ], + ); + } + + Widget _buildCircularChart() { + return SizedBox( + width: 80.w, + height: 80.w, + child: Stack( + alignment: Alignment.center, + children: [ + // 背景圆环 + CustomPaint( + size: Size(80.w, 80.w), + painter: CircleProgressPainter( + progress: 0.5, // 0% 进度 + strokeWidth: 8.w, + backgroundColor: const Color(0xFFF5F5F5), + progressColor: const Color(0xFF4A90E2), + ), + ), + // 中心文字 + Text( + '50%', + style: TextStyle( + fontSize: 20.sp, + fontWeight: FontWeight.w600, + color: const Color(0xFF333333), + ), + ), + ], + ), + ); + } + + Widget _buildStatistics() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 今日出勤率 + Row( + children: [ + Text( + '今日出勤率:', + style: TextStyle( + fontSize: 14.sp, + color: const Color(0xFF333333), + ), + ), + Text( + '0/14', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + color: const Color(0xFF333333), + ), + ), + ], + ), + SizedBox(height: 12.h), + // 未打卡和迟到统计 + Row( + children: [ + // 未打卡 + Row( + children: [ + Text( + '未打卡:', + style: TextStyle( + fontSize: 14.sp, + color: const Color(0xFF666666), + ), + ), + Text( + '14', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + color: const Color(0xFFFF9500), + ), + ), + ], + ), + SizedBox(width: 24.w), + // 迟到 + Row( + children: [ + Text( + '迟到:', + style: TextStyle( + fontSize: 14.sp, + color: const Color(0xFF666666), + ), + ), + Text( + '0', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + color: const Color(0xFFFF4D4F), + ), + ), + ], + ), + ], + ), + ], + ); + } +} + +// 自定义圆形进度画笔 +class CircleProgressPainter extends CustomPainter { + final double progress; + final double strokeWidth; + final Color backgroundColor; + final Color progressColor; + + CircleProgressPainter({ + required this.progress, + required this.strokeWidth, + required this.backgroundColor, + required this.progressColor, + }); + + @override + void paint(Canvas canvas, Size size) { + final center = Offset(size.width / 2, size.height / 2); + final radius = (size.width - strokeWidth) / 2; + + // 绘制背景圆环 + final backgroundPaint = Paint() + ..color = backgroundColor + ..strokeWidth = strokeWidth + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round; + + canvas.drawCircle(center, radius, backgroundPaint); + + // 绘制进度圆弧 + if (progress > 0) { + final progressPaint = Paint() + ..color = progressColor + ..strokeWidth = strokeWidth + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round; + + final sweepAngle = 2 * math.pi * progress; + canvas.drawArc( + Rect.fromCircle(center: center, radius: radius), + -math.pi / 2, // 从顶部开始 + sweepAngle, + false, + progressPaint, + ); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return oldDelegate != this; + } +} diff --git a/lib/views/home/widget/home_carousel_area_widget.dart b/lib/views/home/widget/home_carousel_area_widget.dart new file mode 100644 index 0000000..41511f4 --- /dev/null +++ b/lib/views/home/widget/home_carousel_area_widget.dart @@ -0,0 +1,96 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class HomeCarouselAreaWidget extends StatefulWidget { + const HomeCarouselAreaWidget({ + super.key, + required this.carouselCurrentIndex, + }); + + final RxInt carouselCurrentIndex; + + @override + State createState() => _HomeCarouselAreaWidgetState(); +} + +class _HomeCarouselAreaWidgetState extends State { + final List imgList = [ + 'assets/images/mockImage.jpg', + 'assets/images/mockImage.jpg', + 'assets/images/mockImage.jpg', + ]; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + CarouselSlider( + options: CarouselOptions( + height: 68.h, + autoPlay: true, + autoPlayInterval: const Duration(seconds: 5), + autoPlayAnimationDuration: const Duration(milliseconds: 800), + viewportFraction: 1.0, + onPageChanged: (index, reason) { + widget.carouselCurrentIndex.value = index; + }, + ), + items: imgList.map( + (i) { + return Builder( + builder: (BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.asset( + width: double.infinity, + height: 68.h, + i, + fit: BoxFit.fill, + ), + ); + }, + ); + }, + ).toList(), + ), + // 分段横线指示器 + Positioned( + left: 10.w, + bottom: 6.h, + child: Obx( + () => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: imgList.asMap().entries.map((entry) { + int index = entry.key; + int length = imgList.length; + + // 判断是否为第一个或最后一个 + bool isFirst = index == 0; + bool isLast = index == length - 1; + return Container( + width: 8.w, + height: 2.0.h, + decoration: BoxDecoration( + color: widget.carouselCurrentIndex.value == index + ? Colors.white + : Colors.grey[400], + // 精确控制圆角 + borderRadius: BorderRadius.only( + topLeft: Radius.circular(isFirst ? 2.0 : 0), + bottomLeft: Radius.circular(isFirst ? 2.0 : 0), + topRight: Radius.circular(isLast ? 2.0 : 0), + bottomRight: Radius.circular(isLast ? 2.0 : 0), + ), + ), + ); + }).toList(), + ), + ), + ), + ], + ); + } +} diff --git a/lib/views/home/widget/home_function_list_area_widget.dart b/lib/views/home/widget/home_function_list_area_widget.dart new file mode 100644 index 0000000..79dea13 --- /dev/null +++ b/lib/views/home/widget/home_function_list_area_widget.dart @@ -0,0 +1,245 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class HomeFunctionListAreaWidget extends StatefulWidget { + HomeFunctionListAreaWidget({super.key}); + + @override + State createState() => + _HomeFunctionListAreaWidgetState(); +} + +class _HomeFunctionListAreaWidgetState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 6, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 10.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(8.r), + ), + ), + child: Column( + children: [ + _buildTabBar(), + SizedBox(height: 12.h), + _buildTabBarView(), + ], + ), + ); + } + + Widget _buildTabBar() { + final List functionOptionList = [ + '人员通行', + '考勤', + '审批', + '可视对讲', + '信息发布', + '其他应用', + ]; + + return Row( + children: [ + Expanded( + child: TabBar( + controller: _tabController, + isScrollable: true, + labelColor: Colors.black87, + unselectedLabelColor: Colors.grey, + labelStyle: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + ), + indicatorColor: Colors.black87, + indicatorWeight: 0.5.h, + indicatorSize: TabBarIndicatorSize.label, + labelPadding: EdgeInsets.only(right: 16.w), + // 只保留右边距,左边紧贴 + tabAlignment: TabAlignment.start, + // 从左边开始排列 + padding: EdgeInsets.zero, + // 移除TabBar的默认内边距 + dividerColor: Colors.transparent, + // 移除灰色下边框 + tabs: functionOptionList.map((title) => Tab(text: title)).toList(), + ), + ), + // 右侧固定菜单图标 + Container( + padding: EdgeInsets.only( + left: 8.w, + ), + child: GestureDetector( + onTap: () { + // 处理菜单点击事件 + print('菜单被点击'); + }, + child: Icon( + Icons.menu, + size: 18.sp, + color: Colors.grey[600], + ), + ), + ), + ], + ); + } + + Widget _buildTabBarView() { + final List functionOptionList = [ + '人员通行', + '考勤', + '审批', + '可视对讲', + '信息发布', + '其他应用', + ]; + + // 每个分类对应的功能列表 + final Map>> functionData = { + '人员通行': [ + {'icon': Icons.door_front_door, 'title': '门禁管理'}, + {'icon': Icons.people, 'title': '门禁授权'}, + {'icon': Icons.access_time, 'title': '一键开门'}, + {'icon': Icons.card_membership, 'title': '密码开门'}, + {'icon': Icons.face, 'title': '我的访客'}, + {'icon': Icons.fingerprint, 'title': '访客统计'}, + {'icon': Icons.security, 'title': '访客邀约'}, + ], + '考勤': [ + {'icon': Icons.schedule, 'title': '考勤设置'}, + {'icon': Icons.assignment, 'title': '审批记录'}, + {'icon': Icons.broadcast_on_personal, 'title': '广播'}, + {'icon': Icons.announcement, 'title': '信息发布'}, + {'icon': Icons.bar_chart, 'title': '统计报表'}, + {'icon': Icons.calendar_today, 'title': '排班管理'}, + {'icon': Icons.access_alarm, 'title': '打卡记录'}, + {'icon': Icons.person_add, 'title': '添加常用'}, + ], + '审批': [ + {'icon': Icons.approval, 'title': '待审批'}, + {'icon': Icons.check_circle, 'title': '已审批'}, + {'icon': Icons.pending, 'title': '审批中'}, + {'icon': Icons.history, 'title': '审批历史'}, + {'icon': Icons.rule, 'title': '审批规则'}, + {'icon': Icons.group, 'title': '审批流程'}, + {'icon': Icons.notifications, 'title': '审批提醒'}, + {'icon': Icons.analytics, 'title': '审批统计'}, + ], + '可视对讲': [ + {'icon': Icons.call, 'title': '对讲设备'}, + {'icon': Icons.call_received, 'title': '呼叫记录'}, + {'icon': Icons.video_call, 'title': '视频通话'}, + {'icon': Icons.volume_up, 'title': '广播通知'}, + {'icon': Icons.settings_phone, 'title': '设备设置'}, + {'icon': Icons.group_add, 'title': '群组管理'}, + {'icon': Icons.record_voice_over, 'title': '语音留言'}, + {'icon': Icons.emergency, 'title': '紧急呼叫'}, + ], + '信息发布': [ + {'icon': Icons.campaign, 'title': '发布信息'}, + {'icon': Icons.list_alt, 'title': '信息列表'}, + {'icon': Icons.schedule_send, 'title': '定时发布'}, + {'icon': Icons.group, 'title': '目标群体'}, + {'icon': Icons.image, 'title': '多媒体'}, + {'icon': Icons.analytics, 'title': '发布统计'}, + {'icon': Icons.edit, 'title': '编辑模板'}, + {'icon': Icons.history, 'title': '发布历史'}, + ], + '其他应用': [ + {'icon': Icons.apps, 'title': '应用中心'}, + {'icon': Icons.extension, 'title': '插件管理'}, + {'icon': Icons.download, 'title': '下载中心'}, + {'icon': Icons.update, 'title': '应用更新'}, + {'icon': Icons.settings, 'title': '应用设置'}, + {'icon': Icons.help, 'title': '帮助中心'}, + {'icon': Icons.feedback, 'title': '意见反馈'}, + {'icon': Icons.info, 'title': '关于我们'}, + ], + }; + + return SizedBox( + height: 100.h, // 设置适合的高度 + child: TabBarView( + controller: _tabController, + children: functionOptionList.map((category) { + final currentFunctions = functionData[category] ?? []; + return GridView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 1.6, + ), + itemCount: currentFunctions.length, + itemBuilder: (context, index) { + final function = currentFunctions[index]; + return _buildFunctionItem( + icon: function['icon'], + title: function['title'], + ); + }, + ); + }).toList(), + ), + ); + } + + Widget _buildFunctionItem({required IconData icon, required String title}) { + return GestureDetector( + onTap: () { + print('点击了: $title'); + // 处理功能项点击事件 + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, // 重要:让Column最小化 + children: [ + Icon( + icon, + size: 20.sp, // 稍微减小图标 + color: Colors.blue.shade600, + ), + SizedBox(height: 4.h), // 减少间距 + Flexible( + // 使用Flexible而不是Expanded + child: Text( + title, + style: TextStyle( + fontSize: 12.sp, // 稍微减小字体 + color: Colors.black87, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/home/widget/home_left_drawer_widget.dart b/lib/views/home/widget/home_left_drawer_widget.dart new file mode 100644 index 0000000..5661320 --- /dev/null +++ b/lib/views/home/widget/home_left_drawer_widget.dart @@ -0,0 +1,280 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class HomeLeftDrawerWidget extends StatefulWidget { + HomeLeftDrawerWidget({ + super.key, + required this.teamList, + this.selectedTeam, + this.onTeamSelected, + }); + + final List teamList; + final String? selectedTeam; + final Function(String)? onTeamSelected; + + @override + State createState() => _HomeLeftDrawerWidgetState(); +} + +class _HomeLeftDrawerWidgetState extends State { + @override + Widget build(BuildContext context) { + return Drawer( + width: 0.85.sw, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, // 去掉圆角 + ), + child: Container( + color: const Color(0xFFF6F7FB), + child: SafeArea( + child: Column( + children: [ + // 头部标题栏 + Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '我的团队', + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Container( + width: 28.w, + height: 28.w, + decoration: BoxDecoration( + color: Colors.grey[300], + shape: BoxShape.circle, + ), + child: Icon( + Icons.refresh, + size: 18.sp, + color: Colors.grey[600], + ), + ), + ), + ], + ), + ), + + // 团队列表 + Expanded( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 16.w), + child: ListView.builder( + itemCount: widget.teamList.length, + itemBuilder: (context, index) { + final team = widget.teamList[index]; + final isSelected = team == widget.selectedTeam; + + return Container( + margin: EdgeInsets.only(bottom: 8.h), + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: isSelected + ? const Color(0xFFE3F2FD) // 选中状态:蓝色背景 + : Colors.white, // 未选中状态:白色背景 + borderRadius: BorderRadius.circular(12.r), + border: isSelected + ? Border.all(color: const Color(0xFF2196F3), width: 1) + : Border.all(color: Colors.grey[200]!, width: 1), + ), + child: GestureDetector( + onTap: () { + // 处理团队选择 + if (widget.onTeamSelected != null) { + widget.onTeamSelected!(team); + } + }, + child: Row( + children: [ + // 用户图标 + Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + color: isSelected + ? const Color(0xFF2196F3) + : Colors.grey[400], + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.person, + color: Colors.white, + size: 24.sp, + ), + ), + SizedBox(width: 12.w), + + // 团队信息 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + team, // 使用teamList中的元素作为团队昵称 + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isSelected + ? const Color(0xFF2196F3) + : Colors.black, + ), + ), + SizedBox(height: 4.h), + Row( + children: [ + Icon( + isSelected + ? Icons.check_circle + : Icons.check_circle_outline, + size: 14.sp, + color: isSelected + ? const Color(0xFF2196F3) + : Colors.grey[400], + ), + SizedBox(width: 4.w), + Text( + isSelected ? '已选中' : '未选中', + style: TextStyle( + fontSize: 12.sp, + color: isSelected + ? const Color(0xFF2196F3) + : Colors.grey[600], + ), + ), + ], + ), + ], + ), + ), + + // 设置图标 + GestureDetector( + onTap: () { + // 处理设置点击事件 + Navigator.pop(context); + }, + child: Container( + padding: EdgeInsets.all(8.w), + child: Icon( + Icons.settings, + size: 20.sp, + color: isSelected + ? const Color(0xFF2196F3) + : Colors.grey[600], + ), + ), + ), + ], + ), + ), + ); + }, + ), + ), + ), + + // 底部按钮区域 + Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: Column( + children: [ + // 创建团队和加入团队按钮 + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + // 处理创建团队 + Navigator.pop(context); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 12.h), + decoration: BoxDecoration( + border: Border( + right: BorderSide( + color: Colors.grey[300]!, + width: 0.5, + ), + ), + ), + child: Text( + '创建团队', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + ), + ), + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + // 处理加入团队 + Navigator.pop(context); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 12.h), + child: Text( + '加入团队', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + ), + ), + ), + ), + ), + ], + ), + + // 分割线 + Container( + margin: EdgeInsets.symmetric(vertical: 8.h), + height: 0.5, + color: Colors.grey[300], + ), + + // 快捷添加我的设备按钮 + GestureDetector( + onTap: () { + // 处理快捷添加设备 + Navigator.pop(context); + }, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 12.h), + child: Text( + '快捷添加我的设备', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/home/widget/home_one_button_door_opening_widget.dart b/lib/views/home/widget/home_one_button_door_opening_widget.dart new file mode 100644 index 0000000..aa70ab1 --- /dev/null +++ b/lib/views/home/widget/home_one_button_door_opening_widget.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/common/constant/app_images.dart'; + +class HomeOnButtonDoorOpeningWidget extends StatefulWidget { + final List doorList; // 门禁列表,最多显示4个 + + const HomeOnButtonDoorOpeningWidget({ + super.key, + required this.doorList, + }); + + @override + State createState() => + _HomeOnButtonDoorOpeningWidgetState(); +} + +class _HomeOnButtonDoorOpeningWidgetState + extends State { + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8.r), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + _buildTitle(), + SizedBox(height: 4.h), + _buildDoorCards(), + ], + ), + ); + } + + Widget _buildTitle() { + return Row( + children: [ + Image.asset( + AppImages.iconOneKeyDoor, + width: 16.w, + height: 16.h, + ), + SizedBox(width: 8.w), + Text( + '一键开门'.tr, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + const Spacer(), + Icon( + Icons.arrow_forward_ios, + size: 14.sp, + color: const Color(0xFFBFBFBF), + ), + ], + ); + } + + Widget _buildDoorCards() { + // 限制最多4个元素 + final displayList = widget.doorList.take(4).toList(); + + if (displayList.isEmpty) { + return const SizedBox.shrink(); // 如果没有数据则不显示 + } + + List rows = []; + + // 按照每行2个的方式组织数据 + for (int i = 0; i < displayList.length; i += 2) { + List rowChildren = []; + + // 第一个元素 + rowChildren.add(_buildDoorCard(i, displayList[i])); + + // 第二个元素(如果存在) + if (i + 1 < displayList.length) { + rowChildren.add(_buildDoorCard(i + 1, displayList[i + 1])); + } else { + // 如果只有一个元素,添加空白占位 + rowChildren.add(SizedBox(width: 150.w)); + } + + rows.add( + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: rowChildren, + ), + ); + } + + return Column( + children: rows, + ); + } + + Widget _buildDoorCard(int index, String doorName) { + return Container( + width: 150.w, // 增大宽度以适应一行两个的布局 + height: 80.h, // 增加高度 + margin: EdgeInsets.only(top: 12.h), + child: Stack( + children: [ + // 背景图片 + Container( + width: 150.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + image: const DecorationImage( + image: AssetImage(AppImages.bgOneKeyDoor), + fit: BoxFit.cover, + ), + ), + ), + // 播放按钮 + Positioned( + top: 12.h, + right: 12.w, + child: Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.5), + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.play_arrow_rounded, + size: 14.sp, + color: const Color(0xFF515057), + ), + ), + ), + // 左下角内容 + Positioned( + left: 12.w, + top: 8.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 圆形钥匙图标 + Container( + width: 34.w, + height: 34.h, + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + shape: BoxShape.circle, + ), + child: Center( + child: Image.asset( + AppImages.iconOneKeyDoorKey, + width: 18.w, + height: 18.h, + ), + ), + ), + SizedBox(height: 16.h), + // 文本 + SizedBox( + width: 80.w, // 增加文本宽度 + child: Text( + doorName, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + fontSize: 14.sp, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/home/widget/home_statistics_row_widget.dart b/lib/views/home/widget/home_statistics_row_widget.dart new file mode 100644 index 0000000..44b3b7e --- /dev/null +++ b/lib/views/home/widget/home_statistics_row_widget.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class HomeStatisticsRowWidget extends StatelessWidget { + final int personCount; + final int deviceCount; + + const HomeStatisticsRowWidget({ + super.key, + this.personCount = 0, + this.deviceCount = 0, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + _buildStatisticsCard( + count: personCount, + unit: '人', + label: '人员总数', + backgroundColor: const Color(0xFFCEF2F5), + textColor: const Color(0xFF134347), + buttonText: '人员管理', + ), + SizedBox(width: 8.w), // 卡片之间的间距 + _buildStatisticsCard( + count: deviceCount, + unit: '台', + label: '设备总数', + backgroundColor: const Color(0xFFD4E0FF), + textColor: const Color(0xFF172A5B), + buttonText: '设备管理', + ), + ], + ); + } + + Widget _buildStatisticsCard({ + required int count, + required String unit, + required String label, + required Color backgroundColor, + required Color textColor, + required String buttonText, + }) { + return Expanded( + // 使用Expanded让卡片自适应宽度 + child: Container( + height: 62.h, + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(8.r), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 上半部分:数字 + 单位 + 管理按钮 + Row( + children: [ + // 数字和单位 + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + // 使用baseline对齐 + textBaseline: TextBaseline.alphabetic, + // 设置文本基线 + children: [ + Text( + count.toString(), + style: TextStyle( + fontSize: 20.sp, // text-5 + fontWeight: FontWeight.bold, + color: textColor, + ), + ), + Text( + unit, + style: TextStyle( + fontSize: 12.sp, // text-3 + color: textColor, + ), + ), + ], + ), + const Spacer(), + // 管理按钮 + Container( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 4.h), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.5), + borderRadius: BorderRadius.circular(4.r), + ), + child: Text( + buttonText, + style: TextStyle( + fontSize: 10.sp, // text-2.5 + fontWeight: FontWeight.w600, + color: textColor, + ), + ), + ), + ], + ), + SizedBox(height: 4.h), // mt-1 + // 下半部分:标签 + Text( + label, + style: TextStyle( + fontSize: 10.sp, // text-2.5 + color: textColor, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/home/widget/home_team_notice_row_widget.dart b/lib/views/home/widget/home_team_notice_row_widget.dart new file mode 100644 index 0000000..bb48a0f --- /dev/null +++ b/lib/views/home/widget/home_team_notice_row_widget.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class HomeTeamNoticeRowWidget extends StatefulWidget { + const HomeTeamNoticeRowWidget({super.key}); + + @override + State createState() => _HomeTeamNoticeRowWidgetState(); +} + +class _HomeTeamNoticeRowWidgetState extends State { + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.symmetric(vertical: 10.h), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(8.r), + ), + ), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: '团队'.tr, + style: TextStyle( + color: Colors.black, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + TextSpan( + text: '公告'.tr, + style: TextStyle( + color: Colors.red, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: Icon( + Icons.brightness_1, + size: 6.sp, + ), + ), + ), + TextSpan( + text: '公告标题'.tr, + style: TextStyle( + color: Colors.black, + fontSize: 14.sp, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: '全部'.tr, + style: TextStyle( + color: Colors.grey, + fontSize: 14.sp, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + SizedBox( + width: 4.w, + ), + Icon( + Icons.arrow_forward_ios_rounded, + color: Colors.grey, + size: 12.sp, + ) + ], + ), + ], + ), + ); + } +} diff --git a/lib/views/login/inputVerificationCode/input_verification_code_controller.dart b/lib/views/login/inputVerificationCode/input_verification_code_controller.dart index e933f29..a5f8cf1 100644 --- a/lib/views/login/inputVerificationCode/input_verification_code_controller.dart +++ b/lib/views/login/inputVerificationCode/input_verification_code_controller.dart @@ -70,12 +70,29 @@ class InputVerificationCodeController extends BaseController { } // 校验验证码 - void checkVerificationCode(String pin) { + void checkVerificationCode(String pin) async { if (previousRoute.value.contains(AppRoutes.login)) { - Get.offAllNamed(AppRoutes.main); + showLoading(); + var validationCodeLoginResult = await userApi.validationCodeLogin( + request: ValidationCodeLoginRequest( + platId: int.parse(PlatformType.app.value), + phone: rawPhone.value, + verificationCode: pin, + ), + ); + if (validationCodeLoginResult.isSuccess) { + await SharedPreferencesUtils.setString( + CacheKeys.token, + validationCodeLoginResult.data!.token, + ); + Get.offAllNamed(AppRoutes.main); + } else { + showError(message: validationCodeLoginResult.errorMsg!); + } } else if (previousRoute.value.contains(AppRoutes.forgotPassword)) { Get.toNamed(AppRoutes.setNewPassword); } + hideLoading(); } void _handleSeedVerificationCode({ diff --git a/pubspec.lock b/pubspec.lock index 37d5265..beb4685 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + sha256: bcc61735345c9ab5cb81073896579e735f81e35fd588907a393143ea986be8ff + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.1" characters: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index adfa556..eb8960a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,8 @@ dependencies: flutter_spinkit: 5.2.1 # 骨架屏 skeletonizer: ^1.4.3 + # 轮播图 + carousel_slider: ^5.1.1 dev_dependencies: @@ -45,4 +47,8 @@ dev_dependencies: flutter: uses-material-design: true + assets: + - assets/images/ + - assets/logo/ + - assets/icon/