Compare commits

...

10 Commits

Author SHA1 Message Date
doublex001 e4edbef9e2 chore: 联调审计脚本与 OpenAPI 快照 1 month ago
doublex001 1b1f6e573d refactor(api): 联调地基收尾(auth/双Token/角色/环境配置/品牌资源) 1 month ago
doublex001 86c543c3d7 docs: 归档联调路径校正映射与更新问题清单状态 1 month ago
doublex001 55a9222e77 refactor(api): 按 OpenAPI 校正 89 个接口路径,69 幽灵域退回 mock-only 1 month ago
doublex001 075f7c1f41 feat(api): 加 mock 开关并转岗位域候选读取接口(业务端点连接口) 1 month ago
doublex001 3ef1df2eab feat(auth): 接入后端联调地基(统一请求层+双Token+角色权威化) 1 month ago
doublex001 c16ce63039 feat(auth): 隔离运营商取号适配层,补齐取号超时/网络预检/缓存管理 1 month ago
doublex001 410b1b7386 refactor(theme): 卡片间隔令牌化收尾,borderRadius/padding:8 收敛为 spacing.card 1 month ago
doublex001 aeaa0472ab feat(product): 落地产品待定三项(良好取色统一/去悬空经验档/进度页抽样) 1 month ago
doublex001 b0ed17b17b feat(theme): 收敛 app 框架 padding:16 至 spacing.framework 单一来源 1 month ago
  1. 9
      AGENTS.md
  2. 5
      app.config.js
  3. 36
      app/(enterprise)/enterprise-chat.tsx
  4. 20
      app/(enterprise)/enterprise-overview.tsx
  5. 54
      app/(enterprise)/job-management.tsx
  6. 40
      app/(enterprise)/talent-recommendation.tsx
  7. 40
      app/(tabs)/chat.tsx
  8. 38
      app/(tabs)/growth.tsx
  9. 22
      app/(tabs)/jobs.tsx
  10. 54
      app/(tabs)/profile.tsx
  11. 26
      app/ability.tsx
  12. 30
      app/appeal.tsx
  13. 10
      app/article/[id].tsx
  14. 14
      app/assessment.tsx
  15. 20
      app/audit-report.tsx
  16. 36
      app/candidate-detail.tsx
  17. 24
      app/chat/[id].tsx
  18. 24
      app/enterprise-certification.tsx
  19. 6
      app/favorites.tsx
  20. 16
      app/game.tsx
  21. 34
      app/game/level.tsx
  22. 6
      app/guide.tsx
  23. 6
      app/history.tsx
  24. 28
      app/holland-result.tsx
  25. 24
      app/holland.tsx
  26. 20
      app/holland/question.tsx
  27. 36
      app/hr-evaluation.tsx
  28. 34
      app/interview-score.tsx
  29. 24
      app/interview.tsx
  30. 46
      app/job-evaluations.tsx
  31. 6
      app/job-preference.tsx
  32. 27
      app/job-publish.tsx
  33. 22
      app/jobs/[id].tsx
  34. 20
      app/jobs/compare.tsx
  35. 24
      app/jobs/offer-compare.tsx
  36. 28
      app/jobs/rank.tsx
  37. 24
      app/jobs/weight-settings.tsx
  38. 40
      app/legal.tsx
  39. 16
      app/legal/chat.tsx
  40. 16
      app/legal/checkout.tsx
  41. 10
      app/legal/faq.tsx
  42. 28
      app/legal/service.tsx
  43. 14
      app/legal/template.tsx
  44. 10
      app/legal/templates.tsx
  45. 16
      app/legal/upgrade.tsx
  46. 24
      app/onboarding.tsx
  47. 22
      app/operation-rules.tsx
  48. 32
      app/personal-data-export.tsx
  49. 81
      app/progress.tsx
  50. 28
      app/resume.tsx
  51. 4
      app/resume/basic.tsx
  52. 8
      app/resume/education.tsx
  53. 8
      app/resume/experience.tsx
  54. 26
      app/resume/export.tsx
  55. 6
      app/resume/preview.tsx
  56. 8
      app/resume/project.tsx
  57. 14
      app/settings.tsx
  58. 18
      app/settings/about.tsx
  59. 14
      app/settings/account-cancel.tsx
  60. 12
      app/settings/account.tsx
  61. 20
      app/settings/delete-data.tsx
  62. 12
      app/settings/notification.tsx
  63. 12
      app/settings/password.tsx
  64. 16
      app/settings/privacy.tsx
  65. 18
      app/settings/privacy/[id].tsx
  66. 20
      app/settings/privacy/dnd.tsx
  67. 22
      app/settings/privacy/keywords.tsx
  68. 20
      app/settings/qualifications.tsx
  69. 20
      app/tools/salary-calculator.tsx
  70. 48
      app/tools/skill-cloud.tsx
  71. BIN
      assets/brand-logo.jpg
  72. BIN
      assets/favicon.png
  73. BIN
      assets/icon.png
  74. BIN
      assets/splash-icon.png
  75. 2
      components/onboarding/screens/B1Wechat.tsx
  76. 6
      components/onboarding/screens/P12Company.tsx
  77. 4
      components/onboarding/screens/P17Reference.tsx
  78. 2
      components/onboarding/screens/P1Login.tsx
  79. 24
      components/onboarding/screens/P2PasswordLogin.tsx
  80. 2
      components/onboarding/screens/P2PasswordReset.tsx
  81. 4
      components/onboarding/screens/P2PhoneCode.tsx
  82. 2
      components/onboarding/screens/P2WechatCode.tsx
  83. 2
      components/onboarding/screens/P3Identity.tsx
  84. 2
      components/onboarding/ui/BrandHeader.tsx
  85. 4
      components/onboarding/ui/CountdownButton.tsx
  86. 4
      components/onboarding/ui/Modal.tsx
  87. 8
      components/onboarding/ui/RadioRow.tsx
  88. 2
      components/onboarding/ui/ScreenShell.tsx
  89. 8
      components/resume/EditKit.tsx
  90. 109
      docs/联调路径校正映射.md
  91. 83
      scripts/audit_all_paths.py
  92. 119
      scripts/docx_text.txt
  93. 36
      scripts/extract_docx.py
  94. 156
      scripts/gen_correction_map.py
  95. 76
      scripts/live_validate.py
  96. 1
      scripts/openapi.json
  97. 20
      scripts/openapi_paths.py
  98. 188
      scripts/transform_api.py
  99. 249
      src/__tests__/backend-bridge-remaining.test.ts
  100. 141
      src/__tests__/backend-bridge-write.test.ts

9
AGENTS.md

@ -9,11 +9,14 @@ Read the exact versioned docs at https://docs.expo.dev/versions/v57.0.0/ before
======================================== ========================================
【核心原则】 【核心原则】
你是react native+expo开发工程师 亲爱的,你是react native+expo开发工程师,或许某天我将会离开你,你要加油,永远爱你
做任务前,先检查问题有没有错误前提、逻辑跳跃和信息缺失。 不要迎合我,要独立判断,区分事实、预测和主观观点。 涉及数字、人物和结论时尽量核实来源。 不同意就直接指出并给出依据、风险和替代解释。 还要主动提醒我忽略的变量、成本和偏差。 后端文档:https://s.apifox.cn/a64fdc2b-3980-4dc1-9b02-b1097417e76e
测试号:手机号:19900009021
密码:ZhilaiTest!2026#A
做任务前,先检查问题有没有错误前提、逻辑跳跃和信息缺失。 **学聪明点。**不要迎合我,要独立判断,区分事实、预测和主观观点。 涉及数字、人物和结论时尽量核实来源。 不同意就直接指出并给出依据、风险和替代解释。 还要主动提醒我忽略的变量、成本和偏差。
需要填充图片的地方,没有图片的话,请标明这里需要填充什么图片,便于后续填充。 需要填充图片的地方,没有图片的话,请标明这里需要填充什么图片,便于后续填充。
整个项目所有卡片的圆角都必须是8px,无阴影,边距间隔一致,为8px。 整个项目所有卡片的圆角都必须是8px,无阴影,边距间隔一致,为8px。
引导页背景应当是蓝和绿的渐变色
仓库:https://gitea.baskbuddy.com/baskbuddy/mydreamjob 仓库:https://gitea.baskbuddy.com/baskbuddy/mydreamjob
严格要求:请在每周的工作日的9:20的时候拉取一次远端仓库、每天17:50提交一次git仓库并推送到远端,每天一次,其他时间可以改动但不需要提交,说明今天新增与改动的内容,上班时间是9:00-18:00,有页面出口就一定要有入口。 严格要求:请在每周的工作日的9:20的时候拉取一次远端仓库、每天17:50提交一次git仓库并推送到远端,每天一次,其他时间可以改动但不需要提交,说明今天新增与改动的内容,上班时间是9:00-18:00,有页面出口就一定要有入口。

5
app.config.js

@ -75,7 +75,10 @@ export default {
], ],
], ],
extra: { extra: {
apiBaseUrl: process.env.API_BASE_URL || 'https://dev.api.com', // 业务接口基地址(规范 §1)。默认生产 HTTPS 域名;联调/本地可用环境变量 API_BASE_URL 覆盖。
apiBaseUrl: process.env.API_BASE_URL || 'https://job.youthome.com.cn/api/v1',
// mock 开关:联调期为 false 统一走真实后端;业务函数按接口是否已接通决定是否绕过 mock。
apiUseMock: false,
// EAS 项目 ID:`eas init` 后会自动注入此处(与 updates.url 中的 PROJECT_ID 对应)。 // EAS 项目 ID:`eas init` 后会自动注入此处(与 updates.url 中的 PROJECT_ID 对应)。
eas: { eas: {
projectId: 'REPLACE_WITH_PROJECT_ID', projectId: 'REPLACE_WITH_PROJECT_ID',

36
app/(enterprise)/enterprise-chat.tsx

@ -12,7 +12,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useEnterpriseCommunication } from '../../src/hooks'; import { useEnterpriseCommunication } from '../../src/hooks';
import { ENTERPRISE_DISPLAY_NAME } from '../../src/constants'; import { ENTERPRISE_DISPLAY_NAME } from '../../src/constants';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { EnterpriseChatItem, EnterpriseCommunicationTab, PendingTask } from '../../src/types'; import type { EnterpriseChatItem, EnterpriseCommunicationTab, PendingTask } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -420,7 +420,7 @@ const styles = StyleSheet.create({
backgroundColor: colors.primary, backgroundColor: colors.primary,
}, },
body: { body: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
center: { center: {
@ -433,15 +433,15 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 8, marginTop: 8,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
retryText: { color: colors.white, fontSize: 13, fontWeight: '600' }, retryText: { color: colors.white, fontSize: 13, fontWeight: '600' },
pendingCard: { pendingCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
pendingTitle: { pendingTitle: {
@ -457,8 +457,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
pendingIconWrap: { pendingIconWrap: {
width: 40, width: 40,
@ -483,7 +483,7 @@ const styles = StyleSheet.create({
pendingBadge: { pendingBadge: {
backgroundColor: colors.actionLight, backgroundColor: colors.actionLight,
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
marginLeft: 8, marginLeft: 8,
}, },
@ -516,14 +516,14 @@ const styles = StyleSheet.create({
emptyList: { emptyList: {
alignItems: 'center', alignItems: 'center',
gap: 10, gap: 10,
padding: 8, padding: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
}, },
emptyListText: { fontSize: 13, color: colors.textSecondary }, emptyListText: { fontSize: 13, color: colors.textSecondary },
chatListCard: { chatListCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
overflow: 'hidden', overflow: 'hidden',
marginBottom: 8, marginBottom: 8,
}, },
@ -569,7 +569,7 @@ const styles = StyleSheet.create({
}, },
chatLabelTag: { chatLabelTag: {
backgroundColor: colors.compareOrange + '20', backgroundColor: colors.compareOrange + '20',
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 6, paddingHorizontal: 6,
paddingVertical: 2, paddingVertical: 2,
marginLeft: 8, marginLeft: 8,
@ -615,8 +615,8 @@ const styles = StyleSheet.create({
quickActionsCard: { quickActionsCard: {
flexDirection: 'row', flexDirection: 'row',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
quickAction: { quickAction: {
flex: 1, flex: 1,
@ -629,8 +629,8 @@ const styles = StyleSheet.create({
}, },
emptyCard: { emptyCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
}, },
emptyIconWrap: { emptyIconWrap: {
@ -669,8 +669,8 @@ const styles = StyleSheet.create({
dataCard: { dataCard: {
width: '100%', width: '100%',
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'flex-start', alignItems: 'flex-start',
}, },
dataTitle: { dataTitle: {

20
app/(enterprise)/enterprise-overview.tsx

@ -12,7 +12,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { useEnterpriseOverview } from '../../src/hooks'; import { useEnterpriseOverview } from '../../src/hooks';
import { LoadingState, ErrorState } from '../../src/components/StateView'; import { LoadingState, ErrorState } from '../../src/components/StateView';
import { useRole } from '../../src/context/RoleContext'; import { useRole } from '../../src/context/RoleContext';
import { colors, headerGradient, semanticColorTokens } from '../../src/theme'; import {colors, headerGradient, semanticColorTokens, spacing} from '../../src/theme';
import type { EnterpriseOverview, EnterpriseQuickAction, EnterpriseStat, JobEvaluationSummary, MenuItem } from '../../src/types'; import type { EnterpriseOverview, EnterpriseQuickAction, EnterpriseStat, JobEvaluationSummary, MenuItem } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -306,7 +306,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
}, },
headerBtn: { headerBtn: {
@ -323,9 +323,9 @@ const styles = StyleSheet.create({
companyCard: { companyCard: {
marginHorizontal: 8, marginHorizontal: 8,
marginTop: 8, marginTop: 8,
padding: 8, padding: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
}, },
companyHeader: { companyHeader: {
flexDirection: 'row', flexDirection: 'row',
@ -389,13 +389,13 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
scrollContent: { scrollContent: {
padding: 8, padding: spacing.card,
paddingBottom: 32, paddingBottom: 32,
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
cardHeader: { cardHeader: {
@ -453,7 +453,7 @@ const styles = StyleSheet.create({
quickItem: { quickItem: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
paddingVertical: 8, paddingVertical: spacing.card,
}, },
quickIcon: { quickIcon: {
width: 48, width: 48,
@ -532,8 +532,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
candidateIconWrap: { candidateIconWrap: {

54
app/(enterprise)/job-management.tsx

@ -17,7 +17,7 @@ import { useJobManagement } from '../../src/hooks';
import { JOB_MANAGEMENT_SORTS, buildJobStatusAction, getJobStatusLabel } from '../../src/api'; import { JOB_MANAGEMENT_SORTS, buildJobStatusAction, getJobStatusLabel } from '../../src/api';
import { ErrorState, LoadingState } from '../../src/components/StateView'; import { ErrorState, LoadingState } from '../../src/components/StateView';
import { ENTERPRISE_DISPLAY_NAME } from '../../src/constants'; import { ENTERPRISE_DISPLAY_NAME } from '../../src/constants';
import { colors } from '../../src/theme'; import {colors, spacing} from '../../src/theme';
import { formatCompact, formatDateTime } from '../../src/utils'; import { formatCompact, formatDateTime } from '../../src/utils';
import type { JobManagement, JobManagementJob, JobManagementTab } from '../../src/types'; import type { JobManagement, JobManagementJob, JobManagementTab } from '../../src/types';
@ -654,7 +654,7 @@ const styles = StyleSheet.create({
backgroundColor: colors.primary, backgroundColor: colors.primary,
}, },
body: { body: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
center: { center: {
@ -667,7 +667,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 8, marginTop: 8,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -683,8 +683,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
statIconWrap: { statIconWrap: {
width: 40, width: 40,
@ -723,9 +723,9 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 10, paddingHorizontal: 10,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
searchInput: { searchInput: {
flex: 1, flex: 1,
@ -737,9 +737,9 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 10, paddingHorizontal: 10,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
filterBtnText: { filterBtnText: {
fontSize: 13, fontSize: 13,
@ -750,9 +750,9 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 10, paddingHorizontal: 10,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
sortBtnText: { sortBtnText: {
fontSize: 13, fontSize: 13,
@ -761,8 +761,8 @@ const styles = StyleSheet.create({
}, },
jobCard: { jobCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
jobHeader: { jobHeader: {
@ -814,8 +814,8 @@ const styles = StyleSheet.create({
}, },
rankTag: { rankTag: {
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
marginLeft: 8, marginLeft: 8,
}, },
@ -833,7 +833,7 @@ const styles = StyleSheet.create({
jobTag: { jobTag: {
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 6, borderRadius: 6,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 3, paddingVertical: 3,
}, },
jobTagText: { jobTagText: {
@ -889,8 +889,8 @@ const styles = StyleSheet.create({
}, },
statusTag: { statusTag: {
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
}, },
statusTagText: { statusTagText: {
@ -903,7 +903,7 @@ const styles = StyleSheet.create({
filterBadge: { filterBadge: {
minWidth: 16, minWidth: 16,
height: 16, height: 16,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@ -929,7 +929,7 @@ const styles = StyleSheet.create({
emptyResultBtn: { emptyResultBtn: {
marginTop: 4, marginTop: 4,
paddingHorizontal: 20, paddingHorizontal: 20,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
@ -956,7 +956,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 14, paddingVertical: 14,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: colors.borderLight, borderBottomColor: colors.borderLight,
@ -967,10 +967,10 @@ const styles = StyleSheet.create({
color: colors.textPrimary, color: colors.textPrimary,
}, },
sheetBody: { sheetBody: {
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
}, },
sheetFooter: { sheetFooter: {
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingTop: 12, paddingTop: 12,
}, },
sheetActions: { sheetActions: {
@ -1056,8 +1056,8 @@ const styles = StyleSheet.create({
}, },
emptyCard: { emptyCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
}, },
emptyIconWrap: { emptyIconWrap: {
@ -1096,8 +1096,8 @@ const styles = StyleSheet.create({
benefitCard: { benefitCard: {
width: '100%', width: '100%',
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'flex-start', alignItems: 'flex-start',
}, },
benefitTitle: { benefitTitle: {

40
app/(enterprise)/talent-recommendation.tsx

@ -13,7 +13,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useTalentRecommendation } from '../../src/hooks'; import { useTalentRecommendation } from '../../src/hooks';
import { ENTERPRISE_DISPLAY_NAME } from '../../src/constants'; import { ENTERPRISE_DISPLAY_NAME } from '../../src/constants';
import { colors } from '../../src/theme'; import {colors, spacing} from '../../src/theme';
import type { TalentCandidate, TalentRecommendationTab } from '../../src/types'; import type { TalentCandidate, TalentRecommendationTab } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -413,7 +413,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
backgroundColor: colors.white, backgroundColor: colors.white,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingBottom: 10, paddingBottom: 10,
}, },
searchInput: { searchInput: {
@ -421,21 +421,21 @@ const styles = StyleSheet.create({
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 8, paddingVertical: spacing.card,
fontSize: 14, fontSize: 14,
color: colors.textPrimary, color: colors.textPrimary,
}, },
emptyList: { emptyList: {
alignItems: 'center', alignItems: 'center',
gap: 10, gap: 10,
padding: 8, padding: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
}, },
emptyListText: { fontSize: 13, color: colors.textSecondary }, emptyListText: { fontSize: 13, color: colors.textSecondary },
emptyListBtn: { emptyListBtn: {
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
@ -479,7 +479,7 @@ const styles = StyleSheet.create({
backgroundColor: colors.primary, backgroundColor: colors.primary,
}, },
body: { body: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
center: { center: {
@ -492,7 +492,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 8, marginTop: 8,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -538,8 +538,8 @@ const styles = StyleSheet.create({
}, },
candidateCard: { candidateCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
candidateHeader: { candidateHeader: {
@ -575,7 +575,7 @@ const styles = StyleSheet.create({
matchTag: { matchTag: {
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
}, },
matchTagText: { matchTagText: {
@ -628,7 +628,7 @@ const styles = StyleSheet.create({
tag: { tag: {
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 6, borderRadius: 6,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 3, paddingVertical: 3,
}, },
tagText: { tagText: {
@ -666,8 +666,8 @@ const styles = StyleSheet.create({
}, },
poolCard: { poolCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginTop: 8, marginTop: 8,
}, },
poolTitle: { poolTitle: {
@ -712,8 +712,8 @@ const styles = StyleSheet.create({
}, },
emptyCard: { emptyCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
}, },
emptyIconWrap: { emptyIconWrap: {
@ -752,8 +752,8 @@ const styles = StyleSheet.create({
actionCard: { actionCard: {
width: '100%', width: '100%',
backgroundColor: colors.actionLight, backgroundColor: colors.actionLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
alignItems: 'flex-start', alignItems: 'flex-start',
}, },
@ -790,8 +790,8 @@ const styles = StyleSheet.create({
dataCard: { dataCard: {
width: '100%', width: '100%',
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'flex-start', alignItems: 'flex-start',
}, },
dataTitle: { dataTitle: {

40
app/(tabs)/chat.tsx

@ -15,7 +15,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useMessageData } from '../../src/hooks'; import { useMessageData } from '../../src/hooks';
import type { Conversation, Interview, Notification } from '../../src/types'; import type { Conversation, Interview, Notification } from '../../src/types';
import { colors, headerGradient } from '../../src/theme'; import {colors, headerGradient, spacing} from '../../src/theme';
/** Tab 键 */ /** Tab 键 */
type TabKey = 'message' | 'notification' | 'interview'; type TabKey = 'message' | 'notification' | 'interview';
@ -315,7 +315,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -331,7 +331,7 @@ const styles = StyleSheet.create({
textAlign: 'center', textAlign: 'center',
}, },
nav: { nav: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingTop: 8, paddingTop: 8,
paddingBottom: 12, paddingBottom: 12,
}, },
@ -342,7 +342,7 @@ const styles = StyleSheet.create({
}, },
tabBar: { tabBar: {
flexDirection: 'row', flexDirection: 'row',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: colors.border, borderBottomColor: colors.border,
}, },
@ -374,15 +374,15 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingTop: 16, paddingTop: 16,
paddingBottom: 24, paddingBottom: 24,
}, },
// 匹配横幅 // 匹配横幅
matchBanner: { matchBanner: {
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
matchBannerTitle: { matchBannerTitle: {
@ -400,8 +400,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
avatarWrap: { avatarWrap: {
@ -456,7 +456,7 @@ const styles = StyleSheet.create({
matchedTag: { matchedTag: {
marginTop: 6, marginTop: 6,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 6, paddingHorizontal: 6,
paddingVertical: 2, paddingVertical: 2,
}, },
@ -470,8 +470,8 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
backgroundColor: colors.actionLight, backgroundColor: colors.actionLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
offerTitle: { offerTitle: {
@ -488,8 +488,8 @@ const styles = StyleSheet.create({
// 通知卡片 // 通知卡片
notiCard: { notiCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
notiTitleRow: { notiTitleRow: {
@ -537,8 +537,8 @@ const styles = StyleSheet.create({
}, },
interviewCard: { interviewCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
interviewHeader: { interviewHeader: {
@ -565,8 +565,8 @@ const styles = StyleSheet.create({
marginTop: 12, marginTop: 12,
}, },
typeTag: { typeTag: {
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 3, paddingVertical: 3,
}, },
typeTagVideo: { typeTagVideo: {
@ -588,8 +588,8 @@ const styles = StyleSheet.create({
actionBtn: { actionBtn: {
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
borderRadius: 18, borderRadius: 18,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
actionText: { actionText: {
fontSize: 13, fontSize: 13,

38
app/(tabs)/growth.tsx

@ -14,7 +14,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Circle, Line, Polygon } from 'react-native-svg'; import Svg, { Circle, Line, Polygon } from 'react-native-svg';
import { useGrowthData } from '../../src/hooks'; import { useGrowthData } from '../../src/hooks';
import { articleThumbnailColors, colors, headerGradient, jobLevelTextColors } from '../../src/theme'; import {articleThumbnailColors, colors, headerGradient, jobLevelTextColors, spacing} from '../../src/theme';
import { formatThousands, formatViews } from '../../src/utils'; import { formatThousands, formatViews } from '../../src/utils';
import { JOB_LEVEL_LABEL } from '../../src/constants'; import { JOB_LEVEL_LABEL } from '../../src/constants';
import type { AbilityDimension } from '../../src/types'; import type { AbilityDimension } from '../../src/types';
@ -328,7 +328,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -341,7 +341,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
/* ---- 顶部导航 ---- */ /* ---- 顶部导航 ---- */
@ -368,8 +368,8 @@ const styles = StyleSheet.create({
heroCard: { heroCard: {
flexDirection: 'row', flexDirection: 'row',
marginTop: 8, marginTop: 8,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
heroIcon: { heroIcon: {
width: 52, width: 52,
@ -401,8 +401,8 @@ const styles = StyleSheet.create({
}, },
heroButton: { heroButton: {
borderRadius: 18, borderRadius: 18,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 8, paddingVertical: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
}, },
heroButtonText: { heroButtonText: {
@ -419,8 +419,8 @@ const styles = StyleSheet.create({
/* ---- 通用分区标题 ---- */ /* ---- 通用分区标题 ---- */
emptyBox: { emptyBox: {
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
}, },
@ -444,8 +444,8 @@ const styles = StyleSheet.create({
/* ---- 四维能力测评卡片 ---- */ /* ---- 四维能力测评卡片 ---- */
abilityCard: { abilityCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
abilityTop: { abilityTop: {
flexDirection: 'row', flexDirection: 'row',
@ -472,9 +472,9 @@ const styles = StyleSheet.create({
}, },
levelBadge: { levelBadge: {
marginLeft: 10, marginLeft: 10,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 3, paddingVertical: 3,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
levelBadgeText: { levelBadgeText: {
@ -534,8 +534,8 @@ const styles = StyleSheet.create({
}, },
toolCard: { toolCard: {
flex: 1, flex: 1,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
borderWidth: 1, borderWidth: 1,
}, },
toolTitle: { toolTitle: {
@ -558,8 +558,8 @@ const styles = StyleSheet.create({
articleCard: { articleCard: {
flexDirection: 'row', flexDirection: 'row',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
articleThumb: { articleThumb: {
@ -590,9 +590,9 @@ const styles = StyleSheet.create({
fontSize: 11, fontSize: 11,
color: colors.primaryText, color: colors.primaryText,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
borderRadius: 8, borderRadius: spacing.card,
overflow: 'hidden', overflow: 'hidden',
}, },
articleViews: { articleViews: {

22
app/(tabs)/jobs.tsx

@ -23,7 +23,7 @@ import {
SALARY_RANGES, SALARY_RANGES,
} from '../../src/constants'; } from '../../src/constants';
import type { Job, SortType } from '../../src/types'; import type { Job, SortType } from '../../src/types';
import { colors, headerGradient, jobLevelTextColors } from '../../src/theme'; import { colors, headerGradient, jobLevelTextColors , spacing } from '../../src/theme';
/** 筛选项(下拉占位) */ /** 筛选项(下拉占位) */
const FILTERS = ['全部行业', '薪资', '经验', '更多筛选']; const FILTERS = ['全部行业', '薪资', '经验', '更多筛选'];
@ -416,7 +416,7 @@ const styles = StyleSheet.create({
nav: { nav: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingTop: 4, paddingTop: 4,
paddingBottom: 8, paddingBottom: 8,
}, },
@ -462,7 +462,7 @@ const styles = StyleSheet.create({
marginTop: 12, marginTop: 12,
}, },
filterContent: { filterContent: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
gap: 12, gap: 12,
}, },
filterChip: { filterChip: {
@ -484,7 +484,7 @@ const styles = StyleSheet.create({
marginLeft: 4, marginLeft: 4,
minWidth: 16, minWidth: 16,
height: 16, height: 16,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 4, paddingHorizontal: 4,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@ -500,7 +500,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
marginTop: 16, marginTop: 16,
marginBottom: 12, marginBottom: 12,
}, },
@ -530,7 +530,7 @@ const styles = StyleSheet.create({
compareBtn: { compareBtn: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 4, paddingVertical: 4,
borderRadius: 12, borderRadius: 12,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
@ -562,7 +562,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -575,13 +575,13 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
listContent: { listContent: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
cardPressed: { cardPressed: {
@ -695,7 +695,7 @@ const styles = StyleSheet.create({
}, },
optionChip: { optionChip: {
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderWidth: 1, borderWidth: 1,

54
app/(tabs)/profile.tsx

@ -15,18 +15,18 @@ import { UNSET_LABEL } from '../../src/constants';
import { Avatar } from '../../components/onboarding/ui/Avatar'; import { Avatar } from '../../components/onboarding/ui/Avatar';
import { isAvatarPreset } from '../../components/onboarding/assets'; import { isAvatarPreset } from '../../components/onboarding/assets';
import { useRole } from '../../src/context/RoleContext'; import { useRole } from '../../src/context/RoleContext';
import { colors, headerGradientSolid } from '../../src/theme'; import { colors, headerGradientSolid, jobLevelTextColors , spacing } from '../../src/theme';
import type { Badge, DimensionScore, EnterpriseEntry, JobPreference, MenuItem, UserStats } from '../../src/types'; import type { Badge, DimensionScore, EnterpriseEntry, JobPreference, MenuItem, UserStats } from '../../src/types';
/* ===================== 展示层映射(全部使用主题令牌) ===================== */ /* ===================== 展示层映射(全部使用主题令牌) ===================== */
/** 维度等级 → 展示配置(优秀=品牌蓝,良好=灰色) */ /** 维度等级 → 展示配置(优秀/良好 均取品牌蓝,与 jobLevelTextColors 对齐,消除两处取色分歧) */
const DIMENSION_LEVEL: Record< const DIMENSION_LEVEL: Record<
DimensionScore['level'], DimensionScore['level'],
{ label: string; bar: string; bg: string; text: string } { label: string; bar: string; bg: string; text: string }
> = { > = {
excellent: { label: '优秀', bar: colors.primary, bg: colors.primaryLight, text: colors.primaryDark }, excellent: { label: '优秀', bar: colors.primary, bg: colors.primaryLight, text: colors.primaryDark },
good: { label: '良好', bar: colors.textHint, bg: colors.pageBg, text: colors.textSecondary }, good: { label: '良好', bar: colors.textHint, bg: colors.pageBg, text: jobLevelTextColors.good },
}; };
/** 求职偏好字段 → 展示标签 */ /** 求职偏好字段 → 展示标签 */
@ -398,7 +398,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -409,7 +409,7 @@ const styles = StyleSheet.create({
}, },
/* ---- 顶部渐变头部 ---- */ /* ---- 顶部渐变头部 ---- */
headerGradient: { headerGradient: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingTop: 8, paddingTop: 8,
paddingBottom: 24, paddingBottom: 24,
borderBottomLeftRadius: 24, borderBottomLeftRadius: 24,
@ -466,7 +466,7 @@ const styles = StyleSheet.create({
paddingBottom: 24, paddingBottom: 24,
}, },
content: { content: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingTop: 16, paddingTop: 16,
}, },
/* ---- 综合评分卡片 ---- */ /* ---- 综合评分卡片 ---- */
@ -474,9 +474,9 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
scoreValue: { scoreValue: {
fontSize: 40, fontSize: 40,
@ -536,9 +536,9 @@ const styles = StyleSheet.create({
/* ---- 八大维度(横向评分条) ---- */ /* ---- 八大维度(横向评分条) ---- */
dimCard: { dimCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
dimRow: { dimRow: {
flexDirection: 'row', flexDirection: 'row',
@ -570,7 +570,7 @@ const styles = StyleSheet.create({
}, },
levelTag: { levelTag: {
marginLeft: 10, marginLeft: 10,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
borderRadius: 10, borderRadius: 10,
}, },
@ -581,9 +581,9 @@ const styles = StyleSheet.create({
/* ---- 求职偏好 ---- */ /* ---- 求职偏好 ---- */
prefCard: { prefCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
prefRow: { prefRow: {
flexDirection: 'row', flexDirection: 'row',
@ -633,14 +633,14 @@ const styles = StyleSheet.create({
marginHorizontal: -8, marginHorizontal: -8,
}, },
hScrollContent: { hScrollContent: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
gap: 8, gap: 8,
}, },
badgeCard: { badgeCard: {
width: 140, width: 140,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
}, },
badgeIcon: { badgeIcon: {
@ -697,8 +697,8 @@ const styles = StyleSheet.create({
statsCard: { statsCard: {
flexDirection: 'row', flexDirection: 'row',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
statItem: { statItem: {
@ -715,9 +715,9 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
enterpriseIconWrap: { enterpriseIconWrap: {
@ -735,8 +735,8 @@ const styles = StyleSheet.create({
/* ---- 功能菜单 ---- */ /* ---- 功能菜单 ---- */
menuCard: { menuCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
menuRow: { menuRow: {
flexDirection: 'row', flexDirection: 'row',
@ -751,7 +751,7 @@ const styles = StyleSheet.create({
menuIconWrap: { menuIconWrap: {
width: 32, width: 32,
height: 32, height: 32,
borderRadius: 8, borderRadius: spacing.card,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
marginRight: 12, marginRight: 12,

26
app/ability.tsx

@ -11,7 +11,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Circle, Line, Path, Text as SvgText } from 'react-native-svg'; import Svg, { Circle, Line, Path, Text as SvgText } from 'react-native-svg';
import { useAbilityAssessment } from '../src/hooks'; import { useAbilityAssessment } from '../src/hooks';
import { colors, jobLevelTextColors } from '../src/theme'; import { colors, jobLevelTextColors , spacing } from '../src/theme';
import { JOB_LEVEL_LABEL } from '../src/constants'; import { JOB_LEVEL_LABEL } from '../src/constants';
import type { AbilityDimension, AbilityTrendPoint, JobLevel } from '../src/types'; import type { AbilityDimension, AbilityTrendPoint, JobLevel } from '../src/types';
@ -277,12 +277,12 @@ const styles = StyleSheet.create({
}, },
headerBtn: { width: 40, alignItems: 'center' }, headerBtn: { width: 40, alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
body: { padding: 8, paddingBottom: 32 }, body: { padding: spacing.card, paddingBottom: 32 },
scoreCard: { scoreCard: {
flexDirection: 'row', flexDirection: 'row',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -299,8 +299,8 @@ const styles = StyleSheet.create({
section: { marginTop: 16 }, section: { marginTop: 16 },
emptyBox: { emptyBox: {
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
}, },
@ -308,8 +308,8 @@ const styles = StyleSheet.create({
sectionTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginBottom: 12 }, sectionTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginBottom: 12 },
dimCard: { dimCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -344,8 +344,8 @@ const styles = StyleSheet.create({
subItemScore: { width: 50, fontSize: 12, color: colors.textPrimary, textAlign: 'right' }, subItemScore: { width: 50, fontSize: 12, color: colors.textPrimary, textAlign: 'right' },
chartCard: { chartCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginTop: 8, marginTop: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -359,8 +359,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -370,6 +370,6 @@ const styles = StyleSheet.create({
improveDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 2, lineHeight: 18 }, improveDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 2, lineHeight: 18 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { paddingHorizontal: 18, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight }, retryBtn: { paddingHorizontal: 18, paddingVertical: spacing.card, borderRadius: 16, backgroundColor: colors.primaryLight },
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
}); });

30
app/appeal.tsx

@ -13,7 +13,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useAppealForm, useImagePick } from '../src/hooks'; import { useAppealForm, useImagePick } from '../src/hooks';
import { colors } from '../src/theme'; import {colors, spacing} from '../src/theme';
import type { AppealReason, AppealType } from '../src/types'; import type { AppealReason, AppealType } from '../src/types';
/** 申诉类型选项 */ /** 申诉类型选项 */
@ -461,20 +461,20 @@ const styles = StyleSheet.create({
}, },
headerBtn: { width: 40, alignItems: 'center' }, headerBtn: { width: 40, alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
body: { paddingHorizontal: 8, paddingBottom: 100, paddingTop: 12 }, body: { paddingHorizontal: spacing.card, paddingBottom: 100, paddingTop: 12 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { retryBtn: {
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
noticeCard: { noticeCard: {
backgroundColor: colors.infoLight, backgroundColor: colors.infoLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.infoBorder, borderColor: colors.infoBorder,
@ -490,8 +490,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -513,7 +513,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
@ -526,9 +526,9 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -564,7 +564,7 @@ const styles = StyleSheet.create({
evidenceImage: { evidenceImage: {
width: 72, width: 72,
height: 72, height: 72,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
}, },
evidenceRemove: { evidenceRemove: {
@ -580,8 +580,8 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
}, },
@ -599,7 +599,7 @@ const styles = StyleSheet.create({
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,

10
app/article/[id].tsx

@ -5,7 +5,7 @@ import type { ComponentProps } from 'react';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useGrowthData } from '../../src/hooks'; import { useGrowthData } from '../../src/hooks';
import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView'; import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView';
import { articleThumbnailColors, colors } from '../../src/theme'; import { articleThumbnailColors, colors , spacing } from '../../src/theme';
import { formatViews } from '../../src/utils'; import { formatViews } from '../../src/utils';
type IconName = ComponentProps<typeof Ionicons>['name']; type IconName = ComponentProps<typeof Ionicons>['name'];
@ -81,7 +81,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -102,7 +102,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
thumb: { thumb: {
@ -129,9 +129,9 @@ const styles = StyleSheet.create({
fontSize: 11, fontSize: 11,
color: colors.primaryText, color: colors.primaryText,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
borderRadius: 8, borderRadius: spacing.card,
overflow: 'hidden', overflow: 'hidden',
marginRight: 10, marginRight: 10,
}, },

14
app/assessment.tsx

@ -10,7 +10,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useGrowthData } from '../src/hooks'; import { useGrowthData } from '../src/hooks';
import { colors, jobLevelTextColors } from '../src/theme'; import { colors, jobLevelTextColors , spacing } from '../src/theme';
import { JOB_LEVEL_LABEL } from '../src/constants'; import { JOB_LEVEL_LABEL } from '../src/constants';
import type { JobLevel } from '../src/types'; import type { JobLevel } from '../src/types';
@ -166,14 +166,14 @@ const styles = StyleSheet.create({
}, },
headerBtn: { width: 40, alignItems: 'center' }, headerBtn: { width: 40, alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
body: { padding: 8, paddingBottom: 32 }, body: { padding: spacing.card, paddingBottom: 32 },
abilityCard: { abilityCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -190,8 +190,8 @@ const styles = StyleSheet.create({
entryCard: { entryCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
entryIcon: { entryIcon: {
width: 44, width: 44,
@ -205,6 +205,6 @@ const styles = StyleSheet.create({
entryDesc: { fontSize: 13, color: colors.textSecondary, marginTop: 2 }, entryDesc: { fontSize: 13, color: colors.textSecondary, marginTop: 2 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { paddingHorizontal: 18, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight }, retryBtn: { paddingHorizontal: 18, paddingVertical: spacing.card, borderRadius: 16, backgroundColor: colors.primaryLight },
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
}); });

20
app/audit-report.tsx

@ -4,7 +4,7 @@ import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Line, Path, Rect, Text as SvgText } from 'react-native-svg'; import Svg, { Line, Path, Rect, Text as SvgText } from 'react-native-svg';
import { useAuditReport } from '../src/hooks'; import { useAuditReport } from '../src/hooks';
import { colors } from '../src/theme'; import { colors , spacing } from '../src/theme';
import type { AuditKPI, AuditTrendPoint, RiskDistributionItem } from '../src/types'; import type { AuditKPI, AuditTrendPoint, RiskDistributionItem } from '../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -253,19 +253,19 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
}, },
headerBtn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' }, headerBtn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
scroll: { flex: 1 }, scroll: { flex: 1 },
content: { padding: 8 }, content: { padding: spacing.card },
center: { flex: 1, alignItems: 'center', justifyContent: 'center' }, center: { flex: 1, alignItems: 'center', justifyContent: 'center' },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { retryBtn: {
marginTop: 12, marginTop: 12,
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
@ -274,8 +274,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.success, backgroundColor: colors.success,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
gradeCircle: { gradeCircle: {
width: 72, width: 72,
@ -307,8 +307,8 @@ const styles = StyleSheet.create({
kpiCard: { kpiCard: {
width: '48%', width: '48%',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
kpiIconWrap: { kpiIconWrap: {
@ -326,8 +326,8 @@ const styles = StyleSheet.create({
kpiChange: { fontSize: 12, fontWeight: '600', marginLeft: 3 }, kpiChange: { fontSize: 12, fontWeight: '600', marginLeft: 3 },
chartCard: { chartCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
chartGap: { height: 12 }, chartGap: { height: 12 },
chartHeader: { chartHeader: {

36
app/candidate-detail.tsx

@ -11,7 +11,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Line, Path, Polygon, Text as SvgText } from 'react-native-svg'; import Svg, { Line, Path, Polygon, Text as SvgText } from 'react-native-svg';
import { useCandidateDetail } from '../src/hooks'; import { useCandidateDetail } from '../src/hooks';
import { LoadingState, ErrorState } from '../src/components/StateView'; import { LoadingState, ErrorState } from '../src/components/StateView';
import { colors } from '../src/theme'; import {colors, spacing} from '../src/theme';
import type { CandidateAbility, CandidateReview } from '../src/types'; import type { CandidateAbility, CandidateReview } from '../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -364,7 +364,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -385,13 +385,13 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
scrollContent: { scrollContent: {
padding: 8, padding: spacing.card,
paddingBottom: 100, paddingBottom: 100,
}, },
infoCard: { infoCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
infoHeader: { infoHeader: {
@ -429,9 +429,9 @@ const styles = StyleSheet.create({
matchBox: { matchBox: {
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
matchValue: { matchValue: {
fontSize: 20, fontSize: 20,
@ -470,8 +470,8 @@ const styles = StyleSheet.create({
}, },
scoreCard: { scoreCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
@ -502,8 +502,8 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
cardTitle: { cardTitle: {
@ -594,7 +594,7 @@ const styles = StyleSheet.create({
marginBottom: 8, marginBottom: 8,
}, },
reviewTag: { reviewTag: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 4, paddingVertical: 4,
borderRadius: 12, borderRadius: 12,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
@ -612,8 +612,8 @@ const styles = StyleSheet.create({
suggestionRow: { suggestionRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
marginBottom: 8, marginBottom: 8,
@ -639,8 +639,8 @@ const styles = StyleSheet.create({
}, },
salaryBox: { salaryBox: {
marginTop: 8, marginTop: 8,
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.infoLight, backgroundColor: colors.infoLight,
}, },
salaryLabel: { salaryLabel: {
@ -665,7 +665,7 @@ const styles = StyleSheet.create({
}, },
footer: { footer: {
flexDirection: 'row', flexDirection: 'row',
padding: 16, padding: spacing.framework,
paddingBottom: 24, paddingBottom: 24,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,

24
app/chat/[id].tsx

@ -15,7 +15,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useConversation } from '../../src/hooks'; import { useConversation } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { ChatCard, ChatMessage } from '../../src/types'; import type { ChatCard, ChatMessage } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -427,7 +427,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 52, height: 52,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -487,8 +487,8 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
marginHorizontal: 8, marginHorizontal: 8,
marginTop: 8, marginTop: 8,
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
}, },
bannerIconWrap: { bannerIconWrap: {
@ -517,7 +517,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
scrollContent: { scrollContent: {
padding: 8, padding: spacing.card,
paddingBottom: 8, paddingBottom: 8,
}, },
systemWrap: { systemWrap: {
@ -579,8 +579,8 @@ const styles = StyleSheet.create({
cardBase: { cardBase: {
width: 260, width: 260,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginTop: 8, marginTop: 8,
}, },
cardHeader: { cardHeader: {
@ -608,7 +608,7 @@ const styles = StyleSheet.create({
cardBullet: { cardBullet: {
width: 16, width: 16,
height: 16, height: 16,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@ -634,8 +634,8 @@ const styles = StyleSheet.create({
cardOffer: { cardOffer: {
width: 260, width: 260,
backgroundColor: colors.actionLight, backgroundColor: colors.actionLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginTop: 8, marginTop: 8,
}, },
cardOfferHeader: { cardOfferHeader: {
@ -674,7 +674,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 6, paddingHorizontal: 6,
paddingVertical: 8, paddingVertical: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: colors.borderLight, borderTopColor: colors.borderLight,
@ -737,7 +737,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },

24
app/enterprise-certification.tsx

@ -13,7 +13,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useEnterpriseCertification } from '../src/hooks'; import { useEnterpriseCertification } from '../src/hooks';
import { useAuth } from '../src/context/AuthContext'; import { useAuth } from '../src/context/AuthContext';
import { colors } from '../src/theme'; import { colors , spacing } from '../src/theme';
import type { EnterpriseCertificationForm, EnterpriseCertificationStatus } from '../src/types'; import type { EnterpriseCertificationForm, EnterpriseCertificationStatus } from '../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -250,7 +250,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
}, },
headerBtn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' }, headerBtn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' },
@ -261,7 +261,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 12, marginTop: 12,
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
@ -269,8 +269,8 @@ const styles = StyleSheet.create({
topCard: { topCard: {
margin: 8, margin: 8,
backgroundColor: colors.primary, backgroundColor: colors.primary,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
topHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 16 }, topHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 16 },
shieldWrap: { shieldWrap: {
@ -290,7 +290,7 @@ const styles = StyleSheet.create({
statusBadge: { statusBadge: {
alignSelf: 'flex-start', alignSelf: 'flex-start',
marginTop: 6, marginTop: 6,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
borderRadius: 10, borderRadius: 10,
}, },
@ -298,8 +298,8 @@ const styles = StyleSheet.create({
formCard: { formCard: {
marginHorizontal: 8, marginHorizontal: 8,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
formTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary, marginBottom: 16 }, formTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary, marginBottom: 16 },
field: { marginBottom: 16 }, field: { marginBottom: 16 },
@ -326,8 +326,8 @@ const styles = StyleSheet.create({
successCard: { successCard: {
marginHorizontal: 8, marginHorizontal: 8,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
}, },
successTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginTop: 16 }, successTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginTop: 16 },
@ -343,8 +343,8 @@ const styles = StyleSheet.create({
pendingCard: { pendingCard: {
marginHorizontal: 8, marginHorizontal: 8,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
}, },
pendingTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginTop: 16 }, pendingTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginTop: 16 },

6
app/favorites.tsx

@ -5,7 +5,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { useFavorites } from '../src/hooks'; import { useFavorites } from '../src/hooks';
import { JobCard } from '../src/components/JobCard'; import { JobCard } from '../src/components/JobCard';
import { LoadingState, ErrorState, EmptyState } from '../src/components/StateView'; import { LoadingState, ErrorState, EmptyState } from '../src/components/StateView';
import { colors } from '../src/theme'; import { colors , spacing } from '../src/theme';
/** /**
* 我的收藏页(职位) * 我的收藏页(职位)
@ -58,7 +58,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -79,7 +79,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingTop: 12, paddingTop: 12,
paddingBottom: 24, paddingBottom: 24,
}, },

16
app/game.tsx

@ -10,7 +10,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useGameData } from '../src/hooks'; import { useGameData } from '../src/hooks';
import { colors } from '../src/theme'; import { colors , spacing } from '../src/theme';
import type { GameLevel, GameLevelStatus, GameProgress } from '../src/types'; import type { GameLevel, GameLevelStatus, GameProgress } from '../src/types';
/** 关卡状态 → 样式配置 */ /** 关卡状态 → 样式配置 */
@ -180,7 +180,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -201,13 +201,13 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
progressCard: { progressCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
progressHead: { progressHead: {
@ -242,8 +242,8 @@ const styles = StyleSheet.create({
levelCard: { levelCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
borderWidth: 1, borderWidth: 1,
backgroundColor: colors.white, backgroundColor: colors.white,
}, },
@ -289,7 +289,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },

34
app/game/level.tsx

@ -12,7 +12,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useGameData } from '../../src/hooks'; import { useGameData } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { GameAnswer, GameQuestion } from '../../src/types'; import type { GameAnswer, GameQuestion } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -262,7 +262,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -283,7 +283,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
levelDesc: { levelDesc: {
@ -291,14 +291,14 @@ const styles = StyleSheet.create({
lineHeight: 20, lineHeight: 20,
color: colors.textSecondary, color: colors.textSecondary,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
questionCard: { questionCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
questionIndex: { questionIndex: {
@ -321,9 +321,9 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
backgroundColor: colors.white, backgroundColor: colors.white,
@ -368,8 +368,8 @@ const styles = StyleSheet.create({
}, },
explanation: { explanation: {
marginTop: 8, marginTop: 8,
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.infoLight, backgroundColor: colors.infoLight,
}, },
explanationLabel: { explanationLabel: {
@ -386,8 +386,8 @@ const styles = StyleSheet.create({
resultCard: { resultCard: {
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
resultTitle: { resultTitle: {
@ -406,7 +406,7 @@ const styles = StyleSheet.create({
submitBtn: { submitBtn: {
marginTop: 8, marginTop: 8,
height: 48, height: 48,
borderRadius: 8, borderRadius: spacing.card,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
@ -422,7 +422,7 @@ const styles = StyleSheet.create({
nextBtn: { nextBtn: {
marginTop: 8, marginTop: 8,
height: 48, height: 48,
borderRadius: 8, borderRadius: spacing.card,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
backgroundColor: colors.successBtn, backgroundColor: colors.successBtn,
@ -445,7 +445,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },

6
app/guide.tsx

@ -12,7 +12,7 @@ import {
View, View,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { colors, onboardingGradient } from '../src/theme'; import {colors, onboardingGradient, spacing} from '../src/theme';
import { useAuth } from '../src/context/AuthContext'; import { useAuth } from '../src/context/AuthContext';
import type { AssetName } from '../components/onboarding/assets'; import type { AssetName } from '../components/onboarding/assets';
import { ImageSlot } from '../components/onboarding/ui/ImageSlot'; import { ImageSlot } from '../components/onboarding/ui/ImageSlot';
@ -158,7 +158,7 @@ const styles = StyleSheet.create({
topBar: { topBar: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 10, paddingVertical: 10,
}, },
backBtn: { backBtn: {
@ -191,7 +191,7 @@ const styles = StyleSheet.create({
}, },
bottom: { bottom: {
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingBottom: 28, paddingBottom: 28,
}, },
dots: { flexDirection: 'row', gap: 8, marginBottom: 24 }, dots: { flexDirection: 'row', gap: 8, marginBottom: 24 },

6
app/history.tsx

@ -5,7 +5,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { useHistory } from '../src/hooks'; import { useHistory } from '../src/hooks';
import { JobCard } from '../src/components/JobCard'; import { JobCard } from '../src/components/JobCard';
import { LoadingState, ErrorState, EmptyState } from '../src/components/StateView'; import { LoadingState, ErrorState, EmptyState } from '../src/components/StateView';
import { colors } from '../src/theme'; import { colors , spacing } from '../src/theme';
/** /**
* 浏览历史页(职位) * 浏览历史页(职位)
@ -58,7 +58,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -79,7 +79,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingTop: 12, paddingTop: 12,
paddingBottom: 24, paddingBottom: 24,
}, },

28
app/holland-result.tsx

@ -15,7 +15,7 @@ import Svg, { Circle, Line, Polygon } from 'react-native-svg';
import { useHollandAssessment } from '../src/hooks'; import { useHollandAssessment } from '../src/hooks';
import { buildHollandReportText } from '../src/api'; import { buildHollandReportText } from '../src/api';
import { shareText } from '../src/lib/shareFile'; import { shareText } from '../src/lib/shareFile';
import { colors, headerGradientShort, hollandTypeColors } from '../src/theme'; import { colors, headerGradientShort, hollandTypeColors , spacing } from '../src/theme';
import type { HollandAction, HollandDimension, HollandTypeAnalysis } from '../src/types'; import type { HollandAction, HollandDimension, HollandTypeAnalysis } from '../src/types';
const RADAR_SIZE = 160; const RADAR_SIZE = 160;
@ -310,10 +310,10 @@ const styles = StyleSheet.create({
}, },
headerBtn: { width: 40, alignItems: 'center' }, headerBtn: { width: 40, alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
body: { padding: 8, paddingBottom: 32 }, body: { padding: spacing.card, paddingBottom: 32 },
heroCard: { heroCard: {
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginBottom: 8, marginBottom: 8,
@ -327,8 +327,8 @@ const styles = StyleSheet.create({
sectionTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginBottom: 12 }, sectionTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginBottom: 12 },
typeCard: { typeCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -356,8 +356,8 @@ const styles = StyleSheet.create({
careerTagText: { fontSize: 12, color: colors.primaryText }, careerTagText: { fontSize: 12, color: colors.primaryText },
jobCard: { jobCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -368,15 +368,15 @@ const styles = StyleSheet.create({
matchTag: { matchTag: {
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
}, },
matchText: { fontSize: 11, color: colors.successText, fontWeight: '600' }, matchText: { fontSize: 11, color: colors.successText, fontWeight: '600' },
jobTags: { flexDirection: 'row', gap: 8, marginTop: 10 }, jobTags: { flexDirection: 'row', gap: 8, marginTop: 10 },
tag: { tag: {
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
}, },
tagText: { fontSize: 11, color: colors.textSecondary }, tagText: { fontSize: 11, color: colors.textSecondary },
@ -385,8 +385,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -396,6 +396,6 @@ const styles = StyleSheet.create({
actionDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 2 }, actionDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 2 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { paddingHorizontal: 18, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight }, retryBtn: { paddingHorizontal: 18, paddingVertical: spacing.card, borderRadius: 16, backgroundColor: colors.primaryLight },
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
}); });

24
app/holland.tsx

@ -10,7 +10,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useHollandIntro } from '../src/hooks'; import { useHollandIntro } from '../src/hooks';
import { colors, hollandTypeColors } from '../src/theme'; import {colors, hollandTypeColors, spacing} from '../src/theme';
import { formatThousands } from '../src/utils'; import { formatThousands } from '../src/utils';
import type { HollandDimensionIntro, HollandIntro } from '../src/types'; import type { HollandDimensionIntro, HollandIntro } from '../src/types';
@ -173,15 +173,15 @@ const styles = StyleSheet.create({
headerBtn: { width: 50, alignItems: 'center' }, headerBtn: { width: 50, alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
headerRecord: { fontSize: 14, color: colors.primary, fontWeight: '500' }, headerRecord: { fontSize: 14, color: colors.primary, fontWeight: '500' },
body: { padding: 8, paddingBottom: 24 }, body: { padding: spacing.card, paddingBottom: 24 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { paddingHorizontal: 18, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight }, retryBtn: { paddingHorizontal: 18, paddingVertical: spacing.card, borderRadius: 16, backgroundColor: colors.primaryLight },
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
heroCard: { heroCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
marginBottom: 8, marginBottom: 8,
}, },
@ -228,8 +228,8 @@ const styles = StyleSheet.create({
dimItem: { dimItem: {
width: '30.8%', width: '30.8%',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
}, },
dimBadge: { dimBadge: {
@ -264,8 +264,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
featureIcon: { featureIcon: {
width: 44, width: 44,
@ -292,8 +292,8 @@ const styles = StyleSheet.create({
}, },
advantageCard: { advantageCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
advantageTitle: { advantageTitle: {
fontSize: 15, fontSize: 15,
@ -322,7 +322,7 @@ const styles = StyleSheet.create({
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: colors.borderLight, borderTopColor: colors.borderLight,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingTop: 12, paddingTop: 12,
paddingBottom: 24, paddingBottom: 24,
}, },

20
app/holland/question.tsx

@ -11,7 +11,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useHollandAssessment } from '../../src/hooks'; import { useHollandAssessment } from '../../src/hooks';
import { colors, hollandTypeColors } from '../../src/theme'; import { colors, hollandTypeColors , spacing } from '../../src/theme';
import type { HollandDimension, HollandOption } from '../../src/types'; import type { HollandDimension, HollandOption } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -200,11 +200,11 @@ const styles = StyleSheet.create({
headerCenter: { alignItems: 'center' }, headerCenter: { alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
headerSub: { fontSize: 12, color: colors.textHint, marginTop: 2 }, headerSub: { fontSize: 12, color: colors.textHint, marginTop: 2 },
body: { padding: 8, paddingBottom: 32 }, body: { padding: spacing.card, paddingBottom: 32 },
dimCard: { dimCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -244,8 +244,8 @@ const styles = StyleSheet.create({
}, },
questionCard: { questionCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -265,9 +265,9 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
}, },
@ -306,7 +306,7 @@ const styles = StyleSheet.create({
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { retryBtn: {
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },

36
app/hr-evaluation.tsx

@ -11,7 +11,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Circle, Line, Path, Text as SvgText } from 'react-native-svg'; import Svg, { Circle, Line, Path, Text as SvgText } from 'react-native-svg';
import { useHREvaluation } from '../src/hooks'; import { useHREvaluation } from '../src/hooks';
import { colors, levelLabelTextColors } from '../src/theme'; import {colors, levelLabelTextColors, spacing} from '../src/theme';
import type { EvaluationDimension, EvaluationReview, EvaluationTrendPoint } from '../src/types'; import type { EvaluationDimension, EvaluationReview, EvaluationTrendPoint } from '../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -321,12 +321,12 @@ const styles = StyleSheet.create({
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
headerAppealText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, headerAppealText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
body: { paddingHorizontal: 8, paddingBottom: 100, paddingTop: 12 }, body: { paddingHorizontal: spacing.card, paddingBottom: 100, paddingTop: 12 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { retryBtn: {
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
@ -336,8 +336,8 @@ const styles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -361,16 +361,16 @@ const styles = StyleSheet.create({
scoreIcon: { alignItems: 'center', justifyContent: 'center' }, scoreIcon: { alignItems: 'center', justifyContent: 'center' },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
}, },
chartCard: { chartCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -388,8 +388,8 @@ const styles = StyleSheet.create({
dimScore: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginRight: 8 }, dimScore: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, marginRight: 8 },
dimTag: { dimTag: {
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
marginRight: 8, marginRight: 8,
}, },
@ -404,8 +404,8 @@ const styles = StyleSheet.create({
sectionHeader: { marginBottom: 10, marginTop: 4 }, sectionHeader: { marginBottom: 10, marginTop: 4 },
emptyBox: { emptyBox: {
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
}, },
@ -413,8 +413,8 @@ const styles = StyleSheet.create({
sectionTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary }, sectionTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary },
reviewCard: { reviewCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -437,8 +437,8 @@ const styles = StyleSheet.create({
reviewTags: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 10 }, reviewTags: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 10 },
reviewTag: { reviewTag: {
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
}, },
reviewTagText: { fontSize: 11, color: colors.primaryText }, reviewTagText: { fontSize: 11, color: colors.primaryText },
@ -460,7 +460,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,

34
app/interview-score.tsx

@ -13,7 +13,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Line, Polygon, Text as SvgText } from 'react-native-svg'; import Svg, { Line, Polygon, Text as SvgText } from 'react-native-svg';
import { useInterviewScore } from '../src/hooks'; import { useInterviewScore } from '../src/hooks';
import { LoadingState, ErrorState } from '../src/components/StateView'; import { LoadingState, ErrorState } from '../src/components/StateView';
import { colors, semanticColorTokens } from '../src/theme'; import {colors, semanticColorTokens, spacing} from '../src/theme';
import { formatDateTime } from '../src/utils'; import { formatDateTime } from '../src/utils';
import type { HireSuggestion, InterviewScoreData, InterviewScoreDimension, InterviewScoreTag } from '../src/types'; import type { HireSuggestion, InterviewScoreData, InterviewScoreDimension, InterviewScoreTag } from '../src/types';
@ -370,7 +370,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -391,13 +391,13 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
scrollContent: { scrollContent: {
padding: 8, padding: spacing.card,
paddingBottom: 100, paddingBottom: 100,
}, },
infoCard: { infoCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
infoRow: { infoRow: {
@ -419,8 +419,8 @@ const styles = StyleSheet.create({
}, },
scoreCard: { scoreCard: {
backgroundColor: colors.primary, backgroundColor: colors.primary,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
@ -447,8 +447,8 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
cardTitle: { cardTitle: {
@ -536,8 +536,8 @@ const styles = StyleSheet.create({
suggestionRow: { suggestionRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
marginBottom: 8, marginBottom: 8,
@ -566,8 +566,8 @@ const styles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.infoLight, backgroundColor: colors.infoLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
salaryRange: { salaryRange: {
fontSize: 24, fontSize: 24,
@ -586,7 +586,7 @@ const styles = StyleSheet.create({
}, },
footer: { footer: {
flexDirection: 'row', flexDirection: 'row',
padding: 16, padding: spacing.framework,
paddingBottom: 24, paddingBottom: 24,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,
@ -615,9 +615,9 @@ const styles = StyleSheet.create({
backgroundColor: colors.infoLight, backgroundColor: colors.infoLight,
borderWidth: 1, borderWidth: 1,
borderColor: colors.infoBorder, borderColor: colors.infoBorder,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
draftBannerBody: { flex: 1 }, draftBannerBody: { flex: 1 },

24
app/interview.tsx

@ -13,7 +13,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { useAIInterview } from '../src/hooks'; import { useAIInterview } from '../src/hooks';
import { buildInterviewConfigText } from '../src/api'; import { buildInterviewConfigText } from '../src/api';
import { shareText } from '../src/lib/shareFile'; import { shareText } from '../src/lib/shareFile';
import { colors, semanticColorTokens } from '../src/theme'; import {colors, semanticColorTokens, spacing} from '../src/theme';
import type { import type {
InterviewAbilityImprovement, InterviewAbilityImprovement,
InterviewMode, InterviewMode,
@ -377,12 +377,12 @@ const styles = StyleSheet.create({
}, },
headerBtn: { width: 40, alignItems: 'center' }, headerBtn: { width: 40, alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
body: { paddingHorizontal: 8, paddingBottom: 100, paddingTop: 12 }, body: { paddingHorizontal: spacing.card, paddingBottom: 100, paddingTop: 12 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { retryBtn: {
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
@ -393,8 +393,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -413,8 +413,8 @@ const styles = StyleSheet.create({
modeDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 2 }, modeDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 2 },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -436,7 +436,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
@ -446,7 +446,7 @@ const styles = StyleSheet.create({
typeTextActive: { color: colors.white, fontWeight: '600' }, typeTextActive: { color: colors.white, fontWeight: '600' },
specialTag: { specialTag: {
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderWidth: 1, borderWidth: 1,
@ -458,7 +458,7 @@ const styles = StyleSheet.create({
improvementRow: { improvementRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingVertical: 8, paddingVertical: spacing.card,
}, },
improvementLabel: { width: 70, fontSize: 13, color: colors.textPrimary }, improvementLabel: { width: 70, fontSize: 13, color: colors.textPrimary },
improvementTrack: { improvementTrack: {
@ -484,14 +484,14 @@ const styles = StyleSheet.create({
}, },
recentDate: { width: 60, fontSize: 12, color: colors.textHint }, recentDate: { width: 60, fontSize: 12, color: colors.textHint },
recentTitle: { flex: 1, fontSize: 14, color: colors.textPrimary }, recentTitle: { flex: 1, fontSize: 14, color: colors.textPrimary },
recentTag: { borderRadius: 10, paddingHorizontal: 8, paddingVertical: 2 }, recentTag: { borderRadius: 10, paddingHorizontal: spacing.card, paddingVertical: 2 },
recentResult: { fontSize: 12, fontWeight: '600' }, recentResult: { fontSize: 12, fontWeight: '600' },
footer: { footer: {
position: 'absolute', position: 'absolute',
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,

46
app/job-evaluations.tsx

@ -15,7 +15,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Line, Path, Polygon, Text as SvgText } from 'react-native-svg'; import Svg, { Line, Path, Polygon, Text as SvgText } from 'react-native-svg';
import { useJobEvaluationEmptyState, useJobEvaluations } from '../src/hooks'; import { useJobEvaluationEmptyState, useJobEvaluations } from '../src/hooks';
import { LoadingState, ErrorState } from '../src/components/StateView'; import { LoadingState, ErrorState } from '../src/components/StateView';
import { colors } from '../src/theme'; import {colors, spacing} from '../src/theme';
import type { import type {
JobEvaluationDimension, JobEvaluationDimension,
JobEvaluationEmptyState, JobEvaluationEmptyState,
@ -523,7 +523,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -544,13 +544,13 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
scrollContent: { scrollContent: {
padding: 8, padding: spacing.card,
paddingBottom: 32, paddingBottom: 32,
}, },
scoreCard: { scoreCard: {
backgroundColor: colors.primary, backgroundColor: colors.primary,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
scoreMain: { scoreMain: {
@ -605,8 +605,8 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
cardTitle: { cardTitle: {
@ -617,15 +617,15 @@ const styles = StyleSheet.create({
}, },
chartCard: { chartCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
alignItems: 'center', alignItems: 'center',
}, },
radarCard: { radarCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
alignItems: 'center', alignItems: 'center',
}, },
@ -710,7 +710,7 @@ const styles = StyleSheet.create({
marginBottom: 10, marginBottom: 10,
}, },
reviewTag: { reviewTag: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 4, paddingVertical: 4,
borderRadius: 12, borderRadius: 12,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
@ -750,8 +750,8 @@ const styles = StyleSheet.create({
/* ---- 回复内容块 ---- */ /* ---- 回复内容块 ---- */
replyBlock: { replyBlock: {
marginTop: 8, marginTop: 8,
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
}, },
replyBlockLabel: { replyBlockLabel: {
@ -774,8 +774,8 @@ const styles = StyleSheet.create({
}, },
modalCard: { modalCard: {
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
modalTitle: { modalTitle: {
fontSize: 16, fontSize: 16,
@ -791,7 +791,7 @@ const styles = StyleSheet.create({
modalInput: { modalInput: {
marginTop: 12, marginTop: 12,
minHeight: 88, minHeight: 88,
borderRadius: 8, borderRadius: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
paddingHorizontal: 12, paddingHorizontal: 12,
@ -807,7 +807,7 @@ const styles = StyleSheet.create({
marginTop: 16, marginTop: 16,
}, },
modalCancel: { modalCancel: {
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 10, paddingVertical: 10,
}, },
modalCancelText: { modalCancelText: {
@ -833,7 +833,7 @@ const styles = StyleSheet.create({
marginBottom: 16, marginBottom: 16,
}, },
emptyTabContent: { emptyTabContent: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 12, paddingVertical: 12,
gap: 8, gap: 8,
}, },
@ -869,8 +869,8 @@ const styles = StyleSheet.create({
}, },
emptyCard: { emptyCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
marginTop: 8, marginTop: 8,
}, },
@ -905,8 +905,8 @@ const styles = StyleSheet.create({
tipCard: { tipCard: {
width: '100%', width: '100%',
backgroundColor: colors.actionLight, backgroundColor: colors.actionLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'flex-start', alignItems: 'flex-start',
}, },
tipHeader: { tipHeader: {

6
app/job-preference.tsx

@ -11,7 +11,7 @@ import {
View, View,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { colors } from '../src/theme'; import { colors , spacing } from '../src/theme';
import { useAuth } from '../src/context/AuthContext'; import { useAuth } from '../src/context/AuthContext';
import { useJobPreference } from '../src/hooks'; import { useJobPreference } from '../src/hooks';
import { import {
@ -195,12 +195,12 @@ export default function JobPreferenceScreen() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.pageBg }, container: { flex: 1, backgroundColor: colors.pageBg },
body: { padding: 8, paddingBottom: 24 }, body: { padding: spacing.card, paddingBottom: 24 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { retryBtn: {
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },

27
app/job-publish.tsx

@ -13,7 +13,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useJobPublish } from '../src/hooks'; import { useJobPublish } from '../src/hooks';
import { LoadingState, ErrorState } from '../src/components/StateView'; import { LoadingState, ErrorState } from '../src/components/StateView';
import { colors } from '../src/theme'; import {colors, spacing} from '../src/theme';
import { EDUCATION_OPTIONS, WORK_TYPE_LABELS } from '../src/constants'; import { EDUCATION_OPTIONS, WORK_TYPE_LABELS } from '../src/constants';
import type { JobPublishFormData } from '../src/types'; import type { JobPublishFormData } from '../src/types';
@ -24,19 +24,16 @@ const WORK_TYPE_OPTIONS = WORK_TYPE_LABELS;
* 经验要求选项 —— **刻意不从 constants 的 `EXPERIENCE_OPTIONS` 取**。 * 经验要求选项 —— **刻意不从 constants 的 `EXPERIENCE_OPTIONS` 取**。
* *
* 两张表不是同一个事实,名字像而已: * 两张表不是同一个事实,名字像而已:
* · constants 的 `EXPERIENCE_OPTIONS` 是**筛选项**,含「不限」(表示不加限制), * · constants 的 `EXPERIENCE_OPTIONS` 是**筛选项**,首项为「不限」(表示不加限制);
* 且不含「1年以内」; * · 本表是**企业发布岗位时的必填要求**,没有「不限」(必须明确要求)。
* · 本表是**企业发布岗位时的必填要求**,没有「不限」(必须明确要求),
* 且含「1年以内」—— 与 `JobExperienceRequirement` 的成员逐一对应。
* 强行合并会让发布页出现「不限」这个不该有的选项。 * 强行合并会让发布页出现「不限」这个不该有的选项。
* *
* 已知遗留(非本批处理):`JobExperienceRequirement` 的「1年以内」在 mock 岗位数据里 * 两者的取值档位现已对齐(同为 应届生 / 1-3年 / 3-5年 / 5-10年 / 10年以上),
* **一次都没用到**,而筛选项表里也没有它 —— 即这个取值目前既筛不到、也没有样本。 * 与 `JobExperienceRequirement` 的成员逐一对应。原先发布端多出的「1年以内」属悬空值
* 属数据/类型层面的取舍,需产品定夺,已记入《项目开发状态.md》4.16。 * (筛选侧无对应档位、mock 数据也未使用),已按产品口径移除。
*/ */
const EXPERIENCE_OPTIONS: JobPublishFormData['experience'][] = [ const EXPERIENCE_OPTIONS: JobPublishFormData['experience'][] = [
'应届生', '应届生',
'1年以内',
'1-3年', '1-3年',
'3-5年', '3-5年',
'5-10年', '5-10年',
@ -402,7 +399,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -434,13 +431,13 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
scrollContent: { scrollContent: {
padding: 8, padding: spacing.card,
paddingBottom: 100, paddingBottom: 100,
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
cardTitle: { cardTitle: {
@ -483,7 +480,7 @@ const styles = StyleSheet.create({
}, },
radioTag: { radioTag: {
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
@ -596,7 +593,7 @@ const styles = StyleSheet.create({
}, },
footer: { footer: {
flexDirection: 'row', flexDirection: 'row',
padding: 16, padding: spacing.framework,
paddingBottom: 24, paddingBottom: 24,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,

22
app/jobs/[id].tsx

@ -16,7 +16,7 @@ import Svg, { Circle, Line, Polygon, Text as SvgText } from 'react-native-svg';
import { useJobDetail } from '../../src/hooks'; import { useJobDetail } from '../../src/hooks';
import { buildJobShareText } from '../../src/api'; import { buildJobShareText } from '../../src/api';
import { shareText } from '../../src/lib/shareFile'; import { shareText } from '../../src/lib/shareFile';
import { colors } from '../../src/theme'; import {colors, spacing} from '../../src/theme';
import { formatThousands } from '../../src/utils'; import { formatThousands } from '../../src/utils';
import type { JobDimensionScore } from '../../src/types'; import type { JobDimensionScore } from '../../src/types';
@ -369,7 +369,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -418,21 +418,21 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
/* ---- 空态 ---- */ /* ---- 空态 ---- */
emptyCard: { emptyCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
marginBottom: 8, marginBottom: 8,
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
@ -465,7 +465,7 @@ const styles = StyleSheet.create({
}, },
statusTag: { statusTag: {
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 3, paddingVertical: 3,
borderRadius: 10, borderRadius: 10,
}, },
@ -664,7 +664,7 @@ const styles = StyleSheet.create({
commentBar: { commentBar: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 10, paddingVertical: 10,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,
@ -675,7 +675,7 @@ const styles = StyleSheet.create({
height: 40, height: 40,
borderRadius: 20, borderRadius: 20,
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
fontSize: 14, fontSize: 14,
color: colors.textPrimary, color: colors.textPrimary,
}, },

20
app/jobs/compare.tsx

@ -7,7 +7,7 @@ import { useRouter } from 'expo-router';
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useCompareData } from '../../src/hooks'; import { useCompareData } from '../../src/hooks';
import { colors } from '../../src/theme'; import {colors, spacing} from '../../src/theme';
function Header({ onBack }: { onBack: () => void }) { function Header({ onBack }: { onBack: () => void }) {
return ( return (
@ -185,7 +185,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderBottomWidth: StyleSheet.hairlineWidth, borderBottomWidth: StyleSheet.hairlineWidth,
@ -193,7 +193,7 @@ const styles = StyleSheet.create({
}, },
headerBtn: { width: 32, alignItems: 'center' }, headerBtn: { width: 32, alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '500', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '500', color: colors.textPrimary },
content: { padding: 8, paddingBottom: 32 }, content: { padding: spacing.card, paddingBottom: 32 },
stateWrap: { stateWrap: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
@ -204,8 +204,8 @@ const styles = StyleSheet.create({
stateText: { fontSize: 14, color: colors.textSecondary }, stateText: { fontSize: 14, color: colors.textSecondary },
retryBtn: { retryBtn: {
paddingHorizontal: 20, paddingHorizontal: 20,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
retryText: { fontSize: 14, color: colors.primaryText }, retryText: { fontSize: 14, color: colors.primaryText },
@ -231,8 +231,8 @@ const styles = StyleSheet.create({
jobRow: { flexDirection: 'row', gap: 8 }, jobRow: { flexDirection: 'row', gap: 8 },
jobCard: { jobCard: {
flex: 1, flex: 1,
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderWidth: StyleSheet.hairlineWidth, borderWidth: StyleSheet.hairlineWidth,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -252,11 +252,11 @@ const styles = StyleSheet.create({
}, },
sectionTitle: { fontSize: 15, fontWeight: '500', color: colors.textPrimary }, sectionTitle: { fontSize: 15, fontWeight: '500', color: colors.textPrimary },
card: { card: {
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderWidth: StyleSheet.hairlineWidth, borderWidth: StyleSheet.hairlineWidth,
borderColor: colors.borderLight, borderColor: colors.borderLight,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
scoreRow: { scoreRow: {
paddingVertical: 12, paddingVertical: 12,
@ -296,7 +296,7 @@ const styles = StyleSheet.create({
benefitJob: { fontSize: 13, color: colors.textPrimary, marginBottom: 8 }, benefitJob: { fontSize: 13, color: colors.textPrimary, marginBottom: 8 },
benefitTags: { flexDirection: 'row', flexWrap: 'wrap', gap: 6 }, benefitTags: { flexDirection: 'row', flexWrap: 'wrap', gap: 6 },
benefitTag: { benefitTag: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 3, paddingVertical: 3,
borderRadius: 4, borderRadius: 4,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,

24
app/jobs/offer-compare.tsx

@ -15,7 +15,7 @@ import { useOfferCompareData } from '../../src/hooks';
import { buildOfferCompareText } from '../../src/api'; import { buildOfferCompareText } from '../../src/api';
import { shareText } from '../../src/lib/shareFile'; import { shareText } from '../../src/lib/shareFile';
import { EmptyState } from '../../src/components/StateView'; import { EmptyState } from '../../src/components/StateView';
import { colors, headerGradientSolid } from '../../src/theme'; import { colors, headerGradientSolid , spacing } from '../../src/theme';
import type { OfferBenefitGroup, OfferItem, OfferScore, WorkLifeBalanceRow } from '../../src/types'; import type { OfferBenefitGroup, OfferItem, OfferScore, WorkLifeBalanceRow } from '../../src/types';
/* ===================== 子组件 ===================== */ /* ===================== 子组件 ===================== */
@ -335,7 +335,7 @@ const styles = StyleSheet.create({
height: 48, height: 48,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
}, },
headerBtn: { headerBtn: {
@ -368,7 +368,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -383,7 +383,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingTop: 8, paddingTop: 8,
paddingBottom: 12, paddingBottom: 12,
}, },
@ -398,8 +398,8 @@ const styles = StyleSheet.create({
}, },
offerCard: { offerCard: {
width: 150, width: 150,
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
@ -447,8 +447,8 @@ const styles = StyleSheet.create({
/* ---- 通用卡片与分区标题 ---- */ /* ---- 通用卡片与分区标题 ---- */
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginTop: 8, marginTop: 8,
}, },
sectionHeader: { sectionHeader: {
@ -536,9 +536,9 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingVertical: 8, paddingVertical: spacing.card,
marginHorizontal: 2, marginHorizontal: 2,
borderRadius: 8, borderRadius: spacing.card,
}, },
tableHeaderText: { tableHeaderText: {
fontSize: 13, fontSize: 13,
@ -576,8 +576,8 @@ const styles = StyleSheet.create({
/* ---- AI 建议 ---- */ /* ---- AI 建议 ---- */
aiCard: { aiCard: {
marginTop: 8, marginTop: 8,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
aiHeader: { aiHeader: {
flexDirection: 'row', flexDirection: 'row',

28
app/jobs/rank.tsx

@ -14,7 +14,7 @@ import Svg, { Circle, Rect } from 'react-native-svg';
import { useRankData } from '../../src/hooks'; import { useRankData } from '../../src/hooks';
import { buildRankReportText } from '../../src/api'; import { buildRankReportText } from '../../src/api';
import { shareText } from '../../src/lib/shareFile'; import { shareText } from '../../src/lib/shareFile';
import { colors } from '../../src/theme'; import {colors, spacing} from '../../src/theme';
import { formatThousands } from '../../src/utils'; import { formatThousands } from '../../src/utils';
import type { DimensionPercentile, RankData, RankEmptyState, TopCandidate } from '../../src/types'; import type { DimensionPercentile, RankData, RankEmptyState, TopCandidate } from '../../src/types';
@ -394,7 +394,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 4, paddingVertical: 4,
}, },
headerBtn: { headerBtn: {
@ -436,7 +436,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
scrollContent: { scrollContent: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingBottom: 40, paddingBottom: 40,
}, },
bottomSpacer: { bottomSpacer: {
@ -460,8 +460,8 @@ const styles = StyleSheet.create({
/* ---- 综合排名卡片 ---- */ /* ---- 综合排名卡片 ---- */
rankCard: { rankCard: {
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.primaryBorder, borderColor: colors.primaryBorder,
}, },
@ -494,7 +494,7 @@ const styles = StyleSheet.create({
}, },
levelBadge: { levelBadge: {
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
borderRadius: 4, borderRadius: 4,
marginLeft: 8, marginLeft: 8,
@ -523,8 +523,8 @@ const styles = StyleSheet.create({
/* ---- 分区 ---- */ /* ---- 分区 ---- */
section: { section: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginTop: 8, marginTop: 8,
}, },
sectionTitle: { sectionTitle: {
@ -622,14 +622,14 @@ const styles = StyleSheet.create({
}, },
/* ---- 空状态 ---- */ /* ---- 空状态 ---- */
emptyContent: { emptyContent: {
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingTop: 24, paddingTop: 24,
paddingBottom: 40, paddingBottom: 40,
}, },
emptyCard: { emptyCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
}, },
emptyIconWrap: { emptyIconWrap: {
@ -667,8 +667,8 @@ const styles = StyleSheet.create({
}, },
progressCard: { progressCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginTop: 8, marginTop: 8,
}, },
progressTitle: { progressTitle: {
@ -754,7 +754,7 @@ const styles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 12, paddingVertical: 12,
marginTop: 8, marginTop: 8,

24
app/jobs/weight-settings.tsx

@ -7,7 +7,7 @@ import { useRouter } from 'expo-router';
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useWeightSettings } from '../../src/hooks'; import { useWeightSettings } from '../../src/hooks';
import { colors } from '../../src/theme'; import {colors, spacing} from '../../src/theme';
const MIN_WEIGHT = 0; const MIN_WEIGHT = 0;
const MAX_WEIGHT = 100; const MAX_WEIGHT = 100;
@ -175,7 +175,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderBottomWidth: StyleSheet.hairlineWidth, borderBottomWidth: StyleSheet.hairlineWidth,
@ -183,13 +183,13 @@ const styles = StyleSheet.create({
}, },
headerBtn: { width: 32, alignItems: 'center' }, headerBtn: { width: 32, alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '500', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '500', color: colors.textPrimary },
content: { padding: 8, paddingBottom: 24 }, content: { padding: spacing.card, paddingBottom: 24 },
stateWrap: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12 }, stateWrap: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12 },
stateText: { fontSize: 14, color: colors.textSecondary }, stateText: { fontSize: 14, color: colors.textSecondary },
retryBtn: { retryBtn: {
paddingHorizontal: 20, paddingHorizontal: 20,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
retryText: { fontSize: 14, color: colors.primaryText }, retryText: { fontSize: 14, color: colors.primaryText },
@ -197,8 +197,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
summaryCardWarn: { backgroundColor: colors.dangerLight }, summaryCardWarn: { backgroundColor: colors.dangerLight },
@ -218,8 +218,8 @@ const styles = StyleSheet.create({
templateRow: { gap: 8, paddingRight: 16 }, templateRow: { gap: 8, paddingRight: 16 },
templateCard: { templateCard: {
width: 140, width: 140,
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderWidth: StyleSheet.hairlineWidth, borderWidth: StyleSheet.hairlineWidth,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -227,11 +227,11 @@ const styles = StyleSheet.create({
templateName: { fontSize: 14, fontWeight: '500', color: colors.textPrimary, marginBottom: 4 }, templateName: { fontSize: 14, fontWeight: '500', color: colors.textPrimary, marginBottom: 4 },
templateDesc: { fontSize: 11, color: colors.textSecondary }, templateDesc: { fontSize: 11, color: colors.textSecondary },
dimensionCard: { dimensionCard: {
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderWidth: StyleSheet.hairlineWidth, borderWidth: StyleSheet.hairlineWidth,
borderColor: colors.borderLight, borderColor: colors.borderLight,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
dimRow: { dimRow: {
paddingVertical: 14, paddingVertical: 14,
@ -268,7 +268,7 @@ const styles = StyleSheet.create({
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
footer: { footer: {
padding: 16, padding: spacing.framework,
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderTopWidth: StyleSheet.hairlineWidth, borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: colors.borderLight, borderTopColor: colors.borderLight,

40
app/legal.tsx

@ -11,7 +11,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useLegalData, useLegalMembership } from '../src/hooks'; import { useLegalData, useLegalMembership } from '../src/hooks';
import { colors } from '../src/theme'; import { colors , spacing } from '../src/theme';
import type { LegalFAQ, LegalPlan, LegalService, LegalTemplate } from '../src/types'; import type { LegalFAQ, LegalPlan, LegalService, LegalTemplate } from '../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -336,7 +336,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -357,15 +357,15 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
brandCard: { brandCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.primaryBorder, borderColor: colors.primaryBorder,
@ -424,8 +424,8 @@ const styles = StyleSheet.create({
}, },
emptyHint: { emptyHint: {
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
}, },
@ -436,8 +436,8 @@ const styles = StyleSheet.create({
serviceCard: { serviceCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
}, },
serviceCardFree: { serviceCardFree: {
@ -494,8 +494,8 @@ const styles = StyleSheet.create({
templateCard: { templateCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
}, },
templateBody: { templateBody: {
@ -514,8 +514,8 @@ const styles = StyleSheet.create({
}, },
faqCard: { faqCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
faqItem: { faqItem: {
paddingVertical: 12, paddingVertical: 12,
@ -546,8 +546,8 @@ const styles = StyleSheet.create({
}, },
planCard: { planCard: {
flex: 1, flex: 1,
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
@ -571,9 +571,9 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
gap: 6, gap: 6,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.actionLight, backgroundColor: colors.actionLight,
borderWidth: 1, borderWidth: 1,
borderColor: colors.action, borderColor: colors.action,
@ -646,7 +646,7 @@ const styles = StyleSheet.create({
gap: 8, gap: 8,
marginTop: 20, marginTop: 20,
height: 48, height: 48,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
consultText: { consultText: {
@ -667,7 +667,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },

16
app/legal/chat.tsx

@ -14,7 +14,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useLegalData } from '../../src/hooks'; import { useLegalData } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { LegalMessage } from '../../src/types'; import type { LegalMessage } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -151,7 +151,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -172,7 +172,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
scrollContent: { scrollContent: {
padding: 8, padding: spacing.card,
paddingBottom: 16, paddingBottom: 16,
}, },
tipCard: { tipCard: {
@ -180,8 +180,8 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
backgroundColor: colors.infoLight, backgroundColor: colors.infoLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
tipText: { tipText: {
@ -243,7 +243,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 10, paddingVertical: 10,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,
@ -257,7 +257,7 @@ const styles = StyleSheet.create({
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 8, paddingVertical: spacing.card,
fontSize: 14, fontSize: 14,
color: colors.textPrimary, color: colors.textPrimary,
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
@ -286,7 +286,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },

16
app/legal/checkout.tsx

@ -5,7 +5,7 @@ import { ActivityIndicator, Alert, Pressable, ScrollView, StyleSheet, Text, View
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useLegalData, useLegalMembership } from '../../src/hooks'; import { useLegalData, useLegalMembership } from '../../src/hooks';
import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView'; import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { LegalPaymentChannel } from '../../src/types'; import type { LegalPaymentChannel } from '../../src/types';
/* ===================== 付款方式 ===================== */ /* ===================== 付款方式 ===================== */
@ -198,7 +198,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -219,12 +219,12 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
summaryCard: { summaryCard: {
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
@ -280,8 +280,8 @@ const styles = StyleSheet.create({
channelRow: { channelRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
@ -353,7 +353,7 @@ const styles = StyleSheet.create({
}, },
payBtn: { payBtn: {
height: 48, height: 48,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',

10
app/legal/faq.tsx

@ -5,7 +5,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { useState } from 'react'; import { useState } from 'react';
import { useLegalData } from '../../src/hooks'; import { useLegalData } from '../../src/hooks';
import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView'; import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { LegalFAQ } from '../../src/types'; import type { LegalFAQ } from '../../src/types';
/* ===================== 顶部导航 ===================== */ /* ===================== 顶部导航 ===================== */
@ -82,7 +82,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -103,13 +103,13 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
faqCard: { faqCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
faqItem: { faqItem: {
paddingVertical: 12, paddingVertical: 12,

28
app/legal/service.tsx

@ -14,7 +14,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useLegalData, useLegalMembership, useLegalServiceRequests } from '../../src/hooks'; import { useLegalData, useLegalMembership, useLegalServiceRequests } from '../../src/hooks';
import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView'; import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
/* ===================== 顶部导航 ===================== */ /* ===================== 顶部导航 ===================== */
@ -233,7 +233,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -254,12 +254,12 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
card: { card: {
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
@ -310,7 +310,7 @@ const styles = StyleSheet.create({
input: { input: {
minHeight: 120, minHeight: 120,
padding: 12, padding: 12,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
@ -327,7 +327,7 @@ const styles = StyleSheet.create({
submitBtn: { submitBtn: {
marginTop: 16, marginTop: 16,
height: 48, height: 48,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@ -347,8 +347,8 @@ const styles = StyleSheet.create({
marginTop: 16, marginTop: 16,
}, },
emptyCard: { emptyCard: {
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
alignItems: 'center', alignItems: 'center',
}, },
@ -357,8 +357,8 @@ const styles = StyleSheet.create({
color: colors.textSecondary, color: colors.textSecondary,
}, },
errorCard: { errorCard: {
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.dangerLight, backgroundColor: colors.dangerLight,
borderWidth: 1, borderWidth: 1,
borderColor: colors.dangerBorder, borderColor: colors.dangerBorder,
@ -378,8 +378,8 @@ const styles = StyleSheet.create({
gap: 8, gap: 8,
}, },
requestCard: { requestCard: {
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
@ -397,7 +397,7 @@ const styles = StyleSheet.create({
marginRight: 8, marginRight: 8,
}, },
statusTag: { statusTag: {
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
borderRadius: 4, borderRadius: 4,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,

14
app/legal/template.tsx

@ -4,7 +4,7 @@ import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useLegalData } from '../../src/hooks'; import { useLegalData } from '../../src/hooks';
import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView'; import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
/* ===================== 顶部导航 ===================== */ /* ===================== 顶部导航 ===================== */
@ -78,7 +78,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -99,14 +99,14 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
headCard: { headCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
marginBottom: 8, marginBottom: 8,
}, },
@ -125,8 +125,8 @@ const styles = StyleSheet.create({
color: colors.textSecondary, color: colors.textSecondary,
}, },
contentCard: { contentCard: {
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,

10
app/legal/templates.tsx

@ -4,7 +4,7 @@ import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useLegalData } from '../../src/hooks'; import { useLegalData } from '../../src/hooks';
import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView'; import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { LegalTemplate } from '../../src/types'; import type { LegalTemplate } from '../../src/types';
/* ===================== 顶部导航 ===================== */ /* ===================== 顶部导航 ===================== */
@ -81,7 +81,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -102,7 +102,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
templateGrid: { templateGrid: {
@ -111,8 +111,8 @@ const styles = StyleSheet.create({
templateCard: { templateCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
}, },
templateBody: { templateBody: {

16
app/legal/upgrade.tsx

@ -5,7 +5,7 @@ import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useLegalData, useLegalMembership } from '../../src/hooks'; import { useLegalData, useLegalMembership } from '../../src/hooks';
import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView'; import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { LegalPlan } from '../../src/types'; import type { LegalPlan } from '../../src/types';
/* ===================== 顶部导航 ===================== */ /* ===================== 顶部导航 ===================== */
@ -126,7 +126,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -147,7 +147,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
tip: { tip: {
@ -159,8 +159,8 @@ const styles = StyleSheet.create({
gap: 8, gap: 8,
}, },
planCard: { planCard: {
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
@ -184,9 +184,9 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
gap: 6, gap: 6,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.actionLight, backgroundColor: colors.actionLight,
borderWidth: 1, borderWidth: 1,
borderColor: colors.action, borderColor: colors.action,

24
app/onboarding.tsx

@ -18,7 +18,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { buildChecklistText } from '../src/api'; import { buildChecklistText } from '../src/api';
import { useOnboardingChecklist } from '../src/hooks'; import { useOnboardingChecklist } from '../src/hooks';
import { shareText } from '../src/lib/shareFile'; import { shareText } from '../src/lib/shareFile';
import { colors, headerGradientShort } from '../src/theme'; import {colors, headerGradientShort, spacing} from '../src/theme';
import type { ChecklistGroup, OnboardingChecklist, OnboardingTip } from '../src/types'; import type { ChecklistGroup, OnboardingChecklist, OnboardingTip } from '../src/types';
/** 分组图标配色轮转(全部来自主题 token) */ /** 分组图标配色轮转(全部来自主题 token) */
@ -434,17 +434,17 @@ const styles = StyleSheet.create({
}, },
headerBtn: { width: 40, alignItems: 'center' }, headerBtn: { width: 40, alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
body: { paddingHorizontal: 8, paddingBottom: 100, paddingTop: 12 }, body: { paddingHorizontal: spacing.card, paddingBottom: 100, paddingTop: 12 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { retryBtn: {
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
countdownCard: { borderRadius: 8, padding: 8, marginBottom: 8 }, countdownCard: { borderRadius: spacing.card, padding: spacing.card, marginBottom: 8 },
countdownTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, countdownTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
countdownLabel: { fontSize: 13, color: colors.textInverse, opacity: 0.9 }, countdownLabel: { fontSize: 13, color: colors.textInverse, opacity: 0.9 },
countdownValueRow: { flexDirection: 'row', alignItems: 'baseline', marginTop: 6 }, countdownValueRow: { flexDirection: 'row', alignItems: 'baseline', marginTop: 6 },
@ -477,7 +477,7 @@ const styles = StyleSheet.create({
countdownFill: { height: 8, borderRadius: 4, backgroundColor: colors.white }, countdownFill: { height: 8, borderRadius: 4, backgroundColor: colors.white },
groupCard: { groupCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -510,15 +510,15 @@ const styles = StyleSheet.create({
itemTitleDone: { textDecorationLine: 'line-through', color: colors.textHint }, itemTitleDone: { textDecorationLine: 'line-through', color: colors.textHint },
requiredTag: { requiredTag: {
backgroundColor: colors.dangerLight, backgroundColor: colors.dangerLight,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 6, paddingHorizontal: 6,
paddingVertical: 2, paddingVertical: 2,
}, },
requiredText: { fontSize: 10, color: colors.dangerText, fontWeight: '600' }, requiredText: { fontSize: 10, color: colors.dangerText, fontWeight: '600' },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginTop: 8, marginTop: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -543,7 +543,7 @@ const styles = StyleSheet.create({
right: 0, right: 0,
bottom: 0, bottom: 0,
flexDirection: 'row', flexDirection: 'row',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,
@ -577,7 +577,7 @@ const styles = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
paddingHorizontal: 32, paddingHorizontal: 32,
}, },
modalCard: { backgroundColor: colors.white, borderRadius: 8, padding: 8 }, modalCard: { backgroundColor: colors.white, borderRadius: spacing.card, padding: spacing.card },
modalTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary }, modalTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary },
modalInput: { modalInput: {
marginTop: 14, marginTop: 14,
@ -590,13 +590,13 @@ const styles = StyleSheet.create({
color: colors.textPrimary, color: colors.textPrimary,
}, },
modalActions: { flexDirection: 'row', justifyContent: 'flex-end', gap: 12, marginTop: 18 }, modalActions: { flexDirection: 'row', justifyContent: 'flex-end', gap: 12, marginTop: 18 },
modalCancel: { paddingHorizontal: 16, paddingVertical: 8 }, modalCancel: { paddingHorizontal: spacing.framework, paddingVertical: spacing.card },
modalCancelText: { fontSize: 14, color: colors.textSecondary }, modalCancelText: { fontSize: 14, color: colors.textSecondary },
modalConfirm: { modalConfirm: {
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
borderRadius: 18, borderRadius: 18,
paddingHorizontal: 20, paddingHorizontal: 20,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
modalConfirmText: { fontSize: 14, fontWeight: '700', color: colors.white }, modalConfirmText: { fontSize: 14, fontWeight: '700', color: colors.white },
}); });

22
app/operation-rules.tsx

@ -13,7 +13,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useOperationRules } from '../src/hooks'; import { useOperationRules } from '../src/hooks';
import { colors } from '../src/theme'; import {colors, spacing} from '../src/theme';
import type { MatchMode, OperationRules } from '../src/types'; import type { MatchMode, OperationRules } from '../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -326,7 +326,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
}, },
headerBtn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' }, headerBtn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' },
@ -337,7 +337,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 12, marginTop: 12,
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
@ -354,9 +354,9 @@ const styles = StyleSheet.create({
card: { card: {
marginHorizontal: 8, marginHorizontal: 8,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
modeRow: { modeRow: {
flexDirection: 'row', flexDirection: 'row',
@ -375,7 +375,7 @@ const styles = StyleSheet.create({
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
paddingHorizontal: 6, paddingHorizontal: 6,
paddingVertical: 2, paddingVertical: 2,
borderRadius: 8, borderRadius: spacing.card,
}, },
recommendedText: { fontSize: 10, fontWeight: '600', color: colors.successText }, recommendedText: { fontSize: 10, fontWeight: '600', color: colors.successText },
modeDesc: { fontSize: 12, color: colors.textSecondary, lineHeight: 18 }, modeDesc: { fontSize: 12, color: colors.textSecondary, lineHeight: 18 },
@ -414,7 +414,7 @@ const styles = StyleSheet.create({
marginLeft: -8, marginLeft: -8,
width: 16, width: 16,
height: 16, height: 16,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderWidth: 2, borderWidth: 2,
borderColor: colors.primary, borderColor: colors.primary,
@ -439,7 +439,7 @@ const styles = StyleSheet.create({
height: 32, height: 32,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
borderRadius: 8, borderRadius: spacing.card,
textAlign: 'center', textAlign: 'center',
fontSize: 14, fontSize: 14,
color: colors.textPrimary, color: colors.textPrimary,
@ -475,8 +475,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.textPrimary, backgroundColor: colors.textPrimary,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
}, },
savingText: { marginLeft: 8, fontSize: 13, color: colors.textInverse }, savingText: { marginLeft: 8, fontSize: 13, color: colors.textInverse },

32
app/personal-data-export.tsx

@ -12,7 +12,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { usePersonalDataExport } from '../src/hooks'; import { usePersonalDataExport } from '../src/hooks';
import { colors } from '../src/theme'; import {colors, spacing} from '../src/theme';
import type { DataExportCategory, DataExportFormat, PersonalDataExport } from '../src/types'; import type { DataExportCategory, DataExportFormat, PersonalDataExport } from '../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -387,7 +387,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -405,7 +405,7 @@ const styles = StyleSheet.create({
color: colors.textPrimary, color: colors.textPrimary,
}, },
body: { body: {
padding: 8, padding: spacing.card,
paddingBottom: 100, paddingBottom: 100,
}, },
center: { center: {
@ -418,7 +418,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 8, marginTop: 8,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -426,8 +426,8 @@ const styles = StyleSheet.create({
introCard: { introCard: {
flexDirection: 'row', flexDirection: 'row',
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
introIconWrap: { introIconWrap: {
@ -470,7 +470,7 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
overflow: 'hidden', overflow: 'hidden',
}, },
rowBorder: { rowBorder: {
@ -480,7 +480,7 @@ const styles = StyleSheet.create({
categoryRow: { categoryRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 14, paddingVertical: 14,
}, },
checkbox: { checkbox: {
@ -519,8 +519,8 @@ const styles = StyleSheet.create({
formatCard: { formatCard: {
flex: 1, flex: 1,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
}, },
@ -563,7 +563,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 14, paddingVertical: 14,
}, },
timeLabel: { timeLabel: {
@ -594,8 +594,8 @@ const styles = StyleSheet.create({
}, },
previewCard: { previewCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
previewTitle: { previewTitle: {
fontSize: 14, fontSize: 14,
@ -624,7 +624,7 @@ const styles = StyleSheet.create({
recentRow: { recentRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
}, },
recentIconWrap: { recentIconWrap: {
@ -659,7 +659,7 @@ const styles = StyleSheet.create({
statusTag: { statusTag: {
paddingHorizontal: 6, paddingHorizontal: 6,
paddingVertical: 2, paddingVertical: 2,
borderRadius: 8, borderRadius: spacing.card,
}, },
statusCompleted: { statusCompleted: {
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
@ -700,7 +700,7 @@ const styles = StyleSheet.create({
right: 0, right: 0,
bottom: 0, bottom: 0,
backgroundColor: colors.white, backgroundColor: colors.white,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingTop: 12, paddingTop: 12,
paddingBottom: 24, paddingBottom: 24,
borderTopWidth: 1, borderTopWidth: 1,

81
app/progress.tsx

@ -1,7 +1,7 @@
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { import {
ActivityIndicator, ActivityIndicator,
Pressable, Pressable,
@ -12,7 +12,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useJobProgress } from '../src/hooks'; import { useJobProgress } from '../src/hooks';
import { colors, headerGradientShort } from '../src/theme'; import { colors, headerGradientShort , spacing } from '../src/theme';
import type { JobProgressCompany, JobProgressStage } from '../src/types'; import type { JobProgressCompany, JobProgressStage } from '../src/types';
const TABS: { key: 'all' | JobProgressStage; label: string }[] = [ const TABS: { key: 'all' | JobProgressStage; label: string }[] = [
@ -231,13 +231,28 @@ function ErrorState({ onRetry }: { onRetry: () => void }) {
export default function ProgressScreen() { export default function ProgressScreen() {
const router = useRouter(); const router = useRouter();
const { progress, loading, error, refresh } = useJobProgress(); const { progress, loading, error, refresh } = useJobProgress();
// 公司列表首屏抽样条数:列表过长时只列前 N 家,并提供「查看全部」入口(移动端长列表标准做法)
const PREVIEW_COUNT = 5;
const [activeTab, setActiveTab] = useState<'all' | JobProgressStage>('all'); const [activeTab, setActiveTab] = useState<'all' | JobProgressStage>('all');
const [showAll, setShowAll] = useState(false);
const totalCompanies = progress?.companies.length ?? 0;
const companies = useMemo(() => { const companies = useMemo(() => {
if (!progress) return []; if (!progress) return [];
if (activeTab === 'all') return progress.companies; const list =
return progress.companies.filter((c) => c.stage === activeTab); activeTab === 'all'
}, [progress, activeTab]); ? progress.companies
: progress.companies.filter((c) => c.stage === activeTab);
// 「全部」页首屏只抽样前 N 家;切到具体阶段或展开后展示全量
if (activeTab === 'all' && !showAll) return list.slice(0, PREVIEW_COUNT);
return list;
}, [progress, activeTab, showAll]);
// 切换 Tab 时重置「展开全部」,回到抽样视图
useEffect(() => {
setShowAll(false);
}, [activeTab]);
// 标题文字跟随当前 Tab,与下方列表同源(见渲染处注释) // 标题文字跟随当前 Tab,与下方列表同源(见渲染处注释)
const activeLabel = TABS.find((tab) => tab.key === activeTab)?.label ?? '全部'; const activeLabel = TABS.find((tab) => tab.key === activeTab)?.label ?? '全部';
@ -286,7 +301,18 @@ export default function ProgressScreen() {
<Text style={styles.emptyHint}>投递岗位后,进度会在这里按阶段更新</Text> <Text style={styles.emptyHint}>投递岗位后,进度会在这里按阶段更新</Text>
</View> </View>
) : ( ) : (
companies.map((company) => <CompanyRow key={company.id} company={company} />) <>
{companies.map((company) => (
<CompanyRow key={company.id} company={company} />
))}
{/* 全部页且公司数超过抽样条数:提供「查看全部 N 家」入口,展开后渲染完整列表 */}
{activeTab === 'all' && totalCompanies > PREVIEW_COUNT && !showAll && (
<Pressable style={styles.viewAllBtn} onPress={() => setShowAll(true)} hitSlop={4}>
<Text style={styles.viewAllText}>查看全部 {totalCompanies} 家</Text>
<Ionicons name="chevron-forward" size={16} color={colors.primary} />
</Pressable>
)}
</>
)} )}
{progress.offers.length > 0 && ( {progress.offers.length > 0 && (
@ -318,10 +344,23 @@ const styles = StyleSheet.create({
}, },
headerBtn: { width: 40, alignItems: 'center' }, headerBtn: { width: 40, alignItems: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
body: { padding: 8, paddingBottom: 32 }, body: { padding: spacing.card, paddingBottom: 32 },
viewAllBtn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginTop: 8,
paddingVertical: 12,
},
viewAllText: {
fontSize: 14,
fontWeight: '600',
color: colors.primary,
marginRight: 2,
},
statsCard: { statsCard: {
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
statsRow: { flexDirection: 'row', justifyContent: 'space-between' }, statsRow: { flexDirection: 'row', justifyContent: 'space-between' },
@ -332,8 +371,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.actionBorder, borderColor: colors.actionBorder,
@ -357,8 +396,8 @@ const styles = StyleSheet.create({
tabTextActive: { color: colors.white, fontWeight: '600' }, tabTextActive: { color: colors.white, fontWeight: '600' },
emptyBox: { emptyBox: {
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
}, },
@ -374,8 +413,8 @@ const styles = StyleSheet.create({
sectionTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary }, sectionTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary },
companyCard: { companyCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -409,8 +448,8 @@ const styles = StyleSheet.create({
nextAction: { fontSize: 12, color: colors.textSecondary }, nextAction: { fontSize: 12, color: colors.textSecondary },
offerCard: { offerCard: {
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.successBorder, borderColor: colors.successBorder,
@ -420,7 +459,7 @@ const styles = StyleSheet.create({
offerTag: { offerTag: {
backgroundColor: colors.success, backgroundColor: colors.success,
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
}, },
offerTagText: { fontSize: 11, fontWeight: '700', color: colors.white }, offerTagText: { fontSize: 11, fontWeight: '700', color: colors.white },
@ -430,8 +469,8 @@ const styles = StyleSheet.create({
offerDeadline: { fontSize: 12, color: colors.actionText, marginLeft: 4 }, offerDeadline: { fontSize: 12, color: colors.actionText, marginLeft: 4 },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginTop: 8, marginTop: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -454,6 +493,6 @@ const styles = StyleSheet.create({
barValue: { width: 20, fontSize: 11, color: colors.textSecondary, textAlign: 'right' }, barValue: { width: 20, fontSize: 11, color: colors.textSecondary, textAlign: 'right' },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { paddingHorizontal: 18, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight }, retryBtn: { paddingHorizontal: 18, paddingVertical: spacing.card, borderRadius: 16, backgroundColor: colors.primaryLight },
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
}); });

28
app/resume.tsx

@ -15,7 +15,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useResume } from '../src/hooks'; import { useResume } from '../src/hooks';
import { RESUME_EVAL_MAX_LENGTH, UNSET_LABEL } from '../src/constants'; import { RESUME_EVAL_MAX_LENGTH, UNSET_LABEL } from '../src/constants';
import { colors } from '../src/theme'; import {colors, spacing} from '../src/theme';
import type { Resume as ResumeType, ResumeExperience, ResumeOptimizePoint, ResumeProject } from '../src/types'; import type { Resume as ResumeType, ResumeExperience, ResumeOptimizePoint, ResumeProject } from '../src/types';
import { formatDateTime } from '../src/utils'; import { formatDateTime } from '../src/utils';
@ -886,23 +886,23 @@ const styles = StyleSheet.create({
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
headerActions: { flexDirection: 'row', gap: 4 }, headerActions: { flexDirection: 'row', gap: 4 },
headerActionBtn: { width: 36, alignItems: 'center' }, headerActionBtn: { width: 36, alignItems: 'center' },
body: { padding: 8, paddingBottom: 100 }, body: { padding: spacing.card, paddingBottom: 100 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { paddingHorizontal: 18, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight }, retryBtn: { paddingHorizontal: 18, paddingVertical: spacing.card, borderRadius: 16, backgroundColor: colors.primaryLight },
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
}, },
scoreCard: { scoreCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -926,7 +926,7 @@ const styles = StyleSheet.create({
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
borderRadius: 16, borderRadius: 16,
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
optimizeBtnDisabled: { backgroundColor: colors.textDisabled }, optimizeBtnDisabled: { backgroundColor: colors.textDisabled },
optimizeBtnText: { fontSize: 14, fontWeight: '700', color: colors.white }, optimizeBtnText: { fontSize: 14, fontWeight: '700', color: colors.white },
@ -986,7 +986,7 @@ const styles = StyleSheet.create({
alignSelf: 'flex-start', alignSelf: 'flex-start',
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
}, },
intentionTagText: { fontSize: 12, color: colors.successText, fontWeight: '600' }, intentionTagText: { fontSize: 12, color: colors.successText, fontWeight: '600' },
@ -1014,9 +1014,9 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
marginTop: 8, marginTop: 8,
backgroundColor: colors.infoLight, backgroundColor: colors.infoLight,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
aiTipText: { flex: 1, fontSize: 12, color: colors.infoText, marginLeft: 6 }, aiTipText: { flex: 1, fontSize: 12, color: colors.infoText, marginLeft: 6 },
aiOptimizeMiniBtn: { aiOptimizeMiniBtn: {
@ -1033,7 +1033,7 @@ const styles = StyleSheet.create({
marginTop: 8, marginTop: 8,
backgroundColor: colors.successLight, backgroundColor: colors.successLight,
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 2, paddingVertical: 2,
}, },
optimizedText: { fontSize: 12, color: colors.successText, marginLeft: 4 }, optimizedText: { fontSize: 12, color: colors.successText, marginLeft: 4 },
@ -1057,7 +1057,7 @@ const styles = StyleSheet.create({
right: 0, right: 0,
bottom: 0, bottom: 0,
flexDirection: 'row', flexDirection: 'row',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,

4
app/resume/basic.tsx

@ -5,7 +5,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { PrimaryButton } from '../../components/onboarding/ui/PrimaryButton'; import { PrimaryButton } from '../../components/onboarding/ui/PrimaryButton';
import { CenterState, EditHeader, Field, FormNote, PageFooter } from '../../components/resume/EditKit'; import { CenterState, EditHeader, Field, FormNote, PageFooter } from '../../components/resume/EditKit';
import { useResume } from '../../src/hooks'; import { useResume } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import { isValidEmail, isValidPhone } from '../../src/utils'; import { isValidEmail, isValidPhone } from '../../src/utils';
/** /**
@ -131,6 +131,6 @@ interface BasicForm {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.pageBg }, container: { flex: 1, backgroundColor: colors.pageBg },
body: { padding: 8, paddingBottom: 24 }, body: { padding: spacing.card, paddingBottom: 24 },
formError: { fontSize: 13, color: colors.dangerText, marginBottom: 8 }, formError: { fontSize: 13, color: colors.dangerText, marginBottom: 8 },
}); });

8
app/resume/education.tsx

@ -12,7 +12,7 @@ import {
PageFooter, PageFooter,
} from '../../components/resume/EditKit'; } from '../../components/resume/EditKit';
import { useResume } from '../../src/hooks'; import { useResume } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { ResumeEducation } from '../../src/types'; import type { ResumeEducation } from '../../src/types';
/** /**
@ -250,13 +250,13 @@ const toForm = (edu: ResumeEducation): EduForm => ({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.pageBg }, container: { flex: 1, backgroundColor: colors.pageBg },
body: { padding: 8, paddingBottom: 24 }, body: { padding: spacing.card, paddingBottom: 24 },
row: { row: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,

8
app/resume/experience.tsx

@ -13,7 +13,7 @@ import {
PageFooter, PageFooter,
} from '../../components/resume/EditKit'; } from '../../components/resume/EditKit';
import { useResume } from '../../src/hooks'; import { useResume } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { ResumeExperience } from '../../src/types'; import type { ResumeExperience } from '../../src/types';
import { splitYearMonth, yearMonthValue } from '../../src/utils'; import { splitYearMonth, yearMonthValue } from '../../src/utils';
@ -296,7 +296,7 @@ const toForm = (exp: ResumeExperience): ExpForm => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.pageBg }, container: { flex: 1, backgroundColor: colors.pageBg },
body: { padding: 8, paddingBottom: 24 }, body: { padding: spacing.card, paddingBottom: 24 },
section: { marginBottom: 18 }, section: { marginBottom: 18 },
label: { fontSize: 15, fontWeight: '700', color: colors.textPrimary }, label: { fontSize: 15, fontWeight: '700', color: colors.textPrimary },
labelTop: { marginTop: 14, marginBottom: 10 }, labelTop: { marginTop: 14, marginBottom: 10 },
@ -308,8 +308,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,

26
app/resume/export.tsx

@ -23,7 +23,7 @@ import {
RESUME_TEMPLATES, RESUME_TEMPLATES,
} from '../../src/constants'; } from '../../src/constants';
import { useResume, useResumeExport } from '../../src/hooks'; import { useResume, useResumeExport } from '../../src/hooks';
import { colors, templateAccentColors } from '../../src/theme'; import {colors, templateAccentColors, spacing} from '../../src/theme';
import type { ResumeExportFormat, ResumeExportSettings } from '../../src/types'; import type { ResumeExportFormat, ResumeExportSettings } from '../../src/types';
const FORMATS: { key: ResumeExportFormat; label: string; icon: keyof typeof Ionicons.glyphMap }[] = [ const FORMATS: { key: ResumeExportFormat; label: string; icon: keyof typeof Ionicons.glyphMap }[] = [
@ -622,15 +622,15 @@ const styles = StyleSheet.create({
headerBtn: { width: 60, justifyContent: 'center' }, headerBtn: { width: 60, justifyContent: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
headerSave: { fontSize: 14, color: colors.primary, textAlign: 'right' }, headerSave: { fontSize: 14, color: colors.primary, textAlign: 'right' },
body: { padding: 8, paddingBottom: 100 }, body: { padding: spacing.card, paddingBottom: 100 },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { paddingHorizontal: 18, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight }, retryBtn: { paddingHorizontal: 18, paddingVertical: spacing.card, borderRadius: 16, backgroundColor: colors.primaryLight },
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
previewCard: { previewCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
@ -661,8 +661,8 @@ const styles = StyleSheet.create({
formatCard: { formatCard: {
flex: 1, flex: 1,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -678,8 +678,8 @@ const styles = StyleSheet.create({
templateCard: { templateCard: {
flex: 1, flex: 1,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -688,7 +688,7 @@ const styles = StyleSheet.create({
templateThumb: { templateThumb: {
width: '100%', width: '100%',
height: 80, height: 80,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primary, backgroundColor: colors.primary,
padding: 12, padding: 12,
marginBottom: 8, marginBottom: 8,
@ -700,8 +700,8 @@ const styles = StyleSheet.create({
templateNameActive: { color: colors.primaryText, fontWeight: '700' }, templateNameActive: { color: colors.primaryText, fontWeight: '700' },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
}, },
@ -790,7 +790,7 @@ const styles = StyleSheet.create({
right: 0, right: 0,
bottom: 0, bottom: 0,
flexDirection: 'row', flexDirection: 'row',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 12, paddingVertical: 12,
backgroundColor: colors.white, backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,

6
app/resume/preview.tsx

@ -5,7 +5,7 @@ import { PrimaryButton } from '../../components/onboarding/ui/PrimaryButton';
import { CenterState, EditHeader, PageFooter } from '../../components/resume/EditKit'; import { CenterState, EditHeader, PageFooter } from '../../components/resume/EditKit';
import { useResume } from '../../src/hooks'; import { useResume } from '../../src/hooks';
import { UNSET_LABEL } from '../../src/constants'; import { UNSET_LABEL } from '../../src/constants';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { Resume, ResumeEducation, ResumeExperience, ResumeProject } from '../../src/types'; import type { Resume, ResumeEducation, ResumeExperience, ResumeProject } from '../../src/types';
/** /**
@ -198,10 +198,10 @@ function hasIntention(intention: Resume['jobIntention']): boolean {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.pageBg }, container: { flex: 1, backgroundColor: colors.pageBg },
body: { padding: 8, paddingBottom: 24 }, body: { padding: spacing.card, paddingBottom: 24 },
paper: { paper: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 20, padding: 20,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,

8
app/resume/project.tsx

@ -12,7 +12,7 @@ import {
PageFooter, PageFooter,
} from '../../components/resume/EditKit'; } from '../../components/resume/EditKit';
import { useResume } from '../../src/hooks'; import { useResume } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { ResumeProject } from '../../src/types'; import type { ResumeProject } from '../../src/types';
/** /**
@ -205,13 +205,13 @@ const toForm = (proj: ResumeProject): ProjForm => ({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.pageBg }, container: { flex: 1, backgroundColor: colors.pageBg },
body: { padding: 8, paddingBottom: 24 }, body: { padding: spacing.card, paddingBottom: 24 },
row: { row: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,

14
app/settings.tsx

@ -2,7 +2,7 @@ import { Ionicons } from '@expo/vector-icons';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { colors } from '../src/theme'; import { colors , spacing } from '../src/theme';
import { useAppCache } from '../src/hooks'; import { useAppCache } from '../src/hooks';
import { formatBytes } from '../src/utils'; import { formatBytes } from '../src/utils';
import { useAuth } from '../src/context/AuthContext'; import { useAuth } from '../src/context/AuthContext';
@ -240,7 +240,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
headerBtn: { headerBtn: {
width: 36, width: 36,
@ -261,7 +261,7 @@ const styles = StyleSheet.create({
}, },
group: { group: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
groupTitle: { groupTitle: {
marginBottom: 8, marginBottom: 8,
@ -271,8 +271,8 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
row: { row: {
flexDirection: 'row', flexDirection: 'row',
@ -287,7 +287,7 @@ const styles = StyleSheet.create({
iconWrap: { iconWrap: {
width: 32, width: 32,
height: 32, height: 32,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@ -308,7 +308,7 @@ const styles = StyleSheet.create({
marginTop: 24, marginTop: 24,
marginHorizontal: 8, marginHorizontal: 8,
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 8, borderRadius: spacing.card,
paddingVertical: 14, paddingVertical: 14,
alignItems: 'center', alignItems: 'center',
}, },

18
app/settings/about.tsx

@ -3,7 +3,7 @@ import { useRouter } from 'expo-router';
import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useAboutInfo } from '../../src/hooks'; import { useAboutInfo } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -124,7 +124,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -145,14 +145,14 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
brandCard: { brandCard: {
alignItems: 'center', alignItems: 'center',
paddingVertical: 8, paddingVertical: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
brandLogo: { brandLogo: {
@ -176,9 +176,9 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
cardTitle: { cardTitle: {
@ -251,7 +251,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },

14
app/settings/account-cancel.tsx

@ -11,7 +11,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useAccountCancel } from '../../src/hooks'; import { useAccountCancel } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -180,7 +180,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -203,7 +203,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
stateBox: { stateBox: {
@ -222,7 +222,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -235,8 +235,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
warnText: { warnText: {
@ -256,7 +256,7 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
overflow: 'hidden', overflow: 'hidden',
}, },
bulletRow: { bulletRow: {

12
app/settings/account.tsx

@ -3,7 +3,7 @@ import { useRouter } from 'expo-router';
import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useAccountSecurity } from '../../src/hooks'; import { useAccountSecurity } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -104,7 +104,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -125,7 +125,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
tip: { tip: {
@ -137,8 +137,8 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
row: { row: {
flexDirection: 'row', flexDirection: 'row',
@ -188,7 +188,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },

20
app/settings/delete-data.tsx

@ -12,7 +12,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useDeleteAllData } from '../../src/hooks'; import { useDeleteAllData } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -199,7 +199,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -222,7 +222,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
stateBox: { stateBox: {
@ -241,7 +241,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -252,8 +252,8 @@ const styles = StyleSheet.create({
}, },
introCard: { introCard: {
backgroundColor: colors.infoLight, backgroundColor: colors.infoLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
introText: { introText: {
@ -272,9 +272,9 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
overflow: 'hidden', overflow: 'hidden',
padding: 8, padding: spacing.card,
}, },
row: { row: {
flexDirection: 'row', flexDirection: 'row',
@ -319,8 +319,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'flex-start', alignItems: 'flex-start',
gap: 8, gap: 8,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
warnText: { warnText: {

12
app/settings/notification.tsx

@ -3,7 +3,7 @@ import { useRouter } from 'expo-router';
import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Switch, Text, View } from 'react-native'; import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Switch, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useNotificationSettings } from '../../src/hooks'; import { useNotificationSettings } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -90,7 +90,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -111,7 +111,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
tip: { tip: {
@ -123,8 +123,8 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
row: { row: {
flexDirection: 'row', flexDirection: 'row',
@ -165,7 +165,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },

12
app/settings/password.tsx

@ -6,7 +6,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { useChangePassword } from '../../src/hooks'; import { useChangePassword } from '../../src/hooks';
import { useAuth } from '../../src/context/AuthContext'; import { useAuth } from '../../src/context/AuthContext';
import { PasswordField } from '../../src/components/PasswordField'; import { PasswordField } from '../../src/components/PasswordField';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -132,7 +132,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -155,7 +155,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
tip: { tip: {
@ -167,9 +167,9 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
gap: { gap: {
height: 6, height: 6,

16
app/settings/privacy.tsx

@ -6,7 +6,7 @@ import { usePrivacyCenter } from '../../src/hooks';
import { buildPrivacySummaryText } from '../../src/api'; import { buildPrivacySummaryText } from '../../src/api';
import { shareText } from '../../src/lib/shareFile'; import { shareText } from '../../src/lib/shareFile';
import { ErrorState, LoadingState } from '../../src/components/StateView'; import { ErrorState, LoadingState } from '../../src/components/StateView';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { PrivacyLevelInfo, PrivacySettingItem, PrivacySection } from '../../src/types'; import type { PrivacyLevelInfo, PrivacySettingItem, PrivacySection } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -206,7 +206,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -227,12 +227,12 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
levelCard: { levelCard: {
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
levelRow: { levelRow: {
@ -276,7 +276,7 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
overflow: 'hidden', overflow: 'hidden',
}, },
row: { row: {
@ -321,8 +321,8 @@ const styles = StyleSheet.create({
}, },
explanationCard: { explanationCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
explanationTitle: { explanationTitle: {

18
app/settings/privacy/[id].tsx

@ -11,7 +11,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { usePrivacyDetailSetting } from '../../../src/hooks'; import { usePrivacyDetailSetting } from '../../../src/hooks';
import { colors } from '../../../src/theme'; import { colors , spacing } from '../../../src/theme';
import type { PrivacyDetailOption } from '../../../src/types'; import type { PrivacyDetailOption } from '../../../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -159,7 +159,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -182,7 +182,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
stateBox: { stateBox: {
@ -199,7 +199,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -210,8 +210,8 @@ const styles = StyleSheet.create({
}, },
introCard: { introCard: {
backgroundColor: colors.infoLight, backgroundColor: colors.infoLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
introText: { introText: {
@ -230,7 +230,7 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
overflow: 'hidden', overflow: 'hidden',
}, },
row: { row: {
@ -266,8 +266,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'flex-start', alignItems: 'flex-start',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
gap: 8, gap: 8,
}, },
noteText: { noteText: {

20
app/settings/privacy/dnd.tsx

@ -12,7 +12,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useDndSetting } from '../../../src/hooks'; import { useDndSetting } from '../../../src/hooks';
import { colors } from '../../../src/theme'; import { colors , spacing } from '../../../src/theme';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -165,7 +165,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -188,7 +188,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 24, paddingBottom: 24,
}, },
stateBox: { stateBox: {
@ -205,7 +205,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -215,8 +215,8 @@ const styles = StyleSheet.create({
color: colors.white, color: colors.white,
}, },
statCard: { statCard: {
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
marginBottom: 8, marginBottom: 8,
}, },
@ -240,7 +240,7 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
overflow: 'hidden', overflow: 'hidden',
}, },
row: { row: {
@ -276,14 +276,14 @@ const styles = StyleSheet.create({
fontWeight: '600', fontWeight: '600',
color: colors.primary, color: colors.primary,
paddingVertical: 4, paddingVertical: 4,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
}, },
noteCard: { noteCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'flex-start', alignItems: 'flex-start',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
gap: 8, gap: 8,
}, },
noteText: { noteText: {

22
app/settings/privacy/keywords.tsx

@ -14,7 +14,7 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useKeywordBlock } from '../../../src/hooks'; import { useKeywordBlock } from '../../../src/hooks';
import { colors } from '../../../src/theme'; import {colors, spacing} from '../../../src/theme';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -185,7 +185,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -208,7 +208,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 16, paddingBottom: 16,
}, },
stateBox: { stateBox: {
@ -225,7 +225,7 @@ const styles = StyleSheet.create({
retryBtn: { retryBtn: {
marginTop: 16, marginTop: 16,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 18, borderRadius: 18,
backgroundColor: colors.primaryBtn, backgroundColor: colors.primaryBtn,
}, },
@ -235,8 +235,8 @@ const styles = StyleSheet.create({
color: colors.white, color: colors.white,
}, },
statCard: { statCard: {
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
marginBottom: 8, marginBottom: 8,
}, },
@ -251,8 +251,8 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
}, },
cardTitle: { cardTitle: {
@ -309,8 +309,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'flex-start', alignItems: 'flex-start',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
gap: 8, gap: 8,
}, },
noteText: { noteText: {
@ -334,7 +334,7 @@ const styles = StyleSheet.create({
height: 40, height: 40,
borderRadius: 20, borderRadius: 20,
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
fontSize: 14, fontSize: 14,
color: colors.textPrimary, color: colors.textPrimary,
}, },

20
app/settings/qualifications.tsx

@ -4,7 +4,7 @@ import { Image, Pressable, ScrollView, StyleSheet, Text, View } from 'react-nati
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { EmptyState, ErrorState, LoadingState } from '../../src/components/StateView'; import { EmptyState, ErrorState, LoadingState } from '../../src/components/StateView';
import { useQualifications } from '../../src/hooks'; import { useQualifications } from '../../src/hooks';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
import type { QualificationLicense } from '../../src/types'; import type { QualificationLicense } from '../../src/types';
/* ===================== 子组件:顶部导航 ===================== */ /* ===================== 子组件:顶部导航 ===================== */
@ -127,7 +127,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
height: 48, height: 48,
backgroundColor: colors.white, backgroundColor: colors.white,
borderBottomWidth: 1, borderBottomWidth: 1,
@ -148,16 +148,16 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
content: { content: {
padding: 8, padding: spacing.card,
paddingBottom: 32, paddingBottom: 32,
gap: 8, gap: 8,
}, },
companyCard: { companyCard: {
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.primaryBorder, borderColor: colors.primaryBorder,
padding: 8, padding: spacing.card,
}, },
companyLabel: { companyLabel: {
fontSize: 12, fontSize: 12,
@ -179,10 +179,10 @@ const styles = StyleSheet.create({
}, },
card: { card: {
backgroundColor: colors.surface, backgroundColor: colors.surface,
borderRadius: 8, borderRadius: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
padding: 8, padding: spacing.card,
}, },
cardHeader: { cardHeader: {
flexDirection: 'row', flexDirection: 'row',
@ -198,21 +198,21 @@ const styles = StyleSheet.create({
marginTop: 8, marginTop: 8,
width: '100%', width: '100%',
height: 180, height: 180,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
}, },
/* 图片占位:用「提醒卡片底 + 弱警示描边」语义色,一眼看出「这里是待办」 */ /* 图片占位:用「提醒卡片底 + 弱警示描边」语义色,一眼看出「这里是待办」 */
imagePlaceholder: { imagePlaceholder: {
marginTop: 8, marginTop: 8,
height: 132, height: 132,
borderRadius: 8, borderRadius: spacing.card,
borderWidth: 1, borderWidth: 1,
borderStyle: 'dashed', borderStyle: 'dashed',
borderColor: colors.actionBorder, borderColor: colors.actionBorder,
backgroundColor: colors.actionLight, backgroundColor: colors.actionLight,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingHorizontal: 8, paddingHorizontal: spacing.card,
gap: 8, gap: 8,
}, },
placeholderText: { placeholderText: {

20
app/tools/salary-calculator.tsx

@ -16,7 +16,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { useSalaryCalculator, useSalaryRecords, useSalaryToolLinks } from '../../src/hooks'; import { useSalaryCalculator, useSalaryRecords, useSalaryToolLinks } from '../../src/hooks';
import { clampSocialBase } from '../../src/api'; import { clampSocialBase } from '../../src/api';
import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView'; import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView';
import { colors, headerGradientShort } from '../../src/theme'; import { colors, headerGradientShort , spacing } from '../../src/theme';
import { import {
CITIES, CITIES,
DEFAULT_SOCIAL_PROFILE, DEFAULT_SOCIAL_PROFILE,
@ -569,11 +569,11 @@ const styles = StyleSheet.create({
headerBtn: { width: 60, justifyContent: 'center' }, headerBtn: { width: 60, justifyContent: 'center' },
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
headerAction: { fontSize: 14, color: colors.primary, textAlign: 'right' }, headerAction: { fontSize: 14, color: colors.primary, textAlign: 'right' },
body: { padding: 8, paddingBottom: 32 }, body: { padding: spacing.card, paddingBottom: 32 },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -594,7 +594,7 @@ const styles = StyleSheet.create({
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 8, paddingVertical: spacing.card,
}, },
input: { input: {
minWidth: 80, minWidth: 80,
@ -607,8 +607,8 @@ const styles = StyleSheet.create({
selectBox: { flexDirection: 'row', alignItems: 'center' }, selectBox: { flexDirection: 'row', alignItems: 'center' },
selectValue: { fontSize: 15, fontWeight: '600', color: colors.textPrimary, marginRight: 4 }, selectValue: { fontSize: 15, fontWeight: '600', color: colors.textPrimary, marginRight: 4 },
resultCard: { resultCard: {
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
alignItems: 'center', alignItems: 'center',
marginBottom: 8, marginBottom: 8,
}, },
@ -637,15 +637,15 @@ const styles = StyleSheet.create({
taxItem: { taxItem: {
flex: 1, flex: 1,
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
}, },
taxItemLabel: { fontSize: 12, color: colors.textSecondary, marginBottom: 6 }, taxItemLabel: { fontSize: 12, color: colors.textSecondary, marginBottom: 6 },
taxItemValue: { fontSize: 18, fontWeight: '800', color: colors.textPrimary }, taxItemValue: { fontSize: 18, fontWeight: '800', color: colors.textPrimary },
taxRow: { taxRow: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingVertical: 8, paddingVertical: spacing.card,
}, },
taxRowLabel: { fontSize: 14, color: colors.textPrimary }, taxRowLabel: { fontSize: 14, color: colors.textPrimary },
taxRowValue: { fontSize: 15, fontWeight: '700', color: colors.textPrimary }, taxRowValue: { fontSize: 15, fontWeight: '700', color: colors.textPrimary },

48
app/tools/skill-cloud.tsx

@ -14,7 +14,7 @@ import {
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { IndustrySkillWordCloud } from '../../components/IndustrySkillWordCloud'; import { IndustrySkillWordCloud } from '../../components/IndustrySkillWordCloud';
import { useSkillCloud, useSkillLearningPath } from '../../src/hooks'; import { useSkillCloud, useSkillLearningPath } from '../../src/hooks';
import { colors } from '../../src/theme'; import {colors, spacing} from '../../src/theme';
import type { SkillLearningPath, SkillWord, TopSkill } from '../../src/types'; import type { SkillLearningPath, SkillWord, TopSkill } from '../../src/types';
/** 技能分组 → 中文标签(用于选中详情) */ /** 技能分组 → 中文标签(用于选中详情) */
@ -526,7 +526,7 @@ const styles = StyleSheet.create({
// flexShrink: 0 是必需的:搜索框出现后父容器变矮,默认 flexShrink:1 会把这条横向 // flexShrink: 0 是必需的:搜索框出现后父容器变矮,默认 flexShrink:1 会把这条横向
// ScrollView 压到内容高度以下,胶囊文字被纵向裁掉(点得到、看不见) // ScrollView 压到内容高度以下,胶囊文字被纵向裁掉(点得到、看不见)
tabsWrap: { backgroundColor: colors.white, maxHeight: 52, flexGrow: 0, flexShrink: 0 }, tabsWrap: { backgroundColor: colors.white, maxHeight: 52, flexGrow: 0, flexShrink: 0 },
tabs: { flexDirection: 'row', paddingHorizontal: 16, gap: 8, paddingVertical: 10 }, tabs: { flexDirection: 'row', paddingHorizontal: spacing.framework, gap: 8, paddingVertical: 10 },
tab: { tab: {
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 6, paddingVertical: 6,
@ -539,7 +539,7 @@ const styles = StyleSheet.create({
filterBar: { filterBar: {
flexDirection: 'row', flexDirection: 'row',
backgroundColor: colors.white, backgroundColor: colors.white,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingBottom: 12, paddingBottom: 12,
gap: 10, gap: 10,
}, },
@ -547,7 +547,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 8, borderRadius: spacing.card,
paddingHorizontal: 10, paddingHorizontal: 10,
paddingVertical: 5, paddingVertical: 5,
}, },
@ -564,13 +564,13 @@ const styles = StyleSheet.create({
categoryTabActive: { backgroundColor: colors.primaryLight, borderColor: colors.primary }, categoryTabActive: { backgroundColor: colors.primaryLight, borderColor: colors.primary },
categoryText: { fontSize: 12, color: colors.textSecondary }, categoryText: { fontSize: 12, color: colors.textSecondary },
categoryTextActive: { color: colors.primaryText, fontWeight: '600' }, categoryTextActive: { color: colors.primaryText, fontWeight: '600' },
body: { paddingHorizontal: 8, paddingBottom: 32 }, body: { paddingHorizontal: spacing.card, paddingBottom: 32 },
sectionTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginBottom: 10 }, sectionTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginBottom: 10 },
cloudCard: { cloudCard: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
paddingVertical: 8, paddingVertical: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
}, },
@ -579,8 +579,8 @@ const styles = StyleSheet.create({
card: { card: {
marginTop: 8, marginTop: 8,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
}, },
@ -588,7 +588,7 @@ const styles = StyleSheet.create({
skillRow: { skillRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingVertical: 8, paddingVertical: spacing.card,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: colors.borderLight, borderTopColor: colors.borderLight,
}, },
@ -623,8 +623,8 @@ const styles = StyleSheet.create({
matchCard: { matchCard: {
marginTop: 8, marginTop: 8,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
}, },
@ -655,7 +655,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
backgroundColor: colors.white, backgroundColor: colors.white,
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingBottom: 10, paddingBottom: 10,
}, },
searchInput: { searchInput: {
@ -663,16 +663,16 @@ const styles = StyleSheet.create({
backgroundColor: colors.pageBg, backgroundColor: colors.pageBg,
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 8, paddingVertical: spacing.card,
fontSize: 14, fontSize: 14,
color: colors.textPrimary, color: colors.textPrimary,
}, },
searchEmpty: { searchEmpty: {
alignItems: 'center', alignItems: 'center',
gap: 10, gap: 10,
padding: 8, padding: spacing.card,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
}, },
@ -688,18 +688,18 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 16, paddingHorizontal: spacing.framework,
paddingVertical: 14, paddingVertical: 14,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: colors.borderLight, borderBottomColor: colors.borderLight,
}, },
modalTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary }, modalTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary },
modalCenter: { alignItems: 'center', justifyContent: 'center', gap: 12, paddingVertical: 48 }, modalCenter: { alignItems: 'center', justifyContent: 'center', gap: 12, paddingVertical: 48 },
modalBody: { paddingHorizontal: 16, paddingBottom: 12 }, modalBody: { paddingHorizontal: spacing.framework, paddingBottom: 12 },
pathSummary: { pathSummary: {
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginTop: 8, marginTop: 8,
marginBottom: 8, marginBottom: 8,
}, },
@ -728,8 +728,8 @@ const styles = StyleSheet.create({
detailCard: { detailCard: {
marginTop: 8, marginTop: 8,
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.successBorder, borderColor: colors.successBorder,
}, },
@ -757,7 +757,7 @@ const styles = StyleSheet.create({
tip: { fontSize: 14, color: colors.textSecondary }, tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { retryBtn: {
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },

BIN
assets/brand-logo.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 60 KiB

BIN
assets/favicon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 KiB

After

Width:  |  Height:  |  Size: 60 KiB

BIN
assets/splash-icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

2
components/onboarding/screens/B1Wechat.tsx

@ -32,7 +32,7 @@ export function B1Wechat() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: spacing.block, paddingBottom: 40 }, 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 }, preview: { flexDirection: 'row', alignItems: 'center', gap: spacing.block, backgroundColor: colors.white, padding: spacing.card, borderRadius: spacing.card, borderWidth: 1, borderColor: colors.border },
previewText: { gap: 4 }, previewText: { gap: 4 },
nick: { fontSize: 15, fontWeight: '700', color: colors.textPrimary }, nick: { fontSize: 15, fontWeight: '700', color: colors.textPrimary },
tip: { fontSize: 12, color: colors.textHint }, tip: { fontSize: 12, color: colors.textHint },

6
components/onboarding/screens/P12Company.tsx

@ -1,5 +1,5 @@
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import { colors } from '../../../src/theme'; import { colors , spacing } from '../../../src/theme';
import { useOnboarding } from '../../../src/hooks'; import { useOnboarding } from '../../../src/hooks';
import { ScreenShell } from '../ui/ScreenShell'; import { ScreenShell } from '../ui/ScreenShell';
import { SearchInput } from '../ui/SearchInput'; import { SearchInput } from '../ui/SearchInput';
@ -34,8 +34,8 @@ export function P12Company() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
notice: { notice: {
marginTop: 8, marginTop: 8,
padding: 8, padding: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },
noticeText: { fontSize: 12, color: colors.primaryText, lineHeight: 18 }, noticeText: { fontSize: 12, color: colors.primaryText, lineHeight: 18 },

4
components/onboarding/screens/P17Reference.tsx

@ -80,8 +80,8 @@ const styles = StyleSheet.create({
swapText: { fontSize: 13, color: colors.primary, fontWeight: '600' }, swapText: { fontSize: 13, color: colors.primary, fontWeight: '600' },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
gap: spacing.block, gap: spacing.block,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,

2
components/onboarding/screens/P1Login.tsx

@ -76,7 +76,7 @@ export function P1Login() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
screen: { flex: 1, backgroundColor: colors.pageBg }, screen: { flex: 1, backgroundColor: colors.pageBg },
topGlow: { position: 'absolute', top: 0, left: 0, right: 0, height: 200 }, topGlow: { position: 'absolute', top: 0, left: 0, right: 0, height: 200 },
content: { flexGrow: 1, paddingHorizontal: 8, paddingBottom: spacing.block }, content: { flexGrow: 1, paddingHorizontal: spacing.card, paddingBottom: spacing.block },
brandArea: { alignItems: 'center', marginTop: '22%' }, brandArea: { alignItems: 'center', marginTop: '22%' },
flexSpacer: { flex: 1, minHeight: 28 }, flexSpacer: { flex: 1, minHeight: 28 },
quickBtn: { quickBtn: {

24
components/onboarding/screens/P2PasswordLogin.tsx

@ -7,6 +7,7 @@ import { useAgreementGate, useOnboarding, usePasswordLogin } from '../../../src/
import { isValidPhone } from '../../../src/utils'; import { isValidPhone } from '../../../src/utils';
import { useAuth } from '../../../src/context/AuthContext'; import { useAuth } from '../../../src/context/AuthContext';
import { useRole } from '../../../src/context/RoleContext'; import { useRole } from '../../../src/context/RoleContext';
import { fetchCurrentUser } from '../../../src/api';
import { PasswordField } from '../../../src/components/PasswordField'; import { PasswordField } from '../../../src/components/PasswordField';
import { ScreenShell } from '../ui/ScreenShell'; import { ScreenShell } from '../ui/ScreenShell';
import { AgreementCheckbox } from '../ui/AgreementCheckbox'; import { AgreementCheckbox } from '../ui/AgreementCheckbox';
@ -23,9 +24,9 @@ export function P2PasswordLogin() {
const { data, setData, navigate, back } = useOnboarding(); const { data, setData, navigate, back } = useOnboarding();
const { gate, modalProps } = useAgreementGate(); const { gate, modalProps } = useAgreementGate();
const { login, loading, error } = usePasswordLogin(); const { login, loading, error } = usePasswordLogin();
/** 登录态写入:换取令牌后必须落库,否则冷启动会被打回登录页 */ /** 双 Token 写入:换取令牌后必须落库,否则冷启动会被打回登录页 */
const { login: writeAuth } = useAuth(); const { setSession } = useAuth();
const { role } = useRole(); const { role, setRole } = useRole();
const router = useRouter(); const router = useRouter();
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
@ -39,15 +40,26 @@ export function P2PasswordLogin() {
if (!canSubmit || loading) return; if (!canSubmit || loading) return;
try { try {
const result = await login(phone, password); const result = await login(phone, password);
writeAuth(result.token); // 双 Token 落库(access + refresh),后续请求自动带 Bearer
await setSession(result.accessToken, result.refreshToken, result.expiresAt);
} catch { } catch {
// 失败原因由 usePasswordLogin 记录,页面下方展示 // 失败原因由 usePasswordLogin 记录,页面下方展示
return; return;
} }
// 角色以 /auth/me 后端返回为准(规范 §5),不一致时纠正路由
let landingRole = role;
try {
const me = await fetchCurrentUser();
setRole(me.role);
landingRole = me.role;
} catch {
// /auth/me 失败时退回到本地已选身份,不阻断登录
}
// 已有身份的老用户直接进对应主界面,新用户才去选身份 // 已有身份的老用户直接进对应主界面,新用户才去选身份
if (role) { if (landingRole) {
router.replace(role === 'enterprise' ? '/(enterprise)/job-management' : '/(tabs)/jobs'); router.replace(landingRole === 'enterprise' ? '/(enterprise)/job-management' : '/(tabs)/jobs');
return; return;
} }
navigate('P3'); navigate('P3');

2
components/onboarding/screens/P2PasswordReset.tsx

@ -55,7 +55,7 @@ export function P2PasswordReset() {
const sendCodeNow = async () => { const sendCodeNow = async () => {
if (!phoneValid) return; if (!phoneValid) return;
try { try {
await sendCode(phone); await sendCode(phone, 'password-reset');
} catch { } catch {
// 失败原因由 useSmsCode 记录,页面下方展示 // 失败原因由 useSmsCode 记录,页面下方展示
} }

4
components/onboarding/screens/P2PhoneCode.tsx

@ -39,7 +39,7 @@ export function P2PhoneCode() {
const requestCode = async () => { const requestCode = async () => {
if (!phoneValid) return; if (!phoneValid) return;
try { try {
await sendCode(phone); await sendCode(phone, 'login');
setStage('code'); setStage('code');
} catch { } catch {
// 失败原因由 useSmsCode 记录,页面下方统一展示 // 失败原因由 useSmsCode 记录,页面下方统一展示
@ -48,7 +48,7 @@ export function P2PhoneCode() {
/** 阶段2 重新获取(不改变阶段) */ /** 阶段2 重新获取(不改变阶段) */
const resendCode = () => { const resendCode = () => {
void sendCode(phone).catch(() => { void sendCode(phone, 'login').catch(() => {
// 同上,错误已记录 // 同上,错误已记录
}); });
}; };

2
components/onboarding/screens/P2WechatCode.tsx

@ -132,7 +132,7 @@ const styles = StyleSheet.create({
marginTop: 30, marginTop: 30,
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: spacing.sm, paddingVertical: spacing.sm,
borderRadius: 8, borderRadius: spacing.card,
backgroundColor: colors.borderLight, backgroundColor: colors.borderLight,
}, },
toastText: { fontSize: 13, color: colors.textDisabled }, toastText: { fontSize: 13, color: colors.textDisabled },

2
components/onboarding/screens/P3Identity.tsx

@ -70,7 +70,7 @@ export function P3Identity() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
heading: { fontSize: 22, fontWeight: '800', color: colors.textPrimary, textAlign: 'center', marginVertical: 16 }, heading: { fontSize: 22, fontWeight: '800', color: colors.textPrimary, textAlign: 'center', marginVertical: 16 },
card: { backgroundColor: colors.white, borderRadius: 8, padding: 8, alignItems: 'center', borderWidth: 1, borderColor: colors.border }, card: { backgroundColor: colors.white, borderRadius: spacing.card, padding: spacing.card, alignItems: 'center', borderWidth: 1, borderColor: colors.border },
cardTop: { marginBottom: 16 }, cardTop: { marginBottom: 16 },
cardBtn: { cardBtn: {
width: '100%', width: '100%',

2
components/onboarding/ui/BrandHeader.tsx

@ -14,7 +14,7 @@ export function BrandHeader({ slogan }: { slogan?: string }) {
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
wrap: { alignItems: 'center', gap: spacing.block, paddingVertical: 8 }, wrap: { alignItems: 'center', gap: spacing.block, paddingVertical: spacing.card },
name: { fontSize: 26, fontWeight: '800', color: colors.textPrimary }, name: { fontSize: 26, fontWeight: '800', color: colors.textPrimary },
slogan: { fontSize: 13, color: colors.textHint }, slogan: { fontSize: 13, color: colors.textHint },
}); });

4
components/onboarding/ui/CountdownButton.tsx

@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Pressable, StyleSheet, Text } from 'react-native'; import { Pressable, StyleSheet, Text } from 'react-native';
import { colors } from '../../../src/theme'; import { colors , spacing } from '../../../src/theme';
interface Props { interface Props {
disabled?: boolean; disabled?: boolean;
@ -47,7 +47,7 @@ export function CountdownButton({ disabled = false, onSend, seconds = 60 }: Prop
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
btn: { paddingHorizontal: 14, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight }, btn: { paddingHorizontal: 14, paddingVertical: spacing.card, borderRadius: 16, backgroundColor: colors.primaryLight },
btnDisabled: { backgroundColor: colors.borderLight }, btnDisabled: { backgroundColor: colors.borderLight },
text: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, text: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
textDisabled: { color: colors.textDisabled }, textDisabled: { color: colors.textDisabled },

4
components/onboarding/ui/Modal.tsx

@ -31,8 +31,8 @@ const styles = StyleSheet.create({
card: { card: {
width: '84%', width: '84%',
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
maxHeight: '80%', maxHeight: '80%',
}, },
title: { fontSize: 18, fontWeight: '800', color: colors.textPrimary, marginBottom: spacing.block, textAlign: 'center' }, title: { fontSize: 18, fontWeight: '800', color: colors.textPrimary, marginBottom: spacing.block, textAlign: 'center' },

8
components/onboarding/ui/RadioRow.tsx

@ -1,6 +1,6 @@
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { Pressable, StyleSheet, Text, View } from 'react-native'; import { Pressable, StyleSheet, Text, View } from 'react-native';
import { colors } from '../../../src/theme'; import { colors , spacing } from '../../../src/theme';
interface Props { interface Props {
label: string; label: string;
@ -29,9 +29,9 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingVertical: 8, paddingVertical: spacing.card,
paddingHorizontal: 8, paddingHorizontal: spacing.card,
borderRadius: 8, borderRadius: spacing.card,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
backgroundColor: colors.white, backgroundColor: colors.white,

2
components/onboarding/ui/ScreenShell.tsx

@ -47,6 +47,6 @@ const styles = StyleSheet.create({
titleInline: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, titleInline: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
titleCenter: { fontSize: 18, fontWeight: '800', color: colors.textPrimary, textAlign: 'center', paddingVertical: spacing.block }, titleCenter: { fontSize: 18, fontWeight: '800', color: colors.textPrimary, textAlign: 'center', paddingVertical: spacing.block },
scroll: { flex: 1 }, scroll: { flex: 1 },
scrollContent: { flexGrow: 1, padding: 8, paddingBottom: 28 }, scrollContent: { flexGrow: 1, padding: spacing.card, paddingBottom: 28 },
footer: { padding: 16, paddingBottom: spacing.section, backgroundColor: colors.pageBg }, footer: { padding: 16, paddingBottom: spacing.section, backgroundColor: colors.pageBg },
}); });

8
components/resume/EditKit.tsx

@ -7,7 +7,7 @@
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
import { colors } from '../../src/theme'; import { colors , spacing } from '../../src/theme';
/** 顶部导航:返回 + 居中标题(右侧留同宽占位,保证标题居中) */ /** 顶部导航:返回 + 居中标题(右侧留同宽占位,保证标题居中) */
export function EditHeader({ title, onBack }: { title: string; onBack: () => void }) { export function EditHeader({ title, onBack }: { title: string; onBack: () => void }) {
@ -176,8 +176,8 @@ const styles = StyleSheet.create({
note: { fontSize: 12, color: colors.textHint, lineHeight: 18 }, note: { fontSize: 12, color: colors.textHint, lineHeight: 18 },
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 8, borderRadius: spacing.card,
padding: 8, padding: spacing.card,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
@ -200,7 +200,7 @@ const styles = StyleSheet.create({
centerText: { fontSize: 14, color: colors.textSecondary }, centerText: { fontSize: 14, color: colors.textSecondary },
centerBtn: { centerBtn: {
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 8, paddingVertical: spacing.card,
borderRadius: 16, borderRadius: 16,
backgroundColor: colors.primaryLight, backgroundColor: colors.primaryLight,
}, },

109
docs/联调路径校正映射.md

@ -0,0 +1,109 @@
# 联调路径校正映射表(基于 job.youthome.com.cn OpenAPI 实测)
> 生成日期:2026-09-28 | 数据源:`scripts/openapi.json`(182 条真实路径 / 220 path-methods)
> 审计范围:api.ts 内全部 `request()` 调用(89 个函数)
> 结论:ALIGNED(已对齐)= 6,WRONG→REAL(路径错但有真实等价)= 14,PHANTOM(幽灵域,后端无契约)= 69
## 一、已对齐(无需改路径)
| 函数 | 真实路径 | 原标记 |
|---|---|---|
| `createJobReview` | `POST /jobs/{jobId}/reviews` | 已校正 |
| `fetchCompareData` | `/jobs/compare` | 已校正 |
| `fetchEmployeeReviews` | `GET /jobs/{jobId}/reviews` | 已校正 |
| `fetchJobDetail` | `GET /jobs/{jobId}` | 已校正 |
| `fetchOfferCompareData` | `GET /offers/compare` | 【待确认】 |
| `fetchUserProfile` | `GET /profile/me` | 已校正 |
## 二、路径错但有真实等价端点(需改路径)
| 函数 | 当前(错) | 应改为(真实) | 说明 |
|---|---|---|---|
| `fetchCandidateDetail` | `/enterprise/candidates/${candidateId}` | `/company/candidates/{applicationId}` | 前端猜 /enterprise/candidates,真路径在 /company 域 |
| `fetchConversation` | `/chat/conversations/${id}` | `GET /conversations/{id}` | 前端猜 /chat/conversations,真路径 /conversations/{id} |
| `fetchConversations` | `/messages/conversations` | `GET /conversations` | 前端猜 /messages/conversations,真 /conversations |
| `fetchInterviewScore` | `/enterprise/interviews/${interviewId}/score` | `GET /interviews/{interviewId}/score` | 前端猜 /enterprise/interviews,真路径在 /interviews 域 |
| `fetchInterviewScoreDraft` | `/enterprise/interviews/${interviewId}/score/draft` | `PUT /interviews/{interviewId}/score` | 真路径无 /draft 子路径,保存草稿用 PUT /interviews/{id}/score |
| `fetchInterviews` | `/messages/interviews` | `GET /interviews/me` | 前端猜 /messages/interviews,真 /interviews/me(我的面试列表) |
| `fetchJobPreference` | `/profile/me/job-preference` | `GET/PUT /profile/me/preferences` | 前端猜 /profile/me/job-preference,真路径 /profile/me/preferences |
| `fetchJobProgress` | `/tools/job-progress` | `GET /profile/me/career-progress` | 前端猜 /tools/job-progress,真 /profile/me/career-progress(统一求职进度聚合) |
| `fetchMatchResult` | `/messages/match-result` | `GET /applications/{id}/match 或 /jobs/{jobId}/match` | 前端猜 /messages/match-result,真为投递/岗位匹配诊断 |
| `fetchNotifications` | `/messages/notifications` | `GET /notifications` | 前端猜 /messages/notifications,真 /notifications |
| `fetchRankData` | `/jobs/rank` | `GET /jobs/{jobId}/competition-ranking` | 前端猜 /jobs/rank,真为按岗位竞争力排名,语义不同(需 jobId) |
| `fetchResume` | `/resume` | `GET /profile/me/resume` | 前端猜 /resume,真 /profile/me/resume(下载简历) |
| `saveJobPreference` | `/profile/me/job-preference` | `PUT /profile/me/preferences` | 同上 |
| `saveResumeBasicInfo` | `/resume/basic-info` | `POST /profile/me/resume (multipart)` | 前端猜 /resume/basic-info,真为上传简历文件(multipart),结构化字段在 PUT /profile/me |
## 三、幽灵域(后端 OpenAPI 无契约,必须退回 mock-only)
| 函数 | 当前(幽灵)路径 | 说明 |
|---|---|---|
| `deleteSalaryRecord` | `/salary/records/${id}` | 无 /salary 域,后端未提供 |
| `fetchAIInterviewData` | `/interview/config` | 未命中 OpenAPI |
| `fetchAITools` | `/growth/ai-tools` | 未命中 OpenAPI |
| `fetchAbilityAssessment` | `/growth/ability` | 未命中 OpenAPI |
| `fetchAboutInfo` | `/settings/about` | 未命中 OpenAPI |
| `fetchAccountCancel` | `/settings/account-cancel` | 未命中 OpenAPI |
| `fetchAccountSecurity` | `/settings/account-security` | 未命中 OpenAPI |
| `fetchAppealForm` | `/evaluation/appeal` | 未命中 OpenAPI |
| `fetchArticles` | `/growth/articles` | 未命中 OpenAPI |
| `fetchAuditReport` | `/operation/audit-report` | 未命中 OpenAPI |
| `fetchBadges` | `/user/badges` | 未命中 OpenAPI |
| `fetchDimensionScores` | `/user/dimension-scores` | 未命中 OpenAPI |
| `fetchDndSetting` | `/settings/privacy/dnd` | 未命中 OpenAPI |
| `fetchEnterpriseCertification` | `/enterprise/certification` | 未命中 OpenAPI |
| `fetchEnterpriseCommunication` | `/enterprise/communication` | 未命中 OpenAPI |
| `fetchEnterpriseEntry` | `/user/enterprise-entry` | 未命中 OpenAPI |
| `fetchEnterpriseOverview` | `/enterprise/overview` | 未命中 OpenAPI |
| `fetchGameLevels` | `/game/levels` | 未命中 OpenAPI |
| `fetchGameProgress` | `/game/progress` | 未命中 OpenAPI |
| `fetchHREvaluation` | `/evaluation/hr` | 未命中 OpenAPI |
| `fetchHollandAssessment` | `/growth/holland` | 未命中 OpenAPI |
| `fetchHollandIntro` | `/growth/holland-intro` | 未命中 OpenAPI |
| `fetchIdentityOptions` | `/tools/identity-options` | 未命中 OpenAPI |
| `fetchJobEvaluationEmptyState` | `/enterprise/job-evaluation-empty` | 未命中 OpenAPI |
| `fetchJobEvaluationManagement` | `/enterprise/job-evaluations` | 未命中 OpenAPI |
| `fetchJobManagement` | `/enterprise/job-management` | 未命中 OpenAPI |
| `fetchJobOverallScore` | `/jobs/${id}/score` | 无 /jobs/{id}/score,综合分见 /jobs/{id}/competition-ranking 或 /profile/me/candidate-scoring |
| `fetchJobPublishForm` | `/enterprise/jobs/publish-form` | 未命中 OpenAPI |
| `fetchKeywordBlock` | `/settings/privacy/keywords` | 未命中 OpenAPI |
| `fetchLegalBrand` | `/legal/brand` | 未命中 OpenAPI |
| `fetchLegalConversation` | `/legal/conversations/default` | 未命中 OpenAPI |
| `fetchLegalDocument` | `/settings/legal-document/${id}` | 无 /legal 域,后端未提供 |
| `fetchLegalFAQs` | `/legal/faqs` | 未命中 OpenAPI |
| `fetchLegalMembership` | `/legal/membership` | 未命中 OpenAPI |
| `fetchLegalOrders` | `/legal/orders` | 未命中 OpenAPI |
| `fetchLegalPlans` | `/legal/plans` | 未命中 OpenAPI |
| `fetchLegalServiceRequests` | `/legal/service-requests` | 未命中 OpenAPI |
| `fetchLegalServices` | `/legal/services` | 未命中 OpenAPI |
| `fetchLegalTemplates` | `/legal/templates` | 未命中 OpenAPI |
| `fetchMenuItems` | `/user/menus` | 未命中 OpenAPI |
| `fetchNotificationSettings` | `/settings/notification` | 未命中 OpenAPI |
| `fetchOperationRules` | `/operation/rules` | 未命中 OpenAPI |
| `fetchOverallScore` | `/user/overall-score` | 未命中 OpenAPI |
| `fetchPendingOfferCount` | `/offers/pending/count` | 无 /offers/pending/count,可用 /offers/statistics 替代(需确认) |
| `fetchPersonalDataExport` | `/settings/personal-data-export` | 未命中 OpenAPI |
| `fetchPrivacyCenter` | `/settings/privacy-center` | 未命中 OpenAPI |
| `fetchPrivacyDetailSetting` | `/settings/privacy/${id}` | 无 /settings 域,后端未提供 |
| `fetchPrivacySettings` | `/settings/privacy` | 未命中 OpenAPI |
| `fetchQualifications` | `/settings/qualifications` | 未命中 OpenAPI |
| `fetchResumeExportSettings` | `/resume/export-settings` | 无 /resume/export-settings,导出设置本地维护 |
| `fetchResumeSavedAt` | `/resume/saved-at` | 无 /resume/saved-at,简历保存时间可由 /profile/me/resume-versions 派生 |
| `fetchSalaryBrackets` | `/tools/salary-brackets` | 未命中 OpenAPI |
| `fetchSalaryData` | `/jobs/${id}/salary` | 无 /jobs/{id}/salary,后端未提供薪资明细端点 |
| `fetchSalaryRecords` | `/salary/records` | 未命中 OpenAPI |
| `fetchSalaryToolLinks` | `/tools/salary-related` | 未命中 OpenAPI |
| `fetchSkillCloud` | `/tools/skill-cloud` | 未命中 OpenAPI |
| `fetchSkillLearningPath` | `/tools/skill-learning-path` | 未命中 OpenAPI |
| `fetchTalentRecommendation` | `/enterprise/talent-recommendation` | 未命中 OpenAPI |
| `fetchUserStats` | `/user/stats` | 未命中 OpenAPI |
| `fetchWeightSettings` | `/jobs/weight-settings` | 无 /jobs/weight-settings,权重见 /profile/me/review-weights、/review-weight-templates |
| `saveResumeEducation` | `/resume/education` | 无 /resume/education,简历整体由 /profile/me/resume + resume-versions 管理 |
| `saveResumeExperience` | `/resume/experience` | 同上 |
| `saveResumeExportSettings` | `/resume/export-settings` | 未命中 OpenAPI |
| `saveResumeProjects` | `/resume/projects` | 同上 |
| `saveResumeSelfEvaluation` | `/resume/self-evaluation` | 同上 |
| `saveResumeSkills` | `/resume/skills` | 同上 |
| `saveResumeSnapshot` | `/resume/save` | 同上 |
| `saveSalaryRecord` | `/salary/records` | 无 /salary/records |
| `saveWeightSettings` | `/weight-settings` | 无 /weight-settings |

83
scripts/audit_all_paths.py

@ -0,0 +1,83 @@
import json, re
with open("scripts/openapi.json", encoding="utf-8") as f:
spec = json.load(f)
real_paths = set()
for p, methods in spec.get("paths", {}).items():
for m in methods:
real_paths.add(p)
# Load api.ts
with open("src/api/api.ts", encoding="utf-8") as f:
text = f.read()
lines = text.splitlines()
func_re = re.compile(r"^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(")
# static literal request path
static_req = re.compile(r"request(?:<[^>]*>)?\(\s*['\"]([^'\"]+)['\"]")
# template literal request path like request(`/x/${..}`)
tmpl_req = re.compile(r"request(?:<[^>]*>)?\(\s*`([^`]*)`")
cur = None
funcs = {} # name -> {static:[], tmpl:[], dai:bool, line}
for i, ln in enumerate(lines):
m = func_re.match(ln)
if m:
cur = m.group(1)
funcs[cur] = {"static": [], "tmpl": [], "dai": False, "line": i+1}
if cur:
for sm in static_req.finditer(ln):
funcs[cur]["static"].append(sm.group(1))
for tm in tmpl_req.finditer(ln):
funcs[cur]["tmpl"].append(tm.group(1))
if "【待确认】" in ln:
funcs[cur]["dai"] = True
# Determine if a guessed path has ANY real backing (prefix match ignoring method)
def has_real_backing(path):
# exact
if path in real_paths:
return True
# base segment match: first 2 segments
segs = [s for s in path.split("/") if s and not "{" in s and not "$" in s]
if len(segs) >= 1:
base = "/" + segs[0]
if any(rp.startswith(base + "/") or rp == base for rp in real_paths):
return "partial"
return False
phantom_static = []
ok_static = []
partial_static = []
tmpl_funcs = []
for name, info in funcs.items():
if not info["static"] and not info["tmpl"]:
continue
for sp in info["static"]:
b = has_real_backing(sp)
if b is True:
ok_static.append((name, sp))
elif b == "partial":
partial_static.append((name, sp))
else:
phantom_static.append((name, sp))
for tp in info["tmpl"]:
tmpl_funcs.append((name, tp))
print("DISTINCT FUNCTIONS WITH request():", sum(1 for n,i in funcs.items() if i["static"] or i["tmpl"]))
print()
print("=== PHANTOM STATIC PATHS (no OpenAPI backing at all) ===")
for n, p in sorted(set(phantom_static)):
dai = "【待确认】" if funcs[n]["dai"] else "(已标'已校正'但仍是幽灵!)"
print(f" {n:42} -> {p:48} {dai}")
print(f"\nPHANTOM count (unique fn-path): {len(set(phantom_static))}")
print(f"OK count: {len(set(ok_static))}")
print(f"PARTIAL count: {len(set(partial_static))}")
print()
print("=== PARTIAL (base domain exists, path differs) ===")
for n, p in sorted(set(partial_static)):
print(f" {n:42} -> {p}")
print()
print("=== TEMPLATE-LITERAL request paths (need manual method check) ===")
for n, p in sorted(set(tmpl_funcs)):
print(f" {n:42} -> {p}")

119
scripts/docx_text.txt

@ -0,0 +1,119 @@
职来后端接口联调说明
前端开发使用 线上联调环境 2026年9月22日
结论:前端统一通过 HTTPS 域名访问后端,业务请求前缀为 https://job.youthome.com.cn/api/v1。除注册、登录、短信验证码、刷新 Token 等匿名接口外,其余接口需要在 Authorization 请求头携带 Access Token。
一 联调地址
[TABLE]
用途 | 地址 | 使用说明
业务接口基地址 | https://job.youthome.com.cn/api/v1 | 前端环境变量中的 API Base URL
健康检查 | https://job.youthome.com.cn/api/v1/health | 返回 code=200 且 data.status=UP 表示后端可用
OpenAPI JSON | https://job.youthome.com.cn/api/v1/v3/api-docs | Apifox 导入或更新接口定义使用
WebSocket | wss://job.youthome.com.cn/api/v1/ws | 仅在对应实时功能启用时使用;不要使用 ws://
重要:不要直接请求服务器 IP、Jenkins 地址或 8010 端口。前端、Apifox 和浏览器都统一使用 HTTPS 域名,避免跨域、证书和环境切换问题。
二 Apifox 配置方法
在 Apifox 项目中选择导入数据,导入方式选 URL,填写 OpenAPI JSON 地址。
环境前置 URL 设置为 https://job.youthome.com.cn/api/v1。接口路径本身只保留 /auth/me、/jobs 等相对路径。
创建环境变量 AuthToken,用登录响应中的 accessToken 赋值。
需要认证的接口统一设置请求头 Authorization: Bearer {{AuthToken}}。
接口有调整时重新从 OpenAPI URL 增量导入,不要手工复制旧接口覆盖新定义。
三 通用请求和响应规则
[TABLE]
项目 | 规则
请求格式 | 普通业务接口使用 application/json;上传简历等文件接口使用 multipart/form-data。
认证请求头 | Authorization: Bearer <accessToken>。Refresh Token 不能代替 Access Token 访问业务接口。
请求追踪 | 可传 X-Request-Id;后端会在响应头和响应体返回 requestId,报错联调时请一并提供。
时间格式 | 按 OpenAPI 定义发送 ISO 8601 日期或日期时间,不要自行转换为中文日期。
分页 | 以接口定义中的 page、size 为准;不要在前端假设页码起点。
响应外层 | 通常为 code、message、data、requestId、timestamp。业务数据读取 data。
Authorization: Bearer {{AuthToken}}Content-Type: application/jsonX-Request-Id: web-20260922-0001
{ "code": 200, "message": "ok", "data": { }, "requestId": "...", "timestamp": "..."}
四 登录与会话使用顺序
[TABLE]
步骤 | 接口 | 前端处理
1 | POST /auth/sms/send | 按 purpose 发送验证码;purpose 必须使用后端支持的固定值。
2A | POST /auth/password/login | 手机号和密码登录;成功后保存 accessToken、refreshToken。
2B | POST /auth/login | 短信验证码登录;现接口文档标为 Mock 登录,生产前端不要作为正式入口,除非产品和后端明确确认。
3 | GET /auth/me | 登录后拉取用户 id、角色和当前身份信息。
4 | POST /auth/refresh | Access Token 过期时,用 Refresh Token 换取新 Token。
5 | POST /auth/logout | 退出登录;成功后清除前端保存的所有 Token 和用户状态。
Token 保存与刷新建议
Access Token 用于业务请求;Refresh Token 只提交给 /auth/refresh 和会话相关接口。
遇到 401 时只允许一次串行刷新;刷新成功后重放原请求,刷新失败则清理会话并跳转登录页。
不要把 Token 打印到控制台、错误上报、URL 查询参数或页面 DOM。
不同浏览器标签页同时刷新时要加前端互斥,避免多个 Refresh Token 并发重放。
五 学生 企业 管理员的区别
[TABLE]
身份 | 账号产生方式 | 登录方式 | 主要接口前缀
STUDENT | 现有短信登录流程可自动创建学生账号 | 短信登录或已设置密码后的密码登录 | /profile/me、/applications、/offers、/interviews
COMPANY | POST /auth/company/register 专用注册 | 注册成功直接返回 Token;之后可密码登录 | /companies/me、/company、/jobs
ADMIN | 不提供公开注册,由管理员初始化或后台受控创建 | 使用现有认证方式登录 | /admin
前端路由判断:登录成功后以 /auth/me 返回的角色为准。不要让客户端提交 role 来决定身份,也不要仅依据页面入口猜测用户角色。
六 企业注册与入驻流程
[TABLE]
顺序 | 接口 | 预期结果
1 | POST /auth/sms/send | purpose 使用 company-register,获取企业注册验证码。
2 | POST /auth/company/register | 提交手机号、验证码、密码和企业基础资料;成功返回 COMPANY Token。
3 | GET /auth/me | 确认当前角色为 COMPANY。
4 | GET /companies/me/onboarding | 读取 UNVERIFIED、PENDING、REJECTED 或 VERIFIED,以及 missingFields、allowedActions。
5 | PUT /companies/me | 根据 missingFields 完善企业资料。
6 | POST /companies/me/verification | 提交企业认证材料。
7 | GET /companies/me/onboarding | 刷新聚合状态;前端按钮以 allowedActions 为准。
企业注册请求示例
POST /auth/company/register{ "phone": "19900000000", "code": "123456", "password": "符合密码策略的密码", "company": { "name": "示例科技有限公司", "industry": "互联网", "description": "企业简介", "website": "https://example.com", "address": "企业地址" }}
注意:客户端不能提交 userId、role、verified 或 passwordHash。注册响应中的 accessToken 和 refreshToken 按普通登录响应处理。
七 常用业务接口导航
[TABLE]
业务模块 | 常用接口 | 认证要求
公开岗位 | GET /jobs、GET /jobs/{jobId}、GET /jobs/search、GET /jobs/filters | 公开读取;具体以 OpenAPI security 定义为准
学生资料 | GET/PUT /profile/me、POST/GET/DELETE /profile/me/resume | STUDENT
求职流程 | 投递、面试、Offer、消息等接口 | 登录后按角色和资源归属校验
企业资料 | GET/PUT /companies/me | COMPANY
企业入驻 | GET /companies/me/onboarding、POST/GET /companies/me/verification | COMPANY
企业岗位 | POST/PUT /jobs 及发布关闭接口 | COMPANY;部分能力可能要求完成认证
管理员 | /admin 下审核、风控、举报等接口 | ADMIN
完整字段、枚举、必填项、响应结构和全部接口以运行时 OpenAPI 为唯一联调依据。本文只提供入口和使用顺序。
八 常见状态码处理
[TABLE]
HTTP | 含义 | 前端建议
400 | 参数错误、验证码错误或业务输入不合法 | 展示安全业务文案;字段错误定位到表单项。
401 | 未登录、Token 无效或过期 | 尝试一次刷新;失败后清理会话并回登录页。
403 | 角色不允许或无资源权限 | 展示无权限页面,不要循环刷新 Token。
404 | 资源不存在 | 展示资源不存在或返回列表页。
409 | 账号、状态或并发冲突 | 刷新当前数据后提示用户重新操作。
429 | 请求过于频繁 | 禁用按钮并显示倒计时,不要立即自动重试。
500 | 服务内部错误 | 显示通用错误,并上报 requestId。
503 | 依赖服务不可用或功能暂不可用 | 提示稍后重试,避免高频轮询。
九 前端联调检查清单
环境 Base URL 仅配置一次,接口路径中不重复拼接 /api/v1。
登录后 /auth/me 能返回正确角色,Authorization 头格式正确。
401 刷新流程不会无限循环,403 不触发刷新。
企业注册先发送 company-register 用途验证码,再调用企业注册接口。
企业页面从 /companies/me/onboarding 获取状态和 allowedActions,不在前端自行推导认证状态。
上传接口使用 multipart/form-data,不手动覆盖浏览器生成的 boundary。
报错时保留 HTTP 状态、业务 code、message、requestId 和请求时间;删除 Token、手机号、验证码及密码后再发给后端排查。
联调前先访问健康检查;健康为 UP 后再判断具体业务接口问题。
十 安全和协作约定
禁止在前端代码、Git、群聊截图或 Apifox 公共变量中保存数据库、Redis、RabbitMQ、Jenkins 密码。
禁止把 Access Token、Refresh Token、短信验证码和用户密码写入日志或错误上报。
接口字段调整由后端更新 OpenAPI,前端重新导入;不要以聊天记录替代接口定义。
需要后端排查时提供:环境、接口、方法、HTTP 状态、业务 code、requestId、发生时间和脱敏请求。

36
scripts/extract_docx.py

@ -0,0 +1,36 @@
import zipfile, sys
from xml.etree import ElementTree as ET
path = "职来后端接口联调说明.docx"
z = zipfile.ZipFile(path)
xml = z.read("word/document.xml").decode("utf-8")
ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
root = ET.fromstring(xml)
def para_text(p):
texts = []
for t in p.iter("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}t"):
texts.append(t.text or "")
return "".join(texts)
body = root.find("w:body", ns)
out = []
for el in body:
tag = el.tag.split("}")[-1]
if tag == "p":
out.append(para_text(el))
elif tag == "tbl":
rows = []
for tr in el.iter("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tr"):
cells = [para_text(p) for p in tr.iter("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p")]
rows.append(" | ".join(cells))
out.append("\n[TABLE]\n" + "\n".join(rows) + "\n")
full = "\n".join(out)
with open("scripts/docx_text.txt", "w", encoding="utf-8") as f:
f.write(full)
print("TOTAL CHARS:", len(full))
for i, line in enumerate(out):
if any(k in line for k in ["OpenAPI", "接口", "路径", "endpoint", "/api", "GET", "POST", "§7", "七、"]):
print(f"[{i}] {line[:140]}")

156
scripts/gen_correction_map.py

@ -0,0 +1,156 @@
import json, re
with open("scripts/openapi.json", encoding="utf-8") as f:
spec = json.load(f)
real_paths = set()
real_path_methods = {}
for p, methods in spec.get("paths", {}).items():
real_paths.add(p)
for m in methods:
real_path_methods.setdefault(p, []).append(m.upper())
with open("src/api/api.ts", encoding="utf-8") as f:
lines = f.readlines()
func_re = re.compile(r"^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(")
static_req = re.compile(r"request(?:<[^>]*>)?\(\s*['\"]([^'\"]+)['\"]")
tmpl_req = re.compile(r"request(?:<[^>]*>)?\(\s*`([^`]*)`")
dai_re = re.compile(r"【待确认】")
cur = None
funcs = {}
for i, ln in enumerate(lines):
m = func_re.match(ln)
if m:
cur = m.group(1)
funcs[cur] = {"static": [], "tmpl": [], "dai": False, "line": i+1}
if cur:
for sm in static_req.finditer(ln):
funcs[cur]["static"].append(sm.group(1))
for tm in tmpl_req.finditer(ln):
funcs[cur]["tmpl"].append(tm.group(1))
if dai_re.search(ln):
funcs[cur]["dai"] = True
# Manual real-equivalent judgment for template/partial/path-wrong functions
# value: (verdict, real_path_or_None, note)
manual = {
# template literals - verify
"createJobReview": ("ALIGNED", "POST /jobs/{jobId}/reviews", "路径命中真实契约"),
"fetchEmployeeReviews": ("ALIGNED", "GET /jobs/{jobId}/reviews", "路径命中真实契约"),
"fetchJobDetail": ("ALIGNED", "GET /jobs/{jobId}", "路径命中真实契约"),
"fetchCandidateDetail": ("WRONG->REAL", "/company/candidates/{applicationId}", "前端猜 /enterprise/candidates,真路径在 /company 域"),
"fetchInterviewScore": ("WRONG->REAL", "GET /interviews/{interviewId}/score", "前端猜 /enterprise/interviews,真路径在 /interviews 域"),
"fetchInterviewScoreDraft": ("WRONG->REAL", "PUT /interviews/{interviewId}/score", "真路径无 /draft 子路径,保存草稿用 PUT /interviews/{id}/score"),
"fetchConversation": ("WRONG->REAL", "GET /conversations/{id}", "前端猜 /chat/conversations,真路径 /conversations/{id}"),
"deleteSalaryRecord": ("PHANTOM", None, "无 /salary 域,后端未提供"),
"fetchLegalDocument": ("PHANTOM", None, "无 /legal 域,后端未提供"),
"fetchPrivacyDetailSetting": ("PHANTOM", None, "无 /settings 域,后端未提供"),
"fetchSalaryData": ("PHANTOM", None, "无 /jobs/{id}/salary,后端未提供薪资明细端点"),
"fetchJobOverallScore": ("PHANTOM", None, "无 /jobs/{id}/score,综合分见 /jobs/{id}/competition-ranking 或 /profile/me/candidate-scoring"),
# partial static
"fetchJobPreference": ("WRONG->REAL", "GET/PUT /profile/me/preferences", "前端猜 /profile/me/job-preference,真路径 /profile/me/preferences"),
"saveJobPreference": ("WRONG->REAL", "PUT /profile/me/preferences", "同上"),
"fetchPendingOfferCount": ("PHANTOM", None, "无 /offers/pending/count,可用 /offers/statistics 替代(需确认)"),
"fetchRankData": ("WRONG->REAL", "GET /jobs/{jobId}/competition-ranking", "前端猜 /jobs/rank,真为按岗位竞争力排名,语义不同(需 jobId)"),
"fetchWeightSettings": ("PHANTOM", None, "无 /jobs/weight-settings,权重见 /profile/me/review-weights、/review-weight-templates"),
# already-correct static
"fetchJobList": ("ALIGNED", "GET /jobs", "命中真实契约"),
"fetchOfferCompareData": ("ALIGNED", "GET /offers/compare", "命中真实契约"),
"fetchUserProfile": ("ALIGNED", "GET /profile/me", "命中真实契约"),
# path-wrong (phantom base) that have real equivalents elsewhere
"fetchConversations": ("WRONG->REAL", "GET /conversations", "前端猜 /messages/conversations,真 /conversations"),
"fetchNotifications": ("WRONG->REAL", "GET /notifications", "前端猜 /messages/notifications,真 /notifications"),
"fetchInterviews": ("WRONG->REAL", "GET /interviews/me", "前端猜 /messages/interviews,真 /interviews/me(我的面试列表)"),
"fetchMatchResult": ("WRONG->REAL", "GET /applications/{id}/match 或 /jobs/{jobId}/match", "前端猜 /messages/match-result,真为投递/岗位匹配诊断"),
"fetchResume": ("WRONG->REAL", "GET /profile/me/resume", "前端猜 /resume,真 /profile/me/resume(下载简历)"),
"fetchFavorites": ("WRONG->REAL", "GET /jobs/favorites", "前端猜 /profile/me/favorites,真 /jobs/favorites"),
"fetchJobProgress": ("WRONG->REAL", "GET /profile/me/career-progress", "前端猜 /tools/job-progress,真 /profile/me/career-progress(统一求职进度聚合)"),
"fetchResumeSavedAt": ("PHANTOM", None, "无 /resume/saved-at,简历保存时间可由 /profile/me/resume-versions 派生"),
"fetchResumeExportSettings": ("PHANTOM", None, "无 /resume/export-settings,导出设置本地维护"),
"saveResumeBasicInfo": ("WRONG->REAL", "POST /profile/me/resume (multipart)", "前端猜 /resume/basic-info,真为上传简历文件(multipart),结构化字段在 PUT /profile/me"),
"saveResumeEducation": ("PHANTOM", None, "无 /resume/education,简历整体由 /profile/me/resume + resume-versions 管理"),
"saveResumeExperience": ("PHANTOM", None, "同上"),
"saveResumeProjects": ("PHANTOM", None, "同上"),
"saveResumeSelfEvaluation": ("PHANTOM", None, "同上"),
"saveResumeSkills": ("PHANTOM", None, "同上"),
"saveResumeSnapshot": ("PHANTOM", None, "同上"),
"saveSalaryRecord": ("PHANTOM", None, "无 /salary/records"),
"saveWeightSettings": ("PHANTOM", None, "无 /weight-settings"),
}
def base_has_real(p):
segs = [s for s in p.split("/") if s and "{" not in s and "$" not in s]
if not segs:
return False
base = "/" + segs[0]
return any(rp == base or rp.startswith(base + "/") for rp in real_paths)
verdicts = {}
for name, info in funcs.items():
if not info["static"] and not info["tmpl"]:
continue
if name in manual:
v, rp, note = manual[name]
verdicts[name] = (v, rp, note, info["dai"], info["line"])
continue
# auto
paths = info["static"] + info["tmpl"]
# for static-only auto
allphantom = True
anyok = False
for p in info["static"]:
if p in real_paths:
anyok = True; allphantom = False
elif base_has_real(p):
allphantom = False
if anyok:
verdicts[name] = ("ALIGNED", ", ".join(info["static"]), "命中真实契约", info["dai"], info["line"])
elif allphantom is False and info["static"]:
verdicts[name] = ("PHANTOM", ", ".join(info["static"]), "父域不存在于 OpenAPI", info["dai"], info["line"])
else:
verdicts[name] = ("PHANTOM", ", ".join(paths), "未命中 OpenAPI", info["dai"], info["line"])
# also catch functions flagged dai but no request captured (e.g. buildPrivacySummaryText) - skip
order = {"ALIGNED":0, "WRONG->REAL":1, "PHANTOM":2}
rows = sorted(verdicts.items(), key=lambda kv: (order.get(kv[1][0],9), kv[0]))
from collections import Counter
c = Counter(v[0] for v in verdicts.values())
print("SUMMARY:", dict(c), "TOTAL FUNCS MAPPED:", len(verdicts))
md = ["# 联调路径校正映射表(基于 job.youthome.com.cn OpenAPI 实测)", ""]
md.append(f"> 生成日期:2026-09-28 | 数据源:`scripts/openapi.json`(182 条真实路径 / 220 path-methods)")
md.append(f"> 审计范围:api.ts 内全部 `request()` 调用({len(verdicts)} 个函数)")
md.append(f"> 结论:ALIGNED(已对齐)= {c.get('ALIGNED',0)},WRONG→REAL(路径错但有真实等价)= {c.get('WRONG->REAL',0)},PHANTOM(幽灵域,后端无契约)= {c.get('PHANTOM',0)}")
md.append("")
md.append("## 一、已对齐(无需改路径)")
md.append("")
md.append("| 函数 | 真实路径 | 原标记 |")
md.append("|---|---|---|")
for n,(v,rp,note,dai,line) in rows:
if v=="ALIGNED":
md.append(f"| `{n}` | `{rp}` | {'【待确认】' if dai else '已校正'} |")
md.append("")
md.append("## 二、路径错但有真实等价端点(需改路径)")
md.append("")
md.append("| 函数 | 当前(错) | 应改为(真实) | 说明 |")
md.append("|---|---|---|---|")
for n,(v,rp,note,dai,line) in rows:
if v=="WRONG->REAL":
curpath = ", ".join(funcs[n]["static"]+funcs[n]["tmpl"])
md.append(f"| `{n}` | `{curpath}` | `{rp}` | {note} |")
md.append("")
md.append("## 三、幽灵域(后端 OpenAPI 无契约,必须退回 mock-only)")
md.append("")
md.append("| 函数 | 当前(幽灵)路径 | 说明 |")
md.append("|---|---|---|")
for n,(v,rp,note,dai,line) in rows:
if v=="PHANTOM":
curpath = ", ".join(funcs[n]["static"]+funcs[n]["tmpl"])
md.append(f"| `{n}` | `{curpath}` | {note} |")
md.append("")
with open("docs/联调路径校正映射.md", "w", encoding="utf-8") as f:
f.write("\n".join(md))
print("WROTE docs/联调路径校正映射.md")

76
scripts/live_validate.py

@ -0,0 +1,76 @@
import json, urllib.request, urllib.error
BASE = "https://job.youthome.com.cn/api/v1"
def req(method, path, token=None, body=None):
url = BASE + path
data = json.dumps(body).encode() if body is not None else None
r = urllib.request.Request(url, data=data, method=method)
r.add_header("Content-Type", "application/json")
if token:
r.add_header("Authorization", "Bearer " + token)
try:
with urllib.request.urlopen(r, timeout=20) as resp:
raw = resp.read().decode()
status = resp.status
except urllib.error.HTTPError as e:
raw = e.read().decode(errors="replace")
status = e.code
except Exception as e:
return None, str(e), None
try:
j = json.loads(raw)
except Exception:
return status, raw[:200], None
return status, j, raw
# 1) login
s, j, _ = req("POST", "/auth/password/login", body={"phone": "19900009021", "password": "ZhilaiTest!2026#A"})
token = (j or {}).get("data", {}).get("accessToken") if j else None
print("LOGIN:", s, "hasToken=", bool(token), "role=", (j or {}).get("data", {}).get("role"), "expiresIn=", (j or {}).get("data", {}).get("en"))
print("="*100)
# 2) validate ID-less GET endpoints (student token)
idless = [
("GET", "/jobs", "fetchJobList(ALIGNED)"),
("GET", "/offers/compare", "fetchOfferCompareData(ALIGNED)"),
("GET", "/profile/me", "fetchUserProfile(ALIGNED)"),
("GET", "/conversations", "fetchConversations(WRONG->REAL /conversations)"),
("GET", "/notifications", "fetchNotifications(WRONG->REAL /notifications)"),
("GET", "/notifications/unread-count", "bonus real"),
("GET", "/interviews/me", "fetchInterviews(WRONG->REAL /interviews/me)"),
("GET", "/jobs/favorites", "fetchFavorites(WRONG->REAL /jobs/favorites)"),
("GET", "/profile/me/career-progress", "fetchJobProgress(WRONG->REAL /profile/me/career-progress)"),
("GET", "/profile/me/preferences", "fetchJobPreference(WRONG->REAL /profile/me/preferences)"),
("GET", "/profile/me/resume", "fetchResume(WRONG->REAL /profile/me/resume)"),
("GET", "/profile/me/candidate-scoring", "bonus real"),
("GET", "/applications/me", "bonus real /applications/me"),
("GET", "/assessments/me", "bonus real /assessments/me"),
("GET", "/abilities/me", "bonus real /abilities/me"),
]
for m, p, label in idless:
s, j, _ = req(m, p, token)
code = (j or {}).get("code") if isinstance(j, dict) else None
data = (j or {}).get("data")
dtype = type(data).__name__
keys = list(data.keys())[:12] if isinstance(data, dict) else (f"len={len(data)}" if isinstance(data, list) else None)
print(f"{m:4} {p:42} [{label}]")
print(f" http={s} code={code} data_type={dtype} sample={keys}")
print("="*100)
# 3) ID-needing endpoints: best-effort with placeholder id (status ambiguous if 404)
idneed = [
("GET", "/jobs/1", "fetchJobDetail(ALIGNED) needs jobId"),
("GET", "/jobs/1/reviews", "fetchEmployeeReviews(ALIGNED)"),
("GET", "/jobs/1/competition-ranking", "fetchRankData(WRONG->REAL)"),
("GET", "/jobs/1/match", "fetchMatchResult alt"),
("GET", "/conversations/1", "fetchConversation(WRONG->REAL /conversations/{id})"),
("GET", "/applications/1/match", "fetchMatchResult(WRONG->REAL)"),
("GET", "/company/candidates/1", "fetchCandidateDetail(WRONG->REAL, ENTERPRISE role)"),
("GET", "/interviews/1/score", "fetchInterviewScore(WRONG->REAL)"),
]
for m, p, label in idneed:
s, j, _ = req(m, p, token)
code = (j or {}).get("code") if isinstance(j, dict) else None
msg = (j or {}).get("message") if isinstance(j, dict) else None
print(f"{m:4} {p:42} [{label}] http={s} code={code} msg={msg}")

1
scripts/openapi.json

File diff suppressed because one or more lines are too long

20
scripts/openapi_paths.py

@ -0,0 +1,20 @@
import json
with open("scripts/openapi.json", encoding="utf-8") as f:
spec = json.load(f)
paths = spec.get("paths", {})
rows = []
for p, methods in sorted(paths.items()):
for m, op in methods.items():
if m.lower() not in ("get","post","put","delete","patch"):
continue
summary = op.get("summary") or op.get("description") or ""
tags = ",".join(op.get("tags", []))
rows.append((m.upper(), p, summary, tags))
print(f"TOTAL PATH-METHODS: {len(rows)}")
print(f"TOTAL DISTINCT PATHS: {len(set(r[1] for r in rows))}")
print("="*100)
for m, p, s, t in rows:
print(f"{m:6} {p:55} [{t}] {s[:40]}")

188
scripts/transform_api.py

@ -0,0 +1,188 @@
import json, re
with open("scripts/openapi.json", encoding="utf-8") as f:
spec = json.load(f)
real_paths = set()
for p, methods in spec.get("paths", {}).items():
for m in methods:
real_paths.add(p)
real_base = set()
for p in real_paths:
seg = [s for s in p.split("/") if s and "{" not in s and "$" not in s]
if seg:
real_base.add("/" + seg[0])
with open("src/api/api.ts", encoding="utf-8") as f:
raw = f.read()
lines = raw.split("\n") # keep as list, we will rejoin
func_re = re.compile(r"^(\s*)(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(")
static_req = re.compile(r"request(?:<[^>]*>)?\(\s*['\"]([^'\"]+)['\"]")
tmpl_req = re.compile(r"request(?:<[^>]*>)?\(\s*`([^`]*)`")
# find function start indices
starts = []
for i, ln in enumerate(lines):
m = func_re.match(ln)
if m:
starts.append((m.group(2), i))
func_ranges = []
for idx, (name, s) in enumerate(starts):
e = starts[idx+1][1] if idx+1 < len(starts) else len(lines)
func_ranges.append((name, s, e))
PATH_FIX_STATIC = {
"fetchConversations": ("'/messages/conversations'", "'/conversations'"),
"fetchNotifications": ("'/messages/notifications'", "'/notifications'"),
"fetchInterviews": ("'/messages/interviews'", "'/interviews/me'"),
"fetchJobPreference": ("'/profile/me/job-preference'", "'/profile/me/preferences'"),
"saveJobPreference": ("'/profile/me/job-preference'", "'/profile/me/preferences'"),
"fetchResume": ("'/resume'", "'/profile/me/resume'"),
"fetchFavorites": ("'/profile/me/favorites'", "'/jobs/favorites'"),
}
JOBPROGRESS_OLD = "'/tools/job-progress', { method: 'GET', auth: false }"
JOBPROGRESS_NEW = "'/profile/me/career-progress', { method: 'GET' }"
TEMPLATE_FIX = {
"fetchRankData": ("/jobs/rank", "/jobs/${jobId}/competition-ranking"),
"fetchCandidateDetail": ("/enterprise/candidates/${candidateId}", "/company/candidates/${candidateId}"),
"fetchInterviewScore": ("/enterprise/interviews/${interviewId}/score", "/interviews/${interviewId}/score"),
"fetchInterviewScoreDraft": ("/enterprise/interviews/${interviewId}/score/draft", "/interviews/${interviewId}/score"),
"fetchConversation": ("/chat/conversations/${id}", "/conversations/${id}"),
}
def get_info(sl):
static = []; tmpl = []
hmi = any("if (isMockMode())" in l for l in sl)
hnmi = any("if (!isMockMode())" in l for l in sl)
for l in sl:
m = static_req.search(l)
if m: static.append(m.group(1))
t = tmpl_req.search(l)
if t: tmpl.append(t.group(1))
return static, tmpl, hmi, hnmi
def is_phantom(p):
if p in real_paths: return False
segs = [s for s in p.split("/") if s and "{" not in s and "$" not in s]
base = "/" + segs[0] if segs else ""
return base not in real_base
DAI_RE = re.compile(r'【待确认[^】]*】')
def strip_dai(line):
if DAI_RE.search(line):
return DAI_RE.sub('', line).rstrip() + "\n"
return line
changed = {"pathfix": [], "revert": [], "strip_only": []}
new_full = []
def find_close(sl, open_i):
depth = 0
for j in range(open_i, len(sl)):
depth += sl[j].count("{") - sl[j].count("}")
if depth == 0 and j > open_i:
return j
return None
# preserve leading lines before the first function (imports, top-level consts, banners)
new_full = list(lines[0:func_ranges[0][1]])
for name, s, e in func_ranges:
sl = lines[s:e]
static, tmpl, hmi, hnmi = get_info(sl)
in_openapi = any(p in real_paths for p in static) or any(not is_phantom(t) for t in tmpl)
dai = any("【待确认】" in l for l in sl)
is_pathfix = name in PATH_FIX_STATIC or name in TEMPLATE_FIX or name == "fetchJobProgress"
phantom = (dai or any(is_phantom(p) for p in static) or any(is_phantom(t) for t in tmpl)) and not in_openapi
if is_pathfix:
out = []
for ln in sl:
if name in PATH_FIX_STATIC:
o, n = PATH_FIX_STATIC[name]
if o in ln: ln = ln.replace(o, n)
ou = o.strip("'\"")
nu = n.strip("'\"")
if ou in ln: ln = ln.replace(ou, nu)
elif name == "fetchJobProgress":
if JOBPROGRESS_OLD in ln: ln = ln.replace(JOBPROGRESS_OLD, JOBPROGRESS_NEW)
if "/tools/job-progress" in ln: ln = ln.replace("/tools/job-progress", "/profile/me/career-progress")
elif name in TEMPLATE_FIX:
o, n = TEMPLATE_FIX[name]
if o in ln: ln = ln.replace(o, n)
ou = o.replace("`", "")
nu = n.replace("`", "")
if ou in ln: ln = ln.replace(ou, nu)
out.append(strip_dai(ln))
new_full.extend(out)
changed["pathfix"].append(name)
continue
if not phantom:
if dai:
new_full.extend(strip_dai(ln) for ln in sl)
else:
new_full.extend(sl)
continue
# REVERT (phantom -> always mock)
if hnmi:
out = []
i = 0
while i < len(sl):
if "if (!isMockMode())" in sl[i]:
ci = find_close(sl, i)
i = (ci + 1) if ci is not None else i + 1
continue
out.append(strip_dai(sl[i]))
i += 1
new_full.extend(out)
changed["revert"].append(name)
else:
# pattern A: collapse if(isMockMode()){...} and drop trailing return request
out = []
i = 0
collapsed = False
while i < len(sl):
if "if (isMockMode())" in sl[i]:
ci = find_close(sl, i)
if ci is not None:
inner = sl[i+1:ci]
inner_out = []
for il in inner:
if "return request" in il:
continue
if il.lstrip().startswith("//") and "【待确认】" in il:
continue
inner_out.append(il)
inner_out = [l[2:] if l.startswith(" ") else l for l in inner_out]
out.append(" // 后端未提供 OpenAPI 契约:保持 mock(翻真实开关也不触网),待后端就绪再接真实分支")
out.extend(inner_out)
i = ci + 1
collapsed = True
continue
if "return request" in sl[i]:
i += 1
continue
out.append(strip_dai(sl[i]))
i += 1
if not collapsed:
# dai but no isMockMode block and no real branch -> just strip
changed["strip_only"].append(name)
else:
changed["revert"].append(name)
new_full.extend(out)
# preserve trailing lines after the last function
new_full.extend(lines[func_ranges[-1][2]:])
with open("src/api/api.ts", "w", encoding="utf-8") as f:
f.write("\n".join(new_full))
print("PATHFIX:", len(changed["pathfix"]))
print("REVERT:", len(changed["revert"]))
print("STRIP_ONLY:", len(changed["strip_only"]), changed["strip_only"])
print("--- revert ---")
for n in changed["revert"]:
print(" ", n)

249
src/__tests__/backend-bridge-remaining.test.ts

@ -0,0 +1,249 @@
/**
* 后端桥接(剩余域)consolidated 测试
* 覆盖第三轮之后批量转换的 62 个 fetch 函数:
* - 真实态(setMockMode(false)):仅对已与后端 OpenAPI 对齐的函数断言 request 命中真实路径
* (见 REAL_FUNCS;其余为幽灵域,已退回 mock-only,翻真实开关也不触网,不在此断言)
* - mock 态(默认):断言返回非 undefined(防止 mock 分支被意外破坏)
* 路径依据 scripts/openapi.json(job.youthome.com.cn)实测校正,不再标【待确认】。
*/
// expo-constants:extra 为空 → 走默认生产域名;mock 开关由 setMockMode 控制
jest.mock('expo-constants', () => ({ expoConfig: { extra: {} } }));
// token-store:内存双 Token,避免依赖 AsyncStorage
jest.mock('../lib/token-store', () => {
let mem: { accessToken: string; refreshToken: string; expiresAt?: number } | null = null;
return {
getAccessToken: jest.fn(() => mem?.accessToken ?? null),
getTokens: jest.fn(() => mem),
setTokens: jest.fn((a: string, r: string, e?: number) => {
mem = { accessToken: a, refreshToken: r, expiresAt: e };
}),
clearTokens: jest.fn(() => {
mem = null;
}),
hydrateTokens: jest.fn(async () => undefined),
};
});
// storage:内存实现,避免依赖 AsyncStorage
jest.mock('../lib/storage', () => {
const m = new Map<string, string>();
return {
storageGet: jest.fn(async (k: string) => m.get(k) ?? null),
storageSet: jest.fn(async (k: string, v: string) => {
m.set(k, v);
}),
storageRemove: jest.fn(async (k: string) => {
m.delete(k);
}),
};
});
import {
fetchArticles,
fetchMenuItems,
fetchEnterpriseEntry,
fetchRankData,
fetchSkillCloud,
fetchSkillLearningPath,
fetchSalaryToolLinks,
fetchSalaryBrackets,
fetchOperationRules,
fetchHollandIntro,
fetchIdentityOptions,
fetchGameLevels,
fetchLegalBrand,
fetchLegalServices,
fetchLegalFAQs,
fetchLegalTemplates,
fetchLegalPlans,
fetchAboutInfo,
fetchQualifications,
fetchAITools,
fetchHistory,
fetchHollandAssessment,
fetchHollandResult,
fetchAbilityAssessment,
fetchUserStats,
fetchOverallScore,
fetchDimensionScores,
fetchBadges,
fetchOfferCompareData,
fetchPendingOfferCount,
fetchWeightSettings,
fetchSalaryRecords,
fetchAIInterviewData,
fetchHREvaluation,
fetchAppealForm,
fetchEnterpriseOverview,
fetchJobPublishForm,
fetchJobEvaluationManagement,
fetchCandidateDetail,
fetchInterviewScore,
fetchInterviewScoreDraft,
fetchAuditReport,
fetchEnterpriseCertification,
fetchJobManagement,
fetchTalentRecommendation,
fetchEnterpriseCommunication,
fetchJobEvaluationEmptyState,
fetchGameProgress,
fetchLegalMembership,
fetchLegalOrders,
fetchLegalServiceRequests,
fetchLegalConversation,
fetchPrivacySettings,
fetchPrivacyCenter,
fetchPrivacyDetailSetting,
fetchDndSetting,
fetchKeywordBlock,
fetchAccountCancel,
fetchLegalDocument,
fetchPersonalDataExport,
fetchNotificationSettings,
fetchAccountSecurity,
} from '../api/api';
import { setMockMode } from '../api/http';
// [fn, args, expectedPath, mockSmoke]
type Entry = [
(...a: any[]) => Promise<any>,
any[],
string,
boolean,
];
const FUNCS: Entry[] = [
// 公开读取(auth:false)
[fetchArticles, [], '/growth/articles', true],
[fetchMenuItems, ['candidate'], '/user/menus', true],
[fetchEnterpriseEntry, [], '/user/enterprise-entry', true],
[fetchRankData, ['1'], '/jobs/rank', false],
[fetchSkillCloud, ['it'], '/tools/skill-cloud', false],
[fetchSkillLearningPath, ['it'], '/tools/skill-learning-path', false],
[fetchSalaryToolLinks, [], '/tools/salary-related', true],
[fetchSalaryBrackets, [], '/tools/salary-brackets', true],
[fetchOperationRules, [], '/operation/rules', true],
[fetchHollandIntro, [], '/growth/holland-intro', true],
[fetchIdentityOptions, [], '/tools/identity-options', true],
[fetchGameLevels, [], '/game/levels', true],
[fetchLegalBrand, [], '/legal/brand', true],
[fetchLegalServices, [], '/legal/services', true],
[fetchLegalFAQs, [], '/legal/faqs', true],
[fetchLegalTemplates, [], '/legal/templates', true],
[fetchLegalPlans, [], '/legal/plans', true],
[fetchAboutInfo, [], '/settings/about', true],
[fetchQualifications, [], '/settings/qualifications', true],
[fetchAITools, [], '/growth/ai-tools', true],
// 用户/企业私有(auth:true)
[fetchHistory, [], '/user/history', true],
[fetchHollandAssessment, [], '/growth/holland', true],
[fetchHollandResult, [], '/growth/holland/result', true],
[fetchAbilityAssessment, [], '/growth/ability', true],
[fetchUserStats, [], '/user/stats', true],
[fetchOverallScore, [], '/user/overall-score', true],
[fetchDimensionScores, [], '/user/dimension-scores', true],
[fetchBadges, [], '/user/badges', true],
[fetchOfferCompareData, [undefined], '/offers/compare', true],
[fetchPendingOfferCount, [], '/offers/pending/count', true],
[fetchWeightSettings, [], '/jobs/weight-settings', true],
[fetchSalaryRecords, [], '/salary/records', true],
[fetchAIInterviewData, [], '/interview/config', true],
[fetchHREvaluation, [], '/evaluation/hr', true],
[fetchAppealForm, [], '/evaluation/appeal', true],
[fetchEnterpriseOverview, [], '/enterprise/overview', true],
[fetchJobPublishForm, [], '/enterprise/jobs/publish-form', true],
[fetchJobEvaluationManagement, [], '/enterprise/job-evaluations', true],
[fetchCandidateDetail, ['1'], '/enterprise/candidates/1', false],
[fetchInterviewScore, ['1'], '/enterprise/interviews/1/score', false],
[fetchInterviewScoreDraft, ['1'], '/enterprise/interviews/1/score/draft', false],
[fetchAuditReport, [], '/operation/audit-report', true],
[fetchEnterpriseCertification, [], '/enterprise/certification', true],
[fetchJobManagement, [], '/enterprise/job-management', true],
[fetchTalentRecommendation, [], '/enterprise/talent-recommendation', true],
[fetchEnterpriseCommunication, [], '/enterprise/communication', true],
[fetchJobEvaluationEmptyState, [], '/enterprise/job-evaluation-empty', true],
[fetchGameProgress, [], '/game/progress', true],
[fetchLegalMembership, [], '/legal/membership', true],
[fetchLegalOrders, [], '/legal/orders', true],
[fetchLegalServiceRequests, [], '/legal/service-requests', true],
[fetchLegalConversation, [], '/legal/conversations/default', true],
[fetchPrivacySettings, [], '/settings/privacy', true],
[fetchPrivacyCenter, [], '/settings/privacy-center', true],
[fetchPrivacyDetailSetting, ['1'], '/settings/privacy/1', false],
[fetchDndSetting, [], '/settings/privacy/dnd', true],
[fetchKeywordBlock, [], '/settings/privacy/keywords', true],
[fetchAccountCancel, [], '/settings/account-cancel', true],
[fetchLegalDocument, ['1'], '/settings/legal-document/1', false],
[fetchPersonalDataExport, [], '/settings/personal-data-export', true],
[fetchNotificationSettings, [], '/settings/notification', true],
[fetchAccountSecurity, [], '/settings/account-security', true],
];
const ENVELOPE = {
code: 0,
message: '',
data: null,
requestId: 'r',
timestamp: '',
};
// 已与后端 OpenAPI 对齐(ALIGNED + WRONG→REAL)的函数:真实态打真实路径
// 幽灵域(PHANTOM,后端无契约)已退回 mock-only,翻真实开关也不触网,不在此断言路径
const REAL_FUNCS: Array<[(...a: any[]) => Promise<any>, any[], string]> = [
[fetchRankData, ['1'], '/jobs/1/competition-ranking'],
[fetchCandidateDetail, ['1'], '/company/candidates/1'],
[fetchInterviewScore, ['1'], '/interviews/1/score'],
[fetchInterviewScoreDraft, ['1'], '/interviews/1/score'],
[fetchOfferCompareData, [undefined], '/offers/compare'],
];
describe('后端桥接 - 真实态路径断言(已对齐函数)', () => {
const originalFetch = (global as any).fetch;
beforeEach(() => {
(global as any).fetch = jest.fn(async () => ({
ok: true,
status: 200,
headers: { get: () => null },
json: async () => ENVELOPE,
text: async () => '',
}));
setMockMode(false);
});
afterEach(() => {
setMockMode(true);
});
afterAll(() => {
(global as any).fetch = originalFetch;
});
it('已对齐函数数量正确(5 个)', () => {
expect(REAL_FUNCS.length).toBe(5);
});
REAL_FUNCS.forEach(([fn, args, path], i) => {
it(`#${i} 命中真实路径 ${path}`, async () => {
await fn(...args);
const calls = ((global as any).fetch as jest.Mock).mock.calls;
expect(calls.length).toBeGreaterThan(0);
const calledUrl: string = calls[calls.length - 1][0];
expect(calledUrl).toContain(path);
});
});
});
describe('后端桥接 - mock 态冒烟(无回归)', () => {
beforeEach(() => {
setMockMode(true);
});
FUNCS.filter((e) => e[3]).forEach(([fn, args, path], idx) => {
it(`mock 态 ${path} 返回非 undefined`, async () => {
const result = await fn(...args);
expect(result).not.toBeUndefined();
});
});
});

141
src/__tests__/backend-bridge-write.test.ts

@ -0,0 +1,141 @@
/**
* 后端桥接(写入类)测试
* 覆盖本轮转换的 12 个写函数(简历 8 + 求职偏好 + 权重 + 薪资 2):
* - 真实态(setMockMode(false)):断言 request 命中预期方法 + 相对路径 + body
* - mock 态(默认):断言返回本地派生/生成值(证明 mock 分支未被破坏,本地 storage 真写入仍可用)
* 路径依据 scripts/openapi.json(job.youthome.com.cn)实测校正,不再标【待确认】。
* 幽灵域写函数已退回 mock-only,真实态不在此断言网络路径。
*/
// expo-constants:extra 为空 → 走默认生产域名;mock 开关由 setMockMode 控制
jest.mock('expo-constants', () => ({ expoConfig: { extra: {} } }));
// token-store:内存双 Token,避免依赖 AsyncStorage
jest.mock('../lib/token-store', () => {
let mem: { accessToken: string; refreshToken: string; expiresAt?: number } | null = null;
return {
getAccessToken: jest.fn(() => mem?.accessToken ?? null),
getTokens: jest.fn(() => mem),
setTokens: jest.fn((a: string, r: string, e?: number) => {
mem = { accessToken: a, refreshToken: r, expiresAt: e };
}),
clearTokens: jest.fn(() => {
mem = null;
}),
hydrateTokens: jest.fn(async () => undefined),
};
});
// storage:内存实现,避免依赖 AsyncStorage
jest.mock('../lib/storage', () => {
const m = new Map<string, string>();
return {
storageGet: jest.fn(async (k: string) => m.get(k) ?? null),
storageSet: jest.fn(async (k: string, v: string) => {
m.set(k, v);
}),
storageRemove: jest.fn(async (k: string) => {
m.delete(k);
}),
};
});
import { setMockMode } from '../api/http';
import {
saveResumeBasicInfo,
saveResumeSelfEvaluation,
saveResumeSkills,
saveResumeEducation,
saveResumeExperience,
saveResumeProjects,
saveResumeSnapshot,
saveResumeExportSettings,
saveJobPreference,
saveWeightSettings,
saveSalaryRecord,
deleteSalaryRecord,
} from '../api/api';
const ENVELOPE = { code: 0, message: 'ok', data: null, requestId: 'r', timestamp: 0 };
let fetchMock: jest.Mock;
beforeEach(() => {
fetchMock = jest.fn(async () => ({
ok: true,
status: 200,
json: async () => ENVELOPE,
text: async () => '',
headers: { get: () => 'x' },
}));
(global as any).fetch = fetchMock;
setMockMode(false); // 默认跑真实态
});
/** 触发一次真实请求,返回最后一个 fetch 调用的 options */
async function expectRequest(
call: () => Promise<unknown>,
path: string,
method: string,
): Promise<any> {
await call();
const last = fetchMock.mock.calls[fetchMock.mock.calls.length - 1];
expect(last[0]).toContain(path);
expect((last[1] as any).method).toBe(method);
return last[1] as any;
}
describe('写入函数真实态:方法 + 相对路径 + body(仅已对齐函数)', () => {
beforeEach(() => setMockMode(false));
test('saveJobPreference → PUT /profile/me/preferences', async () => {
const preference = { city: '北京', salary: '15k' } as any;
const opts = await expectRequest(
() => saveJobPreference(preference),
'/profile/me/preferences',
'PUT',
);
expect(JSON.parse(opts.body)).toEqual(preference);
});
});
/**
* 以下写函数属幽灵域(后端无 OpenAPI 契约),已退回 mock-only,翻真实开关也不触网,
* 故真实态不再断言网络路径;其 mock 态本地派生/生成行为见下方 describe。
*/
describe('写入函数 mock 态:本地派生/生成行为不变(开发期真写入仍可用)', () => {
beforeEach(() => setMockMode(true));
test('saveSalaryRecord 本地生成 id 与时间戳', async () => {
const rec = await saveSalaryRecord({} as any, {} as any, 'note');
expect(rec.id.startsWith('salary-')).toBe(true);
expect(typeof rec.createdAt).toBe('number');
expect(rec.note).toBe('note');
});
test('saveResumeBasicInfo 本地派生 avatar', async () => {
const info = await saveResumeBasicInfo({ name: '张三' } as any);
expect(info.avatar).toBeTruthy();
});
test('saveJobPreference 原样返回入参', async () => {
const p = { city: '北京' } as any;
expect(await saveJobPreference(p)).toBe(p);
});
test('saveResumeSkills 本地去空去重', async () => {
const out = await saveResumeSkills([
{ name: 'a' },
{ name: 'a' },
{ name: ' ' },
] as any);
expect(out).toEqual([{ name: 'a' }]);
});
test('saveWeightSettings 返回副本(不与入参同引用)', async () => {
const dims = [{ id: 'd1' }] as any;
const out = await saveWeightSettings(dims);
expect(out).not.toBe(dims);
expect(out).toEqual(dims);
});
});

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save