feat: 扫码商品详情页重设计 + 宣传主页 + nginx 路由完善
- public_product_screen: 1:1 复刻设计稿,新增商品介绍、批次防伪(自定义圆形印章)、完整商品参数表、门店营业时间、页脚链接 - app_theme: 完整品牌色系(brand/gray/accent/semantic) - shop model: 新增 business_hours 字段,public 接口同步返回 - nginx: 新增 /product/ → Flutter app 扫码入口,修复 /docs /download html 后缀兼容 - web/: 新增宣传主页、功能页、文档页、扫码 HTML 及设计资源 - app_shell: 界面优化 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,110 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppTheme {
|
||||
static const Color primary = Color(0xFF1565C0);
|
||||
static const Color primaryDark = Color(0xFF0D47A1);
|
||||
static const Color primaryLight = Color(0xFF1976D2);
|
||||
static const Color accent = Color(0xFFFF6F00);
|
||||
static const Color success = Color(0xFF2E7D32);
|
||||
static const Color danger = Color(0xFFC62828);
|
||||
static const Color background = Color(0xFFF5F5F5);
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// LEGACY API — field names preserved, values updated to 岩美 brand.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
static const Color primary = Color(0xFF2563AC);
|
||||
static const Color primaryDark = Color(0xFF154072);
|
||||
static const Color primaryLight = Color(0xFF4F86C6);
|
||||
|
||||
/// Bordeaux accent — wine context cue. Was warm orange #FF6F00.
|
||||
static const Color accent = Color(0xFF8B2331);
|
||||
|
||||
static const Color success = Color(0xFF2E8B57);
|
||||
static const Color danger = Color(0xFFD14343);
|
||||
static const Color background = Color(0xFFF5F7FA);
|
||||
static const Color surface = Color(0xFFFFFFFF);
|
||||
static const Color border = Color(0xFFE0E0E0);
|
||||
static const Color textPrimary = Color(0xFF212121);
|
||||
static const Color textSecondary = Color(0xFF757575);
|
||||
static const Color border = Color(0xFFDCE2EB);
|
||||
static const Color textPrimary = Color(0xFF232934);
|
||||
static const Color textSecondary = Color(0xFF6E7888);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Full brand scale (50..900)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static const Color brand50 = Color(0xFFEEF4FB);
|
||||
static const Color brand100 = Color(0xFFD6E5F5);
|
||||
static const Color brand200 = Color(0xFFADC9EA);
|
||||
static const Color brand300 = Color(0xFF7FA8DA);
|
||||
static const Color brand400 = Color(0xFF4F86C6);
|
||||
static const Color brand500 = Color(0xFF2563AC);
|
||||
static const Color brand600 = Color(0xFF1B4F8E);
|
||||
static const Color brand700 = Color(0xFF154072);
|
||||
static const Color brand800 = Color(0xFF0F3057);
|
||||
static const Color brand900 = Color(0xFF0A1F3B);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Full neutral (gray) scale
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static const Color gray0 = Color(0xFFFFFFFF);
|
||||
static const Color gray25 = Color(0xFFFBFCFD);
|
||||
static const Color gray50 = Color(0xFFF5F7FA);
|
||||
static const Color gray100 = Color(0xFFECEFF4);
|
||||
static const Color gray200 = Color(0xFFDCE2EB);
|
||||
static const Color gray300 = Color(0xFFC2CAD6);
|
||||
static const Color gray400 = Color(0xFF99A3B3);
|
||||
static const Color gray500 = Color(0xFF6E7888);
|
||||
static const Color gray600 = Color(0xFF4F5867);
|
||||
static const Color gray700 = Color(0xFF353C48);
|
||||
static const Color gray800 = Color(0xFF232934);
|
||||
static const Color gray900 = Color(0xFF141821);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Accent (bordeaux) scale
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static const Color accent50 = Color(0xFFFAEEF0);
|
||||
static const Color accent100 = Color(0xFFF1D2D7);
|
||||
static const Color accent500 = Color(0xFF8B2331);
|
||||
static const Color accent700 = Color(0xFF5F1621);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Semantic scale
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static const Color success50 = Color(0xFFE8F5EE);
|
||||
static const Color success500 = Color(0xFF2E8B57);
|
||||
static const Color success700 = Color(0xFF1F6B41);
|
||||
|
||||
static const Color warning50 = Color(0xFFFFF4DB);
|
||||
static const Color warning500 = Color(0xFFE08E00);
|
||||
static const Color warning700 = Color(0xFFA66700);
|
||||
|
||||
static const Color danger50 = Color(0xFFFDECEC);
|
||||
static const Color danger500 = Color(0xFFD14343);
|
||||
static const Color danger700 = Color(0xFF9E2A2A);
|
||||
|
||||
static const Color info50 = Color(0xFFE5F1FB);
|
||||
static const Color info500 = Color(0xFF2F7BD0);
|
||||
static const Color info700 = Color(0xFF1F5C9F);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// State colors
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static const Color rowHover = Color(0xFFEEF4FB);
|
||||
static const Color tableHeader = Color(0xFFF5F7FA);
|
||||
static const Color borderSubtle = Color(0xFFECEFF4);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Radii
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static const double radiusSm = 4.0;
|
||||
static const double radiusMd = 6.0;
|
||||
static const double radiusLg = 10.0;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Spacing (4px base)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static const double space1 = 4.0;
|
||||
static const double space2 = 8.0;
|
||||
static const double space3 = 12.0;
|
||||
static const double space4 = 16.0;
|
||||
static const double space5 = 20.0;
|
||||
static const double space6 = 24.0;
|
||||
static const double space8 = 32.0;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// ThemeData
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
static ThemeData light() {
|
||||
return ThemeData(
|
||||
@@ -21,78 +114,172 @@ class AppTheme {
|
||||
brightness: Brightness.light,
|
||||
).copyWith(
|
||||
primary: primary,
|
||||
surface: surface,
|
||||
onPrimary: Colors.white,
|
||||
secondary: brand400,
|
||||
surface: surface,
|
||||
onSurface: textPrimary,
|
||||
error: danger,
|
||||
outline: border,
|
||||
outlineVariant: borderSubtle,
|
||||
),
|
||||
scaffoldBackgroundColor: background,
|
||||
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: primary,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
toolbarHeight: 56,
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
color: surface,
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
side: const BorderSide(color: border, width: 0.5),
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
letterSpacing: 0.4,
|
||||
),
|
||||
),
|
||||
|
||||
cardTheme: CardThemeData(
|
||||
color: surface,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(radiusLg),
|
||||
side: const BorderSide(color: border, width: 1),
|
||||
),
|
||||
margin: const EdgeInsets.all(0),
|
||||
),
|
||||
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primary,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(0, 36),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
minimumSize: const Size(0, 40),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(radiusMd),
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
elevation: 0,
|
||||
).copyWith(
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.pressed)) return primaryDark;
|
||||
if (states.contains(WidgetState.hovered)) return brand600;
|
||||
return primary;
|
||||
}),
|
||||
overlayColor:
|
||||
WidgetStateProperty.all(Colors.white.withValues(alpha: 0.08)),
|
||||
),
|
||||
),
|
||||
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: primary,
|
||||
minimumSize: const Size(0, 36),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
side: const BorderSide(color: primary),
|
||||
foregroundColor: brand700,
|
||||
minimumSize: const Size(0, 40),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(radiusMd),
|
||||
),
|
||||
side: const BorderSide(color: border, width: 1),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
).copyWith(
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.pressed)) return gray100;
|
||||
if (states.contains(WidgetState.hovered)) return gray50;
|
||||
return surface;
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: primary,
|
||||
minimumSize: const Size(0, 36),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(radiusSm),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderRadius: BorderRadius.circular(radiusMd),
|
||||
borderSide: const BorderSide(color: border),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderRadius: BorderRadius.circular(radiusMd),
|
||||
borderSide: const BorderSide(color: border),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderRadius: BorderRadius.circular(radiusMd),
|
||||
borderSide: const BorderSide(color: primary, width: 1.5),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(radiusMd),
|
||||
borderSide: const BorderSide(color: danger),
|
||||
),
|
||||
hoverColor: gray50,
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: surface,
|
||||
labelStyle: const TextStyle(fontSize: 13, color: textSecondary),
|
||||
hintStyle: TextStyle(fontSize: 13, color: gray400),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(color: border, thickness: 0.5),
|
||||
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: borderSubtle,
|
||||
thickness: 1,
|
||||
space: 1,
|
||||
),
|
||||
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: gray100,
|
||||
labelStyle: const TextStyle(fontSize: 12, color: textPrimary),
|
||||
side: BorderSide.none,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
),
|
||||
|
||||
tooltipTheme: TooltipThemeData(
|
||||
decoration: BoxDecoration(
|
||||
color: gray900,
|
||||
borderRadius: BorderRadius.circular(radiusSm),
|
||||
),
|
||||
textStyle: const TextStyle(color: Colors.white, fontSize: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
),
|
||||
|
||||
textTheme: const TextTheme(
|
||||
bodyLarge: TextStyle(fontSize: 14, color: textPrimary),
|
||||
bodyMedium: TextStyle(fontSize: 14, color: textPrimary),
|
||||
bodySmall: TextStyle(fontSize: 12, color: textSecondary),
|
||||
titleMedium: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textPrimary),
|
||||
labelMedium: TextStyle(fontSize: 12, color: textSecondary),
|
||||
displayLarge: TextStyle(fontSize: 36, fontWeight: FontWeight.w600, color: brand900, height: 1.2, letterSpacing: 0.4),
|
||||
displayMedium: TextStyle(fontSize: 28, fontWeight: FontWeight.w600, color: brand900, height: 1.2, letterSpacing: 0.4),
|
||||
headlineSmall: TextStyle(fontSize: 22, fontWeight: FontWeight.w600, color: gray900, height: 1.3),
|
||||
titleLarge: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: gray900, height: 1.35),
|
||||
titleMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: textPrimary, height: 1.4),
|
||||
titleSmall: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: textPrimary, height: 1.4),
|
||||
bodyLarge: TextStyle(fontSize: 14, color: textPrimary, height: 1.55),
|
||||
bodyMedium: TextStyle(fontSize: 14, color: textPrimary, height: 1.55),
|
||||
bodySmall: TextStyle(fontSize: 12, color: textSecondary, height: 1.5),
|
||||
labelLarge: TextStyle(fontSize: 13, color: textPrimary, fontWeight: FontWeight.w500),
|
||||
labelMedium: TextStyle(fontSize: 12, color: textSecondary, letterSpacing: 0.4),
|
||||
labelSmall: TextStyle(fontSize: 11, color: textSecondary, letterSpacing: 0.4),
|
||||
),
|
||||
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: {
|
||||
TargetPlatform.windows: FadeUpwardsPageTransitionsBuilder(),
|
||||
TargetPlatform.macOS: FadeUpwardsPageTransitionsBuilder(),
|
||||
TargetPlatform.linux: FadeUpwardsPageTransitionsBuilder(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user