feat: GA/SA 추가 완료
This commit is contained in:
15
app/app.vue
15
app/app.vue
@@ -5,14 +5,6 @@ import LoadingLocal from '#layers/components/blocks/loading/Local.vue'
|
||||
import type { GameDataMetaTag, GameDataValue } from '#layers/types/api/gameData'
|
||||
|
||||
const nuxtApp = useNuxtApp()
|
||||
const { gtag, initialize } = useGtag()
|
||||
const { gaId } = useGoogleAnalyticsId()
|
||||
initialize(gaId.value)
|
||||
gtag('event', 'screen_view', {
|
||||
app_name: 'My App',
|
||||
screen_name: 'Home'
|
||||
})
|
||||
|
||||
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { setGameData } = gameDataStore
|
||||
@@ -116,6 +108,13 @@ if (serverGameData) {
|
||||
setGameData(serverGameData)
|
||||
setupMetaData(serverGameData)
|
||||
}
|
||||
const { gtag, initialize } = useGtag()
|
||||
initialize(gameData.value.ga_code)
|
||||
gtag('event', 'screen_view', {
|
||||
app_name: 'My App',
|
||||
screen_name: 'Home'
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -65,6 +65,7 @@ watchEffect(() => {
|
||||
<component
|
||||
:is="registry[template.template_code]?.component"
|
||||
:components="template.components"
|
||||
:page-ver-tmpl-seq="template.page_ver_tmpl_seq"
|
||||
/>
|
||||
</template>
|
||||
</main>
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
import { getResponsiveSrc, getResponsiveClass } from '#layers/utils/dataUtil'
|
||||
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
|
||||
const props = defineProps<{ resourcesData: PageDataResourceGroup }>()
|
||||
const props = defineProps<{ resourcesData: PageDataResourceGroup, pageVerTmplSeq: number }>()
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogData } = useAnalytics()
|
||||
const logData = useAnalyticsLogData(props.resourcesData, props.pageVerTmplSeq)
|
||||
|
||||
const bgStyles = getResponsiveSrc(props.resourcesData?.res_path, {
|
||||
resourcesType: 'bg',
|
||||
@@ -31,9 +34,9 @@ const handleCloseModal = () => {
|
||||
<button
|
||||
v-if="resourcesData"
|
||||
class="bg-cover bg-center bg-no-repeat w-[66px] h-[66px] lg:w-[100px] lg:h-[100px]"
|
||||
:class="getResponsiveClass"
|
||||
:class="getResponsiveClass()"
|
||||
:style="bgStyles"
|
||||
@click="handleVideoPlayClick"
|
||||
@click="() => { handleVideoPlayClick(); sendLog(locale, logData); }"
|
||||
>
|
||||
<span class="sr-only">videoPlay</span>
|
||||
</button>
|
||||
|
||||
@@ -1,11 +1,51 @@
|
||||
import * as amplitude from '@amplitude/analytics-browser'
|
||||
import type { AnalyticsDetailType } 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'
|
||||
|
||||
declare const svcLog: any
|
||||
declare const twq: any
|
||||
declare const ttq: any
|
||||
|
||||
/**
|
||||
* 페이지 데이터와 템플릿 정보를 기반으로 분석용 로그 데이터를 생성하는 composable
|
||||
* @param resourcesData 페이지 리소스 데이터
|
||||
* @param pageVerTmplSeq 템플릿 시퀀스 번호
|
||||
* @returns 분석용 로그 데이터 객체
|
||||
*/
|
||||
export const useAnalyticsLogData = (
|
||||
resourcesData: PageDataResourceGroup,
|
||||
pageVerTmplSeq: number
|
||||
) => {
|
||||
const store = usePageDataStore()
|
||||
const pageData = store.pageData
|
||||
|
||||
if (!pageData) {
|
||||
return ref({} as AnalyticsDetailType)
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
} as unknown as AnalyticsDetailType)
|
||||
|
||||
return logData
|
||||
}
|
||||
|
||||
|
||||
// target에 {XX1, XX2}와 같은 형태가 포함되어 있을 경우 options.clickItem으로부터 값 추출하여 세팅
|
||||
const findValueFromOption = (target: string, { options = {} }: any) => {
|
||||
if (target.includes('{') && target.includes('}')) {
|
||||
@@ -30,12 +70,13 @@ const findValueFromOption = (target: string, { options = {} }: any) => {
|
||||
* @param {object} options
|
||||
*/
|
||||
const sendGA = (analytics: AnalyticsDetailType, { options = {} }: any) => {
|
||||
console.log("🚀 ~ 1111 sendGA ~ analytics:", analytics)
|
||||
try {
|
||||
const { gtag } = useGtag()
|
||||
|
||||
const eventName = findValueFromOption(analytics.event || '', { options })
|
||||
const eventCategory = findValueFromOption(analytics.eventCategory || '', { options })
|
||||
const eventName = analytics.event || ''
|
||||
const eventLocale = analytics.eventLocale || ''
|
||||
const eventCategory = `${analytics.eventCategory}`
|
||||
|
||||
// GA 클릭 이벤트 명 뒤에 언어 값 추가 노출되도록 개발. ex) GNB_자유게시판_KO
|
||||
const eventLabel = `${eventCategory}_${eventLocale}`
|
||||
@@ -58,9 +99,11 @@ const sendGA = (analytics: AnalyticsDetailType, { options = {} }: any) => {
|
||||
* @param {object} options
|
||||
*/
|
||||
const sendSA = (analytics: AnalyticsDetailType, { mcode = '', options = {} }: any) => {
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
|
||||
try {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const gameNo = runtimeConfig.public.stoveGameNo
|
||||
const gameNo = gameData.value.game_code
|
||||
|
||||
const device = useDevice()
|
||||
const deviceType = device.isDesktop ? 'pcweb' : 'mobileweb'
|
||||
@@ -110,6 +153,7 @@ const sendSA = (analytics: AnalyticsDetailType, { mcode = '', options = {} }: an
|
||||
click_area: clickArea,
|
||||
click_sarea: clickSarea,
|
||||
click_item: {
|
||||
click_item: analytics.clickItem,
|
||||
game_no: gameNo,
|
||||
lang_cd: eventLocale,
|
||||
...options?.clickItem
|
||||
@@ -152,20 +196,21 @@ const sendSA = (analytics: AnalyticsDetailType, { mcode = '', options = {} }: an
|
||||
* @param {AnalyticsDetailType} analytics
|
||||
*/
|
||||
const sendLog = (locale: string, analytics: AnalyticsDetailType) => {
|
||||
console.log("🚀 ~ sendLog ~ analytics:", analytics)
|
||||
|
||||
|
||||
// 언어 코드 대문자 변환
|
||||
analytics.eventLocale = locale.toUpperCase()
|
||||
|
||||
if (analytics) {
|
||||
// GA 전송 : eventCategory 유무로 판별
|
||||
if (analytics.eventCategory && analytics.eventCategory !== '') {
|
||||
sendGA(analytics, { options: analytics.options })
|
||||
}
|
||||
sendGA(analytics, { options: analytics.options })
|
||||
// SA 전송 : actionType, logSourceType 유무로 판별
|
||||
if (
|
||||
analytics.actionType &&
|
||||
analytics.actionType !== '' &&
|
||||
analytics.logSourceType &&
|
||||
analytics.logSourceType !== ''
|
||||
analytics.actionType &&
|
||||
analytics.actionType !== ''
|
||||
// analytics.logSourceType &&
|
||||
// analytics.logSourceType !== ''
|
||||
) {
|
||||
sendSA(analytics, { mcode: analytics.mcode, options: analytics.options })
|
||||
}
|
||||
@@ -268,5 +313,5 @@ const sendMarketingScript = ({
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return { sendGA, sendSA, sendLog, sendMarketingScript }
|
||||
return { sendGA, sendSA, sendLog, sendMarketingScript, useAnalyticsLogData }
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import type { GameDataValue } from '#layers/types/api/gameData'
|
||||
import { DEFAULT_COVERAGES } from '@/i18n.config'
|
||||
|
||||
/**
|
||||
* gameData.lang_codes를 기반으로 동적으로 언어 제외 설정을 생성하는 컴포저블
|
||||
*/
|
||||
export const useDynamicI18nRoutes = () => {
|
||||
const gameDataStore = useGameDataStore()
|
||||
|
||||
/**
|
||||
* 현재 gameData의 lang_codes를 기반으로 허용된 언어 목록을 반환
|
||||
*/
|
||||
const getAllowedLangCodes = (): string[] => {
|
||||
const gameData = gameDataStore.gameData
|
||||
// Ensure we only return string values and filter out undefined/null
|
||||
return Array.isArray(gameData?.lang_codes)
|
||||
? gameData.lang_codes.filter(
|
||||
(code): code is string => typeof code === 'string'
|
||||
)
|
||||
: []
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 언어가 허용되는지 확인
|
||||
*/
|
||||
const isLangAllowed = (langCode: string): boolean => {
|
||||
const allowedLangCodes = getAllowedLangCodes()
|
||||
return allowedLangCodes.length === 0 || allowedLangCodes.includes(langCode)
|
||||
}
|
||||
|
||||
/**
|
||||
* defineI18nRoute에서 사용할 수 있는 언어 제외 설정을 생성
|
||||
* @param pagePath - 페이지 경로 (선택사항)
|
||||
* @returns 언어 제외 설정 객체
|
||||
*/
|
||||
const getI18nRouteConfig = (pagePath?: string) => {
|
||||
const allowedLangCodes = getAllowedLangCodes()
|
||||
|
||||
// 허용된 언어가 없으면 모든 언어 허용
|
||||
if (allowedLangCodes.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// 허용된 언어만 포함하는 설정 반환
|
||||
return {
|
||||
locales: allowedLangCodes,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 언어를 제외하는 설정을 생성
|
||||
* @param excludedLangCodes - 제외할 언어 코드 배열
|
||||
* @returns 언어 제외 설정 객체
|
||||
*/
|
||||
const getExcludedLangConfig = (excludedLangCodes: string[]) => {
|
||||
const allowedLangCodes = getAllowedLangCodes()
|
||||
|
||||
// 허용된 언어에서 제외할 언어를 제거
|
||||
const finalAllowedCodes =
|
||||
allowedLangCodes.length > 0
|
||||
? allowedLangCodes.filter(code => !excludedLangCodes.includes(code))
|
||||
: [
|
||||
'en',
|
||||
'ja',
|
||||
'ko',
|
||||
'zh-tw',
|
||||
'fr',
|
||||
'de',
|
||||
'es',
|
||||
'pt',
|
||||
'th',
|
||||
'zh-cn',
|
||||
].filter(code => !excludedLangCodes.includes(code))
|
||||
|
||||
return {
|
||||
locales: finalAllowedCodes,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getAllowedLangCodes,
|
||||
isLangAllowed,
|
||||
getI18nRouteConfig,
|
||||
getExcludedLangConfig,
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
export const useGoogleAnalyticsId = () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
// SSR에서 context에서 가져오기
|
||||
const getGAIdFromServer = () => {
|
||||
return import.meta.server
|
||||
? (nuxtApp.ssrContext?.event.context.googleAnalyticsId ?? null)
|
||||
: null
|
||||
}
|
||||
|
||||
// useState로 SSR/CSR 상태 동기화
|
||||
const gaId = useState<string | null>('googleAnalyticsId', () => {
|
||||
return getGAIdFromServer()
|
||||
})
|
||||
|
||||
return {
|
||||
gaId
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
export default defineNuxtPlugin(() => {
|
||||
const { $i18n } = useNuxtApp()
|
||||
const gameDataStore = useGameDataStore()
|
||||
|
||||
// gameData가 로드되면 언어 제외 설정 적용
|
||||
watchEffect(() => {
|
||||
const gameData = gameDataStore.gameData
|
||||
if (gameData && gameData.lang_codes && gameData.lang_codes.length > 0) {
|
||||
const allowedLangCodes = gameData.lang_codes
|
||||
|
||||
// 현재 설정된 locales에서 허용된 언어만 필터링
|
||||
const availableLocales = $i18n.locales.value.filter((locale: any) =>
|
||||
allowedLangCodes.includes(locale.code)
|
||||
)
|
||||
|
||||
// locales 업데이트
|
||||
// 에러로 인해 주석처리
|
||||
// $i18n.locales.value = availableLocales
|
||||
|
||||
// 현재 locale이 허용되지 않은 경우 기본 locale로 변경
|
||||
if (!allowedLangCodes.includes($i18n.locale.value)) {
|
||||
const defaultLang = allowedLangCodes.includes(
|
||||
gameData.default_lang_code
|
||||
)
|
||||
? gameData.default_lang_code
|
||||
: 'ko'
|
||||
$i18n.locale.value = defaultLang as any
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,30 +0,0 @@
|
||||
export default defineNuxtPlugin(() => {
|
||||
const { $i18n } = useNuxtApp()
|
||||
|
||||
// SSR에서 gameData를 가져와서 언어 제외 설정 적용
|
||||
const gameDataFromServer = import.meta.server
|
||||
? useNuxtApp().ssrContext?.event.context.gameData
|
||||
: null
|
||||
|
||||
if (
|
||||
gameDataFromServer &&
|
||||
gameDataFromServer.lang_codes &&
|
||||
gameDataFromServer.lang_codes.length > 0
|
||||
) {
|
||||
const allowedLangCodes = gameDataFromServer.lang_codes
|
||||
|
||||
// 현재 설정된 locales에서 허용된 언어만 필터링
|
||||
const availableLocales = $i18n.locales.value.filter((locale: any) =>
|
||||
allowedLangCodes.includes(locale.code)
|
||||
)
|
||||
|
||||
// locales 업데이트
|
||||
// 에러로 인해 주석처리
|
||||
// $i18n.locales.value = availableLocales
|
||||
|
||||
// 현재 locale이 허용되지 않은 경우 기본 locale로 변경
|
||||
if (!allowedLangCodes.includes($i18n.locale.value)) {
|
||||
$i18n.locale.value = gameDataFromServer.default_lang_code || 'ko'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,9 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
components: Record<string, any>
|
||||
pageVerTmplSeq: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -28,6 +31,7 @@ const props = defineProps<Props>()
|
||||
<WidgetsVideoPlay
|
||||
v-if="props.components.videoPlay"
|
||||
:resources-data="props.components.videoPlay.groups[0]"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -52,8 +52,8 @@ interface AnalyticsDetailType extends AnalyticsType {
|
||||
opinion?: string
|
||||
devOption?: string
|
||||
// 가공 -----
|
||||
arrViewInfo?: String[]
|
||||
mapClickItem?: Map<String, Object>
|
||||
arrViewInfo?: string[]
|
||||
mapClickItem?: Map<string, object>
|
||||
eventLocale?: string
|
||||
// 옵션 -----
|
||||
mcode?: string
|
||||
@@ -74,10 +74,19 @@ interface ResGetAnalytics {
|
||||
}
|
||||
}
|
||||
|
||||
interface AnalyticsLogDataTracking {
|
||||
view_type: string
|
||||
click_item: string
|
||||
action_type: string
|
||||
click_sarea: string
|
||||
}
|
||||
|
||||
|
||||
export type {
|
||||
AnalyticsType,
|
||||
AnalyticsDetailType,
|
||||
AnalyticsMetaType,
|
||||
AnalyticsLogDataTracking,
|
||||
// [API] Req / Res -----
|
||||
ReqGetAnalytics,
|
||||
ResGetAnalytics
|
||||
|
||||
@@ -75,6 +75,7 @@ export interface PageDataResourceGroup {
|
||||
display?: {
|
||||
text: string
|
||||
}
|
||||
tracking: string // JSON 문자열
|
||||
}
|
||||
|
||||
// 컴포넌트 타입
|
||||
|
||||
Reference in New Issue
Block a user