feat: dompurify 플러그인설치, 푸터 추가
This commit is contained in:
176
flow-diagram.md
Normal file
176
flow-diagram.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# 전체 로직 흐름도
|
||||
|
||||
## 1. 애플리케이션 초기화 및 요청 처리
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[사용자 요청] --> B[Nuxt 서버 시작]
|
||||
B --> C[환경 설정 로드]
|
||||
C --> D[미들웨어 체인 실행]
|
||||
D --> E[게임 데이터 로드]
|
||||
E --> F[페이지 데이터 로드]
|
||||
F --> G[컴포넌트 렌더링]
|
||||
G --> H[최종 HTML 응답]
|
||||
```
|
||||
|
||||
## 2. 서버 사이드 미들웨어 체인
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[요청 수신] --> B{정적 자산?}
|
||||
B -->|Yes| C[스킵]
|
||||
B -->|No| D[gameAlias.ts 미들웨어]
|
||||
D --> E[호스트에서 게임 별칭 추출]
|
||||
E --> F[게임 데이터 API 호출]
|
||||
F --> G[event.context에 저장]
|
||||
G --> H[라우트 처리]
|
||||
```
|
||||
|
||||
## 3. 데이터 흐름 및 상태 관리
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[게임 데이터 API] --> B[GameDataStore]
|
||||
C[페이지 데이터 API] --> D[PageDataStore]
|
||||
B --> E[게임 기본 정보]
|
||||
B --> F[지원 언어 목록]
|
||||
B --> G[SEO 메타 정보]
|
||||
D --> H[템플릿 배열]
|
||||
D --> I[페이지별 컴포넌트]
|
||||
D --> J[페이지 SEO 정보]
|
||||
```
|
||||
|
||||
## 4. 국제화(i18n) 처리 흐름
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[URL 요청] --> B[언어 코드 추출]
|
||||
B --> C{지원 언어?}
|
||||
C -->|No| D[404 에러]
|
||||
C -->|Yes| E[동적 언어 설정]
|
||||
E --> F[prefix 전략 적용]
|
||||
F --> G[페이지 라우팅]
|
||||
G --> H[템플릿 렌더링]
|
||||
```
|
||||
|
||||
## 5. 페이지 렌더링 흐름
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[app.vue] --> B[SSR gameData 로드]
|
||||
B --> C[스토어에 저장]
|
||||
C --> D[SEO 메타 태그 설정]
|
||||
D --> E[NuxtLayout 렌더링]
|
||||
E --> F[페이지 컴포넌트]
|
||||
F --> G[pageData 로드]
|
||||
G --> H[Section 컴포넌트]
|
||||
H --> I[템플릿 렌더링]
|
||||
```
|
||||
|
||||
## 6. 템플릿 시스템 구조
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Section 컴포넌트] --> B[templates 배열 순회]
|
||||
B --> C[templateRegistry 조회]
|
||||
C --> D{템플릿 코드 매칭}
|
||||
D -->|GR_VISUAL_01| E[GrVisual01]
|
||||
D -->|GR_VISUAL_02| F[GrVisual02]
|
||||
D -->|GR_VISUAL_03| G[GrVisual03]
|
||||
E --> H[동적 컴포넌트 렌더링]
|
||||
F --> H
|
||||
G --> H
|
||||
H --> I[TemplatesBackground]
|
||||
I --> J[최종 UI 출력]
|
||||
```
|
||||
|
||||
## 7. 전체 요청-응답 사이클
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 사용자
|
||||
participant S as 서버
|
||||
participant M as 미들웨어
|
||||
participant A as API
|
||||
participant C as 컴포넌트
|
||||
|
||||
U->>S: 요청 (game.onstove.com/ko/about/story)
|
||||
S->>M: gameAlias.ts 실행
|
||||
M->>M: 게임 별칭 추출 (game)
|
||||
M->>A: 게임 데이터 API 호출
|
||||
A-->>M: 게임 정보 응답
|
||||
M->>M: event.context에 저장
|
||||
S->>C: 페이지 컴포넌트 로드
|
||||
C->>A: 페이지 데이터 API 호출
|
||||
A-->>C: 템플릿 데이터 응답
|
||||
C->>C: 템플릿 렌더링
|
||||
C-->>S: HTML 생성
|
||||
S-->>U: 최종 응답
|
||||
```
|
||||
|
||||
## 8. API 통신 구조
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[게임 데이터 API] --> B[/pub-comm/v1.0/template/game]
|
||||
C[페이지 데이터 API] --> D[/pub-comm/v1.0/template/page]
|
||||
|
||||
B --> E[game_alias]
|
||||
B --> F[lang_code]
|
||||
|
||||
D --> G[game_alias]
|
||||
D --> H[lang_code]
|
||||
D --> I[page_url]
|
||||
|
||||
E --> J[게임 기본 정보]
|
||||
F --> J
|
||||
G --> K[페이지별 템플릿]
|
||||
H --> K
|
||||
I --> K
|
||||
```
|
||||
|
||||
## 9. 상태 관리 구조
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Pinia 스토어] --> B[useGameDataStore]
|
||||
A --> C[usePageDataStore]
|
||||
|
||||
B --> D[gameData]
|
||||
B --> E[setGameData]
|
||||
B --> F[clearGameData]
|
||||
|
||||
C --> G[pageData]
|
||||
C --> H[setPageData]
|
||||
C --> I[clearPageData]
|
||||
|
||||
D --> J[게임 기본 정보]
|
||||
D --> K[지원 언어 목록]
|
||||
D --> L[SEO 메타 정보]
|
||||
|
||||
G --> M[템플릿 배열]
|
||||
G --> N[페이지 컴포넌트]
|
||||
G --> O[페이지 SEO 정보]
|
||||
```
|
||||
|
||||
## 10. 컴포넌트 계층 구조
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[app.vue] --> B[NuxtLayout]
|
||||
B --> C[LayoutDefaultHeader]
|
||||
B --> D[NuxtPage]
|
||||
B --> E[LayoutDefaultFooter]
|
||||
|
||||
D --> F[페이지 컴포넌트]
|
||||
F --> G[Section]
|
||||
G --> H[TemplatesBackground]
|
||||
G --> I[동적 템플릿 컴포넌트]
|
||||
|
||||
I --> J[GrVisual01]
|
||||
I --> K[GrVisual02]
|
||||
I --> L[GrVisual03]
|
||||
|
||||
A --> M[MoleculesLoadingFull]
|
||||
A --> N[MoleculesLoadingLocal]
|
||||
```
|
||||
@@ -1,12 +1,208 @@
|
||||
<template>
|
||||
<footer class="border-t p-4 bg-black">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<a href="/" class="text-white">로고</a>
|
||||
<footer id="footer" ref="footerRef" class="bg-black">
|
||||
<div class="inner relative max-w-7xl mx-auto px-10 py-8 text-[12px] text-gray-400 md:px-4 md:py-7 md:text-[12px]">
|
||||
<div class="menu-area">
|
||||
<ul class="flex items-center flex-wrap gap-1.5 md:gap-0">
|
||||
<li v-for="(footerMenuItem, index) in footerLinks" :key="index"
|
||||
class="footer-menu-item">
|
||||
<NuxtLink
|
||||
:to="footerMenuItem.link"
|
||||
:target="footerMenuItem.target"
|
||||
:class="[
|
||||
footerMenuItem.active === 'y' && 'text-orange-600',
|
||||
'hover:text-gray-600 transition-colors'
|
||||
]"
|
||||
>
|
||||
{{ footerMenuItem.title }}
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<AtomsLanguageSwitcher />
|
||||
|
||||
<div class="rating-area">
|
||||
<img :src="footerData.dev_ci_url" alt="게임등급" class="rating-image" />
|
||||
<dl class="rating-info">
|
||||
<dt class="rating-label">게임명</dt>
|
||||
<dd class="rating-value">{{ footerData.game_rating_info.title }}</dd>
|
||||
<dt class="rating-label">상호</dt>
|
||||
<dd class="rating-value">{{ footerData.game_rating_info.company_name }}</dd>
|
||||
<dt class="rating-label">이용등급</dt>
|
||||
<dd class="rating-value">{{ footerData.game_rating_info.reg_no }}</dd>
|
||||
<dt class="rating-label">등급분류번호</dt>
|
||||
<dd class="rating-value">{{ footerData.game_rating_info.rating_grade }}</dd>
|
||||
<dt class="rating-label">제작년월일</dt>
|
||||
<dd class="rating-value">{{ footerData.game_rating_info.prod_date }}</dd>
|
||||
<dt class="rating-label">신고(등록)번호</dt>
|
||||
<dd class="rating-value">{{ footerData.game_rating_info.rating_class_no }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="address-area mt-6">
|
||||
<address class="not-italic text-gray-500">
|
||||
<div class="row my-1.5">
|
||||
<span v-dompurify-html="footerData.footer_info" class="[&_a]:cursor-pointer [&_a]:text-blue-500 [&_a]:underline"></span>
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
|
||||
<div class="copyright-area mt-5 text-gray-500 md:mt-4">
|
||||
<span>© Smilegate. All rights reserved</span>
|
||||
</div>
|
||||
|
||||
<div class="logo-area flex mt-3 md:mt-2.5">
|
||||
<a
|
||||
:href="locale === 'ja' ? 'https://www.smilegate.com/jp' : 'https://www.smilegate.com/en'"
|
||||
target="_blank"
|
||||
class="smilegate w-22 h-4.5 md:w-14 md:h-3"
|
||||
>
|
||||
<img
|
||||
:src="footerData.dev_ci_url"
|
||||
alt="스마일게이트 로고"
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://www.nx3games.com" target="_blank" class="nx3 w-14 h-3.5 ml-2.5 md:w-9 md:h-2.5 md:ml-4">
|
||||
<img
|
||||
:src="footerData.dev_ci_url"
|
||||
alt="NX3 로고"
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="language-area absolute bottom-7 right-10 text-white md:bottom-5.5 md:right-4">
|
||||
<!-- <SelectLanguage /> -->
|
||||
<!-- <AtomsLanguageSwitcher /> -->
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { locale } = useI18n();
|
||||
interface FooterMenuType {
|
||||
id: string;
|
||||
title: string;
|
||||
link: string;
|
||||
target: string;
|
||||
active: string;
|
||||
highlight?: string;
|
||||
}
|
||||
|
||||
const footerLinks = ref<FooterMenuType[]>([
|
||||
{
|
||||
"id": "company",
|
||||
"title": "회사소개",
|
||||
"link": "#",
|
||||
"target": "_blank",
|
||||
"active": "n"
|
||||
},
|
||||
{
|
||||
"id": "terms",
|
||||
"title": "이용약관",
|
||||
"link": "https://common.game.onstove.com/terms/index?gameType=SG&termsType=1&langCode=@m{Terms_Lang_Code}",
|
||||
"target": "_blank",
|
||||
"active": "n"
|
||||
},
|
||||
{
|
||||
"id": "privacy",
|
||||
"title": "개인정보처리방침",
|
||||
"link": "https://clause.onstove.com/stove/terms?category=privacy",
|
||||
"target": "_blank",
|
||||
"active": "y"
|
||||
},
|
||||
{
|
||||
"id": "operation",
|
||||
"title": "운영정책",
|
||||
"link": "https://common.game.onstove.com/terms/index?gameType=CZN&termsType=3&langCode=@m{Terms_Lang_Code}",
|
||||
"target": "_blank",
|
||||
"active": "n"
|
||||
},
|
||||
{
|
||||
"id": "fund",
|
||||
"title": "청소년보호정책",
|
||||
"link": "https://common.game.onstove.com/terms/index?gameType=CZN&termsType=6&langCode=ja",
|
||||
"target": "_blank",
|
||||
"active": "n"
|
||||
},
|
||||
{
|
||||
"id": "customerService",
|
||||
"title": "게임 이용 등급",
|
||||
"link": "https://cs.onstove.com/@m{Terms_Lang_Code}/service/STOVE_CHAOSZERO",
|
||||
"target": "_blank",
|
||||
"active": "n"
|
||||
}
|
||||
] as FooterMenuType[])
|
||||
|
||||
const footerData = ref({
|
||||
"dev_ci_url": "https://cdn.testgame.com/ci/ko_logo.png",
|
||||
"use_dev_ci_url": true,
|
||||
"fund_display_yn": true,
|
||||
"use_game_rating": true,
|
||||
"fund_display_url": "https://testgame.com/law/fund-ko",
|
||||
"game_rating_info": {
|
||||
"title": "테스트 게임",
|
||||
"reg_no": "R-2024-7890",
|
||||
"prod_date": "2024-05-01",
|
||||
"rating_type": "15",
|
||||
"company_name": "테스트엔터테인먼트",
|
||||
"content_info": "1,2,3,",
|
||||
"rating_grade": "15세 이용가",
|
||||
"rating_class_no": "2024-123456"
|
||||
},
|
||||
"footer_info": "(주)스마일게이트홀딩스 메가포트지점 대표: 성준호<br>주소: 경기도 성남시 분당구 분당로 55, 7층 (서현동 분당 퍼스트타워)<br>통신판매업 신고번호: 제2023-성남분당A-0145호<br>사업자등록번호: 813-85-02492<br>E-mail: <a href='mailto:help@smilegate.com'>help@smilegate.com</a><br>TEL: 1670-0399",
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.footer-menu-item {
|
||||
@apply text-sm md:text-xs md:tracking-tight relative;
|
||||
}
|
||||
|
||||
.footer-menu-item:not(:first-child)::before {
|
||||
content: '';
|
||||
@apply inline-block bg-gray-400 h-2 w-px mx-1.5 mt-1 align-top;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.footer-menu-item:nth-of-type(5)::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 411px) {
|
||||
.footer-menu-item {
|
||||
@apply th:text-xs;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 321px) {
|
||||
.footer-menu-item {
|
||||
@apply th:text-[14px];
|
||||
}
|
||||
}
|
||||
|
||||
/* Rating Area Styles */
|
||||
.rating-area {
|
||||
@apply flex mt-2.5 md:flex-col md:mt-1.5;
|
||||
}
|
||||
|
||||
.rating-image {
|
||||
@apply w-41 h-14 md:w-34 md:h-12 md:order-1;
|
||||
}
|
||||
|
||||
.rating-info {
|
||||
@apply grid grid-cols-[110px_auto_110px_auto] w-full max-w-[490px] ml-5
|
||||
border-t border-l border-gray-600 tracking-tight
|
||||
md:grid-cols-[66px_auto_84px_auto] md:max-w-[358px] md:m-0 md:mb-2.5;
|
||||
}
|
||||
|
||||
.rating-label {
|
||||
@apply p-1.5 px-4 border-r border-b border-gray-600 bg-[#1a1a1a] md:p-0.5 md:px-1.5;
|
||||
}
|
||||
|
||||
.rating-value {
|
||||
@apply p-1.5 px-4 border-r border-b border-gray-600 text-gray-500 md:p-0.5 md:px-1.5;
|
||||
}
|
||||
</style>
|
||||
16
layers/plugins/dompurify.client.ts
Normal file
16
layers/plugins/dompurify.client.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import DOMPurify from 'dompurify'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.directive('dompurify-html', {
|
||||
mounted(el: HTMLElement, binding: any) {
|
||||
if (binding.value) {
|
||||
el.innerHTML = DOMPurify.sanitize(binding.value)
|
||||
}
|
||||
},
|
||||
updated(el: HTMLElement, binding: any) {
|
||||
if (binding.value) {
|
||||
el.innerHTML = DOMPurify.sanitize(binding.value)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -18,6 +18,7 @@
|
||||
"@pinia/nuxt": "^0.6.1",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"@vueuse/nuxt": "^13.6.0",
|
||||
"dompurify": "^3.2.6",
|
||||
"h3": "^1.15.4",
|
||||
"nuxt": "^4.0.3",
|
||||
"pinia": "^2.3.1",
|
||||
@@ -25,6 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
"@types/dompurify": "^3.2.0",
|
||||
"@types/node": "^24.3.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.6",
|
||||
|
||||
588
pnpm-lock.yaml
generated
588
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user