| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- import 'package:flutter/material.dart';
- import 'package:tdesign_flutter/tdesign_flutter.dart';
- import '../../core/theme/app_colors_extension.dart';
- /// Skeleton 占位卡片,匹配 [ListCard] 布局(基于 TDSkeleton.fromRowCol):
- ///
- /// 真实 ListCard 结构:
- /// ┌────────────────────────────┐
- /// │ cardNo ← spaceBetween → amount │ R1: Flexible(14/600) + Text(16/700)
- /// │ 8px │
- /// │ description │ R2: Text(14)
- /// │ 8px │
- /// │ date ← spaceBetween → tag │ R3: Flexible(12) + statusTag
- /// └────────────────────────────┘
- class SkeletonListCard extends StatelessWidget {
- const SkeletonListCard({super.key});
- @override
- Widget build(BuildContext context) {
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- return Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: colors.bgCard,
- borderRadius: BorderRadius.circular(8),
- ),
- child: TDSkeleton.fromRowCol(
- animation: TDSkeletonAnimation.flashed,
- rowCol: TDSkeletonRowCol(
- objects: const [
- // R1: cardNo + amount(spacer 撑开两端)
- [
- TDSkeletonRowColObj.text(width: 120, height: 17, flex: 0),
- TDSkeletonRowColObj.spacer(flex: 1),
- TDSkeletonRowColObj.text(width: 80, height: 20, flex: 0),
- ],
- // R2: description(全宽)
- [
- TDSkeletonRowColObj.text(height: 17),
- ],
- // R3: date + statusTag(spacer 撑开两端)
- [
- TDSkeletonRowColObj.text(width: 100, height: 14, flex: 0),
- TDSkeletonRowColObj.spacer(flex: 1),
- TDSkeletonRowColObj.rect(width: 48, height: 20, flex: 0),
- ],
- ],
- style: TDSkeletonRowColStyle(
- rowSpacing: (_) => 8,
- ),
- ),
- ),
- );
- }
- }
- /// Skeleton 占位卡片,匹配车辆列表卡片布局(基于 TDSkeleton.fromRowCol):
- ///
- /// 真实卡片结构:
- /// ┌────────────────────────────┐
- /// │ 车牌号 ← spaceBetween → 徽章│ R1: Text(16/700) + statusTag
- /// │ 6px │
- /// │ 申请单号 ← spaceBetween → 标签│ R2: Text(12) + purposeTag
- /// │ 6px │
- /// │ 路线(ellipsis) ← → 时间 │ R3: Flexible(13) + Text(12)
- /// └────────────────────────────┘
- class SkeletonVehicleCard extends StatelessWidget {
- const SkeletonVehicleCard({super.key});
- @override
- Widget build(BuildContext context) {
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- return Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: colors.bgCard,
- borderRadius: BorderRadius.circular(8),
- ),
- child: TDSkeleton.fromRowCol(
- animation: TDSkeletonAnimation.flashed,
- rowCol: TDSkeletonRowCol(
- objects: const [
- // R1: licensePlate + status badge
- [
- TDSkeletonRowColObj.text(width: 100, height: 20, flex: 0),
- TDSkeletonRowColObj.spacer(flex: 1),
- TDSkeletonRowColObj.rect(width: 48, height: 20, flex: 0),
- ],
- // R2: applicationNo + purpose tag
- [
- TDSkeletonRowColObj.text(width: 140, height: 14, flex: 0),
- TDSkeletonRowColObj.spacer(flex: 1),
- TDSkeletonRowColObj.rect(width: 40, height: 16, flex: 0),
- ],
- // R3: route + date
- [
- TDSkeletonRowColObj.text(height: 15, flex: 2),
- TDSkeletonRowColObj.spacer(flex: 1),
- TDSkeletonRowColObj.text(width: 120, height: 14, flex: 0),
- ],
- ],
- style: TDSkeletonRowColStyle(
- rowSpacing: (_) => 6,
- ),
- ),
- ),
- );
- }
- }
- /// Skeleton 占位卡片,匹配外勤日志卡片布局(基于 TDSkeleton.fromRowCol):
- ///
- /// 真实卡片结构:
- /// ┌────────────────────────────┐
- /// │ visitNo │ R1: Text(11)
- /// │ 4px │
- /// │ customerName ← → statusTag │ R2: Flexible(15/700) + statusTag
- /// │ 4px │
- /// │ checkInAddress │ R3: Text(12)
- /// │ 4px │
- /// │ summary(ellipsis) ← → date │ R4: Flexible(12) + Text(11)
- /// └────────────────────────────┘
- class SkeletonOutingLogCard extends StatelessWidget {
- const SkeletonOutingLogCard({super.key});
- @override
- Widget build(BuildContext context) {
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- return Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: colors.bgCard,
- borderRadius: BorderRadius.circular(8),
- ),
- child: TDSkeleton.fromRowCol(
- animation: TDSkeletonAnimation.flashed,
- rowCol: TDSkeletonRowCol(
- objects: const [
- // R1: visitNo
- [TDSkeletonRowColObj.text(width: 100, height: 13)],
- // R2: customerName + statusTag
- [
- TDSkeletonRowColObj.text(height: 18, flex: 3),
- TDSkeletonRowColObj.spacer(flex: 1),
- TDSkeletonRowColObj.rect(width: 48, height: 20, flex: 0),
- ],
- // R3: checkInAddress
- [TDSkeletonRowColObj.text(height: 14)],
- // R4: summary + date
- [
- TDSkeletonRowColObj.text(height: 14, flex: 2),
- TDSkeletonRowColObj.spacer(flex: 1),
- TDSkeletonRowColObj.text(width: 80, height: 13, flex: 0),
- ],
- ],
- style: TDSkeletonRowColStyle(rowSpacing: (_) => 4),
- ),
- ),
- );
- }
- }
- /// Skeleton 占位卡片,匹配公告卡片布局(基于 TDSkeleton.fromRowCol):
- ///
- /// 真实卡片结构:
- /// ┌────────────────────────────┐
- /// │ title │ R1: Text(15/600)
- /// │ 8px │
- /// │ typeTag publisher ← → date│ R2: tag(56) + SizedBox(8) + Text(12) + spaceBetween + Text(12)
- /// └────────────────────────────┘
- class SkeletonAnnouncementCard extends StatelessWidget {
- const SkeletonAnnouncementCard({super.key});
- @override
- Widget build(BuildContext context) {
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- return Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: colors.bgCard,
- borderRadius: BorderRadius.circular(8),
- ),
- child: TDSkeleton.fromRowCol(
- animation: TDSkeletonAnimation.flashed,
- rowCol: TDSkeletonRowCol(
- objects: const [
- // R1: title (full width)
- [TDSkeletonRowColObj.text(height: 18)],
- // R2: typeTag + publisher + spacer(flex:1) + date
- [
- TDSkeletonRowColObj.rect(width: 56, height: 20, flex: 0),
- TDSkeletonRowColObj.spacer(width: 8),
- TDSkeletonRowColObj.text(width: 60, height: 14, flex: 0),
- TDSkeletonRowColObj.spacer(flex: 1),
- TDSkeletonRowColObj.text(width: 120, height: 14, flex: 0),
- ],
- ],
- 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(),
- ),
- ),
- );
- }
- }
|