From e280f6ee4acdc33ec6496e9ffcbd2640defe4fd4 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Mon, 14 Jul 2025 15:45:41 +0800 Subject: [PATCH] login/exp log Signed-off-by: bggRGjQaUbCoE --- lib/http/api.dart | 6 + lib/http/user.dart | 31 ++++ lib/models_new/login_log/data.dart | 15 ++ lib/models_new/login_log/list.dart | 26 ++++ lib/pages/blacklist/view.dart | 6 +- .../controller.dart | 3 +- .../{member_coin_log => coin_log}/view.dart | 12 +- lib/pages/exp_log/controller.dart | 21 +++ lib/pages/exp_log/view.dart | 137 ++++++++++++++++++ lib/pages/login_log/controller.dart | 22 +++ lib/pages/login_log/view.dart | 137 ++++++++++++++++++ lib/pages/member/view.dart | 30 +++- lib/pages/member_profile/view.dart | 2 +- 13 files changed, 433 insertions(+), 15 deletions(-) create mode 100644 lib/models_new/login_log/data.dart create mode 100644 lib/models_new/login_log/list.dart rename lib/pages/{member_coin_log => coin_log}/controller.dart (85%) rename lib/pages/{member_coin_log => coin_log}/view.dart (92%) create mode 100644 lib/pages/exp_log/controller.dart create mode 100644 lib/pages/exp_log/view.dart create mode 100644 lib/pages/login_log/controller.dart create mode 100644 lib/pages/login_log/view.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 3f0dd696..b0f7762b 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -929,4 +929,10 @@ class Api { static const String updateReserve = '/x/new-reserve/up/reserve/update'; static const String reserveInfo = '/x/new-reserve/up/reserve/info'; + + static const String loginLog = '/x/member/web/login/log'; + + static const String expLog = '/x/member/web/exp/log'; + + static const String moralLog = '/x/member/web/moral/log'; } diff --git a/lib/http/user.dart b/lib/http/user.dart index d55d56c4..63b6aa07 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -7,6 +7,7 @@ import 'package:PiliPlus/models/user/stat.dart'; import 'package:PiliPlus/models_new/coin_log/data.dart'; import 'package:PiliPlus/models_new/history/data.dart'; import 'package:PiliPlus/models_new/later/data.dart'; +import 'package:PiliPlus/models_new/login_log/data.dart'; import 'package:PiliPlus/models_new/media_list/data.dart'; import 'package:PiliPlus/models_new/space_setting/data.dart'; import 'package:PiliPlus/models_new/sub/sub/data.dart'; @@ -413,4 +414,34 @@ class UserHttp { return Error(res.data['message']); } } + + static Future> loginLog() async { + final res = await Request().get( + Api.loginLog, + queryParameters: { + 'jsonp': 'jsonp', + 'web_location': '333.33', + }, + ); + if (res.data['code'] == 0) { + return Success(LoginLogData.fromJson(res.data['data'])); + } else { + return Error(res.data['message']); + } + } + + static Future> expLog() async { + final res = await Request().get( + Api.expLog, + queryParameters: { + 'jsonp': 'jsonp', + 'web_location': '333.33', + }, + ); + if (res.data['code'] == 0) { + return Success(CoinLogData.fromJson(res.data['data'])); + } else { + return Error(res.data['message']); + } + } } diff --git a/lib/models_new/login_log/data.dart b/lib/models_new/login_log/data.dart new file mode 100644 index 00000000..ca3a19e0 --- /dev/null +++ b/lib/models_new/login_log/data.dart @@ -0,0 +1,15 @@ +import 'package:PiliPlus/models_new/login_log/list.dart'; + +class LoginLogData { + int? count; + List? list; + + LoginLogData({this.count, this.list}); + + factory LoginLogData.fromJson(Map json) => LoginLogData( + count: json['count'] as int?, + list: (json['list'] as List?) + ?.map((e) => LoginLogItem.fromJson(e as Map)) + .toList(), + ); +} diff --git a/lib/models_new/login_log/list.dart b/lib/models_new/login_log/list.dart new file mode 100644 index 00000000..eff2e87c --- /dev/null +++ b/lib/models_new/login_log/list.dart @@ -0,0 +1,26 @@ +class LoginLogItem { + String ip; + int? time; + String timeAt; + bool? status; + int? type; + String geo; + + LoginLogItem({ + required this.ip, + this.time, + required this.timeAt, + this.status, + this.type, + required this.geo, + }); + + factory LoginLogItem.fromJson(Map json) => LoginLogItem( + ip: json['ip'] ?? '', + time: json['time'] as int?, + timeAt: json['time_at'] ?? '', + status: json['status'] as bool?, + type: json['type'] as int?, + geo: json['geo'] ?? '', + ); +} diff --git a/lib/pages/blacklist/view.dart b/lib/pages/blacklist/view.dart index f80160b2..41e22d67 100644 --- a/lib/pages/blacklist/view.dart +++ b/lib/pages/blacklist/view.dart @@ -59,6 +59,7 @@ class _BlackListPageState extends State { } Widget _buildBody(LoadingState?> loadingState) { + late final style = TextStyle(color: Theme.of(context).colorScheme.outline); return switch (loadingState) { Loading() => SliverList.builder( itemCount: 12, @@ -89,10 +90,9 @@ class _BlackListPageState extends State { style: const TextStyle(fontSize: 14), ), subtitle: Text( - DateUtil.dateFormat(item.mtime), + '添加时间: ${DateUtil.format(item.mtime, format: DateUtil.longFormatDs)}', maxLines: 1, - style: - TextStyle(color: Theme.of(context).colorScheme.outline), + style: style, overflow: TextOverflow.ellipsis, ), dense: true, diff --git a/lib/pages/member_coin_log/controller.dart b/lib/pages/coin_log/controller.dart similarity index 85% rename from lib/pages/member_coin_log/controller.dart rename to lib/pages/coin_log/controller.dart index 1b0998dc..82f5d403 100644 --- a/lib/pages/member_coin_log/controller.dart +++ b/lib/pages/coin_log/controller.dart @@ -4,8 +4,7 @@ import 'package:PiliPlus/models_new/coin_log/data.dart'; import 'package:PiliPlus/models_new/coin_log/list.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart'; -class MemberCoinLogController - extends CommonListController { +class CoinLogController extends CommonListController { @override void onInit() { super.onInit(); diff --git a/lib/pages/member_coin_log/view.dart b/lib/pages/coin_log/view.dart similarity index 92% rename from lib/pages/member_coin_log/view.dart rename to lib/pages/coin_log/view.dart index 38cbfe1f..cc45d4e1 100644 --- a/lib/pages/member_coin_log/view.dart +++ b/lib/pages/coin_log/view.dart @@ -2,19 +2,19 @@ import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/coin_log/list.dart'; -import 'package:PiliPlus/pages/member_coin_log/controller.dart'; +import 'package:PiliPlus/pages/coin_log/controller.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -class MemberCoinLogPage extends StatefulWidget { - const MemberCoinLogPage({super.key}); +class CoinLogPage extends StatefulWidget { + const CoinLogPage({super.key}); @override - State createState() => _MemberCoinLogPageState(); + State createState() => _CoinLogPageState(); } -class _MemberCoinLogPageState extends State { - late final _controller = Get.put(MemberCoinLogController()); +class _CoinLogPageState extends State { + late final _controller = Get.put(CoinLogController()); @override Widget build(BuildContext context) { diff --git a/lib/pages/exp_log/controller.dart b/lib/pages/exp_log/controller.dart new file mode 100644 index 00000000..42e29300 --- /dev/null +++ b/lib/pages/exp_log/controller.dart @@ -0,0 +1,21 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/user.dart'; +import 'package:PiliPlus/models_new/coin_log/data.dart'; +import 'package:PiliPlus/models_new/coin_log/list.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; + +class ExpLogController extends CommonListController { + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + List? getDataList(CoinLogData response) { + return response.list; + } + + @override + Future> customGetData() => UserHttp.expLog(); +} diff --git a/lib/pages/exp_log/view.dart b/lib/pages/exp_log/view.dart new file mode 100644 index 00000000..c36d3fdc --- /dev/null +++ b/lib/pages/exp_log/view.dart @@ -0,0 +1,137 @@ +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models_new/coin_log/list.dart'; +import 'package:PiliPlus/pages/exp_log/controller.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class ExpLogPage extends StatefulWidget { + const ExpLogPage({super.key}); + + @override + State createState() => _ExpLogPageState(); +} + +class _ExpLogPageState extends State { + late final _controller = Get.put(ExpLogController()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('经验记录')), + body: SafeArea( + top: false, + bottom: false, + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 680), + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + left: 10, + right: 10, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _buildBody(_controller.loadingState.value)), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildBody(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => linearLoading, + Success(:var response) => response?.isNotEmpty == true + ? Builder( + builder: (context) { + final them = Theme.of(context); + final outline = them.colorScheme.outline.withValues(alpha: 0.1); + final divider = Divider( + height: 1, + color: outline, + ); + final sliverDivider = SliverToBoxAdapter( + child: divider, + ); + final dividerV = VerticalDivider( + width: 1, + color: outline, + ); + return SliverMainAxisGroup( + slivers: [ + sliverDivider, + SliverToBoxAdapter( + child: ColoredBox( + color: them.colorScheme.onInverseSurface, + child: _item( + const CoinLogItem( + time: '时间', + delta: '变化', + reason: '原因', + ), + dividerV, + isHeader: true, + ), + ), + ), + sliverDivider, + SliverList.separated( + itemCount: response!.length, + itemBuilder: (context, index) { + return _item(response[index], dividerV); + }, + separatorBuilder: (context, index) => divider, + ), + sliverDivider, + ], + ); + }, + ) + : HttpError(onReload: _controller.onReload), + Error(:var errMsg) => HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ), + }; + } + + Widget _item(CoinLogItem item, Widget divider, {bool isHeader = false}) { + Widget text(int flex, String text) => Expanded( + flex: flex, + child: Padding( + padding: isHeader + ? const EdgeInsets.symmetric(vertical: 6) + : const EdgeInsets.symmetric(vertical: 8), + child: Center( + child: Text( + text, + textAlign: TextAlign.center, + style: isHeader + ? const TextStyle(fontSize: 13, fontWeight: FontWeight.bold) + : const TextStyle(fontSize: 13), + ), + ), + ), + ); + Widget content = Row( + children: [ + divider, + text(2, item.time), + divider, + text(1, item.delta), + divider, + text(2, item.reason), + divider, + ], + ); + return IntrinsicHeight( + child: isHeader ? content : SelectionArea(child: content), + ); + } +} diff --git a/lib/pages/login_log/controller.dart b/lib/pages/login_log/controller.dart new file mode 100644 index 00000000..72b17a0c --- /dev/null +++ b/lib/pages/login_log/controller.dart @@ -0,0 +1,22 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/user.dart'; +import 'package:PiliPlus/models_new/login_log/data.dart'; +import 'package:PiliPlus/models_new/login_log/list.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; + +class LoginLogController + extends CommonListController { + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + List? getDataList(LoginLogData response) { + return response.list; + } + + @override + Future> customGetData() => UserHttp.loginLog(); +} diff --git a/lib/pages/login_log/view.dart b/lib/pages/login_log/view.dart new file mode 100644 index 00000000..9488a13c --- /dev/null +++ b/lib/pages/login_log/view.dart @@ -0,0 +1,137 @@ +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models_new/login_log/list.dart'; +import 'package:PiliPlus/pages/login_log/controller.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class LoginLogPage extends StatefulWidget { + const LoginLogPage({super.key}); + + @override + State createState() => _LoginLogPageState(); +} + +class _LoginLogPageState extends State { + late final _controller = Get.put(LoginLogController()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('登录记录')), + body: SafeArea( + top: false, + bottom: false, + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 680), + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + left: 10, + right: 10, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _buildBody(_controller.loadingState.value)), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildBody(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => linearLoading, + Success(:var response) => response?.isNotEmpty == true + ? Builder( + builder: (context) { + final them = Theme.of(context); + final outline = them.colorScheme.outline.withValues(alpha: 0.1); + final divider = Divider( + height: 1, + color: outline, + ); + final sliverDivider = SliverToBoxAdapter( + child: divider, + ); + final dividerV = VerticalDivider( + width: 1, + color: outline, + ); + return SliverMainAxisGroup( + slivers: [ + sliverDivider, + SliverToBoxAdapter( + child: ColoredBox( + color: them.colorScheme.onInverseSurface, + child: _item( + LoginLogItem( + timeAt: '时间', + ip: '变化', + geo: '地理位置', + ), + dividerV, + isHeader: true, + ), + ), + ), + sliverDivider, + SliverList.separated( + itemCount: response!.length, + itemBuilder: (context, index) { + return _item(response[index], dividerV); + }, + separatorBuilder: (context, index) => divider, + ), + sliverDivider, + ], + ); + }, + ) + : HttpError(onReload: _controller.onReload), + Error(:var errMsg) => HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ), + }; + } + + Widget _item(LoginLogItem item, Widget divider, {bool isHeader = false}) { + Widget text(int flex, String text) => Expanded( + flex: flex, + child: Padding( + padding: isHeader + ? const EdgeInsets.symmetric(vertical: 6) + : const EdgeInsets.symmetric(vertical: 8), + child: Center( + child: Text( + text, + textAlign: TextAlign.center, + style: isHeader + ? const TextStyle(fontSize: 13, fontWeight: FontWeight.bold) + : const TextStyle(fontSize: 13), + ), + ), + ), + ); + Widget content = Row( + children: [ + divider, + text(3, item.timeAt), + divider, + text(2, item.ip), + divider, + text(3, item.geo), + divider, + ], + ); + return IntrinsicHeight( + child: isHeader ? content : SelectionArea(child: content), + ); + } +} diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 2040e85a..61dd4929 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -4,9 +4,11 @@ import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/space/space/data.dart'; +import 'package:PiliPlus/pages/coin_log/view.dart'; +import 'package:PiliPlus/pages/exp_log/view.dart'; +import 'package:PiliPlus/pages/login_log/view.dart'; import 'package:PiliPlus/pages/member/controller.dart'; import 'package:PiliPlus/pages/member/widget/user_info_card.dart'; -import 'package:PiliPlus/pages/member_coin_log/view.dart'; import 'package:PiliPlus/pages/member_contribute/view.dart'; import 'package:PiliPlus/pages/member_dynamics/view.dart'; import 'package:PiliPlus/pages/member_favorite/view.dart'; @@ -158,16 +160,38 @@ class _MemberPageState extends State { ), ), PopupMenuItem( - onTap: () => Get.to(const MemberCoinLogPage()), + onTap: () => Get.to(const LoginLogPage()), child: const Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(FontAwesomeIcons.b, size: 18), + Icon(Icons.login, size: 18), + SizedBox(width: 10), + Text('登录记录'), + ], + ), + ), + PopupMenuItem( + onTap: () => Get.to(const CoinLogPage()), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(FontAwesomeIcons.b, size: 16), SizedBox(width: 10), Text('硬币记录'), ], ), ), + PopupMenuItem( + onTap: () => Get.to(const ExpLogPage()), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.linear_scale, size: 18), + SizedBox(width: 10), + Text('经验记录'), + ], + ), + ), PopupMenuItem( onTap: () => Get.toNamed('/spaceSetting'), child: const Row( diff --git a/lib/pages/member_profile/view.dart b/lib/pages/member_profile/view.dart index b6482b6a..46f06dc4 100644 --- a/lib/pages/member_profile/view.dart +++ b/lib/pages/member_profile/view.dart @@ -177,7 +177,7 @@ class _EditProfilePageState extends State { onTap: () => showDatePicker( context: context, initialDate: DateTime.parse(response.birthday!), - firstDate: DateTime(1900, 1, 1), + firstDate: DateTime(0001, 1, 1), lastDate: DateTime.now(), ).then((res) { if (res != null) {