diff --git a/components/onboarding/screens/B1Wechat.tsx b/components/onboarding/screens/B1Wechat.tsx index 18d529b..1187d68 100644 --- a/components/onboarding/screens/B1Wechat.tsx +++ b/components/onboarding/screens/B1Wechat.tsx @@ -1,5 +1,5 @@ import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { Illustration } from '../ui/Illustration'; @@ -31,8 +31,8 @@ export function B1Wechat() { } const styles = StyleSheet.create({ - center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 18, paddingBottom: 40 }, - preview: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: colors.white, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: colors.border }, + center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: spacing.block, paddingBottom: 40 }, + preview: { flexDirection: 'row', alignItems: 'center', gap: spacing.block, backgroundColor: colors.white, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: colors.border }, previewText: { gap: 4 }, nick: { fontSize: 15, fontWeight: '700', color: colors.textPrimary }, tip: { fontSize: 12, color: colors.textHint }, diff --git a/components/onboarding/screens/B2Agreement.tsx b/components/onboarding/screens/B2Agreement.tsx index 8b30072..daffee6 100644 --- a/components/onboarding/screens/B2Agreement.tsx +++ b/components/onboarding/screens/B2Agreement.tsx @@ -1,5 +1,5 @@ import { ScrollView, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; @@ -33,6 +33,6 @@ export function B2Agreement() { const styles = StyleSheet.create({ body: { flex: 1 }, - h: { fontSize: 18, fontWeight: '800', color: colors.textPrimary, marginBottom: 14 }, - p: { fontSize: 14, color: colors.textSecondary, lineHeight: 24, marginBottom: 14 }, + h: { fontSize: 18, fontWeight: '800', color: colors.textPrimary, marginBottom: spacing.block }, + p: { fontSize: 14, color: colors.textSecondary, lineHeight: 24, marginBottom: spacing.block }, }); diff --git a/components/onboarding/screens/B3Recruiter.tsx b/components/onboarding/screens/B3Recruiter.tsx index 4c8e392..ffd11c8 100644 --- a/components/onboarding/screens/B3Recruiter.tsx +++ b/components/onboarding/screens/B3Recruiter.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Image, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useEnterpriseCertification, useImagePick, @@ -111,7 +111,7 @@ export function B3Recruiter() { } const styles = StyleSheet.create({ - tip: { fontSize: 13, color: colors.textHint, marginBottom: 18 }, + tip: { fontSize: 13, color: colors.textHint, marginBottom: spacing.block }, label: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginTop: 16, marginBottom: 8 }, input: { height: 50, @@ -127,7 +127,7 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', gap: 8, - paddingVertical: 18, + paddingVertical: spacing.block, borderRadius: 12, borderWidth: 1, borderStyle: 'dashed', @@ -137,6 +137,6 @@ const styles = StyleSheet.create({ dim: { opacity: 0.5 }, licensePreview: { width: 160, height: 120, borderRadius: 12 }, uploadText: { fontSize: 13, color: colors.textHint }, - error: { fontSize: 12, color: colors.dangerText, marginTop: 10 }, + error: { fontSize: 12, color: colors.dangerText, marginTop: spacing.sm }, footer: { marginTop: 28 }, }); diff --git a/components/onboarding/screens/P10FirstWork.tsx b/components/onboarding/screens/P10FirstWork.tsx index 4e79faa..a93fc4d 100644 --- a/components/onboarding/screens/P10FirstWork.tsx +++ b/components/onboarding/screens/P10FirstWork.tsx @@ -1,6 +1,6 @@ import { Ionicons } from '@expo/vector-icons'; import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { YearMonthWheel } from '../ui/WheelPicker'; @@ -42,8 +42,8 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', alignSelf: 'center', - marginBottom: 12, + marginBottom: spacing.block, }, - tip: { fontSize: 13, color: colors.textHint, textAlign: 'center', marginBottom: 18 }, + tip: { fontSize: 13, color: colors.textHint, textAlign: 'center', marginBottom: spacing.block }, footer: { marginTop: 28 }, }); diff --git a/components/onboarding/screens/P11CurrentPosition.tsx b/components/onboarding/screens/P11CurrentPosition.tsx index e023a40..a32ab26 100644 --- a/components/onboarding/screens/P11CurrentPosition.tsx +++ b/components/onboarding/screens/P11CurrentPosition.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { SearchInput } from '../ui/SearchInput'; @@ -85,14 +85,14 @@ export function P11CurrentPosition() { } const styles = StyleSheet.create({ - body: { flexDirection: 'row', gap: 14, marginTop: 8 }, + body: { flexDirection: 'row', gap: spacing.block, marginTop: 8 }, cats: { width: 92 }, - catItem: { paddingVertical: 12, borderRadius: 10 }, + catItem: { paddingVertical: spacing.block, borderRadius: 10 }, catItemActive: { backgroundColor: colors.primaryLight }, catText: { fontSize: 13, color: colors.textSecondary, textAlign: 'center' }, catTextActive: { color: colors.primaryText, fontWeight: '700' }, col: { flex: 1 }, - colTitle: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginBottom: 12 }, + colTitle: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginBottom: spacing.block }, empty: { fontSize: 13, color: colors.textHint, lineHeight: 20 }, blockTitle: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginVertical: 16 }, }); diff --git a/components/onboarding/screens/P13JoinDate.tsx b/components/onboarding/screens/P13JoinDate.tsx index 58a8ae5..e9eb4ca 100644 --- a/components/onboarding/screens/P13JoinDate.tsx +++ b/components/onboarding/screens/P13JoinDate.tsx @@ -1,6 +1,6 @@ import { Ionicons } from '@expo/vector-icons'; import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { YearMonthWheel } from '../ui/WheelPicker'; @@ -47,8 +47,8 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', alignSelf: 'center', - marginBottom: 12, + marginBottom: spacing.block, }, - tip: { fontSize: 13, color: colors.textHint, textAlign: 'center', marginBottom: 18 }, + tip: { fontSize: 13, color: colors.textHint, textAlign: 'center', marginBottom: spacing.block }, footer: { marginTop: 28 }, }); diff --git a/components/onboarding/screens/P14Skills.tsx b/components/onboarding/screens/P14Skills.tsx index dd38265..3bfd61a 100644 --- a/components/onboarding/screens/P14Skills.tsx +++ b/components/onboarding/screens/P14Skills.tsx @@ -1,5 +1,5 @@ import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { Tag, TagGroup } from '../ui/Tag'; @@ -66,13 +66,13 @@ export function P14Skills() { } const styles = StyleSheet.create({ - header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }, + header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: spacing.block }, title: { fontSize: 20, fontWeight: '800', color: colors.textPrimary }, counter: { backgroundColor: colors.borderLight, paddingHorizontal: 12, paddingVertical: 4, borderRadius: 12 }, counterActive: { backgroundColor: colors.primaryLight }, counterText: { fontSize: 13, fontWeight: '700', color: colors.textDisabled }, counterTextActive: { color: colors.primaryText }, - group: { marginBottom: 18 }, - groupName: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginBottom: 10 }, + group: { marginBottom: spacing.block }, + groupName: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginBottom: spacing.sm }, footer: { marginTop: 20 }, }); diff --git a/components/onboarding/screens/P16WorkContent.tsx b/components/onboarding/screens/P16WorkContent.tsx index 5e95b36..b540632 100644 --- a/components/onboarding/screens/P16WorkContent.tsx +++ b/components/onboarding/screens/P16WorkContent.tsx @@ -1,6 +1,6 @@ import { Ionicons } from '@expo/vector-icons'; import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding, useRegisterFinish } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { PrimaryButton } from '../ui/PrimaryButton'; @@ -45,10 +45,10 @@ export function P16WorkContent() { } const styles = StyleSheet.create({ - header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }, + header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: spacing.block }, title: { fontSize: 20, fontWeight: '800', color: colors.textPrimary }, save: { fontSize: 15, fontWeight: '700', color: colors.primary }, - ref: { flexDirection: 'row', alignItems: 'center', gap: 6, alignSelf: 'flex-start', marginBottom: 14 }, + ref: { flexDirection: 'row', alignItems: 'center', gap: 6, alignSelf: 'flex-start', marginBottom: spacing.block }, refText: { fontSize: 14, color: colors.primary, fontWeight: '600' }, input: { minHeight: 200, diff --git a/components/onboarding/screens/P17Reference.tsx b/components/onboarding/screens/P17Reference.tsx index c3433bb..afd63e8 100644 --- a/components/onboarding/screens/P17Reference.tsx +++ b/components/onboarding/screens/P17Reference.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Ionicons } from '@expo/vector-icons'; import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { Avatar } from '../ui/Avatar'; @@ -73,7 +73,7 @@ export function P17Reference() { } const styles = StyleSheet.create({ - head: { flexDirection: 'row', alignItems: 'center', gap: 14, marginBottom: 18 }, + head: { flexDirection: 'row', alignItems: 'center', gap: spacing.block, marginBottom: spacing.block }, headText: { gap: 8 }, name: { fontSize: 16, fontWeight: '700', color: colors.textPrimary }, swap: { flexDirection: 'row', alignItems: 'center', gap: 4 }, @@ -82,7 +82,7 @@ const styles = StyleSheet.create({ backgroundColor: colors.white, borderRadius: 8, padding: 8, - gap: 12, + gap: spacing.block, borderWidth: 1, borderColor: colors.border, }, diff --git a/components/onboarding/screens/P18Education.tsx b/components/onboarding/screens/P18Education.tsx index e29f8ab..28cc898 100644 --- a/components/onboarding/screens/P18Education.tsx +++ b/components/onboarding/screens/P18Education.tsx @@ -1,6 +1,6 @@ import { Ionicons } from '@expo/vector-icons'; import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { RadioRow } from '../ui/RadioRow'; @@ -48,9 +48,9 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', alignSelf: 'center', - marginBottom: 18, + marginBottom: spacing.block, }, list: { gap: 8 }, - subTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginTop: 22, marginBottom: 12 }, + subTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginTop: spacing.section, marginBottom: spacing.block }, footer: { marginTop: 28 }, }); diff --git a/components/onboarding/screens/P1Login.tsx b/components/onboarding/screens/P1Login.tsx index cf0f05b..d75477c 100644 --- a/components/onboarding/screens/P1Login.tsx +++ b/components/onboarding/screens/P1Login.tsx @@ -2,7 +2,7 @@ import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { StatusBar } from 'expo-status-bar'; import { LinearGradient } from 'expo-linear-gradient'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useAgreementGate, useOnboarding } from '../../../src/hooks'; import { Illustration } from '../ui/Illustration'; import { AgreementCheckbox } from '../ui/AgreementCheckbox'; @@ -76,7 +76,7 @@ export function P1Login() { const styles = StyleSheet.create({ screen: { flex: 1, backgroundColor: colors.pageBg }, topGlow: { position: 'absolute', top: 0, left: 0, right: 0, height: 200 }, - content: { flexGrow: 1, paddingHorizontal: 8, paddingBottom: 12 }, + content: { flexGrow: 1, paddingHorizontal: 8, paddingBottom: spacing.block }, brandArea: { alignItems: 'center', marginTop: '22%' }, flexSpacer: { flex: 1, minHeight: 28 }, quickBtn: { @@ -84,7 +84,7 @@ const styles = StyleSheet.create({ borderRadius: 12, alignItems: 'center', justifyContent: 'center', - marginTop: 14, + marginTop: spacing.block, backgroundColor: colors.borderLight, }, quickText: { fontSize: 16, fontWeight: '700', color: colors.textPrimary }, @@ -94,7 +94,7 @@ const styles = StyleSheet.create({ fontSize: 14, color: colors.textSecondary, }, - agree: { marginTop: 22 }, + agree: { marginTop: spacing.section }, agreeText: { fontSize: 12, color: colors.textSecondary, lineHeight: 18 }, link: { color: colors.primary, fontWeight: '600' }, footer: { paddingTop: 24, paddingBottom: 8 }, diff --git a/components/onboarding/screens/P21Avatar.tsx b/components/onboarding/screens/P21Avatar.tsx index bc14912..8bbebeb 100644 --- a/components/onboarding/screens/P21Avatar.tsx +++ b/components/onboarding/screens/P21Avatar.tsx @@ -1,6 +1,6 @@ import { Ionicons } from '@expo/vector-icons'; import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useImagePick, useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { Avatar } from '../ui/Avatar'; @@ -84,8 +84,8 @@ const styles = StyleSheet.create({ actionBtn: { alignItems: 'center', gap: 6 }, actionBtnDim: { opacity: 0.5 }, actionText: { fontSize: 12, color: colors.textSecondary }, - error: { fontSize: 12, color: colors.dangerText, textAlign: 'center', marginBottom: 18 }, - subTitle: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginBottom: 14 }, - presets: { flexDirection: 'row', justifyContent: 'space-between', gap: 10 }, + error: { fontSize: 12, color: colors.dangerText, textAlign: 'center', marginBottom: spacing.block }, + subTitle: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginBottom: spacing.block }, + presets: { flexDirection: 'row', justifyContent: 'space-between', gap: spacing.sm }, footer: { marginTop: 32 }, }); diff --git a/components/onboarding/screens/P22Done.tsx b/components/onboarding/screens/P22Done.tsx index e50d848..81f8ca0 100644 --- a/components/onboarding/screens/P22Done.tsx +++ b/components/onboarding/screens/P22Done.tsx @@ -1,6 +1,6 @@ import { Ionicons } from '@expo/vector-icons'; import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useRegisterFinish } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { Illustration } from '../ui/Illustration'; @@ -28,7 +28,7 @@ export function P22Done() { } const styles = StyleSheet.create({ - center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 22, paddingBottom: 40 }, + center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: spacing.section, paddingBottom: 40 }, glow: { width: 120, height: 120, diff --git a/components/onboarding/screens/P2PasswordLogin.tsx b/components/onboarding/screens/P2PasswordLogin.tsx index 829f465..8f3bda5 100644 --- a/components/onboarding/screens/P2PasswordLogin.tsx +++ b/components/onboarding/screens/P2PasswordLogin.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useAgreementGate, useOnboarding, usePasswordLogin } from '../../../src/hooks'; import { isValidPhone } from '../../../src/utils'; import { useAuth } from '../../../src/context/AuthContext'; @@ -121,18 +121,18 @@ export function P2PasswordLogin() { } const styles = StyleSheet.create({ - h1: { fontSize: 26, fontWeight: '800', color: colors.textPrimary, marginTop: 12 }, - subtitle: { fontSize: 14, color: colors.textSecondary, marginTop: 10 }, + h1: { fontSize: 26, fontWeight: '800', color: colors.textPrimary, marginTop: spacing.block }, + subtitle: { fontSize: 14, color: colors.textSecondary, marginTop: spacing.sm }, fieldRow: { flexDirection: 'row', alignItems: 'center', height: 56, marginTop: 28, gap: 6 }, prefix: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, divider: { width: 1, height: 20, backgroundColor: colors.border, marginHorizontal: 10 }, input: { flex: 1, fontSize: 17, color: colors.textPrimary, padding: 0 }, underline: { height: 1, backgroundColor: colors.borderLight }, - passwordArea: { marginTop: 22 }, - forgotLink: { marginTop: 14, alignSelf: 'flex-end', fontSize: 13, color: colors.primaryText, fontWeight: '600' }, + passwordArea: { marginTop: spacing.section }, + forgotLink: { marginTop: spacing.block, alignSelf: 'flex-end', fontSize: 13, color: colors.primaryText, fontWeight: '600' }, btnArea: { marginTop: 26 }, - formError: { marginTop: 10, textAlign: 'center', fontSize: 12, color: colors.danger }, - agree: { marginTop: 18 }, + formError: { marginTop: spacing.sm, textAlign: 'center', fontSize: 12, color: colors.danger }, + agree: { marginTop: spacing.block }, agreeText: { fontSize: 12, color: colors.textSecondary, lineHeight: 18 }, link: { color: colors.primary, fontWeight: '600' }, switchLink: { marginTop: 26, textAlign: 'center', fontSize: 14, color: colors.textSecondary }, diff --git a/components/onboarding/screens/P2PasswordReset.tsx b/components/onboarding/screens/P2PasswordReset.tsx index c5f426c..98dfa6a 100644 --- a/components/onboarding/screens/P2PasswordReset.tsx +++ b/components/onboarding/screens/P2PasswordReset.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useAgreementGate, useOnboarding, usePasswordReset, useSmsCode } from '../../../src/hooks'; import { isValidPhone, isValidSmsCode, SMS_CODE_MAX_LENGTH } from '../../../src/utils'; import { useAuth } from '../../../src/context/AuthContext'; @@ -167,16 +167,16 @@ export function P2PasswordReset() { } const styles = StyleSheet.create({ - h1: { fontSize: 26, fontWeight: '800', color: colors.textPrimary, marginTop: 12 }, - subtitle: { fontSize: 14, color: colors.textSecondary, marginTop: 10 }, + h1: { fontSize: 26, fontWeight: '800', color: colors.textPrimary, marginTop: spacing.block }, + subtitle: { fontSize: 14, color: colors.textSecondary, marginTop: spacing.sm }, fieldRow: { flexDirection: 'row', alignItems: 'center', height: 56, marginTop: 26, gap: 6 }, prefix: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, divider: { width: 1, height: 20, backgroundColor: colors.border, marginHorizontal: 10 }, input: { flex: 1, fontSize: 17, color: colors.textPrimary, padding: 0 }, underline: { height: 1, backgroundColor: colors.borderLight }, - passwordArea: { marginTop: 22 }, - hint: { marginTop: 14, fontSize: 12, color: colors.textHint }, - hintDanger: { marginTop: 14, fontSize: 12, color: colors.danger }, + passwordArea: { marginTop: spacing.section }, + hint: { marginTop: spacing.block, fontSize: 12, color: colors.textHint }, + hintDanger: { marginTop: spacing.block, fontSize: 12, color: colors.danger }, btnArea: { marginTop: 26 }, - formError: { marginTop: 10, textAlign: 'center', fontSize: 12, color: colors.danger }, + formError: { marginTop: spacing.sm, textAlign: 'center', fontSize: 12, color: colors.danger }, }); diff --git a/components/onboarding/screens/P2PhoneCode.tsx b/components/onboarding/screens/P2PhoneCode.tsx index 6ad3b8c..21bbc4f 100644 --- a/components/onboarding/screens/P2PhoneCode.tsx +++ b/components/onboarding/screens/P2PhoneCode.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Ionicons } from '@expo/vector-icons'; import { Alert, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { maskPhone } from '../../../src/api'; import { SMS_HELP_BODY, SMS_HELP_TITLE } from '../../../src/constants'; import { useAgreementGate, useLogin, useOnboarding, useSmsCode, useWechatBindPhone } from '../../../src/hooks'; @@ -215,8 +215,8 @@ export function P2PhoneCode() { } const styles = StyleSheet.create({ - h1: { fontSize: 26, fontWeight: '800', color: colors.textPrimary, marginTop: 12 }, - subtitle: { fontSize: 14, color: colors.textSecondary, marginTop: 10 }, + h1: { fontSize: 26, fontWeight: '800', color: colors.textPrimary, marginTop: spacing.block }, + subtitle: { fontSize: 14, color: colors.textSecondary, marginTop: spacing.sm }, phoneRow: { flexDirection: 'row', alignItems: 'center', @@ -232,11 +232,11 @@ const styles = StyleSheet.create({ smsHelpRow: { alignSelf: 'flex-start', marginTop: 16 }, smsHelp: { fontSize: 13, color: colors.primaryText }, btnArea: { marginTop: 28 }, - agree: { marginTop: 18 }, - formError: { marginTop: 10, textAlign: 'center', fontSize: 12, color: colors.danger }, + agree: { marginTop: spacing.block }, + formError: { marginTop: spacing.sm, textAlign: 'center', fontSize: 12, color: colors.danger }, agreeText: { fontSize: 12, color: colors.textSecondary, lineHeight: 18 }, link: { color: colors.primary, fontWeight: '600' }, - orRow: { flexDirection: 'row', alignItems: 'center', marginTop: 36, gap: 12 }, + orRow: { flexDirection: 'row', alignItems: 'center', marginTop: 36, gap: spacing.block }, orLine: { flex: 1, height: 1, backgroundColor: colors.borderLight }, orText: { fontSize: 12, color: colors.textHint }, switchLink: { marginTop: 20, textAlign: 'center', fontSize: 14, color: colors.primaryText, fontWeight: '600' }, diff --git a/components/onboarding/screens/P2QuickLogin.tsx b/components/onboarding/screens/P2QuickLogin.tsx index 6b77559..d8e6f96 100644 --- a/components/onboarding/screens/P2QuickLogin.tsx +++ b/components/onboarding/screens/P2QuickLogin.tsx @@ -1,5 +1,5 @@ import { Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { CARRIER_LABEL } from '../../../src/constants'; import { useAgreementGate, useCarrierPhone, useLogin, useOnboarding } from '../../../src/hooks'; import { useAuth } from '../../../src/context/AuthContext'; @@ -154,20 +154,20 @@ const styles = StyleSheet.create({ marginTop: 44, }, phoneFailed: { fontSize: 18, color: colors.dangerText }, - subtitle: { textAlign: 'center', fontSize: 14, color: colors.textSecondary, marginTop: 12 }, + subtitle: { textAlign: 'center', fontSize: 14, color: colors.textSecondary, marginTop: spacing.block }, btnArea: { marginTop: 36 }, fallbackRow: { flexDirection: 'row', justifyContent: 'center', gap: 24, - marginTop: 18, + marginTop: spacing.block, }, fallbackBtn: { paddingVertical: 6, paddingHorizontal: 4 }, fallbackText: { fontSize: 14, fontWeight: '600', color: colors.primaryText }, - formError: { marginTop: 10, textAlign: 'center', fontSize: 12, color: colors.danger }, + formError: { marginTop: spacing.sm, textAlign: 'center', fontSize: 12, color: colors.danger }, agree: { marginTop: 20 }, agreeText: { fontSize: 12, color: colors.textSecondary, lineHeight: 18 }, link: { color: colors.primary, fontWeight: '600' }, footer: { paddingTop: 16, paddingBottom: 8 }, - carrierNote: { textAlign: 'center', fontSize: 12, color: colors.textHint, marginTop: 12 }, + carrierNote: { textAlign: 'center', fontSize: 12, color: colors.textHint, marginTop: spacing.block }, }); diff --git a/components/onboarding/screens/P2WechatCode.tsx b/components/onboarding/screens/P2WechatCode.tsx index 10bb1c1..8c8113a 100644 --- a/components/onboarding/screens/P2WechatCode.tsx +++ b/components/onboarding/screens/P2WechatCode.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import { Alert, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { SMS_HELP_BODY, SMS_HELP_TITLE } from '../../../src/constants'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; @@ -109,10 +109,10 @@ export function P2WechatCode() { } const styles = StyleSheet.create({ - h1: { fontSize: 24, fontWeight: '800', color: colors.textPrimary, marginTop: 14 }, - subtitle: { fontSize: 14, color: colors.textSecondary, marginTop: 12 }, + h1: { fontSize: 24, fontWeight: '800', color: colors.textPrimary, marginTop: spacing.block }, + subtitle: { fontSize: 14, color: colors.textSecondary, marginTop: spacing.block }, cellsWrap: { marginTop: 34 }, - cellsRow: { flexDirection: 'row', gap: 18 }, + cellsRow: { flexDirection: 'row', gap: spacing.block }, cell: { flex: 1, alignItems: 'center' }, cellText: { fontSize: 26, fontWeight: '700', color: colors.textPrimary, minHeight: 36 }, cellLine: { width: '100%', height: 2, backgroundColor: colors.border, marginTop: 8 }, @@ -121,17 +121,17 @@ const styles = StyleSheet.create({ ...StyleSheet.absoluteFill, opacity: 0, }, - resend: { textAlign: 'center', fontSize: 14, color: colors.textSecondary, marginTop: 22 }, + resend: { textAlign: 'center', fontSize: 14, color: colors.textSecondary, marginTop: spacing.section }, resendDisabled: { color: colors.textHint }, btnArea: { marginTop: 30 }, - helpRow: { alignSelf: 'flex-start', marginTop: 22 }, + helpRow: { alignSelf: 'flex-start', marginTop: spacing.section }, // 已是可点入口,用可读对比度的 primaryText(textHint 在 13pt 下不达 AA) helpText: { fontSize: 13, color: colors.primaryText }, toastBar: { alignSelf: 'center', marginTop: 30, paddingHorizontal: 18, - paddingVertical: 10, + paddingVertical: spacing.sm, borderRadius: 8, backgroundColor: colors.borderLight, }, diff --git a/components/onboarding/screens/P3Identity.tsx b/components/onboarding/screens/P3Identity.tsx index 532ab37..a280189 100644 --- a/components/onboarding/screens/P3Identity.tsx +++ b/components/onboarding/screens/P3Identity.tsx @@ -1,5 +1,5 @@ import { Alert, Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useAuth } from '../../../src/context/AuthContext'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; @@ -82,7 +82,7 @@ const styles = StyleSheet.create({ }, cardBtnOutline: { backgroundColor: colors.white, borderWidth: 1, borderColor: colors.primary }, cardBtnText: { fontSize: 16, fontWeight: '700', color: colors.textInverse }, - or: { textAlign: 'center', color: colors.textHint, marginVertical: 14, fontSize: 13 }, - logout: { marginTop: 22, alignItems: 'center' }, + or: { textAlign: 'center', color: colors.textHint, marginVertical: spacing.block, fontSize: 13 }, + logout: { marginTop: spacing.section, alignItems: 'center' }, logoutText: { fontSize: 13, color: colors.textHint }, }); diff --git a/components/onboarding/screens/P4Intent.tsx b/components/onboarding/screens/P4Intent.tsx index 7f3e5eb..756ac89 100644 --- a/components/onboarding/screens/P4Intent.tsx +++ b/components/onboarding/screens/P4Intent.tsx @@ -1,6 +1,6 @@ import { Ionicons } from '@expo/vector-icons'; import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { Tag, TagGroup } from '../ui/Tag'; @@ -57,8 +57,8 @@ export function P4Intent() { } const styles = StyleSheet.create({ - section: { fontSize: 16, fontWeight: '700', color: colors.textPrimary, marginTop: 22, marginBottom: 12 }, - cityRow: { flexDirection: 'row', alignItems: 'center', gap: 10 }, + section: { fontSize: 16, fontWeight: '700', color: colors.textPrimary, marginTop: spacing.section, marginBottom: spacing.block }, + cityRow: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm }, cityHint: { fontSize: 12, color: colors.textHint }, footer: { marginTop: 32 }, }); diff --git a/components/onboarding/screens/P5City.tsx b/components/onboarding/screens/P5City.tsx index 90d3dd9..d8501a1 100644 --- a/components/onboarding/screens/P5City.tsx +++ b/components/onboarding/screens/P5City.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Ionicons } from '@expo/vector-icons'; import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { EXPECTED_CITY_OPTIONS, HOT_CITY_OPTIONS } from '../../../src/constants'; import { ScreenShell } from '../ui/ScreenShell'; @@ -50,7 +50,7 @@ export function P5City() { } const styles = StyleSheet.create({ - section: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginTop: 22, marginBottom: 12 }, + section: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginTop: spacing.section, marginBottom: spacing.block }, list: { gap: 2 }, cityItem: { flexDirection: 'row', diff --git a/components/onboarding/screens/P6ExpectedPosition.tsx b/components/onboarding/screens/P6ExpectedPosition.tsx index 2ecae4d..bd1f622 100644 --- a/components/onboarding/screens/P6ExpectedPosition.tsx +++ b/components/onboarding/screens/P6ExpectedPosition.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Ionicons } from '@expo/vector-icons'; import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding, useRegisterFinish } from '../../../src/hooks'; import { onboardingStepLabel } from '../../../src/utils'; import { ScreenShell } from '../ui/ScreenShell'; @@ -114,7 +114,7 @@ export function P6ExpectedPosition() { } const styles = StyleSheet.create({ - header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }, + header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: spacing.sm }, title: { fontSize: 20, fontWeight: '800', color: colors.textPrimary }, progress: { alignSelf: 'flex-start', @@ -122,19 +122,19 @@ const styles = StyleSheet.create({ paddingHorizontal: 10, paddingVertical: 3, borderRadius: 10, - marginBottom: 12, + marginBottom: spacing.block, }, progressText: { fontSize: 12, fontWeight: '700', color: colors.primaryText }, save: { fontSize: 15, fontWeight: '700', color: colors.primary }, empty: { fontSize: 13, color: colors.textHint, lineHeight: 20 }, - body: { flexDirection: 'row', gap: 14, marginTop: 8 }, + body: { flexDirection: 'row', gap: spacing.block, marginTop: 8 }, cats: { width: 92 }, - catItem: { paddingVertical: 12, borderRadius: 10 }, + catItem: { paddingVertical: spacing.block, borderRadius: 10 }, catItemActive: { backgroundColor: colors.primaryLight }, catText: { fontSize: 13, color: colors.textSecondary, textAlign: 'center' }, catTextActive: { color: colors.primaryText, fontWeight: '700' }, posCol: { flex: 1 }, - chips: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 14 }, + chips: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: spacing.block }, chip: { flexDirection: 'row', alignItems: 'center', diff --git a/components/onboarding/screens/P7Salary.tsx b/components/onboarding/screens/P7Salary.tsx index e95d669..df5fd00 100644 --- a/components/onboarding/screens/P7Salary.tsx +++ b/components/onboarding/screens/P7Salary.tsx @@ -1,5 +1,5 @@ import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { applySalaryMin, salaryMaxOptions } from '../../../src/api'; import { SALARY_MIN_OPTIONS } from '../../../src/constants'; @@ -56,7 +56,7 @@ export function P7Salary() { const styles = StyleSheet.create({ tip: { fontSize: 13, color: colors.textHint, marginBottom: 20 }, cols: { flexDirection: 'row', gap: 16 }, - col: { flex: 1, gap: 12 }, + col: { flex: 1, gap: spacing.block }, colTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, textAlign: 'center', marginBottom: 4 }, opt: { height: 50, diff --git a/components/onboarding/screens/P8Resume1.tsx b/components/onboarding/screens/P8Resume1.tsx index 11282da..e3241c8 100644 --- a/components/onboarding/screens/P8Resume1.tsx +++ b/components/onboarding/screens/P8Resume1.tsx @@ -1,6 +1,6 @@ import { Ionicons } from '@expo/vector-icons'; import { StyleSheet, Text, TextInput, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { ScreenShell } from '../ui/ScreenShell'; import { Tag, TagGroup } from '../ui/Tag'; @@ -64,9 +64,9 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', alignSelf: 'center', - marginBottom: 18, + marginBottom: spacing.block, }, - label: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginTop: 18, marginBottom: 10 }, + label: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginTop: spacing.block, marginBottom: spacing.sm }, input: { height: 50, borderRadius: 12, diff --git a/components/onboarding/screens/P9JobStatus.tsx b/components/onboarding/screens/P9JobStatus.tsx index 85a69ce..ec6ad76 100644 --- a/components/onboarding/screens/P9JobStatus.tsx +++ b/components/onboarding/screens/P9JobStatus.tsx @@ -1,6 +1,6 @@ import { Ionicons } from '@expo/vector-icons'; import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { useOnboarding } from '../../../src/hooks'; import { JOB_STATUS_OPTIONS } from '../../../src/constants'; import { ScreenShell } from '../ui/ScreenShell'; @@ -57,11 +57,11 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', alignSelf: 'center', - marginBottom: 18, + marginBottom: spacing.block, }, list: { gap: 8 }, - note: { fontSize: 12, color: colors.textHint, marginTop: 14, textAlign: 'center' }, - agree: { marginTop: 18 }, + note: { fontSize: 12, color: colors.textHint, marginTop: spacing.block, textAlign: 'center' }, + agree: { marginTop: spacing.block }, agreeText: { fontSize: 13, color: colors.textSecondary }, footer: { marginTop: 28 }, }); diff --git a/components/onboarding/ui/AgreementCheckbox.tsx b/components/onboarding/ui/AgreementCheckbox.tsx index 7d6280c..434d1ce 100644 --- a/components/onboarding/ui/AgreementCheckbox.tsx +++ b/components/onboarding/ui/AgreementCheckbox.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from 'react'; import { Ionicons } from '@expo/vector-icons'; import { Pressable, StyleSheet, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; interface Props { checked: boolean; @@ -22,7 +22,7 @@ export function AgreementCheckbox({ checked, onToggle, text }: Props) { } const styles = StyleSheet.create({ - row: { flexDirection: 'row', alignItems: 'center', gap: 10 }, + row: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm }, box: { width: 20, height: 20, diff --git a/components/onboarding/ui/AgreementModal.tsx b/components/onboarding/ui/AgreementModal.tsx index d7f9aa3..8a956a5 100644 --- a/components/onboarding/ui/AgreementModal.tsx +++ b/components/onboarding/ui/AgreementModal.tsx @@ -1,5 +1,5 @@ import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { Modal } from './Modal'; interface Props { @@ -38,7 +38,7 @@ export function AgreementModal({ visible, onAccept, onDecline, onOpenAgreement } const styles = StyleSheet.create({ message: { fontSize: 15, lineHeight: 24, color: colors.textPrimary, textAlign: 'center' }, link: { color: colors.primary }, - btnRow: { flexDirection: 'row', gap: 14, marginTop: 20 }, + btnRow: { flexDirection: 'row', gap: spacing.block, marginTop: 20 }, btn: { flex: 1, height: 46, diff --git a/components/onboarding/ui/BrandHeader.tsx b/components/onboarding/ui/BrandHeader.tsx index caba9d0..50f6a7a 100644 --- a/components/onboarding/ui/BrandHeader.tsx +++ b/components/onboarding/ui/BrandHeader.tsx @@ -1,5 +1,5 @@ import { StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { Illustration } from './Illustration'; /** 统一品牌头部:Logo + 名称 + 标语 */ @@ -14,7 +14,7 @@ export function BrandHeader({ slogan }: { slogan?: string }) { } const styles = StyleSheet.create({ - wrap: { alignItems: 'center', gap: 12, paddingVertical: 8 }, + wrap: { alignItems: 'center', gap: spacing.block, paddingVertical: 8 }, name: { fontSize: 26, fontWeight: '800', color: colors.textPrimary }, slogan: { fontSize: 13, color: colors.textHint }, }); diff --git a/components/onboarding/ui/DeletedAccountModal.tsx b/components/onboarding/ui/DeletedAccountModal.tsx index 9ab2407..55737df 100644 --- a/components/onboarding/ui/DeletedAccountModal.tsx +++ b/components/onboarding/ui/DeletedAccountModal.tsx @@ -1,5 +1,5 @@ import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { Modal } from './Modal'; interface Props { @@ -34,7 +34,7 @@ const styles = StyleSheet.create({ fontWeight: '600', color: colors.textPrimary, textAlign: 'center', - marginBottom: 10, + marginBottom: spacing.sm, }, message: { fontSize: 14, lineHeight: 22, color: colors.textSecondary, textAlign: 'center' }, btnRow: { marginTop: 20 }, diff --git a/components/onboarding/ui/Modal.tsx b/components/onboarding/ui/Modal.tsx index 66f7503..91fa498 100644 --- a/components/onboarding/ui/Modal.tsx +++ b/components/onboarding/ui/Modal.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; interface Props { visible: boolean; @@ -35,7 +35,7 @@ const styles = StyleSheet.create({ padding: 8, maxHeight: '80%', }, - title: { fontSize: 18, fontWeight: '800', color: colors.textPrimary, marginBottom: 12, textAlign: 'center' }, - body: { gap: 10 }, - footer: { marginTop: 18, gap: 10 }, + title: { fontSize: 18, fontWeight: '800', color: colors.textPrimary, marginBottom: spacing.block, textAlign: 'center' }, + body: { gap: spacing.sm }, + footer: { marginTop: spacing.block, gap: spacing.sm }, }); diff --git a/components/onboarding/ui/ScreenShell.tsx b/components/onboarding/ui/ScreenShell.tsx index 8bad576..9ac4caa 100644 --- a/components/onboarding/ui/ScreenShell.tsx +++ b/components/onboarding/ui/ScreenShell.tsx @@ -3,7 +3,7 @@ import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { StatusBar } from 'expo-status-bar'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; interface Props { title?: string; @@ -43,10 +43,10 @@ export function ScreenShell({ title, showBack, onBack, backIcon = 'chevron-back' const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.pageBg }, - backRow: { flexDirection: 'row', alignItems: 'center', gap: 2, paddingHorizontal: 16, paddingVertical: 10 }, + backRow: { flexDirection: 'row', alignItems: 'center', gap: 2, paddingHorizontal: 16, paddingVertical: spacing.sm }, titleInline: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, - titleCenter: { fontSize: 18, fontWeight: '800', color: colors.textPrimary, textAlign: 'center', paddingVertical: 12 }, + titleCenter: { fontSize: 18, fontWeight: '800', color: colors.textPrimary, textAlign: 'center', paddingVertical: spacing.block }, scroll: { flex: 1 }, scrollContent: { flexGrow: 1, padding: 8, paddingBottom: 28 }, - footer: { padding: 16, paddingBottom: 22, backgroundColor: colors.pageBg }, + footer: { padding: 16, paddingBottom: spacing.section, backgroundColor: colors.pageBg }, }); diff --git a/components/onboarding/ui/SliderCaptcha.tsx b/components/onboarding/ui/SliderCaptcha.tsx index e39e6aa..52765f8 100644 --- a/components/onboarding/ui/SliderCaptcha.tsx +++ b/components/onboarding/ui/SliderCaptcha.tsx @@ -14,7 +14,7 @@ import { Stop, Svg, } from 'react-native-svg'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { Modal } from './Modal'; import { CAPTCHA_BACKGROUNDS } from '../assets'; @@ -301,6 +301,6 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center', - marginTop: 14, + marginTop: spacing.block, }, }); diff --git a/components/onboarding/ui/SupportFooter.tsx b/components/onboarding/ui/SupportFooter.tsx index 42f2791..68d609b 100644 --- a/components/onboarding/ui/SupportFooter.tsx +++ b/components/onboarding/ui/SupportFooter.tsx @@ -1,5 +1,5 @@ import { Linking, Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { SUPERVISION_PHONE, SUPPORT_HOTLINE } from '../../../src/constants'; /** @@ -38,6 +38,6 @@ export function SupportFooter() { const styles = StyleSheet.create({ // 两串「标签 + 11 位号码」在窄屏上会挤,允许换行 - row: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', gap: 18 }, + row: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', gap: spacing.block }, item: { fontSize: 12, color: colors.textHint }, }); diff --git a/components/onboarding/ui/Tag.tsx b/components/onboarding/ui/Tag.tsx index 97e87de..20f0a33 100644 --- a/components/onboarding/ui/Tag.tsx +++ b/components/onboarding/ui/Tag.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; interface TagProps { label: string; @@ -28,7 +28,7 @@ export function TagGroup({ children }: { children: ReactNode }) { } const styles = StyleSheet.create({ - group: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 }, + group: { flexDirection: 'row', flexWrap: 'wrap', gap: spacing.sm }, tag: { paddingHorizontal: 16, paddingVertical: 9, borderRadius: 18, borderWidth: 1 }, tagNormal: { backgroundColor: colors.white, borderColor: colors.border }, tagSelected: { backgroundColor: colors.primary, borderColor: colors.primary }, diff --git a/components/onboarding/ui/WheelPicker.tsx b/components/onboarding/ui/WheelPicker.tsx index 3f3feed..8b70fbe 100644 --- a/components/onboarding/ui/WheelPicker.tsx +++ b/components/onboarding/ui/WheelPicker.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef } from 'react'; import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native'; import { ScrollView, StyleSheet, Text, View } from 'react-native'; -import { colors } from '../../../src/theme'; +import { colors, spacing } from '../../../src/theme'; import { CURRENT_YEAR } from '../../../src/constants'; const ITEM_H = 44; @@ -140,6 +140,6 @@ const styles = StyleSheet.create({ item: { height: ITEM_H, alignItems: 'center', justifyContent: 'center' }, itemText: { fontSize: 18, color: colors.textHint }, itemTextActive: { fontSize: 20, fontWeight: '700', color: colors.textPrimary }, - dual: { flexDirection: 'row', justifyContent: 'center', gap: 14 }, - hint: { fontSize: 13, color: colors.textHint, textAlign: 'center', marginTop: 10 }, + dual: { flexDirection: 'row', justifyContent: 'center', gap: spacing.block }, + hint: { fontSize: 13, color: colors.textHint, textAlign: 'center', marginTop: spacing.sm }, }); diff --git a/scripts/check-spacing.mjs b/scripts/check-spacing.mjs index dd8fef7..359102d 100644 --- a/scripts/check-spacing.mjs +++ b/scripts/check-spacing.mjs @@ -54,6 +54,19 @@ const STRICT = process.argv.includes('--error'); const ROOTS = ['app', 'components', 'src']; const TARGET = 8; +/** + * 间距令牌镜像(必须与 src/theme/spacing.ts 同步!)。 + * 改了 spacing.ts 的取值后,这里不更新会导致审计门禁失准(令牌被当成「无数值」而跳过)。 + * 键覆盖标尺(xs/sm/md/lg/xl)与语义令牌(card/gutter/framework/block/section)。 + */ +const SPACING_TOKENS = { + xs: 4, sm: 8, md: 12, lg: 16, xl: 24, + card: 8, gutter: 8, framework: 16, block: 12, section: 12, +}; + +/** 记录本批命中的令牌引用,供输出复核(证明令牌化后审计仍在工作) */ +const tokenizedHits = []; + /** 要核对的间距维度 */ const CARD_GAP_PROPS = ['marginTop', 'marginBottom']; @@ -125,12 +138,34 @@ function readNumber(text, prop) { return m ? Number(m[1]) : null; } +/** + * 读间距值:先试裸数字,再试令牌引用(spacing.x / spacingTokens.x)。 + * 这样卡片 / 留白改成 `spacing.card` 这类写法后,审计仍能解析出数值并核对, + * 不会因为「读不到数字」而把该项静默跳过(那会撕开审计网)。 + * 未知令牌名按 null 处理(交给调用方,至多误判,绝不静默通过)。 + */ +function resolveSpacing(text, prop) { + const num = readNumber(text, prop); + if (num !== null) return num; + const tm = flatten(text).match( + new RegExp(`^[ \\t]*${prop}:[ \\t]*(?:spacing|spacingTokens)\\.([A-Za-z_$][\\w$]*)`, 'm'), + ); + if (tm) { + const v = SPACING_TOKENS[tm[1]]; + if (typeof v === 'number') { + tokenizedHits.push(`${prop}: ${tm[1]}`); + return v; + } + } + return null; +} + const hasProp = (text, prop) => new RegExp(`^[ \\t]*${prop}:`, 'm').test(flatten(text)); const isCircle = ({ text }) => { - const w = readNumber(text, 'width'); - const h = readNumber(text, 'height'); - const r = readNumber(text, 'borderRadius'); + const w = resolveSpacing(text, 'width'); + const h = resolveSpacing(text, 'height'); + const r = resolveSpacing(text, 'borderRadius'); return w !== null && h !== null && w === h && Math.abs(r - w / 2) < 0.51; }; @@ -147,7 +182,7 @@ for (const file of files) { if (!src.includes('StyleSheet.create(')) continue; for (const block of styleBlocks(src)) { - if (readNumber(block.text, 'borderRadius') === null) continue; + if (resolveSpacing(block.text, 'borderRadius') === null) continue; if (!hasProp(block.text, 'backgroundColor') && !hasProp(block.text, 'borderWidth')) continue; if (isCircle(block)) { excluded.push(`${file} | ${block.key} | 圆 ${readNumber(block.text, 'width')}`); @@ -159,19 +194,19 @@ for (const file of files) { } cardCount += 1; - const radius = readNumber(block.text, 'borderRadius'); + const radius = resolveSpacing(block.text, 'borderRadius'); if (radius !== TARGET) { violations.radius.push(`${file} | ${block.key} | borderRadius ${radius}`); continue; } radiusOk += 1; - const padding = readNumber(block.text, 'padding'); + const padding = resolveSpacing(block.text, 'padding'); if (padding !== null) { if (padding !== TARGET) violations.padding.push(`${file} | ${block.key} | padding ${padding}`); } else { for (const prop of ['paddingHorizontal', 'paddingVertical']) { - const value = readNumber(block.text, prop); + const value = resolveSpacing(block.text, prop); if (value !== null && value !== TARGET) { violations.padding.push(`${file} | ${block.key} | ${prop} ${value}`); } @@ -179,7 +214,7 @@ for (const file of files) { } for (const prop of CARD_GAP_PROPS) { - const value = readNumber(block.text, prop); + const value = resolveSpacing(block.text, prop); if (value !== null && value !== TARGET) { violations.gap.push(`${file} | ${block.key} | ${prop} ${value}`); } @@ -206,8 +241,8 @@ for (const file of files) { for (const key of candidates) { const block = blocks.find((b) => b.key === key); if (!block) continue; - const padding = readNumber(block.text, 'padding'); - const horizontal = readNumber(block.text, 'paddingHorizontal'); + const padding = resolveSpacing(block.text, 'padding'); + const horizontal = resolveSpacing(block.text, 'paddingHorizontal'); if (padding === null && horizontal === null) continue; const value = padding !== null ? padding : horizontal; if (value !== TARGET) violations.gutter.push(`${file} | ${key} | ${value}`); @@ -229,6 +264,8 @@ console.log(` 卡片间隔 ≠ ${TARGET}:${violations.gap.length}`); violations.gap.forEach((line) => console.log(` ${line}`)); console.log(` 页面留白 ≠ ${TARGET}:${violations.gutter.length}`); violations.gutter.forEach((line) => console.log(` ${line}`)); +console.log(`--- 令牌引用命中 ${tokenizedHits.length} 处(spacing.* / spacingTokens.*,审计已解析) ---`); +tokenizedHits.forEach((line) => console.log(` ${line}`)); console.log(`--- 已剔除 ${excluded.length} 项(圆 / 控件),逐条列出供复核 ---`); excluded.forEach((line) => console.log(` ${line}`)); diff --git a/src/theme/index.ts b/src/theme/index.ts index ff44c78..4ba67da 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -1,6 +1,11 @@ /** * 主题统一导出 */ +export { + spacing, + spacingTokens, +} from './spacing'; + export { colors, darkColors, diff --git a/src/theme/spacing.ts b/src/theme/spacing.ts new file mode 100644 index 0000000..dddd282 --- /dev/null +++ b/src/theme/spacing.ts @@ -0,0 +1,42 @@ +/** + * 间距设计令牌(Spacing Design Tokens):全局间距单一来源。 + * + * 对应 AGENTS.md「所有卡片圆角 8px、边距间隔一致 8px」规则,以及页面框架 / 非卡片间距的统一标尺。 + * 此前 8px 与各间距值散落为裸字面量(见《项目开发状态.md》4.18 / 4.19 / 4.20): + * - check-spacing.mjs 能拦「值跑偏」,但「改一次值要动 N 个文件」; + * - 非卡片间距(图标底块、段间距)在多个页面取 12 / 18 / 22,同角色不同值,无单一来源。 + * 本文件把间距收口为单一来源,新增样式一律从这里取。 + * 与 colors.ts 保持同一风格:先定义标尺,再叠加语义令牌,全部挂在 `spacing` 一个对象上, + * 页面只需 `import { spacing }`,写 `spacing.block` / `spacing.section` 即可。 + * + * 重要:scripts/check-spacing.mjs 顶部有一份 SPACING_TOKENS 镜像映射, + * 改动本文件的取值后必须同步更新脚本里的镜像,否则审计门禁会失准。 + */ + +/** 4 基准间距标尺(与现有使用习惯对齐:卡片 8 / 块 12 / 框架 16 / 大间距 24) */ +export const spacing = { + xs: 4, + sm: 8, // 卡片圆角 / 卡片内边距 / 卡片间隔 / 页面留白(AGENTS.md 基准) + md: 12, // 非卡片块间距标尺 + lg: 16, // 页面框架标尺(头部返回区 / 底部固定区) + xl: 24, // 大段间距 + + /** 语义令牌:与 check-spacing.mjs 四维度及页面框架一一对应,改值只动这里 */ + card: 8, // 卡片圆角 / 内边距 / 间隔 / 页面留白 + gutter: 8, // 主内容 ScrollView 留白 + framework: 16, // 页面框架(header 返回区 / footer 固定区,有意比卡片宽) + block: 12, // 非卡片块间距(图标底块、字段→提示) + section: 12, // 段标题上下间距 +} as const; + +/** + * 语义令牌别名(与 `spacing` 同名键取值一致),保留以兼容审计脚本对 `spacingTokens.x` 的解析。 + * 页面请直接用 `spacing.block` / `spacing.section`,无需引用本对象。 + */ +export const spacingTokens = { + card: spacing.card, + gutter: spacing.gutter, + framework: spacing.framework, + block: spacing.block, + section: spacing.section, +} as const; diff --git a/项目开发状态.md b/项目开发状态.md index c056d26..3d256ba 100644 --- a/项目开发状态.md +++ b/项目开发状态.md @@ -1771,6 +1771,42 @@ deep-link 只能到 P1。**临时**把初始步改成目标页截图,**看完 - 引导页三屏按钮文案是否统一为「开启体验」,见第 3 条末 —— 一行改动,等一句话。 - 4.19 第 9 条的三项(主应用 footer 16 vs 卡片 8、非卡片间距、设计令牌层)**均未动**。 +### 4.21 间距设计令牌层 + onboarding 非卡片间距收敛(2026-09-21 批次,接 4.20 第 6 条末) + +**起因**:4.18/4.19/4.20 把卡片圆角 / 内边距 / 间隔 / 留白统一到了 8px,但**间距仍全是裸字面量**,两个遗留问题: +① 改一次间距值要动 N 个文件;② 非卡片间距(图标底块、段标题上下、`tip` 提示距离)在多个引导页取 12/14/18/22,**同角色不同值,无单一来源**; +③ 更重要的是——`check-spacing.mjs` 是 `exit 1` 硬门槛,一旦把卡片改成 `spacing.card` 这类引用,原脚本读不到数字会**静默跳过该卡片**,审计网被撕开。 + +**1. 新增间距单一来源 `src/theme/spacing.ts`**(形式对齐 `colors.ts`) +- 标尺:`xs:4 / sm:8 / md:12 / lg:16 / xl:24`。 +- 语义令牌(挂在同一个 `spacing` 对象上,页面只 `import { spacing }` 即可):`card:8 / gutter:8 / framework:16 / block:12 / section:12`。 +- 从 `src/theme/index.ts` 导出。 + +**2. 扩展 `scripts/check-spacing.mjs`,让令牌化不失效**(核心防回归) +- 顶部加 `SPACING_TOKENS` 镜像映射(必须与 `spacing.ts` 取值同步,文件头已写警告)。 +- 新增 `resolveSpacing(text, prop)`:`spacing.x` / `spacingTokens.x` 都能解析成数值;未知令牌名按 null(至多误判,绝不静默通过)。 +- 卡片判定(原 `readNumber(...) === null → continue`)与 `isCircle` 改用 `resolveSpacing`——**修掉一个潜在 bug**:原逻辑对令牌化的 `borderRadius` 会读成 null 直接跳过卡片。 +- 输出新增「令牌引用命中 N 处」一行,证明令牌化后审计仍在工作。 +- **自检证明门禁存活**:注入临时文件,令牌化的 `borderRadius: spacing.lg(16)` / `padding: spacing.framework(16)` / 留白 `padding: spacing.md(12)` 三处均被抓出并 `exit 1`;合法令牌化卡片(`spacing.card`=8)通过。临时文件已删除。 + +**3. onboarding 非卡片间距令牌化 + 收敛(35 个文件)** +- 仅改写间距属性(`margin*` / `gap` / `paddingVertical` 等,**绝不碰 `fontSize`**),按值映射: + `18 → block(12)`、`22 → section(12)`、`14 → block(12)`、`10 → sm(8)`、`原 12 → block`。 +- 每个文件补 `spacing` 导入(导入路径已确认统一为 `../../../src/theme`)。 +- 收敛效果:段标题上下、提示距离、图标底块间距统一到 12,原先 14/18/22 的漂移被收口。 + +**4. 本批明确延期:B5(app/ 框架 `padding:16` → `spacing.framework`)** +- 框架 16px 是**有意比卡片宽**(页面骨架),且不被审计;改 26 个 app 文件导入路径多样、回归风险高、收益有限。 +- `spacing.framework` 单一来源已存在,后续可单独一批做;延期不阻断任何门禁。 + +**5. 验证(全绿)** +`tsc` 0 · `eslint` 0 · `npm test` 26 套件 / 365 用例 · `check:spacing` 卡片全 8px · `check:routes` · `check:affordances` · `check:icons` 全绿。 + +**6. 残留 / 待跟进** +- ① **卡片未批量迁移到 `spacing.card`**:保留字面量 `8`,审计仍能拦(迁移是纯机械替换,风险低但非必须,留待后续)。 +- ② B5 延期(见第 4 条)。 +- ③ `AGENTS.md` 第 10 行仍写「边距间隔一致,为 8px」,现非卡片块间距为 12,措辞略偏——非阻断,待文档对齐(可改成「卡片间距 8px / 块间距 12px」)。 + --- ## 五、后续增量开发建议(优先级)