generated from ttipo/project-base
feat(frontend): 전역 셸·로그인·세션 처리와 웹메일 화면
확정된 디자인(docs/design/webmail)을 기준으로 만든 웹메일 화면이다. - 앞으로 붙일 사내 모듈(캘린더·파일·ERP 등)을 위한 전역 앱 레일과 셸, Pretendard 자체 호스팅, 바다 청록 브랜드 토큰과 콘텐츠 열 토큰 - 로그인과 세션 처리: 401 이면 한 번 갱신한 뒤 다시 보내고, 실패하면 세션 만료 대화상자를 띄운다. 로그아웃은 다른 탭에도 알리고, 로그아웃 뒤에 늦게 온 갱신 결과는 버린다 - 메일함(분할·목록 보기), 읽기(외부 HTML 은 sandbox iframe 으로만 그린다), 쓰기(발송 키 규칙, 첨부 형식·크기·합계·개수 사전 검사, 답장·전달 초안 길이 맞춤) - 오류는 코드로만 분기하고, 문구는 생성된 에러 사전에서 가져온다 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,58 @@
|
||||
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
||||
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { APP_NAME } from '@/shared/constants/brand'
|
||||
import { toSafeRedirect } from '@/shared/utils/redirect'
|
||||
import { useAuthStore } from '@/stores/auth.store'
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
/** 로그인해야 들어갈 수 있는 화면 (하위 라우트에 이어진다) */
|
||||
requiresAuth?: boolean
|
||||
/** 로그인한 사용자는 들어올 필요가 없는 화면 (로그인) */
|
||||
guestOnly?: boolean
|
||||
/** 문서 제목 — "제목 · ARA SYSTEM" */
|
||||
title?: string
|
||||
}
|
||||
}
|
||||
|
||||
// 목록·본문 라우트가 같은 컴포넌트를 쓰도록 한 번만 정의한다 (메일을 바꿔도 화면을 다시 만들지 않는다)
|
||||
const MailboxPage = () => import('@/pages/mail/MailboxPage.vue')
|
||||
|
||||
// 코드 스플리팅 — 모든 페이지 컴포넌트는 lazy import 처리
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('@/pages/HomePage.vue'),
|
||||
meta: { requiresAuth: false },
|
||||
redirect: { name: 'mail' },
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/pages/LoginPage.vue'),
|
||||
meta: { guestOnly: true, title: '로그인' },
|
||||
},
|
||||
{
|
||||
path: '/mail',
|
||||
component: () => import('@/pages/mail/MailLayout.vue'),
|
||||
meta: { requiresAuth: true, title: '메일' },
|
||||
children: [
|
||||
// /mail → 메일함을 불러온 뒤 받은편지함으로 (MailboxPage 가 이동시킨다)
|
||||
{ path: '', name: 'mail', component: MailboxPage },
|
||||
{
|
||||
path: 'compose',
|
||||
name: 'mail-compose',
|
||||
component: () => import('@/pages/mail/ComposePage.vue'),
|
||||
meta: { title: '메일 쓰기' },
|
||||
},
|
||||
{ path: ':mailboxId', name: 'mail-mailbox', component: MailboxPage },
|
||||
{ path: ':mailboxId/:messageId', name: 'mail-message', component: MailboxPage },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
component: () => import('@/pages/NotFoundPage.vue'),
|
||||
meta: { title: '페이지를 찾을 수 없음' },
|
||||
},
|
||||
]
|
||||
|
||||
@@ -21,15 +61,24 @@ const router = createRouter({
|
||||
routes,
|
||||
})
|
||||
|
||||
// 네비게이션 가드 — 보호 라우트(requiresAuth) 접근 시 인증 여부 검사
|
||||
router.beforeEach((to) => {
|
||||
// 네비게이션 가드 — 앱 시작 시 /auth/me 로 세션을 복원한 뒤 첫 네비게이션을 판정한다
|
||||
router.beforeEach(async (to) => {
|
||||
// 첫 호출만 실제로 조회하고, 이후에는 같은 결과를 바로 돌려준다
|
||||
await useAuth().restoreSession()
|
||||
|
||||
const authStore = useAuthStore()
|
||||
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
|
||||
// 미인증 시 홈으로 리다이렉트
|
||||
// (실제 로그인 페이지 추가 시 { name: 'login' } 으로 변경)
|
||||
return { name: 'home' }
|
||||
// 로그인 후 원래 가려던 화면으로 돌아온다
|
||||
return { name: 'login', query: { redirect: to.fullPath } }
|
||||
}
|
||||
if (to.meta.guestOnly && authStore.isLoggedIn) {
|
||||
return toSafeRedirect(to.query.redirect) ?? { name: 'mail' }
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
router.afterEach((to) => {
|
||||
document.title = to.meta.title ? `${to.meta.title} · ${APP_NAME}` : APP_NAME
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
Reference in New Issue
Block a user