feat:参照研听的基础工程
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user