diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 38da250..e842ee3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,40 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + qhd_prevention CFBundlePackageType APPL - NSCameraUsageDescription - 需要相机权限来扫描二维码 + + NSLocationWhenInUseUsageDescription + app需要定位权限来提供附近服务 + NSLocationAlwaysAndWhenInUseUsageDescription + app需要后台定位以实现持续跟踪 + + + NSPhotoLibraryUsageDescription + app需要访问相册以上传图片 + NSPhotoLibraryAddUsageDescription + app需要保存图片到相册 + + + NSCameraUsageDescription + app需要相机权限来扫描二维码 + + + NSMicrophoneUsageDescription + app需要麦克风权限进行语音通话 + + + NSContactsUsageDescription + app需要通讯录权限添加好友 + + + NSUserNotificationsUsageDescription + app需要发送通知提醒重要信息 + + + NSBluetoothAlwaysUsageDescription + app需要蓝牙权限连接设备 + + + NSLocalNetworkUsageDescription + app需要发现本地网络设备 + + + NSHealthShareUsageDescription + app需要读取健康数据 + NSHealthUpdateUsageDescription + app需要写入健康数据 + + + NSMotionUsageDescription + app需要访问运动数据统计步数 + CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature diff --git a/lib/Model/list_model.dart b/lib/Model/list_model.dart new file mode 100644 index 0000000..f1abb98 --- /dev/null +++ b/lib/Model/list_model.dart @@ -0,0 +1,6 @@ +/// 清单检查记录 +class RecordCheckModel { + final String title; + final String time; + RecordCheckModel(this.title, this.time); +} \ No newline at end of file diff --git a/lib/customWidget/ItemWidgetFactory.dart b/lib/customWidget/ItemWidgetFactory.dart index 461916a..0d1235b 100644 --- a/lib/customWidget/ItemWidgetFactory.dart +++ b/lib/customWidget/ItemWidgetFactory.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; +import '../tools/tools.dart'; + /// 自定义组件 class ListItemFactory { - /// 类型1:横向spaceBetween布局两个文本 + /// 类型1:横向spaceBetween布局两个文本加按钮 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 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), + 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, + ); + } else { + img = Image.asset( + url, + height: imageHeight, + width: imageHeight * 3 / 2, + fit: BoxFit.cover, + ); + } + 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包裹图片区域 GestureDetector( - onTap: onImageTapped, // 将外部传入的回调绑定到点击事件 - child: Builder( - builder: (context) { - // 网络图片 - if (imageUrl.startsWith('http')) { - return Image.network( - imageUrl, - height: imageHeight, - width: imageHeight * 3 / 2, - fit: BoxFit.cover, - alignment: Alignment.centerLeft, - ); - } - // 本地图片 - else { - return Image.asset( - imageUrl, - height: imageHeight, - width: double.infinity, - fit: BoxFit.cover, - alignment: Alignment.centerLeft, - ); - } - }, + 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 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, + ), + ), + ), + ], + ), + ); + } } diff --git a/lib/customWidget/bottom_picker.dart b/lib/customWidget/bottom_picker.dart new file mode 100644 index 0000000..11841e0 --- /dev/null +++ b/lib/customWidget/bottom_picker.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; + +/// 通用底部弹窗选择器 +/// Example: +/// ```dart +/// final choice = await BottomPicker.show( +/// 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 show( + BuildContext context, { + required List items, + required Widget Function(T item) itemBuilder, + int initialIndex = 0, + double itemExtent = 40.0, + double height = 250, + }) { + // 当前选中项 + T selected = items[initialIndex]; + + return showModalBottomSheet( + 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(), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/customWidget/danner_repain_item.dart b/lib/customWidget/danner_repain_item.dart new file mode 100644 index 0000000..d22be3b --- /dev/null +++ b/lib/customWidget/danner_repain_item.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import '../tools/tools.dart'; + +/// 通用列表卡片组件: +/// - 两两为一组,优先尝试同一行显示左右两列并左/右对齐; +/// - 如放不下,则自动拆成两行,上行左对齐,下行右对齐。 +class DannerRepainItem extends StatelessWidget { + final String title; + final List details; + final bool showBottomTags; + final List 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 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, + ); + } +} diff --git a/lib/customWidget/full_screen_video_page.dart b/lib/customWidget/full_screen_video_page.dart new file mode 100644 index 0000000..37558d6 --- /dev/null +++ b/lib/customWidget/full_screen_video_page.dart @@ -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 createState() => _VideoPlayerPopupState(); + +} + +class _VideoPlayerPopupState extends State { + 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 createState() => _FullScreenVideoPageState(); +} + +class _FullScreenVideoPageState extends State { + 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')}'; +} diff --git a/lib/customWidget/photo_picker_row.dart b/lib/customWidget/photo_picker_row.dart index c756b75..e2d94eb 100644 --- a/lib/customWidget/photo_picker_row.dart +++ b/lib/customWidget/photo_picker_row.dart @@ -1,107 +1,154 @@ 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 images) { -/// // images 列表更新 +/// mediaType: MediaType.video, +/// onChanged: (List medias) { +/// // medias 列表更新 /// }, /// ), -class PhotoPickerRow extends StatefulWidget { +class MediaPickerRow extends StatefulWidget { final int maxCount; + final MediaType mediaType; final ValueChanged> 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 { +class _MediaPickerRowState extends State { final ImagePicker _picker = ImagePicker(); - final List _images = []; + final List _files = []; Future _showPickerOptions() async { showModalBottomSheet( context: context, - builder: (_) => SafeArea( - child: Wrap( - children: [ - ListTile( - leading: const Icon(Icons.camera_alt), - title: const Text('拍照'), - onTap: () { - Navigator.of(context).pop(); - _pickCamera(); - }, + builder: + (_) => SafeArea( + child: Wrap( + children: [ + ListTile( + 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: Icon( + widget.mediaType == MediaType.image + ? Icons.photo_library + : Icons.video_library, + ), + title: Text( + widget.mediaType == MediaType.image ? '从相册选择' : '从相册选择视频', + ), + onTap: () { + Navigator.of(context).pop(); + _pickGallery(); + }, + ), + ListTile( + leading: const Icon(Icons.close), + title: const Text('取消'), + onTap: () => Navigator.of(context).pop(), + ), + ], ), - ListTile( - leading: const Icon(Icons.photo_library), - title: const Text('从相册选择'), - onTap: () { - Navigator.of(context).pop(); - _pickGallery(); - }, - ), - ListTile( - leading: const Icon(Icons.close), - title: const Text('取消'), - onTap: () => Navigator.of(context).pop(), - ), - ], - ), - ), + ), ); } Future _pickCamera() async { - if (_images.length >= widget.maxCount) return; - final XFile? picked = await _picker.pickImage(source: ImageSource.camera); - if (picked != null) { - setState(() { - _images.add(File(picked.path)); - }); - widget.onChanged(_images); + 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(() { + _files.add(File(picked!.path)); + }); + widget.onChanged(_files); + } + } catch (e) { + debugPrint('拍摄失败: \$e'); } } Future _pickGallery() async { - if (_images.length >= widget.maxCount) return; - final remaining = widget.maxCount - _images.length; - final List? assets = await AssetPicker.pickAssets( - context, - pickerConfig: AssetPickerConfig( - requestType: RequestType.image, - maxAssets: remaining, - gridCount: 4, - ), - ); - if (assets != null && assets.isNotEmpty) { - for (final asset in assets) { - if (_images.length >= widget.maxCount) break; - final file = await asset.file; - if (file != null) { - _images.add(file); + 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? assets = await AssetPicker.pickAssets( + context, + pickerConfig: AssetPickerConfig( + requestType: + widget.mediaType == MediaType.image + ? RequestType.image + : RequestType.video, + maxAssets: remaining, + gridCount: 4, + ), + ); + if (assets != null) { + for (final asset in assets) { + if (_files.length >= widget.maxCount) break; + final file = await asset.file; + if (file != null) { + _files.add(file); + } } + setState(() {}); + widget.onChanged(_files); } - setState(() {}); - widget.onChanged(_images); + } 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,47 +157,64 @@ class _PhotoPickerRowState extends State { height: 80, child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: _images.length < widget.maxCount - ? _images.length + 1 - : widget.maxCount, + 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], - width: 80, - height: 80, - fit: BoxFit.cover, - ), + 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( top: -6, 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 { ); } } + +/// 整改后照片上传区域组件 +class RepairedPhotoSection extends StatelessWidget { + final int maxCount; + final MediaType mediaType; + final String title; + final ValueChanged> 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隐患识别与处理'), + ], + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/customWidget/single_image_viewer.dart b/lib/customWidget/single_image_viewer.dart index d947482..c13dfa1 100644 --- a/lib/customWidget/single_image_viewer.dart +++ b/lib/customWidget/single_image_viewer.dart @@ -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( diff --git a/lib/main.dart b/lib/main.dart index a573193..32f5626 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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, // 右缩进 diff --git a/lib/pages/app/Danger_paicha/check_record_list_page.dart b/lib/pages/app/Danger_paicha/check_record_list_page.dart new file mode 100644 index 0000000..7c4d77c --- /dev/null +++ b/lib/pages/app/Danger_paicha/check_record_list_page.dart @@ -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 + with SingleTickerProviderStateMixin { + late TabController _tabController; + int _selectedTab = 0; + + // 模拟数据 + final List _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 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); +} diff --git a/lib/pages/app/Danger_paicha/check_record_page.dart b/lib/pages/app/Danger_paicha/check_record_page.dart new file mode 100644 index 0000000..3a3d9b6 --- /dev/null +++ b/lib/pages/app/Danger_paicha/check_record_page.dart @@ -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 createState() => _CheckRecordPageState(); +} + +class _CheckRecordPageState extends State { + final TextEditingController _searchController = TextEditingController(); + final List _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 _scaffoldKey = GlobalKey(); + + 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, + + ), + ); + }, + ), + ) + + ], + )), + ); + } +} diff --git a/lib/pages/app/Danger_paicha/custom_record_drawer.dart b/lib/pages/app/Danger_paicha/custom_record_drawer.dart new file mode 100644 index 0000000..053cdaf --- /dev/null +++ b/lib/pages/app/Danger_paicha/custom_record_drawer.dart @@ -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 { + // 四个选项的单选 index + int _selectedOption = -1; + // 已选择的分类 id + String? _selectedCategoryId; + // 检查人 + String? _selectedPerson; + // 清单类型 + String? _selectedQDType; + // 排查周期 + String? _selectedZQTime; + + // 新增:开始/结束时间 + DateTime? _startDate; + DateTime? _endDate; + + @override + Widget build(BuildContext context) { + final List 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 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( + 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( + 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( + context, + items: ['每日', '每周', '每旬', '每月', '每季', '半年', '每年'], + itemBuilder: (item) => Text(item, textAlign: TextAlign.center), + initialIndex: 0, + ); + if (choice != null) { + setState(() { + _selectedZQTime = choice; + }); + } + } + + + } + + Future _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 _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), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/app/Danger_paicha/quick_report_page.dart b/lib/pages/app/Danger_paicha/quick_report_page.dart new file mode 100644 index 0000000..9734cf9 --- /dev/null +++ b/lib/pages/app/Danger_paicha/quick_report_page.dart @@ -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 createState() => _QuickReportPageState(); +} + +class _QuickReportPageState extends State { + 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 files) { + // 上传图片 files + }, + onAiIdentify: () { + // AI 识别逻辑 + }, + ), + ), + _buildSectionContainer( + child: RepairedPhotoSection( + title: "隐患视频", + maxCount: 1, + mediaType: MediaType.video, + onChanged: (List 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 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), + ], + ), + ), + ); + } +} diff --git a/lib/pages/app/application_page.dart b/lib/pages/app/application_page.dart index a59abea..afe6b08 100644 --- a/lib/pages/app/application_page.dart +++ b/lib/pages/app/application_page.dart @@ -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> 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; 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,25 +162,25 @@ 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((item) { - return SizedBox( - width: itemWidth, - child: _buildItem(item), - ); - }).toList(), + children: items.map((item) { + return SizedBox( + width: itemWidth, + child: _buildItem( + item, + onTap: () => _handleItemClick(context, item['item'] as AppItem), + ), + ); + }).toList(), ); }, ), @@ -129,51 +194,57 @@ class ApplicationPage extends StatelessWidget { ); } - Widget _buildItem(Map item) { + // 添加 onTap 回调参数 + Widget _buildItem(Map item, {VoidCallback? onTap}) { const double size = 60; final int badgeNum = item['num'] as int; - return SizedBox( - width: size, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Image with badge overlay - Stack( - clipBehavior: Clip.none, - children: [ - SizedBox( - width: 30, - height: 30, - child: Image.asset(item['icon'] as String, fit: BoxFit.contain), - ), - if (badgeNum > 0) - Positioned( - top: -5, - right: -10, - child: Container( - width: 16, - height: 16, - decoration: const BoxDecoration( - color: Colors.red, - shape: BoxShape.circle, - ), - alignment: Alignment.center, - child: Text( - badgeNum.toString(), - style: const TextStyle(color: Colors.white, fontSize: 10), + + return InkWell( + onTap: onTap, // 添加点击事件 + borderRadius: BorderRadius.circular(8), + child: SizedBox( + width: size, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + clipBehavior: Clip.none, + children: [ + SizedBox( + width: 30, + height: 30, + child: Image.asset(item['icon'] as String, fit: BoxFit.contain), + ), + if (badgeNum > 0) + Positioned( + top: -5, + right: -10, + child: Container( + width: 16, + height: 16, + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + alignment: Alignment.center, + child: Text( + badgeNum.toString(), + style: const TextStyle(color: Colors.white, fontSize: 10), + ), ), ), - ), - ], - ), - const SizedBox(height: 4), - Text( - item['title'] as String, - style: const TextStyle(fontSize: 12), - textAlign: TextAlign.center, - ), - ], + ], + ), + const SizedBox(height: 4), + Text( + item['title'] as String, + style: const TextStyle(fontSize: 12), + textAlign: TextAlign.center, + ), + ], + ), ), ); } } + diff --git a/lib/pages/home/risk/riskControl_page.dart b/lib/pages/home/risk/riskControl_page.dart index 0bf360e..7d76962 100644 --- a/lib/pages/home/risk/riskControl_page.dart +++ b/lib/pages/home/risk/riskControl_page.dart @@ -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'; diff --git a/lib/customWidget/custom_driver_drawer.dart b/lib/pages/home/work/custom_driver_drawer.dart similarity index 90% rename from lib/customWidget/custom_driver_drawer.dart rename to lib/pages/home/work/custom_driver_drawer.dart index 9ef6eab..6a1f281 100644 --- a/lib/customWidget/custom_driver_drawer.dart +++ b/lib/pages/home/work/custom_driver_drawer.dart @@ -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 { final List 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 { "风险点(单元)", display: '请选择', onTap: () { - // TODO: 打开 B 的下拉 + final choice = BottomPicker.show( + context, + items: ['未知'], + itemBuilder: (item) => Text(item, textAlign: TextAlign.center), + initialIndex: 0, + ); + if (choice != null) { + // 用户点击确定并选择了 choice + setState(() { + + }); + } }, ), diff --git a/lib/pages/home/work/dangerTypeItems/danger_change.dart b/lib/pages/home/work/dangerTypeItems/danger_change.dart new file mode 100644 index 0000000..ac3af81 --- /dev/null +++ b/lib/pages/home/work/dangerTypeItems/danger_change.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class DangerChange extends StatefulWidget { + const DangerChange({super.key}); + + @override + State createState() => _DangerChangeState(); +} + +class _DangerChangeState extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/pages/home/work/dangerTypeItems/danger_detail.dart b/lib/pages/home/work/dangerTypeItems/danger_detail.dart new file mode 100644 index 0000000..d545898 --- /dev/null +++ b/lib/pages/home/work/dangerTypeItems/danger_detail.dart @@ -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 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, + ); + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/home/work/dangerTypeItems/finish/danger_acceptance_finish.dart b/lib/pages/home/work/dangerTypeItems/finish/danger_acceptance_finish.dart new file mode 100644 index 0000000..979de20 --- /dev/null +++ b/lib/pages/home/work/dangerTypeItems/finish/danger_acceptance_finish.dart @@ -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 createState() => _DangerAcceptanceFinishState(); +} + +class _DangerAcceptanceFinishState extends State { + final List 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), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/home/work/dangerTypeItems/finish/danner_repair_finish.dart b/lib/pages/home/work/dangerTypeItems/finish/danner_repair_finish.dart new file mode 100644 index 0000000..08ce234 --- /dev/null +++ b/lib/pages/home/work/dangerTypeItems/finish/danner_repair_finish.dart @@ -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 createState() => _DannerRepairFinishState(); +} + +class _DannerRepairFinishState extends State { + final List 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: "无", + ), + ], + ), + ), + ], + ), + ), + ); + } +} + diff --git a/lib/pages/home/work/dangerTypeItems/wait/danger_acceptance.dart b/lib/pages/home/work/dangerTypeItems/wait/danger_acceptance.dart new file mode 100644 index 0000000..ce1cabc --- /dev/null +++ b/lib/pages/home/work/dangerTypeItems/wait/danger_acceptance.dart @@ -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 createState() => _DangerAcceptanceState(); +} + +class _DangerAcceptanceState extends State { + 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: () {}, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/home/work/dangerTypeItems/wait/danner_repair.dart b/lib/pages/home/work/dangerTypeItems/wait/danner_repair.dart new file mode 100644 index 0000000..57d1c6c --- /dev/null +++ b/lib/pages/home/work/dangerTypeItems/wait/danner_repair.dart @@ -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 createState() => _DannerRepairState(); +} + +class _DannerRepairState extends State { + + // 是否有整改方案 + 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 images) { + // images 列表更新 + }, + ), + ); + } +} diff --git a/lib/pages/home/work/danger_page.dart b/lib/pages/home/work/danger_page.dart index 64bdfce..1bf7412 100644 --- a/lib/pages/home/work/danger_page.dart +++ b/lib/pages/home/work/danger_page.dart @@ -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 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 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"), + ], + ), ); }, ), diff --git a/lib/pages/home/work/danger_project_page.dart b/lib/pages/home/work/danger_project_page.dart index 141e519..6aacf0b 100644 --- a/lib/pages/home/work/danger_project_page.dart +++ b/lib/pages/home/work/danger_project_page.dart @@ -152,7 +152,7 @@ class _DangerProjectPageState extends State { Text( label, style: TextStyle( - fontSize: 16, + fontSize: 14, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, color: isSelected ? color : Colors.grey[600], ), diff --git a/lib/pages/home/work/danger_wait_list_page.dart b/lib/pages/home/work/danger_wait_list_page.dart index ea79b69..f7a7f1c 100644 --- a/lib/pages/home/work/danger_wait_list_page.dart +++ b/lib/pages/home/work/danger_wait_list_page.dart @@ -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,74 +114,94 @@ class _DangerWaitListPageState extends State { }, child: Container( - height: 100, + // height: 100, color: Colors.white, - padding: EdgeInsets.symmetric(horizontal: 16), // 添加水平内边距 - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // 左侧信息列 - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item['title'] ?? '', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - SizedBox(height: 8), - Text( - item['label1'] ?? '', - style: TextStyle(fontSize: 14, color: Colors.grey), - ), - SizedBox(height: 4), - Text( - item['label2'] ?? '', - style: TextStyle(fontSize: 14, color: Colors.grey), - ), - ], - ), - ), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 15), // 添加水平内边距 + child: + IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // 左侧信息列 + Expanded( - // 右侧风险等级和箭头 - Padding( - padding: EdgeInsets.only(top: 15), - child: Column( - children: [ - // 风险等级标签 - Row( - children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: _fxColors[level], - borderRadius: BorderRadius.circular(2), - ), - child: Text( - _levelTexts[level], - style: TextStyle(fontSize: 12), - ), - ), - SizedBox(width: 30), - ], - ), - SizedBox(height: 12), // 添加间距 - Row( - children: [ - SizedBox(width: 110), - Icon(Icons.arrow_forward_ios_rounded, size: 16), - ], - ), - ], + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item['title'] ?? '', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 5), + Text( + item['label1'] ?? '', + 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), + ), + SizedBox(height: 5), + Text( + item['label2'] ?? '', + style: TextStyle(fontSize: 14, color: Colors.grey), + ), + ], + ), ), - ), - ], + + // 右侧风险等级和箭头 + Padding( + padding: EdgeInsets.only(top: 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // 风险等级标签 + Row( + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: _fxColors[level], + borderRadius: BorderRadius.circular(2), + ), + child: Text( + _levelTexts[level], + style: TextStyle(fontSize: 12), + ), + ), + SizedBox(width: 30), + ], + ), + SizedBox(height: 12), // 添加间距 + Row( + children: [ + SizedBox(width: 110), + 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,)],), + ], + ), + ), + ], + ), ), ), ); diff --git a/lib/pages/home/work/risk_list_page.dart b/lib/pages/home/work/risk_list_page.dart index d63e7f3..c3b751e 100644 --- a/lib/pages/home/work/risk_list_page.dart +++ b/lib/pages/home/work/risk_list_page.dart @@ -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 createState() => _riskListPageState(); + State createState() => _riskListPageState(); } -class _riskListPageState extends State { +class _riskListPageState extends State { final List _dataList = [ Model("青椒皮蛋鸡尾酒哦普佛椒皮鸡尾酒哦普佛椒皮鸡尾鸡尾酒哦普佛椒皮鸡尾鸡尾酒哦普佛椒皮鸡尾鸡尾酒哦普佛椒皮鸡尾鸡尾酒哦普佛"), diff --git a/lib/pages/main_tab.dart b/lib/pages/main_tab.dart index c1dff41..10a9559 100644 --- a/lib/pages/main_tab.dart +++ b/lib/pages/main_tab.dart @@ -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 { HomePage(), ApplicationPage(), NotifPage(), - MinePage() + MinePage(), ]; // 页面标题 - final List _titles = [ - '首页', - '应用中心', - '通知公告', - '我的', - ]; + final List _titles = ['首页', '应用中心', '通知公告', '我的']; @override Widget build(BuildContext context) { return Scaffold( - appBar: _currentIndex == 1 - ? null - : AppBar( - title: Text( - _currentIndex == 0 ? "泰盛安全首页" : _titles[_currentIndex], - style: const TextStyle( - fontSize: 17, - color: Colors.white, - ), - ), - centerTitle: true, - backgroundColor: Colors.blue, - actions: [ - if (_currentIndex == 0) - IconButton(onPressed: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => ScanPage() )); - }, icon: Image.asset("assets/images/scan.png", width: 20, height: 20,)) - ], - ), - + appBar: + _currentIndex == 1 + ? null + : 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, + ), + ), + ], + ), 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: '我的', ), ], diff --git a/lib/pages/mine/mine_about_page.dart b/lib/pages/mine/mine_about_page.dart new file mode 100644 index 0000000..2ca4eb2 --- /dev/null +++ b/lib/pages/mine/mine_about_page.dart @@ -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 createState() => _MineAboutPageState(); +} + +class _MineAboutPageState extends State { + String appVersion = "获取中..."; + + @override + void initState() { + super.initState(); + _loadAppVersion(); + } + + Future _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, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/mine/mine_page.dart b/lib/pages/mine/mine_page.dart index 735b6f7..123186b 100644 --- a/lib/pages/mine/mine_page.dart +++ b/lib/pages/mine/mine_page.dart @@ -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 { ), ), 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); + + }, + ), + ],), ); } diff --git a/lib/pages/mine/mine_set_page.dart b/lib/pages/mine/mine_set_page.dart new file mode 100644 index 0000000..cbb3352 --- /dev/null +++ b/lib/pages/mine/mine_set_page.dart @@ -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 _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 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), + ], + ), + ), + ); + } +} diff --git a/lib/pages/mine/mine_set_pwd_page.dart b/lib/pages/mine/mine_set_pwd_page.dart new file mode 100644 index 0000000..e47456d --- /dev/null +++ b/lib/pages/mine/mine_set_pwd_page.dart @@ -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 createState() => _MineSetPwdPageState(); +} + +class _MineSetPwdPageState extends State { + 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), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/my_appbar.dart b/lib/pages/my_appbar.dart index 9c6df44..579e9d0 100644 --- a/lib/pages/my_appbar.dart +++ b/lib/pages/my_appbar.dart @@ -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? actions; // 👉 新增参数:右侧按钮 + final List? 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), - onPressed: onBackPressed ?? () => Navigator.of(context).pop(), + 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? _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!, + ), + ) + ]; + } +} \ No newline at end of file diff --git a/lib/pages/notif/notif_detail_page.dart b/lib/pages/notif/notif_detail_page.dart new file mode 100644 index 0000000..6e950a1 --- /dev/null +++ b/lib/pages/notif/notif_detail_page.dart @@ -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("通知详情"), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/notif/notif_page.dart b/lib/pages/notif/notif_page.dart index c76b561..0a1bfbe 100644 --- a/lib/pages/notif/notif_page.dart +++ b/lib/pages/notif/notif_page.dart @@ -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 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( diff --git a/lib/tools/h_colors.dart b/lib/tools/h_colors.dart index 5f0251a..5553432 100644 --- a/lib/tools/h_colors.dart +++ b/lib/tools/h_colors.dart @@ -1,7 +1,7 @@ import 'dart:ffi'; import 'dart:ui'; -Color h_backGroundColor() => Color(0xF1F1F1FF); +Color h_backGroundColor() => Color(0xFFF1F1F1); List riskLevelTextColors() { return [Color(0xFFE54D42),Color(0xFFF37B1D),Color(0xFFF9BD08),Color(0xFF3281FF)]; } diff --git a/lib/tools/tools.dart b/lib/tools/tools.dart index d7814fe..75d69b6 100644 --- a/lib/tools/tools.dart +++ b/lib/tools/tools.dart @@ -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 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', + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 7c71458..065dc98 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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" diff --git a/pubspec.yaml b/pubspec.yaml index dad599c..f28a5d2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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