release: freeze demo v1.1.0 2026-10-19
CI/CD Pipeline / Backend Build & Test (push) Canceled after 0s
CI/CD Pipeline / Admin Web Build (push) Canceled after 0s
CI/CD Pipeline / Mini Program Compile Check (push) Canceled after 0s
CI/CD Pipeline / Migration Validation (push) Canceled after 0s
CI/CD Pipeline / Test Shell Guard (Phase 23 Gate) (push) Canceled after 0s
CI/CD Pipeline / UI Contracts (source-structure gates) (push) Canceled after 0s
CI/CD Pipeline / E2E (live stack) (push) Canceled after 0s

This commit is contained in:
WorkBuddy
2026-10-19 20:36:22 +08:00
parent 8b505d9133
commit 69ebf33e18
157 changed files with 21111 additions and 72 deletions
+50
View File
@@ -0,0 +1,50 @@
import type { Router } from 'vue-router'
import { APPROVED_ROUTES, PUBLIC_ROUTES } from './routes'
import { useAuthStore } from '@/stores/auth'
import { useUserStore } from '@/stores/user'
import { useOperatorStore } from '@/stores/operator'
/**
* 路由守卫(同构 miniapp/stores/index.js 的 canAccess):
* - /operator/** 用独立会话(仅校验 h5.operatorToken,不套用户审核守卫);
* - 其它路由需登录;账号异常优先拦截;'approved' 路由需资料审核通过。
* 前端不缓存状态为真相(AGENTS 第 9 条),判定依据 `GET /me/status`。
*/
export function setupGuard(router: Router): void {
router.beforeEach(async (to) => {
if (to.path.startsWith('/operator')) {
const operatorStore = useOperatorStore()
if (to.path !== '/operator/login' && !operatorStore.isLoggedIn) {
return { path: '/operator/login' }
}
return true
}
if (PUBLIC_ROUTES.has(to.path)) return true
const authStore = useAuthStore()
if (!authStore.isLoggedIn) {
return { path: '/login', query: to.fullPath !== '/home' ? { redirect: to.fullPath } : {} }
}
const userStore = useUserStore()
if (!userStore.loaded) {
try {
await userStore.fetchStatus()
} catch {
return { path: '/login' }
}
}
if (userStore.accountStatus !== 'normal' && to.path !== '/account/status' && to.path !== '/account/status/detail') {
return { path: '/account/status' }
}
if (APPROVED_ROUTES.has(to.path) && !userStore.isApproved) {
const status = userStore.reviewStatus
return { path: status === 'need_edit' || status === 'rejected' ? '/review/rejected' : '/review/pending' }
}
return true
})
}
+15
View File
@@ -0,0 +1,15 @@
import { createRouter, createWebHistory } from 'vue-router'
import routes from './routes'
import { setupGuard } from './guard'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
scrollBehavior() {
return { top: 0 }
},
})
setupGuard(router)
export default router
+119
View File
@@ -0,0 +1,119 @@
/**
* H5 演示端路由映射(镜像 miniapp 66 个注册页面,id-only 传参)。
* 受保护路由的审核态要求见 guard.ts 的 ROUTE_ACCESS_REQUIREMENTS。
*/
import type { RouteRecordRaw } from 'vue-router'
import TabLayout from '@/layouts/TabLayout.vue'
/** 需要「资料审核通过」才能访问的路由(对齐 miniapp/stores/index.js) */
export const APPROVED_ROUTES = new Set([
'/discover',
'/fate-center',
'/profile/detail',
'/activity/register',
'/matchmaker/apply',
])
/** 无需登录即可访问 */
export const PUBLIC_ROUTES = new Set(['/login', '/operator/login', '/about', '/help'])
export const routes: RouteRecordRaw[] = [
// ==================== 认证 ====================
{ path: '/login', name: 'Login', component: () => import('@/pages/auth/LoginPage.vue') },
{ path: '/bind-phone', name: 'BindPhone', component: () => import('@/pages/auth/BindPhonePage.vue') },
// ==================== Tab ====================
{
path: '/',
component: TabLayout,
children: [
{ path: '', redirect: '/home' },
{ path: 'home', name: 'HomePage', component: () => import('@/pages/home/HomePage.vue') },
{ path: 'discover', name: 'DiscoverPage', component: () => import('@/pages/discover/DiscoverPage.vue') },
{ path: 'activity', name: 'ActivityPage', component: () => import('@/pages/activity/ActivityPage.vue') },
{ path: 'mine', name: 'MinePage', component: () => import('@/pages/mine/MinePage.vue') },
],
},
// ==================== 启动与身份 ====================
{ path: '/verification', name: 'Verification', component: () => import('@/pages/auth/VerificationPage.vue') },
{ path: '/matchmaker-authorization', name: 'MatchmakerAuthorization', component: () => import('@/pages/local/MatchmakerAuthorizationPage.vue') },
{ path: '/local-eligibility', name: 'LocalEligibility', component: () => import('@/pages/local/LocalEligibilityPage.vue') },
{ path: '/local-eligibility/supplement', name: 'LocalEligibilitySupplement', component: () => import('@/pages/local/LocalEligibilitySupplementPage.vue') },
{ path: '/contact-consent', name: 'ContactConsent', component: () => import('@/pages/auth/ContactConsentPage.vue') },
// ==================== 新手资料(固定 5 步顺序) ====================
{ path: '/profile/basic', name: 'ProfileBasic', component: () => import('@/pages/onboarding/ProfileBasicPage.vue') },
{ path: '/profile/lifestyle', name: 'ProfileLifestyle', component: () => import('@/pages/onboarding/ProfileLifestylePage.vue') },
{ path: '/profile/photos', name: 'ProfilePhotos', component: () => import('@/pages/onboarding/ProfilePhotosPage.vue') },
{ path: '/profile/intent', name: 'ProfileIntent', component: () => import('@/pages/onboarding/ProfileIntentPage.vue') },
{ path: '/profile/preference', name: 'ProfilePreference', component: () => import('@/pages/onboarding/ProfilePreferencePage.vue') },
{ path: '/profile/confirm', name: 'ProfileConfirm', component: () => import('@/pages/onboarding/ProfileConfirmPage.vue') },
{ path: '/profile/intro', name: 'ProfileIntro', component: () => import('@/pages/onboarding/ProfileIntroPage.vue') },
{ path: '/profile/interests', name: 'ProfileInterests', component: () => import('@/pages/onboarding/ProfileInterestsPage.vue') },
// ==================== 审核与账号 ====================
{ path: '/review/pending', name: 'ReviewPending', component: () => import('@/pages/review/ReviewPendingPage.vue') },
{ path: '/review/rejected', name: 'ReviewRejected', component: () => import('@/pages/review/ReviewRejectedPage.vue') },
{ path: '/account/status', name: 'AccountStatus', component: () => import('@/pages/account/AccountStatusPage.vue') },
{ path: '/account/status/detail', name: 'AccountStatusDetail', component: () => import('@/pages/account/AccountStatusDetailPage.vue') },
{ path: '/account/logout', name: 'AccountLogout', component: () => import('@/pages/account/AccountLogoutPage.vue') },
{ path: '/report', name: 'Report', component: () => import('@/pages/safety/ReportPage.vue') },
{ path: '/my/reports', name: 'MyReports', component: () => import('@/pages/safety/MyReportsPage.vue') },
{ path: '/my/reports/detail', name: 'ReportDetail', component: () => import('@/pages/safety/ReportDetailPage.vue') },
{ path: '/appeal', name: 'Appeal', component: () => import('@/pages/safety/AppealPage.vue') },
// ==================== 主业务 ====================
{ path: '/profile/detail', name: 'ProfileDetail', component: () => import('@/pages/profile/ProfileDetailPage.vue') },
{ path: '/fate-center', name: 'FateCenter', component: () => import('@/pages/match/FateCenterPage.vue') },
{ path: '/matchmaker', name: 'Matchmaker', component: () => import('@/pages/matchmaker/MatchmakerPage.vue') },
{ path: '/matchmaker/apply', name: 'MatchmakerApply', component: () => import('@/pages/matchmaker/MatchmakerApplyPage.vue') },
{ path: '/matchmaker/progress', name: 'MatchmakerProgress', component: () => import('@/pages/matchmaker/MatchmakerProgressPage.vue') },
{ path: '/matchmaker/recommendations', name: 'MatchmakerRecommendations', component: () => import('@/pages/matchmaker/MatchmakerRecommendationsPage.vue') },
{ path: '/referral/confirmation', name: 'ReferralConfirmation', component: () => import('@/pages/matchmaker/ReferralConfirmationPage.vue') },
// ==================== 活动 ====================
{ path: '/activity/detail', name: 'ActivityDetail', component: () => import('@/pages/activity/ActivityDetailPage.vue') },
{ path: '/activity/register', name: 'ActivityRegister', component: () => import('@/pages/activity/ActivityRegisterPage.vue') },
{ path: '/my/activities', name: 'MyActivities', component: () => import('@/pages/activity/MyActivitiesPage.vue') },
{ path: '/activity/checkin', name: 'ActivityCheckin', component: () => import('@/pages/activity/ActivityCheckinPage.vue') },
{ path: '/activity/feedback', name: 'ActivityFeedback', component: () => import('@/pages/activity/ActivityFeedbackPage.vue') },
{ path: '/activity/matches', name: 'ActivityMatches', component: () => import('@/pages/activity/ActivityMatchesPage.vue') },
{ path: '/activity/mutual/result', name: 'ActivityMutualResult', component: () => import('@/pages/activity/ActivityMutualResultPage.vue') },
{ path: '/activity/mutual/waiting', name: 'ActivityMutualWaiting', component: () => import('@/pages/activity/ActivityMutualWaitingPage.vue') },
// ==================== 公益服务 ====================
{ path: '/service-center', name: 'ServiceCenter', component: () => import('@/pages/service/ServiceCenterPage.vue') },
{ path: '/service-station', name: 'ServiceStation', component: () => import('@/pages/service/ServiceStationPage.vue') },
{ path: '/service/appointment', name: 'ServiceAppointment', component: () => import('@/pages/service/ServiceAppointmentPage.vue') },
{ path: '/my/appointments', name: 'MyAppointments', component: () => import('@/pages/service/MyAppointmentsPage.vue') },
{ path: '/service/program', name: 'ServiceProgram', component: () => import('@/pages/service/ServiceProgramPage.vue') },
{ path: '/my/service-history', name: 'MyServiceHistory', component: () => import('@/pages/service/MyServiceHistoryPage.vue') },
{ path: '/content', name: 'Content', component: () => import('@/pages/content/ContentPage.vue') },
{ path: '/content/detail', name: 'ContentDetail', component: () => import('@/pages/content/ContentDetailPage.vue') },
{ path: '/anti-fraud', name: 'AntiFraud', component: () => import('@/pages/content/AntiFraudPage.vue') },
{ path: '/counseling', name: 'Counseling', component: () => import('@/pages/counseling/CounselingPage.vue') },
{ path: '/counseling/request', name: 'CounselingRequest', component: () => import('@/pages/counseling/CounselingRequestPage.vue') },
{ path: '/my/counseling', name: 'MyCounseling', component: () => import('@/pages/counseling/MyCounselingPage.vue') },
{ path: '/premarital', name: 'Premarital', component: () => import('@/pages/premarital/PremaritalPage.vue') },
{ path: '/premarital/request', name: 'PremaritalRequest', component: () => import('@/pages/premarital/PremaritalRequestPage.vue') },
// ==================== 设置与辅助 ====================
{ path: '/settings', name: 'Settings', component: () => import('@/pages/settings/SettingsPage.vue') },
{ path: '/settings/privacy', name: 'PrivacySettings', component: () => import('@/pages/settings/PrivacySettingsPage.vue') },
{ path: '/settings/relationship', name: 'RelationshipStatus', component: () => import('@/pages/settings/RelationshipStatusPage.vue') },
{ path: '/settings/blacklist', name: 'Blacklist', component: () => import('@/pages/settings/BlacklistPage.vue') },
{ path: '/notifications', name: 'Notifications', component: () => import('@/pages/settings/NotificationsPage.vue') },
{ path: '/help', name: 'HelpCenter', component: () => import('@/pages/settings/HelpCenterPage.vue') },
{ path: '/about', name: 'About', component: () => import('@/pages/settings/AboutPage.vue') },
// ==================== 红娘端(独立会话) ====================
{ path: '/operator/login', name: 'OperatorLogin', component: () => import('@/pages/operator/OperatorLoginPage.vue') },
{ path: '/operator/workbench', name: 'OperatorWorkbench', component: () => import('@/pages/operator/OperatorWorkbenchPage.vue') },
{ path: '/operator/services', name: 'OperatorServiceDetail', component: () => import('@/pages/operator/OperatorServiceDetailPage.vue') },
// ==================== 兜底 ====================
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/pages/NotFoundPage.vue') },
]
export default routes