5 changed files with 493 additions and 5 deletions
Unified View
Diff Options
-
151lib/controller/discover/visitor_controller.dart
-
8lib/controller/mine/mine_controller.dart
-
75lib/model/discover/visitor_model.dart
-
257lib/pages/discover/visitor_list_page.dart
-
7lib/pages/mine/mine_page.dart
@ -0,0 +1,151 @@ |
|||||
|
// controllers/visitor_controller.dart |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:pull_to_refresh/pull_to_refresh.dart'; |
||||
|
import '../../model/discover/visitor_model.dart'; |
||||
|
|
||||
|
class VisitorController extends GetxController { |
||||
|
// 访客列表 |
||||
|
var visitors = <VisitorModel>[].obs; |
||||
|
|
||||
|
// Refresh controllers |
||||
|
final RefreshController refreshController = RefreshController(); |
||||
|
final RefreshController loadMoreController = RefreshController(); |
||||
|
|
||||
|
// 分页参数 |
||||
|
var currentPage = 1.obs; |
||||
|
final int pageSize = 15; |
||||
|
var hasMore = true.obs; |
||||
|
var isLoading = false.obs; |
||||
|
|
||||
|
@override |
||||
|
void onInit() { |
||||
|
super.onInit(); |
||||
|
_loadInitialData(); |
||||
|
} |
||||
|
|
||||
|
// 加载初始数据 |
||||
|
void _loadInitialData() async { |
||||
|
isLoading.value = true; |
||||
|
await Future.delayed(Duration(milliseconds: 800)); // 模拟网络延迟 |
||||
|
|
||||
|
final initialVisitors = _generateMockData(1, pageSize); |
||||
|
visitors.assignAll(initialVisitors); |
||||
|
|
||||
|
isLoading.value = false; |
||||
|
hasMore.value = initialVisitors.length == pageSize; |
||||
|
} |
||||
|
|
||||
|
// 下拉刷新 |
||||
|
void onRefresh() async { |
||||
|
currentPage.value = 1; |
||||
|
hasMore.value = true; |
||||
|
|
||||
|
await Future.delayed(Duration(milliseconds: 1000)); // 模拟网络延迟 |
||||
|
|
||||
|
final newVisitors = _generateMockData(1, pageSize); |
||||
|
visitors.assignAll(newVisitors); |
||||
|
|
||||
|
refreshController.refreshCompleted(); |
||||
|
hasMore.value = newVisitors.length == pageSize; |
||||
|
|
||||
|
if (newVisitors.isEmpty) { |
||||
|
refreshController.loadNoData(); |
||||
|
} else { |
||||
|
refreshController.loadComplete(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 上拉加载更多 |
||||
|
void onLoadMore() async { |
||||
|
if (!hasMore.value) { |
||||
|
loadMoreController.loadNoData(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
currentPage.value++; |
||||
|
|
||||
|
await Future.delayed(Duration(milliseconds: 800)); // 模拟网络延迟 |
||||
|
|
||||
|
final newVisitors = _generateMockData(currentPage.value, pageSize); |
||||
|
|
||||
|
if (newVisitors.isEmpty) { |
||||
|
hasMore.value = false; |
||||
|
loadMoreController.loadNoData(); |
||||
|
} else { |
||||
|
visitors.addAll(newVisitors); |
||||
|
loadMoreController.loadComplete(); |
||||
|
hasMore.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 删除访客 |
||||
|
void deleteVisitor(String visitorId) { |
||||
|
// visitors.removeWhere((visitor) => visitor.id == visitorId); |
||||
|
Get.snackbar('成功', '已删除访客记录'); |
||||
|
} |
||||
|
|
||||
|
// 清空所有访客记录 |
||||
|
void clearAllVisitors() { |
||||
|
visitors.clear(); |
||||
|
Get.snackbar('成功', '已清空所有访客记录'); |
||||
|
} |
||||
|
|
||||
|
// 获取今日访客数量 |
||||
|
int get todayVisitorsCount { |
||||
|
return visitors.length; |
||||
|
} |
||||
|
|
||||
|
// 获取在线访客数量 |
||||
|
int get onlineVisitorsCount { |
||||
|
return visitors.length; |
||||
|
} |
||||
|
|
||||
|
// 模拟数据生成 |
||||
|
List<VisitorModel> _generateMockData(int page, int size) { |
||||
|
if (page > 3) return []; // 模拟只有3页数据 |
||||
|
|
||||
|
final names = [ |
||||
|
'张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十', |
||||
|
'小明', '小红', '小刚', '小花', '大山', '小云', '星辰', '大海' |
||||
|
]; |
||||
|
|
||||
|
final avatars = [ |
||||
|
'https://randomuser.me/api/portraits/men/1.jpg', |
||||
|
'https://randomuser.me/api/portraits/women/1.jpg', |
||||
|
'https://randomuser.me/api/portraits/men/2.jpg', |
||||
|
'https://randomuser.me/api/portraits/women/2.jpg', |
||||
|
'https://randomuser.me/api/portraits/men/3.jpg', |
||||
|
'https://randomuser.me/api/portraits/women/3.jpg', |
||||
|
'https://randomuser.me/api/portraits/men/4.jpg', |
||||
|
'https://randomuser.me/api/portraits/women/4.jpg', |
||||
|
]; |
||||
|
|
||||
|
final pages = [ |
||||
|
'首页', '产品页', '关于我们', '博客', '联系方式', '服务页', '价格页', '案例页' |
||||
|
]; |
||||
|
|
||||
|
final startIndex = (page - 1) * size; |
||||
|
final endIndex = startIndex + size; |
||||
|
|
||||
|
return List.generate(size, (index) { |
||||
|
final globalIndex = startIndex + index; |
||||
|
final isOnline = globalIndex % 3 == 0; // 每3个有一个在线 |
||||
|
|
||||
|
return VisitorModel( |
||||
|
miId: 'visitor_${globalIndex + 1}', |
||||
|
nickName: names[globalIndex % names.length], |
||||
|
profilePhoto: avatars[globalIndex % avatars.length], |
||||
|
visitTime: '2025-11-14 10:42:42', |
||||
|
onlineStatus: 2, |
||||
|
describeInfo: '阿萨德垃圾袋杰克伦敦撒娇克拉斯的健康拉屎的 金坷垃四大皆空拉屎的距离考试啊' |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void onClose() { |
||||
|
refreshController.dispose(); |
||||
|
loadMoreController.dispose(); |
||||
|
super.onClose(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
class VisitorModel { |
||||
|
final String? miId; |
||||
|
final String? profilePhoto; |
||||
|
final String? nickName; |
||||
|
final int? height; |
||||
|
final String? visitTime; |
||||
|
final String? education; |
||||
|
final int? age; |
||||
|
final int? minimumIncome; |
||||
|
final int? maximumIncome; |
||||
|
final String? income; |
||||
|
final String? describeInfo; |
||||
|
final int? vip; |
||||
|
final int? miSessionType; |
||||
|
final int? genderCode; |
||||
|
final int? onlineStatus; |
||||
|
|
||||
|
VisitorModel({ |
||||
|
this.miId, |
||||
|
this.profilePhoto, |
||||
|
this.nickName, |
||||
|
this.height, |
||||
|
this.visitTime, |
||||
|
this.education, |
||||
|
this.age, |
||||
|
this.minimumIncome, |
||||
|
this.maximumIncome, |
||||
|
this.income, |
||||
|
this.describeInfo, |
||||
|
this.vip, |
||||
|
this.miSessionType, |
||||
|
this.genderCode, |
||||
|
this.onlineStatus, |
||||
|
}); |
||||
|
|
||||
|
factory VisitorModel.fromJson(Map<String, dynamic> json) { |
||||
|
return VisitorModel( |
||||
|
miId: json['miId'] as String?, |
||||
|
profilePhoto: json['profilePhoto'] as String?, |
||||
|
nickName: json['nickName'] as String?, |
||||
|
height: json['height'] as int?, |
||||
|
visitTime: json['visitTime'] as String?, |
||||
|
education: json['education'] as String?, |
||||
|
age: json['age'] as int?, |
||||
|
minimumIncome: json['minimumIncome'] as int?, |
||||
|
maximumIncome: json['maximumIncome'] as int?, |
||||
|
income: json['income'] as String?, |
||||
|
describeInfo: json['describeInfo'] as String?, |
||||
|
vip: json['vip'] as int?, |
||||
|
miSessionType: json['miSessionType'] as int?, |
||||
|
genderCode: json['genderCode'] as int?, |
||||
|
onlineStatus: json['onlineStatus'] as int?, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Map<String, dynamic> toJson() { |
||||
|
return { |
||||
|
'miId': miId, |
||||
|
'profilePhoto': profilePhoto, |
||||
|
'nickName': nickName, |
||||
|
'height': height, |
||||
|
'visitTime': visitTime, |
||||
|
'education': education, |
||||
|
'age': age, |
||||
|
'minimumIncome': minimumIncome, |
||||
|
'maximumIncome': maximumIncome, |
||||
|
'income': income, |
||||
|
'describeInfo': describeInfo, |
||||
|
'vip': vip, |
||||
|
'miSessionType': miSessionType, |
||||
|
'genderCode': genderCode, |
||||
|
'onlineStatus': onlineStatus, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,257 @@ |
|||||
|
// pages/visitor_list_page.dart |
||||
|
import 'package:cached_network_image/cached_network_image.dart'; |
||||
|
import 'package:dating_touchme_app/extension/ex_widget.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:pull_to_refresh/pull_to_refresh.dart'; |
||||
|
|
||||
|
import '../../controller/discover/visitor_controller.dart'; |
||||
|
import '../../model/discover/visitor_model.dart'; |
||||
|
|
||||
|
class VisitorListPage extends StatelessWidget { |
||||
|
final VisitorController visitorController = Get.put(VisitorController()); |
||||
|
|
||||
|
VisitorListPage({Key? key}) : super(key: key); |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Scaffold( |
||||
|
backgroundColor: Color(0xffF5F5F5), |
||||
|
appBar: AppBar( |
||||
|
title: Obx(() => Text( |
||||
|
'最近访客 (${visitorController.todayVisitorsCount})', |
||||
|
style: TextStyle(fontSize: 16), |
||||
|
)), |
||||
|
backgroundColor: Colors.white, |
||||
|
actions: [ |
||||
|
// 更多操作 |
||||
|
PopupMenuButton<String>( |
||||
|
onSelected: (value) { |
||||
|
if (value == 'clear') { |
||||
|
_showClearDialog(); |
||||
|
} else if (value == 'sort') { |
||||
|
_showSortDialog(); |
||||
|
} |
||||
|
}, |
||||
|
itemBuilder: (BuildContext context) => [ |
||||
|
PopupMenuItem(value: 'sort', child: Text('排序方式')), |
||||
|
PopupMenuItem(value: 'clear', child: Text('清空记录')), |
||||
|
], |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
body: Obx(() { |
||||
|
if (visitorController.isLoading.value && visitorController.visitors.isEmpty) { |
||||
|
return Center( |
||||
|
child: Column( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
CircularProgressIndicator(), |
||||
|
SizedBox(height: 16), |
||||
|
Text('加载访客数据中...'), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if (visitorController.visitors.isEmpty) { |
||||
|
return Center( |
||||
|
child: Column( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
Icon(Icons.people_outline, size: 80, color: Colors.grey[300]), |
||||
|
SizedBox(height: 16), |
||||
|
Text( |
||||
|
'暂无访客记录', |
||||
|
style: TextStyle(fontSize: 18, color: Colors.grey), |
||||
|
), |
||||
|
SizedBox(height: 8), |
||||
|
Text( |
||||
|
'下拉刷新获取数据', |
||||
|
style: TextStyle(color: Colors.grey), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return SmartRefresher( |
||||
|
controller: visitorController.refreshController, |
||||
|
enablePullDown: true, |
||||
|
enablePullUp: visitorController.hasMore.value, |
||||
|
onRefresh: visitorController.onRefresh, |
||||
|
onLoading: visitorController.onLoadMore, |
||||
|
header: ClassicHeader( |
||||
|
idleText: '下拉刷新', |
||||
|
releaseText: '松开刷新', |
||||
|
refreshingText: '刷新中...', |
||||
|
completeText: '刷新完成', |
||||
|
failedText: '刷新失败', |
||||
|
height: 60, |
||||
|
), |
||||
|
footer: CustomFooter( |
||||
|
builder: (BuildContext context, LoadStatus? mode) { |
||||
|
Widget body; |
||||
|
if (mode == LoadStatus.idle) { |
||||
|
body = Text("上拉加载更多"); |
||||
|
} else if (mode == LoadStatus.loading) { |
||||
|
body = CircularProgressIndicator(); |
||||
|
} else if (mode == LoadStatus.failed) { |
||||
|
body = Text("加载失败,点击重试"); |
||||
|
} else if (mode == LoadStatus.canLoading) { |
||||
|
body = Text("松开加载更多"); |
||||
|
} else { |
||||
|
body = Text("没有更多数据了"); |
||||
|
} |
||||
|
return SizedBox( |
||||
|
height: 36, |
||||
|
child: Center(child: body), |
||||
|
); |
||||
|
}, |
||||
|
), |
||||
|
child: ListView.builder( |
||||
|
padding: const EdgeInsets.only(top: 8, right: 10, left: 10), |
||||
|
itemCount: visitorController.visitors.length, |
||||
|
itemBuilder: (context, index) { |
||||
|
final visitor = visitorController.visitors[index]; |
||||
|
return VisitorListItem(visitor: visitor); |
||||
|
}, |
||||
|
), |
||||
|
); |
||||
|
}), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
void _showClearDialog() { |
||||
|
Get.dialog( |
||||
|
AlertDialog( |
||||
|
title: Text('确认清空'), |
||||
|
content: Text('确定要清空所有访客记录吗?此操作不可恢复。'), |
||||
|
actions: [ |
||||
|
TextButton( |
||||
|
onPressed: () => Get.back(), |
||||
|
child: Text('取消'), |
||||
|
), |
||||
|
TextButton( |
||||
|
onPressed: () { |
||||
|
visitorController.clearAllVisitors(); |
||||
|
Get.back(); |
||||
|
}, |
||||
|
child: Text('确定', style: TextStyle(color: Colors.red)), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
void _showSortDialog() { |
||||
|
Get.dialog( |
||||
|
AlertDialog( |
||||
|
title: Text('排序方式'), |
||||
|
content: Column( |
||||
|
mainAxisSize: MainAxisSize.min, |
||||
|
children: [ |
||||
|
ListTile( |
||||
|
title: Text('按访问时间'), |
||||
|
onTap: () { |
||||
|
// 实现排序逻辑 |
||||
|
Get.back(); |
||||
|
}, |
||||
|
), |
||||
|
ListTile( |
||||
|
title: Text('按访问时长'), |
||||
|
onTap: () { |
||||
|
// 实现排序逻辑 |
||||
|
Get.back(); |
||||
|
}, |
||||
|
), |
||||
|
ListTile( |
||||
|
title: Text('按访问次数'), |
||||
|
onTap: () { |
||||
|
// 实现排序逻辑 |
||||
|
Get.back(); |
||||
|
}, |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class VisitorListItem extends StatelessWidget { |
||||
|
final VisitorModel visitor; |
||||
|
|
||||
|
const VisitorListItem({Key? key, required this.visitor}) : super(key: key); |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Container( |
||||
|
decoration: BoxDecoration( |
||||
|
color: Colors.white, |
||||
|
borderRadius: BorderRadius.circular(12), |
||||
|
), |
||||
|
padding: const EdgeInsets.all(12), |
||||
|
margin: EdgeInsets.only(bottom: 8), |
||||
|
child: Row( |
||||
|
crossAxisAlignment: CrossAxisAlignment.center, |
||||
|
children: [ |
||||
|
// 左侧图片 |
||||
|
Container( |
||||
|
width: 50, |
||||
|
height: 50, |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.circular(8), |
||||
|
color: Colors.blue[100], |
||||
|
image: DecorationImage( |
||||
|
image: NetworkImage(visitor.profilePhoto!), |
||||
|
fit: BoxFit.cover, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
SizedBox(width: 12), |
||||
|
Expanded( |
||||
|
child: Column( |
||||
|
children: [ |
||||
|
Row( |
||||
|
children: [ |
||||
|
Text( |
||||
|
visitor.nickName!, |
||||
|
style: TextStyle( |
||||
|
fontSize: 14, |
||||
|
), |
||||
|
), |
||||
|
Spacer(), |
||||
|
Text( |
||||
|
visitor.visitTime!, |
||||
|
style: TextStyle( |
||||
|
color: Colors.grey[500], |
||||
|
fontSize: 13, |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
SizedBox(height: 4), |
||||
|
SizedBox( |
||||
|
// height: 20, // 固定高度20 |
||||
|
child: Text( |
||||
|
visitor.describeInfo!, |
||||
|
style: TextStyle( |
||||
|
fontSize: 13, |
||||
|
color: Color.fromRGBO(51, 51, 51, 0.6), |
||||
|
), |
||||
|
overflow: TextOverflow.ellipsis, // 文本超出显示... |
||||
|
maxLines: 1, // 限制为单行 |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
) |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
).onTap((){ |
||||
|
// _showVisitorDetail(visitor); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save