hs 2025-07-07 16:49:05 +08:00
parent 5fd495ed29
commit d613464600
41 changed files with 3261 additions and 340 deletions

View File

@ -1,10 +1,40 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 摄像头权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 定位权限 -->
<!-- 定位 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- 后台定位 -->
<!-- 相册 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- Android 13+ -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 12及以下 -->
<!-- 相机 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 麦克风 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 通讯录 -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<!-- 通知 -->
<!-- 无需权限,但需创建通知渠道 -->
<!-- 蓝牙 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <!-- Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <!-- Android 12+ -->
<!-- 本地网络 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- 存储(非相册文件) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 仅支持旧版 -->
<application
android:label="qhd_prevention"
android:name="${applicationName}"

View File

@ -5,15 +5,30 @@ PODS:
- mobile_scanner (7.0.0):
- Flutter
- FlutterMacOS
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- photo_manager (3.7.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- Flutter (from `Flutter`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
EXTERNAL SOURCES:
Flutter:
@ -22,14 +37,26 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_picker_ios/ios"
mobile_scanner:
:path: ".symlinks/plugins/mobile_scanner/darwin"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
photo_manager:
:path: ".symlinks/plugins/photo_manager/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5

View File

@ -161,7 +161,6 @@
19C3F450BC80B5A7805E1AEC /* Pods-RunnerTests.release.xcconfig */,
26B606F41C2A0A98CB6C6675 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
@ -470,6 +469,8 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
ENABLE_BITCODE = NO;
@ -480,6 +481,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -493,6 +495,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention.RunnerTests;
@ -511,6 +514,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention.RunnerTests;
@ -527,6 +531,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention.RunnerTests;
@ -653,6 +658,8 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
ENABLE_BITCODE = NO;
@ -663,6 +670,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -676,6 +684,8 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8AKCJ9LW7D;
ENABLE_BITCODE = NO;
@ -686,6 +696,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View File

@ -16,8 +16,52 @@
<string>qhd_prevention</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<!-- 定位 -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>app需要定位权限来提供附近服务</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>app需要后台定位以实现持续跟踪</string>
<!-- 相册 -->
<key>NSPhotoLibraryUsageDescription</key>
<string>app需要访问相册以上传图片</string>
<key>NSPhotoLibraryAddUsageDescription</key> <!-- iOS 11+ 仅保存图片 -->
<string>app需要保存图片到相册</string>
<!-- 相机 -->
<key>NSCameraUsageDescription</key>
<string>需要相机权限来扫描二维码</string>
<string>app需要相机权限来扫描二维码</string>
<!-- 麦克风 -->
<key>NSMicrophoneUsageDescription</key>
<string>app需要麦克风权限进行语音通话</string>
<!-- 通讯录 -->
<key>NSContactsUsageDescription</key>
<string>app需要通讯录权限添加好友</string>
<!-- 通知 -->
<key>NSUserNotificationsUsageDescription</key>
<string>app需要发送通知提醒重要信息</string>
<!-- 蓝牙 -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>app需要蓝牙权限连接设备</string>
<!-- 本地网络 -->
<key>NSLocalNetworkUsageDescription</key>
<string>app需要发现本地网络设备</string>
<!-- 健康数据 -->
<key>NSHealthShareUsageDescription</key>
<string>app需要读取健康数据</string>
<key>NSHealthUpdateUsageDescription</key>
<string>app需要写入健康数据</string>
<!-- 运动与健身 -->
<key>NSMotionUsageDescription</key>
<string>app需要访问运动数据统计步数</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>

View File

@ -0,0 +1,6 @@
///
class RecordCheckModel {
final String title;
final String time;
RecordCheckModel(this.title, this.time);
}

View File

@ -1,13 +1,16 @@
import 'package:flutter/material.dart';
import '../tools/tools.dart';
///
class ListItemFactory {
/// 1spaceBetween
/// 1spaceBetween
static Widget createRowSpaceBetweenItem({
required String leftText,
required String rightText,
double verticalPadding = 15,
double verticalPadding = 10,
double horizontalPadding = 0,
Color textColor = Colors.black,
bool isRight = false,
}) {
return Padding(
@ -23,7 +26,7 @@ class ListItemFactory {
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
color: textColor,
),
),
if (isRight)
@ -35,7 +38,7 @@ class ListItemFactory {
style: TextStyle(fontSize: 15, color: Colors.grey),
),
SizedBox(width: 2,),
Icon(Icons.arrow_forward_ios_rounded, size: 15),
Icon(Icons.arrow_forward_ios_rounded, color: Colors.black45, size: 15),
],
)
else
@ -84,12 +87,12 @@ class ListItemFactory {
/// 3
static Widget createTextImageItem({
required String text,
required String imageUrl,
required List<String> imageUrls,
double imageHeight = 90,
double verticalPadding = 15,
double verticalPadding = 10,
double horizontalPadding = 0,
//
VoidCallback? onImageTapped, //
// index
void Function(int index)? onImageTapped,
}) {
return Padding(
padding: EdgeInsets.symmetric(
@ -101,39 +104,92 @@ class ListItemFactory {
children: [
Text(
text,
style: TextStyle(
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const SizedBox(height: 10),
// 使GestureDetector
GestureDetector(
onTap: onImageTapped, //
child: Builder(
builder: (context) {
//
if (imageUrl.startsWith('http')) {
return Image.network(
imageUrl,
Wrap(
spacing: 8, //
runSpacing: 8, //
children: List.generate(imageUrls.length, (i) {
final url = imageUrls[i];
Widget img;
if (url.startsWith('http')) {
img = Image.network(
url,
height: imageHeight,
width: imageHeight * 3 / 2,
fit: BoxFit.cover,
alignment: Alignment.centerLeft,
);
}
//
else {
return Image.asset(
imageUrl,
} else {
img = Image.asset(
url,
height: imageHeight,
width: double.infinity,
width: imageHeight * 3 / 2,
fit: BoxFit.cover,
alignment: Alignment.centerLeft,
);
}
return GestureDetector(
onTap: () {
if (onImageTapped != null) onImageTapped(i);
},
child: ClipRRect(
borderRadius: BorderRadius.circular(4),
child: img,
),
);
}),
),
],
),
);
}
/// 6
static Widget createTextVideoItem({
required String text,
required String videoUrl,
double videoHeight = 90,
double verticalPadding = 10,
double horizontalPadding = 0,
VoidCallback? onVideoTapped,
}) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: verticalPadding,
horizontal: horizontalPadding,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
text,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const SizedBox(height: 10),
GestureDetector(
onTap: onVideoTapped,
child: Container(
height: videoHeight,
width: videoHeight * 3 / 2,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
child: const Center(
child: Icon(
Icons.play_circle_outline,
size: 40,
color: Colors.white,
),
),
),
),
],
@ -141,10 +197,11 @@ class ListItemFactory {
);
}
///4
static Widget createAloneTextItem({
required String text,
double verticalPadding = 15,
double verticalPadding = 10,
double horizontalPadding = 0,
}) {
return Padding(
@ -175,13 +232,10 @@ class ListItemFactory {
required bool groupValue,
required ValueChanged<bool> onChanged,
double verticalPadding = 15,
double horizontalPadding = 0,
double horizontalPadding = 10,
}) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: verticalPadding,
horizontal: horizontalPadding,
),
padding: EdgeInsets.only(top: 0, right: horizontalPadding, left: horizontalPadding, bottom: verticalPadding),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
@ -232,6 +286,7 @@ class ListItemFactory {
),
);
}
/// +
static Widget createBuildSimpleSection(String title) {
return Container(
decoration: BoxDecoration(
@ -268,4 +323,35 @@ class ListItemFactory {
child: child,
);
}
///
static Widget createBuildMultilineInput(
String label,
String hint,
TextEditingController controller,
) {
return Container(
height: 130,
padding: const EdgeInsets.only(top: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
HhTextStyleUtils.mainTitle(label, fontSize: 15),
const SizedBox(height: 8),
Expanded(
child: TextField(
controller: controller,
keyboardType: TextInputType.multiline,
maxLines: null,
expands: true,
style: const TextStyle(fontSize: 15),
decoration: InputDecoration(
hintText: hint,
border: InputBorder.none,
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
///
/// Example:
/// ```dart
/// final choice = await BottomPicker.show<String>(
/// context,
/// items: ['选项1', '选项2', '选项3'],
/// itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
/// initialIndex: 1,
/// );
/// if (choice != null) {
/// // choice
/// }
/// ```
class BottomPicker {
///
///
/// [items]:
/// [itemBuilder]: Widget
/// [initialIndex]:
/// [itemExtent]:
/// [height]:
static Future<T?> show<T>(
BuildContext context, {
required List<T> items,
required Widget Function(T item) itemBuilder,
int initialIndex = 0,
double itemExtent = 40.0,
double height = 250,
}) {
//
T selected = items[initialIndex];
return showModalBottomSheet<T>(
context: context,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
),
builder: (ctx) {
return SizedBox(
height: height,
child: Column(
children: [
//
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.of(ctx).pop(selected),
child: const Text('确定'),
),
],
),
),
const Divider(height: 1),
//
Expanded(
child: CupertinoPicker(
scrollController:
FixedExtentScrollController(initialItem: initialIndex),
itemExtent: 30,
onSelectedItemChanged: (index) {
selected = items[index];
},
children: items.map(itemBuilder).toList(),
),
),
],
),
);
},
);
}
}

View File

@ -0,0 +1,147 @@
import 'package:flutter/material.dart';
import '../tools/tools.dart';
///
/// - /
/// -
class DannerRepainItem extends StatelessWidget {
final String title;
final List<String> details;
final bool showBottomTags;
final List<Widget> bottomTags;
final bool showTitleIcon;
const DannerRepainItem({
Key? key,
required this.title,
required this.details,
this.showBottomTags = false,
this.showTitleIcon = true,
this.bottomTags = const [],
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 15, right: 15, bottom: 15),
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(children: [
if (showTitleIcon)
const Icon(Icons.star_rate_sharp, color: Colors.green, size: 18),
SizedBox(width: showTitleIcon ? 5 : 0),
Text(title, style: const TextStyle(fontSize: 14)),
]),
const Icon(Icons.arrow_forward_ios_rounded, color: Colors.grey, size: 15),
],
),
),
const Divider(height: 1),
// /
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child: LayoutBuilder(builder: (context, constraints) {
//
const double horizontalGap = 20;
const double verticalGap = 5;
List<Widget> rows = [];
for (int i = 0; i < details.length; i += 2) {
final left = details[i];
final right = (i + 1 < details.length) ? details[i + 1] : '';
//
final leftPainter = TextPainter(
text: TextSpan(text: left, style: HhTextStyleUtils.secondaryTitleStyle),
maxLines: 1,
textDirection: TextDirection.ltr,
)..layout();
final rightPainter = TextPainter(
text: TextSpan(text: right, style: HhTextStyleUtils.secondaryTitleStyle),
maxLines: 1,
textDirection: TextDirection.ltr,
)..layout();
final canFitOneLine = right.isNotEmpty &&
(leftPainter.width + horizontalGap + rightPainter.width)
<= constraints.maxWidth;
if (right.isNotEmpty && canFitOneLine) {
//
rows.add(Padding(
padding: EdgeInsets.only(bottom: verticalGap),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_DetailText(left),
_DetailText(right),
],
),
));
} else {
//
rows.add(Padding(
padding: EdgeInsets.only(bottom: verticalGap),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//
_DetailText(left),
if (right.isNotEmpty)
//
Align(
alignment: Alignment.centerRight,
child: _DetailText(right),
),
],
),
));
}
}
return Column(children: rows);
}),
),
//
if (showBottomTags && bottomTags.isNotEmpty)
Padding(
padding: const EdgeInsets.only(left: 15, right: 15, bottom: 15),
child: Wrap(spacing: 5, runSpacing: 5, children: bottomTags),
),
],
),
),
);
}
}
/// Detail
///
class _DetailText extends StatelessWidget {
final String text;
const _DetailText(this.text, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(
text,
style: HhTextStyleUtils.secondaryTitleStyle,
softWrap: false,
overflow: TextOverflow.visible,
);
}
}

View File

@ -0,0 +1,265 @@
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:flutter/services.dart';
///VideoPlayerPopup
class VideoPlayerPopup extends StatefulWidget {
///
final String videoUrl;
const VideoPlayerPopup({Key? key, required this.videoUrl}) : super(key: key);
@override
State<VideoPlayerPopup> createState() => _VideoPlayerPopupState();
}
class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
late VideoPlayerController _controller;
bool _showControls = true;
@override
void initState() {
super.initState();
_controller = VideoPlayerController.networkUrl(
Uri.parse(widget.videoUrl),
)..initialize().then((_) {
setState(() {});
_controller.play();
});
//
_controller.addListener(() {
if (_controller.value.isPlaying && _showControls) {
Future.delayed(const Duration(seconds: 3), () {
if (_controller.value.isPlaying && mounted) {
setState(() => _showControls = false);
}
});
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Widget _buildControls() {
final pos = _controller.value.position;
final dur = _controller.value.duration;
String fmt(Duration d) =>
'${d.inMinutes.remainder(60).toString().padLeft(2, '0')}:'
'${d.inSeconds.remainder(60).toString().padLeft(2, '0')}';
return Positioned.fill(
child: AnimatedOpacity(
opacity: _showControls ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: GestureDetector(
onTap: () => setState(() => _showControls = !_showControls),
child: Container(
color: Colors.black45,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
//
Slider(
value: pos.inMilliseconds.toDouble().clamp(0, dur.inMilliseconds.toDouble()),
max: dur.inMilliseconds.toDouble(),
onChanged: (v) {
_controller.seekTo(Duration(milliseconds: v.toInt()));
},
),
// +
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
IconButton(
icon: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
color: Colors.white,
),
onPressed: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
),
Text(
'${fmt(pos)} / ${fmt(dur)}',
style: const TextStyle(color: Colors.white),
),
const Spacer(),
IconButton(
icon: const Icon(Icons.fullscreen, color: Colors.white),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => FullScreenVideoPage(
controller: _controller)));
},
),
],
),
),
],
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Center(
child: Material(
color: Colors.transparent,
child: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.9,
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Stack(
children: [
//
if (_controller.value.isInitialized)
AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
else
const Center(child: CircularProgressIndicator()),
//
Positioned(
top: 4,
right: 4,
child: IconButton(
icon: const Icon(Icons.close, color: Colors.black54),
onPressed: () => Navigator.of(context).pop(),
),
),
//
if (_controller.value.isInitialized) _buildControls(),
],
),
),
),
);
}
}
/// FullScreenVideoPage
class FullScreenVideoPage extends StatefulWidget {
final VideoPlayerController controller;
const FullScreenVideoPage({Key? key, required this.controller}) : super(key: key);
@override
State<FullScreenVideoPage> createState() => _FullScreenVideoPageState();
}
class _FullScreenVideoPageState extends State<FullScreenVideoPage> {
VideoPlayerController get _controller => widget.controller;
@override
void initState() {
super.initState();
//
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
}
@override
void dispose() {
//
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: [
//
if (_controller.value.isInitialized)
SizedBox.expand(child: VideoPlayer(_controller))
else
const Center(child: CircularProgressIndicator()),
// /
GestureDetector(
onTap: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
),
//
Positioned(
top: 20,
left: 20,
child: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white, size: 28),
onPressed: () => Navigator.of(context).pop(),
),
),
// &
if (_controller.value.isInitialized)
Positioned(
bottom: 20,
left: 20,
right: 20,
child: Row(
children: [
Text(
'${_format(_controller.value.position)} / ${_format(_controller.value.duration)}',
style: const TextStyle(color: Colors.white),
),
const SizedBox(width: 12),
Expanded(
child: VideoProgressIndicator(
_controller,
allowScrubbing: true,
colors: VideoProgressColors(
playedColor: Colors.red,
bufferedColor: Colors.white54,
backgroundColor: Colors.white30,
),
),
),
],
),
),
],
),
),
);
}
String _format(Duration d) =>
'${d.inMinutes.remainder(60).toString().padLeft(2, '0')}:'
'${d.inSeconds.remainder(60).toString().padLeft(2, '0')}';
}

View File

@ -1,51 +1,74 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:qhd_prevention/tools/h_colors.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:photo_manager/photo_manager.dart';
///
import 'ItemWidgetFactory.dart';
///
enum MediaType { image, video }
///
/// 使
/// PhotoPickerRow(
/// MediaPickerRow(
/// maxCount: 4,
/// onChanged: (List<File> images) {
/// // images
/// mediaType: MediaType.video,
/// onChanged: (List<File> medias) {
/// // medias
/// },
/// ),
class PhotoPickerRow extends StatefulWidget {
class MediaPickerRow extends StatefulWidget {
final int maxCount;
final MediaType mediaType;
final ValueChanged<List<File>> onChanged;
const PhotoPickerRow({
const MediaPickerRow({
Key? key,
this.maxCount = 4,
this.mediaType = MediaType.image,
required this.onChanged,
}) : super(key: key);
@override
_PhotoPickerRowState createState() => _PhotoPickerRowState();
_MediaPickerRowState createState() => _MediaPickerRowState();
}
class _PhotoPickerRowState extends State<PhotoPickerRow> {
class _MediaPickerRowState extends State<MediaPickerRow> {
final ImagePicker _picker = ImagePicker();
final List<File> _images = [];
final List<File> _files = [];
Future<void> _showPickerOptions() async {
showModalBottomSheet(
context: context,
builder: (_) => SafeArea(
builder:
(_) => SafeArea(
child: Wrap(
children: [
ListTile(
leading: const Icon(Icons.camera_alt),
title: const Text('拍照'),
leading: Icon(
widget.mediaType == MediaType.image
? Icons.camera_alt
: Icons.videocam,
),
title: Text(
widget.mediaType == MediaType.image ? '拍照' : '拍摄视频',
),
onTap: () {
Navigator.of(context).pop();
_pickCamera();
},
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('从相册选择'),
leading: Icon(
widget.mediaType == MediaType.image
? Icons.photo_library
: Icons.video_library,
),
title: Text(
widget.mediaType == MediaType.image ? '从相册选择' : '从相册选择视频',
),
onTap: () {
Navigator.of(context).pop();
_pickGallery();
@ -63,45 +86,69 @@ class _PhotoPickerRowState extends State<PhotoPickerRow> {
}
Future<void> _pickCamera() async {
if (_images.length >= widget.maxCount) return;
final XFile? picked = await _picker.pickImage(source: ImageSource.camera);
if (_files.length >= widget.maxCount) return;
try {
XFile? picked;
if (widget.mediaType == MediaType.image) {
picked = await _picker.pickImage(source: ImageSource.camera);
} else {
picked = await _picker.pickVideo(source: ImageSource.camera);
}
if (picked != null) {
setState(() {
_images.add(File(picked.path));
_files.add(File(picked!.path));
});
widget.onChanged(_images);
widget.onChanged(_files);
}
} catch (e) {
debugPrint('拍摄失败: \$e');
}
}
Future<void> _pickGallery() async {
if (_images.length >= widget.maxCount) return;
final remaining = widget.maxCount - _images.length;
if (_files.length >= widget.maxCount) return;
final permission = await PhotoManager.requestPermissionExtend();
if (permission != PermissionState.authorized &&
permission != PermissionState.limited) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('请到设置中开启相册访问权限')));
return;
}
try {
final remaining = widget.maxCount - _files.length;
final List<AssetEntity>? assets = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(
requestType: RequestType.image,
requestType:
widget.mediaType == MediaType.image
? RequestType.image
: RequestType.video,
maxAssets: remaining,
gridCount: 4,
),
);
if (assets != null && assets.isNotEmpty) {
if (assets != null) {
for (final asset in assets) {
if (_images.length >= widget.maxCount) break;
if (_files.length >= widget.maxCount) break;
final file = await asset.file;
if (file != null) {
_images.add(file);
_files.add(file);
}
}
setState(() {});
widget.onChanged(_images);
widget.onChanged(_files);
}
} catch (e) {
debugPrint('相册选择失败: \$e');
}
}
void _removeImage(int index) {
void _removeFile(int index) {
setState(() {
_images.removeAt(index);
_files.removeAt(index);
});
widget.onChanged(_images);
widget.onChanged(_files);
}
@override
@ -110,22 +157,35 @@ class _PhotoPickerRowState extends State<PhotoPickerRow> {
height: 80,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: _images.length < widget.maxCount
? _images.length + 1
itemCount:
_files.length < widget.maxCount
? _files.length + 1
: widget.maxCount,
separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (context, index) {
if (index < _images.length) {
//
if (index < _files.length) {
return Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Image.file(
_images[index],
child:
widget.mediaType == MediaType.image
? Image.file(
_files[index],
width: 80,
height: 80,
fit: BoxFit.cover,
)
: Container(
width: 80,
height: 80,
color: Colors.black12,
child: const Center(
child: Icon(
Icons.videocam,
color: Colors.white70,
),
),
),
),
Positioned(
@ -133,24 +193,28 @@ class _PhotoPickerRowState extends State<PhotoPickerRow> {
right: -6,
child: IconButton(
icon: const Icon(Icons.cancel, size: 20, color: Colors.red),
onPressed: () => _removeImage(index),
onPressed: () => _removeFile(index),
),
),
],
);
} else {
//
return GestureDetector(
onTap: _showPickerOptions,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
border: Border.all(color: Colors.black12),
borderRadius: BorderRadius.circular(5),
),
child: const Center(
child: Icon(Icons.camera_alt, color: Colors.grey),
child: Center(
child: Icon(
widget.mediaType == MediaType.image
? Icons.camera_alt
: Icons.videocam,
color: Colors.black26,
),
),
),
);
@ -160,3 +224,76 @@ class _PhotoPickerRowState extends State<PhotoPickerRow> {
);
}
}
///
class RepairedPhotoSection extends StatelessWidget {
final int maxCount;
final MediaType mediaType;
final String title;
final ValueChanged<List<File>> onChanged;
final VoidCallback onAiIdentify;
final bool isShowAI;
final double horizontalPadding;
const RepairedPhotoSection({
Key? key,
this.maxCount = 4,
this.mediaType = MediaType.image,
required this.title,
this.isShowAI = false,
required this.onChanged,
required this.onAiIdentify,
this.horizontalPadding = 10,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
padding: const EdgeInsets.only(left: 5, right: 10),
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: title,
rightText: '0/$maxCount',
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
child: MediaPickerRow(
maxCount: maxCount,
mediaType: mediaType,
onChanged: onChanged,
),
),
const SizedBox(height: 20),
if (isShowAI)
Row(
children: [
GestureDetector(
onTap: onAiIdentify,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15),
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFDFEAFF),
borderRadius: BorderRadius.circular(18),
),
child: Row(
children: [
Image.asset('assets/images/ai_img.png', width: 20),
const SizedBox(width: 5),
const Text('AI隐患识别与处理'),
],
),
),
),
],
),
],
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
//
class SingleImageViewer extends StatelessWidget {
final String imageUrl;
@ -9,9 +10,8 @@ class SingleImageViewer extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
appBar: MyAppbar(
backgroundColor: Colors.transparent, title: '',
),
body: Center(
child: PhotoView(

View File

@ -27,9 +27,19 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: '',
builder: (context, child) {
return GestureDetector(
behavior: HitTestBehavior.translucent, //
onTap: () {
//
FocusScope.of(context).unfocus();
},
child: child,
);
},
theme: ThemeData(
dividerTheme: const DividerThemeData(
color: Colors.black12,
color: Color(0xF1F1F1FF),
thickness: 1, // 线
indent: 0, //
endIndent: 0, //

View File

@ -0,0 +1,176 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/danner_repain_item.dart';
import 'package:qhd_prevention/customWidget/department_picker.dart';
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
import 'package:qhd_prevention/pages/home/work/risk_list_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/SmallWidget.dart';
import 'package:qhd_prevention/tools/tools.dart';
class CheckRecordListPage extends StatefulWidget {
const CheckRecordListPage({super.key});
@override
_CheckRecordListPageState createState() => _CheckRecordListPageState();
}
class _CheckRecordListPageState extends State<CheckRecordListPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
int _selectedTab = 0;
//
final List<NotificationItem> _notifications = List.generate(10, (i) {
bool read = i % 3 == 0;
String title = '测试数据标题标题 ${i + 1}';
String time = '2025-06-${10 + i} 12:3${i}';
return NotificationItem(title, time);
});
final List<Category> data = [
Category(
id: '1',
title: '分类一',
children: [
Category(id: '1-1', title: '子项 1-1'),
Category(id: '1-2', title: '子项 1-2'),
],
),
Category(id: '2', title: '分类二'),
Category(
id: '3',
title: '分类三',
children: [
Category(
id: '3-1',
title: '子项 3-1',
children: [Category(id: '3-1-1', title: '子项 3-1-1')],
),
],
),
];
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_tabController.addListener(() {
if (!_tabController.indexIsChanging) {
setState(() => _selectedTab = _tabController.index);
}
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _handleItemTap(NotificationItem item, int index) {
print("点击了是: ${item.title}");
}
//
void showCategoryPicker() {
showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: Colors.black54,
backgroundColor: Colors.transparent,
builder:
(ctx) => DepartmentPicker(
data: data,
onSelected: (selectedId) {
setState(() {});
},
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(
title: "清单检查记录",
),
body: SafeArea(
child: Column(
children: [
// Tab bar
TabBar(
controller: _tabController,
labelStyle: TextStyle(fontSize: 16),
indicator: UnderlineTabIndicator(
borderSide: BorderSide(width: 3.0, color: Colors.blue),
insets: EdgeInsets.symmetric(horizontal: 100.0),
),
labelColor: Colors.blue,
unselectedLabelColor: Colors.grey,
tabs: const [Tab(text: '已检查记录'), Tab(text: '超期未检查记录')],
),
// Search bar
Padding(
padding: const EdgeInsets.all(10),
child: SearchBarWidget(
showResetButton: false,
onInputTap: () {
showCategoryPicker();
},
hintText: "",
isClickableOnly: true,
onSearch: (text) {
print('----------');
},
controller: _searchController,
),
),
// List
Expanded(
child: ListView.separated(
itemCount: _notifications.length,
separatorBuilder: (_, __) => const SizedBox(),
itemBuilder: (context, index) {
NotificationItem item = _notifications[index];
return GestureDetector(
onTap: () => _handleItemTap(item, index),
child: DannerRepainItem(
title: '清单名称:测试的时候写的假数据',
showTitleIcon: false,
details: [
'清单类型:测试',
'排查周期:测试',
'包含检查项3',
'负责人:是测试',
'起始时间2025-6-20',
'',
'测试一下是否跳过时间'
],
showBottomTags: false,
),
);
},
),
),
],
),
),
);
}
}
//
class NotificationItem {
final String title;
final String time;
NotificationItem(this.title, this.time);
}

View File

@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
import 'package:qhd_prevention/pages/app/Danger_paicha/check_record_list_page.dart';
import 'package:qhd_prevention/pages/app/Danger_paicha/custom_record_drawer.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/tools.dart';
import '../../../Model/list_model.dart';
import '../../../customWidget/danner_repain_item.dart';
import '../../home/scan_page.dart';
class CheckRecordPage extends StatefulWidget {
const CheckRecordPage({super.key});
@override
State<CheckRecordPage> createState() => _CheckRecordPageState();
}
class _CheckRecordPageState extends State<CheckRecordPage> {
final TextEditingController _searchController = TextEditingController();
final List<RecordCheckModel> _notifications = List.generate(10, (i) {
bool read = i % 3 == 0;
String title = '测试数据标题标题 ${i + 1}';
String time = '2025-06-${10 + i} 12:3${i}';
return RecordCheckModel(title, time);
});
void _handleItemTap(RecordCheckModel model) {
pushPage(CheckRecordListPage(), context);
}
@override
Widget build(BuildContext context) {
//
final double screenWidth = MediaQuery.of(context).size.width;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
return Scaffold(
key: _scaffoldKey, // key
appBar: MyAppbar(
title: "清单检查记录",
actions: [
TextButton(
onPressed: () {
_scaffoldKey.currentState?.openEndDrawer();
},
child: Text(
"查询",
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
],
),
endDrawer: Drawer(
// Container 3/5
child: Container(
width: screenWidth * 3 / 5,
color: Colors.white,
child: const CustomRecordDrawer(),
),
),
body: SafeArea(child: Column(
children: [
Container(
padding: EdgeInsets.all(15),
color: Colors.white,
child: SearchBarWidget(
controller: _searchController,
onSearch: (keyboard) {
//
},
),
),
Expanded(
child: ListView.separated(
padding: EdgeInsets.only(top: 15),
itemCount: _notifications.length,
separatorBuilder: (_, __) => const SizedBox(),
itemBuilder: (context, index) {
RecordCheckModel item = _notifications[index];
return GestureDetector(
onTap: () => _handleItemTap(item),
child: DannerRepainItem(
showTitleIcon: false,
title: '测试--------new',
details: [
'清单类型:测试',
'排查周期:测试',
'包含检查项3',
'',
'起始时间2025-6-20------',
'测试',"ccccc",'sssss'
],
showBottomTags: false,
),
);
},
),
)
],
)),
);
}
}

View File

@ -0,0 +1,312 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/bottom_picker.dart';
import 'package:qhd_prevention/customWidget/department_picker.dart';
import '../../../tools/h_colors.dart';
import '/customWidget/custom_button.dart';
///
class CustomRecordDrawer extends StatefulWidget {
const CustomRecordDrawer({super.key});
@override
_CustomRecordDrawerState createState() => _CustomRecordDrawerState();
}
class _CustomRecordDrawerState extends State<CustomRecordDrawer> {
// index
int _selectedOption = -1;
// id
String? _selectedCategoryId;
//
String? _selectedPerson;
//
String? _selectedQDType;
//
String? _selectedZQTime;
// /
DateTime? _startDate;
DateTime? _endDate;
@override
Widget build(BuildContext context) {
final List<Category> data = [
Category(
id: '1',
title: '分类一1',
children: [
Category(id: '1-1', title: '子项 1-1'),
Category(id: '1-2', title: '子项 1-2'),
],
),
Category(id: '2', title: '分类二'),
Category(
id: '3',
title: '分类三',
children: [
Category(id: '3-1', title: '子项 3-1', children: [
Category(id: '3-1-1', title: '子项 3-1-1'),
]),
],
),
];
Future<void> showCategoryPicker(int type) async {
if (type == 1) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: Colors.black54,
backgroundColor: Colors.transparent,
builder: (ctx) => DepartmentPicker(
data: data,
onSelected: (selectedId) {
setState(() {
_selectedCategoryId = selectedId;
});
},
),
);
} else if (type == 2) {
final choice = await BottomPicker.show<String>(
context,
items: ['未知'],
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
initialIndex: 0,
);
if (choice != null) {
// choice
setState(() {
_selectedPerson = choice;
});
}
}else if (type == 3) {
final choice = await BottomPicker.show<String>(
context,
items: ['日常', '综合', '专业', '季节性','节假日'],
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
initialIndex: 0,
);
if (choice != null) {
setState(() {
_selectedQDType = choice;
});
}
}else if (type == 4) {
final choice = await BottomPicker.show<String>(
context,
items: ['每日', '每周', '每旬', '每月', '每季', '半年', '每年'],
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
initialIndex: 0,
);
if (choice != null) {
setState(() {
_selectedZQTime = choice;
});
}
}
}
Future<void> _pickStartDate() async {
final now = DateTime.now();
final picked = await showDatePicker(
context: context,
initialDate: _startDate ?? now,
firstDate: DateTime(now.year - 5),
lastDate: DateTime(now.year + 5),
);
if (picked != null) {
setState(() {
_startDate = picked;
// <=
if (_endDate != null && _endDate!.isBefore(picked)) {
_endDate = null;
}
});
}
}
Future<void> _pickEndDate() async {
final now = DateTime.now();
final initial = _endDate ??
(_startDate != null && _startDate!.isAfter(now)
? _startDate!
: now);
final picked = await showDatePicker(
context: context,
initialDate: initial,
firstDate: _startDate ?? DateTime(now.year - 5),
lastDate: DateTime(now.year + 5),
);
if (picked != null) {
setState(() {
_endDate = picked;
});
}
}
Widget _buildDatePickerBox({
required String label,
DateTime? date,
required VoidCallback onTap,
}) {
final display = date != null
? "${date.year.toString().padLeft(4, '0')}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}"
: label;
return Expanded(
child: GestureDetector(
onTap: onTap,
child: Container(
height: 35,
padding: const EdgeInsets.symmetric(horizontal: 5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.grey.shade400),
color: Colors.white,
),
child: Row(
children: [
const Icon(Icons.calendar_today, size: 18, color: Colors.grey),
const SizedBox(width: 6),
Text(display, style: const TextStyle(fontSize: 14, color: Colors.black38)),
],
),
),
),
);
}
return SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"高级查询",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const Divider(height: 24, color: Colors.grey),
//
_buildDropdownBox(
"检查部门",
display: _selectedCategoryId ?? '请选择',
onTap: () => showCategoryPicker(1),
),
const SizedBox(height: 12),
_buildDropdownBox(
"检查人",
display: _selectedPerson ?? '请选择',
onTap: () => showCategoryPicker(2),
),
const SizedBox(height: 12),
_buildDropdownBox(
"清单类型",
display: _selectedQDType ?? '请选择',
onTap: () => showCategoryPicker(3),
),
const SizedBox(height: 12),
_buildDropdownBox(
"排查周期",
display: _selectedZQTime ?? '请选择',
onTap: () => showCategoryPicker(4),
),
const SizedBox(height: 24),
//
const Text(
"时间查询",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
const SizedBox(height: 12),
// - //
Row(
children: [
_buildDatePickerBox(
label: "开始时间",
date: _startDate,
onTap: _pickStartDate,
),
const SizedBox(width: 5),
const Text("", style: TextStyle(fontSize: 8)),
const SizedBox(width: 5),
_buildDatePickerBox(
label: "结束时间",
date: _endDate,
onTap: _pickEndDate,
),
],
),
const Spacer(),
//
Row(
children: [
Expanded(
flex: 1,
child: CustomButton(
text: "重置",
backgroundColor: h_backGroundColor(),
textStyle: const TextStyle(color: Colors.black45),
onPressed: () {
setState(() {
_selectedOption = -1;
_selectedCategoryId = null;
_startDate = null;
_endDate = null;
});
},
),
),
const SizedBox(width: 12),
Expanded(
flex: 2,
child: CustomButton(
text: "完成",
backgroundColor: Colors.blue,
onPressed: () {
// TODO: _startDate_endDate
},
),
),
],
),
],
),
),
);
}
Widget _buildDropdownBox(String title,
{required String display, required VoidCallback onTap}) {
return GestureDetector(
onTap: onTap,
child: Container(
height: 48,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.grey.shade400),
color: Colors.white,
),
child: Row(
children: [
Text(title, style: const TextStyle(fontSize: 14)),
const Spacer(),
Row(
children: [
Text(display),
const Icon(Icons.arrow_drop_down, color: Colors.grey),
],
),
],
),
),
);
}
}

View File

@ -0,0 +1,196 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import '../../../customWidget/photo_picker_row.dart';
class QuickReportPage extends StatefulWidget {
const QuickReportPage({super.key});
@override
State<QuickReportPage> createState() => _QuickReportPageState();
}
class _QuickReportPageState extends State<QuickReportPage> {
final _standardController = TextEditingController();
final _partController = TextEditingController();
final _dangerDetailController = TextEditingController();
final String _repairLevel = "请选择";
final String _repairType = "请选择";
final String _dangerOrganize = "请选择";
final String _dangerTime = "请选择";
late bool _isDanger = false;
@override
void dispose() {
_standardController.dispose();
_partController.dispose();
_dangerDetailController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: "隐患随手拍"),
body: Column(
children: [
//
_pageDetail(),
//
Container(
padding: const EdgeInsets.all(15),
color: Colors.white,
child: Text(
' 严禁在本互联网非涉密平台处理、传输国家秘密和工作秘密,请确认扫描、传输的文件资料不涉及国家秘密和工作秘密',
style: TextStyle(fontSize: 14, color: Colors.red),
),
),
],
),
);
}
Widget _buildSectionContainer({required Widget child}) {
return Container(
margin: const EdgeInsets.only(top: 10),
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: child,
);
}
Widget _pageDetail() {
return Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildSectionContainer(
child: RepairedPhotoSection(
title: "隐患照片",
maxCount: 4,
mediaType: MediaType.image,
isShowAI: true,
onChanged: (List<File> files) {
// files
},
onAiIdentify: () {
// AI
},
),
),
_buildSectionContainer(
child: RepairedPhotoSection(
title: "隐患视频",
maxCount: 1,
mediaType: MediaType.video,
onChanged: (List<File> files) {
// files
},
onAiIdentify: () {
// AI
},
),
),
_buildSectionContainer(
child: ListItemFactory.createBuildMultilineInput(
"隐患描述",
"请对隐患进行详细描述(必填项)",
_standardController,
),
),
_buildSectionContainer(
child: ListItemFactory.createBuildMultilineInput(
"隐患部位",
"请对隐患部位进行详细描述(必填项)",
_partController,
),
),
_buildSectionContainer(
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: "隐患级别",
rightText: _repairLevel,
isRight: true,
),
),
_buildSectionContainer(
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: "隐患类型",
rightText: _repairType,
isRight: true,
),
),
_buildSectionContainer(
child: ListItemFactory.createYesNoSection(
title: "是否立即整改",
horizontalPadding: 0,
verticalPadding: 0,
yesLabel: "",
noLabel: "",
groupValue: _isDanger,
onChanged: (val) {
setState(() {
_isDanger = val;
});
},
),
),
if (_isDanger)
Column(
children: [
_buildSectionContainer(
child: ListItemFactory.createBuildMultilineInput(
"整改描述",
"请对隐患进行整改描述(必填项)",
_dangerDetailController,
),
),
SizedBox(height: 10),
_buildSectionContainer(
child: RepairedPhotoSection(
title: "整改后图片",
maxCount: 4,
horizontalPadding: 0,
mediaType: MediaType.image,
isShowAI: false,
onChanged: (List<File> files) {
// files
},
onAiIdentify: () {},
),
),
],
),
if (!_isDanger)
Column(
children: [
_buildSectionContainer(
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: "整改责任部门",
rightText: _dangerOrganize,
isRight: true,
),
),
_buildSectionContainer(
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: "整改期限",
rightText: _dangerTime,
isRight: true,
),
),
],
),
SizedBox(height: 30),
CustomButton(text: "提交", backgroundColor: Colors.blue),
],
),
),
);
}
}

View File

@ -1,53 +1,120 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/pages/app/Danger_paicha/check_record_page.dart';
import 'package:qhd_prevention/pages/app/Danger_paicha/quick_report_page.dart';
import 'package:qhd_prevention/pages/home/work/danger_wait_list_page.dart';
import 'package:qhd_prevention/pages/home/work/risk_list_page.dart';
import '../../tools/tools.dart';
import '../home/userInfo_page.dart';
import '../home/work/danger_page.dart';
import '../home/work/danger_repair_page.dart';
enum AppItem {
riskInspection, //
quickReport, //
checkRecord, //
riskRecord, //
pendingRectification, //
overdueRectification, //
riskAcceptance, //
acceptedRisk, //
specialRectification, //
specialRecord, //
supervisionRectification, //
supervisionRecord, //
}
class ApplicationPage extends StatelessWidget {
const ApplicationPage({Key? key}) : super(key: key);
//
void _handleItemClick(BuildContext context, AppItem item) {
switch (item) {
case AppItem.riskInspection:
//
pushPage(DangerPage(), context);
break;
case AppItem.quickReport:
//
pushPage(QuickReportPage(), context);
break;
case AppItem.checkRecord:
//
pushPage(CheckRecordPage(), context);
break;
case AppItem.riskRecord:
//
pushPage(DangerWaitListPage(DangerType.ristRecord), context);
break;
case AppItem.pendingRectification:
//
pushPage(DangerWaitListPage(DangerType.wait), context);
break;
case AppItem.overdueRectification:
//
pushPage(DangerWaitListPage(DangerType.expired), context);
break;
case AppItem.riskAcceptance:
//
pushPage(DangerWaitListPage(DangerType.waitAcceptance), context);
break;
case AppItem.acceptedRisk:
//
pushPage(DangerWaitListPage(DangerType.acceptanced), context);
break;
case AppItem.specialRectification:
//
//Navigator.push(context, MaterialPageRoute(builder: (_) => SpecialRectificationPage()));
break;
case AppItem.specialRecord:
//
// Navigator.push(context, MaterialPageRoute(builder: (_) => SpecialRecordPage()));
break;
case AppItem.supervisionRectification:
//
//Navigator.push(context, MaterialPageRoute(builder: (_) => SupervisionRectificationPage()));
break;
case AppItem.supervisionRecord:
//
//Navigator.push(context, MaterialPageRoute(builder: (_) => SupervisionRecordPage()));
break;
}
}
@override
Widget build(BuildContext context) {
// 使
final List<Map<String, dynamic>> buttonInfos = [
{
'title': '隐患排查',
'list': [
{'icon': 'assets/icon-apps/icon-zl-6.png', 'title': '隐患排查', 'num': 0},
{'icon': 'assets/icon-apps/icon-pc-1.png', 'title': '隐患快报', 'num': 2},
{'icon': 'assets/icon-apps/icon-zl-2.png', 'title': '检查记录', 'num': 0},
{'item': AppItem.riskInspection, 'icon': 'assets/icon-apps/icon-zl-6.png', 'title': '隐患排查', 'num': 0},
{'item': AppItem.quickReport, 'icon': 'assets/icon-apps/icon-pc-1.png', 'title': '隐患快报', 'num': 2},
{'item': AppItem.checkRecord, 'icon': 'assets/icon-apps/icon-zl-2.png', 'title': '检查记录', 'num': 0},
],
},
{
'title': '隐患治理',
'list': [
{'icon': 'assets/icon-apps/icon-zl-2.png', 'title': '隐患记录', 'num': 1},
{
'icon': 'assets/icon-apps/icon-zl-3.png',
'title': '待整改隐患',
'num': 3,
},
{
'icon': 'assets/icon-apps/icon-zl-4.png',
'title': '超期未整改',
'num': 0,
},
{'icon': 'assets/icon-apps/icon-yh-1.png', 'title': '隐患验收', 'num': 2},
{
'icon': 'assets/icon-apps/icon-zl-1.png',
'title': '已验收隐患',
'num': 0,
},
{'item': AppItem.riskRecord, 'icon': 'assets/icon-apps/icon-zl-2.png', 'title': '隐患记录', 'num': 1},
{'item': AppItem.pendingRectification, 'icon': 'assets/icon-apps/icon-zl-3.png', 'title': '待整改隐患', 'num': 3},
{'item': AppItem.overdueRectification, 'icon': 'assets/icon-apps/icon-zl-4.png', 'title': '超期未整改', 'num': 0},
{'item': AppItem.riskAcceptance, 'icon': 'assets/icon-apps/icon-yh-1.png', 'title': '隐患验收', 'num': 2},
{'item': AppItem.acceptedRisk, 'icon': 'assets/icon-apps/icon-zl-1.png', 'title': '已验收隐患', 'num': 0},
],
},
{
'title': '专项检查',
'list': [
{'icon': 'assets/icon-apps/icon-pc-1.png', 'title': '隐患整改', 'num': 5},
{'icon': 'assets/icon-apps/icon-zl-2.png', 'title': '隐患记录', 'num': 0},
{'item': AppItem.specialRectification, 'icon': 'assets/icon-apps/icon-pc-1.png', 'title': '隐患整改', 'num': 5},
{'item': AppItem.specialRecord, 'icon': 'assets/icon-apps/icon-zl-2.png', 'title': '隐患记录', 'num': 0},
],
},
{
'title': '监管帮扶',
'list': [
{'icon': 'assets/icon-apps/icon-pc-1.png', 'title': '隐患整改', 'num': 2},
{'icon': 'assets/icon-apps/icon-zl-2.png', 'title': '隐患记录', 'num': 0},
{'item': AppItem.supervisionRectification, 'icon': 'assets/icon-apps/icon-pc-1.png', 'title': '隐患整改', 'num': 2},
{'item': AppItem.supervisionRecord, 'icon': 'assets/icon-apps/icon-zl-2.png', 'title': '隐患记录', 'num': 0},
],
},
];
@ -58,7 +125,6 @@ class ApplicationPage extends StatelessWidget {
padding: const EdgeInsets.all(0),
itemCount: buttonInfos.length + 1,
itemBuilder: (context, index) {
//
if (index == 0) {
return ClipRRect(
child: Image.asset(
@ -67,7 +133,7 @@ class ApplicationPage extends StatelessWidget {
),
);
}
// section
final section = buttonInfos[index - 1];
final items = section['list'] as List<dynamic>;
return Padding(
@ -80,7 +146,6 @@ class ApplicationPage extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Section title
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 5),
child: Row(
@ -97,23 +162,23 @@ class ApplicationPage extends StatelessWidget {
],
),
),
// Items wrap
Padding(
padding: const EdgeInsets.all(10),
child: LayoutBuilder(
builder: (context, constraints) {
const spacing = 10.0;
// 4 items per row
final totalWidth = constraints.maxWidth;
final itemWidth = (totalWidth - spacing * 3) / 4;
return Wrap(
spacing: spacing,
runSpacing: spacing,
children:
items.map<Widget>((item) {
children: items.map<Widget>((item) {
return SizedBox(
width: itemWidth,
child: _buildItem(item),
child: _buildItem(
item,
onTap: () => _handleItemClick(context, item['item'] as AppItem),
),
);
}).toList(),
);
@ -129,15 +194,19 @@ class ApplicationPage extends StatelessWidget {
);
}
Widget _buildItem(Map<String, dynamic> item) {
// onTap
Widget _buildItem(Map<String, dynamic> item, {VoidCallback? onTap}) {
const double size = 60;
final int badgeNum = item['num'] as int;
return SizedBox(
return InkWell(
onTap: onTap, //
borderRadius: BorderRadius.circular(8),
child: SizedBox(
width: size,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Image with badge overlay
Stack(
clipBehavior: Clip.none,
children: [
@ -174,6 +243,8 @@ class ApplicationPage extends StatelessWidget {
),
],
),
),
);
}
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/custom_driver_drawer.dart';
import 'package:qhd_prevention/pages/home/work/custom_driver_drawer.dart';
import 'package:qhd_prevention/pages/home/risk/risk_detail_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/tools.dart';

View File

@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/department_picker.dart';
import '../tools/h_colors.dart';
import '../../../customWidget/bottom_picker.dart';
import '../../../tools/h_colors.dart';
import '/customWidget/custom_button.dart';
import '../tools/tools.dart';
import '../../../tools/tools.dart';
///
class CustomDriverDrawer extends StatefulWidget {
@ -24,7 +25,7 @@ class _CustomDriverDrawerState extends State<CustomDriverDrawer> {
final List<Category> data = [
Category(
id: '1',
title: '分类一',
title: '分类一1',
children: [
Category(id: '1-1', title: '子项 1-1'),
Category(id: '1-2', title: '子项 1-2'),
@ -89,7 +90,18 @@ class _CustomDriverDrawerState extends State<CustomDriverDrawer> {
"风险点(单元)",
display: '请选择',
onTap: () {
// TODO: B
final choice = BottomPicker.show<String>(
context,
items: ['未知'],
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
initialIndex: 0,
);
if (choice != null) {
// choice
setState(() {
});
}
},
),

View File

@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class DangerChange extends StatefulWidget {
const DangerChange({super.key});
@override
State<DangerChange> createState() => _DangerChangeState();
}
class _DangerChangeState extends State<DangerChange> {
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import '../../../../customWidget/ItemWidgetFactory.dart';
import '../../../../customWidget/single_image_viewer.dart';
import '../../../../tools/tools.dart';
class DangerDetail extends StatelessWidget {
const DangerDetail({super.key});
@override
Widget build(BuildContext context) {
final List<String> imgUrls = [
"https://pic.rmb.bdstatic.com/bjh/news/100b8b78cbd136ede03249d9f3b3f5c42221.jpeg",
"https://pic.rmb.bdstatic.com/bjh/news/100b8b78cbd136ede03249d9f3b3f5c42221.jpeg",
];
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: 16,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
Widget item;
if (index == 0) {
item = ListItemFactory.createAloneTextItem(
text: "地坪漆漆未分配全皮肤期漆未分配全皮肤期漆未分配全皮肤期未分配全皮肤期间哦飞机哦脾气金佛怕",
);
} else if ((index > 0 && index < 4) ||
index == 5 ||
(index > 6 && index < 15)) {
item = ListItemFactory.createRowSpaceBetweenItem(
leftText: "隐患来源",
rightText: "隐患排查",
);
} else if (index == 4 || index == 6) {
item = ListItemFactory.createColumnTextItem(
topText: "存在风险",
bottomText: "哦IQ好然后前后hi前后哦i",
);
} else {
item = ListItemFactory.createTextImageItem(
text: "隐患照片",
imageUrls: imgUrls,
onImageTapped: (index) {
present(
SingleImageViewer(imageUrl: imgUrls[index]),
context,
);
},
);
}
// item
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
child: item,
);
},
),
],
),
);
}
}

View File

@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import '../../../../../customWidget/ItemWidgetFactory.dart';
import '../../../../../customWidget/full_screen_video_page.dart';
import '../../../../../customWidget/single_image_viewer.dart';
import '../../../../../tools/tools.dart';
class DangerAcceptanceFinish extends StatefulWidget {
const DangerAcceptanceFinish({super.key});
@override
State<DangerAcceptanceFinish> createState() => _DangerAcceptanceFinishState();
}
class _DangerAcceptanceFinishState extends State<DangerAcceptanceFinish> {
final List<String> imgUrls = [
"https://pic.rmb.bdstatic.com/bjh/news/100b8b78cbd136ede03249d9f3b3f5c42221.jpeg",
"https://pic.rmb.bdstatic.com/bjh/news/100b8b78cbd136ede03249d9f3b3f5c42221.jpeg",
];
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 0),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//
ListItemFactory.createBuildSimpleSection("验收信息"),
const Divider(height: 1),
// summary
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListItemFactory.createColumnTextItem(
topText: "验收描述",
bottomText: "这是一段整改描述",
),
ListItemFactory.createRowSpaceBetweenItem(
leftText: "是否合格",
rightText: "--",
),
const Divider(height: 10),
ListItemFactory.createRowSpaceBetweenItem(
leftText: "验收部门",
rightText: "部门",
),
const Divider(height: 10),
ListItemFactory.createRowSpaceBetweenItem(
leftText: "验收部门负责人",
rightText: "韩双",
),
const Divider(height: 10),
ListItemFactory.createRowSpaceBetweenItem(
leftText: "验收时间",
rightText: "2020-01-01",
),
ListItemFactory.createTextImageItem(
text: "验收图片",
imageUrls: imgUrls,
onImageTapped: (index) {
present(
SingleImageViewer(imageUrl: imgUrls[index]),
context,
);
},
),
ListItemFactory.createTextVideoItem(
text: "验收视频",
videoUrl: "https://www.w3school.com.cn/i/movie.mp4",
onVideoTapped: () {
showDialog(
context: context,
barrierColor: Colors.black54,
builder: (_) => VideoPlayerPopup(videoUrl: "https://www.w3school.com.cn/i/movie.mp4"),
);
}
),
SizedBox(height: 20),
],
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import '../../../../../customWidget/ItemWidgetFactory.dart';
import '../../../../../customWidget/single_image_viewer.dart';
import '../../../../../tools/tools.dart';
class DannerRepairFinish extends StatefulWidget {
const DannerRepairFinish({super.key});
@override
State<DannerRepairFinish> createState() => _DannerRepairFinishState();
}
class _DannerRepairFinishState extends State<DannerRepairFinish> {
final List<String> imgUrls = [
"https://pic.rmb.bdstatic.com/bjh/news/100b8b78cbd136ede03249d9f3b3f5c42221.jpeg",
"https://pic.rmb.bdstatic.com/bjh/news/100b8b78cbd136ede03249d9f3b3f5c42221.jpeg",
];
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 0),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//
ListItemFactory.createBuildSimpleSection("整改信息"),
const Divider(height: 1),
// summary
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12,),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListItemFactory.createColumnTextItem(
topText: "整改描述",
bottomText: "这是一段整改描述",
),
// const SizedBox(height: 10),
ListItemFactory.createRowSpaceBetweenItem(
leftText: "整改部门",
rightText: "部门",
),
const Divider(height: 10),
ListItemFactory.createRowSpaceBetweenItem(
leftText: "整改人",
rightText: "韩双",
),
const Divider(height: 10),
ListItemFactory.createRowSpaceBetweenItem(
leftText: "整改时间",
rightText: "2020-01-01",
),
ListItemFactory.createTextImageItem(
text: "整改后图片",
imageUrls: imgUrls,
onImageTapped: (index) {
present(
SingleImageViewer(imageUrl: imgUrls[index]),
context,
);
},
),
ListItemFactory.createRowSpaceBetweenItem(
leftText: "整改方案",
rightText: "",
),
Divider(height: 10,),
ListItemFactory.createRowSpaceBetweenItem(
leftText: "整改计划",
rightText: "",
),
],
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import '../../../../../customWidget/ItemWidgetFactory.dart';
import '../../../../../customWidget/photo_picker_row.dart';
class DangerAcceptance extends StatefulWidget {
const DangerAcceptance({super.key});
@override
State<DangerAcceptance> createState() => _DangerAcceptanceState();
}
class _DangerAcceptanceState extends State<DangerAcceptance> {
late bool _isQualified = true;
final dangerDiscripController = TextEditingController();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 0),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//
ListItemFactory.createBuildSimpleSection("隐患验收"),
const Divider(height: 1),
ListItemFactory.createYesNoSection(
title: "是否合格",
yesLabel: "",
noLabel: "",
groupValue: _isQualified,
onChanged: (val) {
setState(() {
_isQualified = val;
});
},
),
if (_isQualified)
// summary
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(),
ListItemFactory.createBuildMultilineInput(
"验收描述", "请对隐患进行详细描述",
dangerDiscripController),
Divider(height: 10,),
// const SizedBox(height: 10),
GestureDetector(
onTap: () {
},
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: "验收日期",
rightText: "请选择",
isRight: true)
,
),
Divider(),
RepairedPhotoSection(
horizontalPadding: 0,
title: "验收图片",
maxCount: 4,
mediaType: MediaType.image,
onChanged: (files) {
// files
},
onAiIdentify: () {},
),
],
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,286 @@
import 'dart:io';
import 'package:flutter/material.dart';
import '../../../../../customWidget/ItemWidgetFactory.dart';
import '../../../../../customWidget/custom_button.dart';
import '../../../../../customWidget/date_picker_dialog.dart';
import '../../../../../customWidget/photo_picker_row.dart';
import '../../../../../tools/h_colors.dart';
import '../../../../../tools/tools.dart';
///
class DannerRepair extends StatefulWidget {
const DannerRepair({super.key});
@override
State<DannerRepair> createState() => _DannerRepairState();
}
class _DannerRepairState extends State<DannerRepair> {
//
bool _acceptedPrepare = false;
//
bool _acceptedPlan = false;
final _standardController = TextEditingController();
final _methodController = TextEditingController();
final _fundController = TextEditingController();
final _personController = TextEditingController();
final _workTimeController = TextEditingController();
final _timeController = TextEditingController();
final _workController = TextEditingController();
final _otherController = TextEditingController();
var _selectData = DateTime.now();
@override
void dispose() {
//
_standardController.dispose();
_methodController.dispose();
_fundController.dispose();
_personController.dispose();
_workTimeController.dispose();
_timeController.dispose();
_workController.dispose();
_otherController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Column(
children: [
ListItemFactory.createBuildSimpleSection("隐患整改"),
Divider(height: 1),
Container(
height: 130,
padding: EdgeInsets.all(15),
child: Column(
children: [
Row(
children: [HhTextStyleUtils.mainTitle("隐患描述", fontSize: 15)],
),
TextField(
keyboardType: TextInputType.multiline,
maxLines: null, //
style: TextStyle(fontSize: 15),
decoration: InputDecoration(
hintText: '请对隐患进行详细描述(必填项)',
border: InputBorder.none,
),
),
],
),
),
Divider(height: 1),
GestureDetector(
onTap: () {
showDialog(
context: context,
builder:
(_) => HDatePickerDialog(
initialDate: DateTime.now(),
onCancel: () => Navigator.of(context).pop(),
onConfirm: (selected) {
print('选中日期: $selected');
Navigator.of(context).pop();
setState(() {
_selectData = selected;
});
},
),
);
},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: "整改日期",
rightText: "请选择",
isRight: true,
),
),
),
Divider(),
RepairedPhotoSection(
title: "整改后照片",
maxCount: 4,
mediaType: MediaType.image,
onChanged: (files) {
// files
},
onAiIdentify: () {},
),
Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
CustomButton(
onPressed: () {},
text: "添加",
backgroundColor: Colors.blue,
borderRadius: 17,
height: 34,
padding: EdgeInsets.symmetric(horizontal: 20),
),
],
),
_departmentItem(1),
_departmentItem(2),
Divider(),
ListItemFactory.createYesNoSection(
title: "是否有整改方案",
yesLabel: "",
noLabel: "",
groupValue: _acceptedPrepare,
onChanged: (val) {
setState(() {
_acceptedPrepare = val;
});
},
),
_acceptedPrepare ? _acceptPrepare() : SizedBox(height: 1),
Divider(),
ListItemFactory.createYesNoSection(
title: "是否有整改计划",
yesLabel: "",
noLabel: "",
groupValue: _acceptedPlan,
onChanged: (val) {
setState(() {
_acceptedPlan = val;
});
},
),
_acceptedPlan ? _acceptPlan() : SizedBox(height: 1),
],
),
);
}
///
Widget _acceptPrepare() {
final fields = [
_buildReadOnlyRow("排查日期", "2025-1-2 11:22:30"),
_buildReadOnlyRow("隐患清单", "-----"),
ListItemFactory.createBuildMultilineInput("治理标准", "请输入治理标准", _standardController),
ListItemFactory.createBuildMultilineInput("治理方法", "请输入治理方法", _methodController),
ListItemFactory.createBuildMultilineInput("经费落实", "请输入经费落实", _fundController),
ListItemFactory.createBuildMultilineInput("负责人员", "请输入负责人员", _personController),
ListItemFactory.createBuildMultilineInput("工时安排", "请输入工时安排", _workTimeController),
ListItemFactory.createBuildMultilineInput("时限要求", "请输入时限要求", _timeController),
ListItemFactory.createBuildMultilineInput("工作要求", "请输入工作要求", _workController),
ListItemFactory.createBuildMultilineInput("其他事项", "请输入其他事项", _otherController),
];
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: fields.length,
separatorBuilder:
(_, __) => const Divider(height: 1, color: Colors.black12),
itemBuilder:
(_, index) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: fields[index],
),
);
}
Widget _buildReadOnlyRow(String left, String right) {
return ListItemFactory.createRowSpaceBetweenItem(
leftText: left,
rightText: right,
);
}
/// item
Widget _departmentItem(int num) {
return Padding(
padding: const EdgeInsets.all(10),
child: Stack(
clipBehavior: Clip.none,
children: [
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black12, width: 1),
),
child: _noAccepet_repair(false),
),
// num > 1
if (num > 1)
Positioned(
top: -20,
left: -20,
child: IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
icon: const Icon(Icons.cancel, color: Colors.red, size: 25),
onPressed: () {
//
// setState(() => _items.removeAt(num));
},
),
),
],
),
);
}
// #region
Widget _noAccepet_repair(bool _accept) {
return Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: "整改部门",
rightText: "测试啊",
isRight: true,
),
),
Divider(
height: 10,
color: _accept ? h_backGroundColor() : Colors.transparent,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: "整改负责人",
rightText: "测试啊",
isRight: true,
),
),
],
);
}
///
Widget _acceptPlan() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: MediaPickerRow(
maxCount: 4,
onChanged: (List<File> images) {
// images
},
),
);
}
}

View File

@ -1,6 +1,7 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/danner_repain_item.dart';
import 'package:qhd_prevention/customWidget/department_picker.dart';
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
@ -69,8 +70,8 @@ class _DangerPageState extends State<DangerPage>
super.dispose();
}
void _handleItemTap(NotificationItem item, int index) {
print("点击了: ${item.title}");
pushPage(riskListPage(), context);
print("点击了: ${item.title}");
pushPage(RiskListPage(), context);
}
//
void showCategoryPicker() {
@ -148,7 +149,25 @@ class _DangerPageState extends State<DangerPage>
NotificationItem item = _notifications[index];
return GestureDetector(
onTap: () => _handleItemTap(item, index),
child: _itemCell(item),
child: DannerRepainItem(
title: '测试--------new',
details: [
'清单类型:测试',
'排查周期:测试',
'包含检查项3',
'负责人:是测试',
'起始时间2025-6-20',
'',
'测试一下是否跳过时间'
],
showBottomTags: true,
bottomTags: [
riskTagText(1, "重大风险:0"),
riskTagText(2, "较大:3"),
riskTagText(3, "一般:1"),
riskTagText(4, "低:0"),
],
),
);
},
),

View File

@ -152,7 +152,7 @@ class _DangerProjectPageState extends State<DangerProjectPage> {
Text(
label,
style: TextStyle(
fontSize: 16,
fontSize: 14,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? color : Colors.grey[600],
),

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_driver_drawer.dart';
import 'package:qhd_prevention/pages/home/work/custom_driver_drawer.dart';
import 'package:qhd_prevention/pages/home/risk/risk_detail_page.dart';
import 'package:qhd_prevention/pages/home/work/danger_repair_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
@ -11,7 +11,9 @@ enum DangerType {
wait("待整改隐患", "隐患整改"),
expired("超期未整改", "超期未整改-详情"),
waitAcceptance("隐患验收", "隐患验收"),
acceptance("已验收隐患", "已验收隐患");
acceptance("已验收隐患", "已验收隐患"),
ristRecord("隐患记录", "隐患记录-详情"),
acceptanced("已验收隐患", "隐患记录-详情");
final String displayName;
final String detailTitle;
@ -112,14 +114,17 @@ class _DangerWaitListPageState extends State<DangerWaitListPage> {
},
child: Container(
height: 100,
// height: 100,
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 16), //
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 15), //
child:
IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
//
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
@ -130,12 +135,22 @@ class _DangerWaitListPageState extends State<DangerWaitListPage> {
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 8),
SizedBox(height: 5),
Text(
item['label1'] ?? '',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
SizedBox(height: 4),
SizedBox(height: 5),
Text(
item['label2'] ?? '',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
SizedBox(height: 5),
Text(
item['label2'] ?? '',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
SizedBox(height: 5),
Text(
item['label2'] ?? '',
style: TextStyle(fontSize: 14, color: Colors.grey),
@ -146,8 +161,9 @@ class _DangerWaitListPageState extends State<DangerWaitListPage> {
//
Padding(
padding: EdgeInsets.only(top: 15),
padding: EdgeInsets.only(top: 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
//
Row(
@ -176,12 +192,18 @@ class _DangerWaitListPageState extends State<DangerWaitListPage> {
Icon(Icons.arrow_forward_ios_rounded, size: 16),
],
),
SizedBox(height: 20), //
Row(children: [
Text("2025-1-2",style: TextStyle(fontSize: 12, color: Colors.grey),
)
, SizedBox(width: 20,)],),
],
),
),
],
),
),
),
);
}
}

View File

@ -5,14 +5,14 @@ import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/h_colors.dart';
import 'package:qhd_prevention/tools/tools.dart';
class riskListPage extends StatefulWidget {
const riskListPage({super.key});
class RiskListPage extends StatefulWidget {
const RiskListPage({super.key});
@override
State<riskListPage> createState() => _riskListPageState();
State<RiskListPage> createState() => _riskListPageState();
}
class _riskListPageState extends State<riskListPage> {
class _riskListPageState extends State<RiskListPage> {
final List<Model> _dataList = [
Model("青椒皮蛋鸡尾酒哦普佛椒皮鸡尾酒哦普佛椒皮鸡尾鸡尾酒哦普佛椒皮鸡尾鸡尾酒哦普佛椒皮鸡尾鸡尾酒哦普佛椒皮鸡尾鸡尾酒哦普佛"),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'home/home_page.dart';
import 'app/application_page.dart';
import 'mine/mine_page.dart';
@ -20,66 +21,90 @@ class _MainPageState extends State<MainPage> {
HomePage(),
ApplicationPage(),
NotifPage(),
MinePage()
MinePage(),
];
//
final List<String> _titles = [
'首页',
'应用中心',
'通知公告',
'我的',
];
final List<String> _titles = ['首页', '应用中心', '通知公告', '我的'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _currentIndex == 1
appBar:
_currentIndex == 1
? null
: AppBar(
title: Text(
_currentIndex == 0 ? "泰盛安全首页" : _titles[_currentIndex],
style: const TextStyle(
fontSize: 17,
color: Colors.white,
),
),
centerTitle: true,
: MyAppbar(
title: _currentIndex == 0 ? "泰盛安全首页" : _titles[_currentIndex],
backgroundColor: Colors.blue,
isBack: false,
actions: [
if (_currentIndex == 0)
IconButton(onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => ScanPage() ));
}, icon: Image.asset("assets/images/scan.png", width: 20, height: 20,))
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ScanPage()),
);
},
icon: Image.asset(
"assets/images/scan.png",
width: 20,
height: 20,
),
),
],
),
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed, // 3
type: BottomNavigationBarType.fixed,
// 3
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
onTap: (index) => setState(() => _currentIndex = index),
items: [
BottomNavigationBarItem(
icon: Image.asset('assets/tabbar/basics.png', width: 24, height: 24),
activeIcon: Image.asset('assets/tabbar/basics_cur.png', width: 24, height: 24),
icon: Image.asset(
'assets/tabbar/basics.png',
width: 24,
height: 24,
),
activeIcon: Image.asset(
'assets/tabbar/basics_cur.png',
width: 24,
height: 24,
),
label: '首页',
),
BottomNavigationBarItem(
icon: Image.asset('assets/tabbar/application.png', width: 24, height: 24),
activeIcon: Image.asset('assets/tabbar/application_cur.png', width: 24, height: 24),
icon: Image.asset(
'assets/tabbar/application.png',
width: 24,
height: 24,
),
activeIcon: Image.asset(
'assets/tabbar/application_cur.png',
width: 24,
height: 24,
),
label: '应用',
),
BottomNavigationBarItem(
icon: Image.asset('assets/tabbar/works.png', width: 24, height: 24),
activeIcon: Image.asset('assets/tabbar/works_cur.png', width: 24, height: 24),
activeIcon: Image.asset(
'assets/tabbar/works_cur.png',
width: 24,
height: 24,
),
label: '通知',
),
BottomNavigationBarItem(
icon: Image.asset('assets/tabbar/my.png', width: 24, height: 24),
activeIcon: Image.asset('assets/tabbar/my_cur.png', width: 24, height: 24),
activeIcon: Image.asset(
'assets/tabbar/my_cur.png',
width: 24,
height: 24,
),
label: '我的',
),
],

View File

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import '../../tools/tools.dart';
class MineAboutPage extends StatefulWidget {
const MineAboutPage({super.key});
@override
State<MineAboutPage> createState() => _MineAboutPageState();
}
class _MineAboutPageState extends State<MineAboutPage> {
String appVersion = "获取中...";
@override
void initState() {
super.initState();
_loadAppVersion();
}
Future<void> _loadAppVersion() async {
final versionInfo = await getAppVersion();
setState(() {
appVersion = versionInfo.fullVersion;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: "关于"),
body: SafeArea(
child: Column(
children: [
SizedBox(height: 80),
SizedBox(
width: 1000,
child: Image.asset(
"assets/images/app-logo.png",
width: 70,
height: 70,
),
),
SizedBox(height: 80),
Container(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 5),
color: Colors.white,
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: "当前版本",
rightText: appVersion,
),
),
],
),
),
);
}
}

View File

@ -1,4 +1,7 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/pages/mine/mine_about_page.dart';
import 'package:qhd_prevention/pages/mine/mine_set_page.dart';
import 'package:qhd_prevention/tools/tools.dart';
class MinePage extends StatefulWidget {
const MinePage({super.key});
@ -46,9 +49,22 @@ class _MinePageState extends State<MinePage> {
),
),
SizedBox(height: 10,),
_setItemWidget("设置"),
GestureDetector(
child: _setItemWidget("设置"),
onTap: () {
pushPage(MineSetPage(), context);
},
),
Divider(height: 1,color: Colors.black12,),
_setItemWidget("关于")
GestureDetector(
child: _setItemWidget("关于"),
onTap: () {
pushPage(MineAboutPage(), context);
},
),
],),
);
}

View File

@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/pages/login_page.dart';
import 'package:qhd_prevention/pages/mine/mine_set_pwd_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/h_colors.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../tools/tools.dart';
class MineSetPage extends StatelessWidget {
const MineSetPage({super.key});
Future<void> _clearUserSession() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('isLoggedIn'); //
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: "设置"),
backgroundColor: h_backGroundColor(),
body: Column(
children: [
GestureDetector(
child: _setItemWidget("修改密码"),
onTap: () {
pushPage(MineSetPwdPage(), context);
},
),
Divider(height: 1, color: Colors.black12),
GestureDetector(child: _setItemWidget("检查更新"), onTap: () {}),
SizedBox(height: 15),
GestureDetector(
child: Container(
padding: EdgeInsets.symmetric(vertical: 15),
color: Colors.white,
child: Center(child: Text("退出当前账户", style: TextStyle(fontSize: 16),)),
),
onTap: () async {
//
bool? confirm = await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("确认退出"),
content: Text("确定要退出当前账号吗?"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text("取消"),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text("确定", style: TextStyle(color: Colors.red)),
),
],
),
);
if (confirm == true) {
//
await _clearUserSession();
//
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
(Route<dynamic> route) => false, //
);
}
},
),
],
),
);
}
Widget _setItemWidget(final String text) {
return Container(
height: 55,
color: Colors.white,
child: Padding(
padding: EdgeInsets.all(15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(text, style: TextStyle(fontSize: 16)),
Icon(Icons.chevron_right),
],
),
),
);
}
}

View File

@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:flutter/material.dart';
class MineSetPwdPage extends StatefulWidget {
const MineSetPwdPage({super.key});
@override
State<MineSetPwdPage> createState() => _MineSetPwdPageState();
}
class _MineSetPwdPageState extends State<MineSetPwdPage> {
final _oldPwdController = TextEditingController();
final _newPwdController = TextEditingController();
final _confirmPwdController = TextEditingController();
@override
void dispose() {
_oldPwdController.dispose();
_newPwdController.dispose();
_confirmPwdController.dispose();
super.dispose();
}
void _handleSubmit() {
final oldPwd = _oldPwdController.text.trim();
final newPwd = _newPwdController.text.trim();
final confirmPwd = _confirmPwdController.text.trim();
if (oldPwd.isEmpty || newPwd.isEmpty || confirmPwd.isEmpty) {
_showMessage('请填写完整所有字段');
return;
}
if (newPwd != confirmPwd) {
_showMessage('新密码与确认密码不一致');
return;
}
//
if (newPwd.length < 8 || newPwd.length > 20) {
_showMessage('密码长度需在8-20位之间');
return;
}
_showMessage('密码修改成功'); //
}
void _showMessage(String msg) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
}
Widget _buildPwdField(String hintText, TextEditingController controller) {
return Column(
children: [
TextField(
controller: controller,
obscureText: true,
decoration: InputDecoration(
hintText: hintText,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 10),
),
),
const Divider(height: 1, color: Colors.grey),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: '修改密码'),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'修改密码',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 30),
_buildPwdField('旧密码', _oldPwdController),
const SizedBox(height: 20),
_buildPwdField('新密码', _newPwdController),
const SizedBox(height: 20),
_buildPwdField('确认新密码', _confirmPwdController),
const SizedBox(height: 15),
const Text(
'需8-20位字母大小写、数字、字符混合',
style: TextStyle(color: Colors.grey, fontSize: 13),
),
const SizedBox(height: 30,),
SizedBox(
width: double.infinity,
height: 48,
child: CustomButton(text: "提交", backgroundColor: Colors.blue),
),
],
),
),
),
);
}
}

View File

@ -1,11 +1,14 @@
import 'package:flutter/material.dart';
import 'dart:io' show Platform;
class MyAppbar extends StatelessWidget implements PreferredSizeWidget {
final String title;
final VoidCallback? onBackPressed;
final Color backgroundColor;
final Color textColor;
final List<Widget>? actions; // 👉
final List<Widget>? actions;
final bool isBack;
final bool centerTitle; //
const MyAppbar({
Key? key,
@ -14,34 +17,71 @@ class MyAppbar extends StatelessWidget implements PreferredSizeWidget {
this.backgroundColor = Colors.blue,
this.textColor = Colors.white,
this.actions,
this.isBack = true,
this.centerTitle = true, //
}) : super(key: key);
//
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
Size get preferredSize {
// iOS使(44)Android(56)
return Size.fromHeight(Platform.isIOS ? 44.0 : kToolbarHeight);
}
@override
Widget build(BuildContext context) {
return AppBar(
backgroundColor: backgroundColor,
automaticallyImplyLeading: false,
centerTitle: true,
centerTitle: centerTitle,
toolbarHeight: preferredSize.height, // 使
title: Text(
title,
style: TextStyle(
color: textColor,
fontSize: 18,
fontWeight: FontWeight.bold,
fontSize: Platform.isIOS ? 17.0 : 18.0, // iOS使
fontWeight: FontWeight.w600, // iOS使
),
),
leading: _buildBackButton(context),
actions: actions, // 👉
leading: isBack ? _buildBackButton(context) : null,
actions: _buildActions(),
elevation: Platform.isIOS ? 0 : 4, // iOS
// iOS
shape: Platform.isIOS
? const Border(bottom: BorderSide(color: Colors.black12, width: 0.5))
: null,
);
}
//
Widget _buildBackButton(BuildContext context) {
return IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.white, size: 20),
return Padding(
padding: EdgeInsets.only(left: Platform.isIOS ? 8.0 : 16.0),
child: IconButton(
icon: Icon(
Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back,
color: textColor,
size: Platform.isIOS ? 20.0 : 24.0, // iOS使
),
padding: EdgeInsets.zero, //
constraints: const BoxConstraints(), //
onPressed: onBackPressed ?? () => Navigator.of(context).pop(),
),
);
}
//
List<Widget>? _buildActions() {
if (actions == null) return null;
return [
Padding(
padding: EdgeInsets.only(right: Platform.isIOS ? 8.0 : 16.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: actions!,
),
)
];
}
}

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
class NotifDetailPage extends StatelessWidget {
const NotifDetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: "通知详情"),
body: SafeArea(
child: Container(
width: double.infinity, //
color: Colors.white,
padding: const EdgeInsets.all(15),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, //
children: const [
Text("通知标题"),
SizedBox(height: 8),
Text("2025-1-1 01:01:01"),
SizedBox(height: 16),
Text("通知详情"),
],
),
),
),
);
}
}

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
import 'package:qhd_prevention/pages/notif/notif_detail_page.dart';
import 'package:qhd_prevention/tools/tools.dart';
class NotifPage extends StatefulWidget {
const NotifPage({Key? key}) : super(key: key);
@ -100,8 +102,10 @@ class _NotifPageState extends State<NotifPage> with SingleTickerProviderStateMix
Widget _itemCell(final item) {
return ListTile(
onTap: () {
pushPage(NotifDetailPage(), context);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
title: Padding(
padding: const EdgeInsets.only(bottom: 20), //
child: Text(

View File

@ -1,7 +1,7 @@
import 'dart:ffi';
import 'dart:ui';
Color h_backGroundColor() => Color(0xF1F1F1FF);
Color h_backGroundColor() => Color(0xFFF1F1F1);
List<Color> riskLevelTextColors() {
return [Color(0xFFE54D42),Color(0xFFF37B1D),Color(0xFFF9BD08),Color(0xFF3281FF)];
}

View File

@ -2,6 +2,7 @@ import 'dart:ui';
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
int getRandomWithNum(int min, int max) {
final random = Random();
@ -16,6 +17,15 @@ double screenWidth(BuildContext context) {
void pushPage(Widget page, BuildContext context) {
Navigator.push(context, MaterialPageRoute(builder: (context) => page));
}
void present(Widget page, BuildContext context) {
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => page,
),
);
}
///
///
/// Text Widget
@ -40,6 +50,7 @@ class HhTextStyleUtils {
),
);
}
static TextStyle secondaryTitleStyle = TextStyle(color:Colors.black54, fontSize: 15.0);
/// Text
/// [text]:
@ -55,6 +66,7 @@ class HhTextStyleUtils {
return Text(
text,
style: TextStyle(
color: color,
fontSize: fontSize,
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
@ -83,3 +95,40 @@ class HhTextStyleUtils {
);
}
}
///
class AppVersionInfo {
final String versionName; // 1.0.0
final String buildNumber; // 1
final String fullVersion; // 1.0.0+1
AppVersionInfo({
required this.versionName,
required this.buildNumber,
required this.fullVersion,
});
@override
String toString() {
return fullVersion;
}
}
//
Future<AppVersionInfo> getAppVersion() async {
try {
final packageInfo = await PackageInfo.fromPlatform();
return AppVersionInfo(
versionName: packageInfo.version,
buildNumber: packageInfo.buildNumber,
fullVersion: '${packageInfo.version}+${packageInfo.buildNumber}',
);
} catch (e) {
//
return AppVersionInfo(
versionName: '1.0.0',
buildNumber: '1',
fullVersion: '1.0.0+0',
);
}
}

View File

@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
@ -41,6 +49,22 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "1.19.1"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: "051849e2bd7c7b3bc5844ea0d096609ddc3a859890ec3a9ac4a65a2620cc1f99"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "6.1.4"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "2.0.1"
cross_file:
dependency: transitive
description:
@ -73,6 +97,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "1.0.8"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "0.7.11"
extended_image:
dependency: transitive
description:
@ -368,6 +400,30 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "1.0.0"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "0.5.0"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "8.3.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "3.2.0"
path:
dependency: transitive
description:
@ -424,8 +480,16 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "2.3.0"
photo_manager:
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "6.1.0"
photo_manager:
dependency: "direct main"
description:
name: photo_manager
sha256: a0d9a7a9bc35eda02d33766412bde6d883a8b0acb86bbe37dac5f691a0894e8a
@ -614,7 +678,7 @@ packages:
source: hosted
version: "2.1.4"
video_player:
dependency: transitive
dependency: "direct main"
description:
name: video_player
sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a"
@ -693,6 +757,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "1.0.5"
win32:
dependency: transitive
description:
name: win32
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "5.13.0"
xdg_directories:
dependency: transitive
description:
@ -701,6 +773,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "6.5.0"
sdks:
dart: ">=3.7.0 <4.0.0"
flutter: ">=3.29.0"

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
version: 1.0.1
environment:
sdk: ^3.7.0
@ -33,18 +33,23 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
package_info_plus: ^8.3.0
shared_preferences: ^2.2.2 # 用于保存登录状态
# 扫码
mobile_scanner: ^7.0.1
# 相册
image_picker: ^1.1.2
wechat_assets_picker: ^9.5.1
photo_manager: ^3.7.1
# 日历
table_calendar: ^3.2.0
intl: ^0.20.0
#图片查看大图
photo_view: ^0.15.0
#视频播放器
video_player: ^2.10.0
#网络监听
connectivity_plus: ^6.1.4
dev_dependencies:
flutter_test:
sdk: flutter