feat(frontend): 前端时间显示统一转换为本地时间

- mobile-app: 新增 DateTimeUtils 工具类处理 UTC -> 本地时间转换
- mobile-app: 修改 ledger_detail_page 和 profile_page 使用本地时间
- admin-web: 添加 dayjs 自动转换注释说明
- mobile-upgrade: 优化 toLocaleString 格式化选项

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-23 19:45:51 -08:00
parent 7ae6af7841
commit 19bd804a21
5 changed files with 121 additions and 14 deletions

View File

@ -41,6 +41,7 @@ export function formatPercentage(value: number, decimals = 1): string {
/** /**
* *
* dayjs UTC
*/ */
export function formatDate(date: string | Date, format = 'YYYY-MM-DD'): string { export function formatDate(date: string | Date, format = 'YYYY-MM-DD'): string {
return dayjs(date).format(format); return dayjs(date).format(format);
@ -48,6 +49,7 @@ export function formatDate(date: string | Date, format = 'YYYY-MM-DD'): string {
/** /**
* *
* dayjs UTC
*/ */
export function formatDateTime(date: string | Date, format = 'YYYY-MM-DD HH:mm:ss'): string { export function formatDateTime(date: string | Date, format = 'YYYY-MM-DD HH:mm:ss'): string {
return dayjs(date).format(format); return dayjs(date).format(format);
@ -55,6 +57,7 @@ export function formatDateTime(date: string | Date, format = 'YYYY-MM-DD HH:mm:s
/** /**
* (: 5分钟前, 2) * (: 5分钟前, 2)
* dayjs UTC
*/ */
export function formatRelativeTime(date: string | Date): string { export function formatRelativeTime(date: string | Date): string {
return dayjs(date).fromNow(); return dayjs(date).fromNow();

View File

@ -0,0 +1,96 @@
import 'package:intl/intl.dart';
///
/// UTC
class DateTimeUtils {
DateTimeUtils._();
///
/// UTC
static String formatDateTime(DateTime? dateTime) {
if (dateTime == null) return '-';
final localTime = dateTime.toLocal();
return DateFormat('yyyy-MM-dd HH:mm').format(localTime);
}
///
static String formatDateTimeFull(DateTime? dateTime) {
if (dateTime == null) return '-';
final localTime = dateTime.toLocal();
return DateFormat('yyyy-MM-dd HH:mm:ss').format(localTime);
}
///
static String formatDate(DateTime? dateTime) {
if (dateTime == null) return '-';
final localTime = dateTime.toLocal();
return DateFormat('yyyy-MM-dd').format(localTime);
}
/// /
static String formatShortDate(DateTime? dateTime) {
if (dateTime == null) return '-';
final localTime = dateTime.toLocal();
return DateFormat('MM/dd').format(localTime);
}
///
static String formatShortDateFromString(String? dateStr) {
if (dateStr == null || dateStr.isEmpty) return '-';
try {
final parsed = DateTime.parse(dateStr);
return formatShortDate(parsed);
} catch (e) {
return dateStr;
}
}
///
static String formatTime(DateTime? dateTime) {
if (dateTime == null) return '-';
final localTime = dateTime.toLocal();
return DateFormat('HH:mm').format(localTime);
}
/// 51
static String formatRelativeTime(DateTime? dateTime) {
if (dateTime == null) return '-';
final localTime = dateTime.toLocal();
final now = DateTime.now();
final diff = now.difference(localTime);
if (diff.inSeconds < 60) {
return '刚刚';
} else if (diff.inMinutes < 60) {
return '${diff.inMinutes}分钟前';
} else if (diff.inHours < 24) {
return '${diff.inHours}小时前';
} else if (diff.inDays < 2) {
return '昨天 ${formatTime(dateTime)}';
} else if (diff.inDays < 7) {
return '${diff.inDays}天前';
} else {
return formatDate(dateTime);
}
}
///
static bool isToday(DateTime? dateTime) {
if (dateTime == null) return false;
final localTime = dateTime.toLocal();
final now = DateTime.now();
return localTime.year == now.year &&
localTime.month == now.month &&
localTime.day == now.day;
}
///
static bool isYesterday(DateTime? dateTime) {
if (dateTime == null) return false;
final localTime = dateTime.toLocal();
final yesterday = DateTime.now().subtract(const Duration(days: 1));
return localTime.year == yesterday.year &&
localTime.month == yesterday.month &&
localTime.day == yesterday.day;
}
}

View File

@ -15,6 +15,7 @@ import '../../../../core/storage/storage_keys.dart';
import '../../../../core/services/referral_service.dart'; import '../../../../core/services/referral_service.dart';
import '../../../../core/services/reward_service.dart'; import '../../../../core/services/reward_service.dart';
import '../../../../core/services/notification_service.dart'; import '../../../../core/services/notification_service.dart';
import '../../../../core/utils/date_utils.dart';
import '../../../../routes/route_paths.dart'; import '../../../../routes/route_paths.dart';
import '../../../../routes/app_router.dart'; import '../../../../routes/app_router.dart';
import '../../../auth/presentation/providers/auth_provider.dart'; import '../../../auth/presentation/providers/auth_provider.dart';
@ -2500,9 +2501,10 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
/// ///
Widget _buildSettleableRewardItem(SettleableRewardItem item) { Widget _buildSettleableRewardItem(SettleableRewardItem item) {
// 使 claimedAt使 createdAt // 使 claimedAt使 createdAt- UTC ->
final displayDate = item.claimedAt ?? item.createdAt; final displayDate = item.claimedAt ?? item.createdAt;
final settledDate = '${displayDate.month}/${displayDate.day} ${displayDate.hour.toString().padLeft(2, '0')}:${displayDate.minute.toString().padLeft(2, '0')}'; final localDate = displayDate.toLocal();
final settledDate = '${localDate.month}/${localDate.day} ${localDate.hour.toString().padLeft(2, '0')}:${localDate.minute.toString().padLeft(2, '0')}';
// //
final List<String> amountParts = []; final List<String> amountParts = [];
@ -2585,9 +2587,10 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
/// ///
Widget _buildStackedSettleableRewardCard(SettleableRewardItem item, bool isSelected) { Widget _buildStackedSettleableRewardCard(SettleableRewardItem item, bool isSelected) {
// 使 claimedAt使 createdAt // 使 claimedAt使 createdAt- UTC ->
final displayDate = item.claimedAt ?? item.createdAt; final displayDate = item.claimedAt ?? item.createdAt;
final settledDate = '${displayDate.month}/${displayDate.day} ${displayDate.hour.toString().padLeft(2, '0')}:${displayDate.minute.toString().padLeft(2, '0')}'; final localDate = displayDate.toLocal();
final settledDate = '${localDate.month}/${localDate.day} ${localDate.hour.toString().padLeft(2, '0')}:${localDate.minute.toString().padLeft(2, '0')}';
// //
final List<String> amountParts = []; final List<String> amountParts = [];

View File

@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../../../core/di/injection_container.dart'; import '../../../../core/di/injection_container.dart';
import '../../../../core/services/wallet_service.dart'; import '../../../../core/services/wallet_service.dart';
import '../../../../core/utils/date_utils.dart';
/// - /// -
class LedgerDetailPage extends ConsumerStatefulWidget { class LedgerDetailPage extends ConsumerStatefulWidget {
@ -186,19 +187,14 @@ class _LedgerDetailPageState extends ConsumerState<LedgerDetailPage>
return formatter.format(amount); return formatter.format(amount);
} }
/// /// (UTC -> )
String _formatDate(DateTime date) { String _formatDate(DateTime date) {
return DateFormat('yyyy-MM-dd HH:mm').format(date); return DateTimeUtils.formatDateTime(date);
} }
/// /// (UTC -> )
String _formatShortDate(String date) { String _formatShortDate(String date) {
try { return DateTimeUtils.formatShortDateFromString(date);
final parsed = DateTime.parse(date);
return DateFormat('MM/dd').format(parsed);
} catch (e) {
return date;
}
} }
@override @override

View File

@ -23,9 +23,18 @@ export function VersionCard({ version, onEdit, onDelete, onToggle }: VersionCard
return `${(bytes / (1024 * 1024)).toFixed(1)} MB` return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
} }
// 格式化日期时间 - UTC 自动转换为本地时间
const formatDate = (dateStr: string | null) => { const formatDate = (dateStr: string | null) => {
if (!dateStr) return '-' if (!dateStr) return '-'
return new Date(dateStr).toLocaleString('zh-CN') return new Date(dateStr).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
})
} }
return ( return (