From 7d9c879a4ec0c163a024dd8d6add4cb330b5dd5f Mon Sep 17 00:00:00 2001 From: userGyl Date: Wed, 7 Jan 2026 17:43:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B6=88=E6=81=AF=E4=B8=AD=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/pages/c_page/message/controller.dart | 106 +++++++++++++ ln_jq_app/lib/pages/c_page/message/model.dart | 25 +++ ln_jq_app/lib/pages/c_page/message/view.dart | 150 ++++++++++++++++++ ln_jq_app/lib/pages/c_page/mine/view.dart | 4 +- ln_jq_app/pubspec.lock | 8 + ln_jq_app/pubspec.yaml | 3 + 6 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 ln_jq_app/lib/pages/c_page/message/controller.dart create mode 100644 ln_jq_app/lib/pages/c_page/message/model.dart create mode 100644 ln_jq_app/lib/pages/c_page/message/view.dart diff --git a/ln_jq_app/lib/pages/c_page/message/controller.dart b/ln_jq_app/lib/pages/c_page/message/controller.dart new file mode 100644 index 0000000..b4b899f --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/message/controller.dart @@ -0,0 +1,106 @@ +import 'package:get/get.dart'; +import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'model.dart'; + +class MessageController extends GetxController { + final RefreshController refreshController = RefreshController(initialRefresh: false); + final RxList messageList = [].obs; + int _pageNum = 1; + final int _pageSize = 10; + + // 模拟所有已读状态,实际应根据接口判断 + final RxBool allRead = false.obs; + + @override + void onInit() { + super.onInit(); + _loadData(isRefresh: true); + } + + Future _loadData({bool isRefresh = false}) async { + if (isRefresh) { + _pageNum = 1; + } else { + _pageNum++; + } + + try { + // 模拟请求延迟 + await Future.delayed(const Duration(milliseconds: 500)); + + // 模拟数据 (请替换为实际接口请求) + // var response = await HttpService.to.post('message/list', data: {'pageNum': _pageNum, 'pageSize': _pageSize}); + + List newData = List.generate(10, (index) { + int id = (_pageNum - 1) * _pageSize + index; + return MessageModel( + id: id.toString(), + title: '加氢预约失败通知', + content: '1月6日14时30分-1月6日15时30分,北京朝阳加氢站加氢预约失败。原因:设备维护', + createTime: '刚刚', + isRead: index % 3 == 0 ? 1 : 0, + ); + }); + + if (isRefresh) { + messageList.assignAll(newData); + refreshController.refreshCompleted(); + } else { + if (newData.isEmpty) { + refreshController.loadNoData(); + } else { + messageList.addAll(newData); + refreshController.loadComplete(); + } + } + + _checkAllRead(); + + } catch (e) { + if (isRefresh) { + refreshController.refreshFailed(); + } else { + refreshController.loadFailed(); + } + } + } + + void onRefresh() => _loadData(isRefresh: true); + void onLoading() => _loadData(isRefresh: false); + + // 标记全部已读 + void markAllRead() async { + showLoading('正在标记...'); + try { + // await HttpService.to.post('message/readAll'); + await Future.delayed(const Duration(milliseconds: 500)); + + for (var msg in messageList) { + msg.isRead = 1; + } + messageList.refresh(); + allRead.value = true; + + dismissLoading(); + showSuccessToast('全部已读'); + } catch (e) { + dismissLoading(); + showErrorToast('操作失败'); + } + } + + // 标记单条已读(弹窗显示时调用) + void markRead(MessageModel message) async { + if (message.isRead == 1) return; + + // await HttpService.to.post('message/read', data: {'id': message.id}); + message.isRead = 1; + messageList.refresh(); + _checkAllRead(); + } + + void _checkAllRead() { + allRead.value = messageList.every((msg) => msg.isRead == 1); + } +} diff --git a/ln_jq_app/lib/pages/c_page/message/model.dart b/ln_jq_app/lib/pages/c_page/message/model.dart new file mode 100644 index 0000000..d0a5ce0 --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/message/model.dart @@ -0,0 +1,25 @@ +class MessageModel { + final String id; + final String title; + final String content; + final String createTime; + int isRead; // 0: 未读, 1: 已读 + + MessageModel({ + required this.id, + required this.title, + required this.content, + required this.createTime, + required this.isRead, + }); + + factory MessageModel.fromJson(Map json) { + return MessageModel( + id: json['id']?.toString() ?? '', + title: json['title']?.toString() ?? '消息通知', + content: json['content']?.toString() ?? '', + createTime: json['createTime']?.toString() ?? '', + isRead: json['isRead'] as int? ?? 0, + ); + } +} diff --git a/ln_jq_app/lib/pages/c_page/message/view.dart b/ln_jq_app/lib/pages/c_page/message/view.dart new file mode 100644 index 0000000..18b376b --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/message/view.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'controller.dart'; +import 'model.dart'; + +class MessagePage extends GetView { + const MessagePage({super.key}); + + @override + Widget build(BuildContext context) { + Get.put(MessageController()); + + return Scaffold( + backgroundColor: const Color(0xFFF5F5F5), + appBar: AppBar(title: const Text('消息通知'), centerTitle: true), + body: Column( + children: [ + Expanded( + child: Obx(() => SmartRefresher( + controller: controller.refreshController, + enablePullUp: true, + onRefresh: controller.onRefresh, + onLoading: controller.onLoading, + child: ListView.separated( + padding: const EdgeInsets.all(12), + itemCount: controller.messageList.length, + separatorBuilder: (_, __) => const SizedBox(height: 12), + itemBuilder: (context, index) { + return _buildMessageItem(context, controller.messageList[index]); + }, + ), + )), + ), + Obx(() => !controller.allRead.value + ? Container( + padding: const EdgeInsets.all(16), + color: Colors.white, + child: ElevatedButton( + onPressed: controller.markAllRead, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + minimumSize: const Size(double.infinity, 44), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + child: const Text('全部标为已读', style: TextStyle(fontSize: 16, color: Colors.white)), + ), + ) + : const SizedBox.shrink()), + ], + ), + ); + } + + Widget _buildMessageItem(BuildContext context, MessageModel item) { + return GestureDetector( + onTap: () { + controller.markRead(item); + _showMessageDialog(context, item); + }, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.only(top: 6, right: 12), + width: 8, + height: 8, + decoration: BoxDecoration( + color: item.isRead == 1 ? Colors.grey[300] : const Color(0xFFFAAD14), + shape: BoxShape.circle, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87), + ), + const SizedBox(height: 8), + Text( + item.content, + style: TextStyle(fontSize: 14, color: Colors.grey[600]), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + Text( + item.createTime, + style: TextStyle(fontSize: 12, color: Colors.grey[400]), + ), + ], + ), + ), + ], + ), + ), + ); + } + + void _showMessageDialog(BuildContext context, MessageModel item) { + showDialog( + context: context, + barrierDismissible: true, + builder: (context) { + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + child: Container( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + Text( + item.content, + style: const TextStyle(fontSize: 15, height: 1.5, color: Colors.black87), + ), + const SizedBox(height: 24), + Align( + alignment: Alignment.centerRight, + child: OutlinedButton( + onPressed: () => Navigator.pop(context), + style: OutlinedButton.styleFrom( + side: const BorderSide(color: Colors.blue), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + ), + child: const Text('确认', style: TextStyle(color: Colors.blue)), + ), + ), + ], + ), + ), + ); + }, + ); + } +} \ No newline at end of file diff --git a/ln_jq_app/lib/pages/c_page/mine/view.dart b/ln_jq_app/lib/pages/c_page/mine/view.dart index e7374a9..e1e27ab 100644 --- a/ln_jq_app/lib/pages/c_page/mine/view.dart +++ b/ln_jq_app/lib/pages/c_page/mine/view.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:getx_scaffold/common/index.dart'; import 'package:getx_scaffold/common/widgets/index.dart'; import 'package:ln_jq_app/common/styles/theme.dart'; +import 'package:ln_jq_app/pages/c_page/message/view.dart'; import 'package:ln_jq_app/storage_service.dart'; import 'controller.dart'; @@ -82,7 +83,8 @@ class MinePage extends GetView { ), IconButton( onPressed: () { - // TODO: 跳转 + // 跳转消息中心 + Get.to(() => const MessagePage()); }, // 这里的 style 是为了模拟你图片里的灰色圆形背景 style: IconButton.styleFrom( diff --git a/ln_jq_app/pubspec.lock b/ln_jq_app/pubspec.lock index cd549d5..d680450 100644 --- a/ln_jq_app/pubspec.lock +++ b/ln_jq_app/pubspec.lock @@ -933,6 +933,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.0.3" + pull_to_refresh: + dependency: "direct main" + description: + name: pull_to_refresh + sha256: bbadd5a931837b57739cf08736bea63167e284e71fb23b218c8c9a6e042aad12 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" rational: dependency: transitive description: diff --git a/ln_jq_app/pubspec.yaml b/ln_jq_app/pubspec.yaml index 1d1f73f..8a163a7 100644 --- a/ln_jq_app/pubspec.yaml +++ b/ln_jq_app/pubspec.yaml @@ -51,6 +51,9 @@ dependencies: flutter_inappwebview: ^6.1.5 # WebView插件 geolocator: ^14.0.2 # 获取精确定位 aliyun_push_flutter: ^1.3.6 + pull_to_refresh: ^2.0.0 + + dev_dependencies: flutter_test: