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:
parent
7ae6af7841
commit
19bd804a21
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 格式化相对时间(如:刚刚、5分钟前、1小时前、昨天)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 = [];
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue