fix. gnb 작업 1차
This commit is contained in:
@@ -1,107 +0,0 @@
|
||||
export default defineNuxtPlugin(() => {
|
||||
// 클라이언트 사이드에서 gameData를 가져와서 i18n 설정 업데이트
|
||||
const { $i18n } = useNuxtApp();
|
||||
|
||||
// gameData에서 언어 코드 추출
|
||||
const getGameDataLangCodes = (gameData: any) => {
|
||||
try {
|
||||
console.log("🚀 ~ getGameDataLangCodes ~ gameData:", gameData);
|
||||
|
||||
if (gameData?.lang_codes) {
|
||||
console.log("🚀 ~ getGameDataLangCodes ~ gameData.lang_codes:", gameData.lang_codes);
|
||||
console.log("🚀 ~ getGameDataLangCodes ~ lang_codes type:", typeof gameData.lang_codes);
|
||||
console.log("🚀 ~ getGameDataLangCodes ~ lang_codes isArray:", Array.isArray(gameData.lang_codes));
|
||||
return Array.isArray(gameData.lang_codes) ? gameData.lang_codes : [gameData.lang_codes];
|
||||
} else {
|
||||
console.log("🚀 ~ getGameDataLangCodes ~ gameData.lang_codes is undefined or null");
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to get gameData lang codes:', error);
|
||||
}
|
||||
|
||||
console.log("🚀 ~ getGameDataLangCodes ~ returning default ['ko']");
|
||||
return ['ko']; // 기본값
|
||||
};
|
||||
|
||||
// i18n 설정 업데이트
|
||||
const updateI18nLocales = async (gameData?: any) => {
|
||||
const langCodes = getGameDataLangCodes(gameData);
|
||||
|
||||
if (langCodes && langCodes.length > 0) {
|
||||
// 새로운 로케일 설정 생성
|
||||
const newLocales = langCodes.map(code => ({
|
||||
code,
|
||||
file: `${code}.ts`,
|
||||
name: getLocaleName(code),
|
||||
iso: getLocaleIso(code),
|
||||
dir: 'ltr'
|
||||
}));
|
||||
|
||||
// i18n 설정 업데이트
|
||||
if ($i18n) {
|
||||
// 로케일 메시지 동적 로드
|
||||
for (const code of langCodes) {
|
||||
try {
|
||||
const messages = await import(`../../i18n/locales/${code}.ts`);
|
||||
// defineI18nLocale 함수를 실행하여 실제 메시지 데이터 가져오기
|
||||
const localeMessages = await messages.default(code);
|
||||
console.log(`🚀 ~ loaded messages for ${code}:`, localeMessages);
|
||||
($i18n as any).setLocaleMessage(code, localeMessages);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load locale messages for ${code}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 로케일 이름 가져오기
|
||||
const getLocaleName = (code: string): string => {
|
||||
const localeNames: Record<string, string> = {
|
||||
en: 'English',
|
||||
'zh-tw': '繁體中文',
|
||||
ja: '日本語',
|
||||
ko: '한국어',
|
||||
fr: 'Français',
|
||||
de: 'Deutsch',
|
||||
es: 'Español',
|
||||
pt: 'Português',
|
||||
th: 'ภาษาไทย',
|
||||
'zh-cn': '简体中文'
|
||||
};
|
||||
return localeNames[code] || code;
|
||||
};
|
||||
|
||||
// 로케일 ISO 코드 가져오기
|
||||
const getLocaleIso = (code: string): string => {
|
||||
const localeIsos: Record<string, string> = {
|
||||
en: 'en',
|
||||
'zh-tw': 'zh-tw',
|
||||
ja: 'ja',
|
||||
ko: 'ko-KR',
|
||||
fr: 'fr',
|
||||
de: 'de',
|
||||
es: 'es',
|
||||
pt: 'pt',
|
||||
th: 'th',
|
||||
'zh-cn': 'zh-cn'
|
||||
};
|
||||
return localeIsos[code] || code;
|
||||
};
|
||||
|
||||
// gameData가 설정될 때까지 기다리거나 즉시 실행
|
||||
const gameDataStore = useGameDataStore();
|
||||
|
||||
// gameData가 이미 설정되어 있으면 즉시 실행
|
||||
if (gameDataStore.gameData) {
|
||||
updateI18nLocales(gameDataStore.gameData);
|
||||
}
|
||||
|
||||
// gameData가 변경될 때마다 실행
|
||||
watch(() => gameDataStore.gameData, async (newGameData) => {
|
||||
if (newGameData) {
|
||||
console.log("🚀 ~ gameData changed, updating i18n locales");
|
||||
await updateI18nLocales(newGameData);
|
||||
}
|
||||
}, { immediate: true });
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
export default defineNuxtPlugin(async () => {
|
||||
// 서버 사이드에서 gameData를 가져와서 i18n 설정 업데이트
|
||||
const { $i18n } = useNuxtApp();
|
||||
|
||||
// gameData에서 언어 코드 추출
|
||||
const getGameDataLangCodes = () => {
|
||||
try {
|
||||
// 서버 사이드에서 gameData 접근
|
||||
const nuxtApp = useNuxtApp();
|
||||
const gameData = nuxtApp.ssrContext?.event.context.gameData;
|
||||
|
||||
if (gameData?.lang_codes) {
|
||||
console.log("🚀 ~ dynamic-i18n-runtime.server ~ gameData.lang_codes:", gameData.lang_codes);
|
||||
return Array.isArray(gameData.lang_codes) ? gameData.lang_codes : [gameData.lang_codes];
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to get gameData lang codes on server:', error);
|
||||
}
|
||||
|
||||
return ['ko']; // 기본값
|
||||
};
|
||||
|
||||
// i18n 설정 업데이트
|
||||
const updateI18nLocales = async () => {
|
||||
const langCodes = getGameDataLangCodes();
|
||||
console.log("🚀 ~77777 updateI18nLocales ~ langCodes:", langCodes)
|
||||
|
||||
if (langCodes && langCodes.length > 0) {
|
||||
console.log("🚀 ~ dynamic-i18n-runtime.server ~ updating locales with:", langCodes);
|
||||
|
||||
// 새로운 로케일 설정 생성
|
||||
const newLocales = langCodes.map(code => ({
|
||||
code,
|
||||
file: `${code}.ts`,
|
||||
name: getLocaleName(code),
|
||||
iso: getLocaleIso(code),
|
||||
dir: 'ltr'
|
||||
}));
|
||||
|
||||
// i18n 설정 업데이트
|
||||
if ($i18n) {
|
||||
// 로케일 메시지 동적 로드
|
||||
for (const code of langCodes) {
|
||||
try {
|
||||
const messages = await import(`../../i18n/locales/${code}.ts`);
|
||||
// defineI18nLocale 함수를 실행하여 실제 메시지 데이터 가져오기
|
||||
const localeMessages = await messages.default(code);
|
||||
console.log(`🚀 ~ loaded messages for ${code}:`, localeMessages);
|
||||
($i18n as any).setLocaleMessage(code, localeMessages);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load locale messages for ${code}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 로케일 이름 가져오기
|
||||
const getLocaleName = (code: string): string => {
|
||||
const localeNames: Record<string, string> = {
|
||||
en: 'English',
|
||||
'zh-tw': '繁體中文',
|
||||
ja: '日本語',
|
||||
ko: '한국어',
|
||||
fr: 'Français',
|
||||
de: 'Deutsch',
|
||||
es: 'Español',
|
||||
pt: 'Português',
|
||||
th: 'ภาษาไทย',
|
||||
'zh-cn': '简体中文'
|
||||
};
|
||||
return localeNames[code] || code;
|
||||
};
|
||||
|
||||
// 로케일 ISO 코드 가져오기
|
||||
const getLocaleIso = (code: string): string => {
|
||||
const localeIsos: Record<string, string> = {
|
||||
en: 'en',
|
||||
'zh-tw': 'zh-tw',
|
||||
ja: 'ja',
|
||||
ko: 'ko-KR',
|
||||
fr: 'fr',
|
||||
de: 'de',
|
||||
es: 'es',
|
||||
pt: 'pt',
|
||||
th: 'th',
|
||||
'zh-cn': 'zh-cn'
|
||||
};
|
||||
return localeIsos[code] || code;
|
||||
};
|
||||
|
||||
// 서버 사이드에서 즉시 실행
|
||||
await updateI18nLocales();
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
import { getHeader, defineEventHandler } from 'h3'
|
||||
import { useRuntimeConfig } from 'nuxt/app'
|
||||
import type {
|
||||
GameDataResponse,
|
||||
GameDataValue,
|
||||
} from '../layers/types/api/gameData'
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const config = useRuntimeConfig()
|
||||
const baseDomain = (config.public.baseDomain || '.onstove.com') as string
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/game`
|
||||
let gameAlias = ''
|
||||
|
||||
try {
|
||||
// 미들웨어에서 설정한 gameAlias가 있다면 우선 사용
|
||||
if (event.context.gameAlias) {
|
||||
gameAlias = event.context.gameAlias
|
||||
} else {
|
||||
const host = getHeader(event, 'host') || ''
|
||||
const isGameAliasExtractable = host.includes(baseDomain)
|
||||
|
||||
if (isGameAliasExtractable) {
|
||||
const subdomain = host.split('.')[0]
|
||||
if (subdomain && subdomain !== 'www') {
|
||||
gameAlias = subdomain
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('gameAlias extraction error: ', error)
|
||||
}
|
||||
|
||||
try {
|
||||
const queryParams: Record<string, string> = {
|
||||
game_alias: gameAlias,
|
||||
}
|
||||
|
||||
const response = await $fetch<GameDataResponse>(apiUrl, {
|
||||
query: queryParams,
|
||||
})
|
||||
|
||||
if (response?.code === 0 && 'value' in response) {
|
||||
event.context.gameData = response.value
|
||||
|
||||
// lang_codes를 사용해서 동적으로 i18n 설정 업데이트
|
||||
if (
|
||||
response.value.lang_codes &&
|
||||
Array.isArray(response.value.lang_codes)
|
||||
) {
|
||||
event.context.availableLocales = response.value.lang_codes
|
||||
event.context.defaultLocale =
|
||||
response.value.default_lang_code ||
|
||||
response.value.lang_codes[0] ||
|
||||
'ko'
|
||||
}
|
||||
|
||||
return response.value as GameDataValue
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return {}
|
||||
}
|
||||
})
|
||||
@@ -1,40 +0,0 @@
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
// 서버 사이드에서만 실행
|
||||
if (import.meta.client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gameDataStore = useGameDataStore();
|
||||
|
||||
// gameData가 로드되지 않았다면 gameData API 호출
|
||||
if (!gameDataStore.gameData) {
|
||||
try {
|
||||
await $fetch('/api/gameData');
|
||||
} catch (error) {
|
||||
console.error('gameData 로드 실패:', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const availableLangCodes = gameDataStore.gameData?.lang_codes || ['ko'];
|
||||
const defaultLangCode = gameDataStore.gameData?.default_lang_code || availableLangCodes[0];
|
||||
|
||||
// 현재 경로에서 언어 코드 추출
|
||||
const pathSegments = to.path.split('/').filter(Boolean);
|
||||
const currentLangCode = pathSegments[0];
|
||||
|
||||
// 언어 코드가 유효한지 확인
|
||||
const isValidLangCode = availableLangCodes.includes(currentLangCode);
|
||||
|
||||
// 유효하지 않은 언어 코드인 경우 기본 언어로 리다이렉트
|
||||
if (currentLangCode && !isValidLangCode) {
|
||||
const newPath = `/${defaultLangCode}${to.path.replace(`/${currentLangCode}`, '')}`;
|
||||
return navigateTo(newPath, { replace: true });
|
||||
}
|
||||
|
||||
// 언어 코드가 없는 경우 기본 언어 코드 추가
|
||||
if (!currentLangCode || !availableLangCodes.includes(currentLangCode)) {
|
||||
const newPath = `/${defaultLangCode}${to.path}`;
|
||||
return navigateTo(newPath, { replace: true });
|
||||
}
|
||||
});
|
||||
@@ -4,22 +4,22 @@
|
||||
import commonData from '../../layers/assets/data/common.json'
|
||||
|
||||
export default defineI18nLocale(async (locale: string) => {
|
||||
const config = useRuntimeConfig()
|
||||
const baseType = config.public.baseType
|
||||
const translationItems = config.public.translationItems
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const baseType = runtimeConfig.public.baseType
|
||||
const translationItems = runtimeConfig.public.translationItems
|
||||
const translationItemsArr = translationItems.split(',')
|
||||
const staticUrl = config.public.staticUrl
|
||||
const staticUrl = runtimeConfig.public.staticUrl
|
||||
const translationApi = translationItemsArr.map((item: string): string => {
|
||||
return `${staticUrl}/${baseType}/tmp/${item}.json`
|
||||
})
|
||||
|
||||
// API 데이터 가져오기
|
||||
const fetchDataPromises = translationApi.map((apiUrl) => {
|
||||
const fetchDataPromises = translationApi.map(apiUrl => {
|
||||
return useFetch(apiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ export default defineI18nLocale(async (locale: string) => {
|
||||
const fetchResults = await Promise.all(fetchDataPromises)
|
||||
|
||||
// 각 결과에서 locale에 맞는 데이터를 추출
|
||||
const apiData = fetchResults.map((result) => {
|
||||
const apiData = fetchResults.map(result => {
|
||||
return result.data.value?.[locale] || {} // locale에 맞는 데이터가 없으면 빈 객체 반환
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ export default defineI18nLocale(async (locale: string) => {
|
||||
|
||||
// common.json에서 해당 locale의 데이터를 가져와서 병합
|
||||
const commonLocaleData = commonData[locale] || {}
|
||||
|
||||
|
||||
// API 데이터와 common.json 데이터를 병합 (common.json이 우선순위)
|
||||
const finalResult = { ...mergedResult, ...commonLocaleData }
|
||||
|
||||
|
||||
@@ -4,22 +4,22 @@
|
||||
import commonData from '../../layers/assets/data/common.json'
|
||||
|
||||
export default defineI18nLocale(async (locale: string) => {
|
||||
const config = useRuntimeConfig()
|
||||
const baseType = config.public.baseType
|
||||
const translationItems = config.public.translationItems
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const baseType = runtimeConfig.public.baseType
|
||||
const translationItems = runtimeConfig.public.translationItems
|
||||
const translationItemsArr = translationItems.split(',')
|
||||
const staticUrl = config.public.staticUrl
|
||||
const staticUrl = runtimeConfig.public.staticUrl
|
||||
const translationApi = translationItemsArr.map((item: string): string => {
|
||||
return `${staticUrl}/${baseType}/tmp/${item}.json`
|
||||
})
|
||||
|
||||
// API 데이터 가져오기
|
||||
const fetchDataPromises = translationApi.map((apiUrl) => {
|
||||
const fetchDataPromises = translationApi.map(apiUrl => {
|
||||
return useFetch(apiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ export default defineI18nLocale(async (locale: string) => {
|
||||
const fetchResults = await Promise.all(fetchDataPromises)
|
||||
|
||||
// 각 결과에서 locale에 맞는 데이터를 추출
|
||||
const apiData = fetchResults.map((result) => {
|
||||
const apiData = fetchResults.map(result => {
|
||||
return result.data.value?.[locale] || {} // locale에 맞는 데이터가 없으면 빈 객체 반환
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ export default defineI18nLocale(async (locale: string) => {
|
||||
|
||||
// common.json에서 해당 locale의 데이터를 가져와서 병합
|
||||
const commonLocaleData = commonData[locale] || {}
|
||||
|
||||
|
||||
// API 데이터와 common.json 데이터를 병합 (common.json이 우선순위)
|
||||
const finalResult = { ...mergedResult, ...commonLocaleData }
|
||||
|
||||
|
||||
@@ -4,22 +4,22 @@
|
||||
import commonData from '../../layers/assets/data/common.json'
|
||||
|
||||
export default defineI18nLocale(async (locale: string) => {
|
||||
const config = useRuntimeConfig()
|
||||
const baseType = config.public.baseType
|
||||
const translationItems = config.public.translationItems
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const baseType = runtimeConfig.public.baseType
|
||||
const translationItems = runtimeConfig.public.translationItems
|
||||
const translationItemsArr = translationItems.split(',')
|
||||
const staticUrl = config.public.staticUrl
|
||||
const staticUrl = runtimeConfig.public.staticUrl
|
||||
const translationApi = translationItemsArr.map((item: string): string => {
|
||||
return `${staticUrl}/${baseType}/tmp/${item}.json`
|
||||
})
|
||||
|
||||
// API 데이터 가져오기
|
||||
const fetchDataPromises = translationApi.map((apiUrl) => {
|
||||
const fetchDataPromises = translationApi.map(apiUrl => {
|
||||
return useFetch(apiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ export default defineI18nLocale(async (locale: string) => {
|
||||
const fetchResults = await Promise.all(fetchDataPromises)
|
||||
|
||||
// 각 결과에서 locale에 맞는 데이터를 추출
|
||||
const apiData = fetchResults.map((result) => {
|
||||
const apiData = fetchResults.map(result => {
|
||||
return result.data.value?.[locale] || {} // locale에 맞는 데이터가 없으면 빈 객체 반환
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ export default defineI18nLocale(async (locale: string) => {
|
||||
|
||||
// common.json에서 해당 locale의 데이터를 가져와서 병합
|
||||
const commonLocaleData = commonData[locale] || {}
|
||||
|
||||
|
||||
// API 데이터와 common.json 데이터를 병합 (common.json이 우선순위)
|
||||
const finalResult = { ...mergedResult, ...commonLocaleData }
|
||||
|
||||
|
||||
@@ -4,22 +4,22 @@
|
||||
import commonData from '../../layers/assets/data/common.json'
|
||||
|
||||
export default defineI18nLocale(async (locale: string) => {
|
||||
const config = useRuntimeConfig()
|
||||
const baseType = config.public.baseType
|
||||
const translationItems = config.public.translationItems
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const baseType = runtimeConfig.public.baseType
|
||||
const translationItems = runtimeConfig.public.translationItems
|
||||
const translationItemsArr = translationItems.split(',')
|
||||
const staticUrl = config.public.staticUrl
|
||||
const staticUrl = runtimeConfig.public.staticUrl
|
||||
const translationApi = translationItemsArr.map((item: string): string => {
|
||||
return `${staticUrl}/${baseType}/tmp/${item}.json`
|
||||
})
|
||||
|
||||
// API 데이터 가져오기
|
||||
const fetchDataPromises = translationApi.map((apiUrl) => {
|
||||
const fetchDataPromises = translationApi.map(apiUrl => {
|
||||
return useFetch(apiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ export default defineI18nLocale(async (locale: string) => {
|
||||
const fetchResults = await Promise.all(fetchDataPromises)
|
||||
|
||||
// 각 결과에서 locale에 맞는 데이터를 추출
|
||||
const apiData = fetchResults.map((result) => {
|
||||
const apiData = fetchResults.map(result => {
|
||||
return result.data.value?.[locale] || {} // locale에 맞는 데이터가 없으면 빈 객체 반환
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ export default defineI18nLocale(async (locale: string) => {
|
||||
|
||||
// common.json에서 해당 locale의 데이터를 가져와서 병합
|
||||
const commonLocaleData = commonData[locale] || {}
|
||||
|
||||
|
||||
// API 데이터와 common.json 데이터를 병합 (common.json이 우선순위)
|
||||
const finalResult = { ...mergedResult, ...commonLocaleData }
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassType } from '#layers/types/Common'
|
||||
|
||||
interface Props {
|
||||
to: string
|
||||
target?: string
|
||||
class?: string
|
||||
class?: ClassType
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
@@ -17,11 +17,12 @@ const _breakpoints = useResponsiveBreakpointsReliable()
|
||||
|
||||
const { isPassedStoveGnb } = storeToRefs(scrollStore)
|
||||
|
||||
const gameData = gameDataStore.gameData as GameDataValue
|
||||
const gnbList = (gameData?.gnb?.menus ?? {}) as GameDataMenuChildren
|
||||
const isMenuOpen = ref(false)
|
||||
const navAreaRef = ref<HTMLElement>()
|
||||
const startRef = ref<HTMLElement>()
|
||||
|
||||
const gameData = gameDataStore.gameData as GameDataValue
|
||||
const gnbData = gameData?.gnb
|
||||
const isMenuOpen = ref(false)
|
||||
const navWidth = ref(0)
|
||||
const startWidth = ref(0)
|
||||
const officialItemWidths = ref<number[]>([])
|
||||
@@ -71,7 +72,8 @@ const calculateNavWidth = () => {
|
||||
if (!navAreaRef.value) return 0
|
||||
|
||||
const navAreaWidth = navAreaRef.value.offsetWidth
|
||||
return navAreaWidth
|
||||
const moreWidth = 72
|
||||
return navAreaWidth + moreWidth
|
||||
}
|
||||
|
||||
// startRef의 넓이를 구하는 함수
|
||||
@@ -79,7 +81,8 @@ const calculateStartWidth = () => {
|
||||
if (!startRef.value) return 0
|
||||
|
||||
const startWidth = startRef.value.offsetWidth
|
||||
return startWidth + 40
|
||||
const headerPadding = 40
|
||||
return startWidth + headerPadding
|
||||
}
|
||||
|
||||
// official 자식들의 넓이를 구하는 함수 (뒤에서부터 순서대로)
|
||||
@@ -103,8 +106,6 @@ const calculateOfficialItemWidths = () => {
|
||||
calculateOverflow()
|
||||
}
|
||||
|
||||
console.log(0, Object.keys(gnbList).length)
|
||||
|
||||
// 오버플로우 계산 함수
|
||||
const calculateOverflow = () => {
|
||||
if (!navAreaRef.value) return
|
||||
@@ -112,18 +113,9 @@ const calculateOverflow = () => {
|
||||
const totalNavWidth = navWidth.value + startWidth.value
|
||||
const screenWidth = width.value
|
||||
|
||||
console.log('calculateOverflow called:', {
|
||||
screenWidth,
|
||||
totalNavWidth,
|
||||
navWidth: navWidth.value,
|
||||
startWidth: startWidth.value,
|
||||
officialItemWidths: officialItemWidths.value,
|
||||
})
|
||||
|
||||
// 모바일(1024px 미만)에서는 overflowNam을 0으로 설정
|
||||
if (screenWidth < 1024) {
|
||||
overflowNam.value = 0
|
||||
console.log('Mobile view - overflowNam set to 0')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -132,12 +124,10 @@ const calculateOverflow = () => {
|
||||
let removedCount = 0
|
||||
let currentTotal = totalNavWidth
|
||||
|
||||
// officialItemWidths를 하나씩 빼면서 해상도보다 작아지는지 확인
|
||||
for (let i = 0; i < officialItemWidths.value.length; i++) {
|
||||
currentTotal -= officialItemWidths.value[i]
|
||||
removedCount++
|
||||
|
||||
// 해상도보다 작아지면 중단
|
||||
if (currentTotal <= screenWidth) {
|
||||
break
|
||||
}
|
||||
@@ -147,7 +137,6 @@ const calculateOverflow = () => {
|
||||
console.log('Overflow calculated:', overflowNam.value)
|
||||
} else {
|
||||
overflowNam.value = 0
|
||||
console.log('No overflow needed, setting to 0')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +144,6 @@ const calculateOverflow = () => {
|
||||
onMounted(() => {
|
||||
// 초기화
|
||||
overflowNam.value = 0
|
||||
console.log('onMounted - overflowNam 초기화:', overflowNam.value)
|
||||
|
||||
nextTick(() => {
|
||||
if (navAreaRef.value && startRef.value) {
|
||||
@@ -182,7 +170,7 @@ onClickOutside(navAreaRef, () => (isMenuOpen.value = false))
|
||||
<div class="game-wrapper" :class="{ 'is-fixed': isPassedStoveGnb }">
|
||||
<AtomsLocaleLink to="/brand" class="mx-auto md:hidden">
|
||||
<img
|
||||
:src="gameData?.gnb?.bi_path"
|
||||
:src="gnbData?.bi_path"
|
||||
:alt="gameData?.game_name"
|
||||
class="h-[30px]"
|
||||
/>
|
||||
@@ -198,31 +186,35 @@ onClickOutside(navAreaRef, () => (isMenuOpen.value = false))
|
||||
<div class="nav-logo">
|
||||
<AtomsLocaleLink to="/brand">
|
||||
<img
|
||||
:src="gameData?.gnb?.bi_path"
|
||||
:src="gnbData?.bi_path"
|
||||
:alt="gameData?.game_name"
|
||||
class="h-[30px]"
|
||||
/>
|
||||
</AtomsLocaleLink>
|
||||
</div>
|
||||
<nav class="nav-list">
|
||||
<div v-if="gnbList" class="official">
|
||||
<div v-if="gnbData?.menus" class="official">
|
||||
<div
|
||||
v-for="(gnbItem, key) in gnbList"
|
||||
v-for="(gnbItem, key) in gnbData?.menus"
|
||||
:key="key"
|
||||
class="nav-item"
|
||||
:class="{
|
||||
'is-hidden':
|
||||
overflowNam > 0 &&
|
||||
Number(key) >= Object.keys(gnbList).length - overflowNam,
|
||||
Number(key) >=
|
||||
Object.keys(gnbData?.menus).length - overflowNam,
|
||||
}"
|
||||
>
|
||||
<BlocksHybridLink
|
||||
:to="gnbItem.url_path"
|
||||
:target="gnbItem.link_target"
|
||||
:class="`nav-1depth ${isNavItemActive(gnbItem) ? 'active' : ''}`"
|
||||
:class="['nav-1depth', { active: isNavItemActive(gnbItem) }]"
|
||||
>
|
||||
<span>{{ gnbItem.menu_name }}</span>
|
||||
<AtomsIconsArrowDown v-if="gnbItem.children" />
|
||||
<AtomsIconsArrowDown
|
||||
v-if="gnbItem.children"
|
||||
class="hidden md:block"
|
||||
/>
|
||||
</BlocksHybridLink>
|
||||
<div v-if="gnbItem.children" class="nav-2depth">
|
||||
<ul>
|
||||
@@ -244,44 +236,46 @@ onClickOutside(navAreaRef, () => (isMenuOpen.value = false))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="gnbList && overflowNam > 0" class="more">
|
||||
<div v-if="gnbData?.menus && overflowNam > 0" class="more">
|
||||
<button class="btn-more">
|
||||
<span class="sr-only">more</span>
|
||||
</button>
|
||||
<div class="more-list">
|
||||
<div
|
||||
v-for="(gnbItem, key) in gnbList"
|
||||
:key="key"
|
||||
:class="{
|
||||
hidden:
|
||||
Number(key) < Object.keys(gnbList).length - overflowNam,
|
||||
}"
|
||||
>
|
||||
<BlocksHybridLink
|
||||
:to="gnbItem.url_path"
|
||||
:target="gnbItem.link_target"
|
||||
:class="`${isNavItemActive(gnbItem) ? 'active' : ''}`"
|
||||
<div class="list-inner">
|
||||
<div
|
||||
v-for="(gnbItem, key) in gnbData?.menus"
|
||||
:key="key"
|
||||
:class="{
|
||||
hidden:
|
||||
Number(key) <
|
||||
Object.keys(gnbData?.menus).length - overflowNam,
|
||||
}"
|
||||
>
|
||||
<span>{{ gnbItem.menu_name }}</span>
|
||||
<AtomsIconsArrowDown v-if="gnbItem.children" />
|
||||
</BlocksHybridLink>
|
||||
<div v-if="gnbItem.children">
|
||||
<ul>
|
||||
<li
|
||||
v-for="child in gnbItem.children"
|
||||
:key="child.menu_name"
|
||||
>
|
||||
<BlocksHybridLink
|
||||
:to="child.url_path"
|
||||
:target="child.link_target"
|
||||
<BlocksHybridLink
|
||||
:to="gnbItem.url_path"
|
||||
:target="gnbItem.link_target"
|
||||
:class="`${isNavItemActive(gnbItem) ? 'active' : ''}`"
|
||||
>
|
||||
<span>{{ gnbItem.menu_name }}</span>
|
||||
</BlocksHybridLink>
|
||||
<div v-if="gnbItem.children">
|
||||
<ul>
|
||||
<li
|
||||
v-for="child in gnbItem.children"
|
||||
:key="child.menu_name"
|
||||
>
|
||||
<span>{{ child.menu_name }}</span>
|
||||
<AtomsIconsLinkOut
|
||||
v-if="child.link_target === '_blank'"
|
||||
/>
|
||||
</BlocksHybridLink>
|
||||
</li>
|
||||
</ul>
|
||||
<BlocksHybridLink
|
||||
:to="child.url_path"
|
||||
:target="child.link_target"
|
||||
>
|
||||
<span>{{ child.menu_name }}</span>
|
||||
<AtomsIconsLinkOut
|
||||
v-if="child.link_target === '_blank'"
|
||||
/>
|
||||
</BlocksHybridLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -340,9 +334,6 @@ onClickOutside(navAreaRef, () => (isMenuOpen.value = false))
|
||||
.btn-start {
|
||||
@apply relative mt-2 px-5 md:absolute md:right-0 md:mt-0 md:px-0;
|
||||
}
|
||||
.btn-more {
|
||||
@apply w-[40px] h-[40px] bg-[red];
|
||||
}
|
||||
|
||||
.gnb-game {
|
||||
@apply absolute top-0 left-0 w-0 md:relative md:w-full md:!h-full;
|
||||
@@ -355,12 +346,12 @@ onClickOutside(navAreaRef, () => (isMenuOpen.value = false))
|
||||
@apply content-[''] absolute inset-0 w-[100vw] h-full bg-[rgba(0,0,0,0.6)] md:hidden;
|
||||
}
|
||||
.gnb-game.is-open .nav-area {
|
||||
@apply h-full translate-x-0 transition-transform duration-300 md:translate-x-0;
|
||||
@apply h-full translate-x-0 transition-transform duration-300 md:transform-none;
|
||||
}
|
||||
|
||||
.nav-area {
|
||||
@apply flex flex-col w-[360px] bg-theme-foreground-10 translate-x-[-100%]
|
||||
md:inline-flex md:flex-row md:w-auto md:h-full md:pl-[40px] md:items-center md:bg-transparent transform-none;
|
||||
md:inline-flex md:flex-row md:w-auto md:h-full md:pl-[40px] md:items-center md:bg-transparent md:transform-none;
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
@@ -369,7 +360,7 @@ onClickOutside(navAreaRef, () => (isMenuOpen.value = false))
|
||||
|
||||
.nav-list {
|
||||
@apply overflow-hidden flex flex-col order-1 h-full mt-2 mb-4 px-2
|
||||
md:flex-row md:order-none md:h-full md:my-0 md:mx-10 md:px-0 md:overflow-visible;
|
||||
md:flex-row md:order-none md:h-full md:my-0 md:ml-10 md:mr-6 md:px-0 md:overflow-visible;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
@@ -400,15 +391,15 @@ onClickOutside(navAreaRef, () => (isMenuOpen.value = false))
|
||||
}
|
||||
|
||||
.nav-2depth {
|
||||
@apply block text-[15px] md:hidden md:absolute md:top-[64px] md:left-[-28px] md:min-w-[190px] md:pt-1 md:z-50;
|
||||
@apply text-[15px] md:hidden md:absolute md:top-[64px] md:left-[-28px] md:pt-1;
|
||||
}
|
||||
.nav-2depth ul {
|
||||
@apply bg-theme-foreground-10 rounded-[20px] md:shadow-lg md:p-3;
|
||||
@apply bg-theme-foreground-10 rounded-[20px] md:min-w-[190px] md:p-3 md:shadow-lg;
|
||||
}
|
||||
.nav-2depth a {
|
||||
@apply flex items-center gap-1 px-5 py-[9px] rounded-[12px] transition-colors
|
||||
hover:bg-theme-foreground-reversal-4 active:bg-theme-foreground-reversal-10
|
||||
md:px-4 md:py-[11px];
|
||||
@apply flex items-center gap-1 py-[9px] px-5 rounded-[12px] transition-colors
|
||||
md:py-[11px] md:px-4
|
||||
hover:bg-theme-foreground-reversal-4 active:bg-theme-foreground-reversal-10;
|
||||
}
|
||||
|
||||
.official {
|
||||
@@ -416,14 +407,30 @@ onClickOutside(navAreaRef, () => (isMenuOpen.value = false))
|
||||
}
|
||||
|
||||
.more {
|
||||
@apply relative hidden md:block;
|
||||
@apply relative hidden ml-[32px] pt-[11px] md:block;
|
||||
}
|
||||
.more:hover .more-list {
|
||||
@apply md:block;
|
||||
}
|
||||
.btn-more {
|
||||
@apply w-[40px] h-[40px] rounded-[12px] bg-theme-foreground-reversal-6 hover:bg-theme-foreground-reversal-10 active:bg-theme-foreground-reversal-4;
|
||||
}
|
||||
.more-list {
|
||||
@apply absolute;
|
||||
@apply hidden absolute top-[64px] left-[-20px] pt-1;
|
||||
}
|
||||
.list-inner {
|
||||
@apply min-w-[190px] p-3 rounded-[20px] bg-theme-foreground-10 shadow-lg;
|
||||
}
|
||||
.more-list a {
|
||||
@apply flex items-center gap-1 py-[10px] px-4 rounded-[12px] transition-colors
|
||||
hover:bg-theme-foreground-reversal-4 active:bg-theme-foreground-reversal-10;
|
||||
}
|
||||
.more-list li a {
|
||||
@apply px-6;
|
||||
}
|
||||
|
||||
.event {
|
||||
@apply ml-[100px];
|
||||
@apply relative md:ml-[64px] md:after:content-[''] md:after:absolute md:after:top-[50%] md:after:left-[-32px] md:after:w-[1px] md:after:h-[16px] md:after:bg-theme-foreground-reversal-30 md:after:translate-y-[-50%];
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import * as amplitude from '@amplitude/analytics-browser'
|
||||
import type { AnalyticsDetailType, AnalyticsLogDataTracking } from '../types/AnalyticsType'
|
||||
import type {
|
||||
AnalyticsDetailType,
|
||||
AnalyticsLogDataTracking,
|
||||
} from '../types/AnalyticsType'
|
||||
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
import type { IdentityInfo, ActionInfo, MarketingInfo } from '../types/Stove81Plug'
|
||||
import type { IdentityInfo, ActionInfo, MarketingInfo } from '../types/Stove'
|
||||
|
||||
declare const svcLog: any
|
||||
declare const twq: any
|
||||
@@ -24,32 +27,37 @@ export const useAnalyticsLogData = (
|
||||
return ref({} as AnalyticsDetailType)
|
||||
}
|
||||
|
||||
const pageDataTrack = (typeof resourcesData.tracking === 'object' ? resourcesData.tracking : {}) as AnalyticsLogDataTracking
|
||||
console.log("🚀 ~ useAnalyticsLogData ~ pageDataTrack:", pageData)
|
||||
const pageDataTrack = (
|
||||
typeof resourcesData.tracking === 'object' ? resourcesData.tracking : {}
|
||||
) as AnalyticsLogDataTracking
|
||||
console.log('🚀 ~ useAnalyticsLogData ~ pageDataTrack:', pageData)
|
||||
|
||||
const logData = ref({
|
||||
actionType: pageDataTrack?.action_type,
|
||||
// logSourceType:pageDataTrack.logSourceType,
|
||||
// viewArea:pageDataTrack.viewArea,
|
||||
// viewType:pageDataTrack.viewType,
|
||||
clickArea:pageData.page_name_en,
|
||||
clickArea: pageData.page_name_en,
|
||||
clickSarea: pageData.templates[pageVerTmplSeq].page_ver_tmpl_name_en,
|
||||
clickItem: `${pageData.templates[pageVerTmplSeq].page_ver_tmpl_name}_${pageDataTrack?.click_item}`,
|
||||
event: pageData.page_name,
|
||||
eventCategory: `${pageData.page_name}_${pageDataTrack?.click_item}`,
|
||||
template_code: pageData.templates[pageVerTmplSeq].template_code,
|
||||
page_ver_tmpl_name: pageData.templates[pageVerTmplSeq].page_ver_tmpl_name,
|
||||
page_ver_tmpl_name_en: pageData.templates[pageVerTmplSeq].page_ver_tmpl_name_en,
|
||||
page_ver_tmpl_name_en:
|
||||
pageData.templates[pageVerTmplSeq].page_ver_tmpl_name_en,
|
||||
} as unknown as AnalyticsDetailType)
|
||||
|
||||
return logData
|
||||
}
|
||||
|
||||
|
||||
// target에 {XX1, XX2}와 같은 형태가 포함되어 있을 경우 options.clickItem으로부터 값 추출하여 세팅
|
||||
const findValueFromOption = (target: string, { options = {} }: any) => {
|
||||
if (target.includes('{') && target.includes('}')) {
|
||||
const strTargetClickItem = target.substring(target.indexOf('{') + 1, target.indexOf('}'))
|
||||
const strTargetClickItem = target.substring(
|
||||
target.indexOf('{') + 1,
|
||||
target.indexOf('}')
|
||||
)
|
||||
|
||||
const arrTargetClickItem = strTargetClickItem.split(',')
|
||||
const arrTargetClickItemValue = []
|
||||
@@ -58,7 +66,10 @@ const findValueFromOption = (target: string, { options = {} }: any) => {
|
||||
targetClickItem = targetClickItem.trim()
|
||||
arrTargetClickItemValue.push(options.clickItem[targetClickItem])
|
||||
}
|
||||
target = target.replaceAll(`{${strTargetClickItem}}`, arrTargetClickItemValue.join(','))
|
||||
target = target.replaceAll(
|
||||
`{${strTargetClickItem}}`,
|
||||
arrTargetClickItemValue.join(',')
|
||||
)
|
||||
}
|
||||
return target
|
||||
}
|
||||
@@ -70,7 +81,7 @@ const findValueFromOption = (target: string, { options = {} }: any) => {
|
||||
* @param {object} options
|
||||
*/
|
||||
const sendGA = (analytics: AnalyticsDetailType, { options = {} }: any) => {
|
||||
console.log("🚀 ~ 1111 sendGA ~ analytics:", analytics)
|
||||
console.log('🚀 ~ 1111 sendGA ~ analytics:', analytics)
|
||||
try {
|
||||
const { gtag } = useGtag()
|
||||
|
||||
@@ -84,7 +95,7 @@ const sendGA = (analytics: AnalyticsDetailType, { options = {} }: any) => {
|
||||
gtag('set', 'cookie_domain', `${window?.location?.hostname || ''}`) // env 값으로 설정 시 쿠키 생성 안 돼서 window.location.hostname으로 설정
|
||||
gtag('set', 'cookie_expires', '0') // 0으로 설정 시 쿠키가 Session 기반 쿠키로 전환
|
||||
gtag('event', `${eventName}`, {
|
||||
event_category: eventLabel
|
||||
event_category: eventLabel,
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('[Exception] useAnalytics.sendGA: ', e)
|
||||
@@ -98,7 +109,10 @@ const sendGA = (analytics: AnalyticsDetailType, { options = {} }: any) => {
|
||||
* @param {string} mcode
|
||||
* @param {object} options
|
||||
*/
|
||||
const sendSA = (analytics: AnalyticsDetailType, { mcode = '', options = {} }: any) => {
|
||||
const sendSA = (
|
||||
analytics: AnalyticsDetailType,
|
||||
{ mcode = '', options = {} }: any
|
||||
) => {
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
|
||||
@@ -116,7 +130,9 @@ const sendSA = (analytics: AnalyticsDetailType, { mcode = '', options = {} }: an
|
||||
const viewArea = analytics.viewArea || ''
|
||||
const viewType = analytics.viewType || ''
|
||||
const clickArea = analytics.clickArea || ''
|
||||
const clickSarea = findValueFromOption(analytics.clickSarea || '', { options })
|
||||
const clickSarea = findValueFromOption(analytics.clickSarea || '', {
|
||||
options,
|
||||
})
|
||||
const eventLocale = analytics.eventLocale || ''
|
||||
|
||||
const identityInfo: IdentityInfo = {
|
||||
@@ -126,14 +142,14 @@ const sendSA = (analytics: AnalyticsDetailType, { mcode = '', options = {} }: an
|
||||
locale: eventLocale,
|
||||
lang_cd: eventLocale,
|
||||
member_no: memberNo,
|
||||
channeling_cd: 'SO'
|
||||
channeling_cd: 'SO',
|
||||
}
|
||||
|
||||
const marketingInfo: MarketingInfo = {
|
||||
marketing_code: mcode || '',
|
||||
device_type: deviceType,
|
||||
media_type: '',
|
||||
media_page: ''
|
||||
media_page: '',
|
||||
}
|
||||
|
||||
let actionParam = {}
|
||||
@@ -145,8 +161,8 @@ const sendSA = (analytics: AnalyticsDetailType, { mcode = '', options = {} }: an
|
||||
view_info: {
|
||||
game_no: gameNo,
|
||||
lang_cd: eventLocale,
|
||||
...options?.viewInfo
|
||||
}
|
||||
...options?.viewInfo,
|
||||
},
|
||||
}
|
||||
} else if (actionType === 'click') {
|
||||
actionParam = {
|
||||
@@ -156,21 +172,21 @@ const sendSA = (analytics: AnalyticsDetailType, { mcode = '', options = {} }: an
|
||||
click_item: analytics.clickItem,
|
||||
game_no: gameNo,
|
||||
lang_cd: eventLocale,
|
||||
...options?.clickItem
|
||||
}
|
||||
...options?.clickItem,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const actionInfo: ActionInfo = {
|
||||
action_type: actionType,
|
||||
action_param: actionParam,
|
||||
marketing_info: marketingInfo
|
||||
marketing_info: marketingInfo,
|
||||
}
|
||||
|
||||
const amplitudeActionInfo = {
|
||||
...actionInfo,
|
||||
url: `${location?.href || ''}`,
|
||||
agent: `${navigator?.userAgent || ''}`
|
||||
agent: `${navigator?.userAgent || ''}`,
|
||||
}
|
||||
|
||||
const amplitudeActionParams: {
|
||||
@@ -178,7 +194,7 @@ const sendSA = (analytics: AnalyticsDetailType, { mcode = '', options = {} }: an
|
||||
event_properties: ActionInfo & { url: string; agent: string }
|
||||
} = {
|
||||
event_type: actionType,
|
||||
event_properties: amplitudeActionInfo
|
||||
event_properties: amplitudeActionInfo,
|
||||
}
|
||||
|
||||
svcLog.identity(identityInfo)
|
||||
@@ -196,9 +212,8 @@ const sendSA = (analytics: AnalyticsDetailType, { mcode = '', options = {} }: an
|
||||
* @param {AnalyticsDetailType} analytics
|
||||
*/
|
||||
const sendLog = (locale: string, analytics: AnalyticsDetailType) => {
|
||||
console.log("🚀 ~ sendLog ~ analytics:", analytics)
|
||||
console.log('🚀 ~ sendLog ~ analytics:', analytics)
|
||||
|
||||
|
||||
// 언어 코드 대문자 변환
|
||||
analytics.eventLocale = locale.toUpperCase()
|
||||
|
||||
@@ -207,7 +222,7 @@ console.log("🚀 ~ sendLog ~ analytics:", analytics)
|
||||
sendGA(analytics, { options: analytics.options })
|
||||
// SA 전송 : actionType, logSourceType 유무로 판별
|
||||
if (
|
||||
analytics.actionType &&
|
||||
analytics.actionType &&
|
||||
analytics.actionType !== ''
|
||||
// analytics.logSourceType &&
|
||||
// analytics.logSourceType !== ''
|
||||
@@ -291,7 +306,7 @@ const sendMarketingScript = ({
|
||||
gaEventName,
|
||||
fbEventName,
|
||||
twEventName,
|
||||
ttEventName
|
||||
ttEventName,
|
||||
}: {
|
||||
gaEventName?: string
|
||||
fbEventName?: string
|
||||
|
||||
191
layers/composables/useGameStart.ts
Normal file
191
layers/composables/useGameStart.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { useTokenValidation } from '#layers/composables/useTokenValidation'
|
||||
import { csrGoStoveLogin } from '#layers/utils/stoveUtil'
|
||||
|
||||
export const useCheckGameStart = () => {
|
||||
// const { tm } = useI18n()
|
||||
const modalStore = useModalStore()
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
const disabledDoubleClick = ref(false) // 연속 호출 클릭 방지
|
||||
const isCheckLauncher = ref(false) // 런처 실행 로딩 상태
|
||||
const isShowDownloadLauncher = ref(false) // 런처 다운로드 표시
|
||||
const customerService = { title: '확인', link: 'https://www.google.com' } //[TODO]
|
||||
|
||||
// 로그인 모달 표시
|
||||
const showLoginModal = () => {
|
||||
modalStore.handleOpenAlert({
|
||||
contentText: '로그인',
|
||||
confirmButtonText: '스토브 로그인',
|
||||
className: 'modal-login',
|
||||
|
||||
confirmButtonEvent: () => {
|
||||
modalStore.handleResetAlert()
|
||||
csrGoStoveLogin()
|
||||
},
|
||||
|
||||
closeButtonEvent: () => {
|
||||
modalStore.handleResetAlert()
|
||||
disabledDoubleClick.value = false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 모든 런처 버튼 비활성화
|
||||
const setLauncherButtonDisabled = (disabled: boolean) => {
|
||||
const launcherButton = document.querySelectorAll(
|
||||
'#btn-launcher'
|
||||
) as NodeListOf<HTMLButtonElement>
|
||||
launcherButton.forEach(button => {
|
||||
button.disabled = disabled
|
||||
})
|
||||
}
|
||||
|
||||
// 런처 실행 로딩 시작 UI 처리
|
||||
const startLoadingForLauncher = () => {
|
||||
if (import.meta.client) {
|
||||
setLauncherButtonDisabled(true)
|
||||
isCheckLauncher.value = true
|
||||
|
||||
setTimeout(() => {
|
||||
if (isCheckLauncher.value) {
|
||||
isShowDownloadLauncher.value = true
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
// 런처 실행 로딩 종료 UI 처리
|
||||
const stopLoadingForLauncher = () => {
|
||||
if (import.meta.client) {
|
||||
setLauncherButtonDisabled(false)
|
||||
isCheckLauncher.value = false
|
||||
isShowDownloadLauncher.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 런처 호출
|
||||
const runLauncher = async () => {
|
||||
// 클라이언트에서만 실행
|
||||
if (!import.meta.client) return
|
||||
|
||||
const gameDataStore = useGameDataStore()
|
||||
|
||||
const stoveGameId = gameDataStore.gameData?.game_id || ''
|
||||
const accessTokenSub = useCookie('SUAT')
|
||||
const nationCookie = useCookie('NNTO').value
|
||||
const localeCookie = useCookie('LOCALE').value
|
||||
const isAgent = true
|
||||
|
||||
disabledDoubleClick.value = true
|
||||
window.stoveJsService = window.stoveJsService || {}
|
||||
|
||||
// 토큰 유효성 체크
|
||||
const { validateToken } = useTokenValidation()
|
||||
const validateTokenResult = await validateToken(accessTokenSub.value || '')
|
||||
|
||||
if (validateTokenResult) {
|
||||
startLoadingForLauncher()
|
||||
window.stoveJsService.launcher
|
||||
.run({
|
||||
gameId: stoveGameId,
|
||||
nation: nationCookie,
|
||||
lang: localeCookie,
|
||||
isSkipMaintenance: isAgent,
|
||||
})
|
||||
.then(() => {
|
||||
// 런처 실행 성공 시 처리
|
||||
stopLoadingForLauncher()
|
||||
})
|
||||
.catch((error: any) => {
|
||||
// 런처 실행 실패시 처리
|
||||
if (error.code !== 601) {
|
||||
stopLoadingForLauncher()
|
||||
}
|
||||
errorHandler(error.code)
|
||||
})
|
||||
.finally(() => {
|
||||
disabledDoubleClick.value = false
|
||||
})
|
||||
} else {
|
||||
showLoginModal()
|
||||
}
|
||||
}
|
||||
|
||||
// PC 클라이언트 설치 전 (에러 처리)
|
||||
const errorHandler = (errorCode: number) => {
|
||||
switch (errorCode) {
|
||||
case 601: // PC 클라이언트 미설치
|
||||
break
|
||||
case 40101: // 로그인 정보 확인 중 오류가 발생했습니다. 재로그인 후 다시 이용해 주세요.
|
||||
modalStore.handleOpenAlert({
|
||||
contentText:
|
||||
'로그인 정보 확인 중 오류가 발생했습니다. 재로그인 후 다시 이용해 주세요.',
|
||||
confirmButtonText: '스토브 로그인',
|
||||
className: 'modal-login',
|
||||
confirmButtonEvent: () => {
|
||||
modalStore.handleResetAlert()
|
||||
csrGoStoveLogin()
|
||||
},
|
||||
})
|
||||
break
|
||||
case 40103: // 로그인 정보가 만료되었습니다. 재로그인 후 다시 이용해 주세요.
|
||||
modalStore.handleOpenAlert({
|
||||
contentText:
|
||||
'로그인 정보가 만료되었습니다. 재로그인 후 다시 이용해 주세요.',
|
||||
confirmButtonText: '스토브 로그인',
|
||||
className: 'modal-login',
|
||||
confirmButtonEvent: () => {
|
||||
modalStore.handleResetAlert()
|
||||
csrGoStoveLogin()
|
||||
},
|
||||
})
|
||||
break
|
||||
case 602:
|
||||
case 504:
|
||||
case 70051:
|
||||
case 500000:
|
||||
case 701:
|
||||
case 70052:
|
||||
default:
|
||||
// 일시적으로 오류가 발생했습니다. 잠시 후 다시 이용해 주세요. 동일한 현상이 계속 발생할 경우 고객센터로 문의해 주세요.
|
||||
modalStore.handleOpenConfirm({
|
||||
contentText:
|
||||
'일시적으로 오류가 발생했습니다. 잠시 후 다시 이용해 주세요. 동일한 현상이 계속 발생할 경우 고객센터로 문의해 주세요.',
|
||||
confirmButtonText: customerService.title,
|
||||
cancelButtonText: '취소',
|
||||
confirmButtonEvent: () => {
|
||||
modalStore.handleResetConfirm()
|
||||
window.open(customerService.link, '_blank')
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 디바운스 설정
|
||||
const debounceHandler = useDebounceFn(runLauncher, 500)
|
||||
|
||||
// 런처 상태 검사
|
||||
const validateLauncher = () => {
|
||||
if (!disabledDoubleClick.value) {
|
||||
debounceHandler()
|
||||
}
|
||||
}
|
||||
|
||||
// 런처 다운로드 함수
|
||||
const downloadLauncher = () => {
|
||||
const stoveClientDownloadUrl = runtimeConfig.public.stoveClientDownloadUrl
|
||||
location.href = stoveClientDownloadUrl
|
||||
disabledDoubleClick.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
disabledDoubleClick, // 연속 클릭 방지
|
||||
isCheckLauncher, // 런처 실행 로딩 상태
|
||||
isShowDownloadLauncher, // 런처 다운로드 표시
|
||||
validateLauncher, // 런처 검사 함수
|
||||
downloadLauncher, // 런처 실행 함수
|
||||
stopLoadingForLauncher, // 런처 실행 로딩 종료 함수
|
||||
}
|
||||
}
|
||||
30
layers/composables/useTokenValidation.ts
Normal file
30
layers/composables/useTokenValidation.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export function useTokenValidation() {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const apiBaseUrl = `${runtimeConfig.public.stoveApiUrl}`
|
||||
const isTokenValid = ref(false)
|
||||
|
||||
const validateToken = async (token: string) => {
|
||||
try {
|
||||
const result = await $fetch<{ code: number }>(
|
||||
`${apiBaseUrl}/auth/v5/user_token/check`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `bearer ${token}`,
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
},
|
||||
}
|
||||
)
|
||||
isTokenValid.value = result.code === 0
|
||||
return isTokenValid.value
|
||||
} catch (error) {
|
||||
isTokenValid.value = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isTokenValid,
|
||||
validateToken,
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
|
||||
try {
|
||||
const pageUrl = getPathAfterLanguage(to.path)
|
||||
console.log('🚀 ~ pageUrl:', pageUrl)
|
||||
|
||||
// pageUrl이 빈값이거나 null이면 /brand로 리다이렉트
|
||||
if (!pageUrl || pageUrl === '' || pageUrl === '/') {
|
||||
|
||||
10
layers/types/Common.ts
Normal file
10
layers/types/Common.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { StoveJsService } from '@/layers/types/Stove'
|
||||
|
||||
export type ClassType = HTMLAttributes['class']
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
stoveJsService?: StoveJsService
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Stove 81 Plug 타입 정의
|
||||
*/
|
||||
export interface IdentityInfo {
|
||||
app_id: string
|
||||
log_source_type: string
|
||||
@@ -36,3 +39,10 @@ export interface ActionInfo {
|
||||
action_param: ActionParam
|
||||
marketing_info?: MarketingInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* Stove JS Service 타입 정의
|
||||
*/
|
||||
export interface StoveJsService {
|
||||
launcher?: any
|
||||
}
|
||||
@@ -55,9 +55,14 @@ const buildRequestOptions = (
|
||||
url: string,
|
||||
options: FetchOptions
|
||||
): FetchRequestOptions => {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const callerId = runtimeConfig.public.stoveGameId || ''
|
||||
const callerDetail = useCookie('sgs_da_uuid').value || ''
|
||||
let stoveGameId = ''
|
||||
let callerDetail = ''
|
||||
|
||||
if (import.meta.client) {
|
||||
const gameDataStore = useGameDataStore()
|
||||
stoveGameId = gameDataStore.gameData?.game_id || ''
|
||||
callerDetail = useCookie('sgs_da_uuid').value || ''
|
||||
}
|
||||
|
||||
const requestOptions: FetchRequestOptions = {
|
||||
method,
|
||||
@@ -75,7 +80,7 @@ const buildRequestOptions = (
|
||||
if (url.includes('.onstove.com') || url.includes('.gate8.com')) {
|
||||
requestOptions.headers = {
|
||||
...requestOptions.headers,
|
||||
'Caller-Id': callerId as string,
|
||||
'Caller-Id': stoveGameId as string,
|
||||
'Caller-Detail': callerDetail as string,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,11 @@ import { csrFormatJWT } from '#layers/utils/formatUtil'
|
||||
*/
|
||||
export const csrGoStoveLogin = () => {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const gameDataStore = useGameDataStore()
|
||||
|
||||
const loginUrl = runtimeConfig.public.stoveLoginUrl
|
||||
const stoveGameId = runtimeConfig.public.stoveGameId
|
||||
const stoveGameNo = runtimeConfig.public.stoveGameNo
|
||||
const stoveGameId = gameDataStore.gameData?.game_id
|
||||
const stoveGameNo = gameDataStore.gameData?.game_code
|
||||
const redirectUrl = encodeURIComponent(location.href)
|
||||
|
||||
const url = `${loginUrl}?redirect_url=${redirectUrl}&inflow_path=${stoveGameId}&game_no=${stoveGameNo}`
|
||||
|
||||
Reference in New Issue
Block a user