feat(frontend): 설정(비밀번호 · 부재중 응답) · 메일함 관리 · 스팸 신고 · 원문 내려받기 화면

- 설정 메뉴를 계정 · 메일 묶음으로, 비밀번호 바꾸기(칸 아래 오류 · 30초 한도)와 부재중 자동 응답(기간 · 미리 보기 · 나가기 확인),
  메일함 패널의 부재중 안내(1분마다 다시 계산)
- 메일함 패널의 내 메일함 묶음 · ⋯ 메뉴(보이는 영역 안에 열기) · 대화상자(만들기 · 이름 바꾸기 · 지우기), 목록 머리 비우기
  — 확인한 그 메일함만, 휴지통 비우기와 메일함 지우기는 함께 돌리지 않는다
- 오래 걸리는 처리의 진행 알림("…하는 중 · 600 / 1,234통" → 결과), 세션이 바뀌면 멈추고 늦게 온 응답은 쓰지 않는다
- 읽기 화면 · 여러 통 도구 줄의 스팸 신고 / 스팸 아님 + 되돌리기, ⋯ › 원문 내려받기 (.eml)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 06:47:12 +09:00
parent 05fc3909dd
commit af1e581be1
86 changed files with 7114 additions and 302 deletions
+2 -66
View File
@@ -1,10 +1,10 @@
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
import { useAuth } from '@/composables/useAuth'
import { documentTitle } from '@/shared/utils/document-title'
import { MAIL_SEARCH_MESSAGE_ROUTE, MAIL_SEARCH_ROUTE } from '@/shared/utils/mail-list-source'
import { toSafeRedirect } from '@/shared/utils/redirect'
import { useAuthStore } from '@/stores/auth.store'
import { routes } from './routes'
declare module 'vue-router' {
interface RouteMeta {
@@ -17,70 +17,6 @@ declare module 'vue-router' {
}
}
// 목록·본문 라우트가 같은 컴포넌트를 쓰도록 한 번만 정의한다 (메일을 바꿔도 화면을 다시 만들지 않는다)
const MailboxPage = () => import('@/pages/mail/MailboxPage.vue')
// 코드 스플리팅 — 모든 페이지 컴포넌트는 lazy import 처리
const routes: RouteRecordRaw[] = [
{
path: '/',
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: '메일 쓰기' },
},
// 검색 결과 /mail/search?q=…&in=<메일함 id | all> · 결과에서 연 메일 /mail/search/<메일 id>?q=…&in=… (계약 2절 "검색")
// 고정 경로라 :mailboxId 보다 앞에 둔다 — 같은 화면(MailboxPage)이 목록 출처만 바꿔 그린다
{ path: 'search', name: MAIL_SEARCH_ROUTE, component: MailboxPage, meta: { title: '메일 검색' } },
{
path: 'search/:messageId',
name: MAIL_SEARCH_MESSAGE_ROUTE,
component: MailboxPage,
meta: { title: '메일 검색' },
},
{ path: ':mailboxId', name: 'mail-mailbox', component: MailboxPage },
{ path: ':mailboxId/:messageId', name: 'mail-message', component: MailboxPage },
],
},
{
// 설정 — 왼쪽 메뉴(메일 › 서명) + 하위 화면. /settings 는 첫 메뉴로 바꿔 끼운다 (앱 레일의 "설정")
path: '/settings',
component: () => import('@/pages/settings/SettingsLayout.vue'),
meta: { requiresAuth: true, title: '설정' },
children: [
{ path: '', name: 'settings', redirect: { name: 'settings-mail-signature' } },
{
path: 'mail/signature',
name: 'settings-mail-signature',
component: () => import('@/pages/settings/MailSignaturePage.vue'),
meta: { title: '메일 서명' },
},
],
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/pages/NotFoundPage.vue'),
meta: { title: '페이지를 찾을 수 없음' },
},
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
+83
View File
@@ -0,0 +1,83 @@
import type { RouteRecordRaw } from 'vue-router'
import { SETTINGS_ROUTES } from '@/shared/constants/settings-menu'
import { MAIL_SEARCH_MESSAGE_ROUTE, MAIL_SEARCH_ROUTE } from '@/shared/utils/mail-list-source'
// 앱의 라우트 표 — 라우터(index.ts)와 라우트 테스트가 함께 쓴다 (이 파일은 라우터를 만들지 않는다)
// 목록·본문 라우트가 같은 컴포넌트를 쓰도록 한 번만 정의한다 (메일을 바꿔도 화면을 다시 만들지 않는다)
const MailboxPage = () => import('@/pages/mail/MailboxPage.vue')
// 코드 스플리팅 — 모든 페이지 컴포넌트는 lazy import 처리
export const routes: RouteRecordRaw[] = [
{
path: '/',
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: '메일 쓰기' },
},
// 검색 결과 /mail/search?q=…&in=<메일함 id | all> · 결과에서 연 메일 /mail/search/<메일 id>?q=…&in=… (계약 2절 "검색")
// 고정 경로라 :mailboxId 보다 앞에 둔다 — 같은 화면(MailboxPage)이 목록 출처만 바꿔 그린다
{ path: 'search', name: MAIL_SEARCH_ROUTE, component: MailboxPage, meta: { title: '메일 검색' } },
{
path: 'search/:messageId',
name: MAIL_SEARCH_MESSAGE_ROUTE,
component: MailboxPage,
meta: { title: '메일 검색' },
},
{ path: ':mailboxId', name: 'mail-mailbox', component: MailboxPage },
{ path: ':mailboxId/:messageId', name: 'mail-message', component: MailboxPage },
],
},
{
// 설정 — 왼쪽 메뉴(계정 › 비밀번호 / 메일 › 서명 · 부재중 자동 응답) + 하위 화면.
// /settings 는 첫 메뉴(계정 › 비밀번호)로 바꿔 끼운다 (앱 레일의 "설정")
path: '/settings',
component: () => import('@/pages/settings/SettingsLayout.vue'),
meta: { requiresAuth: true, title: '설정' },
children: [
{ path: '', name: 'settings', redirect: { name: SETTINGS_ROUTES.password } },
{
path: 'account/password',
name: SETTINGS_ROUTES.password,
component: () => import('@/pages/settings/AccountPasswordPage.vue'),
meta: { title: '비밀번호' },
},
{
path: 'mail/signature',
name: SETTINGS_ROUTES.signature,
component: () => import('@/pages/settings/MailSignaturePage.vue'),
meta: { title: '메일 서명' },
},
{
path: 'mail/vacation',
name: SETTINGS_ROUTES.vacation,
component: () => import('@/pages/settings/MailVacationPage.vue'),
meta: { title: '부재중 자동 응답' },
},
],
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/pages/NotFoundPage.vue'),
meta: { title: '페이지를 찾을 수 없음' },
},
]