255 lines
7.2 KiB
Dart
255 lines
7.2 KiB
Dart
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,
|
||
// 链接用深绿 accentForeground(lime 只做底色,不做文字色)
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|