From 445c1c55610cf563ac25f32009eb9736ea8b1a1d Mon Sep 17 00:00:00 2001
From: lafay <2021211506@stu.hit.edu.cn>
Date: Tue, 7 Apr 2026 15:58:51 +0800
Subject: [PATCH] feat(auth): add user identity verification system with new
screens
Add comprehensive identity verification flow including:
- New verification guide screen at /verification-guide for selecting identity type
- New verification form screen at /verification-form for submitting verification documents
- Verification settings screen at /profile/verification for managing verification status
- Move terms and privacy policy to root-level routes (/terms, /privacy)
- Update login to require active terms agreement checkbox
- Database: add in-memory fallback when OPFS is unavailable on web
BREAKING CHANGE: Terms and privacy policy routes moved from /profile/terms and /profile/privacy to /terms and /privacy
---
app/(app)/(tabs)/profile/_layout.tsx | 1 +
app/(app)/(tabs)/profile/verification.tsx | 5 ++
app/(auth)/verification-form.tsx | 5 ++
app/(auth)/verification-guide.tsx | 5 ++
app/_layout.tsx | 24 +++++++
app/privacy.tsx | 5 ++
app/terms.tsx | 5 ++
src/database/core/DatabaseConfig.ts | 4 +-
src/database/core/DatabaseManager.ts | 12 +++-
src/navigation/hrefs.ts | 19 +++++-
src/screens/auth/LoginScreen.tsx | 66 ++++++++++++++++---
src/screens/auth/RegisterStep3Profile.tsx | 2 +-
src/screens/auth/VerificationGuideScreen.tsx | 11 ++--
src/screens/auth/index.ts | 2 +
src/screens/profile/SettingsScreen.tsx | 2 +-
.../profile/VerificationSettingsScreen.tsx | 34 +++-------
src/screens/profile/index.ts | 1 +
17 files changed, 152 insertions(+), 51 deletions(-)
create mode 100644 app/(app)/(tabs)/profile/verification.tsx
create mode 100644 app/(auth)/verification-form.tsx
create mode 100644 app/(auth)/verification-guide.tsx
create mode 100644 app/privacy.tsx
create mode 100644 app/terms.tsx
diff --git a/app/(app)/(tabs)/profile/_layout.tsx b/app/(app)/(tabs)/profile/_layout.tsx
index 12acaac..3d62485 100644
--- a/app/(app)/(tabs)/profile/_layout.tsx
+++ b/app/(app)/(tabs)/profile/_layout.tsx
@@ -40,6 +40,7 @@ export default function ProfileStackLayout() {
+
);
}
diff --git a/app/(app)/(tabs)/profile/verification.tsx b/app/(app)/(tabs)/profile/verification.tsx
new file mode 100644
index 0000000..d9212f1
--- /dev/null
+++ b/app/(app)/(tabs)/profile/verification.tsx
@@ -0,0 +1,5 @@
+import { VerificationSettingsScreen } from '@/screens/profile/VerificationSettingsScreen';
+
+export default function VerificationSettingsPage() {
+ return ;
+}
diff --git a/app/(auth)/verification-form.tsx b/app/(auth)/verification-form.tsx
new file mode 100644
index 0000000..5c935ef
--- /dev/null
+++ b/app/(auth)/verification-form.tsx
@@ -0,0 +1,5 @@
+import { VerificationFormScreen } from '@/screens/auth/VerificationFormScreen';
+
+export default function VerificationFormPage() {
+ return ;
+}
diff --git a/app/(auth)/verification-guide.tsx b/app/(auth)/verification-guide.tsx
new file mode 100644
index 0000000..61eab8f
--- /dev/null
+++ b/app/(auth)/verification-guide.tsx
@@ -0,0 +1,5 @@
+import { VerificationGuideScreen } from '@/screens/auth/VerificationGuideScreen';
+
+export default function VerificationGuidePage() {
+ return ;
+}
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 0fb9437..a200dad 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -175,6 +175,30 @@ function ThemedStack() {
+ router.back()} />,
+ }}
+ />
+ router.back()} />,
+ }}
+ />
;
+}
\ No newline at end of file
diff --git a/app/terms.tsx b/app/terms.tsx
new file mode 100644
index 0000000..0a85ae1
--- /dev/null
+++ b/app/terms.tsx
@@ -0,0 +1,5 @@
+import { TermsOfServiceScreen } from '../src/screens/profile/TermsOfServiceScreen';
+
+export default function TermsRoute() {
+ return ;
+}
\ No newline at end of file
diff --git a/src/database/core/DatabaseConfig.ts b/src/database/core/DatabaseConfig.ts
index 0d3c5cf..0e09882 100644
--- a/src/database/core/DatabaseConfig.ts
+++ b/src/database/core/DatabaseConfig.ts
@@ -9,6 +9,6 @@ export const DB_CONFIG = {
DB_NAME_PREFIX: 'carrot_bbs_',
} as const;
-export const getDbName = (userId: string | null): string => {
+export function getDbName(userId: string | null): string {
return userId ? `${DB_CONFIG.DB_NAME_PREFIX}${userId}.db` : DB_CONFIG.DEFAULT_DB_NAME;
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/database/core/DatabaseManager.ts b/src/database/core/DatabaseManager.ts
index 36b902b..4af18ab 100644
--- a/src/database/core/DatabaseManager.ts
+++ b/src/database/core/DatabaseManager.ts
@@ -109,25 +109,31 @@ class DatabaseManager {
const isWeb = typeof window !== 'undefined';
return LockManager.withWebLock(`${DB_CONFIG.LOCK_PREFIX}${dbName}`, async () => {
+ let lastError: unknown;
for (let attempt = 0; attempt <= DB_CONFIG.MAX_RETRIES; attempt++) {
try {
const database = await SQLite.openDatabaseAsync(dbName);
console.log(`[DatabaseManager] 成功打开数据库 (尝试 ${attempt + 1}/${DB_CONFIG.MAX_RETRIES + 1})`);
return database;
} catch (error) {
+ lastError = error;
const msg = String(error);
console.error(`[DatabaseManager] 打开失败 (尝试 ${attempt + 1}/${DB_CONFIG.MAX_RETRIES + 1}):`, msg);
+ if (isWeb && msg.includes('cannot create file')) {
+ console.log('[DatabaseManager] OPFS 不可用,回退到内存数据库');
+ const memoryDb = await SQLite.openDatabaseAsync(':memory:');
+ return memoryDb;
+ }
+
if (attempt < DB_CONFIG.MAX_RETRIES) {
const delay = isWeb ? DB_CONFIG.WEB_RETRY_DELAY_BASE * (attempt + 1) : DB_CONFIG.NATIVE_RETRY_DELAY;
console.warn(`[DatabaseManager] ${delay}ms 后重试...`);
await new Promise(r => setTimeout(r, delay));
- } else {
- throw error;
}
}
}
- throw new Error('打开数据库失败');
+ throw lastError;
});
}
diff --git a/src/navigation/hrefs.ts b/src/navigation/hrefs.ts
index 9a5e260..c12009e 100644
--- a/src/navigation/hrefs.ts
+++ b/src/navigation/hrefs.ts
@@ -185,9 +185,24 @@ export function hrefProfileAbout(): string {
}
export function hrefProfileTerms(): string {
- return '/profile/terms';
+ return '/terms';
}
export function hrefProfilePrivacy(): string {
- return '/profile/privacy';
+ return '/privacy';
+}
+
+export function hrefProfileVerification(): string {
+ return '/profile/verification';
+}
+
+export function hrefVerificationGuide(): string {
+ return '/verification-guide';
+}
+
+export function hrefVerificationForm(identity?: string): string {
+ if (identity) {
+ return `/verification-form?identity=${encodeURIComponent(identity)}`;
+ }
+ return '/verification-form';
}
diff --git a/src/screens/auth/LoginScreen.tsx b/src/screens/auth/LoginScreen.tsx
index a25337d..3abef85 100644
--- a/src/screens/auth/LoginScreen.tsx
+++ b/src/screens/auth/LoginScreen.tsx
@@ -53,6 +53,7 @@ export const LoginScreen: React.FC = () => {
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
+ const [agreedToTerms, setAgreedToTerms] = useState(false);
// 动画值
const fadeAnim = useRef(new Animated.Value(0)).current;
@@ -84,6 +85,10 @@ export const LoginScreen: React.FC = () => {
showPrompt({ title: '提示', message: '请输入密码', type: 'warning' });
return false;
}
+ if (!agreedToTerms) {
+ showPrompt({ title: '提示', message: '请同意用户协议和隐私政策', type: 'warning' });
+ return false;
+ }
return true;
};
@@ -211,6 +216,37 @@ export const LoginScreen: React.FC = () => {
忘记密码?
+ {/* 服务条款勾选 */}
+
+ setAgreedToTerms(!agreedToTerms)}
+ activeOpacity={0.8}
+ style={styles.checkboxWrapper}
+ >
+
+
+
+ 我已阅读并同意
+ router.push(hrefs.hrefProfileTerms())}
+ >
+ 《用户协议》
+
+ 和
+ router.push(hrefs.hrefProfilePrivacy())}
+ >
+ 《隐私政策》
+
+
+
+
{/* 登录按钮 */}
{
- {/* 协议提示 */}
-
-
- 登录即表示您同意
- router.push(hrefs.hrefProfileTerms())}>《用户协议》
- 和
- router.push(hrefs.hrefProfilePrivacy())}>《隐私政策》
-
-
+
@@ -394,6 +422,26 @@ function createLoginStyles(colors: AppColors) {
color: THEME_COLORS.primary,
fontWeight: '500',
},
+ termsContainer: {
+ flexDirection: 'row',
+ alignItems: 'flex-start',
+ marginBottom: 20,
+ marginTop: 4,
+ },
+ checkboxWrapper: {
+ paddingTop: 2,
+ },
+ termsText: {
+ fontSize: 14,
+ color: colors.text.secondary,
+ marginLeft: 10,
+ flex: 1,
+ lineHeight: 22,
+ },
+ termsLink: {
+ color: THEME_COLORS.primary,
+ fontWeight: '500',
+ },
});
}
diff --git a/src/screens/auth/RegisterStep3Profile.tsx b/src/screens/auth/RegisterStep3Profile.tsx
index 77a3f6d..06db465 100644
--- a/src/screens/auth/RegisterStep3Profile.tsx
+++ b/src/screens/auth/RegisterStep3Profile.tsx
@@ -38,7 +38,7 @@ export const RegisterStep3Profile: React.FC = ({
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
- const [agreedToTerms, setAgreedToTerms] = useState(true);
+ const [agreedToTerms, setAgreedToTerms] = useState(false);
const [errors, setErrors] = useState>({});
const validateForm = (): boolean => {
diff --git a/src/screens/auth/VerificationGuideScreen.tsx b/src/screens/auth/VerificationGuideScreen.tsx
index 7ac84da..1711f2f 100644
--- a/src/screens/auth/VerificationGuideScreen.tsx
+++ b/src/screens/auth/VerificationGuideScreen.tsx
@@ -37,7 +37,7 @@ const IDENTITY_OPTIONS = [
key: 'teacher',
title: '教职工',
description: '需要提交工作证或教师资格证',
- icon: 'chalkboard-teacher',
+ icon: 'account-tie',
},
{
key: 'staff',
@@ -82,10 +82,7 @@ export const VerificationGuideScreen: React.FC = () => {
return;
}
// 导航到认证表单页面
- router.push({
- pathname: '/auth/verification-form',
- params: { identity: selectedIdentity },
- });
+ router.push(hrefs.hrefVerificationForm(selectedIdentity));
};
const renderIdentityCard = (option: typeof IDENTITY_OPTIONS[0]) => {
@@ -148,7 +145,7 @@ export const VerificationGuideScreen: React.FC = () => {
@@ -164,7 +161,7 @@ export const VerificationGuideScreen: React.FC = () => {
diff --git a/src/screens/auth/index.ts b/src/screens/auth/index.ts
index d1d5bfe..fbc0e2c 100644
--- a/src/screens/auth/index.ts
+++ b/src/screens/auth/index.ts
@@ -7,6 +7,8 @@ export { LoginScreen } from './LoginScreen';
export { RegisterScreen } from './RegisterScreen';
export { ForgotPasswordScreen } from './ForgotPasswordScreen';
export { WelcomeScreen } from './WelcomeScreen';
+export { VerificationGuideScreen } from './VerificationGuideScreen';
+export { VerificationFormScreen } from './VerificationFormScreen';
// 注册步骤组件
export { RegisterStep1Email } from './RegisterStep1Email';
diff --git a/src/screens/profile/SettingsScreen.tsx b/src/screens/profile/SettingsScreen.tsx
index c9a7511..ba65fb9 100644
--- a/src/screens/profile/SettingsScreen.tsx
+++ b/src/screens/profile/SettingsScreen.tsx
@@ -254,7 +254,7 @@ export const SettingsScreen: React.FC = () => {
router.push(hrefs.hrefProfileEdit());
break;
case 'verification':
- router.push('/profile/verification');
+ router.push(hrefs.hrefProfileVerification());
break;
case 'notification_settings':
router.push(hrefs.hrefProfileNotifications());
diff --git a/src/screens/profile/VerificationSettingsScreen.tsx b/src/screens/profile/VerificationSettingsScreen.tsx
index bf1ea14..520a95e 100644
--- a/src/screens/profile/VerificationSettingsScreen.tsx
+++ b/src/screens/profile/VerificationSettingsScreen.tsx
@@ -84,7 +84,7 @@ export const VerificationSettingsScreen: React.FC = () => {
}, []);
const handleStartVerification = () => {
- router.push('/auth/verification-guide');
+ router.push(hrefs.hrefVerificationGuide());
};
const handleViewRecords = () => {
@@ -109,27 +109,10 @@ export const VerificationSettingsScreen: React.FC = () => {
const identityText = IDENTITY_TEXT[status?.identity || 'general'];
return (
-
-
- router.back()}
- activeOpacity={0.8}
- >
-
-
- 身份认证
-
-
-
-
+
{/* 状态卡片 */}
@@ -167,7 +150,7 @@ export const VerificationSettingsScreen: React.FC = () => {
@@ -183,7 +166,7 @@ export const VerificationSettingsScreen: React.FC = () => {
@@ -199,7 +182,7 @@ export const VerificationSettingsScreen: React.FC = () => {
@@ -278,7 +261,6 @@ export const VerificationSettingsScreen: React.FC = () => {
-
);
};
diff --git a/src/screens/profile/index.ts b/src/screens/profile/index.ts
index 85a8cb9..7784813 100644
--- a/src/screens/profile/index.ts
+++ b/src/screens/profile/index.ts
@@ -17,6 +17,7 @@ export { AccountSecurityScreen } from './AccountSecurityScreen';
export { AboutScreen } from './AboutScreen';
export { TermsOfServiceScreen } from './TermsOfServiceScreen';
export { PrivacyPolicyScreen } from './PrivacyPolicyScreen';
+export { VerificationSettingsScreen } from './VerificationSettingsScreen';
// 导出 Hook 供需要自定义的场景使用
export { useUserProfile, createSharedProfileStyles, TABS, TAB_ICONS } from './useUserProfile';