消息中心
This commit is contained in:
106
ln_jq_app/lib/pages/c_page/message/controller.dart
Normal file
106
ln_jq_app/lib/pages/c_page/message/controller.dart
Normal file
@@ -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<MessageModel> messageList = <MessageModel>[].obs;
|
||||||
|
int _pageNum = 1;
|
||||||
|
final int _pageSize = 10;
|
||||||
|
|
||||||
|
// 模拟所有已读状态,实际应根据接口判断
|
||||||
|
final RxBool allRead = false.obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
_loadData(isRefresh: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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<MessageModel> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
ln_jq_app/lib/pages/c_page/message/model.dart
Normal file
25
ln_jq_app/lib/pages/c_page/message/model.dart
Normal file
@@ -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<String, dynamic> 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
150
ln_jq_app/lib/pages/c_page/message/view.dart
Normal file
150
ln_jq_app/lib/pages/c_page/message/view.dart
Normal file
@@ -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<MessageController> {
|
||||||
|
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)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:getx_scaffold/common/index.dart';
|
import 'package:getx_scaffold/common/index.dart';
|
||||||
import 'package:getx_scaffold/common/widgets/index.dart';
|
import 'package:getx_scaffold/common/widgets/index.dart';
|
||||||
import 'package:ln_jq_app/common/styles/theme.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 'package:ln_jq_app/storage_service.dart';
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
@@ -82,7 +83,8 @@ class MinePage extends GetView<MineController> {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: 跳转
|
// 跳转消息中心
|
||||||
|
Get.to(() => const MessagePage());
|
||||||
},
|
},
|
||||||
// 这里的 style 是为了模拟你图片里的灰色圆形背景
|
// 这里的 style 是为了模拟你图片里的灰色圆形背景
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
|
|||||||
@@ -933,6 +933,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.3"
|
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:
|
rational:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ dependencies:
|
|||||||
flutter_inappwebview: ^6.1.5 # WebView插件
|
flutter_inappwebview: ^6.1.5 # WebView插件
|
||||||
geolocator: ^14.0.2 # 获取精确定位
|
geolocator: ^14.0.2 # 获取精确定位
|
||||||
aliyun_push_flutter: ^1.3.6
|
aliyun_push_flutter: ^1.3.6
|
||||||
|
pull_to_refresh: ^2.0.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user