Files
jinzhi/lib/common/widgets/detail_kit.dart
T
2026-06-18 15:02:28 +08:00

255 lines
7.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_remix/flutter_remix.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../theme/app_text.dart';
import '../theme/spacing.dart';
// ─────────────────────────────────────────────────────────────────────────
// 详情/主页通用卡片语言。
// ─────────────────────────────────────────────────────────────────────────
/// 白底描边卡片(demo `.mcard`)。
class DetailCard extends StatelessWidget {
const DetailCard({super.key, required this.child, this.color});
final Widget child;
/// 卡片底色,默认白 `card`。
final Color? color;
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(Spacing.cardPadding),
decoration: BoxDecoration(
color: color ?? cs.card,
border: Border.all(color: cs.border),
borderRadius: BorderRadius.circular(Spacing.radiusCard),
),
child: child,
);
}
}
/// 卡片头部:编号圆角块(或图标)+ 标题(demo `.mh > .mnum + h2`)。
class DetailSectionHeader extends StatelessWidget {
const DetailSectionHeader({
super.key,
this.number,
this.icon,
required this.title,
});
final int? number;
final IconData? icon;
final String title;
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
return Row(
children: [
Container(
width: 26,
height: 26,
alignment: Alignment.center,
decoration: BoxDecoration(
color: cs.accent,
borderRadius: BorderRadius.circular(7),
),
child: number != null
? Text(
'$number',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: cs.accentForeground,
),
)
: Icon(icon, size: 15, color: cs.accentForeground),
),
const SizedBox(width: 10),
Expanded(
child: Text(
title,
style: AppText.listTitle.copyWith(
fontSize: 17,
fontWeight: FontWeight.w700,
color: cs.foreground,
),
),
),
],
);
}
}
/// 徽标(demo `.badge` secondary / outline / 实底)。
class DetailBadge extends StatelessWidget {
const DetailBadge(
this.label, {
super.key,
this.bg,
this.fg,
this.outline = false,
this.icon,
});
final String label;
final Color? bg;
final Color? fg;
final bool outline;
final IconData? icon;
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 3),
decoration: BoxDecoration(
color: outline ? Colors.transparent : (bg ?? cs.secondary),
borderRadius: BorderRadius.circular(Spacing.radiusBadge),
// 实底也带 1px 透明描边,与 outline 徽标等高等宽
border: outline
? Border.all(color: cs.border)
: Border.all(color: Colors.transparent),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(
icon,
size: 10,
color: outline ? cs.mutedForeground : (fg ?? cs.foreground),
),
const SizedBox(width: 3),
],
Text(
label,
style: AppText.badge.copyWith(
color: outline ? cs.mutedForeground : (fg ?? cs.foreground),
),
),
],
),
);
}
}
/// 键值行(demo `.crow > .cl + .cv`)。value 可选链接样式 + 右上箭头,点击回调。
class DetailKeyValueRow extends StatelessWidget {
const DetailKeyValueRow({
super.key,
required this.label,
required this.value,
this.link = false,
this.onTap,
});
final String label;
final String value;
final bool link;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
final valueWidget = Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
value,
textAlign: TextAlign.right,
style: AppText.meta.copyWith(
fontSize: 13.5,
// 链接用深绿 accentForegroundlime 只做底色,不做文字色)
color: link ? cs.accentForeground : cs.foreground,
fontWeight: FontWeight.w500,
),
),
),
if (link) ...[
const SizedBox(width: 3),
Icon(
FlutterRemix.external_link_line,
size: 13,
color: cs.accentForeground,
),
],
],
);
return Container(
padding: const EdgeInsets.symmetric(vertical: 11),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: cs.border)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: AppText.meta.copyWith(
fontSize: 13.5,
color: cs.mutedForeground,
),
),
const SizedBox(width: 14),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: link && onTap != null
? GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: valueWidget,
)
: valueWidget,
),
),
],
),
);
}
}
/// 合规提示条(demo `.disc`:⚠ 图标 + 灰字)。
class DetailDisclaimer extends StatelessWidget {
const DetailDisclaimer({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: cs.secondary,
borderRadius: BorderRadius.circular(Spacing.radiusBase),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
FlutterRemix.error_warning_line,
size: 15,
color: cs.mutedForeground,
),
const SizedBox(width: 8),
Expanded(
child: Text(
text,
style: AppText.meta.copyWith(
fontSize: 12,
color: cs.mutedForeground,
height: 1.5,
),
),
),
],
),
);
}
}