style(mine): 重构紧凑 VIP 会员入口

This commit is contained in:
Zhou Weidong committed 2026-11-13 17:41:47 +08:00
1 parent 9ef78ad670
commit 6ef5429a52
7 files changed
+155 -178

No files matched your search

+16 -13
View File
@@ -57,20 +57,23 @@ abstract final class AppColors {
static const vipGoldStart = Color(0xFFF5D9A0);
static const vipGoldEnd = Color(0xFFE8B65A);
/// 「我的」页 VIP 卡:品牌酒红、暖黑与香槟金共同形成会员层级。
static const vipCardStart = Color(0xFF351318);
static const vipCardMiddle = Color(0xFF1B1112);
static const vipCardEnd = Color(0xFF0D0B0C);
static const vipCardBorder = Color(0x66F5D9A0);
static const vipCardRedGlow = Color(0x52F04E4E);
static const vipCardGoldGlow = Color(0x38F3B34C);
static const vipCardText = Color(0xFFFFF5E6);
static const vipCardMuted = Color(0xB3F5D9A0);
static const vipBadgeSurface = Color(0x24FFFFFF);
/// 「我的」页 VIP 卡:品牌红、暖黑与中性灰形成统一层级。
static const vipCardStart = Color(0xFF21191C);
static const vipCardMiddle = Color(0xFF181719);
static const vipCardEnd = Color(0xFF141416);
static const vipCardBorder = Color(0x2EFFFFFF);
static const vipCardRedGlow = Color(0x2EF04E4E);
static const vipCardGoldGlow = Color(0x16F5D9A0);
static const vipArtGlow = Color(0x55F04E4E);
static const vipArtStart = Color(0xFFE45757);
static const vipArtEnd = Color(0xFF6E1F2D);
static const vipCardText = Color(0xFFF5F5F7);
static const vipCardMuted = Color(0xB3B0B0B0);
static const vipBadgeSurface = Color(0x24F04E4E);
static const vipBenefitSurface = Color(0x12FFFFFF);
static const vipBenefitBorder = Color(0x33F5D9A0);
static const vipButtonStart = brandRed;
static const vipButtonEnd = Color(0xFFFF745E);
static const vipBenefitBorder = Color(0x14FFFFFF);
static const vipButtonStart = Color(0xFFF04E4E);
static const vipButtonEnd = Color(0xFFC93648);
static const bronze = Color(0xFFCD7F32);
static const bronzeDeep = Color(0xFFA9713A);
static const rankSilver = Color(0xFFC0C0C0);
+14 -10
View File
@@ -273,20 +273,24 @@ abstract final class AppDimens {
static const double mineSectionLinkFont = 12;
static const double mineSectionGap = 12;
/// 「我的」页会员卡。高度由内容撑开,上下内边距恒为 [mineVipPadding]。
/// 「我的」页紧凑会员横幅尺寸。
static const double mineVipRadius = 18;
static const double mineVipPadding = 16;
static const double mineVipTitleFont = 22;
static const double mineVipSubtitleFont = 12;
static const double mineVipPadding = 10;
static const double mineVipTitleFont = 21;
static const double mineVipSubtitleFont = 11;
static const double mineVipEmblem = 48;
static const double mineVipEmblemIcon = 28;
static const double mineVipActionWidth = 100;
static const double mineVipArtWidth = 84;
static const double mineVipArtHeight = 84;
static const double mineVipArtGlow = 56;
static const double mineVipArtSurface = 44;
static const double mineVipArtIcon = 22;
static const double mineVipActionWidth = 86;
static const double mineVipBenefitFont = 10.5;
static const double mineVipBenefitBox = 34;
static const double mineVipBenefitBoxRadius = 11;
static const double mineVipBenefitGlyph = 19;
static const double mineVipBenefitRowGap = 6;
static const double mineVipButtonHeight = tapTarget;
static const double mineVipBenefitSurface = 28;
static const double mineVipBenefitGlyph = 16;
static const double mineVipBenefitRowGap = 2;
static const double mineVipButtonHeight = 26;
static const double mineContinueCardWidth = 96;
static const double mineContinueRowHeight = 172;
static const double mineContinueTitleFont = 13;
+83 -144
View File
@@ -8,10 +8,11 @@ import '../../../core/widgets/app_press_scale.dart';
import '../../../l10n/app_localizations.dart';
const double _vipMaxTextScale = 1.15;
const double _vipGradientMiddleStop = 0.48;
const double _vipGradientMiddleStop = 0.62;
const double _vipGlowRadius = 0.9;
const double _vipButtonLetterSpacing = 0.2;
/// 「我的」页高级 VIP 卡:品牌信息、会员徽章、开通按钮与横排权益。
/// 「我的」页 VIP 会员卡:主视觉、权益横排与开通入口。
class MineVipBanner extends StatelessWidget {
const MineVipBanner({super.key, required this.onOpen});
@@ -61,13 +62,7 @@ class MineVipBanner extends StatelessWidget {
stops: [0, _vipGradientMiddleStop, 1],
),
borderRadius: BorderRadius.circular(AppDimens.mineVipRadius),
border: Border.all(color: AppColors.vipCardBorder),
boxShadow: const [
BoxShadow(
color: AppColors.vipCardRedGlow,
blurRadius: AppDimens.spaceXl,
offset: Offset(0, AppDimens.spaceSm),
),
BoxShadow(
color: AppColors.shadowSoft,
blurRadius: AppDimens.spaceLg,
@@ -90,16 +85,16 @@ class _VipContent extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: _VipCopy(l10n: l10n)),
Expanded(
child: _VipCopy(l10n: l10n, onOpen: onOpen),
),
const SizedBox(width: AppDimens.spaceSm),
_VipAction(l10n: l10n, onOpen: onOpen),
const _VipArt(),
],
),
const SizedBox(height: AppDimens.spaceSm),
const Divider(height: 1, color: AppColors.vipBenefitBorder),
const SizedBox(height: AppDimens.spaceSm),
const SizedBox(height: AppDimens.spaceXs),
_VipBenefits(l10n: l10n),
],
);
@@ -107,9 +102,10 @@ class _VipContent extends StatelessWidget {
}
class _VipCopy extends StatelessWidget {
const _VipCopy({required this.l10n});
const _VipCopy({required this.l10n, required this.onOpen});
final AppLocalizations l10n;
final VoidCallback onOpen;
@override
Widget build(BuildContext context) {
@@ -117,8 +113,6 @@ class _VipCopy extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_VipPill(label: l10n.vipMembership),
const SizedBox(height: AppDimens.spaceSm),
Text(
l10n.vipBrandName,
maxLines: 1,
@@ -126,14 +120,14 @@ class _VipCopy extends StatelessWidget {
style: const TextStyle(
color: AppColors.vipCardText,
fontSize: AppDimens.mineVipTitleFont,
fontWeight: FontWeight.w700,
letterSpacing: -0.4,
fontWeight: FontWeight.w800,
letterSpacing: 1.0,
),
),
const SizedBox(height: AppDimens.spaceXs),
Text(
l10n.mineVipSubtitle,
maxLines: 2,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipCardMuted,
@@ -141,90 +135,28 @@ class _VipCopy extends StatelessWidget {
height: 1.3,
),
),
const SizedBox(height: AppDimens.spaceXs),
_VipOpenButton(label: l10n.vipOpenNow, onOpen: onOpen),
],
);
}
}
class _VipPill extends StatelessWidget {
const _VipPill({required this.label});
final String label;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.spaceSm,
vertical: AppDimens.spaceXs,
),
decoration: BoxDecoration(
color: AppColors.vipBadgeSurface,
border: Border.all(color: AppColors.vipBenefitBorder),
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipGoldStart,
fontSize: AppDimens.chipFont,
fontWeight: FontWeight.w700,
letterSpacing: 1.1,
),
),
);
}
}
class _VipAction extends StatelessWidget {
const _VipAction({required this.l10n, required this.onOpen});
final AppLocalizations l10n;
final VoidCallback onOpen;
class _VipArt extends StatelessWidget {
const _VipArt();
@override
Widget build(BuildContext context) {
return SizedBox(
width: AppDimens.mineVipActionWidth,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const _VipEmblem(),
const SizedBox(height: AppDimens.spaceXs),
_VipOpenButton(label: l10n.vipOpenNow, onOpen: onOpen),
],
),
);
}
}
class _VipEmblem extends StatelessWidget {
const _VipEmblem();
@override
Widget build(BuildContext context) {
return Container(
key: const Key('mine-vip-emblem'),
width: AppDimens.mineVipEmblem,
height: AppDimens.mineVipEmblem,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.vipGoldStart, AppColors.vipGoldEnd],
),
border: Border.all(color: AppColors.vipCardText),
boxShadow: const [
BoxShadow(color: AppColors.vipCardGoldGlow, blurRadius: 18),
],
),
child: const Icon(
Icons.workspace_premium_rounded,
color: AppColors.vipCardStart,
size: AppDimens.mineVipEmblemIcon,
key: const Key('mine-vip-art'),
width: AppDimens.mineVipArtWidth,
height: AppDimens.mineVipArtHeight,
child: Image.asset(
'assets/icon/vip_membership_badge.png',
key: const Key('mine-vip-emblem'),
width: AppDimens.mineVipArtWidth,
height: AppDimens.mineVipArtHeight,
fit: BoxFit.contain,
),
);
}
@@ -242,34 +174,32 @@ class _VipOpenButton extends StatelessWidget {
key: const Key('mine-vip-open'),
width: AppDimens.mineVipActionWidth,
height: AppDimens.mineVipButtonHeight,
child: DecoratedBox(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [AppColors.vipButtonStart, AppColors.vipButtonEnd],
),
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
boxShadow: const [
BoxShadow(color: AppColors.vipCardRedGlow, blurRadius: 14),
],
),
child: Material(
color: AppColors.transparent,
child: InkWell(
onTap: onOpen,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
child: Center(
child: InkWell(
onTap: onOpen,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
color: AppColors.vipGoldStart,
fontSize: AppDimens.body,
fontWeight: FontWeight.w700,
letterSpacing: _vipButtonLetterSpacing,
),
),
),
),
const SizedBox(width: AppDimens.spaceXs),
const Icon(
Icons.arrow_forward_rounded,
color: AppColors.vipGoldStart,
size: AppDimens.mineVipBenefitGlyph,
),
],
),
),
);
@@ -289,11 +219,20 @@ class _VipBenefits extends StatelessWidget {
(Icons.hd_outlined, l10n.vipBenefitHd),
(Icons.download_for_offline_outlined, l10n.vipBenefitFastCache),
];
return Row(
return Column(
key: const Key('mine-vip-benefits'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (var index = 0; index < items.length; index++)
_row(items, 0),
const SizedBox(height: AppDimens.spaceXs),
_row(items, 2),
],
);
}
Widget _row(List<(IconData, String)> items, int start) {
return Row(
children: [
for (var index = start; index < start + 2; index++)
Expanded(
child: _VipBenefit(
itemKey: Key('mine-vip-benefit-$index'),
@@ -319,44 +258,44 @@ class _VipBenefit extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
return Row(
key: itemKey,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: AppDimens.mineVipBenefitBox,
height: AppDimens.mineVipBenefitBox,
decoration: BoxDecoration(
color: AppColors.vipBenefitSurface,
border: Border.all(color: AppColors.vipBenefitBorder),
borderRadius: BorderRadius.circular(
AppDimens.mineVipBenefitBoxRadius,
_VipBenefitGlyph(icon: icon),
const SizedBox(width: AppDimens.spaceXs),
Expanded(
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipCardMuted,
fontSize: AppDimens.mineVipBenefitFont,
fontWeight: FontWeight.w500,
height: 1.15,
),
),
child: Icon(
icon,
color: AppColors.vipGoldStart,
size: AppDimens.mineVipBenefitGlyph,
),
),
const SizedBox(height: AppDimens.mineVipBenefitRowGap),
Text(
label,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.vipCardMuted,
fontSize: AppDimens.mineVipBenefitFont,
fontWeight: FontWeight.w500,
height: 1.15,
),
),
],
);
}
}
class _VipBenefitGlyph extends StatelessWidget {
const _VipBenefitGlyph({required this.icon});
final IconData icon;
@override
Widget build(BuildContext context) {
return Icon(
icon,
color: AppColors.vipGoldStart,
size: AppDimens.mineVipBenefitGlyph,
);
}
}
class _VipAmbientGlow extends StatelessWidget {
const _VipAmbientGlow();