feat: 主页页面开发

This commit is contained in:
liyi 2025-09-03 09:39:06 +08:00
parent 981eef1145
commit 54df3eb276
22 changed files with 1579 additions and 72 deletions

View File

@ -9,7 +9,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 读权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 系统通知 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name="${applicationName}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
assets/images/mockImage.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -10,9 +10,16 @@ import 'package:starwork_flutter/i18n/app_i18n.dart';
import 'package:starwork_flutter/routes/app_pages.dart';
import 'package:starwork_flutter/routes/app_routes.dart';
class App extends StatelessWidget {
const App({super.key});
class App extends StatefulWidget {
App({super.key, required this.initialRoute});
String initialRoute;
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
@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;
}
}
}

View File

@ -20,4 +20,30 @@ class AppPermission {
}
return true;
}
static Future<bool> requestPermission({
required Permission permission,
}) async {
var status = await permission.request();
return status == PermissionStatus.granted;
}
static Future<bool> 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;
}
}

View File

@ -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';
}

View File

@ -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:

View File

@ -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<String> _handleInitialRoute() async {
var token = await SharedPreferencesUtils.getString(CacheKeys.token);
if (token != null && token.isNotEmpty) {
return AppRoutes.main;
} else {
return AppRoutes.login;
}
}

View File

@ -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<Color> 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];
}
}
}

View File

@ -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<HomeController> {
const HomeView({super.key});
static final GlobalKey<ScaffoldState> _scaffoldKey =
GlobalKey<ScaffoldState>();
@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,
)
],
),
),
),
);

View File

@ -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<HomeAttendanceChartAreaWidget> createState() =>
_HomeAttendanceChartAreaWidgetState();
}
class _HomeAttendanceChartAreaWidgetState
extends State<HomeAttendanceChartAreaWidget> {
@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;
}
}

View File

@ -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<HomeCarouselAreaWidget> createState() => _HomeCarouselAreaWidgetState();
}
class _HomeCarouselAreaWidgetState extends State<HomeCarouselAreaWidget> {
final List<String> 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(),
),
),
),
],
);
}
}

View File

@ -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<HomeFunctionListAreaWidget> createState() =>
_HomeFunctionListAreaWidgetState();
}
class _HomeFunctionListAreaWidgetState extends State<HomeFunctionListAreaWidget>
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<String> 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<String> functionOptionList = [
'人员通行',
'考勤',
'审批',
'可视对讲',
'信息发布',
'其他应用',
];
//
final Map<String, List<Map<String, dynamic>>> 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,
),
),
],
),
);
}
}

View File

@ -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<String> teamList;
final String? selectedTeam;
final Function(String)? onTeamSelected;
@override
State<HomeLeftDrawerWidget> createState() => _HomeLeftDrawerWidgetState();
}
class _HomeLeftDrawerWidgetState extends State<HomeLeftDrawerWidget> {
@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,
),
),
),
),
],
),
),
],
),
),
),
);
}
}

View File

@ -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<String> doorList; // 4
const HomeOnButtonDoorOpeningWidget({
super.key,
required this.doorList,
});
@override
State<HomeOnButtonDoorOpeningWidget> createState() =>
_HomeOnButtonDoorOpeningWidgetState();
}
class _HomeOnButtonDoorOpeningWidgetState
extends State<HomeOnButtonDoorOpeningWidget> {
@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<Widget> rows = [];
// 2
for (int i = 0; i < displayList.length; i += 2) {
List<Widget> 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,
),
),
],
),
),
],
),
);
}
}

View File

@ -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,
),
),
],
),
),
);
}
}

View File

@ -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<HomeTeamNoticeRowWidget> createState() => _HomeTeamNoticeRowWidgetState();
}
class _HomeTeamNoticeRowWidgetState extends State<HomeTeamNoticeRowWidget> {
@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,
)
],
),
],
),
);
}
}

View File

@ -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({

View File

@ -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:

View File

@ -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/