feat:参照研听的基础工程

This commit is contained in:
jingyun
2026-06-18 15:02:28 +08:00
commit 364fc837b3
367 changed files with 16495 additions and 0 deletions
+254
View File
@@ -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,
// 链接用深绿 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,
),
),
),
],
),
);
}
}