feat: 主页页面开发
This commit is contained in:
parent
981eef1145
commit
54df3eb276
@ -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}"
|
||||
|
||||
BIN
assets/icon/icon_one_key_door.png
Normal file
BIN
assets/icon/icon_one_key_door.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 199 B |
BIN
assets/icon/icon_one_key_door_key.png
Normal file
BIN
assets/icon/icon_one_key_door_key.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 462 B |
BIN
assets/images/bg_one_key_door.png
Normal file
BIN
assets/images/bg_one_key_door.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
assets/images/mockImage.jpg
Normal file
BIN
assets/images/mockImage.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
22
lib/app.dart
22
lib/app.dart
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
6
lib/common/constant/app_images.dart
Normal file
6
lib/common/constant/app_images.dart
Normal 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';
|
||||
}
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
243
lib/views/home/widget/home_attendance_chart_area_widget.dart
Normal file
243
lib/views/home/widget/home_attendance_chart_area_widget.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
96
lib/views/home/widget/home_carousel_area_widget.dart
Normal file
96
lib/views/home/widget/home_carousel_area_widget.dart
Normal 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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
245
lib/views/home/widget/home_function_list_area_widget.dart
Normal file
245
lib/views/home/widget/home_function_list_area_widget.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
280
lib/views/home/widget/home_left_drawer_widget.dart
Normal file
280
lib/views/home/widget/home_left_drawer_widget.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
194
lib/views/home/widget/home_one_button_door_opening_widget.dart
Normal file
194
lib/views/home/widget/home_one_button_door_opening_widget.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
128
lib/views/home/widget/home_statistics_row_widget.dart
Normal file
128
lib/views/home/widget/home_statistics_row_widget.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
101
lib/views/home/widget/home_team_notice_row_widget.dart
Normal file
101
lib/views/home/widget/home_team_notice_row_widget.dart
Normal 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,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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({
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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/
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user