import 'package:flutter/material.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import '../../core/theme/app_colors.dart'; /// Skeleton 占位卡片,匹配 [ListCard] 布局: /// ┌────────────────────────┐ /// │ cardNo amount │ ← Row: 单号(14/600) + 金额(16/700) /// │ │ /// │ description │ ← 描述(14) /// │ │ /// │ date statusTag │ ← Row: 日期(12) + 状态标签 /// └────────────────────────┘ class SkeletonListCard extends StatelessWidget { const SkeletonListCard({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: TDSkeleton.fromRowCol( animation: TDSkeletonAnimation.flashed, rowCol: TDSkeletonRowCol( objects: const [ // R1: cardNo + spacer + amount [ TDSkeletonRowColObj.text(width: 120, height: 14), TDSkeletonRowColObj.spacer(), TDSkeletonRowColObj.text(width: 80, height: 16), ], // R2: description [ TDSkeletonRowColObj.text(height: 14), ], // R3: date + spacer + statusTag [ TDSkeletonRowColObj.text(width: 100, height: 12), TDSkeletonRowColObj.spacer(), TDSkeletonRowColObj.rect(width: 48, height: 20), ], ], style: TDSkeletonRowColStyle( rowSpacing: (_) => 8, ), ), ), ); } } /// Skeleton 占位卡片,匹配车辆列表卡片布局: /// ┌────────────────────────┐ /// │ 车牌号 状态徽章 │ ← Row: 车牌(16/700) + 徽章(12) /// │ │ /// │ 申请单号 用途标签 │ ← Row: 单号(12) + 标签(10) /// │ │ /// │ 路线 ... 时间 │ ← Row: 路线(13/ellipsis) + 时间(12) /// └────────────────────────┘ class SkeletonVehicleCard extends StatelessWidget { const SkeletonVehicleCard({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: TDSkeleton.fromRowCol( animation: TDSkeletonAnimation.flashed, rowCol: TDSkeletonRowCol( objects: const [ // R1: licensePlate + spacer + status badge [ TDSkeletonRowColObj.text(width: 100, height: 16), TDSkeletonRowColObj.spacer(), TDSkeletonRowColObj.rect(width: 48, height: 20), ], // R2: applicationNo + spacer + purpose tag [ TDSkeletonRowColObj.text(width: 140, height: 12), TDSkeletonRowColObj.spacer(), TDSkeletonRowColObj.rect(width: 40, height: 16), ], // R3: route (flex) + spacer + date [ TDSkeletonRowColObj.text(flex: 2, height: 13), TDSkeletonRowColObj.spacer(flex: 1), TDSkeletonRowColObj.text(width: 120, height: 12), ], ], style: TDSkeletonRowColStyle( rowSpacing: (_) => 6, ), ), ), ); } } /// 外勤日志 Skeleton 占位卡片 /// /// 匹配外勤日志卡片布局:visitNo → customerName+状态 → address → summary+date class SkeletonOutingLogCard extends StatelessWidget { const SkeletonOutingLogCard({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: TDSkeleton.fromRowCol( animation: TDSkeletonAnimation.flashed, rowCol: TDSkeletonRowCol( objects: const [ // R1: visitNo [TDSkeletonRowColObj.text(width: 100, height: 11)], // R2: customerName + spacer + statusTag [ TDSkeletonRowColObj.text(flex: 3, height: 15), TDSkeletonRowColObj.spacer(flex: 1), TDSkeletonRowColObj.rect(width: 48, height: 20), ], // R3: checkInAddress [TDSkeletonRowColObj.text(height: 12)], // R4: summary + spacer + date [ TDSkeletonRowColObj.text(flex: 2, height: 12), TDSkeletonRowColObj.spacer(flex: 1), TDSkeletonRowColObj.text(width: 80, height: 11), ], ], style: TDSkeletonRowColStyle(rowSpacing: (_) => 4), ), ), ); } } /// 公告 Skeleton 占位卡片 /// /// 匹配公告卡片布局:title 行 + typeTag/publisher/date 行 class SkeletonAnnouncementCard extends StatelessWidget { const SkeletonAnnouncementCard({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: TDSkeleton.fromRowCol( animation: TDSkeletonAnimation.flashed, rowCol: TDSkeletonRowCol( objects: const [ // R1: title (full width) [TDSkeletonRowColObj.text(height: 15)], // R2: typeTag + spacer + date [ TDSkeletonRowColObj.rect(width: 56, height: 20), TDSkeletonRowColObj.spacer(width: 8), TDSkeletonRowColObj.text(width: 60, height: 12), TDSkeletonRowColObj.spacer(), TDSkeletonRowColObj.text(width: 120, height: 12), ], ], style: TDSkeletonRowColStyle(rowSpacing: (_) => 8), ), ), ); } } /// 列表加载态:骨架卡片占位 /// /// [cardCount] 骨架卡片数量,默认 5 /// [cardBuilder] 骨架卡片构建器,默认 [SkeletonListCard] class SkeletonLoadingList extends StatelessWidget { final int cardCount; final Widget Function() cardBuilder; const SkeletonLoadingList({ super.key, this.cardCount = 5, this.cardBuilder = _defaultBuilder, }); static Widget _defaultBuilder() => const SkeletonListCard(); @override Widget build(BuildContext context) { return ListView( padding: const EdgeInsets.fromLTRB(16, 16, 16, 24), physics: const NeverScrollableScrollPhysics(), children: List.generate( cardCount, (_) => Padding( padding: const EdgeInsets.only(bottom: 16), child: cardBuilder(), ), ), ); } }