feat: 完善主页页面、消息页面开发

This commit is contained in:
liyi 2025-09-03 14:09:32 +08:00
parent 54df3eb276
commit 63773b1e24
53 changed files with 1240 additions and 145 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/icon/bar/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/icon/bar/mine.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/icon/broadcast.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

BIN
assets/icon/go_out.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

BIN
assets/icon/my_visitor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

BIN
assets/icon/team_qrcode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

View File

@ -3,4 +3,61 @@ 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';
static const String mockImage = 'assets/images/mockImage.jpg';
//
static const String iconVideoCenter = 'assets/icon/video_center.png';
static const String iconIntelligentInspection = 'assets/icon/intelligent_inspection.png';
static const String iconVideoSearch = 'assets/icon/video_search.png';
static const String iconIntelligentAnalysis = 'assets/icon/intelligent_analysis.png';
static const String iconPersonCapture = 'assets/icon/person_capture.png';
static const String iconVehicleManagement = 'assets/icon/vehicle_management.png';
//
static const String iconAccessManagement = 'assets/icon/access_management.png';
static const String iconAccessAuthorization = 'assets/icon/access_authorization.png';
static const String iconOneClickOpenDoor = 'assets/icon/one_click_open_door.png';
static const String iconPasswordOpenDoor = 'assets/icon/password_open_door.png';
static const String iconTrafficRecord = 'assets/icon/traffic_record.png';
static const String iconVisitorManagement = 'assets/icon/visitor_management.png';
static const String iconMyVisitor = 'assets/icon/my_visitor.png';
static const String iconVisitorInvitation = 'assets/icon/visitor_invitation.png';
//
static const String iconAttendanceSetting = 'assets/icon/attendance_setting.png';
static const String iconAttendanceStatistics = 'assets/icon/attendance_statistics.png';
static const String iconMyAttendance = 'assets/icon/my_attendance.png';
static const String iconMobileCheckin = 'assets/icon/mobile_checkin.png';
static const String iconLeaveRequest = 'assets/icon/leave_request.png';
static const String iconMakeUpCard = 'assets/icon/make_up_card.png';
static const String iconBusinessTrip = 'assets/icon/business_trip.png';
static const String iconGoOut = 'assets/icon/go_out.png';
//
static const String iconInitiateApproval = 'assets/icon/initiate_approval.png';
static const String iconApprovalRecord = 'assets/icon/approval_record.png';
//
static const String iconIntercomDevice = 'assets/icon/intercom_device.png';
static const String iconCallRelationship = 'assets/icon/call_relationship.png';
static const String iconCallReminder = 'assets/icon/call_reminder.png';
//
static const String iconBroadcast = 'assets/icon/broadcast.png';
static const String iconAnnouncement = 'assets/icon/announcement.png';
static const String iconInfoPublish = 'assets/icon/info_publish.png';
//
static const String iconPersonnelManagement = 'assets/icon/personnel_management.png';
static const String iconTeamQrcode = 'assets/icon/team_qrcode.png';
static const String iconDeviceManagement = 'assets/icon/device_management.png';
static const String iconTableMenu = 'assets/icon/icon_table_menu.png';
//
static const String iconHome = 'assets/icon/bar/home.png';
static const String iconHomeSelected = 'assets/icon/bar/home_selected.png';
static const String iconNotification = 'assets/icon/bar/notification.png';
static const String iconNotificationSelected = 'assets/icon/bar/notification_selected.png';
static const String iconMine = 'assets/icon/bar/mine.png';
static const String iconMineSelected = 'assets/icon/bar/mine_selected.png';
}

View File

@ -4,43 +4,89 @@ import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:starwork_flutter/base/app_permission.dart';
import 'package:starwork_flutter/base/base_controller.dart';
import 'package:starwork_flutter/views/main/main_controller.dart';
class HomeController extends BaseController {
final mainController = Get.find<MainController>();
final isOpenNotificationPermission = false.obs;
//
final isLoading = true.obs;
var carouselCurrentIndex = 0.obs;
//
final List<Color> gradientColors = [
const Color(0xFFBFCBEF), // #bfcbef
const Color(0xFFECBE9B), //
const Color(0xFFD5F3D5), // 绿
const Color(0xFFFFB6C1), //
const Color(0xFFBFCBEF),
const Color(0xFFECBE9B),
];
//
var currentGradientColor = const Color(0xFFBFCBEF).obs;
@override
void onInit() async {
super.onInit();
isOpenNotificationPermission.value = await AppPermission.checkPermission(
permission: Permission.notification,
);
//
await _initializeData();
//
carouselCurrentIndex.listen((index) {
updateGradientColor(index);
});
}
//
Future<void> _initializeData() async {
// 2
await Future.delayed(const Duration(seconds: 2));
//
isOpenNotificationPermission.value = await AppPermission.checkPermission(
permission: Permission.notification,
);
//
isLoading.value = false;
}
//
void updateGradientColor(int index) {
if (index < gradientColors.length) {
currentGradientColor.value = gradientColors[index];
} else {
// 使
currentGradientColor.value = gradientColors[index % gradientColors.length];
currentGradientColor.value =
gradientColors[index % gradientColors.length];
}
}
//
Future<void> refreshHome() async {
//
// isLoading.value = true;
//
await Future.delayed(const Duration(seconds: 1));
//
// 1.
// 2.
// 3.
// 4.
// 5.
//
isOpenNotificationPermission.value = await AppPermission.checkPermission(
permission: Permission.notification,
);
//
// isLoading.value = false;
print('首页数据刷新完成');
}
}

View File

@ -1,4 +1,3 @@
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
@ -18,9 +17,6 @@ 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 Obx(
@ -36,68 +32,26 @@ class HomeView extends GetView<HomeController> {
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(),
child: 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,
),
],
),
),
),
],
),
//
Expanded(
child: _buildRefreshableContent(),
),
],
),
),
drawer: HomeLeftDrawerWidget(
teamList: ['家庭群组', '测试团队1', '测试团队2'],
),
),
),
);
@ -106,7 +60,8 @@ class HomeView extends GetView<HomeController> {
_buildPageHead(BuildContext context) {
return GestureDetector(
onTap: () {
_scaffoldKey.currentState?.openDrawer();
// 使MainController的专用方法
controller.mainController.openDrawer();
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -199,4 +154,68 @@ class HomeView extends GetView<HomeController> {
),
);
}
//
Widget _buildRefreshableContent() {
return RefreshIndicator(
onRefresh: _onRefresh,
//
color: const Color(0xFF4A90E2), //
backgroundColor: Colors.white, //
//
displacement: 60.0, //
edgeOffset: 0.0, //
//
triggerMode: RefreshIndicatorTriggerMode.onEdge, //
//
strokeWidth: 2.5, //
//
semanticsLabel: '下拉刷新首页内容',
semanticsValue: '刷新中...',
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(
//
parent: BouncingScrollPhysics(),
),
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),
],
),
),
);
}
//
Future<void> _onRefresh() async {
// controller的刷新方法
await controller.refreshHome();
}
}

View File

@ -3,6 +3,7 @@ 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 HomeCarouselAreaWidget extends StatefulWidget {
const HomeCarouselAreaWidget({
@ -18,9 +19,9 @@ class HomeCarouselAreaWidget extends StatefulWidget {
class _HomeCarouselAreaWidgetState extends State<HomeCarouselAreaWidget> {
final List<String> imgList = [
'assets/images/mockImage.jpg',
'assets/images/mockImage.jpg',
'assets/images/mockImage.jpg',
AppImages.mockImage,
AppImages.mockImage,
AppImages.mockImage,
];
@override

View File

@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:starwork_flutter/common/constant/app_images.dart';
class HomeFunctionListAreaWidget extends StatefulWidget {
HomeFunctionListAreaWidget({super.key});
@ -15,18 +16,81 @@ class _HomeFunctionListAreaWidgetState extends State<HomeFunctionListAreaWidget>
with SingleTickerProviderStateMixin {
late TabController _tabController;
// Map
final Map<String, ImageProvider> _imageCache = {};
//
bool _isPreloaded = false;
@override
void initState() {
super.initState();
_tabController = TabController(length: 6, vsync: this);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// MediaQuery可用
if (!_isPreloaded) {
_preloadImages();
_isPreloaded = true;
}
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
//
void _preloadImages() {
final List<String> imagePaths = [
AppImages.iconAccessManagement,
AppImages.iconAccessAuthorization,
AppImages.iconOneClickOpenDoor,
AppImages.iconPasswordOpenDoor,
AppImages.iconTrafficRecord,
AppImages.iconVisitorManagement,
AppImages.iconMyVisitor,
AppImages.iconVisitorInvitation,
AppImages.iconAttendanceSetting,
AppImages.iconAttendanceStatistics,
AppImages.iconMyAttendance,
AppImages.iconMobileCheckin,
AppImages.iconLeaveRequest,
AppImages.iconMakeUpCard,
AppImages.iconBusinessTrip,
AppImages.iconGoOut,
AppImages.iconInitiateApproval,
AppImages.iconApprovalRecord,
AppImages.iconIntercomDevice,
AppImages.iconCallRelationship,
AppImages.iconCallReminder,
AppImages.iconBroadcast,
AppImages.iconAnnouncement,
AppImages.iconInfoPublish,
AppImages.iconPersonnelManagement,
AppImages.iconTeamQrcode,
AppImages.iconDeviceManagement,
AppImages.iconVideoCenter,
AppImages.iconIntelligentInspection,
AppImages.iconVideoSearch,
AppImages.iconIntelligentAnalysis,
AppImages.iconPersonCapture,
];
for (String path in imagePaths) {
_imageCache[path] = AssetImage(path);
// 使
precacheImage(_imageCache[path]!, context).catchError((error) {
//
debugPrint('预加载图片失败: $path, 错误: $error');
});
}
}
@override
Widget build(BuildContext context) {
return Container(
@ -59,7 +123,7 @@ class _HomeFunctionListAreaWidgetState extends State<HomeFunctionListAreaWidget>
return Row(
children: [
Expanded(
Flexible(
child: TabBar(
controller: _tabController,
isScrollable: true,
@ -97,10 +161,22 @@ class _HomeFunctionListAreaWidgetState extends State<HomeFunctionListAreaWidget>
//
print('菜单被点击');
},
child: Icon(
Icons.menu,
size: 18.sp,
color: Colors.grey[600],
child: Image(
image: AssetImage(AppImages.iconTableMenu),
width: 20.w,
height: 20.w,
fit: BoxFit.contain,
gaplessPlayback: true,
//
filterQuality: FilterQuality.medium,
//
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.image_not_supported,
size: 26.sp,
color: Colors.grey,
);
},
),
),
),
@ -121,27 +197,28 @@ class _HomeFunctionListAreaWidgetState extends State<HomeFunctionListAreaWidget>
//
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': AppImages.iconAccessManagement, 'title': '门禁管理'},
{'icon': AppImages.iconAccessAuthorization, 'title': '门禁授权'},
{'icon': AppImages.iconOneClickOpenDoor, 'title': '一键开门'},
{'icon': AppImages.iconPasswordOpenDoor, 'title': '密码开门'},
{'icon': AppImages.iconTrafficRecord, 'title': '通行记录'},
{'icon': AppImages.iconVisitorManagement, 'title': '访客管理'},
{'icon': AppImages.iconMyVisitor, 'title': '我的访客'},
{'icon': AppImages.iconVisitorInvitation, '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': AppImages.iconAttendanceSetting, 'title': '考勤设置'},
{'icon': AppImages.iconAttendanceStatistics, 'title': '考勤统计'},
{'icon': AppImages.iconMyAttendance, 'title': '我的考勤'},
{'icon': AppImages.iconMobileCheckin, 'title': '手机打卡'},
{'icon': AppImages.iconLeaveRequest, 'title': '请假'},
{'icon': AppImages.iconMakeUpCard, 'title': '补卡'},
{'icon': AppImages.iconBusinessTrip, 'title': '出差'},
{'icon': AppImages.iconGoOut, 'title': '外出'},
],
'审批': [
{'icon': Icons.approval, 'title': '审批'},
{'icon': Icons.check_circle, 'title': '审批'},
{'icon': AppImages.iconInitiateApproval, 'title': '发起审批'},
{'icon': AppImages.iconApprovalRecord, 'title': '审批记录'},
{'icon': Icons.pending, 'title': '审批中'},
{'icon': Icons.history, 'title': '审批历史'},
{'icon': Icons.rule, 'title': '审批规则'},
@ -150,34 +227,34 @@ class _HomeFunctionListAreaWidgetState extends State<HomeFunctionListAreaWidget>
{'icon': Icons.analytics, 'title': '审批统计'},
],
'可视对讲': [
{'icon': Icons.call, 'title': '对讲设备'},
{'icon': Icons.call_received, 'title': '呼叫记录'},
{'icon': AppImages.iconIntercomDevice, 'title': '对讲设备'},
{'icon': AppImages.iconCallRelationship, 'title': '呼叫关系'},
{'icon': AppImages.iconCallReminder, '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': AppImages.iconBroadcast, 'title': '广播'},
{'icon': AppImages.iconAnnouncement, 'title': '公告'},
{'icon': AppImages.iconInfoPublish, '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': '关于我们'},
{'icon': AppImages.iconPersonnelManagement, 'title': '人员管理'},
{'icon': AppImages.iconTeamQrcode, 'title': '团队二维码'},
{'icon': AppImages.iconDeviceManagement, 'title': '设备管理'},
{'icon': AppImages.iconVideoCenter, 'title': '视频中心'},
{'icon': AppImages.iconIntelligentInspection, 'title': '智能巡检'},
{'icon': AppImages.iconVideoSearch, 'title': '录像智搜'},
{'icon': AppImages.iconIntelligentAnalysis, 'title': '智能分析'},
{'icon': AppImages.iconPersonCapture, 'title': '人员抓拍'},
],
};
@ -192,7 +269,7 @@ class _HomeFunctionListAreaWidgetState extends State<HomeFunctionListAreaWidget>
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
childAspectRatio: 1.6,
childAspectRatio: 1.4,
),
itemCount: currentFunctions.length,
itemBuilder: (context, index) {
@ -208,7 +285,7 @@ class _HomeFunctionListAreaWidgetState extends State<HomeFunctionListAreaWidget>
);
}
Widget _buildFunctionItem({required IconData icon, required String title}) {
Widget _buildFunctionItem({required dynamic icon, required String title}) {
return GestureDetector(
onTap: () {
print('点击了: $title');
@ -218,18 +295,21 @@ class _HomeFunctionListAreaWidgetState extends State<HomeFunctionListAreaWidget>
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, // Column最小化
children: [
Icon(
icon,
size: 20.sp, //
color: Colors.blue.shade600,
),
//
icon is String
? _buildOptimizedImage(icon)
: Icon(
icon,
size: 26.sp,
color: Colors.blue.shade600,
),
SizedBox(height: 4.h), //
Flexible(
// 使Flexible而不是Expanded
child: Text(
title,
style: TextStyle(
fontSize: 12.sp, //
fontSize: 10.sp, //
color: Colors.black87,
fontWeight: FontWeight.w400,
),
@ -242,4 +322,27 @@ class _HomeFunctionListAreaWidgetState extends State<HomeFunctionListAreaWidget>
),
);
}
//
Widget _buildOptimizedImage(String imagePath) {
return RepaintBoundary(
child: Image(
image: _imageCache[imagePath] ?? AssetImage(imagePath),
width: 26.w,
height: 26.w,
fit: BoxFit.contain,
gaplessPlayback: true,
//
filterQuality: FilterQuality.medium,
//
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.image_not_supported,
size: 26.sp,
color: Colors.grey,
);
},
),
);
}
}

View File

@ -1,28 +1,17 @@
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/base/base_controller.dart';
import 'package:starwork_flutter/common/constant/app_images.dart';
import 'package:starwork_flutter/views/home/home_view.dart';
import 'package:starwork_flutter/views/messages/messages_view.dart';
import 'package:starwork_flutter/views/mine/mine_view.dart';
class MainController extends BaseController {
//
final List<BottomNavigationBarItem> bottomNavItems = [
const BottomNavigationBarItem(
icon: Icon(Icons.home_rounded),
label: '首页',
),
const BottomNavigationBarItem(
icon: Icon(Icons.messenger_outline_rounded),
label: '消息',
),
const BottomNavigationBarItem(
icon: Icon(Icons.person),
label: '我的',
),
];
GlobalKey<ScaffoldState> scaffoldKey =
GlobalKey<ScaffoldState>();
//
var currentIndex = 0.obs;
@ -37,4 +26,56 @@ class MainController extends BaseController {
void changeIndex(int index) {
currentIndex.value = index;
}
//
List<BottomNavigationBarItem> get bottomNavItems => [
BottomNavigationBarItem(
icon: _buildTabIcon(AppImages.iconHome, AppImages.iconHomeSelected, 0),
label: '首页',
),
BottomNavigationBarItem(
icon: _buildTabIcon(AppImages.iconNotification, AppImages.iconNotificationSelected, 1),
label: '消息',
),
BottomNavigationBarItem(
icon: _buildTabIcon(AppImages.iconMine, AppImages.iconMineSelected, 2),
label: '我的',
),
];
//
Widget _buildTabIcon(String unselectedIcon, String selectedIcon, int index) {
return Obx(() => Image.asset(
currentIndex.value == index ? selectedIcon : unselectedIcon,
width: 24.w,
height: 24.w,
fit: BoxFit.contain,
));
}
//
void openDrawer() {
try {
final scaffoldState = scaffoldKey.currentState;
if (scaffoldState != null && scaffoldState.hasDrawer) {
scaffoldState.openDrawer();
} else {
print('Cannot open drawer: Scaffold state is null or has no drawer');
}
} catch (e) {
print('Error opening drawer: $e');
}
}
//
void closeDrawer() {
try {
final scaffoldState = scaffoldKey.currentState;
if (scaffoldState != null && scaffoldState.isDrawerOpen) {
scaffoldState.closeDrawer();
}
} catch (e) {
print('Error closing drawer: $e');
}
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:starwork_flutter/views/main/widget/main_left_drawer_widget.dart';
import 'main_controller.dart';
@ -10,6 +11,7 @@ class MainView extends GetView<MainController> {
@override
Widget build(BuildContext context) {
return Scaffold(
key: controller.scaffoldKey, //
// 使 Obx currentIndex
body: Obx(
() => IndexedStack(
@ -28,6 +30,9 @@ class MainView extends GetView<MainController> {
unselectedFontSize: 12.sp,
),
),
drawer: MainLeftDrawerWidget(
teamList: ['家庭群组', '测试团队1', '测试团队2'],
),
);
}
}

View File

@ -0,0 +1,280 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class MainLeftDrawerWidget extends StatefulWidget {
MainLeftDrawerWidget({
super.key,
required this.teamList,
this.selectedTeam,
this.onTeamSelected,
});
final List<String> teamList;
final String? selectedTeam;
final Function(String)? onTeamSelected;
@override
State<MainLeftDrawerWidget> createState() => _MainLeftDrawerWidgetState();
}
class _MainLeftDrawerWidgetState extends State<MainLeftDrawerWidget> {
@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

@ -1,5 +1,81 @@
import 'package:get/get.dart';
import 'package:starwork_flutter/base/base_controller.dart';
import 'package:starwork_flutter/views/home/home_controller.dart';
import 'package:starwork_flutter/views/main/main_controller.dart';
import 'messages_model.dart';
class MessagesController extends BaseController {
final mainController = Get.find<MainController>();
final homeController = Get.find<HomeController>();
//
final RxList<MessageItem> messageList = <MessageItem>[].obs;
@override
void onInit() {
super.onInit();
_initializeMessages();
}
//
void _initializeMessages() {
messageList.value = [
MessageItem(
type: MessageType.deviceStatus,
title: '设备状态',
subtitle: '[设备在线] DS-K(L40959329)',
time: '25/08/28',
hasRedDot: true,
),
MessageItem(
type: MessageType.accessControl,
title: '通行权限',
subtitle: '[通行权限] 下发已结束成功0条失败1条,成功0条失败1条,成功0条失败1条成功0条失败1条成功0条失败1条',
time: '25/08/26',
hasRedDot: false,
),
MessageItem(
type: MessageType.systemNotice,
title: '系统通知',
subtitle: '没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息',
time: '',
hasRedDot: false,
),
];
}
//
Future<void> refreshMessages() async {
//
await Future.delayed(const Duration(seconds: 1));
// API调用
//
_initializeMessages();
print('消息数据刷新完成');
}
//
void addMessage(MessageItem message) {
messageList.insert(0, message);
}
//
void removeMessage(int index) {
if (index >= 0 && index < messageList.length) {
messageList.removeAt(index);
}
}
//
void clearAllUnread() {
for (int i = 0; i < messageList.length; i++) {
if (messageList[i].hasRedDot) {
messageList[i] = messageList[i].copyWith(hasRedDot: false);
}
}
messageList.refresh();
}
}

View File

@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
//
enum MessageType {
deviceStatus, //
accessControl, //
systemNotice, //
}
//
class MessageItem {
final MessageType type;
final String title;
final String subtitle;
final String time;
final bool hasRedDot;
MessageItem({
required this.type,
required this.title,
required this.subtitle,
required this.time,
required this.hasRedDot,
});
//
IconData get icon {
switch (type) {
case MessageType.deviceStatus:
return Icons.devices;
case MessageType.accessControl:
return Icons.security;
case MessageType.systemNotice:
return Icons.notifications;
}
}
//
Color get iconColor {
switch (type) {
case MessageType.deviceStatus:
return const Color(0xFF4A90E2);
case MessageType.accessControl:
return const Color(0xFF4A90E2);
case MessageType.systemNotice:
return const Color(0xFFFF8C42);
}
}
//
Color get iconBgColor {
switch (type) {
case MessageType.deviceStatus:
return const Color(0xFFE8F4FD);
case MessageType.accessControl:
return const Color(0xFFE8F4FD);
case MessageType.systemNotice:
return const Color(0xFFFFF2E8);
}
}
//
MessageItem copyWith({
MessageType? type,
String? title,
String? subtitle,
String? time,
bool? hasRedDot,
}) {
return MessageItem(
type: type ?? this.type,
title: title ?? this.title,
subtitle: subtitle ?? this.subtitle,
time: time ?? this.time,
hasRedDot: hasRedDot ?? this.hasRedDot,
);
}
}

View File

@ -1,7 +1,11 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:starwork_flutter/base/app_permission.dart';
import 'messages_controller.dart';
import 'messages_model.dart';
class MessagesView extends GetView<MessagesController> {
const MessagesView({super.key});
@ -9,16 +13,400 @@ class MessagesView extends GetView<MessagesController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('MessagesView'),
centerTitle: true,
),
body: const Center(
child: Text(
'MessagesView is working',
style: TextStyle(fontSize: 20),
backgroundColor: const Color(0xFFF6F7FB),
body: SafeArea(
child: Container(
width: 1.sw,
padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 4.h),
child: Column(
children: [
//
_buildPageHead(context),
_buildSystemNotificationPermissionRow(),
Obx(
() => Visibility(
visible: controller
.homeController.isOpenNotificationPermission.value,
child: SizedBox(
height: 10.h,
),
),
),
//
Expanded(
child: _buildRefreshableMessageList(),
),
],
),
),
),
);
}
_buildPageHead(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
//
Expanded(
child: GestureDetector(
onTap: () {
controller.mainController.scaffoldKey.currentState?.openDrawer();
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
'19104656的互12312联',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
Icon(
Icons.arrow_right_rounded,
size: 22.sp,
),
],
),
),
),
//
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
children: [
//
Flexible(
child: GestureDetector(
onTap: () {
//
controller.clearAllUnread();
print('清除未读被点击');
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 6.w, //
vertical: 3.h,
),
decoration: BoxDecoration(
color: const Color(0xFFF0F0F0),
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
'清除未读',
style: TextStyle(
fontSize: 12.sp, //
color: Colors.black54,
fontWeight: FontWeight.w400,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
),
),
),
// 线
Container(
height: 16.h, //
width: 1.w,
color: Colors.grey.withOpacity(0.3),
),
//
Flexible(
child: GestureDetector(
onTap: () {
//
print('通知设置被点击');
},
child: Text(
'通知设置',
style: TextStyle(
fontSize: 14.sp, //
color: Colors.black,
fontWeight: FontWeight.w400,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
],
),
),
],
);
}
_buildSystemNotificationPermissionRow() {
return Obx(
() => Visibility(
visible: !controller.homeController.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),
),
),
margin: EdgeInsets.symmetric(vertical: 8.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'系统通知未开启,报警消息无法通知'.tr,
style: TextStyle(
color: const Color(0xFFEE9846),
fontSize: 12.sp,
),
),
const Spacer(),
GestureDetector(
onTap: () async {
controller.homeController.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,
)
],
),
),
),
);
}
_buildRefreshableMessageList() {
return RefreshIndicator(
onRefresh: _onRefresh,
//
color: const Color(0xFF4A90E2), //
backgroundColor: Colors.white, //
//
displacement: 60.0, // 40.0
edgeOffset: 0.0, // 0.0
//
triggerMode: RefreshIndicatorTriggerMode.onEdge, //
// RefreshIndicatorTriggerMode.onEdge:
// RefreshIndicatorTriggerMode.anywhere:
//
strokeWidth: 2.5, // 2.0
//
semanticsLabel: '下拉刷新消息列表',
semanticsValue: '刷新中...',
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(
//
parent: BouncingScrollPhysics(), //
),
child: Column(
children: [
_buildMessageList(),
SizedBox(height: 16.h),
_buildMessageTip(),
],
),
),
);
}
Future<void> _onRefresh() async {
// controller的刷新方法
await controller.refreshMessages();
}
_buildMessageList() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.r),
),
child: Obx(
() => Column(
children: [
//
...controller.messageList.asMap().entries.map((entry) {
int index = entry.key;
MessageItem message = entry.value;
return Column(
children: [
_buildMessageItem(
message: message,
onTap: () => _onMessageTap(index, message),
),
// 线
if (index < controller.messageList.length - 1)
_buildDivider(),
],
);
}).toList(),
],
),
),
);
}
_buildMessageTip() {
return Container(
padding: EdgeInsets.symmetric(vertical: 10.h),
child: Text(
'只展示最近7天的消息',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey[500],
fontWeight: FontWeight.w400,
),
),
);
}
_buildMessageItem({
required MessageItem message,
VoidCallback? onTap,
}) {
return GestureDetector(
onTap: onTap ??
() {
//
print('${message.title} 被点击');
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
child: Row(
children: [
//
Stack(
children: [
Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: message.iconBgColor,
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(
message.icon,
color: message.iconColor,
size: 20.sp,
),
),
if (message.hasRedDot)
Positioned(
top: 0,
right: 0,
child: Container(
width: 8.w,
height: 8.w,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
),
],
),
SizedBox(width: 12.w),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
message.title,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
SizedBox(height: 4.h),
Text(
message.subtitle,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[600],
fontWeight: FontWeight.w400,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
//
if (message.time.isNotEmpty)
Text(
message.time,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey[400],
fontWeight: FontWeight.w400,
),
),
],
),
),
);
}
//
void _onMessageTap(int index, MessageItem message) {
print('${message.title} 被点击,索引: $index');
//
//
if (message.hasRedDot) {
// controller的方法标记为已读
}
}
_buildDivider() {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
height: 1.h,
color: Colors.grey[200],
);
}
}

View File

@ -51,4 +51,5 @@ flutter:
- assets/images/
- assets/logo/
- assets/icon/
- assets/icon/bar/